cyi-box: Memphis-style toolbox admin platform
Go(fun)+SQLite backend, Vue 3 frontend. Modules: dashboard, pay gateway (Epay-compatible + CDK stock), license/card system (per-app appid, domain binding), AI memory bank with built-in MCP server (/mcp, FTS5 search, signed read links), server SSH monitor + terminal + SFTP, proxy pool with protocol-level alive checks, DB manager (mysql/pg/sqlite + SQL autocomplete + import/export), disk share, vault (AES-GCM + TOTP), bookmarks, dev tools. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#f8f6f6" />
|
||||
<style>html,body{background:#f8f6f6}</style>
|
||||
<title>池易工作箱</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2439
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "cyi-box-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-sql": "^6.10.0",
|
||||
"@vicons/ionicons5": "^0.13.0",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"codemirror": "^6.0.2",
|
||||
"echarts": "^5.6.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"naive-ui": "^2.45.0",
|
||||
"pinia": "^4.0.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.5.40",
|
||||
"vue-router": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@vitejs/plugin-vue": "^6.0.8",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.2.0",
|
||||
"vue-tsc": "^3.3.10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { NConfigProvider, NMessageProvider, NDialogProvider, zhCN, dateZhCN } from 'naive-ui'
|
||||
import type { GlobalThemeOverrides } from 'naive-ui'
|
||||
|
||||
// Memphis / 新粗野主义:亮橙主色 + 大圆角,黑边与硬投影在 style.css 统一覆盖
|
||||
const themeOverrides: GlobalThemeOverrides = {
|
||||
common: {
|
||||
primaryColor: '#ec5b13',
|
||||
primaryColorHover: '#f5712b',
|
||||
primaryColorPressed: '#d14e0e',
|
||||
primaryColorSuppl: '#ec5b13',
|
||||
successColor: '#00a544',
|
||||
warningColor: '#f59e0b',
|
||||
errorColor: '#e40014',
|
||||
borderRadius: '10px',
|
||||
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif",
|
||||
fontWeight: '500'
|
||||
},
|
||||
Menu: {
|
||||
itemColorHover: 'transparent',
|
||||
itemColorActive: 'transparent',
|
||||
itemTextColor: '#57534d',
|
||||
itemTextColorHover: '#1a1a1a',
|
||||
itemTextColorActive: '#1a1a1a',
|
||||
itemIconColor: '#8a857e',
|
||||
itemIconColorHover: '#1a1a1a',
|
||||
itemIconColorActive: '#1a1a1a',
|
||||
groupTextColor: '#8a857e',
|
||||
itemHeight: '42px',
|
||||
borderRadius: '10px',
|
||||
itemTextColorActiveHover: '#1a1a1a'
|
||||
},
|
||||
Card: { borderRadius: '16px' },
|
||||
Tag: { fontWeight: '700' }
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :locale="zhCN" :date-locale="dateZhCN" :theme-overrides="themeOverrides">
|
||||
<n-message-provider>
|
||||
<n-dialog-provider>
|
||||
<router-view />
|
||||
</n-dialog-provider>
|
||||
</n-message-provider>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type changePasswordDto from "./changePasswordDto";
|
||||
import type loginDto from "./loginDto";
|
||||
import type okView from "./okView";
|
||||
import type sessionView from "./sessionView";
|
||||
|
||||
export default class authSvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async changePassword(dto:changePasswordDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("authSvc", "changePassword", dto, options)
|
||||
}
|
||||
async login(dto:loginDto, options?: RequestOptions): Promise<result<sessionView>> {
|
||||
return await this.client.request<sessionView>("authSvc", "login", dto, options)
|
||||
}
|
||||
async logout(options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("authSvc", "logout", undefined, options)
|
||||
}
|
||||
async me(options?: RequestOptions): Promise<result<sessionView>> {
|
||||
return await this.client.request<sessionView>("authSvc", "me", undefined, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface bookmarkIdDto {
|
||||
id:number
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type bookmarkIdDto from "./bookmarkIdDto";
|
||||
import type bookmarkView from "./bookmarkView";
|
||||
import type okView from "./okView";
|
||||
import type saveBookmarkDto from "./saveBookmarkDto";
|
||||
|
||||
export default class bookmarkSvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async delete(dto:bookmarkIdDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("bookmarkSvc", "delete", dto, options)
|
||||
}
|
||||
async list(options?: RequestOptions): Promise<result<bookmarkView[]>> {
|
||||
return await this.client.request<bookmarkView[]>("bookmarkSvc", "list", undefined, options)
|
||||
}
|
||||
async save(dto:saveBookmarkDto, options?: RequestOptions): Promise<result<bookmarkView>> {
|
||||
return await this.client.request<bookmarkView>("bookmarkSvc", "save", dto, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default interface bookmarkView {
|
||||
id:number
|
||||
title:string
|
||||
url:string
|
||||
icon:string
|
||||
sort:number
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface changePasswordDto {
|
||||
oldPassword:string
|
||||
newPassword:string
|
||||
}
|
||||
@@ -0,0 +1,665 @@
|
||||
// 大整数安全 JSON:超过 Number.MAX_SAFE_INTEGER(2^53-1)的整数字面量
|
||||
// 解析为 BigInt,序列化时 BigInt 还原为数字字面量——雪花 ID 等不再丢精度。
|
||||
// 自包含实现,不引入运行时依赖;如需换 json-bigint,替换下面两个函数即可
|
||||
const BIGINT_PREFIX = "\u0000fun-bigint:";
|
||||
|
||||
function reviveBigint(value: any): any {
|
||||
if (typeof value === "string" && value.startsWith(BIGINT_PREFIX) &&
|
||||
/^-?\d+$/.test(value.slice(BIGINT_PREFIX.length))) {
|
||||
return BigInt(value.slice(BIGINT_PREFIX.length));
|
||||
}
|
||||
if (Array.isArray(value)) return value.map(reviveBigint);
|
||||
if (value !== null && typeof value === "object") {
|
||||
for (const key of Object.keys(value)) value[key] = reviveBigint(value[key]);
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseLossless(text: string): any {
|
||||
// 先把超出安全范围的大整数包成带哨兵的字符串(正则的字符串分支优先,
|
||||
// 字符串内容里的数字不受影响),JSON.parse 后再还原为 BigInt
|
||||
const guarded = text.replace(
|
||||
/"(?:[^"\\]|\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4}))*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g,
|
||||
token => {
|
||||
if (token.startsWith('"')) return token;
|
||||
if (!/[.eE]/.test(token) && !Number.isSafeInteger(Number(token))) {
|
||||
// 哨兵必须以转义形式 \\u0000 写入文本(JSON 字符串禁止裸控制字符),
|
||||
// JSON.parse 解码后即为 BIGINT_PREFIX(真实 NUL 开头)
|
||||
return '"\\u0000fun-bigint:' + token + '"';
|
||||
}
|
||||
return token;
|
||||
}
|
||||
);
|
||||
return reviveBigint(JSON.parse(guarded));
|
||||
}
|
||||
|
||||
function stringifyLossless(value: any): string | undefined {
|
||||
const raw = JSON.stringify(value, (_key, v) =>
|
||||
typeof v === "bigint" ? BIGINT_PREFIX + v.toString() : v
|
||||
);
|
||||
if (raw === undefined) return undefined;
|
||||
// 哨兵字符串在序列化文本中形如 "\u0000fun-bigint:123"(NUL 被转义),去引号还原为数字字面量
|
||||
return raw.replace(/"\\u0000fun-bigint:(-?\d+)"/g, "$1");
|
||||
}
|
||||
|
||||
export type result<T> = {
|
||||
code?: number;
|
||||
data?: T;
|
||||
msg?: string;
|
||||
status: number;
|
||||
};
|
||||
|
||||
export type resultStatus = 0 | 1 | 2 | 4 | 5;
|
||||
// 0 success; 1 framework/client protocol error; 2 business error; 4 external request error; 5 external timeout
|
||||
|
||||
export type RequestOptions = {
|
||||
signal?: AbortSignal;
|
||||
state?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type StreamOptions = {
|
||||
signal?: AbortSignal;
|
||||
state?: Record<string, string>;
|
||||
/**
|
||||
* 原生断线重连:流因传输失败(status 4/5)结束时按指数退避自动重连。
|
||||
* dto 传工厂函数时每次重连重新求值,用于刷新续传游标。
|
||||
* 未传时关闭(默认),与 request 语义一致。
|
||||
*/
|
||||
retry?: StreamRetryOptions | false;
|
||||
/** 空闲看门狗:超过该毫秒数未收到任何字节(含心跳空行)即判定流死亡并走重连。0 关闭。 */
|
||||
idleTimeoutMs?: number;
|
||||
};
|
||||
|
||||
export type StreamRetryOptions = {
|
||||
/** 最大重连次数,默认 Infinity(直到 signal 取消或成功)。 */
|
||||
maxAttempts?: number;
|
||||
/** 首次重连延迟(毫秒),默认 500。 */
|
||||
baseDelayMs?: number;
|
||||
/** 退避上限(毫秒),默认 10000。实际延迟为指数退避 × 0.75~1.25 抖动。 */
|
||||
maxDelayMs?: number;
|
||||
};
|
||||
|
||||
export type RequestInterceptor = (
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
state: Record<string, string>,
|
||||
dto?: any
|
||||
) => Promise<void> | void;
|
||||
|
||||
export type ResponseContext = {
|
||||
readonly requestState: Readonly<Record<string, string>>;
|
||||
readonly response?: Response;
|
||||
};
|
||||
|
||||
export type ResponseInterceptor = (
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
result: result<any>
|
||||
) => Promise<result<any> | void> | result<any> | void;
|
||||
|
||||
export type ContextResponseInterceptor = (
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
result: result<any>,
|
||||
context: ResponseContext
|
||||
) => Promise<result<any> | void> | result<any> | void;
|
||||
|
||||
function messageOf(error: unknown): string {
|
||||
if (error instanceof Error && error.message) return error.message;
|
||||
if (typeof error === "string" && error) return error;
|
||||
return "unknown error";
|
||||
}
|
||||
|
||||
function failure(status: resultStatus, msg: string): result<any> {
|
||||
return { status, msg };
|
||||
}
|
||||
|
||||
function isTimeout(error: unknown, signal?: AbortSignal): boolean {
|
||||
const errorName = error !== null && typeof error === "object"
|
||||
? (error as { name?: unknown }).name
|
||||
: undefined;
|
||||
const reason = signal?.reason;
|
||||
const reasonName = reason !== null && typeof reason === "object"
|
||||
? (reason as { name?: unknown }).name
|
||||
: undefined;
|
||||
return errorName === "TimeoutError" || reasonName === "TimeoutError";
|
||||
}
|
||||
|
||||
function requestFailure(error: unknown, signal: AbortSignal | undefined, stream: boolean): result<any> {
|
||||
if (isTimeout(error, signal)) {
|
||||
return failure(5, stream ? "Stream timed out" : "Request timed out");
|
||||
}
|
||||
if (signal?.aborted === true) {
|
||||
return failure(4, stream ? "Stream aborted" : "Request aborted");
|
||||
}
|
||||
const kind = stream ? "External stream request" : "External request";
|
||||
return failure(4, `${kind} failed: ${messageOf(error)}`);
|
||||
}
|
||||
|
||||
function isResult(value: unknown): value is result<any> {
|
||||
return value !== null && typeof value === "object" &&
|
||||
typeof (value as { status?: unknown }).status === "number";
|
||||
}
|
||||
|
||||
function mediaType(response: Response): string {
|
||||
return (response.headers.get("content-type") || "").split(";", 1)[0].trim().toLowerCase();
|
||||
}
|
||||
|
||||
function excerpt(text: string, limit = 180): string {
|
||||
const value = text.replace(/\s+/g, " ").trim();
|
||||
return value.length <= limit ? value : `${value.slice(0, limit)}...`;
|
||||
}
|
||||
|
||||
function externalFailure(response: Response, detail?: string): result<any> {
|
||||
const timeout = response.status === 408 || response.status === 504;
|
||||
const statusText = response.statusText || (timeout ? "timeout" : "request failed");
|
||||
const suffix = detail ? `: ${detail}` : "";
|
||||
return failure(timeout ? 5 : 4, `HTTP ${response.status} ${statusText}${suffix}`);
|
||||
}
|
||||
|
||||
function responseReadFailure(
|
||||
error: unknown,
|
||||
response: Response,
|
||||
signal: AbortSignal | undefined,
|
||||
stream: boolean
|
||||
): result<any> {
|
||||
if (isTimeout(error, signal)) {
|
||||
return failure(5, stream ? "Stream timed out" : "Request timed out");
|
||||
}
|
||||
if (signal?.aborted === true) {
|
||||
return failure(4, stream ? "Stream aborted" : "Request aborted");
|
||||
}
|
||||
const kind = stream ? "Stream" : "Response body";
|
||||
return response.ok
|
||||
? failure(4, `${kind} failed: ${messageOf(error)}`)
|
||||
: externalFailure(response, `response body failed: ${messageOf(error)}`);
|
||||
}
|
||||
|
||||
function parseResult(response: Response, text: string): result<any> {
|
||||
const body = text.trim();
|
||||
if (!body) {
|
||||
return response.ok
|
||||
? failure(1, "Empty response body")
|
||||
: externalFailure(response);
|
||||
}
|
||||
|
||||
let value: unknown;
|
||||
try {
|
||||
value = parseLossless(body);
|
||||
} catch {
|
||||
if (!response.ok) return externalFailure(response, excerpt(body));
|
||||
const type = mediaType(response);
|
||||
if (type === "text/html" || /^\s*(?:<!doctype\s+html|<html\b)/i.test(body)) {
|
||||
return failure(1, `Unexpected HTML response: ${excerpt(body)}`);
|
||||
}
|
||||
return failure(1, `Invalid JSON response: ${excerpt(body)}`);
|
||||
}
|
||||
if (!isResult(value)) {
|
||||
return response.ok
|
||||
? failure(1, "Invalid fun response")
|
||||
: externalFailure(response, "invalid fun response");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export class Client {
|
||||
private url: string;
|
||||
private state: Record<string, string> = {};
|
||||
private requestInterceptors: RequestInterceptor[] = [];
|
||||
private responseInterceptors: ContextResponseInterceptor[] = [];
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url.replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
setState(state: Record<string, string>) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
addRequestInterceptor(interceptor: RequestInterceptor) {
|
||||
this.requestInterceptors.push(interceptor);
|
||||
}
|
||||
|
||||
addResponseInterceptor(interceptor: ResponseInterceptor): void;
|
||||
addResponseInterceptor(interceptor: ContextResponseInterceptor): void;
|
||||
addResponseInterceptor(interceptor: ResponseInterceptor | ContextResponseInterceptor) {
|
||||
this.responseInterceptors.push(interceptor as ContextResponseInterceptor);
|
||||
}
|
||||
|
||||
private async interceptResponse(
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
initial: result<any>,
|
||||
requestState: Readonly<Record<string, string>>,
|
||||
response?: Response
|
||||
): Promise<result<any>> {
|
||||
let current = initial;
|
||||
const context: ResponseContext = Object.freeze(
|
||||
response === undefined ? { requestState } : { requestState, response }
|
||||
);
|
||||
for (const interceptor of this.responseInterceptors) {
|
||||
try {
|
||||
const replaced = await interceptor(serviceName, methodName, current, context);
|
||||
if (replaced) current = replaced;
|
||||
} catch (error) {
|
||||
current = failure(1, `Response interceptor failed: ${messageOf(error)}`);
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private async interceptRequest(
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
state: Record<string, string>,
|
||||
dto: any
|
||||
): Promise<void> {
|
||||
for (const interceptor of this.requestInterceptors) {
|
||||
await interceptor(serviceName, methodName, state, dto);
|
||||
}
|
||||
}
|
||||
|
||||
private snapshotState(state: Record<string, string>): Readonly<Record<string, string>> {
|
||||
return Object.freeze({ ...state });
|
||||
}
|
||||
|
||||
async request<T>(
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
dto?: any,
|
||||
options?: RequestOptions
|
||||
): Promise<result<T>> {
|
||||
let state: Record<string, string>;
|
||||
try {
|
||||
state = { ...this.state, ...options?.state };
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Could not prepare request state: ${messageOf(error)}`),
|
||||
Object.freeze({})
|
||||
) as result<T>;
|
||||
}
|
||||
try {
|
||||
await this.interceptRequest(serviceName, methodName, state, dto);
|
||||
} catch (error) {
|
||||
let requestState: Readonly<Record<string, string>>;
|
||||
try {
|
||||
requestState = this.snapshotState(state);
|
||||
} catch (snapshotError) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Could not snapshot request state: ${messageOf(snapshotError)}`),
|
||||
Object.freeze({})
|
||||
) as result<T>;
|
||||
}
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Request interceptor failed: ${messageOf(error)}`),
|
||||
requestState
|
||||
) as result<T>;
|
||||
}
|
||||
let requestState: Readonly<Record<string, string>>;
|
||||
try {
|
||||
requestState = this.snapshotState(state);
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Could not snapshot request state: ${messageOf(error)}`),
|
||||
Object.freeze({})
|
||||
) as result<T>;
|
||||
}
|
||||
|
||||
let body: string;
|
||||
try {
|
||||
const serialized = stringifyLossless({
|
||||
serviceName,
|
||||
methodName,
|
||||
data: dto,
|
||||
...(Object.keys(requestState).length ? { state: requestState } : {}),
|
||||
});
|
||||
if (serialized === undefined) throw new Error("serialization produced no output");
|
||||
body = serialized;
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Could not serialize request: ${messageOf(error)}`),
|
||||
requestState
|
||||
) as result<T>;
|
||||
}
|
||||
|
||||
let output: result<any>;
|
||||
let response: Response | undefined;
|
||||
try {
|
||||
response = await fetch(`${this.url}/cell`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body,
|
||||
signal: options?.signal,
|
||||
});
|
||||
try {
|
||||
output = parseResult(response, await response.text());
|
||||
} catch (error) {
|
||||
output = responseReadFailure(error, response, options?.signal, false);
|
||||
}
|
||||
} catch (error) {
|
||||
output = requestFailure(error, options?.signal, false);
|
||||
}
|
||||
return await this.interceptResponse(serviceName, methodName, output, requestState, response) as result<T>;
|
||||
}
|
||||
|
||||
async stream<T>(
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
dto: any | undefined | (() => any | undefined),
|
||||
onMessage: (data: T) => unknown,
|
||||
options?: StreamOptions
|
||||
): Promise<result<void>> {
|
||||
// dto 工厂:重连时重新求值,用于刷新续传游标(如 afterId)
|
||||
const getDto = typeof dto === "function" ? (dto as () => any | undefined) : () => dto;
|
||||
// 重连默认关闭(与 request 语义一致);传 retry: {...} 显式开启
|
||||
const retry = options?.retry
|
||||
? {
|
||||
maxAttempts: Infinity,
|
||||
baseDelayMs: 500,
|
||||
maxDelayMs: 10000,
|
||||
...options.retry,
|
||||
}
|
||||
: null;
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
const r = await this.streamOnce<T>(serviceName, methodName, getDto(), onMessage, options);
|
||||
// 0 成功、1 协议错误、2 业务错误:终结态,不重连
|
||||
if (r.status !== 4 && r.status !== 5) return r;
|
||||
if (!retry) return r;
|
||||
if (options?.signal?.aborted) return r;
|
||||
if (attempt + 1 > retry.maxAttempts) return r;
|
||||
const delay =
|
||||
Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** attempt) * (0.75 + Math.random() * 0.5);
|
||||
// 退避等待:AbortSignal.timeout 免定时器 API,且后台标签页照常走时
|
||||
await new Promise<void>((resolve) => {
|
||||
if (options?.signal?.aborted) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
AbortSignal.timeout(delay).addEventListener("abort", () => resolve(), { once: true });
|
||||
options?.signal?.addEventListener("abort", () => resolve(), { once: true });
|
||||
});
|
||||
if (options?.signal?.aborted) return r;
|
||||
}
|
||||
}
|
||||
|
||||
private async streamOnce<T>(
|
||||
serviceName: string,
|
||||
methodName: string,
|
||||
dto: any | undefined,
|
||||
onMessage: (data: T) => unknown,
|
||||
options?: StreamOptions
|
||||
): Promise<result<void>> {
|
||||
let state: Record<string, string>;
|
||||
try {
|
||||
state = { ...this.state, ...options?.state };
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Could not prepare request state: ${messageOf(error)}`),
|
||||
Object.freeze({})
|
||||
);
|
||||
}
|
||||
try {
|
||||
await this.interceptRequest(serviceName, methodName, state, dto);
|
||||
} catch (error) {
|
||||
let requestState: Readonly<Record<string, string>>;
|
||||
try {
|
||||
requestState = this.snapshotState(state);
|
||||
} catch (snapshotError) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Could not snapshot request state: ${messageOf(snapshotError)}`),
|
||||
Object.freeze({})
|
||||
);
|
||||
}
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Request interceptor failed: ${messageOf(error)}`),
|
||||
requestState
|
||||
);
|
||||
}
|
||||
let requestState: Readonly<Record<string, string>>;
|
||||
try {
|
||||
requestState = this.snapshotState(state);
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Could not snapshot request state: ${messageOf(error)}`),
|
||||
Object.freeze({})
|
||||
);
|
||||
}
|
||||
|
||||
let body: string;
|
||||
try {
|
||||
const serialized = stringifyLossless({
|
||||
serviceName,
|
||||
methodName,
|
||||
data: dto,
|
||||
...(Object.keys(requestState).length ? { state: requestState } : {}),
|
||||
});
|
||||
if (serialized === undefined) throw new Error("serialization produced no output");
|
||||
body = serialized;
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failure(1, `Could not serialize request: ${messageOf(error)}`),
|
||||
requestState
|
||||
);
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${this.url}/cell`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body,
|
||||
signal: options?.signal,
|
||||
});
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
requestFailure(error, options?.signal, true),
|
||||
requestState
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
let text: string;
|
||||
try {
|
||||
text = await response.text();
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
responseReadFailure(error, response, options?.signal, true),
|
||||
requestState,
|
||||
response
|
||||
);
|
||||
}
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
parseResult(response, text),
|
||||
requestState,
|
||||
response
|
||||
);
|
||||
}
|
||||
|
||||
if (mediaType(response) !== "application/x-ndjson") {
|
||||
let text: string;
|
||||
try {
|
||||
text = await response.text();
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
responseReadFailure(error, response, options?.signal, true),
|
||||
requestState,
|
||||
response
|
||||
);
|
||||
}
|
||||
const rpcResult = parseResult(response, text);
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
rpcResult.status === 0
|
||||
? failure(1, "Expected application/x-ndjson response")
|
||||
: rpcResult,
|
||||
requestState,
|
||||
response
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
{ status: 0 },
|
||||
requestState,
|
||||
response
|
||||
);
|
||||
}
|
||||
|
||||
let reader: ReadableStreamDefaultReader<Uint8Array>;
|
||||
try {
|
||||
reader = response.body.getReader();
|
||||
} catch (error) {
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
responseReadFailure(error, response, options?.signal, true),
|
||||
requestState,
|
||||
response
|
||||
);
|
||||
}
|
||||
const decoder = new TextDecoder("utf-8", { fatal: true });
|
||||
let buffer = "";
|
||||
let lineNumber = 0;
|
||||
let failed: result<any> | undefined;
|
||||
let cause: unknown;
|
||||
|
||||
const emitLine = async (line: string) => {
|
||||
const payload = line.replace(/\r$/, "").trim();
|
||||
if (!payload) return;
|
||||
let data: T;
|
||||
try {
|
||||
data = parseLossless(payload) as T;
|
||||
} catch (error) {
|
||||
failed = failure(1, `Invalid NDJSON at line ${lineNumber}: ${excerpt(payload)}`);
|
||||
cause = error;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await onMessage(data);
|
||||
} catch (error) {
|
||||
failed = failure(1, `Stream callback failed: ${messageOf(error)}`);
|
||||
cause = error;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// 空闲看门狗:idleTimeoutMs 内未收到任何字节(含心跳空行)判定流死亡,
|
||||
// 抛出哨兵错误 → status 5 → 交给上层重连。服务端 25s 心跳时建议 35000。
|
||||
const STREAM_IDLE = Symbol("stream-idle");
|
||||
for (;;) {
|
||||
let part: ReadableStreamReadResult<Uint8Array>;
|
||||
try {
|
||||
if (options?.idleTimeoutMs) {
|
||||
const idlePromise = new Promise<never>((_, reject) => {
|
||||
AbortSignal.timeout(options.idleTimeoutMs!).addEventListener(
|
||||
"abort",
|
||||
() => reject(STREAM_IDLE),
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
idlePromise.catch(() => {}); // read 先返回时防 unhandled rejection
|
||||
part = await Promise.race([reader.read(), idlePromise]);
|
||||
} else {
|
||||
part = await reader.read();
|
||||
}
|
||||
} catch (error) {
|
||||
cause = error;
|
||||
if (error === STREAM_IDLE) {
|
||||
failed = failure(5, "Stream idle timeout");
|
||||
} else if (isTimeout(error, options?.signal)) {
|
||||
failed = failure(5, "Stream timed out");
|
||||
} else if (options?.signal?.aborted === true) {
|
||||
failed = failure(4, "Stream aborted");
|
||||
} else {
|
||||
failed = failure(4, `Stream read failed: ${messageOf(error)}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (part.done) break;
|
||||
try {
|
||||
buffer += decoder.decode(part.value, { stream: true });
|
||||
} catch (error) {
|
||||
failed = failure(1, `Invalid UTF-8 stream data: ${messageOf(error)}`);
|
||||
cause = error;
|
||||
break;
|
||||
}
|
||||
for (;;) {
|
||||
const newline = buffer.indexOf("\n");
|
||||
if (newline < 0) break;
|
||||
const line = buffer.slice(0, newline);
|
||||
buffer = buffer.slice(newline + 1);
|
||||
lineNumber++;
|
||||
await emitLine(line);
|
||||
if (failed) break;
|
||||
}
|
||||
if (failed) break;
|
||||
}
|
||||
|
||||
if (!failed) {
|
||||
try {
|
||||
buffer += decoder.decode();
|
||||
} catch (error) {
|
||||
failed = failure(1, `Invalid UTF-8 stream data: ${messageOf(error)}`);
|
||||
cause = error;
|
||||
}
|
||||
}
|
||||
if (!failed && buffer.length > 0) {
|
||||
lineNumber++;
|
||||
await emitLine(buffer);
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (failed) {
|
||||
try {
|
||||
await reader.cancel(cause);
|
||||
} catch {
|
||||
// The reader may already be closed by the runtime.
|
||||
}
|
||||
}
|
||||
try {
|
||||
reader.releaseLock();
|
||||
} catch {
|
||||
// The reader may already be errored or released.
|
||||
}
|
||||
}
|
||||
return await this.interceptResponse(
|
||||
serviceName,
|
||||
methodName,
|
||||
failed || { status: 0 },
|
||||
requestState,
|
||||
response
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface createShareDto {
|
||||
fileId:number
|
||||
expireMinutes:number
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type statsView from "./statsView";
|
||||
|
||||
export default class dashboardSvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async stats(options?: RequestOptions): Promise<result<statsView>> {
|
||||
return await this.client.request<statsView>("dashboardSvc", "stats", undefined, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default interface dbColumnView {
|
||||
table:string
|
||||
name:string
|
||||
type:string
|
||||
pk:boolean
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface dbDatabasesView {
|
||||
names:string[]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type dbDatabasesView from "./dbDatabasesView";
|
||||
import type dbQueryDto from "./dbQueryDto";
|
||||
import type dbQueryView from "./dbQueryView";
|
||||
import type dbScopeDto from "./dbScopeDto";
|
||||
import type dbTablesView from "./dbTablesView";
|
||||
import type dbconnIdDto from "./dbconnIdDto";
|
||||
import type dbconnView from "./dbconnView";
|
||||
import type okView from "./okView";
|
||||
import type saveDbconnDto from "./saveDbconnDto";
|
||||
|
||||
export default class dbMgrSvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async databases(dto:dbScopeDto, options?: RequestOptions): Promise<result<dbDatabasesView>> {
|
||||
return await this.client.request<dbDatabasesView>("dbMgrSvc", "databases", dto, options)
|
||||
}
|
||||
async delete(dto:dbconnIdDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("dbMgrSvc", "delete", dto, options)
|
||||
}
|
||||
async list(options?: RequestOptions): Promise<result<dbconnView[]>> {
|
||||
return await this.client.request<dbconnView[]>("dbMgrSvc", "list", undefined, options)
|
||||
}
|
||||
async query(dto:dbQueryDto, options?: RequestOptions): Promise<result<dbQueryView>> {
|
||||
return await this.client.request<dbQueryView>("dbMgrSvc", "query", dto, options)
|
||||
}
|
||||
async save(dto:saveDbconnDto, options?: RequestOptions): Promise<result<dbconnView>> {
|
||||
return await this.client.request<dbconnView>("dbMgrSvc", "save", dto, options)
|
||||
}
|
||||
async tables(dto:dbScopeDto, options?: RequestOptions): Promise<result<dbTablesView>> {
|
||||
return await this.client.request<dbTablesView>("dbMgrSvc", "tables", dto, options)
|
||||
}
|
||||
async test(dto:dbconnIdDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("dbMgrSvc", "test", dto, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default interface dbQueryDto {
|
||||
id:number
|
||||
database?:string | null
|
||||
sql:string
|
||||
maxRows?:number | null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default interface dbQueryView {
|
||||
isSelect:boolean
|
||||
columns:string[]
|
||||
rowsJson:string
|
||||
rowCount:number
|
||||
affectedRows:number
|
||||
truncated:boolean
|
||||
elapsedMs:number
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface dbScopeDto {
|
||||
id:number
|
||||
database?:string | null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface dbTableView {
|
||||
name:string
|
||||
type:string
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type dbColumnView from "./dbColumnView";
|
||||
import type dbTableView from "./dbTableView";
|
||||
export default interface dbTablesView {
|
||||
tables:dbTableView[]
|
||||
columns:dbColumnView[]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface dbconnIdDto {
|
||||
id:number
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default interface dbconnView {
|
||||
id:number
|
||||
name:string
|
||||
engine:string
|
||||
host:string
|
||||
port:number
|
||||
username:string
|
||||
database:string
|
||||
hasPassword:boolean
|
||||
createdAt:number
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface deleteToolDto {
|
||||
id:number
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface deleteUserDto {
|
||||
id:number
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default interface diskMountView {
|
||||
mount:string
|
||||
fs:string
|
||||
total:number
|
||||
used:number
|
||||
avail:number
|
||||
percent:number
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type createShareDto from "./createShareDto";
|
||||
import type fileView from "./fileView";
|
||||
import type idDto from "./idDto";
|
||||
import type okView from "./okView";
|
||||
import type shareView from "./shareView";
|
||||
|
||||
export default class diskSvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async createShare(dto:createShareDto, options?: RequestOptions): Promise<result<shareView>> {
|
||||
return await this.client.request<shareView>("diskSvc", "createShare", dto, options)
|
||||
}
|
||||
async deleteFile(dto:idDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("diskSvc", "deleteFile", dto, options)
|
||||
}
|
||||
async deleteShare(dto:idDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("diskSvc", "deleteShare", dto, options)
|
||||
}
|
||||
async list(options?: RequestOptions): Promise<result<fileView[]>> {
|
||||
return await this.client.request<fileView[]>("diskSvc", "list", undefined, options)
|
||||
}
|
||||
async listShares(options?: RequestOptions): Promise<result<shareView[]>> {
|
||||
return await this.client.request<shareView[]>("diskSvc", "listShares", undefined, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default interface fileView {
|
||||
id:number
|
||||
name:string
|
||||
size:number
|
||||
mime:string
|
||||
createdAt:number
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Client } from "./client";
|
||||
import authSvc from "./authSvc";
|
||||
import bookmarkSvc from "./bookmarkSvc";
|
||||
import dashboardSvc from "./dashboardSvc";
|
||||
import dbMgrSvc from "./dbMgrSvc";
|
||||
import diskSvc from "./diskSvc";
|
||||
import licenseSvc from "./licenseSvc";
|
||||
import memorySvc from "./memorySvc";
|
||||
import paySvc from "./paySvc";
|
||||
import proxySvc from "./proxySvc";
|
||||
import serverSvc from "./serverSvc";
|
||||
import settingSvc from "./settingSvc";
|
||||
import toolSvc from "./toolSvc";
|
||||
import userSvc from "./userSvc";
|
||||
import vaultSvc from "./vaultSvc";
|
||||
import webSvc from "./webSvc";
|
||||
|
||||
export class defaultApi extends Client {
|
||||
constructor(url: string) {
|
||||
super(url);
|
||||
}
|
||||
public authSvc: authSvc = new authSvc(this);
|
||||
public bookmarkSvc: bookmarkSvc = new bookmarkSvc(this);
|
||||
public dashboardSvc: dashboardSvc = new dashboardSvc(this);
|
||||
public dbMgrSvc: dbMgrSvc = new dbMgrSvc(this);
|
||||
public diskSvc: diskSvc = new diskSvc(this);
|
||||
public licenseSvc: licenseSvc = new licenseSvc(this);
|
||||
public memorySvc: memorySvc = new memorySvc(this);
|
||||
public paySvc: paySvc = new paySvc(this);
|
||||
public proxySvc: proxySvc = new proxySvc(this);
|
||||
public serverSvc: serverSvc = new serverSvc(this);
|
||||
public settingSvc: settingSvc = new settingSvc(this);
|
||||
public toolSvc: toolSvc = new toolSvc(this);
|
||||
public userSvc: userSvc = new userSvc(this);
|
||||
public vaultSvc: vaultSvc = new vaultSvc(this);
|
||||
public webSvc: webSvc = new webSvc(this);
|
||||
}
|
||||
|
||||
export default class api {
|
||||
static create(url: string): defaultApi {
|
||||
return new defaultApi(url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface idDto {
|
||||
id:number
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface licenseAppSaveDto {
|
||||
id:number
|
||||
name:string
|
||||
enabled?:number | null
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default interface licenseAppView {
|
||||
id:number
|
||||
appid:string
|
||||
name:string
|
||||
enabled:number
|
||||
cardCount:number
|
||||
createdAt:number
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import type licenseAppView from "./licenseAppView";
|
||||
export default interface licenseAppsView {
|
||||
items:licenseAppView[]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export default interface licenseCardView {
|
||||
id:number
|
||||
card:string
|
||||
hours:number
|
||||
domain:string
|
||||
status:number
|
||||
activatedAt:number
|
||||
expiresAt:number
|
||||
createdAt:number
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface licenseGenDto {
|
||||
appId:number
|
||||
count:number
|
||||
hours:number
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface licenseGenView {
|
||||
cards:string[]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface licenseIdDto {
|
||||
id:number
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default interface licenseListDto {
|
||||
appId:number
|
||||
page:number
|
||||
pageSize:number
|
||||
kw?:string | null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type licenseCardView from "./licenseCardView";
|
||||
export default interface licensePageView {
|
||||
total:number
|
||||
items:licenseCardView[]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface licenseStatsDto {
|
||||
appId:number
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default interface licenseStatsView {
|
||||
total:number
|
||||
unused:number
|
||||
active:number
|
||||
expired:number
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type licenseAppSaveDto from "./licenseAppSaveDto";
|
||||
import type licenseAppView from "./licenseAppView";
|
||||
import type licenseAppsView from "./licenseAppsView";
|
||||
import type licenseGenDto from "./licenseGenDto";
|
||||
import type licenseGenView from "./licenseGenView";
|
||||
import type licenseIdDto from "./licenseIdDto";
|
||||
import type licenseListDto from "./licenseListDto";
|
||||
import type licensePageView from "./licensePageView";
|
||||
import type licenseStatsDto from "./licenseStatsDto";
|
||||
import type licenseStatsView from "./licenseStatsView";
|
||||
import type okView from "./okView";
|
||||
|
||||
export default class licenseSvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async apps(options?: RequestOptions): Promise<result<licenseAppsView>> {
|
||||
return await this.client.request<licenseAppsView>("licenseSvc", "apps", undefined, options)
|
||||
}
|
||||
async delete(dto:licenseIdDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("licenseSvc", "delete", dto, options)
|
||||
}
|
||||
async deleteApp(dto:licenseIdDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("licenseSvc", "deleteApp", dto, options)
|
||||
}
|
||||
async generate(dto:licenseGenDto, options?: RequestOptions): Promise<result<licenseGenView>> {
|
||||
return await this.client.request<licenseGenView>("licenseSvc", "generate", dto, options)
|
||||
}
|
||||
async list(dto:licenseListDto, options?: RequestOptions): Promise<result<licensePageView>> {
|
||||
return await this.client.request<licensePageView>("licenseSvc", "list", dto, options)
|
||||
}
|
||||
async saveApp(dto:licenseAppSaveDto, options?: RequestOptions): Promise<result<licenseAppView>> {
|
||||
return await this.client.request<licenseAppView>("licenseSvc", "saveApp", dto, options)
|
||||
}
|
||||
async stats(dto:licenseStatsDto, options?: RequestOptions): Promise<result<licenseStatsView>> {
|
||||
return await this.client.request<licenseStatsView>("licenseSvc", "stats", dto, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface listUsersDto {
|
||||
page:number
|
||||
pageSize:number
|
||||
keyword?:string | null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface loginDto {
|
||||
name:string
|
||||
password:string
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface memoryKeyDto {
|
||||
key:string
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default interface memoryListDto {
|
||||
project:string
|
||||
tag:string
|
||||
query:string
|
||||
limit:number
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import type memoryMetaView from "./memoryMetaView";
|
||||
export default interface memoryListView {
|
||||
items:memoryMetaView[]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface memoryMcpView {
|
||||
url:string
|
||||
token:string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default interface memoryMetaView {
|
||||
key:string
|
||||
project:string
|
||||
tags:string[]
|
||||
writtenBy:string
|
||||
revision:number
|
||||
updatedAt:number
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface memoryProjectView {
|
||||
name:string
|
||||
count:number
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import type memoryProjectView from "./memoryProjectView";
|
||||
export default interface memoryProjectsView {
|
||||
items:memoryProjectView[]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default interface memorySaveDto {
|
||||
key:string
|
||||
content:string
|
||||
project:string
|
||||
tags:string[]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface memorySaveView {
|
||||
key:string
|
||||
url:string
|
||||
revision:number
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type memoryKeyDto from "./memoryKeyDto";
|
||||
import type memoryListDto from "./memoryListDto";
|
||||
import type memoryListView from "./memoryListView";
|
||||
import type memoryMcpView from "./memoryMcpView";
|
||||
import type memoryProjectsView from "./memoryProjectsView";
|
||||
import type memorySaveDto from "./memorySaveDto";
|
||||
import type memorySaveView from "./memorySaveView";
|
||||
import type memoryView from "./memoryView";
|
||||
import type okView from "./okView";
|
||||
|
||||
export default class memorySvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async delete(dto:memoryKeyDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("memorySvc", "delete", dto, options)
|
||||
}
|
||||
async get(dto:memoryKeyDto, options?: RequestOptions): Promise<result<memoryView>> {
|
||||
return await this.client.request<memoryView>("memorySvc", "get", dto, options)
|
||||
}
|
||||
async list(dto:memoryListDto, options?: RequestOptions): Promise<result<memoryListView>> {
|
||||
return await this.client.request<memoryListView>("memorySvc", "list", dto, options)
|
||||
}
|
||||
async mcpInfo(options?: RequestOptions): Promise<result<memoryMcpView>> {
|
||||
return await this.client.request<memoryMcpView>("memorySvc", "mcpInfo", undefined, options)
|
||||
}
|
||||
async projects(options?: RequestOptions): Promise<result<memoryProjectsView>> {
|
||||
return await this.client.request<memoryProjectsView>("memorySvc", "projects", undefined, options)
|
||||
}
|
||||
async resetToken(options?: RequestOptions): Promise<result<memoryMcpView>> {
|
||||
return await this.client.request<memoryMcpView>("memorySvc", "resetToken", undefined, options)
|
||||
}
|
||||
async save(dto:memorySaveDto, options?: RequestOptions): Promise<result<memorySaveView>> {
|
||||
return await this.client.request<memorySaveView>("memorySvc", "save", dto, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default interface memoryView {
|
||||
key:string
|
||||
url:string
|
||||
content:string
|
||||
project:string
|
||||
tags:string[]
|
||||
writtenBy:string
|
||||
revision:number
|
||||
createdAt:number
|
||||
updatedAt:number
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface okView {
|
||||
message:string
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface payCashierDto {
|
||||
tradeNo:string
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface payCashierStatusView {
|
||||
status:number
|
||||
returnUrl:string
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export default interface payCashierView {
|
||||
tradeNo:string
|
||||
outTradeNo:string
|
||||
subject:string
|
||||
money:number
|
||||
channel:string
|
||||
status:number
|
||||
payurl:string
|
||||
qrcode:string
|
||||
cards:string
|
||||
createdAt:number
|
||||
expiredAt:number
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default interface payOrderListDto {
|
||||
page:number
|
||||
pageSize:number
|
||||
status?:number | null
|
||||
kw?:string | null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type payOrderView from "./payOrderView";
|
||||
export default interface payOrderPageView {
|
||||
total:number
|
||||
items:payOrderView[]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export default interface payOrderView {
|
||||
id:number
|
||||
tradeNo:string
|
||||
outTradeNo:string
|
||||
channel:string
|
||||
subject:string
|
||||
money:number
|
||||
status:number
|
||||
notified:number
|
||||
payurl:string
|
||||
paidAt:number
|
||||
expiredAt:number
|
||||
createdAt:number
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default interface payStatsView {
|
||||
todayCount:number
|
||||
todayMoney:number
|
||||
totalCount:number
|
||||
totalMoney:number
|
||||
pendingCount:number
|
||||
todayPaidCount:number
|
||||
totalPaidCount:number
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Client, type result, type RequestOptions, type StreamOptions } from "./client";
|
||||
import type payCashierDto from "./payCashierDto";
|
||||
import type payCashierStatusView from "./payCashierStatusView";
|
||||
import type payCashierView from "./payCashierView";
|
||||
import type payOrderListDto from "./payOrderListDto";
|
||||
import type payOrderPageView from "./payOrderPageView";
|
||||
import type payStatsView from "./payStatsView";
|
||||
import type payTestPayDto from "./payTestPayDto";
|
||||
import type payTestPayView from "./payTestPayView";
|
||||
import type payUpstreamDto from "./payUpstreamDto";
|
||||
import type payUpstreamStatusView from "./payUpstreamStatusView";
|
||||
|
||||
export default class paySvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async cashierInfo(dto:payCashierDto, options?: RequestOptions): Promise<result<payCashierView>> {
|
||||
return await this.client.request<payCashierView>("paySvc", "cashierInfo", dto, options)
|
||||
}
|
||||
async cashierStatus(dto:payCashierDto, options?: RequestOptions): Promise<result<payCashierStatusView>> {
|
||||
return await this.client.request<payCashierStatusView>("paySvc", "cashierStatus", dto, options)
|
||||
}
|
||||
async list(dto:payOrderListDto, options?: RequestOptions): Promise<result<payOrderPageView>> {
|
||||
return await this.client.request<payOrderPageView>("paySvc", "list", dto, options)
|
||||
}
|
||||
async saveUpstream(dto:payUpstreamDto, options?: RequestOptions): Promise<result<payUpstreamStatusView>> {
|
||||
return await this.client.request<payUpstreamStatusView>("paySvc", "saveUpstream", dto, options)
|
||||
}
|
||||
async stats(options?: RequestOptions): Promise<result<payStatsView>> {
|
||||
return await this.client.request<payStatsView>("paySvc", "stats", undefined, options)
|
||||
}
|
||||
async testPay(dto:payTestPayDto, options?: RequestOptions): Promise<result<payTestPayView>> {
|
||||
return await this.client.request<payTestPayView>("paySvc", "testPay", dto, options)
|
||||
}
|
||||
async upstreamStatus(options?: RequestOptions): Promise<result<payUpstreamStatusView>> {
|
||||
return await this.client.request<payUpstreamStatusView>("paySvc", "upstreamStatus", undefined, options)
|
||||
}
|
||||
async watch(dto:payCashierDto | (() => payCashierDto), onMessage: (data: payCashierStatusView) => unknown, options?: StreamOptions): Promise<result<void>> {
|
||||
return await this.client.stream<payCashierStatusView>("paySvc", "watch", dto, onMessage, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface payTestPayDto {
|
||||
money:string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default interface payTestPayView {
|
||||
tradeNo:string
|
||||
payurl:string
|
||||
qrcode:string
|
||||
money:number
|
||||
cashier:string
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface payTrendPoint {
|
||||
day:string
|
||||
money:number
|
||||
count:number
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface payUpstreamDto {
|
||||
username:string
|
||||
password:string
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export default interface payUpstreamStatusView {
|
||||
configured:number
|
||||
username:string
|
||||
nickname:string
|
||||
shop:string
|
||||
tokenAge:number
|
||||
goodsId:number
|
||||
goodsKey:string
|
||||
goodsName:string
|
||||
unitPrice:number
|
||||
stockCount:number
|
||||
targetMoney:number
|
||||
walletOk:number
|
||||
walletAvail:number
|
||||
walletFrozen:number
|
||||
ensuring:number
|
||||
lastError:string
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface proxyKeySaveDto {
|
||||
name:string
|
||||
regionId:number
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export default interface proxyKeyView {
|
||||
id:number
|
||||
key:string
|
||||
name:string
|
||||
regionId:number
|
||||
regionCode:string
|
||||
regionZhName:string
|
||||
enabled:boolean
|
||||
usedCount:number
|
||||
lastUsedAt:number
|
||||
createdAt:number
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default interface proxyListDto {
|
||||
regionId:number
|
||||
protocol:string
|
||||
keyword:string
|
||||
alive:number
|
||||
page:number
|
||||
pageSize:number
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type proxyView from "./proxyView";
|
||||
export default interface proxyListView {
|
||||
total:number
|
||||
items:proxyView[]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export default interface proxyRegionView {
|
||||
id:number
|
||||
code:string
|
||||
name:string
|
||||
zhName:string
|
||||
region:string
|
||||
aliveCount:number
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default interface proxyStatsView {
|
||||
total:number
|
||||
regions:number
|
||||
lastSyncAt:number
|
||||
lastCheckAt:number
|
||||
lastStatus:string
|
||||
checking:boolean
|
||||
syncing:boolean
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type idDto from "./idDto";
|
||||
import type okView from "./okView";
|
||||
import type proxyKeySaveDto from "./proxyKeySaveDto";
|
||||
import type proxyKeyView from "./proxyKeyView";
|
||||
import type proxyListDto from "./proxyListDto";
|
||||
import type proxyListView from "./proxyListView";
|
||||
import type proxyRegionView from "./proxyRegionView";
|
||||
import type proxyStatsView from "./proxyStatsView";
|
||||
import type proxySyncView from "./proxySyncView";
|
||||
|
||||
export default class proxySvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async checkNow(options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("proxySvc", "checkNow", undefined, options)
|
||||
}
|
||||
async createKey(dto:proxyKeySaveDto, options?: RequestOptions): Promise<result<proxyKeyView>> {
|
||||
return await this.client.request<proxyKeyView>("proxySvc", "createKey", dto, options)
|
||||
}
|
||||
async deleteKey(dto:idDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("proxySvc", "deleteKey", dto, options)
|
||||
}
|
||||
async list(dto:proxyListDto, options?: RequestOptions): Promise<result<proxyListView>> {
|
||||
return await this.client.request<proxyListView>("proxySvc", "list", dto, options)
|
||||
}
|
||||
async listKeys(options?: RequestOptions): Promise<result<proxyKeyView[]>> {
|
||||
return await this.client.request<proxyKeyView[]>("proxySvc", "listKeys", undefined, options)
|
||||
}
|
||||
async regions(options?: RequestOptions): Promise<result<proxyRegionView[]>> {
|
||||
return await this.client.request<proxyRegionView[]>("proxySvc", "regions", undefined, options)
|
||||
}
|
||||
async stats(options?: RequestOptions): Promise<result<proxyStatsView>> {
|
||||
return await this.client.request<proxyStatsView>("proxySvc", "stats", undefined, options)
|
||||
}
|
||||
async syncNow(options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("proxySvc", "syncNow", undefined, options)
|
||||
}
|
||||
async syncs(options?: RequestOptions): Promise<result<proxySyncView[]>> {
|
||||
return await this.client.request<proxySyncView[]>("proxySvc", "syncs", undefined, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export default interface proxySyncView {
|
||||
id:number
|
||||
startedAt:number
|
||||
finishedAt:number
|
||||
status:string
|
||||
source:string
|
||||
total:number
|
||||
added:number
|
||||
removed:number
|
||||
updated:number
|
||||
detailJson:string
|
||||
error:string
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export default interface proxyView {
|
||||
id:number
|
||||
ip:string
|
||||
port:number
|
||||
address:string
|
||||
protocols:string
|
||||
anonymity:string
|
||||
regionId:number
|
||||
regionCode:string
|
||||
regionName:string
|
||||
regionZhName:string
|
||||
latency:number
|
||||
speed:number
|
||||
uptime:number
|
||||
alive:boolean
|
||||
failCount:number
|
||||
lastCheckedAt:number
|
||||
lastAliveAt:number
|
||||
lastSeenAt:number
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface recentMemoryView {
|
||||
key:string
|
||||
project:string
|
||||
updatedAt:number
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default interface recentOrderView {
|
||||
tradeNo:string
|
||||
subject:string
|
||||
money:number
|
||||
paidAt:number
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default interface saveBookmarkDto {
|
||||
id?:number | null
|
||||
title:string
|
||||
url:string
|
||||
icon?:string | null
|
||||
sort?:number | null
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default interface saveDbconnDto {
|
||||
id?:number | null
|
||||
name:string
|
||||
engine:string
|
||||
host?:string | null
|
||||
port?:number | null
|
||||
username?:string | null
|
||||
password?:string | null
|
||||
database?:string | null
|
||||
params?:string | null
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export default interface saveServerDto {
|
||||
id?:number | null
|
||||
name:string
|
||||
host:string
|
||||
port?:number | null
|
||||
username:string
|
||||
authType:string
|
||||
secret?:string | null
|
||||
note?:string | null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default interface saveToolDto {
|
||||
id?:number | null
|
||||
name:string
|
||||
icon?:string | null
|
||||
url:string
|
||||
description?:string | null
|
||||
sort?:number | null
|
||||
enabled?:boolean | null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default interface saveUserDto {
|
||||
id?:number | null
|
||||
name:string
|
||||
password?:string | null
|
||||
role:string
|
||||
status?:string | null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default interface saveVaultDto {
|
||||
id?:number | null
|
||||
title:string
|
||||
username?:string | null
|
||||
password?:string | null
|
||||
url?:string | null
|
||||
note?:string | null
|
||||
totpSecret?:string | null
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface secretView {
|
||||
value:string
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface serverIdDto {
|
||||
id:number
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type diskMountView from "./diskMountView";
|
||||
export default interface serverMetricsView {
|
||||
hostname:string
|
||||
os:string
|
||||
kernel:string
|
||||
uptimeSeconds:number
|
||||
cpuPercent:number
|
||||
load1:string
|
||||
load5:string
|
||||
load15:string
|
||||
memTotal:number
|
||||
memUsed:number
|
||||
memAvail:number
|
||||
swapTotal:number
|
||||
swapUsed:number
|
||||
diskTotal:number
|
||||
diskUsed:number
|
||||
diskAvail:number
|
||||
netRxRate:number
|
||||
netTxRate:number
|
||||
netRxTotal:number
|
||||
netTxTotal:number
|
||||
disks:diskMountView[]
|
||||
collectedAt:number
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type okView from "./okView";
|
||||
import type saveServerDto from "./saveServerDto";
|
||||
import type serverIdDto from "./serverIdDto";
|
||||
import type serverMetricsView from "./serverMetricsView";
|
||||
import type serverView from "./serverView";
|
||||
import type sftpListView from "./sftpListView";
|
||||
import type sftpPathDto from "./sftpPathDto";
|
||||
import type sftpRenameDto from "./sftpRenameDto";
|
||||
|
||||
export default class serverSvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async delete(dto:serverIdDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("serverSvc", "delete", dto, options)
|
||||
}
|
||||
async list(options?: RequestOptions): Promise<result<serverView[]>> {
|
||||
return await this.client.request<serverView[]>("serverSvc", "list", undefined, options)
|
||||
}
|
||||
async metrics(dto:serverIdDto, options?: RequestOptions): Promise<result<serverMetricsView>> {
|
||||
return await this.client.request<serverMetricsView>("serverSvc", "metrics", dto, options)
|
||||
}
|
||||
async save(dto:saveServerDto, options?: RequestOptions): Promise<result<serverView>> {
|
||||
return await this.client.request<serverView>("serverSvc", "save", dto, options)
|
||||
}
|
||||
async sftpList(dto:sftpPathDto, options?: RequestOptions): Promise<result<sftpListView>> {
|
||||
return await this.client.request<sftpListView>("serverSvc", "sftpList", dto, options)
|
||||
}
|
||||
async sftpMkdir(dto:sftpPathDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("serverSvc", "sftpMkdir", dto, options)
|
||||
}
|
||||
async sftpRemove(dto:sftpPathDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("serverSvc", "sftpRemove", dto, options)
|
||||
}
|
||||
async sftpRename(dto:sftpRenameDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("serverSvc", "sftpRename", dto, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default interface serverView {
|
||||
id:number
|
||||
name:string
|
||||
host:string
|
||||
port:number
|
||||
username:string
|
||||
authType:string
|
||||
hasSecret:boolean
|
||||
note?:string | null
|
||||
createdAt:number
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type userView from "./userView";
|
||||
export default interface sessionView {
|
||||
user?:userView | null
|
||||
token?:string | null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface settingDto {
|
||||
key:string
|
||||
value:string
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Client, type result, type RequestOptions } from "./client";
|
||||
import type okView from "./okView";
|
||||
import type settingDto from "./settingDto";
|
||||
import type settingView from "./settingView";
|
||||
|
||||
export default class settingSvc {
|
||||
private client: Client;
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
async get(dto:settingDto, options?: RequestOptions): Promise<result<settingView>> {
|
||||
return await this.client.request<settingView>("settingSvc", "get", dto, options)
|
||||
}
|
||||
async set(dto:settingDto, options?: RequestOptions): Promise<result<okView>> {
|
||||
return await this.client.request<okView>("settingSvc", "set", dto, options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default interface settingView {
|
||||
value:string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export default interface sftpEntryView {
|
||||
name:string
|
||||
size:number
|
||||
mode:string
|
||||
isDir:boolean
|
||||
modTime:number
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type sftpEntryView from "./sftpEntryView";
|
||||
export default interface sftpListView {
|
||||
path:string
|
||||
entries:sftpEntryView[]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default interface sftpPathDto {
|
||||
id:number
|
||||
path:string
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface sftpRenameDto {
|
||||
id:number
|
||||
path:string
|
||||
newName:string
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export default interface shareView {
|
||||
id:number
|
||||
code:string
|
||||
fileName:string
|
||||
expiresAt:number
|
||||
expired:boolean
|
||||
downloads:number
|
||||
createdAt:number
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type payTrendPoint from "./payTrendPoint";
|
||||
import type recentMemoryView from "./recentMemoryView";
|
||||
import type recentOrderView from "./recentOrderView";
|
||||
import type trendPoint from "./trendPoint";
|
||||
export default interface statsView {
|
||||
totalUsers:number
|
||||
activeSessions:number
|
||||
todayLogins:number
|
||||
uptimeSeconds:number
|
||||
loginTrend:trendPoint[]
|
||||
servers:number
|
||||
proxies:number
|
||||
aliveProxies:number
|
||||
todayOrders:number
|
||||
todayMoney:number
|
||||
totalMoney:number
|
||||
pendingOrders:number
|
||||
licenseApps:number
|
||||
licenseCards:number
|
||||
licenseActive:number
|
||||
memories:number
|
||||
memoryProjects:number
|
||||
bookmarks:number
|
||||
vaultItems:number
|
||||
dbconns:number
|
||||
files:number
|
||||
payTrend:payTrendPoint[]
|
||||
recentOrders:recentOrderView[]
|
||||
recentMemories:recentMemoryView[]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user