Merge pull request 'ui/sidebar: max-width of section, scroll within sections' (#5083) from ui/update-machine into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5083
This commit is contained in:
hsjobeki
2025-09-03 12:49:22 +00:00
9 changed files with 86 additions and 49 deletions

View File

@@ -1,3 +1,3 @@
.sidebar {
@apply w-60 border-none z-10 h-full flex flex-col;
@apply w-60 border-none z-10 h-full flex flex-col rounded-b-md overflow-hidden;
}

View File

@@ -1,14 +1,15 @@
div.sidebar-body {
@apply py-4 px-2 h-full;
@apply py-4 px-2;
/* full - (y padding) */
height: calc(100% - 2rem);
@apply border border-inv-3 rounded-bl-md rounded-br-md;
/* TODO: This is weird, we shouldn't disable native browser features, a11y impacts incomming */
&::-webkit-scrollbar {
display: none;
}
overflow-y: auto;
scrollbar-width: none;
background:
linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%),
linear-gradient(
@@ -20,13 +21,14 @@ div.sidebar-body {
@apply backdrop-blur-sm;
.accordion {
@apply w-full mb-4;
@apply w-full mb-4 h-full flex flex-col justify-start gap-4;
&:last-child {
@apply mb-0;
}
& > .item {
max-height: 50%;
&:last-child {
@apply mb-0;
}
@@ -58,9 +60,13 @@ div.sidebar-body {
}
& > .content {
@apply overflow-hidden flex flex-col;
@apply flex flex-col;
@apply py-3 px-1.5 bg-inv-4 rounded-md mb-4;
max-height: calc(100% - 24px);
overflow-y: auto;
scrollbar-width: none;
animation: slideAccordionUp 300ms cubic-bezier(0.87, 0, 0.13, 1);
&[data-expanded] {

View File

@@ -10,6 +10,7 @@ import { useMachineStateQuery } from "@/src/hooks/queries";
import { SidebarProps } from "./Sidebar";
import { Button } from "../Button/Button";
import { useClanContext } from "@/src/routes/Clan/Clan";
import { Instance } from "@/src/workflows/Service/models";
interface MachineProps {
clanURI: string;
@@ -129,22 +130,42 @@ const Machines = () => {
export const ServiceRoute = (props: {
clanURI: string;
machineName?: string;
label: string;
id: string;
module: { input?: string | null | undefined; name: string };
instance: Instance;
}) => (
<A href={buildServicePath(props)} replace={true}>
<A
href={buildServicePath({
clanURI: props.clanURI,
id: props.id,
module: props.instance.module,
})}
replace={true}
>
<div class="flex w-full flex-col gap-2">
<Typography
hierarchy="label"
size="s"
weight="bold"
color="primary"
inverted
>
{props.label}
</Typography>
<div class="flex flex-row items-center justify-between">
<Typography
hierarchy="label"
size="xs"
weight="bold"
color="primary"
inverted
>
{props.id}
</Typography>
</div>
<div class="flex w-full flex-row items-center gap-1">
<Icon icon="Code" size="0.75rem" inverted color="tertiary" />
<Typography
hierarchy="label"
family="mono"
size="s"
inverted
color="primary"
>
{props.instance.resolved.usage_ref.name}
</Typography>
</div>
</div>
</A>
);
@@ -165,8 +186,12 @@ const Services = () => {
const moduleName = instance.module.name;
const label = moduleName == id ? moduleName : `${moduleName} (${id})`;
return { id, label, module: instance.module };
console.log("Service instance", id, instance, label);
return {
id,
label,
instance: instance,
};
},
);
};
@@ -191,7 +216,14 @@ const Services = () => {
<Accordion.Content class="content">
<nav>
<For each={serviceInstances()}>
{(instance) => <ServiceRoute clanURI={ctx.clanURI} {...instance} />}
{(mapped) => (
<ServiceRoute
clanURI={ctx.clanURI}
id={mapped.id}
label={mapped.label}
instance={mapped.instance}
/>
)}
</For>
</nav>
</Accordion.Content>

View File

@@ -5,6 +5,7 @@ import { useMachineName } from "@/src/hooks/clan";
import { useMachineStateQuery } from "@/src/hooks/queries";
import styles from "./SidebarSectionInstall.module.css";
import { Alert } from "../Alert/Alert";
import { useClanContext } from "@/src/routes/Clan/Clan";
export interface SidebarSectionInstallProps {
clanURI: string;
@@ -12,8 +13,8 @@ export interface SidebarSectionInstallProps {
}
export const SidebarSectionInstall = (props: SidebarSectionInstallProps) => {
const ctx = useClanContext();
const query = useMachineStateQuery(props.clanURI, props.machineName);
const [showInstall, setShowModal] = createSignal(false);
return (
@@ -32,7 +33,12 @@ export const SidebarSectionInstall = (props: SidebarSectionInstallProps) => {
<InstallModal
open={showInstall()}
machineName={useMachineName()}
onClose={() => setShowModal(false)}
onClose={async () => {
// refresh some queries
ctx.machinesQuery.refetch();
ctx.serviceInstancesQuery.refetch();
setShowModal(false);
}}
/>
</Show>
</div>

View File

@@ -4,6 +4,7 @@ import { useMachineName } from "@/src/hooks/clan";
import { useMachineStateQuery } from "@/src/hooks/queries";
import styles from "./SidebarSectionInstall.module.css";
import { UpdateModal } from "@/src/workflows/InstallMachine/UpdateMachine";
import { useClanContext } from "@/src/routes/Clan/Clan";
export interface SidebarSectionUpdateProps {
clanURI: string;
@@ -11,6 +12,7 @@ export interface SidebarSectionUpdateProps {
}
export const SidebarSectionUpdate = (props: SidebarSectionUpdateProps) => {
const ctx = useClanContext();
const query = useMachineStateQuery(props.clanURI, props.machineName);
const [showUpdate, setShowUpdate] = createSignal(false);
@@ -29,7 +31,13 @@ export const SidebarSectionUpdate = (props: SidebarSectionUpdateProps) => {
<UpdateModal
open={showUpdate()}
machineName={useMachineName()}
onClose={() => setShowUpdate(false)}
onClose={async () => {
// refresh some queries
ctx.machinesQuery.refetch();
ctx.serviceInstancesQuery.refetch();
setShowUpdate(false);
}}
/>
</Show>
</div>

View File

@@ -20,14 +20,15 @@ export const SectionServices = () => {
return (ctx.machinesQuery.data[machineName].instance_refs ?? []).map(
(id) => {
const module = ctx.serviceInstancesQuery.data?.[id].module;
if (!module) {
throw new Error(`Service instance ${id} has no module`);
const instance = ctx.serviceInstancesQuery.data?.[id];
if (!instance) {
throw new Error(`Service instance ${id} not found`);
}
const module = instance.module;
return {
id,
module,
instance,
label: module.name == id ? module.name : `${module.name} (${id})`,
};
},
@@ -41,11 +42,7 @@ export const SectionServices = () => {
<nav>
<For each={services()}>
{(instance) => (
<ServiceRoute
clanURI={ctx.clanURI}
machineName={useMachineName()}
{...instance}
/>
<ServiceRoute clanURI={ctx.clanURI} {...instance} />
)}
</For>
</nav>

View File

@@ -13,7 +13,6 @@ import { installSteps } from "./steps/installSteps";
import { ApiCall } from "@/src/hooks/api";
import cx from "classnames";
import { useClanContext } from "@/src/routes/Clan/Clan";
interface InstallStepperProps {
onDone: () => void;
@@ -67,8 +66,6 @@ export interface InstallStoreType {
export type PromptValues = Record<string, Record<string, string>>;
export const InstallModal = (props: InstallModalProps) => {
const ctx = useClanContext();
const stepper = createStepper(
{
steps,
@@ -111,10 +108,6 @@ export const InstallModal = (props: InstallModalProps) => {
};
const onClose = async () => {
// refresh some queries
await ctx.machinesQuery.refetch();
await ctx.serviceInstancesQuery.refetch();
props.onClose?.();
};

View File

@@ -19,7 +19,6 @@ import { LoadingBar } from "@/src/components/LoadingBar/LoadingBar";
import { useApiClient } from "@/src/hooks/ApiClient";
import { useClanURI } from "@/src/hooks/clan";
import { AlertProps } from "@/src/components/Alert/Alert";
import { useClanContext } from "@/src/routes/Clan/Clan";
// TODO: Deduplicate
interface UpdateStepperProps {
@@ -238,8 +237,6 @@ export type UpdateSteps = typeof steps;
export type PromptValues = Record<string, Record<string, string>>;
export const UpdateModal = (props: UpdateModalProps) => {
const ctx = useClanContext();
const stepper = createStepper(
{
steps,
@@ -285,10 +282,6 @@ export const UpdateModal = (props: UpdateModalProps) => {
};
const onClose = async () => {
// refresh some queries
await ctx.machinesQuery.refetch();
await ctx.serviceInstancesQuery.refetch();
props.onClose?.();
};

View File

@@ -43,6 +43,8 @@ export interface Module {
type ValueOf<T> = T[keyof T];
export type Instance = ValueOf<NonNullable<ServiceInstancesQuery["data"]>>;
/**
* Collect all members (machines and tags) for a given role in a service instance
*
@@ -50,7 +52,7 @@ type ValueOf<T> = T[keyof T];
*
*/
export function getRoleMembers(
instance: ValueOf<NonNullable<ServiceInstancesQuery["data"]>>,
instance: Instance,
all_machines: NonNullable<MachinesQuery["data"]>,
role: string,
) {