feat: auto cleanup workflow history runs and expired certificates

This commit is contained in:
Fu Diwei
2025-03-19 17:12:24 +08:00
parent 914c5b4870
commit e27d4f11ee
19 changed files with 355 additions and 55 deletions

View File

@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import {
ApiOutlined as ApiOutlinedIcon,
DatabaseOutlined as DatabaseOutlinedIcon,
LockOutlined as LockOutlinedIcon,
SendOutlined as SendOutlinedIcon,
UserOutlined as UserOutlinedIcon,
@@ -69,6 +70,15 @@ const Settings = () => {
</Space>
),
},
{
key: "persistence",
label: (
<Space>
<DatabaseOutlinedIcon />
<label>{t("settings.persistence.tab")}</label>
</Space>
),
},
]}
activeTabKey={tabValue}
onTabChange={(key) => {

View File

@@ -18,7 +18,7 @@ const SettingsAccount = () => {
const [notificationApi, NotificationContextHolder] = notification.useNotification();
const formSchema = z.object({
username: z.string({ message: "settings.account.form.email.placeholder" }).email({ message: t("common.errmsg.email_invalid") }),
username: z.string({ message: t("settings.account.form.email.placeholder") }).email({ message: t("common.errmsg.email_invalid") }),
});
const formRule = createSchemaFieldRule(formSchema);
const {

View File

@@ -0,0 +1,132 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Button, Form, InputNumber, Skeleton, message, notification } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { produce } from "immer";
import { z } from "zod";
import Show from "@/components/Show";
import { type PersistenceSettingsContent, SETTINGS_NAMES, type SettingsModel } from "@/domain/settings";
import { useAntdForm } from "@/hooks";
import { get as getSettings, save as saveSettings } from "@/repository/settings";
import { getErrMsg } from "@/utils/error";
const SettingsPersistence = () => {
const { t } = useTranslation();
const [messageApi, MessageContextHolder] = message.useMessage();
const [notificationApi, NotificationContextHolder] = notification.useNotification();
const [settings, setSettings] = useState<SettingsModel<PersistenceSettingsContent>>();
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
const settings = await getSettings<PersistenceSettingsContent>(SETTINGS_NAMES.PERSISTENCE);
setSettings(settings);
setLoading(false);
};
fetchData();
}, []);
const formSchema = z.object({
workflowRunsMaxDaysRetention: z
.number({ message: t("settings.persistence.form.workflow_runs_max_days.placeholder") })
.gte(0, t("settings.persistence.form.workflow_runs_max_days.placeholder")),
expiredCertificatesMaxDaysRetention: z
.number({ message: t("settings.persistence.form.expired_certificates_max_days.placeholder") })
.gte(0, t("settings.persistence.form.expired_certificates_max_days.placeholder")),
});
const formRule = createSchemaFieldRule(formSchema);
const {
form: formInst,
formPending,
formProps,
} = useAntdForm<z.infer<typeof formSchema>>({
initialValues: {
workflowRunsMaxDaysRetention: settings?.content?.workflowRunsMaxDaysRetention ?? 0,
expiredCertificatesMaxDaysRetention: settings?.content?.expiredCertificatesMaxDaysRetention ?? 0,
},
onSubmit: async (values) => {
try {
await saveSettings(
produce(settings!, (draft) => {
draft.content ??= {} as PersistenceSettingsContent;
draft.content.workflowRunsMaxDaysRetention = values.workflowRunsMaxDaysRetention;
draft.content.expiredCertificatesMaxDaysRetention = values.expiredCertificatesMaxDaysRetention;
})
);
messageApi.success(t("common.text.operation_succeeded"));
} catch (err) {
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
throw err;
}
},
});
const [formChanged, setFormChanged] = useState(false);
const handleInputChange = () => {
const changed =
formInst.getFieldValue("workflowRunsMaxDaysRetention") !== formProps.initialValues?.workflowRunsMaxDaysRetention ||
formInst.getFieldValue("expiredCertificatesMaxDaysRetention") !== formProps.initialValues?.workflowRunsMaxDaysRetention;
setFormChanged(changed);
};
return (
<>
{MessageContextHolder}
{NotificationContextHolder}
<Show when={!loading} fallback={<Skeleton active />}>
<div className="md:max-w-[40rem]">
<Form {...formProps} form={formInst} disabled={formPending} layout="vertical">
<Form.Item
name="workflowRunsMaxDaysRetention"
label={t("settings.persistence.form.workflow_runs_max_days.label")}
extra={<span dangerouslySetInnerHTML={{ __html: t("settings.persistence.form.workflow_runs_max_days.extra") }}></span>}
rules={[formRule]}
>
<InputNumber
className="w-full"
min={0}
max={36500}
placeholder={t("settings.persistence.form.workflow_runs_max_days.placeholder")}
addonAfter={t("settings.persistence.form.workflow_runs_max_days.unit")}
onChange={handleInputChange}
/>
</Form.Item>
<Form.Item
name="expiredCertificatesMaxDaysRetention"
label={t("settings.persistence.form.expired_certificates_max_days.label")}
extra={<span dangerouslySetInnerHTML={{ __html: t("settings.persistence.form.expired_certificates_max_days.extra") }}></span>}
rules={[formRule]}
>
<InputNumber
className="w-full"
min={0}
max={36500}
placeholder={t("settings.persistence.form.expired_certificates_max_days.placeholder")}
addonAfter={t("settings.persistence.form.expired_certificates_max_days.unit")}
onChange={handleInputChange}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={!formChanged} loading={formPending}>
{t("common.button.save")}
</Button>
</Form.Item>
</Form>
</div>
</Show>
</>
);
};
export default SettingsPersistence;