mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
feat: support removing certificates
This commit is contained in:
parent
831f0ee5d9
commit
3a2baba746
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultExpireSubject = "您有 ${COUNT} 张证书即将过期"
|
defaultExpireSubject = "有 ${COUNT} 张证书即将过期"
|
||||||
defaultExpireMessage = "有 ${COUNT} 张证书即将过期,域名分别为 ${DOMAINS},请保持关注!"
|
defaultExpireMessage = "有 ${COUNT} 张证书即将过期,域名分别为 ${DOMAINS},请保持关注!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ func (s *CertificateService) InitSchedule(ctx context.Context) error {
|
|||||||
err := scheduler.Add("certificate", "0 0 * * *", func() {
|
err := scheduler.Add("certificate", "0 0 * * *", func() {
|
||||||
certs, err := s.repo.ListExpireSoon(context.Background())
|
certs, err := s.repo.ListExpireSoon(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.GetLogger().Error("failed to get expire soon certificate", "err", err)
|
app.GetLogger().Error("failed to get certificates which expire soon", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ func (s *CertificateService) InitSchedule(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := notify.SendToAllChannels(notification.Subject, notification.Message); err != nil {
|
if err := notify.SendToAllChannels(notification.Subject, notification.Message); err != nil {
|
||||||
app.GetLogger().Error("failed to send expire soon certificate", "err", err)
|
app.GetLogger().Error("failed to send notification", "err", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/usual2970/certimate/internal/app"
|
"github.com/usual2970/certimate/internal/app"
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccessRepository struct{}
|
type AccessRepository struct{}
|
||||||
@ -27,7 +26,7 @@ func (r *AccessRepository) GetById(ctx context.Context, id string) (*domain.Acce
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !types.IsNil(record.Get("deleted")) {
|
if !record.GetDateTime("deleted").Time().IsZero() {
|
||||||
return nil, domain.ErrRecordNotFound
|
return nil, domain.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/usual2970/certimate/internal/app"
|
"github.com/usual2970/certimate/internal/app"
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/utils/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CertificateRepository struct{}
|
type CertificateRepository struct{}
|
||||||
@ -52,7 +51,7 @@ func (r *CertificateRepository) GetById(ctx context.Context, id string) (*domain
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !types.IsNil(record.Get("deleted")) {
|
if !record.GetDateTime("deleted").Time().IsZero() {
|
||||||
return nil, domain.ErrRecordNotFound
|
return nil, domain.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ export type NotifyTemplate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const defaultNotifyTemplate: NotifyTemplate = {
|
export const defaultNotifyTemplate: NotifyTemplate = {
|
||||||
subject: "您有 ${COUNT} 张证书即将过期",
|
subject: "有 ${COUNT} 张证书即将过期",
|
||||||
message: "有 ${COUNT} 张证书即将过期,域名分别为 ${DOMAINS},请保持关注!",
|
message: "有 ${COUNT} 张证书即将过期,域名分别为 ${DOMAINS},请保持关注!",
|
||||||
};
|
};
|
||||||
// #endregion
|
// #endregion
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
"certificate.action.view": "View certificate",
|
"certificate.action.view": "View certificate",
|
||||||
"certificate.action.delete": "Delete certificate",
|
"certificate.action.delete": "Delete certificate",
|
||||||
|
"certificate.action.delete.confirm": "Are you sure to delete this certificate?",
|
||||||
"certificate.action.download": "Download certificate",
|
"certificate.action.download": "Download certificate",
|
||||||
|
|
||||||
"certificate.props.subject_alt_names": "Name",
|
"certificate.props.subject_alt_names": "Name",
|
||||||
|
@ -13,6 +13,6 @@
|
|||||||
"dashboard.quick_actions": "Quick actions",
|
"dashboard.quick_actions": "Quick actions",
|
||||||
"dashboard.quick_actions.create_workflow": "Create workflow",
|
"dashboard.quick_actions.create_workflow": "Create workflow",
|
||||||
"dashboard.quick_actions.change_login_password": "Change login password",
|
"dashboard.quick_actions.change_login_password": "Change login password",
|
||||||
"dashboard.quick_actions.notification_settings": "Notification settings",
|
"dashboard.quick_actions.cofigure_notification": "Configure notificaion",
|
||||||
"dashboard.quick_actions.certificate_authority_configuration": "Certificate authority configuration"
|
"dashboard.quick_actions.configure_ca": "Configure certificate authority"
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"access.form.name.placeholder": "请输入授权名称",
|
"access.form.name.placeholder": "请输入授权名称",
|
||||||
"access.form.provider.label": "提供商",
|
"access.form.provider.label": "提供商",
|
||||||
"access.form.provider.placeholder": "请选择提供商",
|
"access.form.provider.placeholder": "请选择提供商",
|
||||||
"access.form.provider.tooltip": "提供商分为两种类型:<br>【DNS 提供商】您的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。<br>【主机提供商】您的服务器或云服务的托管方,用于部署签发的证书。<br><br>该字段保存后不可修改。",
|
"access.form.provider.tooltip": "提供商分为两种类型:<br>【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。<br>【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。<br><br>该字段保存后不可修改。",
|
||||||
"access.form.acmehttpreq_endpoint.label": "服务端点",
|
"access.form.acmehttpreq_endpoint.label": "服务端点",
|
||||||
"access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点",
|
"access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点",
|
||||||
"access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
"access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
"certificate.action.view": "查看证书",
|
"certificate.action.view": "查看证书",
|
||||||
"certificate.action.delete": "删除证书",
|
"certificate.action.delete": "删除证书",
|
||||||
|
"certificate.action.delete.confirm": "确定要删除此证书吗?",
|
||||||
"certificate.action.download": "下载证书",
|
"certificate.action.download": "下载证书",
|
||||||
|
|
||||||
"certificate.props.subject_alt_names": "名称",
|
"certificate.props.subject_alt_names": "名称",
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
"workflow.detail.orchestration.action.release.confirm": "确定要发布更改吗?",
|
"workflow.detail.orchestration.action.release.confirm": "确定要发布更改吗?",
|
||||||
"workflow.detail.orchestration.action.release.failed.uncompleted": "流程编排未完成,请检查是否有节点未配置",
|
"workflow.detail.orchestration.action.release.failed.uncompleted": "流程编排未完成,请检查是否有节点未配置",
|
||||||
"workflow.detail.orchestration.action.run": "执行",
|
"workflow.detail.orchestration.action.run": "执行",
|
||||||
"workflow.detail.orchestration.action.run.confirm": "你有尚未发布的更改。你确定要以最近一次发布的版本继续执行吗?",
|
"workflow.detail.orchestration.action.run.confirm": "你有尚未发布的更改。确定要以最近一次发布的版本继续执行吗?",
|
||||||
"workflow.detail.orchestration.action.run.prompt": "执行中……请稍后查看执行历史",
|
"workflow.detail.orchestration.action.run.prompt": "执行中……请稍后查看执行历史",
|
||||||
"workflow.detail.runs.tab": "执行历史"
|
"workflow.detail.runs.tab": "执行历史"
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"workflow_node.action.rename_branch": "重命名",
|
"workflow_node.action.rename_branch": "重命名",
|
||||||
"workflow_node.action.remove_branch": "删除分支",
|
"workflow_node.action.remove_branch": "删除分支",
|
||||||
|
|
||||||
"workflow_node.unsaved_changes.confirm": "你有尚未保存的更改。你确定要关闭面板吗?",
|
"workflow_node.unsaved_changes.confirm": "你有尚未保存的更改。确定要关闭面板吗?",
|
||||||
|
|
||||||
"workflow_node.start.label": "开始",
|
"workflow_node.start.label": "开始",
|
||||||
"workflow_node.start.form.trigger.label": "触发方式",
|
"workflow_node.start.form.trigger.label": "触发方式",
|
||||||
|
@ -130,7 +130,7 @@ const AccessList = () => {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { loading } = useRequest(
|
const { loading, run: refreshTableData } = useRequest(
|
||||||
() => {
|
() => {
|
||||||
const startIndex = (page - 1) * pageSize;
|
const startIndex = (page - 1) * pageSize;
|
||||||
const endIndex = startIndex + pageSize;
|
const endIndex = startIndex + pageSize;
|
||||||
@ -157,6 +157,7 @@ const AccessList = () => {
|
|||||||
// TODO: 有关联数据的不允许被删除
|
// TODO: 有关联数据的不允许被删除
|
||||||
try {
|
try {
|
||||||
await deleteAccess(data);
|
await deleteAccess(data);
|
||||||
|
refreshTableData();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||||
|
@ -4,13 +4,13 @@ import { useNavigate, useSearchParams } from "react-router-dom";
|
|||||||
import { DeleteOutlined as DeleteOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
|
import { DeleteOutlined as DeleteOutlinedIcon, SelectOutlined as SelectOutlinedIcon } from "@ant-design/icons";
|
||||||
import { PageHeader } from "@ant-design/pro-components";
|
import { PageHeader } from "@ant-design/pro-components";
|
||||||
import { useRequest } from "ahooks";
|
import { useRequest } from "ahooks";
|
||||||
import { Button, Divider, Empty, Menu, type MenuProps, Radio, Space, Table, type TableProps, Tooltip, Typography, notification, theme } from "antd";
|
import { Button, Divider, Empty, Menu, type MenuProps, Modal, Radio, Space, Table, type TableProps, Tooltip, Typography, notification, theme } from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
|
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
|
||||||
import { CERTIFICATE_SOURCES, type CertificateModel } from "@/domain/certificate";
|
import { CERTIFICATE_SOURCES, type CertificateModel } from "@/domain/certificate";
|
||||||
import { type ListCertificateRequest, list as listCertificate } from "@/repository/certificate";
|
import { type ListCertificateRequest, list as listCertificate, remove as removeCertificate } from "@/repository/certificate";
|
||||||
import { getErrMsg } from "@/utils/error";
|
import { getErrMsg } from "@/utils/error";
|
||||||
|
|
||||||
const CertificateList = () => {
|
const CertificateList = () => {
|
||||||
@ -21,6 +21,7 @@ const CertificateList = () => {
|
|||||||
|
|
||||||
const { token: themeToken } = theme.useToken();
|
const { token: themeToken } = theme.useToken();
|
||||||
|
|
||||||
|
const [modalApi, ModalContextHolder] = Modal.useModal();
|
||||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||||
|
|
||||||
const tableColumns: TableProps<CertificateModel>["columns"] = [
|
const tableColumns: TableProps<CertificateModel>["columns"] = [
|
||||||
@ -169,14 +170,7 @@ const CertificateList = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Tooltip title={t("certificate.action.delete")}>
|
<Tooltip title={t("certificate.action.delete")}>
|
||||||
<Button
|
<Button color="danger" icon={<DeleteOutlinedIcon />} variant="text" onClick={() => handleDeleteClick(record)} />
|
||||||
color="danger"
|
|
||||||
icon={<DeleteOutlinedIcon />}
|
|
||||||
variant="text"
|
|
||||||
onClick={() => {
|
|
||||||
alert("TODO: 暂时不支持删除证书");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Button.Group>
|
</Button.Group>
|
||||||
),
|
),
|
||||||
@ -194,7 +188,7 @@ const CertificateList = () => {
|
|||||||
const [page, setPage] = useState<number>(() => parseInt(+searchParams.get("page")! + "") || 1);
|
const [page, setPage] = useState<number>(() => parseInt(+searchParams.get("page")! + "") || 1);
|
||||||
const [pageSize, setPageSize] = useState<number>(() => parseInt(+searchParams.get("perPage")! + "") || 10);
|
const [pageSize, setPageSize] = useState<number>(() => parseInt(+searchParams.get("perPage")! + "") || 10);
|
||||||
|
|
||||||
const { loading } = useRequest(
|
const { loading, run: refreshTableData } = useRequest(
|
||||||
() => {
|
() => {
|
||||||
return listCertificate({
|
return listCertificate({
|
||||||
page: page,
|
page: page,
|
||||||
@ -219,8 +213,28 @@ const CertificateList = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDeleteClick = (certificate: CertificateModel) => {
|
||||||
|
modalApi.confirm({
|
||||||
|
title: t("certificate.action.delete"),
|
||||||
|
content: t("certificate.action.delete.confirm"),
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const resp = await removeCertificate(certificate);
|
||||||
|
if (resp) {
|
||||||
|
setTableData((prev) => prev.filter((item) => item.id !== certificate.id));
|
||||||
|
refreshTableData();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
notificationApi.error({ message: t("common.text.request_error"), description: getErrMsg(err) });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
|
{ModalContextHolder}
|
||||||
{NotificationContextHolder}
|
{NotificationContextHolder}
|
||||||
|
|
||||||
<PageHeader title={t("certificate.page.title")} />
|
<PageHeader title={t("certificate.page.title")} />
|
||||||
|
@ -251,10 +251,10 @@ const Dashboard = () => {
|
|||||||
{t("dashboard.quick_actions.change_login_password")}
|
{t("dashboard.quick_actions.change_login_password")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button block size="large" icon={<SendOutlined />} onClick={() => navigate("/settings/notification")}>
|
<Button block size="large" icon={<SendOutlined />} onClick={() => navigate("/settings/notification")}>
|
||||||
{t("dashboard.quick_actions.notification_settings")}
|
{t("dashboard.quick_actions.cofigure_notification")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button block size="large" icon={<ApiOutlined />} onClick={() => navigate("/settings/ssl-provider")}>
|
<Button block size="large" icon={<ApiOutlined />} onClick={() => navigate("/settings/ssl-provider")}>
|
||||||
{t("dashboard.quick_actions.certificate_authority_configuration")}
|
{t("dashboard.quick_actions.configure_ca")}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -109,7 +109,7 @@ const WorkflowDetail = () => {
|
|||||||
content: t("workflow.action.delete.confirm"),
|
content: t("workflow.action.delete.confirm"),
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
const resp: boolean = await removeWorkflow(workflow);
|
const resp = await removeWorkflow(workflow);
|
||||||
if (resp) {
|
if (resp) {
|
||||||
navigate("/workflows", { replace: true });
|
navigate("/workflows", { replace: true });
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,7 @@ const WorkflowList = () => {
|
|||||||
const [page, setPage] = useState<number>(() => parseInt(+searchParams.get("page")! + "") || 1);
|
const [page, setPage] = useState<number>(() => parseInt(+searchParams.get("page")! + "") || 1);
|
||||||
const [pageSize, setPageSize] = useState<number>(() => parseInt(+searchParams.get("perPage")! + "") || 10);
|
const [pageSize, setPageSize] = useState<number>(() => parseInt(+searchParams.get("perPage")! + "") || 10);
|
||||||
|
|
||||||
const { loading } = useRequest(
|
const { loading, run: refreshTableData } = useRequest(
|
||||||
() => {
|
() => {
|
||||||
return listWorkflow({
|
return listWorkflow({
|
||||||
page: page,
|
page: page,
|
||||||
@ -302,9 +302,10 @@ const WorkflowList = () => {
|
|||||||
content: t("workflow.action.delete.confirm"),
|
content: t("workflow.action.delete.confirm"),
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
const resp: boolean = await removeWorkflow(workflow);
|
const resp = await removeWorkflow(workflow);
|
||||||
if (resp) {
|
if (resp) {
|
||||||
setTableData((prev) => prev.filter((item) => item.id !== workflow.id));
|
setTableData((prev) => prev.filter((item) => item.id !== workflow.id));
|
||||||
|
refreshTableData();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -30,4 +30,5 @@ export const remove = async (record: MaybeModelRecordWithId<AccessModel>) => {
|
|||||||
if ("provider" in record && record.provider === "pdns") record.provider = "powerdns";
|
if ("provider" in record && record.provider === "pdns") record.provider = "powerdns";
|
||||||
|
|
||||||
await getPocketBase().collection(COLLECTION_NAME).update<AccessModel>(record.id!, record);
|
await getPocketBase().collection(COLLECTION_NAME).update<AccessModel>(record.id!, record);
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -42,4 +42,5 @@ export const remove = async (record: MaybeModelRecordWithId<CertificateModel>) =
|
|||||||
record = { ...record, deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") };
|
record = { ...record, deleted: dayjs.utc().format("YYYY-MM-DD HH:mm:ss") };
|
||||||
|
|
||||||
await getPocketBase().collection(COLLECTION_NAME).update<CertificateModel>(record.id!, record);
|
await getPocketBase().collection(COLLECTION_NAME).update<CertificateModel>(record.id!, record);
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user