فهرست منبع

chore: frontend updates

jamesread 10 ماه پیش
والد
کامیت
7345744e41

+ 0 - 1
frontend/index.html

@@ -8,7 +8,6 @@
 
 		<title>OliveTin</title>
 
-		<link rel = "stylesheet" type = "text/css" href = "/style.css" />
 		<link rel = "stylesheet" type = "text/css" href = "/theme.css" />
 		<link rel = "stylesheet" href = "node_modules/@xterm/xterm/css/xterm.css" />
 

+ 1 - 0
frontend/main.js

@@ -1,6 +1,7 @@
 'use strict'
 
 import 'femtocrank/style.css'
+import '/style.css'
 
 import { createClient } from '@connectrpc/connect'
 import { createConnectTransport } from '@connectrpc/connect-web'

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 255 - 415
frontend/package-lock.json


+ 7 - 7
frontend/package.json

@@ -11,8 +11,8 @@
 		"eslint-plugin-node": "^11.1.0",
 		"eslint-plugin-promise": "^4.3.1",
 		"process": "^0.11.10",
-		"stylelint": "^15.6.0",
-		"stylelint-config-standard": "^33.0.0"
+		"stylelint": "^16.23.1",
+		"stylelint-config-standard": "^39.0.0"
 	},
 	"scripts": {
 		"test": "echo \"Error: no test specified\" && exit 1"
@@ -27,16 +27,16 @@
 	],
 	"license": "AGPL-3.0-only",
 	"dependencies": {
-		"@connectrpc/connect": "^2.0.3",
-		"@connectrpc/connect-web": "^2.0.3",
+		"@connectrpc/connect": "^2.1.0",
+		"@connectrpc/connect-web": "^2.1.0",
 		"@hugeicons/core-free-icons": "^1.0.16",
 		"@hugeicons/vue": "^1.0.3",
 		"@vitejs/plugin-vue": "^6.0.1",
 		"@xterm/addon-fit": "^0.10.0",
 		"@xterm/xterm": "^5.5.0",
-		"femtocrank": "^1.2.4",
-		"unplugin-vue-components": "^28.8.0",
-		"vite": "^7.0.6",
+		"picocrank": "^1.3.1",
+		"unplugin-vue-components": "^29.0.0",
+		"vite": "^7.1.4",
 		"vue-router": "^4.5.1"
 	}
 }

+ 12 - 8
frontend/resources/vue/App.vue

@@ -11,10 +11,6 @@
             </button>
         </div>
 
-        <div class="fg1">
-            <Breadcrumbs />
-        </div>
-
 		<div id="banner" v-if="bannerMessage" :style="bannerCss">
 			<p>{{ bannerMessage }}</p>
 		</div>
@@ -65,10 +61,11 @@
 
 <script setup>
 import { ref, onMounted } from 'vue';
-import Sidebar from './components/Sidebar.vue';
+import Sidebar from 'picocrank/vue/components/Sidebar.vue';
 import { HugeiconsIcon } from '@hugeicons/vue'
 import { Menu01Icon } from '@hugeicons/core-free-icons'
 import { UserCircle02Icon } from '@hugeicons/core-free-icons'
+import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
 
 const sidebar = ref(null);
 const username = ref('guest');
@@ -88,22 +85,29 @@ async function requestInit() {
     try {
         const initResponse = await window.client.init({})
 
-        console.log("init response", initResponse)
-
         username.value = initResponse.authenticatedUser
         currentVersion.value = initResponse.currentVersion
 		bannerMessage.value = initResponse.bannerMessage || '';
 		bannerCss.value = initResponse.bannerCss || '';
 
+        sidebar.value.addRouterLink('Actions')
+
         for (const rootDashboard of initResponse.rootDashboards) {
             sidebar.value.addNavigationLink({
                 id: rootDashboard,
+                name: rootDashboard,
                 title: rootDashboard,
                 path: `/dashboards/${rootDashboard}`,
-                icon: '📊'
+                icon: DashboardSquare01Icon,
             })
         }
 
+        sidebar.value.addSeparator()
+        sidebar.value.addRouterLink('Entities')
+        sidebar.value.addRouterLink('Logs')
+        sidebar.value.addRouterLink('Diagnostics')
+
+
 		hasLoaded.value = true;
     } catch (error) {
         console.error("Error initializing client", error)

+ 0 - 1
frontend/resources/vue/Dashboard.vue

@@ -59,7 +59,6 @@ const props = defineProps({
 const dashboard = ref(null)
 
 async function getDashboard() {
-    console.log("getting dashboard", props.title)
     const ret = await window.client.getDashboard({
         title: props.title,
     })

+ 0 - 246
frontend/resources/vue/components/Sidebar.vue

@@ -1,246 +0,0 @@
-<template>
-  <aside :class="{ 'shown': isOpen, 'stuck': isStuck }" class="sidebar">
-    <div class = "flex-row">
-      <h2>Navigation</h2>
-      <div class = "fg1" />
-      <button
-        class="stick-toggle"
-        :aria-pressed="isStuck"
-        :title="isStuck ? 'Unstick sidebar' : 'Stick sidebar'"
-        @click="toggleStick"
-      >
-        <span v-if="isStuck">
-          <HugeiconsIcon :icon="Pin02Icon" width = "1em" height = "1em" />
-        </span>
-        <span v-else>
-          <HugeiconsIcon :icon="PinIcon" width = "1em" height = "1em" />
-        </span>
-      </button>
-    </div>
-
-    <nav class="mainnav">
-      <ul class="navigation-links">
-        <li v-for="link in navigationLinks" :key="link.id" :title="link.title">
-          <router-link :to="link.path" :class="{ active: isActive(link.path) }">
-            <HugeiconsIcon :icon="link.icon" />
-            <span>{{ link.title }}</span>
-          </router-link>
-        </li>
-      </ul>
-
-      <ul class="supplemental-links">
-        <li v-for="link in supplementalLinks" :key="link.id" :title="link.title">
-          <router-link :to="link.path" :class="{ active: isActive(link.path) }">
-            <HugeiconsIcon :icon="link.icon" />
-            <span>{{ link.title }}</span>
-          </router-link>
-        </li>
-      </ul>
-    </nav>
-  </aside>
-</template>
-
-<script setup>
-import { ref, onMounted, getCurrentInstance } from 'vue'
-import { useRoute } from 'vue-router'
-import { HugeiconsIcon } from '@hugeicons/vue'
-import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
-import { LeftToRightListDashIcon } from '@hugeicons/core-free-icons'
-import { Wrench01Icon } from '@hugeicons/core-free-icons'
-import { Pin02Icon } from '@hugeicons/core-free-icons'
-import { PinIcon } from '@hugeicons/core-free-icons'
-import { CellsIcon } from '@hugeicons/core-free-icons'
-
-const isOpen = ref(false)
-const isStuck = ref(false)
-const navigationLinks = ref([
-  {
-    id: 'actions',
-    title: 'Actions',
-    path: '/',
-    icon: DashboardSquare01Icon,
-  }
-])
-
-const supplementalLinks = ref([
-  {
-    id: 'entities',
-	title: 'Entities',
-	path: '/entities',
-	icon: CellsIcon,
-  },
-  {
-    id: 'logs',
-    title: 'Logs',
-    path: '/logs',
-    icon: LeftToRightListDashIcon,
-  },
-  {
-    id: 'diagnostics',
-    title: 'Diagnostics',
-    path: '/diagnostics',
-    icon: Wrench01Icon,
-  }
-])
-
-const route = useRoute()
-
-function toggleStick() {
-  isStuck.value = !isStuck.value
-}
-
-function toggle() {
-  isOpen.value = !isOpen.value
-  isStuck.value = false
-}
-
-function open() {
-  isOpen.value = true
-}
-
-function close() {
-  isOpen.value = false
-  isStuck.value = false
-}
-
-function isActive(path) {
-  return route.path === path
-}
-
-// Method to add navigation links from other components
-function addNavigationLink(link) {
-  link.icon = DashboardSquare01Icon
-
-  const existingIndex = navigationLinks.value.findIndex(l => l.id === link.id)
-  if (existingIndex >= 0) {
-    navigationLinks.value[existingIndex] = { ...link }
-  } else {
-    navigationLinks.value.push({ ...link })
-  }
-}
-
-// Method to add supplemental links from other components
-function addSupplementalLink(link) {
-  const existingIndex = supplementalLinks.value.findIndex(l => l.id === link.id)
-  if (existingIndex >= 0) {
-    supplementalLinks.value[existingIndex] = { ...link }
-  } else {
-    supplementalLinks.value.push({ ...link })
-  }
-}
-
-// Method to remove links
-function removeNavigationLink(linkId) {
-  navigationLinks.value = navigationLinks.value.filter(link => link.id !== linkId)
-}
-
-function removeSupplementalLink(linkId) {
-  supplementalLinks.value = supplementalLinks.value.filter(link => link.id !== linkId)
-}
-
-// Method to clear all links
-function clearNavigationLinks() {
-  navigationLinks.value = []
-}
-
-function clearSupplementalLinks() {
-  supplementalLinks.value = []
-}
-
-// Method to get all links
-function getNavigationLinks() {
-  return [...navigationLinks.value]
-}
-
-function getSupplementalLinks() {
-  return [...supplementalLinks.value]
-}
-
-onMounted(() => {
-  // Make the sidebar globally accessible
-  window.sidebar = {
-    get isOpen() { return isOpen.value },
-    set isOpen(val) { isOpen.value = val },
-    toggle,
-    open,
-    close,
-    addNavigationLink,
-    addSupplementalLink,
-    removeNavigationLink,
-    removeSupplementalLink,
-    clearNavigationLinks,
-    clearSupplementalLinks,
-    getNavigationLinks,
-    getSupplementalLinks
-  }
-})
-
-defineExpose({
-  isOpen,
-  navigationLinks,
-  supplementalLinks,
-  toggle,
-  open,
-  close,
-  isActive,
-  addNavigationLink,
-  addSupplementalLink,
-  removeNavigationLink,
-  removeSupplementalLink,
-  clearNavigationLinks,
-  clearSupplementalLinks,
-  getNavigationLinks,
-  getSupplementalLinks
-})
-</script>
-
-<style scoped>
-
-.active {
-  text-decoration: underline;
-}
-
-li {
-  margin: 0;
-  padding: 0;
-}
-.navigation-links a,
-.supplemental-links a {
-  display: flex;
-  align-items: center;
-  gap: 0.75rem;
-  padding: 0.75rem 1rem;
-  color: #333;
-  transition: background-color 0.2s ease;
-  border-left: 3px solid transparent;
-}
-
-.navigation-links a:hover,
-.supplemental-links a:hover {
-  background: #f8f9fa;
-  color: #007bff;
-}
-
-.icon {
-  font-size: 1.2em;
-  width: 1.5rem;
-  text-align: center;
-}
-
-.supplemental-links {
-  border-top: 1px solid #eee;
-  margin-top: 1rem;
-}
-
-/* Responsive design */
-@media (max-width: 768px) {
-  .sidebar {
-    width: 100%;
-    left: -100%;
-  }
-  
-  .sidebar.shown {
-    left: 0;
-  }
-}
-</style> 

+ 19 - 5
frontend/resources/vue/router.js

@@ -1,12 +1,17 @@
 import { createRouter, createWebHistory } from 'vue-router'
 
+import { Wrench01Icon } from '@hugeicons/core-free-icons'
+import { LeftToRightListDashIcon } from '@hugeicons/core-free-icons'
+import { CellsIcon } from '@hugeicons/core-free-icons'
+import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
+
 const routes = [
   {
     path: '/',
-    name: 'Home',
+    name: 'Actions',
     component: () => import('./Dashboard.vue'),
     props: { title: 'default' },
-    meta: { title: 'OliveTin - Dashboard' }
+    meta: { title: 'Actions', icon: DashboardSquare01Icon }
   },
   {
     path: '/dashboards/:title',
@@ -26,13 +31,19 @@ const routes = [
     path: '/logs',
     name: 'Logs',
     component: () => import('./views/LogsListView.vue'),
-    meta: { title: 'OliveTin - Logs' }
+    meta: { 
+      title: 'OliveTin - Logs',
+      icon: LeftToRightListDashIcon
+    }
   },
   {
     path: '/entities',
     name: 'Entities',
     component: () => import('./views/EntitiesView.vue'),
-    meta: { title: 'OliveTin - Entities' }
+    meta: { 
+      title: 'OliveTin - Entities',
+      icon: CellsIcon
+    }
   },
   {
     path: '/entity-details/:entityType/:entityKey',
@@ -64,7 +75,10 @@ const routes = [
     path: '/diagnostics',
     name: 'Diagnostics',
     component: () => import('./views/DiagnosticsView.vue'),
-    meta: { title: 'OliveTin - Diagnostics' }
+    meta: { 
+      title: 'OliveTin - Diagnostics',
+      icon: Wrench01Icon
+    }
   },
   {
     path: '/login',

+ 30 - 45
frontend/resources/vue/views/DiagnosticsView.vue

@@ -1,55 +1,40 @@
 <template>
-  <section>
-    <div class="section-header">
-      <h2>Get support</h2>
+  <Section title = "Get support">
+    <p>If you are having problems with OliveTin and want to raise a support request, it would be very helpful to include a sosreport from this page.
+    </p>
+    <ul>
+      <li>
+        <a href="https://docs.olivetin.app/sosreport.html" target="_blank">sosreport Documentation</a>
+      </li>
+      <li>
+        <a href = "https://docs.olivetin.app/troubleshooting/wheretofindhelp.html" target="_blank">Where to find help</a>
+      </li>
+    </ul>
+  </Section>
+
+  <Section title = "SSH">
+    <dl>
+      <dt>Found Key</dt>
+      <dd>{{ diagnostics.sshFoundKey || '?' }}</dd>
+      <dt>Found Config</dt>
+      <dd>{{ diagnostics.sshFoundConfig || '?' }}</dd>
+    </dl>
+  </Section>
+
+  <Section title = "SOS Report">
+    <p>This section allows you to generate a detailed report of your configuration and environment. It is a good idea to include this when raising a support request.</p>
+
+    <div role="toolbar">
+      <button @click="generateSosReport" :disabled="loading" class = "good">Generate SOS Report</button>
     </div>
-    <div class="section-content">
-      <p>If you are having problems with OliveTin and want to raise a support request, it would be very helpful to include a sosreport from this page.
-      </p>
-      <ul>
-        <li>
-          <a href="https://docs.olivetin.app/sosreport.html" target="_blank">sosreport Documentation</a>
-        </li>
-        <li>
-          <a href = "https://docs.olivetin.app/troubleshooting/wheretofindhelp.html" target="_blank">Where to find help</a>
-        </li>
-      </ul>
-    </div>
-  </section>
 
-  <section>
-    <div class="section-header">
-      <h2>SSH</h2>
-    </div>
-    <div class="section-content">
-      <dl>
-        <dt>Found Key</dt>
-        <dd>{{ diagnostics.sshFoundKey || '?' }}</dd>
-        <dt>Found Config</dt>
-        <dd>{{ diagnostics.sshFoundConfig || '?' }}</dd>
-      </dl>
-    </div>
-  </section>
-
-  <section>
-    <div class="section-header">
-      <h2>SOS Report</h2>
-    </div>
-    <div class="section-content">
-
-      <p>This section allows you to generate a detailed report of your configuration and environment. It is a good idea to include this when raising a support request.</p>
-
-      <div role="toolbar">
-        <button @click="generateSosReport" :disabled="loading" class = "good">Generate SOS Report</button>
-      </div>
-
-      <textarea v-model="sosReport" readonly style="flex: 1; min-height: 200px; resize: vertical;"></textarea>
-    </div>
-  </section>
+    <textarea v-model="sosReport" readonly style="flex: 1; min-height: 200px; resize: vertical;"></textarea>
+  </Section>
 </template>
 
 <script setup>
 import { ref, onMounted } from 'vue'
+import Section from 'picocrank/vue/components/Section.vue'
 
 const diagnostics = ref({})
 const loading = ref(false)

+ 5 - 8
frontend/resources/vue/views/EntitiesView.vue

@@ -1,17 +1,13 @@
 <template>
-	<section class = "with-header-and-content" v-if="entityDefinitions.length === 0">
+	<Section class = "with-header-and-content" v-if="entityDefinitions.length === 0" title="Loading entity definitions...">
 		<div class = "section-header">
 			<h2 class="loading-message">
 				Loading entity definitions...
 			</h2>
 		</div>
-	</section>
+	</Section>
 	<template v-else>
-		<section v-for="def in entityDefinitions" :key="def.name" class="with-header-and-content">
-			<div class = "section-header">
-				<h2>Entity: {{ def.title }}</h2>
-			</div>
-
+		<Section v-for="def in entityDefinitions" :key="def.name" :title="'Entity: ' + def.title ">
 			<div class = "section-content">
 				<p>{{ def.instances.length }} instances.</p>
 
@@ -32,12 +28,13 @@
 					</li>
 				</ul>
 			</div>
-		</section>
+		</Section>
 	</template>
 </template>
 
 <script setup>
 	import { ref, onMounted } from 'vue'
+	import Section from 'picocrank/vue/components/Section.vue'
 
 	const entityDefinitions = ref([])
 

+ 5 - 8
frontend/resources/vue/views/EntityDetailsView.vue

@@ -1,19 +1,16 @@
 <template>
-	<section class="with-header-and-content">
-		<div class="section-header">
-			<h2>Entity Details</h2>
-		</div>
-
-		<div class="section-content">
+	<Section title="Entity Details">
+		<div>
 			<p v-if="!entityDetails">Loading entity details...</p>
 			<p v-else-if="!entityDetails.title">No details available for this entity.</p>
 			<p v-else>{{ entityDetails.title }}</p>
 		</div>
-	</section>
+	</Section>
 </template>
 
 <script setup>
-	import { ref, onMounted, onBeforeUnmount } from 'vue'
+	import { ref, onMounted } from 'vue'
+	import Section from 'picocrank/vue/components/Section.vue'
 
 	const entityDetails = ref(null)
 

+ 12 - 18
frontend/resources/vue/views/ExecutionView.vue

@@ -1,23 +1,16 @@
 <template>
-	<section class="with-header-and-content">
-		<div class="section-header">
-			<h2>Execution Results: {{ title }}</h2>
-
+	<Section :title="'Execution Results: ' + title">
+    <template #toolbar>
 			<button @click="toggleSize" title="Toggle dialog size">
 				<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
 					<path fill="currentColor"
 						  d="M3 3h6v2H6.462l4.843 4.843l-1.415 1.414L5 6.367V9H3zm0 18h6v-2H6.376l4.929-4.928l-1.415-1.414L5 17.548V15H3zm12 0h6v-6h-2v2.524l-4.867-4.866l-1.414 1.414L17.647 19H15zm6-18h-6v2h2.562l-4.843 4.843l1.414 1.414L19 6.39V9h2z" />
 				</svg>
 			</button>
+    </template>
 
-		</div>
-		<div class="section-content">
-			<div v-if="logEntry">
-				<div class="action-header padded-content" style="float: right">
-					<span class="icon" role="img" v-html="icon"></span>
-				</div>
-
-				<dl>
+		<div v-if="logEntry" class = "flex-row">
+				<dl class = "fg1">
 					<dt>Duration</dt>
 					<dd><span v-html="duration"></span></dd>
 
@@ -26,9 +19,10 @@
 						<ActionStatusDisplay :log-entry="logEntry" />
 					</dd>
 				</dl>
-			</div>
+        <span class="icon" role="img" v-html="icon" style = "align-self: start"></span>
+    </div>
 
-			<div ref="xtermOutput"></div>
+    <div ref="xtermOutput"></div>
 
 			<br />
 
@@ -49,13 +43,14 @@
 						Kill
 					</button>
 				</div>
-			</div>
-	</section>
+
+	</Section>
 </template>
 
 <script setup>
-	import { ref, reactive, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
+	import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
 import ActionStatusDisplay from '../components/ActionStatusDisplay.vue'
+import Section from 'picocrank/vue/components/Section.vue'
 import { OutputTerminal } from '../../../js/OutputTerminal.js'
 import { HugeiconsIcon } from '@hugeicons/vue'
 import { WorkoutRunIcon, Cancel02Icon, ArrowLeftIcon } from '@hugeicons/core-free-icons'
@@ -66,7 +61,6 @@ const router = useRouter()
 
 // Refs for DOM elements
 const xtermOutput = ref(null)
-const dialog = ref(null)
 
 const props = defineProps({
   executionTrackingId: {

+ 7 - 13
frontend/resources/vue/views/LogsListView.vue

@@ -1,9 +1,6 @@
 <template>
-  <section>
-    <div class="section-header">
-      <h2>Logs</h2>
-
-      <div>
+  <Section title="Logs" :padding="false">
+      <template #toolbar>
         <label class="input-with-icons">
           <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
             <path fill="currentColor"
@@ -17,12 +14,9 @@
             </svg>
           </button>
         </label>
-      </div>
-
-    </div>
+      </template>
 
-    <div class="section-content">
-      <p>This is a list of logs from actions that have been executed. You can filter the list by action title.</p>
+      <p class = "padding">This is a list of logs from actions that have been executed. You can filter the list by action title.</p>
       <div v-show="filteredLogs.length > 0">
         <table class="logs-table">
           <thead>
@@ -60,7 +54,7 @@
           </tbody>
         </table>
 
-        <Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" @page-change="handlePageChange"
+        <Pagination :pageSize="pageSize" :total="totalCount" :currentPage="currentPage" @page-change="handlePageChange" class = "padding"
           @page-size-change="handlePageSizeChange" itemTitle="execution logs" />
       </div>
 
@@ -68,13 +62,13 @@
         <p>There are no logs to display.</p>
         <router-link to="/">Return to index</router-link>
       </div>
-    </div>
-  </section>
+  </Section>
 </template>
 
 <script setup>
 import { ref, computed, onMounted } from 'vue'
 import Pagination from '../components/Pagination.vue'
+import Section from 'picocrank/vue/components/Section.vue'
 
 const logs = ref([])
 const searchText = ref('')

+ 0 - 4
frontend/style.css

@@ -92,10 +92,6 @@ div.buttons button svg {
 	vertical-align: middle;
 }
 
-section .section-content {
-	padding-top: 0;
-}
-
 footer {
 	font-size: small;
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است