新增 global_tools 表及 CRUD 命令,Dashboard 顶部增加独立的「快启」工具栏, 可配置与项目无关的日常应用。ProjectToolsModal 重构为通用组件,支持注入 自定义 commands / queryKey;ProjectCard 快启行始终显示并内联添加按钮。
1161 lines
36 KiB
TypeScript
1161 lines
36 KiB
TypeScript
import { invoke } from "@tauri-apps/api/core";
|
||
import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
|
||
|
||
export interface Project {
|
||
/** workspace.id(也是旧 projects.id) */
|
||
id: string;
|
||
/** profile.id,跨空间共享的项目档案 */
|
||
profile_id?: string;
|
||
/** 同 id,前端可用于语义区分 */
|
||
workspace_id?: string;
|
||
/** 关联的 git_repos.id */
|
||
repo_id?: string;
|
||
name: string;
|
||
description?: string;
|
||
type?: string;
|
||
// ── profile 级(项目档案,跨空间共享)────────────────────────
|
||
status?: string;
|
||
priority?: string;
|
||
tech_stack?: string;
|
||
tags?: string;
|
||
repo_url?: string;
|
||
upstream_url?: string;
|
||
default_branch?: string;
|
||
staging_url?: string;
|
||
prod_url?: string;
|
||
server_note?: string;
|
||
// ── workspace 级(每空间独立)───────────────────────────────
|
||
space_id?: string;
|
||
wsl_path?: string;
|
||
win_path?: string;
|
||
platform?: "windows" | "wsl";
|
||
recent_focus?: string;
|
||
startup_commands?: string;
|
||
last_push_at?: string;
|
||
updated_at?: string;
|
||
}
|
||
|
||
export interface ProjectDetail extends Project {
|
||
recent_activities: ActivityLog[];
|
||
}
|
||
|
||
export interface ActivityLog {
|
||
id: number;
|
||
project_id: string;
|
||
summary: string;
|
||
created_at?: string;
|
||
}
|
||
|
||
export interface ProductGroup {
|
||
id: string;
|
||
name: string;
|
||
description?: string;
|
||
space_id?: string;
|
||
sort_order: number;
|
||
}
|
||
|
||
export interface ProjectGroupMap {
|
||
project_id: string;
|
||
group_id: string;
|
||
role?: string;
|
||
}
|
||
|
||
export interface EditorCandidate {
|
||
name: string;
|
||
cmd: string;
|
||
path: string | null;
|
||
found: boolean;
|
||
}
|
||
|
||
// ── Projects ──────────────────────────────────────────────────────────────────
|
||
|
||
export const getProjects = (status?: string, spaceId?: string) =>
|
||
invoke<Project[]>("get_projects", { status, spaceId });
|
||
|
||
export const getProject = (id: string) =>
|
||
invoke<ProjectDetail>("get_project", { id });
|
||
|
||
export const createProject = (input: Omit<Project, "updated_at" | "workspace_id">) =>
|
||
invoke<void>("create_project", { input });
|
||
|
||
export const updateProject = (id: string, input: Partial<Omit<Project, "id" | "updated_at">>) =>
|
||
invoke<void>("update_project", { id, input });
|
||
|
||
export const updateProjectTags = (id: string, tags: string) =>
|
||
invoke<void>("update_project_tags", { id, tags });
|
||
|
||
export const deleteProject = (id: string, deleteLocal?: boolean) =>
|
||
invoke<void>("delete_project", { id, deleteLocal });
|
||
|
||
export interface SubFolder { name: string; path: string }
|
||
|
||
export const scanSubfolders = (dir: string) =>
|
||
invoke<SubFolder[]>("scan_subfolders", { dir });
|
||
|
||
export interface BatchProjectInput {
|
||
id: string; name: string;
|
||
win_path?: string; wsl_path?: string;
|
||
status: string; priority: string;
|
||
space_id?: string; platform?: string;
|
||
}
|
||
export const batchCreateProjects = (items: BatchProjectInput[]) =>
|
||
invoke<string[]>("batch_create_projects", { items });
|
||
|
||
// ── Activity ──────────────────────────────────────────────────────────────────
|
||
|
||
export const getActivity = (projectId: string, limit?: number) =>
|
||
invoke<ActivityLog[]>("get_activity", { projectId, limit });
|
||
|
||
export const addActivity = (projectId: string, summary: string) =>
|
||
invoke<void>("add_activity", { projectId, summary });
|
||
|
||
// ── Groups ────────────────────────────────────────────────────────────────────
|
||
|
||
export const getGroups = (spaceId?: string) => invoke<ProductGroup[]>("get_groups", { spaceId });
|
||
|
||
export const createGroup = (input: { id?: string; name: string; description?: string; space_id?: string }) =>
|
||
invoke<void>("create_group", { input });
|
||
|
||
export const updateGroup = (id: string, input: { name: string; description?: string }) =>
|
||
invoke<void>("update_group", { id, input });
|
||
|
||
export const deleteGroup = (id: string) => invoke<void>("delete_group", { id });
|
||
|
||
/** 按 ids 数组顺序批量写入 sort_order */
|
||
export const reorderGroups = (ids: string[]) => invoke<void>("reorder_groups", { ids });
|
||
|
||
export const getGroupMaps = () => invoke<ProjectGroupMap[]>("get_group_maps");
|
||
|
||
export const addGroupMember = (projectId: string, groupId: string, role?: string) =>
|
||
invoke<string | null>("add_group_member", { projectId, groupId, role });
|
||
|
||
export const removeGroupMember = (projectId: string, groupId: string) =>
|
||
invoke<void>("remove_group_member", { projectId, groupId });
|
||
|
||
// ── Settings ──────────────────────────────────────────────────────────────────
|
||
|
||
export const getSettings = (spaceId?: string) =>
|
||
invoke<Record<string, string>>("get_settings", { spaceId });
|
||
|
||
export const updateSettings = (settings: Record<string, string>, spaceId?: string) =>
|
||
invoke<void>("update_settings", { settings, spaceId });
|
||
|
||
export const detectEditors = () => invoke<EditorCandidate[]>("detect_editors");
|
||
|
||
export const searchEditorPath = (cmd: string) =>
|
||
invoke<string | null>("search_editor_path", { cmd });
|
||
|
||
export interface McpHealthResult {
|
||
ok: boolean;
|
||
port: number;
|
||
latency_ms?: number;
|
||
error?: string;
|
||
}
|
||
|
||
export const checkMcpHealth = () => invoke<McpHealthResult>("check_mcp_health");
|
||
|
||
export interface HealthItem {
|
||
id: string;
|
||
name: string;
|
||
status: "ok" | "warn" | "error" | "skip";
|
||
detail?: string;
|
||
}
|
||
|
||
export interface DormantProject {
|
||
id: string;
|
||
name: string;
|
||
days_since_push: number;
|
||
}
|
||
|
||
export interface OrphanedProject {
|
||
id: string;
|
||
name: string;
|
||
}
|
||
|
||
export interface QualityMetrics {
|
||
dormant_projects: DormantProject[];
|
||
orphaned_projects: OrphanedProject[];
|
||
groups_with_inject_warn: string[];
|
||
projects_without_path: number;
|
||
}
|
||
|
||
export interface HealthReport {
|
||
checked_at: string;
|
||
items: HealthItem[];
|
||
quality: QualityMetrics;
|
||
}
|
||
|
||
export const runHealthCheck = () => invoke<HealthReport>("run_health_check");
|
||
|
||
// ── Project Tools ─────────────────────────────────────────────────────────────
|
||
|
||
export interface ProjectTool {
|
||
id: string;
|
||
project_id: string;
|
||
name: string;
|
||
icon: string;
|
||
exe_path: string;
|
||
args: string | null;
|
||
sort_order: number;
|
||
}
|
||
|
||
export interface ProjectToolInput {
|
||
name: string;
|
||
icon: string;
|
||
exe_path: string;
|
||
args?: string;
|
||
sort_order?: number;
|
||
}
|
||
|
||
export const getProjectTools = (projectId: string) =>
|
||
invoke<ProjectTool[]>("get_project_tools", { projectId });
|
||
|
||
export const addProjectTool = (projectId: string, input: ProjectToolInput) =>
|
||
invoke<string>("add_project_tool", { projectId, input });
|
||
|
||
export const updateProjectTool = (id: string, input: ProjectToolInput) =>
|
||
invoke<void>("update_project_tool", { id, input });
|
||
|
||
export const removeProjectTool = (id: string) =>
|
||
invoke<void>("remove_project_tool", { id });
|
||
|
||
export const launchTool = (exePath: string, args?: string) =>
|
||
invoke<void>("launch_tool", { exePath, args });
|
||
|
||
// ── Global Quick Launch ───────────────────────────────────────────────────────
|
||
|
||
export const getGlobalTools = () =>
|
||
invoke<ProjectTool[]>("get_global_tools");
|
||
|
||
export const addGlobalTool = (input: ProjectToolInput) =>
|
||
invoke<string>("add_global_tool", { input });
|
||
|
||
export const updateGlobalTool = (id: string, input: ProjectToolInput) =>
|
||
invoke<void>("update_global_tool", { id, input });
|
||
|
||
export const removeGlobalTool = (id: string) =>
|
||
invoke<void>("remove_global_tool", { id });
|
||
|
||
// ── Dev Tools ─────────────────────────────────────────────────────────────────
|
||
|
||
export interface ScannedTool {
|
||
id: string;
|
||
name: string;
|
||
category: string;
|
||
cmd: string;
|
||
path: string | null;
|
||
version: string | null;
|
||
found: boolean;
|
||
}
|
||
|
||
export type DevEnv = "windows" | "wsl";
|
||
|
||
export interface DevToolRecord {
|
||
id: string;
|
||
name: string;
|
||
category: string;
|
||
cmd: string | null;
|
||
path: string | null;
|
||
version: string | null;
|
||
note: string | null;
|
||
env: DevEnv;
|
||
space_id?: string | null;
|
||
}
|
||
|
||
export interface SaveDevToolInput {
|
||
id: string;
|
||
name: string;
|
||
category: string;
|
||
cmd?: string;
|
||
path?: string;
|
||
version?: string;
|
||
note?: string;
|
||
env?: DevEnv;
|
||
space_id?: string;
|
||
}
|
||
|
||
export const scanDevTools = () =>
|
||
invoke<ScannedTool[]>("scan_dev_tools");
|
||
|
||
export const scanWslDevTools = () =>
|
||
invoke<ScannedTool[]>("scan_wsl_dev_tools");
|
||
|
||
export const getDevTools = (env: DevEnv, spaceId?: string) =>
|
||
invoke<DevToolRecord[]>("get_dev_tools", { env, spaceId });
|
||
|
||
export const saveDevTools = (tools: SaveDevToolInput[]) =>
|
||
invoke<void>("save_dev_tools", { tools });
|
||
|
||
export const deleteDevTool = (id: string) =>
|
||
invoke<void>("delete_dev_tool", { id });
|
||
|
||
export const upsertDevTool = (tool: SaveDevToolInput) =>
|
||
invoke<void>("upsert_dev_tool", { tool });
|
||
|
||
// ── Project Env Scan ──────────────────────────────────────────────────────────
|
||
|
||
export interface EnvToolItem {
|
||
id: string;
|
||
project_id: string;
|
||
layer: number;
|
||
name: string;
|
||
source?: string;
|
||
version?: string;
|
||
note?: string;
|
||
}
|
||
|
||
export interface SaveEnvToolInput {
|
||
id: string;
|
||
layer: number;
|
||
name: string;
|
||
source?: string;
|
||
version?: string;
|
||
note?: string;
|
||
}
|
||
|
||
export const scanProjectEnv = (projectId: string, env: 'win' | 'wsl') =>
|
||
invoke<EnvToolItem[]>('scan_project_env', { projectId, env });
|
||
|
||
export const getProjectEnvTools = (projectId: string) =>
|
||
invoke<EnvToolItem[]>('get_project_env_tools', { projectId });
|
||
|
||
export const saveProjectEnvTools = (projectId: string, items: SaveEnvToolInput[]) =>
|
||
invoke<void>('save_project_env_tools', { projectId, items });
|
||
|
||
// ── Shell ─────────────────────────────────────────────────────────────────────
|
||
|
||
export const openEditor = (projectId: string, env: "wsl" | "win", editorPath: string) =>
|
||
invoke<string>("open_editor", { projectId, env, editorPath });
|
||
|
||
export const openTerminal = (projectId: string, env: "wsl" | "win") =>
|
||
invoke<string>("open_terminal", { projectId, env });
|
||
|
||
// ── GitHub 账户(DB 操作,Rust 侧)────────────────────────────────────────────
|
||
|
||
export interface GitAccount {
|
||
id: string;
|
||
username: string;
|
||
avatar_url?: string;
|
||
provider: string;
|
||
created_at?: string;
|
||
}
|
||
|
||
export const githubGetAccount = () =>
|
||
invoke<GitAccount | null>("github_get_account");
|
||
|
||
export const githubGetToken = () =>
|
||
invoke<string | null>("github_get_token");
|
||
|
||
export const githubGetClientId = () =>
|
||
invoke<string>("github_get_client_id");
|
||
|
||
export const githubSaveAccount = (
|
||
username: string,
|
||
avatarUrl: string,
|
||
accessToken: string
|
||
) => invoke<GitAccount>("github_save_account", { username, avatarUrl, accessToken });
|
||
|
||
export const githubLogout = () =>
|
||
invoke<void>("github_logout");
|
||
|
||
// ── GitHub API(前端 fetch)───────────────────────────────────────────────────
|
||
|
||
export interface GithubRepo {
|
||
id: number;
|
||
name: string;
|
||
full_name: string;
|
||
private: boolean;
|
||
clone_url: string;
|
||
ssh_url: string;
|
||
description?: string;
|
||
default_branch: string;
|
||
}
|
||
|
||
/** 打开应用内 GitHub 登录窗口(用户在窗口内完成授权,无需外部浏览器) */
|
||
export const githubOpenLoginWindow = () => invoke<void>("github_open_login_window");
|
||
|
||
/** 轮询授权结果(null = 等待中,string = token) */
|
||
export const githubPollOAuth = () => invoke<string | null>("github_poll_oauth");
|
||
|
||
/** 启动本地回调服务器,返回授权 URL(备用:外部浏览器方式) */
|
||
export const githubStartOAuth = () => invoke<string>("github_start_oauth");
|
||
|
||
/** 用 token 获取 GitHub 用户信息 */
|
||
export async function githubFetchUser(token: string) {
|
||
const resp = await tauriFetch("https://api.github.com/user", {
|
||
headers: { Authorization: `Bearer ${token}`, "User-Agent": "dev-manager-tauri" },
|
||
});
|
||
if (!resp.ok) throw new Error("获取用户信息失败");
|
||
return resp.json() as Promise<{ login: string; avatar_url: string }>;
|
||
}
|
||
|
||
/** 获取用户的 GitHub 仓库列表 */
|
||
export async function githubListRepos(token: string): Promise<GithubRepo[]> {
|
||
const resp = await tauriFetch(
|
||
"https://api.github.com/user/repos?per_page=100&sort=updated&affiliation=owner",
|
||
{ headers: { Authorization: `Bearer ${token}`, "User-Agent": "dev-manager-tauri" } }
|
||
);
|
||
if (!resp.ok) throw new Error("获取仓库列表失败");
|
||
return resp.json();
|
||
}
|
||
|
||
// ── Spaces ────────────────────────────────────────────────────────────────────
|
||
|
||
export interface Space {
|
||
id: string;
|
||
name: string;
|
||
is_current: boolean;
|
||
created_at?: string;
|
||
}
|
||
|
||
export interface DeployCommand {
|
||
id: string;
|
||
project_id: string;
|
||
name: string;
|
||
command: string;
|
||
run_in: "windows" | "wsl";
|
||
}
|
||
|
||
export const getSpaces = () =>
|
||
invoke<Space[]>("get_spaces");
|
||
|
||
export const createSpace = (name: string) =>
|
||
invoke<Space>("create_space", { name });
|
||
|
||
export const updateSpace = (id: string, name: string) =>
|
||
invoke<void>("update_space", { id, name });
|
||
|
||
export const deleteSpace = (id: string) =>
|
||
invoke<void>("delete_space", { id });
|
||
|
||
export const setCurrentSpace = (id: string) =>
|
||
invoke<void>("set_current_space", { id });
|
||
|
||
export const getDeployCommands = (projectId: string) =>
|
||
invoke<DeployCommand[]>("get_deploy_commands", { projectId });
|
||
|
||
export const addDeployCommand = (
|
||
projectId: string,
|
||
name: string,
|
||
command: string,
|
||
runIn: "windows" | "wsl"
|
||
) => invoke<DeployCommand>("add_deploy_command", { projectId, name, command, runIn });
|
||
|
||
export const removeDeployCommand = (id: string) =>
|
||
invoke<void>("remove_deploy_command", { id });
|
||
|
||
export const executeDeploy = (command: string, runIn: "windows" | "wsl", cwd?: string) =>
|
||
invoke<string>("execute_deploy", { command, runIn, cwd });
|
||
|
||
// ── Git 操作 ──────────────────────────────────────────────────────────────────
|
||
|
||
export interface GitStatus {
|
||
branch: string;
|
||
ahead: number;
|
||
behind: number;
|
||
has_changes: boolean;
|
||
last_commit_msg?: string;
|
||
last_commit_time?: string;
|
||
}
|
||
|
||
export const gitClone = (repoUrl: string, localPath: string, upstreamUrl?: string) =>
|
||
invoke<void>("git_clone", { repoUrl, localPath, upstreamUrl });
|
||
|
||
export const gitStatus = (localPath: string) =>
|
||
invoke<GitStatus>("git_status", { localPath });
|
||
|
||
export const gitFetch = (localPath: string) =>
|
||
invoke<void>("git_fetch", { localPath });
|
||
|
||
export const gitPull = (localPath: string) =>
|
||
invoke<string>("git_pull", { localPath });
|
||
|
||
export const gitPush = (localPath: string) =>
|
||
invoke<string>("git_push", { localPath });
|
||
|
||
export const gitHeadCommit = (localPath: string) =>
|
||
invoke<string | null>("git_head_commit", { localPath });
|
||
|
||
export const gitUpstreamBehind = (localPath: string, defaultBranch: string) =>
|
||
invoke<number>("git_upstream_behind", { localPath, defaultBranch });
|
||
|
||
export const gitCreateBranch = (localPath: string, branchName: string) =>
|
||
invoke<void>("git_create_branch", { localPath, branchName });
|
||
|
||
export const gitListBranches = (localPath: string) =>
|
||
invoke<string[]>("git_list_branches", { localPath });
|
||
|
||
export const gitCheckoutBranch = (localPath: string, branchName: string) =>
|
||
invoke<void>("git_checkout_branch", { localPath, branchName });
|
||
|
||
// ── WSL 代理同步 ──────────────────────────────────────────────────────────────
|
||
|
||
export const getWslIp = () => invoke<string>("get_wsl_ip");
|
||
|
||
/** 读取 v2rayN 代理端口,写入 WSL /etc/profile.d/v2rayn-proxy.sh */
|
||
export const syncWslProxy = (dbPath?: string) =>
|
||
invoke<string>("sync_wsl_proxy", { dbPath });
|
||
|
||
export interface WslProxyStatus {
|
||
configured: boolean;
|
||
port?: number;
|
||
/** /etc/environment 中记录的 IP 与当前网关不一致(Windows 重启后常见) */
|
||
stale?: boolean;
|
||
configured_ip?: string;
|
||
current_ip?: string;
|
||
/** /etc/bash.bashrc 中的 source 行是否存在(非登录 shell 代理是否生效) */
|
||
bashrc_ok?: boolean;
|
||
}
|
||
export const getWslProxyStatus = () => invoke<WslProxyStatus>("get_wsl_proxy_status");
|
||
export const clearWslProxy = () => invoke<void>("clear_wsl_proxy");
|
||
export const testWslProxyIp = () => invoke<string>("test_wsl_proxy_ip");
|
||
export const testWinProxyIp = () => invoke<string>("test_win_proxy_ip");
|
||
|
||
export interface ActiveV2raynNode {
|
||
index_id: string;
|
||
remarks: string;
|
||
address: string;
|
||
port: number;
|
||
config_type: number;
|
||
share_link: string;
|
||
}
|
||
export const getActiveV2raynNode = (dbPath?: string) =>
|
||
invoke<ActiveV2raynNode>("get_active_v2rayn_node", { dbPath });
|
||
|
||
// ── v2rayN 节点导入 ───────────────────────────────────────────────────────────
|
||
|
||
export interface V2raynNode {
|
||
index_id: string;
|
||
config_type: number; // 1=VMess 3=SS 5=VLESS 6=Trojan 7=Hysteria2
|
||
remarks: string;
|
||
address: string;
|
||
port: number;
|
||
id?: string; // UUID
|
||
security?: string;
|
||
network?: string;
|
||
stream_security?: string;
|
||
flow?: string;
|
||
sni?: string;
|
||
fingerprint?: string;
|
||
public_key?: string;
|
||
short_id?: string;
|
||
spider_x?: string;
|
||
request_host?: string;
|
||
path?: string;
|
||
alter_id?: number;
|
||
}
|
||
|
||
export const CONFIG_TYPE_LABEL: Record<number, string> = {
|
||
1: "VMess",
|
||
3: "Shadowsocks",
|
||
4: "Socks",
|
||
5: "VLESS",
|
||
6: "Trojan",
|
||
7: "Hysteria2",
|
||
};
|
||
|
||
export const scanV2raynNodes = (dbPath?: string) =>
|
||
invoke<V2raynNode[]>("scan_v2rayn_nodes", { dbPath });
|
||
|
||
export const detectV2raynDbPath = () =>
|
||
invoke<string | null>("detect_v2rayn_db_path");
|
||
|
||
// ── Git Repos Registry(仓库注册表)──────────────────────────────────────────
|
||
|
||
export interface GitRepo {
|
||
id: string;
|
||
repo_url: string;
|
||
name: string;
|
||
description?: string;
|
||
default_branch?: string;
|
||
upstream_url?: string;
|
||
staging_url?: string;
|
||
prod_url?: string;
|
||
server_note?: string;
|
||
status?: string;
|
||
priority?: string;
|
||
tech_stack?: string;
|
||
last_synced_at?: string;
|
||
created_at?: string;
|
||
}
|
||
|
||
export interface ImportGitRepoInput {
|
||
id: string;
|
||
repo_url: string;
|
||
name: string;
|
||
description?: string;
|
||
default_branch?: string;
|
||
upstream_url?: string;
|
||
tech_stack?: string;
|
||
}
|
||
|
||
export interface UpdateGitRepoDeployInput {
|
||
staging_url?: string;
|
||
prod_url?: string;
|
||
server_note?: string;
|
||
upstream_url?: string;
|
||
tech_stack?: string;
|
||
status?: string;
|
||
priority?: string;
|
||
}
|
||
|
||
export interface MountRepoInput {
|
||
workspace_id: string;
|
||
repo_id: string;
|
||
space_id?: string;
|
||
win_path?: string;
|
||
wsl_path?: string;
|
||
platform?: string;
|
||
}
|
||
|
||
export const getGitRepos = () =>
|
||
invoke<GitRepo[]>("get_git_repos");
|
||
|
||
export const importGitRepos = (repos: ImportGitRepoInput[]) =>
|
||
invoke<string[]>("import_git_repos", { repos });
|
||
|
||
export const updateGitRepoDeploy = (id: string, input: UpdateGitRepoDeployInput) =>
|
||
invoke<void>("update_git_repo_deploy", { id, input });
|
||
|
||
export const mountRepo = (input: MountRepoInput) =>
|
||
invoke<void>("mount_repo", { input });
|
||
|
||
export const deleteGitRepo = (id: string) =>
|
||
invoke<void>("delete_git_repo", { id });
|
||
|
||
// ── Publish to GitHub ─────────────────────────────────────────────────────────
|
||
|
||
export interface ChangedFile {
|
||
status: string; // "M" | "A" | "D" | "?" | "R"
|
||
path: string;
|
||
}
|
||
|
||
export interface GitRemoteInfo {
|
||
name: string;
|
||
url: string;
|
||
}
|
||
|
||
export interface GitScanResult {
|
||
has_git: boolean;
|
||
has_commits: boolean;
|
||
branch: string;
|
||
has_changes: boolean;
|
||
changed_files: ChangedFile[];
|
||
remotes: GitRemoteInfo[];
|
||
has_gitignore: boolean;
|
||
suspicious_files: string[];
|
||
suggested_templates: string[];
|
||
}
|
||
|
||
export const scanLocalGit = (path: string) =>
|
||
invoke<GitScanResult>("scan_local_git", { path });
|
||
|
||
export const githubCreateRepo = (name: string, description: string, privateRepo: boolean) =>
|
||
invoke<string>("github_create_repo", { name, description, private: privateRepo });
|
||
|
||
export const gitInitAndCommit = (path: string, message: string) =>
|
||
invoke<void>("git_init_and_commit", { path, message });
|
||
|
||
export const gitRemoteSet = (path: string, url: string) =>
|
||
invoke<void>("git_remote_set", { path, url });
|
||
|
||
export const gitPushToGithub = (path: string, branch: string) =>
|
||
invoke<void>("git_push_to_github", { path, branch });
|
||
|
||
/** 将 Linux WSL 路径转换为 Windows UNC 路径(\\wsl.localhost\distro\...) */
|
||
export const resolveWslPath = (path: string) =>
|
||
invoke<string>("resolve_wsl_path", { path });
|
||
|
||
/** 列出已安装的 WSL 发行版 */
|
||
export const getWslDistros = () =>
|
||
invoke<string[]>("get_wsl_distros");
|
||
|
||
// ── 项目模板 ──────────────────────────────────────────────────────────────────
|
||
|
||
export interface TemplateFile {
|
||
id: string;
|
||
template_id: string;
|
||
path: string;
|
||
content: string;
|
||
}
|
||
|
||
export interface ProjectTemplate {
|
||
id: string;
|
||
name: string;
|
||
description?: string;
|
||
platform: string; // "win" | "wsl" | "both"
|
||
techStack?: string;
|
||
initCommands?: string;
|
||
isBuiltin: boolean;
|
||
createdAt?: string;
|
||
files: TemplateFile[];
|
||
}
|
||
|
||
export interface SaveTemplateInput {
|
||
id?: string;
|
||
name: string;
|
||
description?: string;
|
||
platform: string;
|
||
techStack?: string;
|
||
initCommands?: string;
|
||
files: { path: string; content: string }[];
|
||
}
|
||
|
||
export interface CreateProjectResult {
|
||
projectId: string;
|
||
logs: string[];
|
||
}
|
||
|
||
export const listTemplates = () =>
|
||
invoke<ProjectTemplate[]>("list_templates");
|
||
|
||
export const getTemplate = (id: string) =>
|
||
invoke<ProjectTemplate>("get_template", { id });
|
||
|
||
export const saveTemplate = (input: SaveTemplateInput) =>
|
||
invoke<string>("save_template", { input });
|
||
|
||
export const deleteTemplate = (id: string) =>
|
||
invoke<void>("delete_template", { id });
|
||
|
||
export const createProjectFromTemplate = (
|
||
templateId: string,
|
||
parentPath: string,
|
||
projectName: string,
|
||
platform: string,
|
||
spaceId?: string,
|
||
) =>
|
||
invoke<CreateProjectResult>("create_project_from_template", {
|
||
templateId,
|
||
parentPath,
|
||
projectName,
|
||
platform,
|
||
spaceId,
|
||
});
|
||
|
||
export const registerAndLinkRepo = (
|
||
projectId: string,
|
||
repoUrl: string,
|
||
name: string,
|
||
description?: string,
|
||
defaultBranch?: string,
|
||
) => invoke<void>("register_and_link_repo", { projectId, repoUrl, name, description, defaultBranch });
|
||
|
||
// ── 项目标签 ──────────────────────────────────────────────────────────────────
|
||
|
||
export interface ProjectTag {
|
||
id: string;
|
||
name: string;
|
||
color: string;
|
||
}
|
||
|
||
export interface ProfileTagMap {
|
||
profile_id: string;
|
||
tag_id: string;
|
||
}
|
||
|
||
export const getTags = () => invoke<ProjectTag[]>("get_tags");
|
||
export const createTag = (name: string, color: string) => invoke<string>("create_tag", { name, color });
|
||
export const updateTag = (id: string, name: string, color: string) => invoke<void>("update_tag", { id, name, color });
|
||
export const deleteTag = (id: string) => invoke<void>("delete_tag", { id });
|
||
export const getProjectTags = (workspaceId: string) => invoke<ProjectTag[]>("get_project_tags", { workspaceId });
|
||
export const setProjectTags = (workspaceId: string, tagIds: string[]) => invoke<void>("set_project_tags", { workspaceId, tagIds });
|
||
export const getAllProjectTagMaps = () => invoke<ProfileTagMap[]>("get_all_project_tag_maps");
|
||
|
||
// ── CI/CD 配置 ────────────────────────────────────────────────────────────────
|
||
|
||
export interface CicdConfig {
|
||
id: string;
|
||
project_id: string;
|
||
/** 'web_ssh' | 'tauri_oss' */
|
||
config_type: string;
|
||
trigger_branch: string;
|
||
build_command?: string;
|
||
node_version?: string;
|
||
// web_ssh 专属
|
||
server_host?: string;
|
||
server_user?: string;
|
||
deploy_path?: string;
|
||
start_command?: string;
|
||
// tauri_oss 专属
|
||
oss_endpoint?: string;
|
||
created_at?: string;
|
||
}
|
||
|
||
export interface WorkflowResult {
|
||
content: string;
|
||
path: string;
|
||
secrets: string[];
|
||
}
|
||
|
||
export const getCicdConfig = (projectId: string) =>
|
||
invoke<CicdConfig | null>("get_cicd_config", { projectId });
|
||
|
||
export const saveCicdConfig = (input: CicdConfig) =>
|
||
invoke<CicdConfig>("save_cicd_config", { input });
|
||
|
||
/** 生成 workflow 文件写入磁盘,projectPath 必须是 Windows 可访问路径 */
|
||
export const generateWorkflow = (projectId: string, projectPath: string) =>
|
||
invoke<WorkflowResult>("generate_workflow", { projectId, projectPath });
|
||
|
||
// ── GitHub Actions API(前端直接调用,使用已存储的 token)────────────────────
|
||
|
||
export interface ActionsRun {
|
||
id: number;
|
||
name: string;
|
||
status: string; // "queued" | "in_progress" | "completed"
|
||
conclusion: string | null; // "success" | "failure" | "cancelled" | null
|
||
head_branch: string;
|
||
created_at: string;
|
||
updated_at: string;
|
||
html_url: string;
|
||
run_number: number;
|
||
}
|
||
|
||
/** 解析 repo_url 为 {owner}/{repo} 格式,失败返回 null */
|
||
export function parseRepoPath(repoUrl: string): string | null {
|
||
const m = repoUrl.match(/github\.com[/:](.+?)(?:\.git)?$/);
|
||
return m ? m[1] : null;
|
||
}
|
||
|
||
/** 获取最近 N 次 Actions run */
|
||
export async function fetchActionsRuns(
|
||
token: string,
|
||
repoPath: string,
|
||
perPage = 5
|
||
): Promise<ActionsRun[]> {
|
||
const res = await fetch(
|
||
`https://api.github.com/repos/${repoPath}/actions/runs?per_page=${perPage}`,
|
||
{ headers: { Authorization: `Bearer ${token}`, "User-Agent": "dev-manager-tauri" } }
|
||
);
|
||
if (!res.ok) throw new Error(`GitHub API ${res.status}`);
|
||
const data = await res.json();
|
||
return data.workflow_runs ?? [];
|
||
}
|
||
|
||
/** 手动触发 workflow_dispatch */
|
||
export async function triggerWorkflow(
|
||
token: string,
|
||
repoPath: string,
|
||
workflowFile: string,
|
||
branch: string
|
||
): Promise<void> {
|
||
const res = await fetch(
|
||
`https://api.github.com/repos/${repoPath}/actions/workflows/${workflowFile}/dispatches`,
|
||
{
|
||
method: "POST",
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"User-Agent": "dev-manager-tauri",
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({ ref: branch }),
|
||
}
|
||
);
|
||
if (!res.ok && res.status !== 204) throw new Error(`触发失败: ${res.status}`);
|
||
}
|
||
|
||
/** 根据节点数据构造 share link(前端完成,避免 Rust 侧 URL 编码依赖) */
|
||
export function buildShareLink(node: V2raynNode): string {
|
||
const remarks = encodeURIComponent(node.remarks);
|
||
|
||
if (node.config_type === 5) {
|
||
// VLESS
|
||
const uuid = node.id ?? "";
|
||
const params = new URLSearchParams();
|
||
params.set("encryption", "none");
|
||
params.set("security", node.stream_security ?? "none");
|
||
if (node.network) params.set("type", node.network);
|
||
if (node.stream_security === "reality" || node.stream_security === "tls") {
|
||
if (node.sni) params.set("sni", node.sni);
|
||
if (node.fingerprint) params.set("fp", node.fingerprint);
|
||
}
|
||
if (node.stream_security === "reality") {
|
||
if (node.public_key) params.set("pbk", node.public_key);
|
||
if (node.short_id) params.set("sid", node.short_id);
|
||
if (node.spider_x) params.set("spx", node.spider_x);
|
||
}
|
||
if (node.flow) params.set("flow", node.flow);
|
||
if (node.request_host) params.set("host", node.request_host);
|
||
if (node.path) params.set("path", node.path);
|
||
return `vless://${uuid}@${node.address}:${node.port}?${params.toString()}#${remarks}`;
|
||
}
|
||
|
||
if (node.config_type === 6) {
|
||
// Trojan
|
||
const params = new URLSearchParams();
|
||
params.set("security", node.stream_security ?? "tls");
|
||
if (node.sni) params.set("sni", node.sni);
|
||
if (node.fingerprint) params.set("fp", node.fingerprint);
|
||
if (node.network) params.set("type", node.network);
|
||
return `trojan://${node.id ?? ""}@${node.address}:${node.port}?${params.toString()}#${remarks}`;
|
||
}
|
||
|
||
if (node.config_type === 1) {
|
||
// VMess — base64 JSON 格式
|
||
const obj = {
|
||
v: "2", ps: node.remarks, add: node.address, port: String(node.port),
|
||
id: node.id ?? "", aid: String(node.alter_id ?? 0),
|
||
scy: node.security ?? "auto", net: node.network ?? "tcp",
|
||
type: "none", host: node.request_host ?? "", path: node.path ?? "",
|
||
tls: node.stream_security ?? "", sni: node.sni ?? "", fp: node.fingerprint ?? "",
|
||
};
|
||
return `vmess://${btoa(JSON.stringify(obj))}`;
|
||
}
|
||
|
||
return "";
|
||
}
|
||
|
||
// ── Blueprint ──────────────────────────────────────────────────────────────
|
||
|
||
export interface BlueprintTask {
|
||
title: string;
|
||
status: string;
|
||
complexity?: string;
|
||
files?: string;
|
||
depends?: string;
|
||
acceptance?: string;
|
||
notes?: string;
|
||
blocked_reason?: string;
|
||
locked?: boolean;
|
||
}
|
||
|
||
export interface BlueprintModule {
|
||
id: string;
|
||
name: string;
|
||
area: string;
|
||
status: string;
|
||
progress: number;
|
||
position: number[];
|
||
description?: string;
|
||
decisions: string[];
|
||
tasks: BlueprintTask[];
|
||
}
|
||
|
||
export interface BlueprintEdge {
|
||
from: string;
|
||
to: string;
|
||
edge_type: string;
|
||
}
|
||
|
||
export interface BlueprintArea {
|
||
id: string;
|
||
name: string;
|
||
color?: string;
|
||
}
|
||
|
||
export interface BlueprintStats {
|
||
total_modules: number;
|
||
done: number;
|
||
in_progress: number;
|
||
planned: number;
|
||
concept: number;
|
||
blocked: number;
|
||
total_tasks: number;
|
||
tasks_done: number;
|
||
tasks_blocked: number;
|
||
tasks_locked: number;
|
||
dispatchable: number;
|
||
}
|
||
|
||
export interface BlueprintData {
|
||
manifest: {
|
||
version: number;
|
||
name: string;
|
||
description?: string;
|
||
updated?: string;
|
||
/** 当前迭代号,默认 1,重构已完成模块时递增 */
|
||
iteration?: number;
|
||
areas: BlueprintArea[];
|
||
modules: BlueprintModule[];
|
||
edges: BlueprintEdge[];
|
||
};
|
||
stats: BlueprintStats;
|
||
}
|
||
|
||
export const getBlueprint = (projectPath: string) =>
|
||
invoke<BlueprintData | null>("get_blueprint", { projectPath });
|
||
|
||
export const generateBlueprintPrompt = (projectPath: string, promptType: "init" | "sync") =>
|
||
invoke<string>("generate_blueprint_prompt", { projectPath, promptType });
|
||
|
||
export interface BlueprintStatus {
|
||
status: "none" | "rules_outdated" | "content_stale" | "synced";
|
||
conventions_version?: string;
|
||
claude_md_managed: boolean;
|
||
claude_md_version?: string;
|
||
manifest_updated?: string;
|
||
master_version: string;
|
||
}
|
||
|
||
export interface SyncResult {
|
||
success: boolean;
|
||
conventions_written: boolean;
|
||
claude_md_action: string;
|
||
message: string;
|
||
}
|
||
|
||
export const getBlueprintStatus = (projectPath: string) =>
|
||
invoke<BlueprintStatus>("get_blueprint_status", { projectPath });
|
||
|
||
export const syncBlueprintRules = (projectPath: string) =>
|
||
invoke<SyncResult>("sync_blueprint_rules", { projectPath });
|
||
|
||
export interface FlywheelCohort {
|
||
conventions_version: string;
|
||
project_count: number;
|
||
/** 可派发率:dispatched(可派发数) / total tasks(最新快照均值) */
|
||
dispatchable_rate: number;
|
||
/** 任务完成率:done / total tasks(最新快照均值,反映整体进度) */
|
||
completion_rate: number;
|
||
/** 飞轮持续率:30天内有快照的项目比例 */
|
||
continuity_rate: number;
|
||
/** 阻塞密度:blocked / total tasks(最新快照均值) */
|
||
blocked_density: number;
|
||
}
|
||
|
||
export interface BlockedReasonCount {
|
||
reason: string;
|
||
count: number;
|
||
}
|
||
|
||
export interface StalledProject {
|
||
name: string;
|
||
/** UTC 日期字符串 "YYYY-MM-DD",未知时为 "unknown" */
|
||
last_active: string;
|
||
}
|
||
|
||
export interface IterationJump {
|
||
project: string;
|
||
from_iteration: number;
|
||
to_iteration: number;
|
||
/** 跳变前最后一条快照的任务完成率 */
|
||
rate_before: number;
|
||
/** 跳变后第一条快照的任务完成率 */
|
||
rate_after: number;
|
||
}
|
||
|
||
export interface FlywheelStats {
|
||
cohorts: FlywheelCohort[];
|
||
top_blocked_reasons: BlockedReasonCount[];
|
||
/** 飞轮停转项目(60天无快照) */
|
||
stalled_projects: StalledProject[];
|
||
/** AI文档休眠项目(90天无快照) */
|
||
ai_doc_stale_projects: StalledProject[];
|
||
/** 迭代跳变记录(iteration 递增,说明发生了主动重构) */
|
||
iteration_jumps: IterationJump[];
|
||
/** 停滞模块(in_progress 但无可推进任务,格式 "project/module_id") */
|
||
stalled_modules: string[];
|
||
total_projects_scanned: number;
|
||
projects_with_data: number;
|
||
}
|
||
|
||
export const getFlywheelStats = (projectPaths: string[]) =>
|
||
invoke<FlywheelStats>("get_flywheel_stats", { projectPaths });
|
||
|
||
export const generateMuhePrompt = (stats: FlywheelStats) =>
|
||
invoke<string>("generate_muhe_prompt", { stats });
|
||
|
||
export interface ArchiveResult {
|
||
archived_files: string[];
|
||
archive_dir: string;
|
||
}
|
||
|
||
export const archiveProjectDocs = (projectPath: string) =>
|
||
invoke<ArchiveResult>("archive_project_docs", { projectPath });
|
||
|
||
// ── Canvas State ───────────────────────────────────────────────────────────────
|
||
export const getCanvasState = (groupId: string) =>
|
||
invoke<string | null>("get_canvas_state", { groupId });
|
||
|
||
export const saveCanvasState = (groupId: string, data: string) =>
|
||
invoke<void>("save_canvas_state", { groupId, data });
|
||
|
||
// ── Mentor ────────────────────────────────────────────────────────────────────
|
||
|
||
export interface ProjectHealth {
|
||
path_valid: boolean;
|
||
has_git: boolean;
|
||
last_commit?: string;
|
||
tech_stack?: string;
|
||
}
|
||
|
||
export interface BlueprintSummary {
|
||
module_count: number;
|
||
done_count: number;
|
||
in_progress: number;
|
||
planned: number;
|
||
total_tasks: number;
|
||
done_tasks: number;
|
||
dispatchable: number;
|
||
}
|
||
|
||
export interface RuntimeLogEntry {
|
||
ts: string;
|
||
category: string;
|
||
level: string;
|
||
message: string;
|
||
}
|
||
|
||
export interface MentorContext {
|
||
project_id: string;
|
||
project_name: string;
|
||
health: ProjectHealth;
|
||
blueprint_summary?: BlueprintSummary;
|
||
recent_errors: RuntimeLogEntry[];
|
||
mentor_markdown: string;
|
||
}
|
||
|
||
export const getMentorContext = (projectId: string) =>
|
||
invoke<MentorContext>("get_mentor_context", { projectId });
|
||
|
||
export const getGroupMentorReport = (groupId: string) =>
|
||
invoke<string>("get_group_mentor_report", { groupId });
|
||
|
||
export interface StartupAlert {
|
||
project_name: string;
|
||
alert_type: string;
|
||
detail: string;
|
||
}
|
||
|
||
export const getStartupAlerts = () =>
|
||
invoke<StartupAlert[]>("get_startup_alerts");
|
||
|
||
export interface ProjectNote {
|
||
id: number;
|
||
project_id: string;
|
||
source: string;
|
||
content: string;
|
||
created_at: string;
|
||
}
|
||
|
||
export const getProjectNotes = (projectId: string) =>
|
||
invoke<ProjectNote[]>("get_project_notes", { projectId });
|
||
|
||
export const addProjectNote = (projectId: string, content: string, source: string = "manual") =>
|
||
invoke<void>("add_project_note", { projectId, content, source });
|
||
|
||
// ── Blueprint Buff ────────────────────────────────────────────────────────────
|
||
|
||
export interface BuffStatus {
|
||
project_path: string;
|
||
project_id: string;
|
||
applied_at: string;
|
||
last_sync_at?: string;
|
||
last_commit?: string;
|
||
status: string;
|
||
}
|
||
|
||
export const applyBlueprintBuff = (projectPath: string, projectId: string) =>
|
||
invoke<BuffStatus>("apply_blueprint_buff", { projectPath, projectId });
|
||
|
||
export const removeBlueprintBuff = (projectPath: string) =>
|
||
invoke<void>("remove_blueprint_buff", { projectPath });
|
||
|
||
export const getBuffStatus = (projectPath: string) =>
|
||
invoke<BuffStatus | null>("get_buff_status", { projectPath });
|
||
|
||
export const listBuffedProjects = () =>
|
||
invoke<BuffStatus[]>("list_buffed_projects");
|
||
|