added danger zones to storage managament menu

This commit is contained in:
onysd 2026-09-03 10:04:46 +03:00
parent a00f6ad814
commit 5a7618aaba
7 changed files with 80 additions and 28 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -23,8 +23,8 @@
})(); })();
</script> </script>
<script type="module" crossorigin src="/assets/index-NtDKRYU1.js"></script> <script type="module" crossorigin src="/assets/index-DL3krfqb.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CU8XZPsy.css"> <link rel="stylesheet" crossorigin href="/assets/index-B7j2PW0q.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View file

@ -225,6 +225,14 @@ const CATEGORY_AGE_FIELDS: { key: string; envKey: string; label: string }[] = [
{ key: "avatar", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_AVATAR", label: "Avatar" } { key: "avatar", envKey: "TELESRV_STORAGE_RETENTION_MAX_AGE_AVATAR", label: "Avatar" }
]; ];
// DANGER_ZONE_CATEGORY_KEYS marks the two CATEGORY_AGE_FIELDS/manual-purge
// categories that reach beyond ordinary per-user media: Avatar also covers
// the built-in system bots' own profile photos, and GIF also covers the
// server's bundled GIF catalog (@gif). CategoryRetentionModal and
// ManualPurgeStorageModal both split these into their own boxed "Danger
// zone" section instead of listing them alongside Photo/Video/Music/etc.
const DANGER_ZONE_CATEGORY_KEYS = new Set(["gif", "avatar"]);
// Mirrors internal/app/files.MaxUploadPartBytes * MaxUploadParts -- the // Mirrors internal/app/files.MaxUploadPartBytes * MaxUploadParts -- the
// protocol's own upload-part-count ceiling that TELESRV_STORAGE_MAX_UPLOAD_FILE_BYTES // protocol's own upload-part-count ceiling that TELESRV_STORAGE_MAX_UPLOAD_FILE_BYTES
// can never legally exceed (internal/config validates this server-side too; // can never legally exceed (internal/config validates this server-side too;
@ -420,10 +428,7 @@ function CategoryRetentionModal({
<p className="env-field-desc"> <p className="env-field-desc">
{"Leave a category at 0 to inherit the shared Retention age. The mode switch still applies to all of them -- these only change how old that one category's media must be."} {"Leave a category at 0 to inherit the shared Retention age. The mode switch still applies to all of them -- these only change how old that one category's media must be."}
</p> </p>
<p className="modal-warn-note"> {CATEGORY_AGE_FIELDS.filter((f) => !DANGER_ZONE_CATEGORY_KEYS.has(f.key)).map((field) => (
{"Danger zone: Avatar and GIF are shared buckets, not per-user ones. Avatar also covers the built-in system bots' own profile photos (BotFather, Stickers, ChatBot, VerifyBot, the official system account), and GIF also covers the server's bundled GIF catalog (@gif) -- a short age here purges those right along with ordinary user media."}
</p>
{CATEGORY_AGE_FIELDS.map((field) => (
<DurationField <DurationField
key={field.key} key={field.key}
label={field.label} label={field.label}
@ -433,6 +438,24 @@ function CategoryRetentionModal({
onChange={(minutes) => onChange(field.key, minutes)} onChange={(minutes) => onChange(field.key, minutes)}
/> />
))} ))}
<div className="danger-zone-box">
<div className="danger-zone-box-title">{"Danger zone"}</div>
<div className="danger-zone-box-body">
<p className="env-field-desc">
{"Also affects built-in system bot avatars and the bundled GIF catalog (@gif), not just user media."}
</p>
{CATEGORY_AGE_FIELDS.filter((f) => DANGER_ZONE_CATEGORY_KEYS.has(f.key)).map((field) => (
<DurationField
key={field.key}
label={field.label}
help={"Inherits the shared Retention age when left at 0."}
minutes={categoryAgeMinutes[field.key] || "0"}
disabled={disabled}
onChange={(minutes) => onChange(field.key, minutes)}
/>
))}
</div>
</div>
</div> </div>
<div className="modal-actions"> <div className="modal-actions">
<button className="btn primary" type="button" onClick={onClose}>{"Close"}</button> <button className="btn primary" type="button" onClick={onClose}>{"Close"}</button>
@ -491,21 +514,32 @@ function ManualPurgeStorageModal({ onClose }: { onClose: () => void }) {
<p className="env-field-desc"> <p className="env-field-desc">
{"Deletes the file bytes of every document/photo matching the categories below, right now -- independent of the retention mode/age configured above. The message/profile-photo itself is never deleted, only its file; a purged item starts showing as unavailable. Leave \"Created before\" empty to purge everything in the selected categories, regardless of age."} {"Deletes the file bytes of every document/photo matching the categories below, right now -- independent of the retention mode/age configured above. The message/profile-photo itself is never deleted, only its file; a purged item starts showing as unavailable. Leave \"Created before\" empty to purge everything in the selected categories, regardless of age."}
</p> </p>
<p className="modal-warn-note">
{"Danger zone: Avatar and GIF are shared buckets, not per-user ones. Selecting Avatar also purges the built-in system bots' own profile photos (BotFather, Stickers, ChatBot, VerifyBot, the official system account) -- these are bundled with the server and restore themselves automatically on the next restart. Selecting GIF also purges the server's bundled GIF catalog (@gif) entries, and those do NOT restore themselves: the catalog only re-imports a GIF whose source file is still sitting in TELESRV_GIF_SEED_DIR, so a purge is permanent for any catalog entry whose original file was since moved/deleted."}
</p>
<div className="attr-block"> <div className="attr-block">
<label className="checkline"> <label className="checkline">
<input type="checkbox" checked={allSelected} onChange={toggleAll} /> <input type="checkbox" checked={allSelected} onChange={toggleAll} />
{" "}{"Select all"} {" "}{"Select all"}
</label> </label>
{CATEGORY_AGE_FIELDS.map((field) => ( {CATEGORY_AGE_FIELDS.filter((f) => !DANGER_ZONE_CATEGORY_KEYS.has(f.key)).map((field) => (
<label key={field.key} className="checkline"> <label key={field.key} className="checkline">
<input type="checkbox" checked={Boolean(selected[field.key])} onChange={() => toggle(field.key)} /> <input type="checkbox" checked={Boolean(selected[field.key])} onChange={() => toggle(field.key)} />
{" "}{field.label} {" "}{field.label}
</label> </label>
))} ))}
</div> </div>
<div className="danger-zone-box">
<div className="danger-zone-box-title">{"Danger zone"}</div>
<div className="danger-zone-box-body">
<p className="env-field-desc">
{"Avatar also purges system bot avatars (auto-restored on next restart). GIF also purges the bundled GIF catalog (@gif) -- permanently, unless its source file is still in TELESRV_GIF_SEED_DIR."}
</p>
{CATEGORY_AGE_FIELDS.filter((f) => DANGER_ZONE_CATEGORY_KEYS.has(f.key)).map((field) => (
<label key={field.key} className="checkline">
<input type="checkbox" checked={Boolean(selected[field.key])} onChange={() => toggle(field.key)} />
{" "}{field.label}
</label>
))}
</div>
</div>
<label className="duration-field"> <label className="duration-field">
<span>{"Created before (optional)"}</span> <span>{"Created before (optional)"}</span>
<input type="date" value={dateValue} onChange={(event) => setDateValue(event.target.value)} /> <input type="date" value={dateValue} onChange={(event) => setDateValue(event.target.value)} />

View file

@ -206,6 +206,40 @@
border-top: 0; border-top: 0;
} }
/* A GitHub-Settings-style "Danger Zone" box: a red-bordered card with its own
title strip, wrapping the specific fields/toggles that reach outside plain
per-user data (e.g. StoragePage's Avatar/GIF retention controls, which also
govern the built-in system bot avatars and the bundled GIF catalog) --
distinct from .danger-zone above, which is just a plain action-row divider,
not a bordered card. */
.danger-zone-box {
margin: 4px 0 14px;
border: 1px solid var(--danger-border);
border-radius: var(--radius);
overflow: hidden;
}
.danger-zone-box-title {
padding: 8px 12px;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.02em;
text-transform: uppercase;
color: var(--danger);
background: var(--danger-tint);
border-bottom: 1px solid var(--danger-border);
}
.danger-zone-box-body {
padding: 10px 12px;
display: grid;
gap: 10px;
}
.danger-zone-box-body .env-field-desc {
color: var(--danger-text, var(--danger));
}
.authorization-block { .authorization-block {
display: grid; display: grid;
gap: 10px; gap: 10px;

View file

@ -461,22 +461,6 @@
border-radius: var(--radius); border-radius: var(--radius);
} }
/* Warns that an Avatar/GIF retention control also reaches built-in server
resources (system bot avatars, the bundled GIF catalog), not just
ordinary user media -- see StoragePage.tsx's CategoryRetentionModal and
ManualPurgeStorageModal. */
.modal-warn-note {
padding: 9px 12px;
background: var(--warn-tint);
border: 1px solid var(--warn-border);
border-radius: var(--radius);
color: var(--warn);
font-size: 12px;
font-weight: 700;
line-height: 1.4;
margin: 0 0 10px;
}
.secret-reveal-label { .secret-reveal-label {
display: flex; display: flex;
align-items: center; align-items: center;