This commit is contained in:
Johannes Kirschbauer
2023-11-17 11:42:20 +01:00
parent 7044774567
commit dcd6e9e9c1
11 changed files with 116 additions and 91 deletions

View File

@@ -8,7 +8,7 @@
{ {
packages = { packages = {
# disabled because frontend is broken after cli refactoring # disabled because frontend is broken after cli refactoring
# ui = base.pkg.global; ui = base.pkg.global;
ui-assets = pkgs.callPackage ./nix/ui-assets.nix { }; ui-assets = pkgs.callPackage ./nix/ui-assets.nix { };
# EXAMPLE: GITEA_TOKEN=$(rbw get -f GITEA_TOKEN git.clan.lol) nix run .#update-ui-assets # EXAMPLE: GITEA_TOKEN=$(rbw get -f GITEA_TOKEN git.clan.lol) nix run .#update-ui-assets
update-ui-assets = pkgs.callPackage ./nix/update-ui-assets.nix { }; update-ui-assets = pkgs.callPackage ./nix/update-ui-assets.nix { };

View File

@@ -77,18 +77,18 @@ export default function RootLayout({
show={showSidebarDerived} show={showSidebarDerived}
onClose={() => setShowSidebar(false)} onClose={() => setShowSidebar(false)}
clanSelect={ clanSelect={
appState.data.clanName && ( appState.data.clanDir && (
<Select <Select
color="secondary" color="secondary"
label="clan" label="clan"
fullWidth fullWidth
variant="standard" variant="standard"
disableUnderline disableUnderline
value={appState.data.clanName} value={appState.data.clanDir}
onChange={(ev) => { onChange={(ev) => {
appState.setAppState((c) => ({ appState.setAppState((c) => ({
...c, ...c,
clanName: ev.target.value, clanDir: ev.target.value,
})); }));
}} }}
> >

View File

@@ -4,12 +4,13 @@ import { MachineContextProvider } from "@/components/hooks/useMachines";
export default function Layout({ children }: { children: React.ReactNode }) { export default function Layout({ children }: { children: React.ReactNode }) {
const { const {
data: { clanName }, data: { clanDir },
} = useAppState(); } = useAppState();
return ( return (
<> <>
{clanName && ( {!clanDir && <div>No clan selected</div>}
<MachineContextProvider flakeName={clanName}> {clanDir && (
<MachineContextProvider flakeName={clanDir}>
{children} {children}
</MachineContextProvider> </MachineContextProvider>
)} )}

View File

@@ -1,5 +1,7 @@
import { getMachineSchema } from "@/api/machine/machine"; import { getMachineSchema } from "@/api/machine/machine";
import { HTTPValidationError } from "@/api/model";
import { useListClanModules } from "@/api/modules/modules"; import { useListClanModules } from "@/api/modules/modules";
import { clanErrorToast } from "@/error/errorToast";
import { import {
Alert, Alert,
AlertTitle, AlertTitle,
@@ -15,6 +17,7 @@ import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem"; import MenuItem from "@mui/material/MenuItem";
import OutlinedInput from "@mui/material/OutlinedInput"; import OutlinedInput from "@mui/material/OutlinedInput";
import Select, { SelectChangeEvent } from "@mui/material/Select"; import Select, { SelectChangeEvent } from "@mui/material/Select";
import { AxiosError } from "axios";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Controller } from "react-hook-form"; import { Controller } from "react-hook-form";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
@@ -31,40 +34,6 @@ const MenuProps = {
}, },
}; };
// interface IupdateSchema {
// clanName: string;
// modules: string[];
// formHooks: FormHooks;
// setSchemaError: Dispatch<SetStateAction<null | string>>;
// }
// const updateSchema = ({
// clanName,
// modules,
// formHooks,
// setSchemaError,
// }: IupdateSchema) => {
// formHooks.setValue("isSchemaLoading", true);
// getMachineSchema(clanName, {
// clanImports: modules,
// })
// .then((response) => {
// if (response.statusText == "OK") {
// formHooks.setValue("schema", response.data.schema);
// setSchemaError(null);
// }
// })
// .catch((error) => {
// formHooks.setValue("schema", {});
// console.error({ error });
// setSchemaError(error.message);
// toast.error(`${error.message}`);
// })
// .finally(() => {
// formHooks.setValue("isSchemaLoading", false);
// });
// };
type ClanModulesProps = FormStepContentProps; type ClanModulesProps = FormStepContentProps;
const SchemaSuccessMsg = () => ( const SchemaSuccessMsg = () => (
@@ -93,22 +62,34 @@ const SchemaErrorMsg = (props: SchemaErrorMsgProps) => (
); );
export default function ClanModules(props: ClanModulesProps) { export default function ClanModules(props: ClanModulesProps) {
const { clanName, formHooks } = props; const { clanDir, formHooks } = props;
const { data, isLoading } = useListClanModules(clanName); const { data, isLoading } = useListClanModules({ flake_dir: clanDir });
const [schemaError] = useState<string | null>(null); const [schemaError] = useState<string | null>(null);
const selectedModules = formHooks.watch("modules"); const selectedModules = formHooks.watch("modules");
useEffect(() => { useEffect(() => {
getMachineSchema(clanName, { const load = async () => {
clanImports: [], try {
}).then((response) => { const response = await getMachineSchema(
if (response.statusText == "OK") { {
formHooks.setValue("schema", response.data.schema); clanImports: [],
} },
}); {
flake_dir: clanDir,
},
);
// Only re-run if global clanName has changed if (response.statusText == "OK") {
formHooks.setValue("schema", response.data.schema);
}
} catch (e) {
clanErrorToast(e as AxiosError<HTTPValidationError>);
}
};
load();
// Only re-run if global clanDir has changed
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [clanName]); }, [clanDir]);
const isSchemaLoading = formHooks.watch("isSchemaLoading"); const isSchemaLoading = formHooks.watch("isSchemaLoading");
const handleChange = ( const handleChange = (
@@ -119,9 +100,14 @@ export default function ClanModules(props: ClanModulesProps) {
} = event; } = event;
const newValue = typeof value === "string" ? value.split(",") : value; const newValue = typeof value === "string" ? value.split(",") : value;
formHooks.setValue("modules", newValue); formHooks.setValue("modules", newValue);
getMachineSchema(clanName, { getMachineSchema(
clanImports: newValue, {
}) clanImports: newValue,
},
{
flake_dir: clanDir,
},
)
.then((response) => { .then((response) => {
if (response.statusText == "OK") { if (response.statusText == "OK") {
formHooks.setValue("schema", response.data.schema); formHooks.setValue("schema", response.data.schema);

View File

@@ -29,7 +29,7 @@ interface PureCustomConfigProps extends FormStepContentProps {
initialValues: any; initialValues: any;
} }
export function CustomConfig(props: FormStepContentProps) { export function CustomConfig(props: FormStepContentProps) {
const { formHooks, clanName } = props; const { formHooks, clanDir } = props;
const schema = formHooks.watch("schema"); const schema = formHooks.watch("schema");
const initialValues = useMemo( const initialValues = useMemo(
@@ -49,7 +49,7 @@ export function CustomConfig(props: FormStepContentProps) {
return ( return (
<PureCustomConfig <PureCustomConfig
clanName={clanName} clanDir={clanDir}
formHooks={formHooks} formHooks={formHooks}
initialValues={initialValues} initialValues={initialValues}
/> />

View File

@@ -1,4 +1,4 @@
import { putMachine } from "@/api/machine/machine"; import { setMachineConfig } from "@/api/machine/machine";
import { import {
Box, Box,
Button, Button,
@@ -21,7 +21,7 @@ import { CreateMachineForm, FormStep } from "./interfaces";
export function CreateMachineForm() { export function CreateMachineForm() {
const { const {
data: { clanName }, data: { clanDir },
} = useAppState(); } = useAppState();
const formHooks = useForm<CreateMachineForm>({ const formHooks = useForm<CreateMachineForm>({
defaultValues: { defaultValues: {
@@ -41,8 +41,8 @@ export function CreateMachineForm() {
{ {
id: "modules", id: "modules",
label: "Modules", label: "Modules",
content: clanName ? ( content: clanDir ? (
<ClanModules clanName={clanName} formHooks={formHooks} /> <ClanModules clanDir={clanDir} formHooks={formHooks} />
) : ( ) : (
<LinearProgress /> <LinearProgress />
), ),
@@ -50,8 +50,8 @@ export function CreateMachineForm() {
{ {
id: "config", id: "config",
label: "Customize", label: "Customize",
content: clanName ? ( content: clanDir ? (
<CustomConfig formHooks={formHooks} clanName={clanName} /> <CustomConfig formHooks={formHooks} clanDir={clanDir} />
) : ( ) : (
<LinearProgress /> <LinearProgress />
), ),
@@ -74,15 +74,21 @@ export function CreateMachineForm() {
async function onSubmit(data: CreateMachineForm) { async function onSubmit(data: CreateMachineForm) {
console.log({ data }, "Aggregated Data; creating machine from"); console.log({ data }, "Aggregated Data; creating machine from");
if (clanName) { if (clanDir) {
if (!data.name) { if (!data.name) {
toast.error("Machine name should not be empty"); toast.error("Machine name should not be empty");
return; return;
} }
await putMachine(clanName, data.name, { await setMachineConfig(
clan: data.config.formData, data.name,
clanImports: data.modules, {
}); clan: data.config.formData,
clanImports: data.modules,
},
{
flake_dir: clanDir,
},
);
} }
} }

View File

@@ -22,7 +22,7 @@ export type FormStep = {
export interface FormStepContentProps { export interface FormStepContentProps {
formHooks: FormHooks; formHooks: FormHooks;
clanName: string; clanDir: string;
} }
export type FormStepContent = ReactElement<FormStepContentProps>; export type FormStepContent = ReactElement<FormStepContentProps>;

View File

@@ -1,6 +1,6 @@
import { useListAllFlakes } from "@/api/flake/flake"; // import { useListAllFlakes } from "@/api/flake/flake";
import { FlakeListResponse } from "@/api/model"; // import { FlakeListResponse } from "@/api/model";
import { AxiosError, AxiosResponse } from "axios"; import { AxiosError } from "axios";
import React, { import React, {
Dispatch, Dispatch,
ReactNode, ReactNode,
@@ -9,7 +9,6 @@ import React, {
useEffect, useEffect,
useState, useState,
} from "react"; } from "react";
import { KeyedMutator } from "swr";
type AppContextType = { type AppContextType = {
// data: AxiosResponse<{}, any> | undefined; // data: AxiosResponse<{}, any> | undefined;
@@ -19,7 +18,7 @@ type AppContextType = {
error: AxiosError<any> | undefined; error: AxiosError<any> | undefined;
setAppState: Dispatch<SetStateAction<AppState>>; setAppState: Dispatch<SetStateAction<AppState>>;
mutate: KeyedMutator<AxiosResponse<FlakeListResponse, any>>; // mutate: KeyedMutator<AxiosResponse<FlakeListResponse, any>>;
swrKey: string | false | Record<any, any>; swrKey: string | false | Record<any, any>;
}; };
@@ -27,28 +26,45 @@ export const AppContext = createContext<AppContextType>({} as AppContextType);
type AppState = { type AppState = {
isJoined?: boolean; isJoined?: boolean;
clanName?: string; clanDir?: string;
flakes?: FlakeListResponse["flakes"]; flakes?: string[]; //FlakeListResponse["flakes"];
}; };
interface AppContextProviderProps { interface AppContextProviderProps {
children: ReactNode; children: ReactNode;
} }
const mock = {
data: { flakes: [] },
};
// list_clans
export const WithAppState = (props: AppContextProviderProps) => { export const WithAppState = (props: AppContextProviderProps) => {
const { children } = props; const { children } = props;
// const {
// isLoading,
// error,
// swrKey,
// data: flakesResponse,
// mutate,
// } = useListAllFlakes({
// swr: {
// revalidateIfStale: false,
// revalidateOnFocus: false,
// revalidateOnReconnect: false,
// },
// });
const { const {
isLoading, isLoading,
error, error,
swrKey, swrKey,
data: flakesResponse, data: flakesResponse,
mutate, } = {
} = useListAllFlakes({ isLoading: false,
swr: { error: undefined,
revalidateIfStale: false, swrKey: "",
revalidateOnFocus: false, data: mock,
revalidateOnReconnect: false, };
},
});
const [data, setAppState] = useState<AppState>({ isJoined: false }); const [data, setAppState] = useState<AppState>({ isJoined: false });
useEffect(() => { useEffect(() => {
@@ -57,7 +73,7 @@ export const WithAppState = (props: AppContextProviderProps) => {
data: { flakes }, data: { flakes },
} = flakesResponse; } = flakesResponse;
if (flakes.length >= 1) { if (flakes.length >= 1) {
setAppState((c) => ({ ...c, clanName: flakes[0], isJoined: true })); setAppState((c) => ({ ...c, clanDir: flakes[0], isJoined: true }));
} }
setAppState((c) => ({ ...c, flakes })); setAppState((c) => ({ ...c, flakes }));
} }
@@ -71,7 +87,7 @@ export const WithAppState = (props: AppContextProviderProps) => {
isLoading, isLoading,
error, error,
swrKey, swrKey,
mutate, // mutate,
}} }}
> >
{children} {children}

View File

@@ -65,7 +65,7 @@ export const MachineContextProvider = (props: MachineContextProviderProps) => {
isValidating, isValidating,
mutate, mutate,
swrKey, swrKey,
} = useListMachines(flakeName); } = useListMachines({ flake_dir: flakeName });
const [filters, setFilters] = useState<Filters>([]); const [filters, setFilters] = useState<Filters>([]);
const data = useMemo(() => { const data = useMemo(() => {

View File

@@ -0,0 +1,12 @@
import { HTTPValidationError } from "@/api/model";
import { AxiosError } from "axios";
import { toast } from "react-hot-toast";
export function clanErrorToast(error: AxiosError<HTTPValidationError>) {
console.error({ error });
const detail = error.response?.data.detail?.[0]?.msg;
const cause = error.cause?.message;
const axiosMessage = error.message;
const sanitizedMsg = detail || cause || axiosMessage || "Unexpected error";
toast.error(sanitizedMsg);
}

View File

@@ -100,10 +100,14 @@ export default function JoinPrequel() {
onSubmit={handleSubmit(async (values) => { onSubmit={handleSubmit(async (values) => {
if (workflow === "create") { if (workflow === "create") {
try { try {
await createFlake({ await createFlake(
flake_name: values.dest || "default", {
url: values.flakeUrl, url: values.flakeUrl,
}); },
{
flake_dir: values.dest || "myclan",
},
);
setAppState((s) => ({ ...s, isJoined: true })); setAppState((s) => ({ ...s, isJoined: true }));
} catch (error) { } catch (error) {
toast.error( toast.error(