Merge pull request 'fix ui' (#528) from hsjobeki-hsjobeki into main
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
{
|
||||
packages = {
|
||||
# disabled because frontend is broken after cli refactoring
|
||||
# ui = base.pkg.global;
|
||||
ui = base.pkg.global;
|
||||
ui-assets = pkgs.callPackage ./nix/ui-assets.nix { };
|
||||
# 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 { };
|
||||
|
||||
@@ -77,18 +77,18 @@ export default function RootLayout({
|
||||
show={showSidebarDerived}
|
||||
onClose={() => setShowSidebar(false)}
|
||||
clanSelect={
|
||||
appState.data.clanName && (
|
||||
appState.data.clanDir && (
|
||||
<Select
|
||||
color="secondary"
|
||||
label="clan"
|
||||
fullWidth
|
||||
variant="standard"
|
||||
disableUnderline
|
||||
value={appState.data.clanName}
|
||||
value={appState.data.clanDir}
|
||||
onChange={(ev) => {
|
||||
appState.setAppState((c) => ({
|
||||
...c,
|
||||
clanName: ev.target.value,
|
||||
clanDir: ev.target.value,
|
||||
}));
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -4,12 +4,13 @@ import { MachineContextProvider } from "@/components/hooks/useMachines";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const {
|
||||
data: { clanName },
|
||||
data: { clanDir },
|
||||
} = useAppState();
|
||||
return (
|
||||
<>
|
||||
{clanName && (
|
||||
<MachineContextProvider flakeName={clanName}>
|
||||
{!clanDir && <div>No clan selected</div>}
|
||||
{clanDir && (
|
||||
<MachineContextProvider flakeName={clanDir}>
|
||||
{children}
|
||||
</MachineContextProvider>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { getMachineSchema } from "@/api/machine/machine";
|
||||
import { HTTPValidationError } from "@/api/model";
|
||||
import { useListClanModules } from "@/api/modules/modules";
|
||||
import { clanErrorToast } from "@/error/errorToast";
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
@@ -15,6 +17,7 @@ import InputLabel from "@mui/material/InputLabel";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import OutlinedInput from "@mui/material/OutlinedInput";
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import { AxiosError } from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller } from "react-hook-form";
|
||||
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;
|
||||
|
||||
const SchemaSuccessMsg = () => (
|
||||
@@ -93,22 +62,34 @@ const SchemaErrorMsg = (props: SchemaErrorMsgProps) => (
|
||||
);
|
||||
|
||||
export default function ClanModules(props: ClanModulesProps) {
|
||||
const { clanName, formHooks } = props;
|
||||
const { data, isLoading } = useListClanModules(clanName);
|
||||
const { clanDir, formHooks } = props;
|
||||
const { data, isLoading } = useListClanModules({ flake_dir: clanDir });
|
||||
const [schemaError] = useState<string | null>(null);
|
||||
const selectedModules = formHooks.watch("modules");
|
||||
useEffect(() => {
|
||||
getMachineSchema(clanName, {
|
||||
const load = async () => {
|
||||
try {
|
||||
const response = await getMachineSchema(
|
||||
{
|
||||
clanImports: [],
|
||||
}).then((response) => {
|
||||
},
|
||||
{
|
||||
flake_dir: clanDir,
|
||||
},
|
||||
);
|
||||
|
||||
if (response.statusText == "OK") {
|
||||
formHooks.setValue("schema", response.data.schema);
|
||||
}
|
||||
});
|
||||
|
||||
// Only re-run if global clanName has changed
|
||||
} catch (e) {
|
||||
clanErrorToast(e as AxiosError<HTTPValidationError>);
|
||||
}
|
||||
};
|
||||
load();
|
||||
// Only re-run if global clanDir has changed
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [clanName]);
|
||||
}, [clanDir]);
|
||||
|
||||
const isSchemaLoading = formHooks.watch("isSchemaLoading");
|
||||
|
||||
const handleChange = (
|
||||
@@ -119,9 +100,14 @@ export default function ClanModules(props: ClanModulesProps) {
|
||||
} = event;
|
||||
const newValue = typeof value === "string" ? value.split(",") : value;
|
||||
formHooks.setValue("modules", newValue);
|
||||
getMachineSchema(clanName, {
|
||||
getMachineSchema(
|
||||
{
|
||||
clanImports: newValue,
|
||||
})
|
||||
},
|
||||
{
|
||||
flake_dir: clanDir,
|
||||
},
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.statusText == "OK") {
|
||||
formHooks.setValue("schema", response.data.schema);
|
||||
|
||||
@@ -29,7 +29,7 @@ interface PureCustomConfigProps extends FormStepContentProps {
|
||||
initialValues: any;
|
||||
}
|
||||
export function CustomConfig(props: FormStepContentProps) {
|
||||
const { formHooks, clanName } = props;
|
||||
const { formHooks, clanDir } = props;
|
||||
const schema = formHooks.watch("schema");
|
||||
|
||||
const initialValues = useMemo(
|
||||
@@ -49,7 +49,7 @@ export function CustomConfig(props: FormStepContentProps) {
|
||||
|
||||
return (
|
||||
<PureCustomConfig
|
||||
clanName={clanName}
|
||||
clanDir={clanDir}
|
||||
formHooks={formHooks}
|
||||
initialValues={initialValues}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { putMachine } from "@/api/machine/machine";
|
||||
import { setMachineConfig } from "@/api/machine/machine";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -21,7 +21,7 @@ import { CreateMachineForm, FormStep } from "./interfaces";
|
||||
|
||||
export function CreateMachineForm() {
|
||||
const {
|
||||
data: { clanName },
|
||||
data: { clanDir },
|
||||
} = useAppState();
|
||||
const formHooks = useForm<CreateMachineForm>({
|
||||
defaultValues: {
|
||||
@@ -41,8 +41,8 @@ export function CreateMachineForm() {
|
||||
{
|
||||
id: "modules",
|
||||
label: "Modules",
|
||||
content: clanName ? (
|
||||
<ClanModules clanName={clanName} formHooks={formHooks} />
|
||||
content: clanDir ? (
|
||||
<ClanModules clanDir={clanDir} formHooks={formHooks} />
|
||||
) : (
|
||||
<LinearProgress />
|
||||
),
|
||||
@@ -50,8 +50,8 @@ export function CreateMachineForm() {
|
||||
{
|
||||
id: "config",
|
||||
label: "Customize",
|
||||
content: clanName ? (
|
||||
<CustomConfig formHooks={formHooks} clanName={clanName} />
|
||||
content: clanDir ? (
|
||||
<CustomConfig formHooks={formHooks} clanDir={clanDir} />
|
||||
) : (
|
||||
<LinearProgress />
|
||||
),
|
||||
@@ -74,15 +74,21 @@ export function CreateMachineForm() {
|
||||
|
||||
async function onSubmit(data: CreateMachineForm) {
|
||||
console.log({ data }, "Aggregated Data; creating machine from");
|
||||
if (clanName) {
|
||||
if (clanDir) {
|
||||
if (!data.name) {
|
||||
toast.error("Machine name should not be empty");
|
||||
return;
|
||||
}
|
||||
await putMachine(clanName, data.name, {
|
||||
await setMachineConfig(
|
||||
data.name,
|
||||
{
|
||||
clan: data.config.formData,
|
||||
clanImports: data.modules,
|
||||
});
|
||||
},
|
||||
{
|
||||
flake_dir: clanDir,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export type FormStep = {
|
||||
|
||||
export interface FormStepContentProps {
|
||||
formHooks: FormHooks;
|
||||
clanName: string;
|
||||
clanDir: string;
|
||||
}
|
||||
|
||||
export type FormStepContent = ReactElement<FormStepContentProps>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useListAllFlakes } from "@/api/flake/flake";
|
||||
import { FlakeListResponse } from "@/api/model";
|
||||
import { AxiosError, AxiosResponse } from "axios";
|
||||
// import { useListAllFlakes } from "@/api/flake/flake";
|
||||
// import { FlakeListResponse } from "@/api/model";
|
||||
import { AxiosError } from "axios";
|
||||
import React, {
|
||||
Dispatch,
|
||||
ReactNode,
|
||||
@@ -9,7 +9,6 @@ import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { KeyedMutator } from "swr";
|
||||
|
||||
type AppContextType = {
|
||||
// data: AxiosResponse<{}, any> | undefined;
|
||||
@@ -19,7 +18,7 @@ type AppContextType = {
|
||||
error: AxiosError<any> | undefined;
|
||||
|
||||
setAppState: Dispatch<SetStateAction<AppState>>;
|
||||
mutate: KeyedMutator<AxiosResponse<FlakeListResponse, any>>;
|
||||
// mutate: KeyedMutator<AxiosResponse<FlakeListResponse, any>>;
|
||||
swrKey: string | false | Record<any, any>;
|
||||
};
|
||||
|
||||
@@ -27,28 +26,45 @@ export const AppContext = createContext<AppContextType>({} as AppContextType);
|
||||
|
||||
type AppState = {
|
||||
isJoined?: boolean;
|
||||
clanName?: string;
|
||||
flakes?: FlakeListResponse["flakes"];
|
||||
clanDir?: string;
|
||||
flakes?: string[]; //FlakeListResponse["flakes"];
|
||||
};
|
||||
|
||||
interface AppContextProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
const mock = {
|
||||
data: { flakes: [] },
|
||||
};
|
||||
|
||||
// list_clans
|
||||
|
||||
export const WithAppState = (props: AppContextProviderProps) => {
|
||||
const { children } = props;
|
||||
// const {
|
||||
// isLoading,
|
||||
// error,
|
||||
// swrKey,
|
||||
// data: flakesResponse,
|
||||
// mutate,
|
||||
// } = useListAllFlakes({
|
||||
// swr: {
|
||||
// revalidateIfStale: false,
|
||||
// revalidateOnFocus: false,
|
||||
// revalidateOnReconnect: false,
|
||||
// },
|
||||
// });
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
swrKey,
|
||||
data: flakesResponse,
|
||||
mutate,
|
||||
} = useListAllFlakes({
|
||||
swr: {
|
||||
revalidateIfStale: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
},
|
||||
});
|
||||
} = {
|
||||
isLoading: false,
|
||||
error: undefined,
|
||||
swrKey: "",
|
||||
data: mock,
|
||||
};
|
||||
const [data, setAppState] = useState<AppState>({ isJoined: false });
|
||||
|
||||
useEffect(() => {
|
||||
@@ -57,7 +73,7 @@ export const WithAppState = (props: AppContextProviderProps) => {
|
||||
data: { flakes },
|
||||
} = flakesResponse;
|
||||
if (flakes.length >= 1) {
|
||||
setAppState((c) => ({ ...c, clanName: flakes[0], isJoined: true }));
|
||||
setAppState((c) => ({ ...c, clanDir: flakes[0], isJoined: true }));
|
||||
}
|
||||
setAppState((c) => ({ ...c, flakes }));
|
||||
}
|
||||
@@ -71,7 +87,7 @@ export const WithAppState = (props: AppContextProviderProps) => {
|
||||
isLoading,
|
||||
error,
|
||||
swrKey,
|
||||
mutate,
|
||||
// mutate,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -65,7 +65,7 @@ export const MachineContextProvider = (props: MachineContextProviderProps) => {
|
||||
isValidating,
|
||||
mutate,
|
||||
swrKey,
|
||||
} = useListMachines(flakeName);
|
||||
} = useListMachines({ flake_dir: flakeName });
|
||||
const [filters, setFilters] = useState<Filters>([]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
|
||||
12
pkgs/ui/src/error/errorToast.ts
Normal file
12
pkgs/ui/src/error/errorToast.ts
Normal 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);
|
||||
}
|
||||
@@ -100,10 +100,14 @@ export default function JoinPrequel() {
|
||||
onSubmit={handleSubmit(async (values) => {
|
||||
if (workflow === "create") {
|
||||
try {
|
||||
await createFlake({
|
||||
flake_name: values.dest || "default",
|
||||
await createFlake(
|
||||
{
|
||||
url: values.flakeUrl,
|
||||
});
|
||||
},
|
||||
{
|
||||
flake_dir: values.dest || "myclan",
|
||||
},
|
||||
);
|
||||
setAppState((s) => ({ ...s, isJoined: true }));
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
|
||||
Reference in New Issue
Block a user