Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 9 additions & 27 deletions src/pages/ApiKeys/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ const ApiKeys: React.FC = () => {
const values = await form.validateFields();
const req: CreateApiKeyRequest = {
name: values.name,
keyType: "custom",
projectIds: (values.projectIds as string[] | undefined)?.join(",") || "",
readOnly: values.readOnly ?? false,
};
Expand Down Expand Up @@ -107,27 +106,12 @@ const ApiKeys: React.FC = () => {
});
};

const getKeyTypeTag = (keyType: string) => {
const colorMap: Record<string, string> = {
anon: "blue",
service: "red",
custom: "default",
};
return <Tag color={colorMap[keyType] || "default"}>{t(`key_type_${keyType}`)}</Tag>;
};

const columns = [
{
title: t("name"),
dataIndex: "name",
key: "name",
},
{
title: t("key_type"),
dataIndex: "keyType",
key: "keyType",
render: (keyType: string) => getKeyTypeTag(keyType),
},
{
title: t("key_prefix"),
dataIndex: "keyPrefix",
Expand Down Expand Up @@ -169,16 +153,14 @@ const ApiKeys: React.FC = () => {
{t("regenerate")}
</Button>
</Popconfirm>
{record.keyType === "custom" && (
<Popconfirm
title={t("api_key_delete_confirm")}
onConfirm={() => handleDelete(record.id)}
>
<Button size="small" danger icon={<DeleteOutlined />}>
{t("delete")}
</Button>
</Popconfirm>
)}
<Popconfirm
title={t("api_key_delete_confirm")}
onConfirm={() => handleDelete(record.id)}
>
<Button size="small" danger icon={<DeleteOutlined/>}>
{t("delete")}
</Button>
</Popconfirm>
</Space>
),
},
Expand Down Expand Up @@ -216,7 +198,7 @@ const ApiKeys: React.FC = () => {
message={t("api_key_frontend_warning")}
style={{marginBottom: 16}}
/>
<Form form={form} layout="vertical" initialValues={{ readOnly: false }}>
<Form form={form} layout="vertical" initialValues={{readOnly: false}}>
<Form.Item name="name" label={t("name")} rules={[{ required: true, message: t("name_required") }]}>
<Input placeholder={t("api_key_name_placeholder")} />
</Form.Item>
Expand Down
10 changes: 9 additions & 1 deletion src/pages/DataModeling/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import EnumForm from "@/pages/DataModeling/components/EnumForm";
import type {Enum} from "@/types/data-modeling.d.ts";
import ERDiagram from "@/pages/DataModeling/components/ERDiagramView";
import {useProject} from "@/store/appStore";
import {AppstoreOutlined, UnorderedListOutlined} from "@ant-design/icons";
import {AppstoreOutlined, ReloadOutlined, UnorderedListOutlined} from "@ant-design/icons";
import ERView from "@/pages/DataView/components/ERView.tsx";
import {spacing} from "@/theme/designTokens";

Expand Down Expand Up @@ -51,6 +51,10 @@ const ModelingPage: React.FC = () => {
setNativeQueryIsEditing(prev => !prev);
}, []);

const handleRefresh = useCallback(() => {
setSelectModelVersion(v => v + 1);
}, []);

const handleCancelNativeQueryEdit = useCallback(() => {
setNativeQueryIsEditing(false);
}, []);
Expand Down Expand Up @@ -118,6 +122,10 @@ const ModelingPage: React.FC = () => {
title={t('data_modeling')}
extra={[
<Space>
<Button
icon={<ReloadOutlined/>}
onClick={handleRefresh}
/>
<Button
type={viewMode === 'list' ? 'default' : 'text'}
icon={<UnorderedListOutlined/>}
Expand Down
6 changes: 3 additions & 3 deletions src/pages/Functions/components/FunctionInvokePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import {Alert, Button, Card, message, Space, Spin, Tabs, Tag, theme, Typography,
import {SendOutlined} from "@ant-design/icons";
import {useTranslation} from "react-i18next";
import ScriptEditor from "@/components/common/ScriptEditor";
import type {FunctionInvokeResult,} from "@/services/function";
import {invokeFunctionViaServer} from "@/services/function";
import type {FunctionInvokeResult} from "@/services/function";
import {invokeFunction} from "@/services/function";

const {Text, Paragraph} = Typography;

Expand Down Expand Up @@ -43,7 +43,7 @@ const FunctionInvokePanel: React.FC<FunctionInvokePanelProps> = ({

try {
// 请求体直接作为函数的 Request body(不再嵌套在 input 字段中)
const res = await invokeFunctionViaServer(projectId, functionName, data);
const res = await invokeFunction(projectId, functionName, data);
setResponse(res);
} catch (err: any) {
setError(err?.message || String(err));
Expand Down
22 changes: 11 additions & 11 deletions src/pages/Functions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,17 @@ const FunctionsPage: React.FC = () => {
title={t("function.title")}
extra={
<Space>
<Input
placeholder={t("function.searchByName")}
prefix={<SearchOutlined/>}
value={searchName}
onChange={(e) => {
setSearchName(e.target.value);
setPage(1);
}}
style={{width: 220}}
allowClear
/>
<Button icon={<ReloadOutlined/>} onClick={loadList}>
{t("refresh")}
</Button>
Expand All @@ -182,17 +193,6 @@ const FunctionsPage: React.FC = () => {
}
loading={loading}
>
<Space style={{marginBottom: 16}} wrap>
<Input
placeholder={t("function.searchByName")}
prefix={<SearchOutlined/>}
value={searchName}
onChange={(e) => { setSearchName(e.target.value); setPage(1); }}
style={{width: 220}}
allowClear
/>
</Space>

<Table
columns={columns}
dataSource={functions}
Expand Down
12 changes: 0 additions & 12 deletions src/pages/Overview/components/ProjectInfoPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ const ProjectInfoPanel: React.FC = () => {
config.pagesUrlTemplate || '/pages/{{projectId}}',
projectId,
);
const functionsUrl = resolveUrl(
config.edgeUrlTemplate || '/functions/{{projectId}}/{{name}}',
projectId,
);

return (
<Card
Expand Down Expand Up @@ -60,14 +56,6 @@ const ProjectInfoPanel: React.FC = () => {
<Text type="secondary" style={{fontSize: 13}}>请先选择项目</Text>
)}
</div>
<div style={{display: 'flex', alignItems: 'center', gap: 12}}>
<Text style={{fontSize: 14, color: 'var(--ant-color-text-secondary)', whiteSpace: 'nowrap'}}>函数入口</Text>
{projectId ? (
<Text copyable={{text: functionsUrl}}>{functionsUrl}</Text>
) : (
<Text type="secondary" style={{fontSize: 13}}>请先选择项目</Text>
)}
</div>
</div>
</Card>
);
Expand Down
11 changes: 9 additions & 2 deletions src/pages/Storage/components/FileBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const FileBrowser: React.FC<FileBrowserProps> = ({bucketName, projectId, visibil
const [createFolderVisible, setCreateFolderVisible] = useState<boolean>(false);
const [folderName, setFolderName] = useState<string>('');
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [uploadFileList, setUploadFileList] = useState<any[]>([]);

const loadFiles = useCallback(async () => {
setLoading(true);
Expand Down Expand Up @@ -67,6 +68,10 @@ const FileBrowser: React.FC<FileBrowserProps> = ({bucketName, projectId, visibil
const handleUpload = () => {
setUploadVisible(true);
};
const handleUploadClose = () => {
setUploadVisible(false);
setUploadFileList([]);
};

const handleCreateFolder = () => {
setCreateFolderVisible(true);
Expand Down Expand Up @@ -285,16 +290,18 @@ const FileBrowser: React.FC<FileBrowserProps> = ({bucketName, projectId, visibil
<Modal
title={t('upload_file')}
open={uploadVisible}
onCancel={() => setUploadVisible(false)}
onCancel={handleUploadClose}
footer={[
<Button key="close" onClick={() => setUploadVisible(false)}>
<Button key="close" onClick={handleUploadClose}>
{t('close')}
</Button>,
]}
>
<Upload.Dragger
multiple
showUploadList={true}
fileList={uploadFileList}
onChange={({fileList}) => setUploadFileList(fileList)}
customRequest={async ({file, onSuccess, onError}) => {
try {
const fileObj = file as File;
Expand Down
68 changes: 2 additions & 66 deletions src/services/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ export interface FunctionInvokeResult {
meta?: FunctionInvokeMeta;
}

export interface InvokeTokenResponse {
invokeToken: string;
runtimeUrl: string;
}

export interface PageDTO<T> {
list: T[];
total: number;
Expand Down Expand Up @@ -83,74 +78,15 @@ export const deleteFunction = (
return api.delete(`/projects/${projectId}/functions/${encodeURIComponent(name)}`);
};

// ---- Invoke Token (Edge Function) ----

/**
* Get an invoke-token for edge function direct invocation.
* Returns invokeToken + runtimeUrl for direct Deno Runtime call.
*/
export const getInvokeToken = (
projectId: string,
name: string,
): Promise<InvokeTokenResponse> => {
return api.post(`/projects/${projectId}/functions/${encodeURIComponent(name)}/invoke-token`);
};

// ---- Invoke (Edge Function — direct Deno call) ----
// ---- Invoke (via Java server proxy → Deno Runtime) ----

/**
* Invoke a function via edge route (direct Deno Runtime call).
* 1. Get invoke-token from Java server
* 2. Call Deno Runtime directly with the token
* Invoke a function via the Java server, which proxies to the Deno Runtime.
*/
export const invokeFunction = async (
projectId: string,
name: string,
data: any,
): Promise<FunctionInvokeResult> => {
// 1. Get invoke-token from Java server
const {invokeToken, runtimeUrl} = await getInvokeToken(projectId, name);

// 2. Call Deno Runtime directly
const response = await fetch(runtimeUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${invokeToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});

// 3. Parse response
let meta: FunctionInvokeMeta | undefined;
const metaStr = response.headers.get('x-function-meta');
if (metaStr) {
try {
meta = JSON.parse(metaStr);
} catch { /* ignore */
}
}

let responseData: any;
try {
responseData = await response.json();
} catch {
responseData = await response.text();
}

return {status: response.status, data: responseData, meta};
};

// ---- Invoke (Legacy — via Java server, kept as fallback) ----

/**
* Invoke a function via Java server (legacy path).
* Use invokeFunction() for edge function direct invocation instead.
*/
export const invokeFunctionViaServer = async (
projectId: string,
name: string,
data: any,
): Promise<FunctionInvokeResult> => {
const response = await api.rawPost(
`/projects/${projectId}/functions/${encodeURIComponent(name)}/invoke`,
Expand Down
2 changes: 0 additions & 2 deletions src/types/api-key.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export interface ApiKey {
id: string;
name: string;
keyPrefix: string;
keyType: 'anon' | 'service' | 'custom';
projectIds?: string;
readOnly: boolean;
expiresAt: string | null;
Expand All @@ -20,7 +19,6 @@ export interface ApiKey {
*/
export interface CreateApiKeyRequest {
name: string;
keyType?: string;
projectIds?: string;
readOnly?: boolean;
}
4 changes: 2 additions & 2 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export default defineConfig({
server: {
host: "0.0.0.0",
proxy: {
"/api": {
"/api/": {
target: "http://localhost:8080",
ws: true,
changeOrigin: true,
Expand All @@ -191,7 +191,7 @@ export default defineConfig({
changeOrigin: true,
},
// Pages fallback → Java server (when local files not found)
"/pages": {
"/pages/": {
target: "http://localhost:8080",
changeOrigin: true,
},
Expand Down
Loading