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:
@@ -1,3 +1,3 @@
|
|||||||
.sidebar {
|
.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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
div.sidebar-body {
|
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;
|
@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 {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
|
|
||||||
background:
|
background:
|
||||||
linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%),
|
linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%),
|
||||||
linear-gradient(
|
linear-gradient(
|
||||||
@@ -20,13 +21,14 @@ div.sidebar-body {
|
|||||||
@apply backdrop-blur-sm;
|
@apply backdrop-blur-sm;
|
||||||
|
|
||||||
.accordion {
|
.accordion {
|
||||||
@apply w-full mb-4;
|
@apply w-full mb-4 h-full flex flex-col justify-start gap-4;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
@apply mb-0;
|
@apply mb-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .item {
|
& > .item {
|
||||||
|
max-height: 50%;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
@apply mb-0;
|
@apply mb-0;
|
||||||
}
|
}
|
||||||
@@ -58,9 +60,13 @@ div.sidebar-body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
& > .content {
|
& > .content {
|
||||||
@apply overflow-hidden flex flex-col;
|
@apply flex flex-col;
|
||||||
@apply py-3 px-1.5 bg-inv-4 rounded-md mb-4;
|
@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);
|
animation: slideAccordionUp 300ms cubic-bezier(0.87, 0, 0.13, 1);
|
||||||
|
|
||||||
&[data-expanded] {
|
&[data-expanded] {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useMachineStateQuery } from "@/src/hooks/queries";
|
|||||||
import { SidebarProps } from "./Sidebar";
|
import { SidebarProps } from "./Sidebar";
|
||||||
import { Button } from "../Button/Button";
|
import { Button } from "../Button/Button";
|
||||||
import { useClanContext } from "@/src/routes/Clan/Clan";
|
import { useClanContext } from "@/src/routes/Clan/Clan";
|
||||||
|
import { Instance } from "@/src/workflows/Service/models";
|
||||||
|
|
||||||
interface MachineProps {
|
interface MachineProps {
|
||||||
clanURI: string;
|
clanURI: string;
|
||||||
@@ -129,22 +130,42 @@ const Machines = () => {
|
|||||||
|
|
||||||
export const ServiceRoute = (props: {
|
export const ServiceRoute = (props: {
|
||||||
clanURI: string;
|
clanURI: string;
|
||||||
machineName?: string;
|
|
||||||
label: string;
|
label: string;
|
||||||
id: 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">
|
<div class="flex w-full flex-col gap-2">
|
||||||
<Typography
|
<div class="flex flex-row items-center justify-between">
|
||||||
hierarchy="label"
|
<Typography
|
||||||
size="s"
|
hierarchy="label"
|
||||||
weight="bold"
|
size="xs"
|
||||||
color="primary"
|
weight="bold"
|
||||||
inverted
|
color="primary"
|
||||||
>
|
inverted
|
||||||
{props.label}
|
>
|
||||||
</Typography>
|
{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>
|
</div>
|
||||||
</A>
|
</A>
|
||||||
);
|
);
|
||||||
@@ -165,8 +186,12 @@ const Services = () => {
|
|||||||
const moduleName = instance.module.name;
|
const moduleName = instance.module.name;
|
||||||
|
|
||||||
const label = moduleName == id ? moduleName : `${moduleName} (${id})`;
|
const label = moduleName == id ? moduleName : `${moduleName} (${id})`;
|
||||||
|
console.log("Service instance", id, instance, label);
|
||||||
return { id, label, module: instance.module };
|
return {
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
instance: instance,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -191,7 +216,14 @@ const Services = () => {
|
|||||||
<Accordion.Content class="content">
|
<Accordion.Content class="content">
|
||||||
<nav>
|
<nav>
|
||||||
<For each={serviceInstances()}>
|
<For each={serviceInstances()}>
|
||||||
{(instance) => <ServiceRoute clanURI={ctx.clanURI} {...instance} />}
|
{(mapped) => (
|
||||||
|
<ServiceRoute
|
||||||
|
clanURI={ctx.clanURI}
|
||||||
|
id={mapped.id}
|
||||||
|
label={mapped.label}
|
||||||
|
instance={mapped.instance}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</For>
|
</For>
|
||||||
</nav>
|
</nav>
|
||||||
</Accordion.Content>
|
</Accordion.Content>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useMachineName } from "@/src/hooks/clan";
|
|||||||
import { useMachineStateQuery } from "@/src/hooks/queries";
|
import { useMachineStateQuery } from "@/src/hooks/queries";
|
||||||
import styles from "./SidebarSectionInstall.module.css";
|
import styles from "./SidebarSectionInstall.module.css";
|
||||||
import { Alert } from "../Alert/Alert";
|
import { Alert } from "../Alert/Alert";
|
||||||
|
import { useClanContext } from "@/src/routes/Clan/Clan";
|
||||||
|
|
||||||
export interface SidebarSectionInstallProps {
|
export interface SidebarSectionInstallProps {
|
||||||
clanURI: string;
|
clanURI: string;
|
||||||
@@ -12,8 +13,8 @@ export interface SidebarSectionInstallProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SidebarSectionInstall = (props: SidebarSectionInstallProps) => {
|
export const SidebarSectionInstall = (props: SidebarSectionInstallProps) => {
|
||||||
|
const ctx = useClanContext();
|
||||||
const query = useMachineStateQuery(props.clanURI, props.machineName);
|
const query = useMachineStateQuery(props.clanURI, props.machineName);
|
||||||
|
|
||||||
const [showInstall, setShowModal] = createSignal(false);
|
const [showInstall, setShowModal] = createSignal(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,7 +33,12 @@ export const SidebarSectionInstall = (props: SidebarSectionInstallProps) => {
|
|||||||
<InstallModal
|
<InstallModal
|
||||||
open={showInstall()}
|
open={showInstall()}
|
||||||
machineName={useMachineName()}
|
machineName={useMachineName()}
|
||||||
onClose={() => setShowModal(false)}
|
onClose={async () => {
|
||||||
|
// refresh some queries
|
||||||
|
ctx.machinesQuery.refetch();
|
||||||
|
ctx.serviceInstancesQuery.refetch();
|
||||||
|
setShowModal(false);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useMachineName } from "@/src/hooks/clan";
|
|||||||
import { useMachineStateQuery } from "@/src/hooks/queries";
|
import { useMachineStateQuery } from "@/src/hooks/queries";
|
||||||
import styles from "./SidebarSectionInstall.module.css";
|
import styles from "./SidebarSectionInstall.module.css";
|
||||||
import { UpdateModal } from "@/src/workflows/InstallMachine/UpdateMachine";
|
import { UpdateModal } from "@/src/workflows/InstallMachine/UpdateMachine";
|
||||||
|
import { useClanContext } from "@/src/routes/Clan/Clan";
|
||||||
|
|
||||||
export interface SidebarSectionUpdateProps {
|
export interface SidebarSectionUpdateProps {
|
||||||
clanURI: string;
|
clanURI: string;
|
||||||
@@ -11,6 +12,7 @@ export interface SidebarSectionUpdateProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SidebarSectionUpdate = (props: SidebarSectionUpdateProps) => {
|
export const SidebarSectionUpdate = (props: SidebarSectionUpdateProps) => {
|
||||||
|
const ctx = useClanContext();
|
||||||
const query = useMachineStateQuery(props.clanURI, props.machineName);
|
const query = useMachineStateQuery(props.clanURI, props.machineName);
|
||||||
|
|
||||||
const [showUpdate, setShowUpdate] = createSignal(false);
|
const [showUpdate, setShowUpdate] = createSignal(false);
|
||||||
@@ -29,7 +31,13 @@ export const SidebarSectionUpdate = (props: SidebarSectionUpdateProps) => {
|
|||||||
<UpdateModal
|
<UpdateModal
|
||||||
open={showUpdate()}
|
open={showUpdate()}
|
||||||
machineName={useMachineName()}
|
machineName={useMachineName()}
|
||||||
onClose={() => setShowUpdate(false)}
|
onClose={async () => {
|
||||||
|
// refresh some queries
|
||||||
|
ctx.machinesQuery.refetch();
|
||||||
|
ctx.serviceInstancesQuery.refetch();
|
||||||
|
|
||||||
|
setShowUpdate(false);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,14 +20,15 @@ export const SectionServices = () => {
|
|||||||
|
|
||||||
return (ctx.machinesQuery.data[machineName].instance_refs ?? []).map(
|
return (ctx.machinesQuery.data[machineName].instance_refs ?? []).map(
|
||||||
(id) => {
|
(id) => {
|
||||||
const module = ctx.serviceInstancesQuery.data?.[id].module;
|
const instance = ctx.serviceInstancesQuery.data?.[id];
|
||||||
if (!module) {
|
if (!instance) {
|
||||||
throw new Error(`Service instance ${id} has no module`);
|
throw new Error(`Service instance ${id} not found`);
|
||||||
}
|
}
|
||||||
|
const module = instance.module;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
module,
|
instance,
|
||||||
label: module.name == id ? module.name : `${module.name} (${id})`,
|
label: module.name == id ? module.name : `${module.name} (${id})`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -41,11 +42,7 @@ export const SectionServices = () => {
|
|||||||
<nav>
|
<nav>
|
||||||
<For each={services()}>
|
<For each={services()}>
|
||||||
{(instance) => (
|
{(instance) => (
|
||||||
<ServiceRoute
|
<ServiceRoute clanURI={ctx.clanURI} {...instance} />
|
||||||
clanURI={ctx.clanURI}
|
|
||||||
machineName={useMachineName()}
|
|
||||||
{...instance}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { installSteps } from "./steps/installSteps";
|
|||||||
import { ApiCall } from "@/src/hooks/api";
|
import { ApiCall } from "@/src/hooks/api";
|
||||||
|
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import { useClanContext } from "@/src/routes/Clan/Clan";
|
|
||||||
|
|
||||||
interface InstallStepperProps {
|
interface InstallStepperProps {
|
||||||
onDone: () => void;
|
onDone: () => void;
|
||||||
@@ -67,8 +66,6 @@ export interface InstallStoreType {
|
|||||||
export type PromptValues = Record<string, Record<string, string>>;
|
export type PromptValues = Record<string, Record<string, string>>;
|
||||||
|
|
||||||
export const InstallModal = (props: InstallModalProps) => {
|
export const InstallModal = (props: InstallModalProps) => {
|
||||||
const ctx = useClanContext();
|
|
||||||
|
|
||||||
const stepper = createStepper(
|
const stepper = createStepper(
|
||||||
{
|
{
|
||||||
steps,
|
steps,
|
||||||
@@ -111,10 +108,6 @@ export const InstallModal = (props: InstallModalProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onClose = async () => {
|
const onClose = async () => {
|
||||||
// refresh some queries
|
|
||||||
await ctx.machinesQuery.refetch();
|
|
||||||
await ctx.serviceInstancesQuery.refetch();
|
|
||||||
|
|
||||||
props.onClose?.();
|
props.onClose?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { LoadingBar } from "@/src/components/LoadingBar/LoadingBar";
|
|||||||
import { useApiClient } from "@/src/hooks/ApiClient";
|
import { useApiClient } from "@/src/hooks/ApiClient";
|
||||||
import { useClanURI } from "@/src/hooks/clan";
|
import { useClanURI } from "@/src/hooks/clan";
|
||||||
import { AlertProps } from "@/src/components/Alert/Alert";
|
import { AlertProps } from "@/src/components/Alert/Alert";
|
||||||
import { useClanContext } from "@/src/routes/Clan/Clan";
|
|
||||||
|
|
||||||
// TODO: Deduplicate
|
// TODO: Deduplicate
|
||||||
interface UpdateStepperProps {
|
interface UpdateStepperProps {
|
||||||
@@ -238,8 +237,6 @@ export type UpdateSteps = typeof steps;
|
|||||||
export type PromptValues = Record<string, Record<string, string>>;
|
export type PromptValues = Record<string, Record<string, string>>;
|
||||||
|
|
||||||
export const UpdateModal = (props: UpdateModalProps) => {
|
export const UpdateModal = (props: UpdateModalProps) => {
|
||||||
const ctx = useClanContext();
|
|
||||||
|
|
||||||
const stepper = createStepper(
|
const stepper = createStepper(
|
||||||
{
|
{
|
||||||
steps,
|
steps,
|
||||||
@@ -285,10 +282,6 @@ export const UpdateModal = (props: UpdateModalProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onClose = async () => {
|
const onClose = async () => {
|
||||||
// refresh some queries
|
|
||||||
await ctx.machinesQuery.refetch();
|
|
||||||
await ctx.serviceInstancesQuery.refetch();
|
|
||||||
|
|
||||||
props.onClose?.();
|
props.onClose?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ export interface Module {
|
|||||||
|
|
||||||
type ValueOf<T> = T[keyof T];
|
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
|
* 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(
|
export function getRoleMembers(
|
||||||
instance: ValueOf<NonNullable<ServiceInstancesQuery["data"]>>,
|
instance: Instance,
|
||||||
all_machines: NonNullable<MachinesQuery["data"]>,
|
all_machines: NonNullable<MachinesQuery["data"]>,
|
||||||
role: string,
|
role: string,
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user