diff --git a/pkgs/clan-app/ui/src/components/Search/TagSelect.tsx b/pkgs/clan-app/ui/src/components/Search/TagSelect.tsx index 590577fd8..4fb798fba 100644 --- a/pkgs/clan-app/ui/src/components/Search/TagSelect.tsx +++ b/pkgs/clan-app/ui/src/components/Search/TagSelect.tsx @@ -55,7 +55,6 @@ export function TagSelect( > aria-label="Fruits"> {(state) => { - console.log("combobox state selected", state.selectedOptions()); return ( { const navigate = useNavigate(); - const [dialogHandlers, setDialogHandlers] = createSignal<{ + const [showService, setShowService] = createSignal(false); + + const [showModal, setShowModal] = createSignal(false); + const [currentPromise, setCurrentPromise] = createSignal<{ resolve: ({ id }: { id: string }) => void; reject: (err: unknown) => void; } | null>(null); @@ -187,7 +195,15 @@ const ClanSceneController = (props: RouteSectionProps) => { const onCreate = async (): Promise<{ id: string }> => { return new Promise((resolve, reject) => { setShowModal(true); - setDialogHandlers({ resolve, reject }); + setCurrentPromise({ resolve, reject }); + }); + }; + + const onAddService = async (): Promise<{ id: string }> => { + return new Promise((resolve, reject) => { + setShowService(true); + console.log("setting current promise"); + setCurrentPromise({ resolve, reject }); }); }; @@ -217,8 +233,6 @@ const ClanSceneController = (props: RouteSectionProps) => { return { id: values.name }; }; - const [showModal, setShowModal] = createSignal(false); - const [loadingError, setLoadingError] = createSignal< { title: string; description: string } | undefined >(); @@ -265,6 +279,26 @@ const ClanSceneController = (props: RouteSectionProps) => { }), ); + const client = useApiClient(); + const handleSubmitService = async (instance: InventoryInstance) => { + console.log("Create Instance", instance); + const call = client.fetch("create_service_instance", { + flake: { + identifier: ctx.clanURI, + }, + module_ref: instance.module, + roles: instance.roles, + }); + const result = await call.result; + + if (result.status === "error") { + console.error("Error creating service instance", result.errors); + } + // + currentPromise()?.resolve({ id: "0" }); + setShowService(false); + }; + return ( <> @@ -274,15 +308,15 @@ const ClanSceneController = (props: RouteSectionProps) => { { setShowModal(false); - dialogHandlers()?.reject(new Error("User cancelled")); + currentPromise()?.reject(new Error("User cancelled")); }} onSubmit={async (values) => { try { const result = await sendCreate(values); - dialogHandlers()?.resolve(result); + currentPromise()?.resolve(result); setShowModal(false); } catch (err) { - dialogHandlers()?.reject(err); + currentPromise()?.reject(err); setShowModal(false); } }} @@ -297,10 +331,22 @@ const ClanSceneController = (props: RouteSectionProps) => { + { + setShowService(false); + currentPromise()?.resolve({ id: "0" }); + }} + /> + + } onCreate={onCreate} clanURI={ctx.clanURI} sceneStore={() => store.sceneData?.[ctx.clanURI]} diff --git a/pkgs/clan-app/ui/src/scene/cubes.css b/pkgs/clan-app/ui/src/scene/cubes.css index 9be825105..063fe6ad8 100644 --- a/pkgs/clan-app/ui/src/scene/cubes.css +++ b/pkgs/clan-app/ui/src/scene/cubes.css @@ -4,6 +4,8 @@ cursor: pointer; } +/*
+ */ .toolbar-container { @apply absolute bottom-10 z-10 w-full; @apply flex justify-center items-center; diff --git a/pkgs/clan-app/ui/src/scene/cubes.tsx b/pkgs/clan-app/ui/src/scene/cubes.tsx index ef3e322f8..b20024c2a 100644 --- a/pkgs/clan-app/ui/src/scene/cubes.tsx +++ b/pkgs/clan-app/ui/src/scene/cubes.tsx @@ -1,4 +1,11 @@ -import { createSignal, createEffect, onCleanup, onMount, on } from "solid-js"; +import { + createSignal, + createEffect, + onCleanup, + onMount, + on, + JSX, +} from "solid-js"; import "./cubes.css"; import * as THREE from "three"; @@ -34,12 +41,14 @@ function garbageCollectGroup(group: THREE.Group) { export function CubeScene(props: { cubesQuery: MachinesQueryResult; onCreate: () => Promise<{ id: string }>; + onAddService: () => Promise<{ id: string }>; selectedIds: Accessor>; onSelect: (v: Set) => void; sceneStore: Accessor; setMachinePos: (machineId: string, pos: [number, number] | null) => void; isLoading: boolean; clanURI: string; + toolbarPopup?: JSX.Element; }) { let container: HTMLDivElement; let scene: THREE.Scene; @@ -213,7 +222,7 @@ export function CubeScene(props: { controls = new MapControls(camera, renderer.domElement); controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled controls.mouseButtons.RIGHT = null; - controls.enableRotate = false; + // controls.enableRotate = false; controls.minZoom = 1.2; controls.maxZoom = 3.5; controls.addEventListener("change", () => { @@ -543,6 +552,9 @@ export function CubeScene(props: { <>
(container = el)} />
+
+ {props.toolbarPopup} +
( { + console.log("Submitted instance:", instance); + }} initialStep="select:members" initialStore={{ currentRole: "peer", diff --git a/pkgs/clan-app/ui/src/workflows/Service/Service.tsx b/pkgs/clan-app/ui/src/workflows/Service/Service.tsx index 944dd1e2b..b4a4c0277 100644 --- a/pkgs/clan-app/ui/src/workflows/Service/Service.tsx +++ b/pkgs/clan-app/ui/src/workflows/Service/Service.tsx @@ -18,8 +18,6 @@ import { Search } from "@/src/components/Search/Search"; import Icon from "@/src/components/Icon/Icon"; import { Combobox } from "@kobalte/core/combobox"; import { Typography } from "@/src/components/Typography/Typography"; -import { Toolbar } from "@/src/components/Toolbar/Toolbar"; -import { ToolbarButton } from "@/src/components/Toolbar/ToolbarButton"; import { TagSelect } from "@/src/components/Search/TagSelect"; import { Tag } from "@/src/components/Tag/Tag"; import { createForm, FieldValues, setValue } from "@modular-forms/solid"; @@ -145,7 +143,6 @@ const ConfigureService = () => { const [store, set] = getStepStore(stepper); const [formStore, { Form, Field }] = createForm({ - // initialValues: props.initialValues, initialValues: { instanceName: "backup-instance-1", }, @@ -157,7 +154,28 @@ const ConfigureService = () => { const options = useOptions(tagsQuery, machinesQuery); const handleSubmit = (values: RolesForm) => { - console.log("Create service submitted with values:", values); + const roles: Record = Object.fromEntries( + Object.entries(store.roles).map(([key, value]) => [ + key, + { + machines: Object.fromEntries( + value.filter((v) => v.type === "machine").map((v) => [v.label, {}]), + ), + tags: Object.fromEntries( + value.filter((v) => v.type === "tag").map((v) => [v.label, {}]), + ), + }, + ]), + ); + + store.handleSubmit({ + name: values.instanceName, + module: { + name: store.module.name, + input: store.module.input, + }, + roles, + }); }; return ( @@ -185,13 +203,19 @@ const ConfigureService = () => { )}
-
{(role) => { const values = store.roles?.[role] || []; - console.log("Role members:", role, values, "from", options()); return ( label={role} @@ -221,7 +245,9 @@ const ConfigureService = () => {
- +
); @@ -269,8 +295,6 @@ const ConfigureRole = () => { set("roles", {}); } set("roles", (r) => ({ ...r, [store.currentRole as string]: members })); - console.log("Roles form submitted ", members); - stepper.setActiveStep("view:members"); }; @@ -381,6 +405,21 @@ const steps = [ export type ServiceSteps = typeof steps; +// TODO: Ideally we would impot this from a backend model package +export interface InventoryInstance { + name: string; + module: { + name: string; + input: string; + }; + roles: Record; +} + +interface RoleType { + machines: Record; + tags: Record; +} + export interface ServiceStoreType { module: { name: string; @@ -390,45 +429,36 @@ export interface ServiceStoreType { roles: Record; currentRole?: string; close: () => void; + handleSubmit: (values: InventoryInstance) => void; } interface ServiceWorkflowProps { initialStep?: ServiceSteps[number]["id"]; initialStore?: Partial; + onClose?: () => void; + handleSubmit: (values: InventoryInstance) => void; } + export const ServiceWorkflow = (props: ServiceWorkflowProps) => { - const [show, setShow] = createSignal(false); const stepper = createStepper( { steps }, { initialStep: props.initialStep || "select:service", initialStoreData: { ...props.initialStore, - close: () => setShow(false), + close: () => props.onClose?.(), + handleSubmit: props.handleSubmit, } satisfies Partial, }, ); return ( - <> -
- -
- -
{stepper.currentStep().content()}
-
-
-
-
- - setShow(!show())} - description="Add new Service" - name="modules" - icon="Modules" - /> - -
-
- +
+ +
{stepper.currentStep().content()}
+
+
); };