Skip to content

Commit

Permalink
Add storage managment section + disks managment page
Browse files Browse the repository at this point in the history
  • Loading branch information
christophehenry committed Feb 16, 2025
1 parent 3d907a0 commit ba8ac84
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 15 deletions.
3 changes: 1 addition & 2 deletions app/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ declare module 'vue' {
BSkeleton: typeof import('./src/components/globals/skeletons/BSkeleton.vue')['default']
BSkeletonWrapper: typeof import('./src/components/globals/skeletons/BSkeletonWrapper.vue')['default']
BTab: typeof import('bootstrap-vue-next/components/BTabs')['BTab']
BTable: typeof import('bootstrap-vue-next/components/BTable')['BTable']
BTableLite: typeof import('bootstrap-vue-next/components/BTable')['BTableLite']
BTabs: typeof import('bootstrap-vue-next/components/BTabs')['BTabs']
BToast: typeof import('bootstrap-vue-next/components/BToast')['BToast']
BToastOrchestrator: typeof import('bootstrap-vue-next/components/BToast')['BToastOrchestrator']
Expand Down Expand Up @@ -100,7 +100,6 @@ declare module 'vue' {
ModalOverlay: typeof import('./src/components/modals/ModalOverlay.vue')['default']
ModalReconnecting: typeof import('./src/components/modals/ModalReconnecting.vue')['default']
ModalWaiting: typeof import('./src/components/modals/ModalWaiting.vue')['default']
ModalWarning: typeof import('./src/components/modals/ModalWarning.vue')['default']
QueryHeader: typeof import('./src/components/QueryHeader.vue')['default']
ReadOnlyAlertItem: typeof import('./src/components/globals/formItems/ReadOnlyAlertItem.vue')['default']
RecursiveListGroup: typeof import('./src/components/RecursiveListGroup.vue')['default']
Expand Down
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
50 changes: 39 additions & 11 deletions app/src/helpers/commons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function objectGet<
* @param value - Anything.
*/
export function isEmptyValue(
value: any,
value: any
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
): value is null | undefined | '' | [] | {} {
if (typeof value === 'number' || typeof value === 'boolean') return false
Expand All @@ -68,11 +68,11 @@ export function filterObject<T extends Obj>(
filter: (
entries: [string, any],
index: number,
array: [string, any][],
) => boolean,
array: [string, any][]
) => boolean
) {
return Object.fromEntries(
Object.entries(obj).filter((...args) => filter(...args)),
Object.entries(obj).filter((...args) => filter(...args))
)
}

Expand All @@ -81,13 +81,13 @@ export function filterObject<T extends Obj>(
*/
export function arrayDiff<T extends string>(
arr1: T[] = [],
arr2: T[] = [],
arr2: T[] = []
): T[] {
return arr1.filter((item) => !arr2.includes(item))
}

export function joinOrNull(
value: any[] | string | null | undefined,
value: any[] | string | null | undefined
): string | null {
if (Array.isArray(value) && value.length) {
return value.join(i18n.global.t('words.separator'))
Expand Down Expand Up @@ -127,7 +127,7 @@ export function randint(min: number, max: number) {
*/
export function getFileContent(
file: File,
{ base64 = false } = {},
{ base64 = false } = {}
): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader()
Expand All @@ -147,7 +147,7 @@ export function getKeys<T extends Obj, K extends (keyof T)[]>(obj: T): K {
}

export function toEntries<T extends Record<PropertyKey, unknown>>(
obj: T,
obj: T
): { [K in keyof T]: [K, T[K]] }[keyof T][] {
return Object.entries(obj) as { [K in keyof T]: [K, T[K]] }[keyof T][]
}
Expand All @@ -160,7 +160,7 @@ export function fromEntries<

export function pick<T extends Obj, K extends (keyof T)[]>(
obj: T,
keys: K,
keys: K
): Pick<T, K[number]> {
return Object.fromEntries(keys.map((key) => [key, obj[key]])) as Pick<
T,
Expand All @@ -170,11 +170,39 @@ export function pick<T extends Obj, K extends (keyof T)[]>(

export function omit<T extends Obj, K extends (keyof T)[]>(
obj: T,
keys: K,
keys: K
): Omit<T, K[number]> {
return Object.fromEntries(
Object.keys(obj)
.filter((key) => !keys.includes(key))
.map((key) => [key, obj[key]]),
.map((key) => [key, obj[key]])
) as Omit<T, K[number]>
}

const humanSizeUnits = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']

export function humanSize(size: number) {
function format(num: number|bigint, unit:string): string {
return `${num.toLocaleString(undefined, { maximumFractionDigits: 2 })}${unit}B`
}

if (size < 1024) {
return format(size, "")
}

const bigSize = BigInt(size)


let lowerBound = 0n

for (let i = 0; i < humanSizeUnits.length; i++) {
lowerBound = 1n << (BigInt(i) + 1n) * 10n
const upperBound = 1n << (BigInt(i + 1) + 1n) * 10n

if(bigSize < upperBound) {
return format(bigSize / lowerBound, humanSizeUnits[i])
}
}

return format(bigSize / lowerBound, humanSizeUnits[humanSizeUnits.length - 1])
}
13 changes: 12 additions & 1 deletion app/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,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 @@ -561,6 +563,15 @@
"start": "Start",
"status": "Status",
"stop": "Stop",
"storage": "Storage",
"storage_disks": {
"category_name": "Hard drives",
"table": {
"disks_name": "Name",
"disks_serial": "Serial",
"disks_size": "Size"
}
},
"system": "System",
"system_apps_nothing": "All apps are up to date!",
"system_packages_nothing": "All system packages are up to date!",
Expand Down
4 changes: 3 additions & 1 deletion app/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,9 @@
"backups": "aucune sauvegarde | sauvegarde | sauvegardes",
"apps": "aucune application | app | apps",
"permissions": "pas d'autorisations | permission | autorisations",
"items": "aucun article | article | articles"
"items": "aucun article | article | articles",
"disk": "aucun disque | disque | disques",
"inserted_disk": "aucun disque inséré | disque inséré | disques insérés"
},
"history": {
"methods": {
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
15 changes: 15 additions & 0 deletions app/src/types/core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,18 @@ export type Certificate = {
ACME_eligible: boolean
has_wildcards: boolean
}

export type APIDiskResult = {
name: string
model: string
serial: string
removable: boolean
size: number
connection_bus: string
type: "HDD" | "SSD",
rpm?: number
}

export type Disk = APIDiskResult & {
size: string
}
1 change: 1 addition & 0 deletions app/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const menu = [
{ routeName: 'user-list', icon: 'users', translation: 'users' },
{ 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
40 changes: 40 additions & 0 deletions app/src/views/storage/DiskList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<YAlert
v-if="!hasDisks"
alert
icon="exclamation-triangle"
variant="warning"
>
{{ $t("items_verbose_count", { items: $t('items.inserted_disk', 0) }, 0) }}
</YAlert>
<BRow v-else>
<YListItem
v-for="disk in disks"
:key="disk.serial"
:to="{ name: 'app-info', params: { id: disk.serial } }"
:label="disk.name"
:sublabel="disk.size"
:description="disk.model"
/>
</BRow>
</template>

<script setup lang="ts">
import { humanSize } from '@/helpers/commons'
import { ref, type Ref, computed } from 'vue'
import api from '@/api'
import type { APIDiskResult, Disk } from '@/types/core/api.ts'
import YListItem from '@/components/globals/YListItem.vue'
const disks: Ref<Disk[]> = ref([])
const hasDisks = computed(() => disks.value.length > 0)
api.fetch<{ disks: APIDiskResult[] }>({ uri: 'storage/disk/list' }).then((result) => {
disks.value = result.disks.map((disk) => {
return {
...disk,
size: humanSize(disk.size),
}
})
})
</script>
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>
12 changes: 12 additions & 0 deletions app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,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 @@ -1790,6 +1797,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

0 comments on commit ba8ac84

Please sign in to comment.