Add bots files handling logic

This commit is contained in:
2026-10-21 17:56:09 +01:00
parent b0af4db1bf
commit d17a4a7bdd
97 changed files with 5843 additions and 41 deletions
@@ -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>
);
}