Merge pull request 'UI: display block devices' (#1635) from hsjobeki/clan-core:hsjobeki-main into main
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
|
from clan_cli.nix import nix_shell, run_no_stdout
|
||||||
|
|
||||||
from . import API
|
from . import API
|
||||||
|
|
||||||
@@ -81,3 +83,46 @@ def get_directory(current_path: str) -> Directory:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return directory
|
return directory
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BlkInfo:
|
||||||
|
name: str
|
||||||
|
rm: str
|
||||||
|
size: str
|
||||||
|
ro: bool
|
||||||
|
mountpoints: list[str]
|
||||||
|
type_: Literal["disk"]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Blockdevices:
|
||||||
|
blockdevices: list[BlkInfo]
|
||||||
|
|
||||||
|
|
||||||
|
def blk_from_dict(data: dict) -> BlkInfo:
|
||||||
|
return BlkInfo(
|
||||||
|
name=data["name"],
|
||||||
|
rm=data["rm"],
|
||||||
|
size=data["size"],
|
||||||
|
ro=data["ro"],
|
||||||
|
mountpoints=data["mountpoints"],
|
||||||
|
type_=data["type"], # renamed here
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@API.register
|
||||||
|
def show_block_devices() -> Blockdevices:
|
||||||
|
"""
|
||||||
|
Abstract api method to show block devices.
|
||||||
|
It must return a list of block devices.
|
||||||
|
"""
|
||||||
|
cmd = nix_shell(["nixpkgs#util-linux"], ["lsblk", "--json"])
|
||||||
|
proc = run_no_stdout(cmd)
|
||||||
|
res = proc.stdout.strip()
|
||||||
|
|
||||||
|
blk_info: dict[str, Any] = json.loads(res)
|
||||||
|
|
||||||
|
return Blockdevices(
|
||||||
|
blockdevices=[blk_from_dict(device) for device in blk_info["blockdevices"]]
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { MachineListView } from "./routes/machines/view";
|
|||||||
import { colors } from "./routes/colors/view";
|
import { colors } from "./routes/colors/view";
|
||||||
import { clan } from "./routes/clan/view";
|
import { clan } from "./routes/clan/view";
|
||||||
import { HostList } from "./routes/hosts/view";
|
import { HostList } from "./routes/hosts/view";
|
||||||
|
import { BlockDevicesView } from "./routes/blockdevices/view";
|
||||||
|
|
||||||
export type Route = keyof typeof routes;
|
export type Route = keyof typeof routes;
|
||||||
|
|
||||||
@@ -22,6 +23,11 @@ export const routes = {
|
|||||||
label: "hosts",
|
label: "hosts",
|
||||||
icon: "devices_other",
|
icon: "devices_other",
|
||||||
},
|
},
|
||||||
|
blockdevices: {
|
||||||
|
child: BlockDevicesView,
|
||||||
|
label: "blockdevices",
|
||||||
|
icon: "devices_other",
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
child: colors,
|
child: colors,
|
||||||
label: "Colors",
|
label: "Colors",
|
||||||
|
|||||||
61
pkgs/webview-ui/app/src/routes/blockdevices/view.tsx
Normal file
61
pkgs/webview-ui/app/src/routes/blockdevices/view.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { route } from "@/src/App";
|
||||||
|
import { OperationResponse, pyApi } from "@/src/api";
|
||||||
|
import { Component, For, Show, createEffect, createSignal } from "solid-js";
|
||||||
|
|
||||||
|
type DevicesModel = Extract<
|
||||||
|
OperationResponse<"show_block_devices">,
|
||||||
|
{ status: "success" }
|
||||||
|
>["data"]["blockdevices"];
|
||||||
|
|
||||||
|
export const BlockDevicesView: Component = () => {
|
||||||
|
const [devices, setServices] = createSignal<DevicesModel>();
|
||||||
|
|
||||||
|
pyApi.show_block_devices.receive((r) => {
|
||||||
|
const { status } = r;
|
||||||
|
if (status === "error") return console.error(r.errors);
|
||||||
|
setServices(r.data.blockdevices);
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (route() === "blockdevices") pyApi.show_block_devices.dispatch({});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div class="tooltip" data-tip="Refresh">
|
||||||
|
<button
|
||||||
|
class="btn btn-ghost"
|
||||||
|
onClick={() => pyApi.show_block_devices.dispatch({})}
|
||||||
|
>
|
||||||
|
<span class="material-icons ">refresh</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex max-w-screen-lg flex-col gap-4">
|
||||||
|
<Show when={devices()}>
|
||||||
|
{(devices) => (
|
||||||
|
<For each={devices()}>
|
||||||
|
{(device) => (
|
||||||
|
<div class="stats shadow">
|
||||||
|
<div class="stat w-28 py-8">
|
||||||
|
<div class="stat-title">Name</div>
|
||||||
|
<div class="stat-value">
|
||||||
|
{" "}
|
||||||
|
<span class="material-icons">storage</span> {device.name}
|
||||||
|
</div>
|
||||||
|
<div class="stat-desc"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat w-28">
|
||||||
|
<div class="stat-title">Size</div>
|
||||||
|
<div class="stat-value">{device.size}</div>
|
||||||
|
<div class="stat-desc"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
For,
|
For,
|
||||||
Match,
|
|
||||||
Show,
|
Show,
|
||||||
Switch,
|
|
||||||
createEffect,
|
createEffect,
|
||||||
createSignal,
|
createSignal,
|
||||||
type Component,
|
type Component,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { useMachineContext } from "../../Config";
|
|
||||||
import { route } from "@/src/App";
|
import { route } from "@/src/App";
|
||||||
import { OperationResponse, pyApi } from "@/src/api";
|
import { OperationResponse, pyApi } from "@/src/api";
|
||||||
import toast from "solid-toast";
|
|
||||||
|
|
||||||
type ServiceModel = Extract<
|
type ServiceModel = Extract<
|
||||||
OperationResponse<"show_mdns">,
|
OperationResponse<"show_mdns">,
|
||||||
@@ -40,13 +36,13 @@ export const HostList: Component = () => {
|
|||||||
<span class="material-icons ">refresh</span>
|
<span class="material-icons ">refresh</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 flex-wrap">
|
<div class="flex flex-wrap gap-2">
|
||||||
<Show when={services()}>
|
<Show when={services()}>
|
||||||
{(services) => (
|
{(services) => (
|
||||||
<For each={Object.values(services())}>
|
<For each={Object.values(services())}>
|
||||||
{(service) => (
|
{(service) => (
|
||||||
<div class="rounded-lg bg-white p-5 shadow-lg w-[30rem]">
|
<div class="w-[30rem] rounded-lg bg-white p-5 shadow-lg">
|
||||||
<div class="stats shadow flex flex-col">
|
<div class="stats flex flex-col shadow">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Host</div>
|
<div class="stat-title">Host</div>
|
||||||
<div class="stat-value">{service.host}</div>
|
<div class="stat-value">{service.host}</div>
|
||||||
@@ -61,7 +57,7 @@ export const HostList: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="join join-vertical w-full px-0">
|
<div class="join join-vertical w-full px-0">
|
||||||
<div class="collapse collapse-arrow join-item">
|
<div class="collapse join-item collapse-arrow">
|
||||||
<input type="radio" name="my-accordion-4" />
|
<input type="radio" name="my-accordion-4" />
|
||||||
<div class="collapse-title text-xl font-medium">
|
<div class="collapse-title text-xl font-medium">
|
||||||
Details
|
Details
|
||||||
|
|||||||
Reference in New Issue
Block a user