mirror of
https://github.com/certimate-go/certimate.git
synced 2026-10-16 15:16:05 +08:00
Merge branch 'next' into next
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { get as httpGet, post as httpPost } from "./_api";
|
||||
import { getPocketBase } from "@/repository/_pocketbase";
|
||||
import { get as httpGet, post as httpPost } from "./_api";
|
||||
|
||||
const pb = getPocketBase();
|
||||
|
||||
|
||||
@@ -66,7 +66,9 @@ const renderControl = (col: SchemaColumn, t: (key: string) => string): ReactNode
|
||||
case "switch":
|
||||
return <Switch />;
|
||||
case "number":
|
||||
return <Input type="number" min={col.min} max={col.max} placeholder={placeholder} style={{ width: "100%" }} {...unitFieldProps("text", col.unitKey, t)} />;
|
||||
return (
|
||||
<Input type="number" min={col.min} max={col.max} placeholder={placeholder} style={{ width: "100%" }} {...unitFieldProps("text", col.unitKey, t)} />
|
||||
);
|
||||
case "select":
|
||||
return <Select placeholder={placeholder} options={optionList(col, t)} style={{ width: "100%" }} />;
|
||||
case "radio":
|
||||
|
||||
@@ -54,11 +54,7 @@ export const discriminatorFields = (columns: SchemaColumn[]): string[] => {
|
||||
return [...seen];
|
||||
};
|
||||
|
||||
export const unitFieldProps = (
|
||||
mappedValueType: string,
|
||||
unitKey: string | undefined,
|
||||
t: (key: string) => string
|
||||
): Record<string, unknown> => {
|
||||
export const unitFieldProps = (mappedValueType: string, unitKey: string | undefined, t: (key: string) => string): Record<string, unknown> => {
|
||||
if (!unitKey) return {};
|
||||
const text = t(unitKey);
|
||||
return mappedValueType === "digit" ? { addonAfter: text } : { suffix: text };
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import i18n from "@/i18n";
|
||||
|
||||
import type { PluginCatalogEntry } from "@/api/plugincatalog";
|
||||
import { ACCESS_USAGES, DEPLOYMENT_CATEGORIES, accessProvidersMap, deploymentProvidersMap } from "@/domain/provider";
|
||||
import i18n from "@/i18n";
|
||||
|
||||
const PLUGIN_PLACEHOLDER_ICON =
|
||||
"data:image/svg+xml," +
|
||||
@@ -29,9 +28,7 @@ export const applyPluginCatalog = (entries: PluginCatalogEntry[]): void => {
|
||||
for (const entry of entries) {
|
||||
const accessType = entry.accessProviderType || entry.providerType;
|
||||
|
||||
const usages = (entry.accessUsages || ["hosting"])
|
||||
.map((u) => usageMap[u])
|
||||
.filter(Boolean);
|
||||
const usages = (entry.accessUsages || ["hosting"]).map((u) => usageMap[u]).filter(Boolean);
|
||||
|
||||
if (entry.providerType && !deploymentProvidersMap.has(entry.providerType)) {
|
||||
deploymentProvidersMap.set(entry.providerType, {
|
||||
|
||||
+4
-1
@@ -19,6 +19,9 @@ const renderApp = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const pluginCatalogReady = usePluginCatalogStore.getState().init().catch(() => undefined);
|
||||
const pluginCatalogReady = usePluginCatalogStore
|
||||
.getState()
|
||||
.init()
|
||||
.catch(() => undefined);
|
||||
const renderDeadline = new Promise<void>((resolve) => setTimeout(resolve, 2000));
|
||||
void Promise.race([pluginCatalogReady, renderDeadline]).finally(renderApp);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { useMount } from "ahooks";
|
||||
import { App, Button, Flex, Form, Alert } from "antd";
|
||||
import { Alert, App, Button, Flex, Form } from "antd";
|
||||
|
||||
import AccessForm, { type AccessFormUsages } from "@/components/access/AccessForm";
|
||||
import AccessProviderPicker, { type AccessProviderPickerInstance } from "@/components/provider/AccessProviderPicker";
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import { IconDatabaseCog, IconHeartRateMonitor, IconInfoCircle, IconKey, IconPalette, IconPlugConnected, IconPuzzle, IconUserShield } from "@tabler/icons-react";
|
||||
import {
|
||||
IconDatabaseCog,
|
||||
IconHeartRateMonitor,
|
||||
IconInfoCircle,
|
||||
IconKey,
|
||||
IconPalette,
|
||||
IconPlugConnected,
|
||||
IconPuzzle,
|
||||
IconUserShield,
|
||||
} from "@tabler/icons-react";
|
||||
import { Menu } from "antd";
|
||||
|
||||
const Settings = () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconDownload, IconPlus, IconRefresh } from "@tabler/icons-react";
|
||||
import { useRequest } from "ahooks";
|
||||
import { App, Button, Empty, Form, Input, Modal, Select, Space, Table, Typography, type TableProps } from "antd";
|
||||
import { App, Button, Empty, Form, Input, Modal, Select, Space, Table, type TableProps, Typography } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import dayjs from "dayjs";
|
||||
import { saveAs } from "file-saver";
|
||||
@@ -216,8 +216,8 @@ const SettingsAcmeAccounts = () => {
|
||||
<div>
|
||||
<div className="mb-4 flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
<h2 className="!mb-1">{t("settings.acmeaccounts.title")}</h2>
|
||||
<Typography.Paragraph type="secondary" className="!mb-0">
|
||||
<h2 className="mb-1!">{t("settings.acmeaccounts.title")}</h2>
|
||||
<Typography.Paragraph type="secondary" className="mb-0!">
|
||||
{t("settings.acmeaccounts.tips")}
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
@@ -249,12 +249,7 @@ const SettingsAcmeAccounts = () => {
|
||||
dataSource={tableData}
|
||||
scroll={{ x: "max(100%, 960px)" }}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<Empty
|
||||
description={!loading ? t("settings.acmeaccounts.empty") : " "}
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
/>
|
||||
),
|
||||
emptyText: <Empty description={!loading ? t("settings.acmeaccounts.empty") : " "} image={Empty.PRESENTED_IMAGE_SIMPLE} />,
|
||||
}}
|
||||
pagination={{
|
||||
current: page,
|
||||
@@ -295,7 +290,12 @@ const SettingsAcmeAccounts = () => {
|
||||
>
|
||||
<Input.TextArea autoSize={{ minRows: 6, maxRows: 12 }} placeholder={t("settings.acmeaccounts.import.form.private_key_placeholder")} />
|
||||
</Form.Item>
|
||||
<Form.Item name="email" label={t("settings.acmeaccounts.import.form.email")} rules={[importRule]} extra={t("settings.acmeaccounts.import.form.email_extra")}>
|
||||
<Form.Item
|
||||
name="email"
|
||||
label={t("settings.acmeaccounts.import.form.email")}
|
||||
rules={[importRule]}
|
||||
extra={t("settings.acmeaccounts.import.form.email_extra")}
|
||||
>
|
||||
<Input placeholder={t("settings.acmeaccounts.import.form.email_placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconDownload, IconPuzzle, IconTrash, IconRefresh } from "@tabler/icons-react";
|
||||
import { IconDownload, IconPuzzle, IconRefresh, IconTrash } from "@tabler/icons-react";
|
||||
import { Alert, App, Button, Card, Empty, Input, Progress, Segmented, Skeleton, Tag, Tooltip } from "antd";
|
||||
|
||||
import {
|
||||
fetchMarketListing,
|
||||
installPlugin,
|
||||
deletePlugin,
|
||||
updatePlugin,
|
||||
getInstallStatus,
|
||||
isTerminalState,
|
||||
type InstallJobStatus,
|
||||
type MarketEntry,
|
||||
deletePlugin,
|
||||
fetchMarketListing,
|
||||
getInstallStatus,
|
||||
installPlugin,
|
||||
isTerminalState,
|
||||
updatePlugin,
|
||||
} from "@/api/pluginmarket";
|
||||
import { usePluginCatalogStore } from "@/stores/pluginCatalog";
|
||||
|
||||
@@ -345,7 +345,11 @@ const SettingsPlugins = () => {
|
||||
const downloaded = job?.downloaded ?? 0;
|
||||
if (job!.state === "downloading" && total > 0) {
|
||||
const pct = Math.min(100, Math.round((downloaded / total) * 100));
|
||||
return <span className="text-xs text-blue-500">{t("plugin.market.job.downloading")} · {pct}%</span>;
|
||||
return (
|
||||
<span className="text-xs text-blue-500">
|
||||
{t("plugin.market.job.downloading")} · {pct}%
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="flex items-center gap-1.5 text-xs text-blue-500">
|
||||
@@ -359,7 +363,7 @@ const SettingsPlugins = () => {
|
||||
}
|
||||
return (
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className={`inline-block h-2 w-2 rounded-full ${statusColor[entry.status] ?? "bg-gray-400"}`} />
|
||||
<span className={`inline-block size-2 rounded-full ${statusColor[entry.status] ?? "bg-gray-400"}`} />
|
||||
{t(statusLabel[entry.status], entry.status)}
|
||||
</span>
|
||||
);
|
||||
@@ -419,12 +423,7 @@ const SettingsPlugins = () => {
|
||||
value={statusFilter}
|
||||
onChange={(value) => setStatusFilter(value as typeof statusFilter)}
|
||||
/>
|
||||
<Input.Search
|
||||
allowClear
|
||||
className="w-48"
|
||||
placeholder={t("plugin.market.action.search_placeholder")}
|
||||
onSearch={(value) => setKeyword(value)}
|
||||
/>
|
||||
<Input.Search allowClear className="w-48" placeholder={t("plugin.market.action.search_placeholder")} onSearch={(value) => setKeyword(value)} />
|
||||
</div>
|
||||
<Button icon={<IconRefresh size={16} />} onClick={() => loadListing()} loading={loading}>
|
||||
{t("plugin.market.action.refresh")}
|
||||
@@ -439,9 +438,9 @@ const SettingsPlugins = () => {
|
||||
<Card key={entry.provider_type} hoverable className="flex flex-col">
|
||||
<div className="flex h-full flex-col gap-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-gray-100 p-2.5 dark:bg-gray-800">
|
||||
<div className="flex size-12 shrink-0 items-center justify-center rounded-xl bg-gray-100 p-2.5 dark:bg-gray-800">
|
||||
{marketIconSrc(entry) ? (
|
||||
<img src={marketIconSrc(entry)} alt={entry.provider_type} className="h-full w-full object-contain" />
|
||||
<img src={marketIconSrc(entry)} alt={entry.provider_type} className="size-full object-contain" />
|
||||
) : (
|
||||
<IconPuzzle size={22} className="text-gray-400" />
|
||||
)}
|
||||
@@ -454,7 +453,7 @@ const SettingsPlugins = () => {
|
||||
</div>
|
||||
<div className="shrink-0">{renderActions(entry)}</div>
|
||||
</div>
|
||||
<div className="line-clamp-2 min-h-[2.5rem] text-sm text-gray-500">{entry.description}</div>
|
||||
<div className="line-clamp-2 min-h-10 text-sm text-gray-500">{entry.description}</div>
|
||||
<div className="mt-auto flex items-center justify-between border-t border-gray-100 pt-2 text-xs text-gray-400 dark:border-gray-800">
|
||||
{renderStatus(entry)}
|
||||
<span>v{entry.version}</span>
|
||||
|
||||
@@ -15,8 +15,8 @@ import SettingsAccount from "@/pages/settings/SettingsAccount";
|
||||
import SettingsAcmeAccounts from "@/pages/settings/SettingsAcmeAccounts";
|
||||
import SettingsAppearance from "@/pages/settings/SettingsAppearance";
|
||||
import SettingsDiagnostics from "@/pages/settings/SettingsDiagnostics";
|
||||
import SettingsPlugins from "@/pages/settings/SettingsPlugins";
|
||||
import SettingsPersistence from "@/pages/settings/SettingsPersistence";
|
||||
import SettingsPlugins from "@/pages/settings/SettingsPlugins";
|
||||
import SettingsSSLProvider from "@/pages/settings/SettingsSSLProvider";
|
||||
import WorkflowDetail from "@/pages/workflows/WorkflowDetail";
|
||||
import WorkflowDetailDesign from "@/pages/workflows/WorkflowDetailDesign";
|
||||
|
||||
Reference in New Issue
Block a user