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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# linkwork-web

English | [中文](./README_zh-CN.md)

`linkwork-web` is the LinkWork management frontend, built with Vue 3 + Vite + TypeScript. It covers task operations, role configuration, approvals, and MCP/Skills related pages.

## Local Development

### 1) Requirements

- Node.js 22+
- npm 10+

### 2) Install dependencies

```bash
cd linkwork-web
npm install
```

### 3) Run dev server

```bash
npm run dev
```

By default, Vite proxies `/api` to `http://localhost:8081`. You can override it with `VITE_DEV_API_PROXY_TARGET`.

### 4) Production build and preview

```bash
npm run build
npm run preview
```

## Key Environment Variables

| Variable | Description |
|---|---|
| `VITE_DEV_API_PROXY_TARGET` | API proxy target in local development |
| `VITE_SSO_LOGIN_URL` | Redirect URL when unauthenticated (default: `/login`) |
| `VITE_BYPASS_AUTH_GUARD` | Bypass frontend auth guard (`false` to disable bypass) |
| `VITE_WATERMARK_PRODUCT` | UI watermark product name |
| `VITE_WATERMARK_OWNER` | UI watermark owner |
| `VITE_MODEL_ALL_ACCESS_WHITELIST` | CSV whitelist for full model access |
| `VITE_MODEL_DEFAULT_ALLOW_LIST` | CSV default model allow-list |

## Deploy Flow

### Option A: Remote deploy via workspace script (recommended)

Run from workspace root (`link-work`):

```bash
GG_HOST=<remote_host> \
REMOTE_USER=<remote_user> \
BRANCH=<target_branch> \
PORT=3000 \
```

The script performs:

1. SSH to remote and validate/clone `linkwork-web`
2. Checkout target branch and run `npm install && npm run build`
3. Generate Nginx config with `/api/` proxy to backend
4. Run `nginx:1.27-alpine` container
5. Check `/` and `/home` (SPA fallback)

### Option B: Manual container deployment

```bash
cd linkwork-web
npm install
npm run build

docker run -d --name linkwork-web -p 3000:80 \
-v $(pwd)/dist:/usr/share/nginx/html:ro \
-v $(pwd)/nginx.default.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:1.27-alpine
```

## Dependencies

- Backend APIs are usually provided by `LinkWork/back`
- Runtime auth path depends on browser cookie/JWT flow
86 changes: 86 additions & 0 deletions README_zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# linkwork-web

`linkwork-web` 是 LinkWork 的前端管理台,基于 Vue 3 + Vite + TypeScript,负责任务管理、岗位配置、审批、MCP/Skills 管理等页面。

## 本地开发

### 1) 环境要求

- Node.js 22+
- npm 10+

### 2) 安装依赖

```bash
cd linkwork-web
npm install
```

### 3) 启动开发服务

```bash
npm run dev
```

默认通过 Vite 代理 `/api` 到 `http://localhost:8081`(可通过 `VITE_DEV_API_PROXY_TARGET` 覆盖)。

### 4) 生产构建与预览

```bash
npm run build
npm run preview
```

## 关键环境变量

| 变量 | 说明 |
|---|---|
| `VITE_DEV_API_PROXY_TARGET` | 本地开发 API 代理地址 |
| `VITE_SSO_LOGIN_URL` | 未登录时跳转地址(默认 `/login`) |
| `VITE_BYPASS_AUTH_GUARD` | 是否绕过前端路由鉴权(默认开启,设置为 `false` 可关闭) |
| `VITE_WATERMARK_PRODUCT` | 页面水印产品名 |
| `VITE_WATERMARK_OWNER` | 页面水印归属 |
| `VITE_MODEL_ALL_ACCESS_WHITELIST` | 模型全量可见用户白名单(CSV) |
| `VITE_MODEL_DEFAULT_ALLOW_LIST` | 默认可见模型列表(CSV) |

## Deploy 流程

### 方案 A:使用仓库根目录脚本部署到远端主机(推荐)

在 `link-work` 根目录执行:

```bash
GG_HOST=<remote_host> \
REMOTE_USER=<remote_user> \
BRANCH=<target_branch> \
PORT=3000 \
BACKEND_UPSTREAM=http://<backend_host>:8081 \
./deploy-linkwork-web-gg.local.sh
```

脚本行为:

1. SSH 到远端并校验/克隆 `linkwork-web` 仓库
2. 切换到指定分支并 `npm install && npm run build`
3. 生成 Nginx 配置(`/api/` 反向代理到后端)
4. 使用 `nginx:1.27-alpine` 启动容器
5. 执行 `/` 与 `/home` 健康检查(含 SPA fallback)

### 方案 B:手动容器发布

```bash
# 在 linkwork-web 内
npm install
npm run build

# 用 dist + nginx 配置启动容器
docker run -d --name linkwork-web -p 3000:80 \
-v $(pwd)/dist:/usr/share/nginx/html:ro \
-v $(pwd)/nginx.default.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:1.27-alpine
```

## 联动依赖

- 后端默认对接 `linkwork-server` API(通常由 `LinkWork/back` 提供)
- 运行时依赖浏览器与后端 Cookie/JWT 认证链路
3 changes: 2 additions & 1 deletion src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

interface ImportMetaEnv {
readonly VITE_SSO_LOGIN_URL?: string
readonly VITE_MODEL_REGISTRY_URL?: string
readonly VITE_DEV_API_PROXY_TARGET?: string
readonly VITE_WATERMARK_PRODUCT?: string
readonly VITE_WATERMARK_OWNER?: string
}

interface ImportMeta {
Expand Down
16 changes: 15 additions & 1 deletion src/layouts/AppLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ const pageTitle = computed(() => {
if (!isDeveloperMode.value && route.path.startsWith('/fleet')) return '历史任务'
return String(route.meta.title || '仪表盘')
})
const watermarkProduct = (import.meta.env.VITE_WATERMARK_PRODUCT || 'LinkWork').trim() || 'LinkWork'
const watermarkOwner = (import.meta.env.VITE_WATERMARK_OWNER || 'momotech').trim() || 'momotech'
const watermarkLabel = computed(() => `${watermarkProduct} OSS · ${watermarkOwner}`)
const watermarkInstance = computed(() => {
if (typeof window === 'undefined') {
return 'instance: unknown'
}
return `instance: ${window.location.host || 'local'}`
})

const setWorkMode = (mode: WorkMode) => {
if (workMode.value === mode) return
Expand Down Expand Up @@ -541,7 +550,12 @@ watch(
</Transition>
</nav>
</ScrollArea>
<div class="p-4 border-t border-border/50">
<div class="p-4 border-t border-border/50 space-y-2">
<div class="text-[10px] text-muted-foreground/90 flex items-center gap-1">
<Bot class="h-3 w-3 shrink-0" />
<span class="font-mono truncate">{{ watermarkLabel }}</span>
<span class="font-mono text-[9px] text-muted-foreground/70 truncate">{{ watermarkInstance }}</span>
</div>
<span class="text-[10px] text-muted-foreground flex items-center gap-1">
<Info class="h-3 w-3 shrink-0" /> 如有问题,请前往
<a href="https://github.com/momotech/LinkWork/issues" target="_blank" rel="noopener noreferrer" class="text-primary hover:text-primary/80 hover:underline transition-colors cursor-pointer font-medium">Issues</a>
Expand Down
2 changes: 1 addition & 1 deletion src/views/CronJobsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const fallbackModels: ModelOption[] = [
{ id: 'kimi-k2-5-thinking', label: 'kimi-k2-5-thinking', brand: 'kimi' },
]

const modelRegistryApi = import.meta.env.VITE_MODEL_REGISTRY_URL || '/api/v1/models'
const modelRegistryApi = '/api/v1/models'

const inferModelBrand = (modelId: string): string => {
const normalized = modelId.toLowerCase()
Expand Down
4 changes: 1 addition & 3 deletions src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ const fallbackModels: ModelOption[] = [
{ id: 'kimi-k2-5-thinking', name: 'kimi-k2-5-thinking', brand: 'kimi', enabled: true }
]

const modelRegistryApi =
import.meta.env.VITE_MODEL_REGISTRY_URL ||
'/api/v1/models'
const modelRegistryApi = '/api/v1/models'

const splitCsv = (raw: unknown): string[] =>
String(raw ?? '')
Expand Down