Merge branch 'next' into next

This commit is contained in:
Yoan.liu
2026-08-28 14:50:22 +08:00
committed by GitHub
10 changed files with 50 additions and 44 deletions
+1 -1
View File
@@ -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 };
+2 -5
View File
@@ -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
View File
@@ -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);
+1 -1
View File
@@ -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";
+10 -1
View File
@@ -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 = () => {
+10 -10
View File
@@ -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>
+17 -18
View File
@@ -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>
+1 -1
View File
@@ -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";