Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add storage managment section + disks managment page #585

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ ca.crt
src/css/style.css

src/dist

vite.config.local.ts
1 change: 1 addition & 0 deletions app/.prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist/
vite.config.local.ts
1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
},
"devDependencies": {
"@types/uuid": "^10.0.0",
"@types/node": "^22.13.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-prettier": "^10.1.0",
"@vue/eslint-config-typescript": "^14.1.4",
Expand Down
25 changes: 24 additions & 1 deletion app/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,9 @@
"logs": "no logs | log | logs",
"permissions": "no permissions | permission | permissions",
"services": "no services | service | services",
"users": "no users | user | users"
"users": "no users | user | users",
"disk": "no disk | disk | disks",
"inserted_disk": "no inserted disk | inserted disk | inserted disks"
},
"items_verbose_count": "There are {items}. | There is 1 {items}. | There are {items}.",
"items_verbose_items_left": "There are {items} left. | There is 1 {items} left. | There are {items} left.",
Expand Down Expand Up @@ -477,6 +479,27 @@
"status": "Status",
"stop": "Stop",
"stopped": "Stopped",
"storage": "Storage",
"storage_disks": {
"category_name": "Hard drives",
"infos": {
"usb_icon_alt": "This device is a connected to USB",
"ejectable_icon_alt": "This device is ejectable",
"smart_status_icon_alt": {
"SANE": "This device is sane",
"CRITICAL": "This device is in a critical state",
"UNKNOWN": "The status of this device is unknown"
},
"serial": "Serial:",
"serial_unknown": "Unknown",
"size": "Size:",
"type": "Type:"
},
"infos_meta": {
"usb": "USB device",
"ejectable": "This device can be ejected"
}
},
"system": "System",
"system_apps_nothing": "All apps are up to date!",
"system_packages_nothing": "All system packages are up to date!",
Expand Down
22 changes: 22 additions & 0 deletions app/src/router/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,28 @@ const routes: RouteRecordRaw[] = [
},
},

/* ────────────╮
│ STORAGE │
╰──────────── */
{
name: 'storage',
path: '/storage',
component: () => import('@/views/storage/StorageList.vue'),
meta: {
args: { trad: 'storage' },
breadcrumb: ['storage'],
},
},
{
name: 'storage-disks',
path: '/storage/disks',
component: () => import('@/views/storage/DiskList.vue'),
meta: {
args: { trad: 'storage_disks.category_name' },
breadcrumb: ['storage', 'storage-disks'],
},
},

/* ────────────╮
│ DIAGNOSIS │
╰──────────── */
Expand Down
12 changes: 12 additions & 0 deletions app/src/types/core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,15 @@ export type Certificate = {
ACME_eligible: boolean
has_wildcards: boolean
}

export type Disk = {
name: string
model: string
serial: string
removable: boolean
size: string | number
connection_bus: string
type: "HDD" | "SSD"
rpm?: number
smartStatus: "SANE" | "CRITICAL" | "UNKNOWN"
}
1 change: 1 addition & 0 deletions app/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const menu = [
},
{ routeName: 'domain-list', icon: 'globe', translation: 'domains' },
{ routeName: 'app-list', icon: 'cubes', translation: 'applications' },
{ routeName: 'storage', icon: 'hdd-o', translation: 'storage' },
{ routeName: 'update', icon: 'refresh', translation: 'system_update' },
{ routeName: 'tool-list', icon: 'wrench', translation: 'tools' },
{
Expand Down
125 changes: 125 additions & 0 deletions app/src/views/storage/DiskList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<template>
<YAlert v-if="!hasDisks" alert icon="exclamation-triangle" variant="warning">
{{ $t('items_verbose_count', { items: $t('items.inserted_disk', 0) }, 0) }}
</YAlert>
<BContainer v-else>
<BRow>
<BCol v-for="disk in disks" :key="disk.serial" :lg="4">
<BCard>
<BCardTitle class="clearfix">
<span v-bind="smartStatusBadgeAttrs(disk)" />{{ disk.model }}
<span class="disks-icons float-end">
<i
v-if="disk.connection_bus == 'usb'"
class="fa fa-usb"
role="img"
:title="$t('storage_disks.infos.usb_icon_alt')"
></i>
<i
v-if="disk.removable"
class="fa fa-eject"
role="img"
:title="$t('storage_disks.infos.ejectable_icon_alt')"
></i>
</span>
</BCardTitle>
<section class="disk-infos">
<ul>
<li>
<strong>{{ $t('storage_disks.infos.serial') }} </strong>
<span v-if="disk.serial.length >= 0">{{ disk.serial }}</span>
<em v-else>{{ $t('storage_disks.infos.serial_unknown') }}</em>
</li>
<li>
<strong>{{ $t('storage_disks.infos.size') }}</strong>
{{ disk.size }}
</li>
<li>
<strong>{{ $t('storage_disks.infos.type') }}</strong>
{{ disk.type }}<template v-if="disk.rpm">
({{ disk.rpm }} RPM)</template>
</li>
</ul>
</section>
</BCard>
</BCol>
</BRow>
</BContainer>
</template>

<script setup lang="ts">
import { ref, type Ref, computed } from 'vue'
import api from '@/api'
import type { Disk } from '@/types/core/api.ts'
import {
BContainer,
BRow,
BCol,
BCard,
BCardTitle,
} from 'bootstrap-vue-next'
import { useI18n } from "vue-i18n"

const { t } = useI18n()

const disks: Ref<Disk[]> = ref([])
const hasDisks = computed(
() => Number.isInteger(disks.value.length) && disks.value.length > 0,
)

function smartStatusBadgeAttrs(disk: Disk) {
const common = {title: t(`storage_disks.infos.smart_status_icon_alt.${disk.smartStatus}`)}
switch (disk.smartStatus) {
case 'SANE':
return {class: "status bg-success", ...common}
case 'CRITICAL':
return {class: "status bg-danger", ...common}
default:
return {class: "status bg-secondary", ...common}
}
}

api
.fetch<{
disks: Disk[]
}>({ uri: 'storage/disk/list?with_info&human_readable_size' })
.then((result) => {
disks.value = result.disks
})
</script>

<style lang="scss" scoped>
.row {
row-gap: 1rem;
}

.disks-icons > * {
margin-right: 0.5rem;
margin-left: 0.5rem;

&:first-child {
margin-left: 0;
}

&:last-child {
margin-right: 0;
}
}

.disk-infos ul {
list-style: none;
padding-left: 1rem;
&,
& * {
white-space: nowrap;
}
}

.status {
border-radius: 100%;
display: inline-block;
width: 1rem;
height: 1rem;
margin-right: 0.5rem;
}
</style>
25 changes: 25 additions & 0 deletions app/src/views/storage/StorageList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<BListGroup class="menu-list">
<BListGroupItem
v-for="item in menu"
:key="item.routeName"
:to="{ name: item.routeName }"
>
<YIcon :iname="item.icon" class="lg ml-1" />
<h4>{{ $t(item.translation) }}</h4>
<YIcon iname="chevron-right" class="lg fs-sm ml-auto" />
</BListGroupItem>
</BListGroup>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const menu = ref([
{
routeName: 'storage-disks',
icon: 'hdd-o',
translation: 'storage_disks.category_name',
},
])
</script>
13 changes: 9 additions & 4 deletions app/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fileURLToPath, URL } from 'url'
import { defineConfig, loadEnv } from 'vite'
import { defineConfig, loadEnv, UserConfig } from 'vite'
import fs from 'fs'
import createVuePlugin from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
Expand All @@ -13,12 +13,12 @@ const supportedDatefnsLocales = Object.entries(supportedLocales).map(
},
)

export default defineConfig(({ mode }) => {
export default defineConfig(async ({ mode }) => {
// Load env file based on `mode` in the current working directory.
// Set the third parameter to '' to load all env regardless of the `VITE_` prefix.
const env = loadEnv(mode, process.cwd())

const config = {
let config: UserConfig = {
define: {
// fake process.env for some deps
'process.env': {},
Expand Down Expand Up @@ -102,7 +102,7 @@ export default defineConfig(({ mode }) => {
}
}
// mode dev
return {
config = {
...config,
server: {
...(env.VITE_LOCAL === 'true'
Expand Down Expand Up @@ -132,4 +132,9 @@ export default defineConfig(({ mode }) => {
},
},
}
try {
return (await import("./vite.config.local")).overrideConfig(config)
} catch {
return config
}
Comment on lines +135 to +139
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows local override of Vite's config. In my case, it allows proxying YNH API requests to my dev API instead of the stable one.

})
12 changes: 12 additions & 0 deletions app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,13 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==

"@types/node@^22.13.4":
version "22.13.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.4.tgz#3fe454d77cd4a2d73c214008b3e331bfaaf5038a"
integrity sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==
dependencies:
undici-types "~6.20.0"

"@types/showdown@^2.0.1":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@types/showdown/-/showdown-2.0.6.tgz#3d7affd5f971b4a17783ec2b23b4ad3b97477b7e"
Expand Down Expand Up @@ -1796,6 +1803,11 @@ ufo@^1.5.4:
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754"
integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==

undici-types@~6.20.0:
version "6.20.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==

unplugin-vue-components@^0.28.0:
version "0.28.0"
resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-0.28.0.tgz#36108e4808c6e6bb36403613d3084338e2f6a4c0"
Expand Down