Add backups with S3, daily cost push, automation templates and a browser tool
- Daily backups (database + config) into <data_dir>/backups, keep N, optional upload to any S3-compatible bucket (SigV4, bucket pruning); settings panel with run-now, list and download - Daily cost summary push at 20:00 (opt-in per user, preview button) - Automations from templates (dependency check, security review, test health, daily summary, TODO report, docs freshness) via POST /jobs/create - browser tool: headless Chromium over CDP – click, type, keys, waits, JavaScript, navigation, screenshots, console errors; requests to private network addresses are blocked (sub-resources and redirects included) - Secrets masked in the config view: S3 secret key, metrics token - Replace the remaining text arrows/refresh glyphs with SVG icons Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
873447ed17
commit
01c1484033
25 files changed
+1720
-47
No files matched your search
@@ -141,6 +141,44 @@ function VersionInfo() {
|
||||
return <div className="hint mono">{info ? `CodeCraft ${info.version}${info.commit ? ` (${info.commit})` : ""}` : "…"}</div>;
|
||||
}
|
||||
|
||||
/** Daily cost summary at 20:00 (per user, all devices). */
|
||||
function DigestToggle() {
|
||||
const t = useT();
|
||||
const [on, setOn] = useState<boolean>();
|
||||
useEffect(() => {
|
||||
get<{ enabled: boolean }>("/push/digest").then((r) => setOn(r.enabled)).catch(() => {});
|
||||
}, []);
|
||||
if (on === undefined) return null;
|
||||
return (
|
||||
<div className="row" style={{ gap: 8, flexBasis: "100%", alignItems: "center", flexWrap: "wrap" }}>
|
||||
<label className="row" style={{ gap: 6, alignItems: "center" }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={on}
|
||||
onChange={(e) => {
|
||||
const v = e.target.checked;
|
||||
setOn(v);
|
||||
put("/push/digest", { enabled: v }).catch((err) => (setOn(!v), toast(err.message)));
|
||||
}}
|
||||
/>
|
||||
{t("settings.digest")}
|
||||
</label>
|
||||
{on && (
|
||||
<button
|
||||
className="small ghost"
|
||||
onClick={() =>
|
||||
post<{ sent: number; errors: string[] }>("/push/digest")
|
||||
.then((r) => toast(r.sent ? t("settings.pushTestSent") : r.errors.join("; ") || t("settings.pushTestNone"), r.sent ? "success" : "error"))
|
||||
.catch((e) => toast(e.message))
|
||||
}
|
||||
>
|
||||
{t("settings.digestNow")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Web Push on this device: enable/disable and a test notification. */
|
||||
function PushControl() {
|
||||
const t = useT();
|
||||
@@ -189,6 +227,7 @@ function PushControl() {
|
||||
<button className="small ghost" disabled={busy} onClick={run(async () => (await disablePush(), setState("off")))}>
|
||||
{t("settings.pushDisable")}
|
||||
</button>
|
||||
<DigestToggle />
|
||||
</>
|
||||
) : (
|
||||
<button className="small" disabled={busy} onClick={run(async () => (await enablePush(), setState("on")))}>
|
||||
|
||||
Reference in new issue
Block a user