Add bots files handling logic
This commit is contained in:
@@ -5,6 +5,7 @@ import sanitizeUserBio from "@/src/functions/backend/db/users/sanitize-user-bio"
|
||||
import grabUserPasswordHash from "@/src/functions/backend/auth/grab-user-password-hash";
|
||||
import type { PagePropsType } from "@/src/types";
|
||||
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
|
||||
import grabStorageQuotaOverview from "@/src/functions/backend/db/app-settings/grab-storage-quota-overview";
|
||||
|
||||
type Params = Parameters<BunextPageServerFn<PagePropsType>>[0];
|
||||
|
||||
@@ -40,6 +41,7 @@ export default async function grabSettingsServerProps({ req }: Params) {
|
||||
props: {
|
||||
person: sanitizeUserBio({ user: user_res.singleRes }),
|
||||
has_password,
|
||||
storage_quota: await grabStorageQuotaOverview().catch(() => null),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { useState } from "react";
|
||||
import fetchApi from "@moduletrace/twui/utils/fetch/fetchApi";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
import Form from "@moduletrace/twui/form/Form";
|
||||
import Input from "@moduletrace/twui/form/Input";
|
||||
import Span from "@moduletrace/twui/layout/Span";
|
||||
import Stack from "@moduletrace/twui/layout/Stack";
|
||||
import type { StorageQuotaOverview } from "@/src/functions/backend/db/app-settings/grab-storage-quota-overview";
|
||||
import SettingsSaveBar from "../../(partials)/settings-save-bar";
|
||||
|
||||
type Props = {
|
||||
storage?: StorageQuotaOverview | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* The per-folder storage quota for what bots save (a bot's own folder and
|
||||
* each workspace's shared one). Saved on its own, without a reload.
|
||||
*/
|
||||
export default function StorageQuotaForm({ storage }: Props) {
|
||||
const [overview, setOverview] = useState(storage || undefined);
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const [value, setValue] = useState(String(storage?.quota_mb ?? ""));
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [status, setStatus] = useState<{ msg?: string; error?: boolean; success?: boolean }>();
|
||||
|
||||
const save = async () => {
|
||||
setLoading(true);
|
||||
setStatus(undefined);
|
||||
|
||||
const res = await fetchApi<{}, APIResponseObject<StorageQuotaOverview>>(
|
||||
"/api/admin/settings/storage-quota",
|
||||
{ method: "POST", body: { quota_mb: Number(value) || 0 } },
|
||||
).catch(() => null);
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (!res?.success || !res.singleRes) {
|
||||
setStatus({ error: true, msg: res?.msg || `Couldn't save the quota.` });
|
||||
return;
|
||||
}
|
||||
|
||||
setOverview(res.singleRes);
|
||||
setValue(String(res.singleRes.quota_mb));
|
||||
setDirty(false);
|
||||
setStatus({ success: true, msg: `Saved: ${res.singleRes.quota_mb} MB per folder.` });
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
save().catch(() => {});
|
||||
}}
|
||||
className="w-full gap-3! items-stretch"
|
||||
>
|
||||
<Stack className="w-full gap-1! items-stretch">
|
||||
<Input
|
||||
label="Megabytes per folder"
|
||||
type="number"
|
||||
min={0}
|
||||
step={1}
|
||||
value={value}
|
||||
onChange={(event) => {
|
||||
setValue(event.target.value);
|
||||
setDirty(true);
|
||||
setStatus(undefined);
|
||||
}}
|
||||
info="A bot's own folder and each workspace's folder each get this much. A write past it is refused, so the bot clears space first."
|
||||
/>
|
||||
{overview ? (
|
||||
<Span className="text-[13px] leading-[18px] text-ink-secondary">
|
||||
{`${overview.total_files} file(s) in ${overview.folders} folder(s), ${(overview.total_bytes / (1024 * 1024)).toFixed(1)} MB in total.`}
|
||||
</Span>
|
||||
) : null}
|
||||
</Stack>
|
||||
<SettingsSaveBar
|
||||
dirty={dirty}
|
||||
loading={loading}
|
||||
status={status}
|
||||
save_label="Save quota"
|
||||
onDiscard={() => {
|
||||
setValue(String(overview?.quota_mb ?? ""));
|
||||
setDirty(false);
|
||||
setStatus(undefined);
|
||||
}}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useContext } from "react";
|
||||
import { AppContext } from "@/src/pages/__root";
|
||||
import canManageCrew from "@/src/utils/rbac/can-manage-crew";
|
||||
import SettingsGroup from "../../(partials)/settings-group";
|
||||
import StorageQuotaForm from "../(partials)/storage-quota-form";
|
||||
|
||||
/**
|
||||
* What bots keep on disk: the quota each of their folders gets. Only a
|
||||
* crew manager sees it, since it's an install-wide limit.
|
||||
*/
|
||||
export default function StorageSection() {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
|
||||
if (!canManageCrew({ user_types: pageProps?.user_types })) return null;
|
||||
|
||||
return (
|
||||
<SettingsGroup
|
||||
title="Bot files Quota"
|
||||
description="Bots keep the documents, images and reports they make in folders on the app's data volume, never on a computer. This is how much each folder may hold."
|
||||
id="storage"
|
||||
>
|
||||
<StorageQuotaForm storage={pageProps?.storage_quota} />
|
||||
</SettingsGroup>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import AppearanceSection from "./(sections)/appearance-section";
|
||||
import StorageSection from "./(sections)/storage-section";
|
||||
import SettingsShell from "../(partials)/settings-shell";
|
||||
import settingsPageMeta from "../(functions)/settings-page-meta";
|
||||
|
||||
@@ -9,6 +10,7 @@ export default function AdminSettingsGeneralPage() {
|
||||
description="How the app looks and behaves."
|
||||
>
|
||||
<AppearanceSection />
|
||||
<StorageSection />
|
||||
</SettingsShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user