fix ui
This commit is contained in:
@@ -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 { };
|
||||||
|
|||||||
@@ -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,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
try {
|
||||||
|
const response = await getMachineSchema(
|
||||||
|
{
|
||||||
clanImports: [],
|
clanImports: [],
|
||||||
}).then((response) => {
|
},
|
||||||
|
{
|
||||||
|
flake_dir: clanDir,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (response.statusText == "OK") {
|
if (response.statusText == "OK") {
|
||||||
formHooks.setValue("schema", response.data.schema);
|
formHooks.setValue("schema", response.data.schema);
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
|
clanErrorToast(e as AxiosError<HTTPValidationError>);
|
||||||
// Only re-run if global clanName has changed
|
}
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
data.name,
|
||||||
|
{
|
||||||
clan: data.config.formData,
|
clan: data.config.formData,
|
||||||
clanImports: data.modules,
|
clanImports: data.modules,
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
flake_dir: clanDir,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
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) => {
|
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(
|
||||||
|
|||||||
Reference in New Issue
Block a user