refactor(ui): refactor accesses state using zustand store
This commit is contained in:
@@ -18,7 +18,6 @@ import {
|
||||
import Version from "@/components/certimate/Version";
|
||||
import { useTheme } from "@/hooks";
|
||||
import { getPocketBase } from "@/repository/pocketbase";
|
||||
import { ConfigProvider } from "@/providers/config";
|
||||
|
||||
const ConsoleLayout = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -52,65 +51,61 @@ const ConsoleLayout = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfigProvider>
|
||||
<Layout className="w-full min-h-screen">
|
||||
<Layout.Sider className="max-md:hidden" theme="light" width={256}>
|
||||
<div className="flex flex-col items-center justify-between w-full h-full overflow-hidden">
|
||||
<div className="w-full">
|
||||
<SiderMenu />
|
||||
</div>
|
||||
<div className="w-full py-2 text-center">
|
||||
<Version />
|
||||
</div>
|
||||
<Layout className="w-full min-h-screen">
|
||||
<Layout.Sider className="max-md:hidden" theme="light" width={256}>
|
||||
<div className="flex flex-col items-center justify-between w-full h-full overflow-hidden">
|
||||
<div className="w-full">
|
||||
<SiderMenu />
|
||||
</div>
|
||||
<div className="w-full py-2 text-center">
|
||||
<Version />
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Sider>
|
||||
|
||||
<Layout>
|
||||
<Layout.Header style={{ padding: 0, background: themeToken.colorBgContainer }}>
|
||||
<div className="flex items-center justify-between size-full px-4 overflow-hidden">
|
||||
<div className="flex items-center gap-4 size-full">
|
||||
<Button className="md:hidden" icon={<MenuIcon />} size="large" onClick={handleSiderOpen} />
|
||||
<Drawer
|
||||
closable={false}
|
||||
destroyOnClose
|
||||
open={siderOpen}
|
||||
placement="left"
|
||||
styles={{
|
||||
content: { paddingTop: themeToken.paddingSM, paddingBottom: themeToken.paddingSM },
|
||||
body: { padding: 0 },
|
||||
}}
|
||||
onClose={handleSiderClose}
|
||||
>
|
||||
<SiderMenu onSelect={() => handleSiderClose()} />
|
||||
</Drawer>
|
||||
</div>
|
||||
</Layout.Sider>
|
||||
<div className="flex-grow flex items-center justify-end gap-4 size-full overflow-hidden">
|
||||
<Tooltip title={t("common.menu.theme")} mouseEnterDelay={2}>
|
||||
<ThemeToggleButton size="large" />
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.menu.locale")} mouseEnterDelay={2}>
|
||||
<LocaleToggleButton size="large" />
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.menu.settings")} mouseEnterDelay={2}>
|
||||
<Button icon={<SettingsIcon size={18} />} size="large" onClick={handleSettingsClick} />
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.menu.logout")} mouseEnterDelay={2}>
|
||||
<Button danger icon={<LogOutIcon size={18} />} size="large" onClick={handleLogoutClick} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Header>
|
||||
|
||||
<Layout>
|
||||
<Layout.Header style={{ padding: 0, background: themeToken.colorBgContainer }}>
|
||||
<div className="flex items-center justify-between size-full px-4 overflow-hidden">
|
||||
<div className="flex items-center gap-4 size-full">
|
||||
<Button className="md:hidden" icon={<MenuIcon />} size="large" onClick={handleSiderOpen} />
|
||||
<Drawer
|
||||
closable={false}
|
||||
destroyOnClose
|
||||
open={siderOpen}
|
||||
placement="left"
|
||||
styles={{
|
||||
content: { paddingTop: themeToken.paddingSM, paddingBottom: themeToken.paddingSM },
|
||||
body: { padding: 0 },
|
||||
}}
|
||||
onClose={handleSiderClose}
|
||||
>
|
||||
<SiderMenu onSelect={() => handleSiderClose()} />
|
||||
</Drawer>
|
||||
</div>
|
||||
<div className="flex-grow flex items-center justify-end gap-4 size-full overflow-hidden">
|
||||
<Tooltip title={t("common.menu.theme")} mouseEnterDelay={2}>
|
||||
<ThemeToggleButton size="large" />
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.menu.locale")} mouseEnterDelay={2}>
|
||||
<LocaleToggleButton size="large" />
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.menu.settings")} mouseEnterDelay={2}>
|
||||
<Button icon={<SettingsIcon size={18} />} size="large" onClick={handleSettingsClick} />
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.menu.logout")} mouseEnterDelay={2}>
|
||||
<Button danger icon={<LogOutIcon size={18} />} size="large" onClick={handleLogoutClick} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Header>
|
||||
|
||||
<Layout.Content>
|
||||
<div className="p-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</ConfigProvider>
|
||||
</>
|
||||
<Layout.Content>
|
||||
<div className="p-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,9 +7,8 @@ import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import AccessEditDialog from "@/components/certimate/AccessEditDialog";
|
||||
import { accessProvidersMap, type Access as AccessType } from "@/domain/access";
|
||||
import { remove as removeAccess } from "@/repository/access";
|
||||
import { useConfigContext } from "@/providers/config";
|
||||
import { accessProvidersMap, type AccessModel } from "@/domain/access";
|
||||
import { useAccessStore } from "@/stores/access";
|
||||
|
||||
const AccessList = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -17,9 +16,11 @@ const AccessList = () => {
|
||||
const [modalApi, ModelContextHolder] = Modal.useModal();
|
||||
const [notificationApi, NotificationContextHolder] = notification.useNotification();
|
||||
|
||||
const { accesses, fetchAccesses, deleteAccess } = useAccessStore();
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const tableColumns: TableProps<AccessType>["columns"] = [
|
||||
const tableColumns: TableProps<AccessModel>["columns"] = [
|
||||
{
|
||||
key: "$index",
|
||||
align: "center",
|
||||
@@ -105,13 +106,15 @@ const AccessList = () => {
|
||||
),
|
||||
},
|
||||
];
|
||||
const [tableData, setTableData] = useState<AccessType[]>([]);
|
||||
const [tableData, setTableData] = useState<AccessModel[]>([]);
|
||||
const [tableTotal, setTableTotal] = useState<number>(0);
|
||||
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [pageSize, setPageSize] = useState<number>(10);
|
||||
|
||||
const configContext = useConfigContext();
|
||||
useEffect(() => {
|
||||
fetchAccesses();
|
||||
}, []);
|
||||
|
||||
const fetchTableData = useCallback(async () => {
|
||||
if (loading) return;
|
||||
@@ -120,10 +123,10 @@ const AccessList = () => {
|
||||
try {
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const items = configContext.config?.accesses?.slice(startIndex, endIndex) ?? [];
|
||||
const items = accesses.slice(startIndex, endIndex);
|
||||
|
||||
setTableData(items);
|
||||
setTableTotal(configContext.config?.accesses?.length ?? 0);
|
||||
setTableTotal(accesses.length);
|
||||
} catch (err) {
|
||||
if (err instanceof ClientResponseError && err.isAbort) {
|
||||
return;
|
||||
@@ -134,21 +137,20 @@ const AccessList = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [page, pageSize, configContext.config.accesses]);
|
||||
}, [page, pageSize, accesses]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTableData();
|
||||
}, [fetchTableData]);
|
||||
|
||||
const handleDeleteClick = async (data: AccessType) => {
|
||||
const handleDeleteClick = async (data: AccessModel) => {
|
||||
modalApi.confirm({
|
||||
title: t("access.action.delete"),
|
||||
content: t("access.action.delete.confirm"),
|
||||
onOk: async () => {
|
||||
// TODO: 有关联数据的不允许被删除
|
||||
try {
|
||||
const res = await removeAccess(data);
|
||||
configContext.deleteAccess(res.id);
|
||||
await deleteAccess(data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
notificationApi.error({ message: t("common.text.request_error"), description: <>{String(err)}</> });
|
||||
@@ -177,7 +179,7 @@ const AccessList = () => {
|
||||
]}
|
||||
/>
|
||||
|
||||
<Table<AccessType>
|
||||
<Table<AccessModel>
|
||||
columns={tableColumns}
|
||||
dataSource={tableData}
|
||||
loading={loading}
|
||||
|
||||
@@ -8,7 +8,7 @@ import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
|
||||
import { Certificate as CertificateType } from "@/domain/certificate";
|
||||
import { CertificateModel } from "@/domain/certificate";
|
||||
import { list as listCertificate, type CertificateListReq } from "@/repository/certificate";
|
||||
|
||||
const CertificateList = () => {
|
||||
@@ -23,7 +23,7 @@ const CertificateList = () => {
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const tableColumns: TableProps<CertificateType>["columns"] = [
|
||||
const tableColumns: TableProps<CertificateModel>["columns"] = [
|
||||
{
|
||||
key: "$index",
|
||||
align: "center",
|
||||
@@ -165,7 +165,7 @@ const CertificateList = () => {
|
||||
),
|
||||
},
|
||||
];
|
||||
const [tableData, setTableData] = useState<CertificateType[]>([]);
|
||||
const [tableData, setTableData] = useState<CertificateModel[]>([]);
|
||||
const [tableTotal, setTableTotal] = useState<number>(0);
|
||||
|
||||
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
|
||||
@@ -177,7 +177,7 @@ const CertificateList = () => {
|
||||
const [page, setPage] = useState<number>(() => parseInt(+searchParams.get("page")! + "") || 1);
|
||||
const [pageSize, setPageSize] = useState<number>(() => parseInt(+searchParams.get("perPage")! + "") || 10);
|
||||
|
||||
const [currentRecord, setCurrentRecord] = useState<CertificateType>();
|
||||
const [currentRecord, setCurrentRecord] = useState<CertificateModel>();
|
||||
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
|
||||
@@ -210,7 +210,7 @@ const CertificateList = () => {
|
||||
fetchTableData();
|
||||
}, [fetchTableData]);
|
||||
|
||||
const handleViewClick = (certificate: CertificateType) => {
|
||||
const handleViewClick = (certificate: CertificateModel) => {
|
||||
setDrawerOpen(true);
|
||||
setCurrentRecord(certificate);
|
||||
};
|
||||
@@ -221,7 +221,7 @@ const CertificateList = () => {
|
||||
|
||||
<PageHeader title={t("certificate.page.title")} />
|
||||
|
||||
<Table<CertificateType>
|
||||
<Table<CertificateModel>
|
||||
columns={tableColumns}
|
||||
dataSource={tableData}
|
||||
loading={loading}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { type Statistic as StatisticType } from "@/domain/domain";
|
||||
import { type Statistics } from "@/domain/statistics";
|
||||
import { get as getStatistics } from "@/api/statistics";
|
||||
|
||||
const Dashboard = () => {
|
||||
@@ -26,7 +26,7 @@ const Dashboard = () => {
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const statisticGridSpans = {
|
||||
const statisticsGridSpans = {
|
||||
xs: { flex: "50%" },
|
||||
md: { flex: "50%" },
|
||||
lg: { flex: "33.3333%" },
|
||||
@@ -34,15 +34,15 @@ const Dashboard = () => {
|
||||
xxl: { flex: "20%" },
|
||||
};
|
||||
|
||||
const [statistic, setStatistic] = useState<StatisticType>();
|
||||
const [statistics, setStatistics] = useState<Statistics>();
|
||||
|
||||
const fetchStatistic = useCallback(async () => {
|
||||
const fetchStatistics = useCallback(async () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const data = await getStatistics();
|
||||
setStatistic(data);
|
||||
setStatistics(data);
|
||||
} catch (err) {
|
||||
if (err instanceof ClientResponseError && err.isAbort) {
|
||||
return;
|
||||
@@ -56,7 +56,7 @@ const Dashboard = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStatistic();
|
||||
fetchStatistics();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -66,47 +66,47 @@ const Dashboard = () => {
|
||||
<PageHeader title={t("dashboard.page.title")} />
|
||||
|
||||
<Row className="justify-stretch" gutter={[16, 16]}>
|
||||
<Col {...statisticGridSpans}>
|
||||
<Col {...statisticsGridSpans}>
|
||||
<StatisticCard
|
||||
icon={<SquareSigmaIcon size={48} strokeWidth={1} color={themeToken.colorInfo} />}
|
||||
label={t("dashboard.statistics.all_certificates")}
|
||||
value={statistic?.certificateTotal ?? "-"}
|
||||
value={statistics?.certificateTotal ?? "-"}
|
||||
suffix={t("dashboard.statistics.unit")}
|
||||
onClick={() => navigate("/certificates")}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statisticGridSpans}>
|
||||
<Col {...statisticsGridSpans}>
|
||||
<StatisticCard
|
||||
icon={<CalendarClockIcon size={48} strokeWidth={1} color={themeToken.colorWarning} />}
|
||||
label={t("dashboard.statistics.expire_soon_certificates")}
|
||||
value={statistic?.certificateExpireSoon ?? "-"}
|
||||
value={statistics?.certificateExpireSoon ?? "-"}
|
||||
suffix={t("dashboard.statistics.unit")}
|
||||
onClick={() => navigate("/certificates?state=expireSoon")}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statisticGridSpans}>
|
||||
<Col {...statisticsGridSpans}>
|
||||
<StatisticCard
|
||||
icon={<CalendarX2Icon size={48} strokeWidth={1} color={themeToken.colorError} />}
|
||||
label={t("dashboard.statistics.expired_certificates")}
|
||||
value={statistic?.certificateExpired ?? "-"}
|
||||
value={statistics?.certificateExpired ?? "-"}
|
||||
suffix={t("dashboard.statistics.unit")}
|
||||
onClick={() => navigate("/certificates?state=expired")}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statisticGridSpans}>
|
||||
<Col {...statisticsGridSpans}>
|
||||
<StatisticCard
|
||||
icon={<WorkflowIcon size={48} strokeWidth={1} color={themeToken.colorInfo} />}
|
||||
label={t("dashboard.statistics.all_workflows")}
|
||||
value={statistic?.workflowTotal ?? "-"}
|
||||
value={statistics?.workflowTotal ?? "-"}
|
||||
suffix={t("dashboard.statistics.unit")}
|
||||
onClick={() => navigate("/workflows")}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statisticGridSpans}>
|
||||
<Col {...statisticsGridSpans}>
|
||||
<StatisticCard
|
||||
icon={<FolderCheckIcon size={48} strokeWidth={1} color={themeToken.colorSuccess} />}
|
||||
label={t("dashboard.statistics.enabled_workflows")}
|
||||
value={statistic?.workflowEnabled ?? "-"}
|
||||
value={statistics?.workflowEnabled ?? "-"}
|
||||
suffix={t("dashboard.statistics.unit")}
|
||||
onClick={() => navigate("/workflows?state=enabled")}
|
||||
/>
|
||||
|
||||
@@ -12,14 +12,14 @@ import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { getErrMsg } from "@/utils/error";
|
||||
import { SSLProvider as SSLProviderType, SSLProviderSetting, Settings } from "@/domain/settings";
|
||||
import { SSLProvider as SSLProviderType, SSLProviderSetting, SettingsModel } from "@/domain/settings";
|
||||
import { get, save } from "@/repository/settings";
|
||||
import { produce } from "immer";
|
||||
|
||||
type SSLProviderContext = {
|
||||
setting: Settings<SSLProviderSetting>;
|
||||
onSubmit: (data: Settings<SSLProviderSetting>) => void;
|
||||
setConfig: (config: Settings<SSLProviderSetting>) => void;
|
||||
setting: SettingsModel<SSLProviderSetting>;
|
||||
onSubmit: (data: SettingsModel<SSLProviderSetting>) => void;
|
||||
setConfig: (config: SettingsModel<SSLProviderSetting>) => void;
|
||||
};
|
||||
|
||||
const Context = createContext({} as SSLProviderContext);
|
||||
@@ -31,12 +31,12 @@ export const useSSLProviderContext = () => {
|
||||
const SSLProvider = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [config, setConfig] = useState<Settings<SSLProviderSetting>>({
|
||||
const [config, setConfig] = useState<SettingsModel<SSLProviderSetting>>({
|
||||
content: {
|
||||
provider: "letsencrypt",
|
||||
config: {},
|
||||
},
|
||||
} as Settings<SSLProviderSetting>);
|
||||
} as SettingsModel<SSLProviderSetting>);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -73,7 +73,7 @@ const SSLProvider = () => {
|
||||
return "";
|
||||
};
|
||||
|
||||
const onSubmit = async (data: Settings<SSLProviderSetting>) => {
|
||||
const onSubmit = async (data: SettingsModel<SSLProviderSetting>) => {
|
||||
try {
|
||||
console.log(data);
|
||||
const resp = await save({ ...data });
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Filter as FilterIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { Workflow as WorkflowType } from "@/domain/workflow";
|
||||
import { WorkflowModel } from "@/domain/workflow";
|
||||
import { list as listWorkflow, remove as removeWorkflow, save as saveWorkflow } from "@/repository/workflow";
|
||||
|
||||
const WorkflowList = () => {
|
||||
@@ -39,7 +39,7 @@ const WorkflowList = () => {
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const tableColumns: TableProps<WorkflowType>["columns"] = [
|
||||
const tableColumns: TableProps<WorkflowModel>["columns"] = [
|
||||
{
|
||||
key: "$index",
|
||||
align: "center",
|
||||
@@ -196,7 +196,7 @@ const WorkflowList = () => {
|
||||
),
|
||||
},
|
||||
];
|
||||
const [tableData, setTableData] = useState<WorkflowType[]>([]);
|
||||
const [tableData, setTableData] = useState<WorkflowModel[]>([]);
|
||||
const [tableTotal, setTableTotal] = useState<number>(0);
|
||||
|
||||
const [filters, setFilters] = useState<Record<string, unknown>>(() => {
|
||||
@@ -237,7 +237,7 @@ const WorkflowList = () => {
|
||||
fetchTableData();
|
||||
}, [fetchTableData]);
|
||||
|
||||
const handleEnabledChange = async (workflow: WorkflowType) => {
|
||||
const handleEnabledChange = async (workflow: WorkflowModel) => {
|
||||
try {
|
||||
const resp = await saveWorkflow({
|
||||
id: workflow.id,
|
||||
@@ -259,7 +259,7 @@ const WorkflowList = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteClick = (workflow: WorkflowType) => {
|
||||
const handleDeleteClick = (workflow: WorkflowModel) => {
|
||||
modalApi.confirm({
|
||||
title: t("workflow.action.delete"),
|
||||
content: t("workflow.action.delete.confirm"),
|
||||
@@ -302,7 +302,7 @@ const WorkflowList = () => {
|
||||
]}
|
||||
/>
|
||||
|
||||
<Table<WorkflowType>
|
||||
<Table<WorkflowModel>
|
||||
columns={tableColumns}
|
||||
dataSource={tableData}
|
||||
loading={loading}
|
||||
|
||||
Reference in New Issue
Block a user