Compare commits
1 Commits
main
...
doc/update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67bf25bd77 |
@ -1,986 +0,0 @@
|
||||
# VLESS URI Scheme Troubleshooting Runbook
|
||||
|
||||
## Overview
|
||||
|
||||
This runbook helps diagnose and fix issues with VLESS QR code generation, copy link, and download functionality in the user center. The system **requires valid node data from the server** - there are no hardcoded fallbacks.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ agent.svc.plus │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Agent Registration & Heartbeat │ │
|
||||
│ │ - Registers with metadata (name, region, etc.) │ │
|
||||
│ │ - Sends periodic heartbeats │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
└───────────────────────────┼─────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ accounts.svc.plus │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ URI Scheme Templates (embedded in binary) │ │
|
||||
│ │ - VLESS-TCP-URI.Scheme │ │
|
||||
│ │ - VLESS-XHTTP-URI.Scheme │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ /api/agent/nodes Handler │ │
|
||||
│ │ 1. Reads registered agent metadata │ │
|
||||
│ │ 2. Loads URI scheme templates │ │
|
||||
│ │ 3. Renders templates with user UUID, domain, etc. │ │
|
||||
│ │ 4. Returns array of VlessNode objects │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
└───────────────────────────┼─────────────────────────────────────┘
|
||||
│
|
||||
▼ JSON Response
|
||||
{
|
||||
"name": "NODE-NAME",
|
||||
"address": "example.com",
|
||||
"transport": "tcp",
|
||||
"uri_scheme_tcp": "vless://...",
|
||||
"uri_scheme_xhttp": "vless://..."
|
||||
}
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ console.svc.plus │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ /api/agent/nodes (proxy to accounts.svc.plus) │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ VlessQrCard Component │ │
|
||||
│ │ - Fetches nodes via useSWR │ │
|
||||
│ │ - User selects node and transport (TCP/XHTTP) │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ buildVlessUri(uuid, node) │ │
|
||||
│ │ ✅ Validates: uuid, node, node.transport │ │
|
||||
│ │ ✅ Selects: uri_scheme_tcp or uri_scheme_xhttp │ │
|
||||
│ │ ❌ No fallbacks - returns null if data missing │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ renderVlessUriFromScheme(template, values) │ │
|
||||
│ │ - Substitutes ${UUID}, ${DOMAIN}, etc. │ │
|
||||
│ │ - Returns complete VLESS URI │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ vless://uuid@host:port?... │
|
||||
│ │ │
|
||||
│ ┌─────────────┼─────────────┐ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ QR Code Copy Link Download QR │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
### ✅ What the System Does
|
||||
|
||||
1. **Server-Driven Configuration**: All node information comes from `accounts.svc.plus`
|
||||
2. **Dynamic Node Discovery**: Nodes are discovered from registered agents in `agent.svc.plus`
|
||||
3. **Template-Based URI Generation**: URI schemes are defined in `accounts.svc.plus` and rendered server-side
|
||||
4. **Strict Validation**: Frontend validates all required fields and fails fast with clear errors
|
||||
5. **No Hardcoded Defaults**: No fake nodes, hosts, or labels in the UI code
|
||||
|
||||
### ❌ What the System Does NOT Do
|
||||
|
||||
1. **No Fallback Nodes**: If no agents are registered, no nodes are shown (not "TOKYO-NODE")
|
||||
2. **No Hardcoded Hosts**: No `'ha-proxy-jp.svc.plus'` or similar in frontend code
|
||||
3. **No Manual URI Construction**: Frontend never builds URIs from scratch using URLSearchParams
|
||||
4. **No Default Labels**: No hardcoded "TOKYO-NODE" or similar labels
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Issue 0: `/api/agent/nodes` Returns 500 Internal Server Error
|
||||
|
||||
**Symptoms:**
|
||||
- Browser console shows: `GET /api/agent/nodes 500 (Internal Server Error)`
|
||||
- VLESS QR card displays: "❌ 节点数据缺失 / 无法从服务器获取代理节点列表"
|
||||
- Frontend error: `[VLESS] Cannot build URI: node is undefined`
|
||||
|
||||
**Root Cause:**
|
||||
|
||||
The `agentRegistry` in `accounts.svc.plus` is not properly initialized, causing `h.agentStatusReader` to be `nil` when `/api/agent/nodes` is called.
|
||||
|
||||
**Architecture:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ hk-xhttp.svc.plus (VM) │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ agent.svc.plus │ │
|
||||
│ │ Config: /etc/agent/account-agent.yaml │ │
|
||||
│ │ - agent.id: "hk-xhttp.svc.plus" │ │
|
||||
│ │ - apiToken: "uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=" │
|
||||
│ │ - controllerUrl: "https://accounts-svc-plus-...run.app" │
|
||||
│ └──────────────────┬─────────────────────────────────┘ │
|
||||
└─────────────────────┼─────────────────────────────────────┘
|
||||
│ POST /api/agent-server/v1/status
|
||||
│ Authorization: Bearer <apiToken>
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Cloud Run: accounts-svc-plus │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ Environment Variables (REQUIRED): │ │
|
||||
│ │ - INTERNAL_SERVICE_TOKEN=uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I= │
|
||||
│ │ - AGENT_ID=hk-xhttp.svc.plus │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ agentRegistry Initialization (main.go:659-673) │ │
|
||||
│ │ if INTERNAL_SERVICE_TOKEN is set: │ │
|
||||
│ │ agentID = AGENT_ID (or "internal-agent") │ │
|
||||
│ │ registry = NewRegistry({ │ │
|
||||
│ │ ID: agentID, │ │
|
||||
│ │ Token: INTERNAL_SERVICE_TOKEN │ │
|
||||
│ │ }) │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ agentAuthMiddleware (main.go:1002-1021) │ │
|
||||
│ │ 1. Extract Bearer token from Authorization header│ │
|
||||
│ │ 2. registry.Authenticate(token) │ │
|
||||
│ │ 3. If valid, set agent identity in context │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Configuration Mapping:**
|
||||
|
||||
| Component | Variable | Value | Purpose |
|
||||
|-----------|----------|-------|---------|
|
||||
| agent.svc.plus | `agent.id` | `hk-xhttp.svc.plus` | Agent's self-reported ID |
|
||||
| agent.svc.plus | `agent.apiToken` | `uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=` | Authentication token |
|
||||
| accounts.svc.plus | `INTERNAL_SERVICE_TOKEN` | `uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=` | **Must match** agent.apiToken |
|
||||
| accounts.svc.plus | `AGENT_ID` | `hk-xhttp.svc.plus` | **Must match** agent.id |
|
||||
|
||||
**Diagnosis Steps:**
|
||||
|
||||
1. **Check Cloud Run environment variables:**
|
||||
```bash
|
||||
gcloud run services describe accounts-svc-plus \
|
||||
--region=asia-northeast1 \
|
||||
--format="value(spec.template.spec.containers[0].env)" | \
|
||||
grep -E "INTERNAL_SERVICE_TOKEN|AGENT_ID"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
{'name': 'INTERNAL_SERVICE_TOKEN', 'value': 'uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I='}
|
||||
{'name': 'AGENT_ID', 'value': 'hk-xhttp.svc.plus'}
|
||||
```
|
||||
|
||||
2. **Check agent heartbeat logs:**
|
||||
```bash
|
||||
gcloud logging read 'resource.type=cloud_run_revision AND resource.labels.service_name=accounts-svc-plus AND textPayload:"agent status"' \
|
||||
--limit=10 \
|
||||
--format="value(textPayload)" \
|
||||
--project=xzerolab-480008
|
||||
```
|
||||
|
||||
✅ **Good** (agent authenticated):
|
||||
```
|
||||
time=2026-02-04T15:42:35.158Z level=INFO msg="agent status updated" agent=hk-xhttp.svc.plus healthy=true clients=7
|
||||
time=2026-02-04T15:42:35.158Z level=INFO msg=request method=POST path=/api/agent-server/v1/status status=204 latency=142.949µs
|
||||
```
|
||||
|
||||
❌ **Bad** (authentication failed):
|
||||
```
|
||||
time=2026-02-04T15:46:35.098Z level=INFO msg=request method=POST path=/api/agent-server/v1/status status=401 latency=48.72µs
|
||||
```
|
||||
|
||||
3. **Check agent configuration on VM:**
|
||||
```bash
|
||||
ssh root@hk-xhttp.svc.plus
|
||||
cat /etc/agent/account-agent.yaml | grep -A 5 "agent:"
|
||||
```
|
||||
|
||||
Expected:
|
||||
```yaml
|
||||
agent:
|
||||
id: "hk-xhttp.svc.plus"
|
||||
controllerUrl: "https://accounts-svc-plus-266500572462.asia-northeast1.run.app"
|
||||
apiToken: "uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I="
|
||||
statusInterval: 1m
|
||||
```
|
||||
|
||||
**Fix:**
|
||||
|
||||
1. **Set Cloud Run environment variables:**
|
||||
```bash
|
||||
gcloud run services update accounts-svc-plus \
|
||||
--region=asia-northeast1 \
|
||||
--set-env-vars="INTERNAL_SERVICE_TOKEN=uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=,AGENT_ID=hk-xhttp.svc.plus"
|
||||
```
|
||||
|
||||
2. **Verify deployment:**
|
||||
```bash
|
||||
# Wait for deployment to complete
|
||||
gcloud run services describe accounts-svc-plus \
|
||||
--region=asia-northeast1 \
|
||||
--format="value(status.url)"
|
||||
```
|
||||
|
||||
3. **Wait for agent heartbeat (1 minute):**
|
||||
```bash
|
||||
# Agent sends heartbeat every 1 minute (statusInterval: 1m)
|
||||
sleep 65
|
||||
```
|
||||
|
||||
4. **Verify agent registration:**
|
||||
```bash
|
||||
gcloud logging read 'resource.type=cloud_run_revision AND resource.labels.service_name=accounts-svc-plus AND textPayload:"agent status updated"' \
|
||||
--limit=1 \
|
||||
--format="value(textPayload)" \
|
||||
--project=xzerolab-480008
|
||||
```
|
||||
|
||||
Should show:
|
||||
```
|
||||
level=INFO msg="agent status updated" agent=hk-xhttp.svc.plus healthy=true clients=X
|
||||
```
|
||||
|
||||
5. **Test API:**
|
||||
```bash
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes | jq '.'
|
||||
```
|
||||
|
||||
Expected: Array with node data (not 500 error)
|
||||
|
||||
**Code Changes Required:**
|
||||
|
||||
Modified `accounts.svc.plus/cmd/accountsvc/main.go` (lines 659-673):
|
||||
|
||||
```go
|
||||
} else if token := os.Getenv("INTERNAL_SERVICE_TOKEN"); token != "" {
|
||||
// Fallback: if no credentials configured but we have an internal token,
|
||||
// accept any agent that presents this token (ID will be taken from agent's self-reported ID)
|
||||
// This allows the agent to use its configured ID (e.g., "hk-xhttp.svc.plus")
|
||||
agentID := strings.TrimSpace(os.Getenv("AGENT_ID"))
|
||||
if agentID == "" {
|
||||
agentID = "internal-agent" // fallback ID if not specified
|
||||
}
|
||||
agentRegistry, err = agentserver.NewRegistry(agentserver.Config{
|
||||
Credentials: []agentserver.Credential{{
|
||||
ID: agentID,
|
||||
Name: "Internal Agent",
|
||||
Token: token,
|
||||
Groups: []string{"internal"},
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Verification:**
|
||||
|
||||
After fix, you should see:
|
||||
- ✅ Agent heartbeat logs show `status=204` (success) instead of `status=401`
|
||||
- ✅ `/api/agent/nodes` returns `200` with node data
|
||||
- ✅ VLESS QR code displays correctly in UI
|
||||
- ✅ No `[VLESS] Cannot build URI` errors in browser console
|
||||
|
||||
---
|
||||
|
||||
### Issue 1: No Nodes Displayed / Empty Node List
|
||||
|
||||
**Symptoms:**
|
||||
- Node selector is empty or not shown
|
||||
- QR code shows error or "Generating QR code..." indefinitely
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. Check `/api/agent/nodes` response:
|
||||
```bash
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes
|
||||
```
|
||||
|
||||
Expected: Array with at least one node
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "NODE-NAME",
|
||||
"address": "example.com",
|
||||
"transport": "tcp",
|
||||
"uri_scheme_tcp": "vless://...",
|
||||
"uri_scheme_xhttp": "vless://..."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
2. Check browser console for errors:
|
||||
```
|
||||
[VLESS] Cannot build URI: node is undefined
|
||||
```
|
||||
|
||||
**Root Causes & Fixes:**
|
||||
|
||||
| Cause | Diagnosis | Fix |
|
||||
|-------|-----------|-----|
|
||||
| No agents registered | Check agent.svc.plus logs for registration | Register at least one agent |
|
||||
| `XRAY_PROXY_NODES` env var empty | Check accounts.svc.plus env vars | Set `XRAY_PROXY_NODES=host1.com,host2.com` |
|
||||
| Agent heartbeat failing | Check agent.svc.plus health endpoint | Fix agent connectivity |
|
||||
| API proxy misconfigured | Check console.svc.plus API routes | Verify `/api/agent/*` proxies to accounts.svc.plus |
|
||||
|
||||
### Issue 2: QR Code Not Displaying
|
||||
|
||||
**Symptoms:**
|
||||
- Nodes are listed but QR code doesn't generate
|
||||
- Error in browser console
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
Check browser console for `[VLESS]` errors:
|
||||
|
||||
```javascript
|
||||
// Error 1: Missing transport
|
||||
[VLESS] Missing transport type from node: example.com
|
||||
|
||||
// Error 2: Missing URI scheme
|
||||
[VLESS] Missing URI scheme template from server for transport: tcp.
|
||||
Node: example.com.
|
||||
Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields.
|
||||
|
||||
// Error 3: Invalid UUID
|
||||
[VLESS] Cannot build URI: node is undefined
|
||||
```
|
||||
|
||||
**Root Causes & Fixes:**
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Missing transport | Node object missing `transport` field | Check accounts.svc.plus `listAgentNodes` function |
|
||||
| Missing URI scheme | `uri_scheme_tcp` or `uri_scheme_xhttp` not in response | Verify `VLESSTCPScheme()` and `VLESSXHTTPScheme()` are called |
|
||||
| Invalid UUID | User doesn't have proxy UUID | Check user record in database |
|
||||
|
||||
### Issue 3: Copy Link Returns Empty
|
||||
|
||||
**Symptoms:**
|
||||
- Click "Copy Link" button
|
||||
- Nothing copied to clipboard
|
||||
- Or clipboard contains "null"
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
This is the same as Issue 2 - `buildVlessUri()` is returning `null`.
|
||||
|
||||
**Fix:**
|
||||
|
||||
See Issue 2 root causes and fixes.
|
||||
|
||||
### Issue 4: Switching TCP/XHTTP Doesn't Update QR
|
||||
|
||||
**Symptoms:**
|
||||
- Click TCP or XHTTP button
|
||||
- QR code doesn't change
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. Check if both `uri_scheme_tcp` and `uri_scheme_xhttp` exist in node data
|
||||
2. Check React DevTools to see if `preferredTransport` state is updating
|
||||
3. Check if `vlessUri` is recomputing
|
||||
|
||||
**Root Cause:**
|
||||
|
||||
Usually a React rendering issue, not related to URI scheme logic. The `useEffect` hook should automatically trigger when `vlessUri` changes.
|
||||
|
||||
**Fix:**
|
||||
|
||||
```typescript
|
||||
// In VlessQrCard.tsx, verify this useEffect exists:
|
||||
useEffect(() => {
|
||||
if (!vlessUri) {
|
||||
setQrDataUrl(null)
|
||||
return
|
||||
}
|
||||
|
||||
toDataURL(vlessUri, { /* ... */ })
|
||||
.then(setQrDataUrl)
|
||||
.catch(console.error)
|
||||
}, [vlessUri]) // ✅ Depends on vlessUri
|
||||
```
|
||||
|
||||
### Issue 5: Node Shows Generic "Node" Label
|
||||
|
||||
**Symptoms:**
|
||||
- Node badge shows "Node" instead of actual node name
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
Check node data structure:
|
||||
```javascript
|
||||
// In browser console
|
||||
console.log(effectiveNode)
|
||||
```
|
||||
|
||||
Expected:
|
||||
```javascript
|
||||
{
|
||||
name: "TOKYO-NODE", // ✅ Should have name
|
||||
address: "example.com",
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Root Causes & Fixes:**
|
||||
|
||||
| Cause | Fix |
|
||||
|-------|-----|
|
||||
| Agent not sending name metadata | Update agent to send `name` in registration |
|
||||
| `resolveNodeName()` logic broken | Check accounts.svc.plus `resolveNodeName` function |
|
||||
| Fallback to generic label | Expected behavior if `name` and `address` both missing |
|
||||
|
||||
## Debugging Steps
|
||||
|
||||
### Step 1: Verify Backend URI Schemes
|
||||
|
||||
```bash
|
||||
# SSH into accounts.svc.plus container or check source code
|
||||
cd /app/internal/xrayconfig
|
||||
|
||||
# Check scheme files exist
|
||||
ls -la VLESS-*.Scheme
|
||||
|
||||
# View TCP scheme
|
||||
cat VLESS-TCP-URI.Scheme
|
||||
# Expected: vless://${UUID}@${DOMAIN}:1443?encryption=none&type=tcp&security=tls&sni=${SNI}&fp=${FP}&flow=${FLOW}#${TAG}
|
||||
|
||||
# View XHTTP scheme
|
||||
cat VLESS-XHTTP-URI.Scheme
|
||||
# Expected: vless://${UUID}@${DOMAIN}:443?encryption=none&type=xhttp&security=tls&host=${DOMAIN}&path=${PATH}&mode=${MODE}&sni=${SNI}&fp=${FP}&alpn=h2%2Chttp%2F1.1%2Ch3#${TAG}
|
||||
```
|
||||
|
||||
### Step 2: Test API Endpoint
|
||||
|
||||
```bash
|
||||
# Get auth token from browser cookies
|
||||
TOKEN="your-xc_session-cookie-value"
|
||||
|
||||
# Test nodes endpoint
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes | jq '.'
|
||||
|
||||
# Expected output:
|
||||
[
|
||||
{
|
||||
"name": "NODE-NAME",
|
||||
"address": "example.com",
|
||||
"port": 443,
|
||||
"users": ["uuid-here"],
|
||||
"transport": "xhttp",
|
||||
"path": "/split",
|
||||
"mode": "auto",
|
||||
"security": "tls",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"server_name": "example.com",
|
||||
"xhttp_port": 443,
|
||||
"tcp_port": 1443,
|
||||
"uri_scheme_xhttp": "vless://uuid@example.com:443?...",
|
||||
"uri_scheme_tcp": "vless://uuid@example.com:1443?..."
|
||||
}
|
||||
]
|
||||
|
||||
# Check specific fields
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes | \
|
||||
jq '.[0] | {name, address, uri_scheme_tcp, uri_scheme_xhttp}'
|
||||
```
|
||||
|
||||
### Step 3: Check Frontend Logs
|
||||
|
||||
Open browser DevTools Console and look for:
|
||||
|
||||
```
|
||||
✅ Good (no errors):
|
||||
(no [VLESS] messages)
|
||||
|
||||
❌ Bad:
|
||||
[VLESS] Cannot build URI: node is undefined
|
||||
[VLESS] Missing transport type from node: example.com
|
||||
[VLESS] Missing URI scheme template from server for transport: tcp. Node: example.com. Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields.
|
||||
```
|
||||
|
||||
### Step 4: Verify QR Code Generation
|
||||
|
||||
```javascript
|
||||
// In browser console, test QR generation manually
|
||||
import { toDataURL } from 'qrcode'
|
||||
|
||||
const testUri = 'vless://test-uuid@example.com:1443?encryption=none&type=tcp&security=tls#TEST'
|
||||
|
||||
toDataURL(testUri, {
|
||||
errorCorrectionLevel: 'M',
|
||||
margin: 1,
|
||||
scale: 8,
|
||||
}).then(url => {
|
||||
console.log('QR generated successfully:', url.substring(0, 50) + '...')
|
||||
// Create img element to view
|
||||
const img = document.createElement('img')
|
||||
img.src = url
|
||||
document.body.appendChild(img)
|
||||
})
|
||||
```
|
||||
|
||||
### Step 5: Check Agent Registration
|
||||
|
||||
```bash
|
||||
# Check if agents are registered
|
||||
# (This depends on your agent.svc.plus implementation)
|
||||
|
||||
# Example: Check agent heartbeat logs
|
||||
kubectl logs -n your-namespace deployment/agent-svc-plus | grep heartbeat
|
||||
|
||||
# Example: Check registered agents in database
|
||||
psql -c "SELECT * FROM agents WHERE last_heartbeat > NOW() - INTERVAL '5 minutes';"
|
||||
```
|
||||
|
||||
## Code References
|
||||
|
||||
### Frontend (console.svc.plus)
|
||||
|
||||
**Main Logic:**
|
||||
- [vless.ts](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus/src/modules/extensions/builtin/user-center/lib/vless.ts)
|
||||
- `VLESS_DEFAULTS` - Technical constants (fingerprint, tcpFlow)
|
||||
- `buildVlessUri()` - Renders URI from server template (**no fallbacks**)
|
||||
- `renderVlessUriFromScheme()` - Template variable substitution
|
||||
- `buildVlessConfig()` - Builds Xray config for download
|
||||
|
||||
**UI Component:**
|
||||
- [VlessQrCard.tsx](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus/src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx)
|
||||
- Fetches nodes from `/api/agent/nodes` via `useSWR`
|
||||
- Manages TCP/XHTTP transport switching
|
||||
- Generates QR code via `toDataURL()`
|
||||
- Displays node selector if multiple nodes available
|
||||
|
||||
### Backend (accounts.svc.plus)
|
||||
|
||||
**URI Schemes:**
|
||||
- `internal/xrayconfig/VLESS-TCP-URI.Scheme` - TCP URI template
|
||||
- `internal/xrayconfig/VLESS-XHTTP-URI.Scheme` - XHTTP URI template
|
||||
|
||||
**Template Loading:**
|
||||
- `internal/xrayconfig/templates.go`
|
||||
- `VLESSTCPScheme()` - Returns embedded TCP URI template
|
||||
- `VLESSXHTTPScheme()` - Returns embedded XHTTP URI template
|
||||
|
||||
**API Handler:**
|
||||
- `api/user_agents.go`
|
||||
- `listAgentNodes()` - Returns node list with rendered URI schemes
|
||||
- `renderVLESSURIScheme()` - Substitutes template variables (${UUID}, ${DOMAIN}, etc.)
|
||||
- `registeredNodeMetadata()` - Reads agent registration data
|
||||
- `parseProxyNodeHosts()` - Parses `XRAY_PROXY_NODES` env var
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### accounts.svc.plus
|
||||
|
||||
| Variable | Purpose | Default | Example |
|
||||
|----------|---------|---------|---------|
|
||||
| `XRAY_PROXY_NODES` | Additional proxy nodes (comma-separated) | - | `node1.com,node2.com` |
|
||||
| `XRAY_XHTTP_PATH` | XHTTP path | `/split` | `/custom-path` |
|
||||
| `XRAY_XHTTP_MODE` | XHTTP mode | `auto` | `stream` |
|
||||
| `XRAY_XHTTP_PORT` | XHTTP port | `443` | `8443` |
|
||||
| `XRAY_TCP_PORT` | TCP port | `1443` | `1444` |
|
||||
|
||||
## Recovery Procedures
|
||||
|
||||
### Procedure 1: Fix Missing URI Schemes
|
||||
|
||||
If `uri_scheme_tcp` or `uri_scheme_xhttp` are missing from API response:
|
||||
|
||||
1. **Check scheme files exist:**
|
||||
```bash
|
||||
ls -la /app/internal/xrayconfig/VLESS-*.Scheme
|
||||
```
|
||||
|
||||
2. **Verify templates.go loads them:**
|
||||
```go
|
||||
// Should have these embed directives:
|
||||
//go:embed VLESS-TCP-URI.Scheme
|
||||
vlessTCPScheme []byte
|
||||
|
||||
//go:embed VLESS-XHTTP-URI.Scheme
|
||||
vlessXHTTPScheme []byte
|
||||
```
|
||||
|
||||
3. **Rebuild and redeploy accounts.svc.plus:**
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/accounts.svc.plus
|
||||
go build -o accounts-svc-plus
|
||||
# Deploy to Cloud Run or your platform
|
||||
```
|
||||
|
||||
4. **Verify schemes are returned:**
|
||||
```bash
|
||||
curl https://accounts.svc.plus/api/agent/nodes | jq '.[0].uri_scheme_tcp'
|
||||
```
|
||||
|
||||
### Procedure 2: Fix No Nodes Returned
|
||||
|
||||
If `/api/agent/nodes` returns empty array `[]`:
|
||||
|
||||
1. **Check `XRAY_PROXY_NODES` env var:**
|
||||
```bash
|
||||
# In accounts.svc.plus container
|
||||
echo $XRAY_PROXY_NODES
|
||||
# Should output: host1.com,host2.com
|
||||
```
|
||||
|
||||
2. **Check agent registration:**
|
||||
```bash
|
||||
# Verify agents are sending heartbeats
|
||||
# (Implementation-specific)
|
||||
```
|
||||
|
||||
3. **Add manual proxy nodes:**
|
||||
```bash
|
||||
# Set env var in Cloud Run or your platform
|
||||
XRAY_PROXY_NODES=node1.example.com,node2.example.com
|
||||
```
|
||||
|
||||
4. **Restart accounts.svc.plus**
|
||||
|
||||
### Procedure 3: Reset Frontend State
|
||||
|
||||
If QR code is stuck or showing old data:
|
||||
|
||||
1. **Clear browser cache:**
|
||||
- Chrome: Cmd+Shift+Delete (Mac) or Ctrl+Shift+Delete (Windows)
|
||||
- Select "Cached images and files"
|
||||
|
||||
2. **Hard refresh:**
|
||||
- Mac: Cmd+Shift+R
|
||||
- Windows: Ctrl+Shift+R
|
||||
|
||||
3. **Clear SWR cache:**
|
||||
```javascript
|
||||
// In browser console
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
location.reload()
|
||||
```
|
||||
|
||||
4. **Verify API returns valid data:**
|
||||
```bash
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Key Metrics
|
||||
|
||||
1. **QR Code Generation Success Rate**
|
||||
- Track `toDataURL()` success/failure ratio
|
||||
- Alert if failure rate > 5%
|
||||
|
||||
2. **API Response Time**
|
||||
- Monitor `/api/agent/nodes` latency
|
||||
- Alert if p95 > 500ms
|
||||
|
||||
3. **Error Rate**
|
||||
- Count `[VLESS]` console errors
|
||||
- Alert if error rate > 1% of page loads
|
||||
|
||||
4. **Node Availability**
|
||||
- Track number of nodes returned by `/api/agent/nodes`
|
||||
- Alert if drops to 0
|
||||
|
||||
### Alerts
|
||||
|
||||
Set up alerts for:
|
||||
|
||||
- High rate of `[VLESS] Missing URI scheme` errors (> 10/min)
|
||||
- `/api/agent/nodes` returning empty array for > 5 minutes
|
||||
- QR code generation failures (> 5% of attempts)
|
||||
- Agent heartbeat failures (no heartbeat for > 5 minutes)
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before deploying changes:
|
||||
|
||||
- [ ] `/api/agent/nodes` returns valid node data with `uri_scheme_tcp` and `uri_scheme_xhttp`
|
||||
- [ ] QR code generates successfully for TCP transport
|
||||
- [ ] QR code generates successfully for XHTTP transport
|
||||
- [ ] Copy link button copies valid VLESS URI
|
||||
- [ ] Download QR button downloads valid QR code image
|
||||
- [ ] Switching TCP ↔ XHTTP regenerates QR code
|
||||
- [ ] Node selector shows all registered nodes (if multiple exist)
|
||||
- [ ] No `[VLESS]` errors in browser console (with valid data)
|
||||
- [ ] Clear error messages in console when data is missing
|
||||
- [ ] No hardcoded "TOKYO-NODE" or fake hosts appear in UI
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [VLESS Protocol Specification](https://github.com/XTLS/Xray-core)
|
||||
- [Implementation Plan](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/implementation_plan.md)
|
||||
- [Walkthrough](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/walkthrough.md)
|
||||
|
||||
## Change Log
|
||||
|
||||
| Date | Change | Impact |
|
||||
|------|--------|--------|
|
||||
| 2026-02-04 | Removed `DEFAULT_VLESS_TEMPLATE` hardcoded values | No more fake "TOKYO-NODE" or `ha-proxy-jp.svc.plus` |
|
||||
| 2026-02-04 | Removed fallback URI construction logic | System now fails fast with clear errors |
|
||||
| 2026-02-04 | Removed `DEFAULT_VLESS_LABEL` export | Node labels come from server only |
|
||||
| 2026-02-04 | Added strict validation in `buildVlessUri` | Better error messages for debugging |
|
||||
| 2026-02-04 | Created troubleshooting runbook | Easier diagnosis and recovery |
|
||||
|
||||
|
||||
### Issue 1: QR Code Not Displaying
|
||||
|
||||
**Symptoms:**
|
||||
- QR code shows "Generating QR code..." indefinitely
|
||||
- Or shows error message
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. Check browser console for `[VLESS]` errors:
|
||||
```
|
||||
[VLESS] Missing URI scheme template from server for transport: tcp
|
||||
```
|
||||
|
||||
2. Check `/api/agent/nodes` response in Network tab:
|
||||
```bash
|
||||
# Should include these fields:
|
||||
{
|
||||
"uri_scheme_tcp": "vless://...",
|
||||
"uri_scheme_xhttp": "vless://..."
|
||||
}
|
||||
```
|
||||
|
||||
**Root Causes:**
|
||||
|
||||
| Cause | Fix |
|
||||
|-------|-----|
|
||||
| accounts.svc.plus not returning URI schemes | Check `user_agents.go` is calling `xrayconfig.VLESSXHTTPScheme()` and `xrayconfig.VLESSTCPScheme()` |
|
||||
| URI scheme files missing from binary | Ensure `VLESS-TCP-URI.Scheme` and `VLESS-XHTTP-URI.Scheme` exist and are embedded via `//go:embed` |
|
||||
| API proxy not working | Check console.svc.plus API proxy configuration for `/api/agent/*` routes |
|
||||
|
||||
### Issue 2: Copy Link Returns Empty
|
||||
|
||||
**Symptoms:**
|
||||
- Click "Copy Link" button
|
||||
- Nothing is copied to clipboard
|
||||
- Or error in console
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
Check if `buildVlessUri()` is returning null:
|
||||
```javascript
|
||||
// In browser console
|
||||
console.log(vlessUri) // Should be a string starting with "vless://"
|
||||
```
|
||||
|
||||
**Root Causes:**
|
||||
|
||||
Same as Issue 1 - missing URI schemes from server.
|
||||
|
||||
### Issue 3: Switching TCP/XHTTP Doesn't Update QR
|
||||
|
||||
**Symptoms:**
|
||||
- Click TCP or XHTTP button
|
||||
- QR code doesn't change
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
This should work automatically via React state. Check:
|
||||
1. `preferredTransport` state is updating
|
||||
2. `effectiveNode` is recomputing
|
||||
3. `vlessUri` is changing
|
||||
4. QR code `useEffect` is triggering
|
||||
|
||||
**Root Cause:**
|
||||
|
||||
Likely a React rendering issue, not related to URI scheme logic.
|
||||
|
||||
### Issue 4: Node Selector Not Showing
|
||||
|
||||
**Symptoms:**
|
||||
- Only one node shown even though multiple agents are registered
|
||||
- Node selector dropdown doesn't appear
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. Check `/api/agent/nodes` returns multiple nodes:
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <token>" \
|
||||
https://accounts.svc.plus/api/agent/nodes | jq 'length'
|
||||
```
|
||||
|
||||
2. Check agent.svc.plus has registered multiple nodes
|
||||
|
||||
**Root Causes:**
|
||||
|
||||
| Cause | Fix |
|
||||
|-------|-----|
|
||||
| Only one agent registered | Register more agents via agent.svc.plus |
|
||||
| `XRAY_PROXY_NODES` env var only has one host | Add more hosts to env var in accounts.svc.plus |
|
||||
| Agent registration not working | Check agent.svc.plus heartbeat and registration logic |
|
||||
|
||||
## Debugging Steps
|
||||
|
||||
### Step 1: Verify Backend URI Schemes
|
||||
|
||||
```bash
|
||||
# SSH into accounts.svc.plus container
|
||||
cd /app/internal/xrayconfig
|
||||
|
||||
# Check scheme files exist
|
||||
ls -la VLESS-*.Scheme
|
||||
|
||||
# View TCP scheme
|
||||
cat VLESS-TCP-URI.Scheme
|
||||
# Expected: vless://${UUID}@${DOMAIN}:1443?encryption=none&type=tcp&security=tls&sni=${SNI}&fp=${FP}&flow=${FLOW}#${TAG}
|
||||
|
||||
# View XHTTP scheme
|
||||
cat VLESS-XHTTP-URI.Scheme
|
||||
# Expected: vless://${UUID}@${DOMAIN}:443?encryption=none&type=xhttp&security=tls&host=${DOMAIN}&path=${PATH}&mode=${MODE}&sni=${SNI}&fp=${FP}&alpn=h2%2Chttp%2F1.1%2Ch3#${TAG}
|
||||
```
|
||||
|
||||
### Step 2: Test API Endpoint
|
||||
|
||||
```bash
|
||||
# Get auth token from browser (check cookies or localStorage)
|
||||
TOKEN="your-session-token"
|
||||
|
||||
# Test nodes endpoint
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes | jq '.'
|
||||
|
||||
# Should return array with uri_scheme_tcp and uri_scheme_xhttp fields
|
||||
```
|
||||
|
||||
### Step 3: Check Frontend Logs
|
||||
|
||||
Open browser DevTools Console and look for:
|
||||
|
||||
```
|
||||
✅ Good:
|
||||
(no [VLESS] errors)
|
||||
|
||||
❌ Bad:
|
||||
[VLESS] Cannot build URI: node is undefined
|
||||
[VLESS] Missing URI scheme template from server for transport: tcp. Node: TOKYO-NODE. Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields.
|
||||
```
|
||||
|
||||
### Step 4: Verify QR Code Generation
|
||||
|
||||
```javascript
|
||||
// In browser console, test QR generation manually
|
||||
import { toDataURL } from 'qrcode'
|
||||
|
||||
const testUri = 'vless://test-uuid@example.com:1443?encryption=none&type=tcp&security=tls#TEST'
|
||||
|
||||
toDataURL(testUri, {
|
||||
errorCorrectionLevel: 'M',
|
||||
margin: 1,
|
||||
scale: 8,
|
||||
}).then(url => console.log('QR generated:', url))
|
||||
```
|
||||
|
||||
## Code References
|
||||
|
||||
### Frontend (console.svc.plus)
|
||||
|
||||
- **Main logic:** `src/modules/extensions/builtin/user-center/lib/vless.ts`
|
||||
- `buildVlessUri()` - Renders URI from server template
|
||||
- `renderVlessUriFromScheme()` - Template variable substitution
|
||||
|
||||
- **UI component:** `src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx`
|
||||
- Fetches nodes from `/api/agent/nodes`
|
||||
- Manages TCP/XHTTP transport switching
|
||||
- Generates QR code via `toDataURL()`
|
||||
|
||||
### Backend (accounts.svc.plus)
|
||||
|
||||
- **URI schemes:** `internal/xrayconfig/VLESS-TCP-URI.Scheme`, `VLESS-XHTTP-URI.Scheme`
|
||||
- **Template loading:** `internal/xrayconfig/templates.go`
|
||||
- `VLESSTCPScheme()` - Returns TCP URI template
|
||||
- `VLESSXHTTPScheme()` - Returns XHTTP URI template
|
||||
|
||||
- **API handler:** `api/user_agents.go`
|
||||
- `listAgentNodes()` - Returns node list with rendered URI schemes
|
||||
- `renderVLESSURIScheme()` - Substitutes template variables
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### accounts.svc.plus
|
||||
|
||||
| Variable | Purpose | Example |
|
||||
|----------|---------|---------|
|
||||
| `XRAY_PROXY_NODES` | Additional proxy nodes | `ha-proxy-jp.svc.plus,ha-proxy-us.svc.plus` |
|
||||
| `XRAY_XHTTP_PATH` | XHTTP path | `/split` |
|
||||
| `XRAY_XHTTP_MODE` | XHTTP mode | `auto` |
|
||||
| `XRAY_XHTTP_PORT` | XHTTP port | `443` |
|
||||
| `XRAY_TCP_PORT` | TCP port | `1443` |
|
||||
|
||||
## Recovery Procedures
|
||||
|
||||
### Procedure 1: Fix Missing URI Schemes
|
||||
|
||||
If URI schemes are missing from API response:
|
||||
|
||||
1. Check scheme files exist in accounts.svc.plus:
|
||||
```bash
|
||||
ls internal/xrayconfig/VLESS-*.Scheme
|
||||
```
|
||||
|
||||
2. Rebuild accounts.svc.plus to embed schemes:
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/accounts.svc.plus
|
||||
go build -o accounts-svc-plus
|
||||
```
|
||||
|
||||
3. Redeploy accounts.svc.plus
|
||||
|
||||
4. Verify schemes are returned:
|
||||
```bash
|
||||
curl https://accounts.svc.plus/api/agent/nodes | jq '.[0].uri_scheme_tcp'
|
||||
```
|
||||
|
||||
### Procedure 2: Reset Frontend State
|
||||
|
||||
If QR code is stuck:
|
||||
|
||||
1. Clear browser cache
|
||||
2. Hard refresh (Cmd+Shift+R on Mac)
|
||||
3. Check for console errors
|
||||
4. Verify `/api/agent/nodes` returns valid data
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Key Metrics
|
||||
|
||||
- **QR Code Generation Success Rate:** Track `toDataURL()` success/failure
|
||||
- **API Response Time:** `/api/agent/nodes` latency
|
||||
- **Error Rate:** Count of `[VLESS]` console errors
|
||||
|
||||
### Alerts
|
||||
|
||||
Set up alerts for:
|
||||
- High rate of `[VLESS] Missing URI scheme` errors
|
||||
- `/api/agent/nodes` returning empty array
|
||||
- QR code generation failures
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [VLESS Protocol Specification](https://github.com/XTLS/Xray-core)
|
||||
- [Implementation Plan](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/implementation_plan.md)
|
||||
- [Walkthrough](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/walkthrough.md)
|
||||
|
||||
## Change Log
|
||||
|
||||
| Date | Change | Author |
|
||||
|------|--------|--------|
|
||||
| 2026-02-04 | Removed fallback URI construction logic | System |
|
||||
| 2026-02-04 | Added error logging for missing URI schemes | System |
|
||||
| 2026-02-04 | Created troubleshooting runbook | System |
|
||||
@ -1,18 +0,0 @@
|
||||
# Security Scrubbing Skill
|
||||
|
||||
This skill provides a standardized workflow for identifying and removing sensitive information (passwords, tokens, keys) from Git history using `git filter-repo` and `gitleaks`.
|
||||
|
||||
## Guidelines
|
||||
|
||||
1. **Identification**: Always run `gitleaks detect -v` first to identify the scope of exposed secrets.
|
||||
2. **Replacement Plan**: Create a mapping file (e.g., `expressions.txt`) using the format `old_value==>new_value`.
|
||||
3. **Execution**: Use `git filter-repo --replace-text expressions.txt --force` to rewrite history.
|
||||
4. **Verification**: Re-run `gitleaks` to ensure zero leaks remain.
|
||||
5. **Synchronization**: Force-push to all remote remotes (`git push origin <branch> --force`). Update all collaborators.
|
||||
6. **Archiving**: Log the operation in the project's Runbook directory with a timestamped record.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Always use descriptive placeholders like `YOUR_PASSWORD` or `MFA_SECRET_PLACEHOLDER`.
|
||||
- Scan all branches, not just the active one, if secrets were historically committed elsewhere.
|
||||
- Notify the team immediately after a force-push as it breaks local clones.
|
||||
@ -1,40 +0,0 @@
|
||||
---
|
||||
name: UI Design Standards
|
||||
description: Rules and guidelines for consistent UI components including dropdowns, popovers, and list items.
|
||||
---
|
||||
|
||||
# UI Design Standards
|
||||
|
||||
This skill provides guidelines for building consistent, accessible, and high-quality UI components within the codebase.
|
||||
|
||||
## 1. Popovers and Dropdowns (弹窗与下拉规范)
|
||||
|
||||
### Alignment (定位逻辑)
|
||||
- **Right Edge Alignment**: For triggers located on the right side of the screen or navigation bar, the associated dropdown/popover MUST be right-aligned with the trigger.
|
||||
- In Radix UI / shadcn: Use `align="end"`.
|
||||
- In CSS/Tailwind: Use `absolute right-0`.
|
||||
- **Vertical Spacing**: Maintain a consistent vertical offset from the trigger, typically `8px` (`mt-2` or `sideOffset={8}`).
|
||||
|
||||
### Layering and Visual Hierarchy (层级管理)
|
||||
- **Z-Index**: Always explicitly declare a `z-index` of `50` or higher for floating UI elements to ensure they appear above all other content.
|
||||
- **Background Integrity**: Drodown containers should use opaque background colors (e.g., `bg-white` or theme-defined `bg-surface`) to prevent "see-through" visual noise from underlying content. Avoid excessive transparency/blur if it compromises readability.
|
||||
- **Shadows**: Use distinct shadows (e.g., `shadow-md` or `shadow-xl`) to provide depth.
|
||||
|
||||
### Clipping Prevention (溢出处理)
|
||||
- Use Portals (e.g., `DropdownMenu.Portal`) to render floating content into the `document.body`. This prevents the menu from being clipped by parents with `overflow: hidden`.
|
||||
|
||||
## 2. List Items and Components (列表项规范)
|
||||
|
||||
### Layout and Alignment (对齐规范)
|
||||
- **Flexbox**: Use `flex items-center` for all list items that include both an icon and a text label.
|
||||
- **Icon Spacing**: Maintain a standard horizontal gap between icons and labels (recommended: `gap-3` or `12px`).
|
||||
|
||||
### Component Integrity (防压缩与自适应)
|
||||
- **Icon Shrinking**: Icons within flex containers MUST have `flex-shrink: 0` (Tailwind: `shrink-0`) to prevent them from distorting when the container is narrow.
|
||||
- **Minimum Width**: Containers displaying variable-length text (like user emails) should have a reasonable `min-width` (e.g., `min-w-[200px]`) to ensure comfortable display.
|
||||
|
||||
## 3. Interaction and Accessibility
|
||||
|
||||
- **Keyboard Support**: Ensure dropdowns support `Esc` to close and allow keyboard navigation (Tab/Arrows).
|
||||
- **Outside Clicks**: Implement "click outside to close" logic for all popovers.
|
||||
- **Motion**: Use subtle entry/exit animations (e.g., 120ms fade and scale). Respect `prefers-reduced-motion`.
|
||||
12
.cursorrules
12
.cursorrules
@ -1,12 +0,0 @@
|
||||
# UI Development Rules
|
||||
|
||||
## 1. 弹窗与下拉规范 (Popovers & Dropdowns)
|
||||
- **定位逻辑**:除非特殊说明,右上角的触发器对应的弹出层必须强制 `right-0` (或 `align="end"`) 对齐。
|
||||
- **层级管理**:所有 Floating UI 必须显式声明 `z-[50+]` 以防被 Content Card 遮挡或透视。
|
||||
- **背景显示**:下拉容器背景应使用不透明色(如 `bg-white` 或主题定义的 `bg-surface`),避免透明度导致的视觉干扰。
|
||||
|
||||
## 2. 列表项规范 (List Items)
|
||||
- **对齐**:所有带有图标的列表项必须使用 `flex items-center`。
|
||||
- **防止重叠**:图标与文字之间应保持足够的间距(推荐 `gap-3` 或 `12px`)。
|
||||
- **防压缩**:图标必须带有 `flex-shrink: 0` (或 Tailwind 的 `shrink-0`),防止在容器宽度不足时图标变形。
|
||||
- **最小宽度**:涉及用户信息的容器应设置合理的 `min-width` (如 `min-w-[200px]`) 以保证长文字内容正常显示。
|
||||
@ -1,15 +0,0 @@
|
||||
.git
|
||||
.github
|
||||
.next
|
||||
.contentlayer
|
||||
node_modules
|
||||
coverage
|
||||
dist
|
||||
build
|
||||
test-results
|
||||
*.log
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
deploy/single-node/.env.runtime
|
||||
knowledge/.git
|
||||
74
.env.example
74
.env.example
@ -1,74 +0,0 @@
|
||||
# Frontend site base URLs
|
||||
APP_BASE_URL=
|
||||
NEXT_PUBLIC_APP_BASE_URL=
|
||||
NEXT_PUBLIC_SITE_URL=
|
||||
NEXT_PUBLIC_LOGIN_URL=
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=
|
||||
DOCS_SERVICE_URL=https://docs.svc.plus
|
||||
DOCS_SERVICE_INTERNAL_URL=
|
||||
SESSION_COOKIE_SECURE=true
|
||||
NEXT_PUBLIC_SESSION_COOKIE_SECURE=true
|
||||
RUNTIME_HOSTNAME=
|
||||
NEXT_RUNTIME_HOSTNAME=
|
||||
DEPLOYMENT_HOSTNAME=
|
||||
RUNTIME_ENV=prod
|
||||
REGION=cn
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=prod
|
||||
NEXT_PUBLIC_RUNTIME_REGION=cn
|
||||
|
||||
# Upstream service endpoints
|
||||
# Use root service origins only. Do not point ACCOUNT_SERVICE_URL at console.svc.plus
|
||||
# and do not include /api/auth or any other path suffix here.
|
||||
ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
NEXT_PUBLIC_ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
SERVER_SERVICE_URL=https://api.svc.plus
|
||||
NEXT_PUBLIC_SERVER_SERVICE_URL=https://api.svc.plus
|
||||
SERVER_SERVICE_INTERNAL_URL=
|
||||
|
||||
# XWorkmate bridge runtime
|
||||
# Read server-side by /api/xworkmate/bridge. Do not expose the token as NEXT_PUBLIC_*.
|
||||
BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus
|
||||
BRIDGE_AUTH_TOKEN=
|
||||
|
||||
# OpenClaw assistant integrations
|
||||
# Use environment variables to prefill the assistant and integrations page.
|
||||
# Values are read server-side and are not hardcoded into the UI.
|
||||
OPENCLAW_GATEWAY_REMOTE_URL=
|
||||
OPENCLAW_GATEWAY_TOKEN=
|
||||
VAULT_SERVER_URL=
|
||||
VAULT_NAMESPACE=
|
||||
VAULT_TOKEN=
|
||||
APISIX_AI_GATEWAY_URL=
|
||||
AI_GATEWAY_ACCESS_TOKEN=
|
||||
|
||||
# Giscus Configuration (GitHub Discussions Integration)
|
||||
# See https://giscus.app to generate these values
|
||||
NEXT_PUBLIC_GISCUS_REPO=cloud-neutral-toolkit/console.svc.plus
|
||||
NEXT_PUBLIC_GISCUS_REPO_ID=R_kgDOQoiZ_g
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY=General
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY_ID=DIC_kwDOQoiZ_s4Clj_q
|
||||
|
||||
# Internal service token used to read aggregate counts from accounts.svc.plus (/api/internal/public-overview)
|
||||
INTERNAL_SERVICE_TOKEN=
|
||||
|
||||
# Cloudflare Web Analytics GraphQL credentials
|
||||
CLOUDFLARE_DNS_API_TOKEN=
|
||||
CLOUDFLARE_DNS_ZONE_TAG=
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
|
||||
CLOUDFLARE_ZONE_TAG=
|
||||
|
||||
# Root email whitelist for privileged user-creation actions (comma-separated)
|
||||
# Default: admin@svc.plus
|
||||
ROOT_EMAIL_WHITELIST=admin@svc.plus
|
||||
|
||||
# Stripe public price ids used by /prices, product pages, and /panel/subscription
|
||||
# These values are safe to expose to the browser. Use Stripe test-mode price ids for local/dev.
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=
|
||||
37
.github/actions/auto-tag/action.yml
vendored
Normal file
37
.github/actions/auto-tag/action.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: "Cloud-Neutral Auto Tag"
|
||||
description: "Generate Docker tags for main, release, PR and dev branches"
|
||||
inputs:
|
||||
image:
|
||||
description: "Base image name (e.g. ghcr.io/.../image)"
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
tags:
|
||||
description: "Generated Docker tags"
|
||||
value: ${{ steps.meta.outputs.tags }}
|
||||
labels:
|
||||
description: "Generated Docker labels"
|
||||
value: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Generate metadata (auto tags)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ inputs.image }}
|
||||
|
||||
tags: |
|
||||
# main → latest
|
||||
type=raw,enable=${{ github.ref == 'refs/heads/main' }},value=latest
|
||||
|
||||
# release tag(v1.2.3)
|
||||
type=ref,event=tag
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
# PR → pr-123
|
||||
type=raw,enable=${{ startsWith(github.ref, 'refs/pull/') }},value=pr-${{ github.event.pull_request.number }}
|
||||
|
||||
# dev/feature branches → branch name
|
||||
type=ref,event=branch
|
||||
90
.github/actions/build/action.yml
vendored
Normal file
90
.github/actions/build/action.yml
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
name: Build
|
||||
description: Build artifacts for each service and platform with optional container publishing.
|
||||
inputs:
|
||||
service:
|
||||
description: Target service name
|
||||
required: true
|
||||
platform:
|
||||
description: Target platform (e.g., linux/amd64)
|
||||
required: true
|
||||
environment:
|
||||
description: Deployment environment (dev or prod)
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare matrix context
|
||||
id: matrix
|
||||
uses: ../matrix-support
|
||||
with:
|
||||
service: ${{ inputs.service }}
|
||||
platform: ${{ inputs.platform }}
|
||||
environment: ${{ inputs.environment }}
|
||||
enable_docker: 'true'
|
||||
|
||||
- name: Cache build artifacts
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
build/${{ inputs.service }}
|
||||
dashboard/.next
|
||||
key: build-${{ inputs.service }}-${{ inputs.platform }}-${{ hashFiles('**/go.sum', 'dashboard/yarn.lock') }}-${{ inputs.environment }}
|
||||
restore-keys: |
|
||||
build-${{ inputs.service }}-${{ inputs.platform }}-
|
||||
build-${{ inputs.service }}-
|
||||
|
||||
- name: Prepare Go toolchain
|
||||
if: inputs.service != 'dashboard'
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache: true
|
||||
|
||||
- name: Build Go binaries
|
||||
if: inputs.service != 'dashboard'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
goos="${{ steps.matrix.outputs.goos }}"
|
||||
goarch="${{ steps.matrix.outputs.goarch }}"
|
||||
mkdir -p build/${{ inputs.service }}/"${goos}-${goarch}"
|
||||
declare -a targets
|
||||
if [[ "${{ inputs.service }}" == "rag-server" ]]; then
|
||||
targets=("rag-server/cmd/xcontrol-server" "rag-server/cmd/rag-server-cli")
|
||||
elif [[ "${{ inputs.service }}" == "account" ]]; then
|
||||
targets=("account/cmd/accountsvc")
|
||||
else
|
||||
targets=("./...")
|
||||
fi
|
||||
for target in "${targets[@]}"; do
|
||||
binary_name=$(basename "$target")
|
||||
GOOS="$goos" GOARCH="$goarch" go build -o build/${{ inputs.service }}/"${goos}-${goarch}"/"${binary_name}" "$target"
|
||||
done
|
||||
|
||||
- name: Upload Go artifacts
|
||||
if: inputs.service != 'dashboard'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.service }}-${{ inputs.platform }}-${{ inputs.environment }}
|
||||
path: build/${{ inputs.service }}/
|
||||
|
||||
- name: Install dashboard dependencies
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Build dashboard
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
env:
|
||||
NEXT_PUBLIC_ENV: ${{ inputs.environment }}
|
||||
run: yarn build
|
||||
|
||||
- name: Upload dashboard build output
|
||||
if: inputs.service == 'dashboard'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dashboard-${{ inputs.platform }}-${{ inputs.environment }}
|
||||
path: dashboard/.next
|
||||
53
.github/actions/code-quality/action.yml
vendored
Normal file
53
.github/actions/code-quality/action.yml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: Code Quality
|
||||
description: Run linting and basic quality checks per service/platform/environment matrix entry.
|
||||
inputs:
|
||||
service:
|
||||
description: Target service name
|
||||
required: true
|
||||
platform:
|
||||
description: Target platform (e.g., linux/amd64)
|
||||
required: true
|
||||
environment:
|
||||
description: Deployment environment (dev or prod)
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare matrix context
|
||||
id: matrix
|
||||
uses: ./.github/actions/matrix-support
|
||||
with:
|
||||
service: ${{ inputs.service }}
|
||||
platform: ${{ inputs.platform }}
|
||||
environment: ${{ inputs.environment }}
|
||||
|
||||
- name: Install git-secrets
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git clone https://github.com/awslabs/git-secrets.git
|
||||
sudo make install -C git-secrets
|
||||
git secrets --install
|
||||
git secrets --scan
|
||||
|
||||
- name: Go vet
|
||||
if: inputs.service != 'dashboard'
|
||||
shell: bash
|
||||
run: go vet ./...
|
||||
|
||||
- name: Go unit tests (quality gate)
|
||||
if: inputs.service != 'dashboard'
|
||||
shell: bash
|
||||
run: go test ./...
|
||||
|
||||
- name: Install dashboard dependencies
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Dashboard lint
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
run: yarn lint
|
||||
48
.github/actions/deploy/action.yml
vendored
Normal file
48
.github/actions/deploy/action.yml
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
name: Deploy
|
||||
description: Coordinate deployments per service/environment.
|
||||
inputs:
|
||||
service:
|
||||
description: Target service name
|
||||
required: true
|
||||
platform:
|
||||
description: Target platform (e.g., linux/amd64)
|
||||
required: true
|
||||
environment:
|
||||
description: Deployment environment (dev or prod)
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare matrix context
|
||||
id: matrix
|
||||
uses: ./.github/actions/matrix-support
|
||||
with:
|
||||
service: ${{ inputs.service }}
|
||||
platform: ${{ inputs.platform }}
|
||||
environment: ${{ inputs.environment }}
|
||||
|
||||
- name: Prepare rollout context
|
||||
id: context
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "service=${{ inputs.service }}" >> "$GITHUB_OUTPUT"
|
||||
echo "environment=${{ inputs.environment }}" >> "$GITHUB_OUTPUT"
|
||||
echo "platform=${{ inputs.platform }}" >> "$GITHUB_OUTPUT"
|
||||
echo "release_channel=${{ steps.matrix.outputs.is_prod == 'true' && 'prod' || 'dev' }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Deploy placeholder
|
||||
shell: bash
|
||||
env:
|
||||
TARGET_ENV: ${{ steps.context.outputs.environment }}
|
||||
TARGET_SERVICE: ${{ steps.context.outputs.service }}
|
||||
TARGET_PLATFORM: ${{ steps.context.outputs.platform }}
|
||||
RELEASE_CHANNEL: ${{ steps.context.outputs.release_channel }}
|
||||
run: |
|
||||
echo "Deploying ${TARGET_SERVICE} (${TARGET_PLATFORM}) to ${TARGET_ENV} namespace via ${RELEASE_CHANNEL} rollout"
|
||||
echo "Hook in Helm/kubectl/ArgoCD rollouts here"
|
||||
|
||||
- name: Rollback plan
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Rollback can be re-run per matrix entry by dispatching with allow_deploy=true"
|
||||
106
.github/actions/matrix-support/action.yml
vendored
Normal file
106
.github/actions/matrix-support/action.yml
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
name: Matrix Support
|
||||
description: Common setup for matrix-driven workflows with language and cache bootstrapping.
|
||||
inputs:
|
||||
service:
|
||||
description: Target service name
|
||||
required: true
|
||||
platform:
|
||||
description: Target platform (e.g., linux/amd64)
|
||||
required: true
|
||||
environment:
|
||||
description: Deployment environment (dev or prod)
|
||||
required: true
|
||||
enable_docker:
|
||||
description: Enable Docker buildx/QEMU setup
|
||||
required: false
|
||||
default: 'false'
|
||||
outputs:
|
||||
goos:
|
||||
description: Derived GOOS from the platform input
|
||||
value: ${{ steps.platforms.outputs.goos }}
|
||||
goarch:
|
||||
description: Derived GOARCH from the platform input
|
||||
value: ${{ steps.platforms.outputs.goarch }}
|
||||
is_prod:
|
||||
description: Whether the environment is prod or the ref is a tag
|
||||
value: ${{ steps.flags.outputs.is_prod }}
|
||||
target_platforms:
|
||||
description: Platform list for builds (single in dev, multi-arch in prod)
|
||||
value: ${{ steps.flags.outputs.target_platforms }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Derive platform matrix values
|
||||
id: platforms
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
platform="${{ inputs.platform }}"
|
||||
goos="${platform%%/*}"
|
||||
goarch="${platform##*/}"
|
||||
echo "goos=${goos}" >> "$GITHUB_OUTPUT"
|
||||
echo "goarch=${goarch}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve environment flags
|
||||
id: flags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${{ inputs.environment }}" == "prod" || "${GITHUB_REF_TYPE:-}" == "tag" ]]; then
|
||||
echo "is_prod=true" >> "$GITHUB_OUTPUT"
|
||||
echo "target_platforms=linux/amd64,linux/arm64" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_prod=false" >> "$GITHUB_OUTPUT"
|
||||
echo "target_platforms=${{ inputs.platform }}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Set up Go
|
||||
if: inputs.service != 'dashboard'
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache: true
|
||||
|
||||
- name: Cache Go build data
|
||||
if: inputs.service != 'dashboard'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: go-${{ inputs.service }}-${{ inputs.platform }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
go-${{ inputs.service }}-${{ inputs.platform }}-
|
||||
go-${{ inputs.service }}-
|
||||
|
||||
- name: Set up Node.js
|
||||
if: inputs.service == 'dashboard'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
cache-dependency-path: dashboard/yarn.lock
|
||||
|
||||
- name: Cache dashboard artifacts
|
||||
if: inputs.service == 'dashboard'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
dashboard/.next/cache
|
||||
~/.cache/yarn
|
||||
key: dashboard-${{ inputs.platform }}-${{ hashFiles('dashboard/yarn.lock') }}
|
||||
restore-keys: |
|
||||
dashboard-${{ inputs.platform }}-
|
||||
dashboard-
|
||||
|
||||
- name: Enable Docker build tooling
|
||||
if: inputs.enable_docker == 'true'
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up buildx
|
||||
if: inputs.enable_docker == 'true'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
76
.github/actions/security/action.yml
vendored
Normal file
76
.github/actions/security/action.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
name: Security
|
||||
description: Security scanning per service/platform/environment.
|
||||
inputs:
|
||||
service:
|
||||
description: Target service name
|
||||
required: true
|
||||
platform:
|
||||
description: Target platform (e.g., linux/amd64)
|
||||
required: true
|
||||
environment:
|
||||
description: Deployment environment (dev or prod)
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare matrix context
|
||||
id: matrix
|
||||
uses: ./.github/actions/matrix-support
|
||||
with:
|
||||
service: ${{ inputs.service }}
|
||||
platform: ${{ inputs.platform }}
|
||||
environment: ${{ inputs.environment }}
|
||||
|
||||
- name: Run golangci-lint
|
||||
if: inputs.service != 'dashboard'
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: ./...
|
||||
|
||||
- name: Install gosec
|
||||
if: inputs.service != 'dashboard'
|
||||
shell: bash
|
||||
run: go install github.com/securego/gosec/v2/cmd/gosec@latest
|
||||
|
||||
- name: Run gosec
|
||||
if: inputs.service != 'dashboard'
|
||||
shell: bash
|
||||
run: gosec ./...
|
||||
|
||||
- name: Trivy filesystem scan
|
||||
if: inputs.service != 'dashboard'
|
||||
uses: aquasecurity/trivy-action@0.24.0
|
||||
with:
|
||||
scan-type: fs
|
||||
scan-ref: .
|
||||
severity: HIGH,CRITICAL
|
||||
ignore-unfixed: true
|
||||
format: table
|
||||
exit-code: "0"
|
||||
|
||||
- name: Install dashboard dependencies
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Run ESLint
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
run: yarn lint
|
||||
|
||||
- name: Semgrep security rules
|
||||
if: inputs.service == 'dashboard'
|
||||
uses: returntocorp/semgrep-action@v1
|
||||
with:
|
||||
config: p/ci
|
||||
paths: dashboard
|
||||
|
||||
- name: npm audit (production)
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
run: npm audit --production
|
||||
continue-on-error: true
|
||||
52
.github/actions/test/action.yml
vendored
Normal file
52
.github/actions/test/action.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
name: Test
|
||||
description: Run service-specific tests.
|
||||
inputs:
|
||||
service:
|
||||
description: Target service name
|
||||
required: true
|
||||
platform:
|
||||
description: Target platform (e.g., linux/amd64)
|
||||
required: true
|
||||
environment:
|
||||
description: Deployment environment (dev or prod)
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepare matrix context
|
||||
id: matrix
|
||||
uses: ./.github/actions/matrix-support
|
||||
with:
|
||||
service: ${{ inputs.service }}
|
||||
platform: ${{ inputs.platform }}
|
||||
environment: ${{ inputs.environment }}
|
||||
|
||||
- name: Run Go integration tests
|
||||
if: inputs.service != 'dashboard'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
go test ./... -run Integration -count=1
|
||||
|
||||
- name: Install dashboard dependencies
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Run dashboard unit tests
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
env:
|
||||
NODE_ENV: ${{ inputs.environment }}
|
||||
run: yarn test:unit
|
||||
|
||||
- name: Run dashboard e2e tests
|
||||
if: inputs.service == 'dashboard'
|
||||
working-directory: dashboard
|
||||
shell: bash
|
||||
env:
|
||||
PORT: 3100
|
||||
NODE_ENV: ${{ inputs.environment }}
|
||||
run: yarn test:e2e
|
||||
9
.github/scripts/cosign/sign.sh
vendored
Executable file
9
.github/scripts/cosign/sign.sh
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
REG="ghcr.io/cloud-neutral-toolkit"
|
||||
|
||||
cosign sign --yes "$REG/node-builder@$NODE_BUILDER_DIGEST"
|
||||
cosign sign --yes "$REG/node-runtime@$NODE_RUNTIME_DIGEST"
|
||||
cosign sign --yes "$REG/openresty-geoip@$OPENRESTY_GEOIP_DIGEST"
|
||||
cosign sign --yes "$REG/postgres-runtime@$POSTGRES_RUNTIME_DIGEST"
|
||||
28
.github/scripts/metadata/gen.py
vendored
Executable file
28
.github/scripts/metadata/gen.py
vendored
Executable file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
import json, sys
|
||||
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: gen.py <image-name> <digest> <tags>")
|
||||
sys.exit(1)
|
||||
|
||||
name = sys.argv[1]
|
||||
digest = sys.argv[2]
|
||||
raw_tags = sys.argv[3]
|
||||
tags = raw_tags.splitlines()
|
||||
|
||||
preferred = next((t for t in tags if t.endswith(":latest")), tags[0] if tags else "")
|
||||
|
||||
metadata = {
|
||||
"name": name,
|
||||
"digest": digest,
|
||||
"tags": tags,
|
||||
"preferred_tag": preferred,
|
||||
"image": f"ghcr.io/cloud-neutral-toolkit/{name}",
|
||||
"image_with_digest": f"ghcr.io/cloud-neutral-toolkit/{name}@{digest}",
|
||||
}
|
||||
|
||||
outfile = f"image-metadata-{name}.json"
|
||||
with open(outfile, "w", encoding="utf-8") as f:
|
||||
json.dump(metadata, f, indent=2)
|
||||
|
||||
print(f"[metadata] Wrote: {outfile}")
|
||||
7
.github/scripts/sbom/generate.sh
vendored
Executable file
7
.github/scripts/sbom/generate.sh
vendored
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
IMAGE="$1"
|
||||
OUT="$2"
|
||||
|
||||
anchore-cli sbom generate "$IMAGE" -o "$OUT"
|
||||
15
.github/scripts/utils/preferred-tag.sh
vendored
Executable file
15
.github/scripts/utils/preferred-tag.sh
vendored
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
tags="$1"
|
||||
preferred=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" == *":latest" ]] && preferred="$line" && break
|
||||
done <<< "$tags"
|
||||
|
||||
if [[ -z "$preferred" ]]; then
|
||||
preferred="$(echo "$tags" | head -n 1)"
|
||||
fi
|
||||
|
||||
echo "$preferred"
|
||||
296
.github/workflows/build-images.yml
vendored
Normal file
296
.github/workflows/build-images.yml
vendored
Normal file
@ -0,0 +1,296 @@
|
||||
name: Build Dashboard Images
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
push_images:
|
||||
description: "Push service images instead of local builds"
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
dockerhub_namespace:
|
||||
description: "Docker Hub namespace (user/org)"
|
||||
type: string
|
||||
|
||||
skip_security:
|
||||
description: "Skip security scans and signing"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
node_builder_image:
|
||||
type: string
|
||||
default: "node:22-bookworm"
|
||||
|
||||
node_runtime_image:
|
||||
type: string
|
||||
default: "node:22-slim"
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
push_images:
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
dockerhub_namespace:
|
||||
description: "Docker Hub namespace (user/org)"
|
||||
type: string
|
||||
default: "cloudneutral"
|
||||
|
||||
skip_security:
|
||||
description: "Skip security scans and signing"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
node_builder_image:
|
||||
type: string
|
||||
default: "node:22-bookworm"
|
||||
|
||||
node_runtime_image:
|
||||
type: string
|
||||
default: "node:22-slim"
|
||||
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
# ✅ 不硬编码:默认推到 ghcr.io/<当前仓库 owner>/...
|
||||
ORG: ${{ github.repository_owner }}
|
||||
|
||||
SKIP_SECURITY: ${{ inputs.skip_security || github.event.inputs.skip_security || 'false' }}
|
||||
|
||||
NODE_BUILDER_IMAGE: ${{ inputs.node_builder_image || github.event.inputs.node_builder_image || 'node:22-bookworm' }}
|
||||
NODE_RUNTIME_IMAGE: ${{ inputs.node_runtime_image || github.event.inputs.node_runtime_image || 'node:22-slim' }}
|
||||
|
||||
PUSH_IMAGES: ${{ github.event_name == 'push'
|
||||
|| (github.event_name == 'workflow_call' && inputs.push_images)
|
||||
|| (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true') }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- { platform: linux/amd64, artifact: linux-amd64 }
|
||||
- { platform: linux/arm64, artifact: linux-arm64 }
|
||||
service:
|
||||
- { name: dashboard, workdir: ., dockerfile: Dockerfile }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate Auto Tags
|
||||
id: meta
|
||||
uses: ./.github/actions/auto-tag
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}
|
||||
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Clone knowledge content
|
||||
run: git clone https://github.com/Cloud-Neutral-Workshop/knowledge.git knowledge
|
||||
|
||||
- name: Build Service Image (per-arch)
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ matrix.service.workdir }}
|
||||
file: ${{ matrix.service.dockerfile }}
|
||||
platforms: ${{ matrix.arch.platform }}
|
||||
push: ${{ env.PUSH_IMAGES }}
|
||||
tags: |
|
||||
${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}:build-${{ github.sha }}-${{ matrix.arch.artifact }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
GO_RUNTIME_IMAGE=${{ env.GO_RUNTIME_IMAGE }}
|
||||
NODE_BUILDER_IMAGE=${{ env.NODE_BUILDER_IMAGE }}
|
||||
NODE_RUNTIME_IMAGE=${{ env.NODE_RUNTIME_IMAGE }}
|
||||
CONTENTLAYER_BUILD=true
|
||||
|
||||
- name: Record digest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "${{ steps.build.outputs.digest }}" > digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}.txt
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
|
||||
path: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}.txt
|
||||
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: ${{ (github.event_name == 'push' || inputs.push_images == true || github.event.inputs.push_images == 'true') && !((inputs.skip_security == true) || (github.event.inputs.skip_security == 'true')) }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- { platform: linux/amd64, artifact: linux-amd64 }
|
||||
- { platform: linux/arm64, artifact: linux-arm64 }
|
||||
service:
|
||||
- { name: dashboard }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
|
||||
|
||||
- name: Load image digest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "IMAGE_DIGEST=$(cat digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}.txt)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set image ref
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "IMG=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}:build-${{ github.sha }}-${{ matrix.arch.artifact }}@${{ env.IMAGE_DIGEST }}" >> "$GITHUB_ENV"
|
||||
|
||||
- uses: anchore/sbom-action@v0
|
||||
with:
|
||||
image: ${{ env.IMG }}
|
||||
output-file: sbom.spdx.json
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sbom-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
|
||||
path: sbom.spdx.json
|
||||
|
||||
- uses: aquasecurity/trivy-action@0.28.0
|
||||
with:
|
||||
image-ref: ${{ env.IMG }}
|
||||
severity: HIGH,CRITICAL
|
||||
exit-code: '1'
|
||||
|
||||
- uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
cosign-release: 'v2.4.1'
|
||||
|
||||
- name: Cosign Sign Image
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: "true"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cosign sign --yes "${{ env.IMG }}"
|
||||
|
||||
push:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
- security
|
||||
if: ${{ needs.build.result == 'success' && (github.event_name == 'push' || inputs.push_images == true || github.event.inputs.push_images == 'true') && ((inputs.skip_security == true) || (github.event.inputs.skip_security == 'true') || (needs.security.result == 'success')) }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
registry:
|
||||
- ghcr.io
|
||||
- docker.io
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: digest-dashboard-linux-amd64
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: digest-dashboard-linux-arm64
|
||||
|
||||
- name: Load digests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "AMD_DIGEST=$(cat digest-dashboard-linux-amd64.txt)" >> "$GITHUB_ENV"
|
||||
echo "ARM_DIGEST=$(cat digest-dashboard-linux-arm64.txt)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Generate Auto Tags
|
||||
id: meta
|
||||
uses: ./.github/actions/auto-tag
|
||||
with:
|
||||
image: ${{ env.REGISTRY }}/${{ env.ORG }}/dashboard
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
if: matrix.registry == 'ghcr.io'
|
||||
with:
|
||||
registry: ${{ matrix.registry }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create & Push Multi-Arch Manifests (GHCR)
|
||||
if: matrix.registry == 'ghcr.io'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
SRC_AMD="${{ env.REGISTRY }}/${{ env.ORG }}/dashboard:build-${{ github.sha }}-linux-amd64@${{ env.AMD_DIGEST }}"
|
||||
SRC_ARM="${{ env.REGISTRY }}/${{ env.ORG }}/dashboard:build-${{ github.sha }}-linux-arm64@${{ env.ARM_DIGEST }}"
|
||||
|
||||
FIRST_TAG=""
|
||||
echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n' | while read -r TAG; do
|
||||
[ -z "$TAG" ] && continue
|
||||
if [ -z "$FIRST_TAG" ]; then FIRST_TAG="$TAG"; fi
|
||||
docker buildx imagetools create -t "$TAG" "$SRC_AMD" "$SRC_ARM"
|
||||
done
|
||||
|
||||
TAG1="$(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n' | head -n 1)"
|
||||
DIGEST="$(docker buildx imagetools inspect "$TAG1" --format '{{.Digest}}')"
|
||||
echo "MANIFEST_DIGEST=$DIGEST" >> "$GITHUB_ENV"
|
||||
echo "FINAL_TAG=$TAG1" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Clone knowledge content
|
||||
if: matrix.registry == 'ghcr.io'
|
||||
run: git clone https://github.com/Cloud-Neutral-Workshop/knowledge.git knowledge
|
||||
|
||||
- name: Validate blog content mount
|
||||
if: matrix.registry == 'ghcr.io'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.ORG }}/dashboard@${{ env.MANIFEST_DIGEST }}"
|
||||
docker pull "$IMAGE"
|
||||
docker run --rm \
|
||||
-v "${{ github.workspace }}/knowledge/content:/app/dashboard/src/content/blog:ro" \
|
||||
"$IMAGE" \
|
||||
sh -c 'test -d /app/dashboard/src/content/blog'
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: matrix.registry == 'docker.io'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Copy Multi-Arch Image to Docker Hub (skopeo)
|
||||
if: matrix.registry == 'docker.io'
|
||||
env:
|
||||
TARGET_NS: ${{ inputs.dockerhub_namespace || github.event.inputs.dockerhub_namespace || 'cloudneutral' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y skopeo
|
||||
|
||||
SRC="docker://ghcr.io/${{ env.ORG }}/dashboard@${{ env.MANIFEST_DIGEST }}"
|
||||
DST="docker://docker.io/${TARGET_NS}/dashboard:latest"
|
||||
|
||||
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
|
||||
skopeo login docker.io -u "${{ secrets.DOCKERHUB_USERNAME }}" -p "${{ secrets.DOCKERHUB_TOKEN }}"
|
||||
|
||||
skopeo copy --all "$SRC" "$DST"
|
||||
49
.github/workflows/check-image.yaml
vendored
Normal file
49
.github/workflows/check-image.yaml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: Check XControl Image Ready
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
required: false
|
||||
default: latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Authenticate to GHCR
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "$GITHUB_TOKEN" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
||||
|
||||
- name: Check images exist and are pullable
|
||||
env:
|
||||
TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
IMAGES=(
|
||||
"ghcr.io/cloud-neutral-toolkit/openresty-geoip"
|
||||
"ghcr.io/cloud-neutral-toolkit/postgres-runtime"
|
||||
"ghcr.io/cloud-neutral-toolkit/account"
|
||||
"ghcr.io/cloud-neutral-toolkit/dashboard"
|
||||
"ghcr.io/cloud-neutral-toolkit/rag-server"
|
||||
"ghcr.io/cloud-neutral-toolkit/xcontrol-init"
|
||||
"docker.io/cloudneutral/openresty-geoip"
|
||||
"docker.io/cloudneutral/postgres-runtime"
|
||||
"docker.io/cloudneutral/account"
|
||||
"docker.io/cloudneutral/dashboard"
|
||||
"docker.io/cloudneutral/rag-server"
|
||||
"docker.io/cloudneutral/xcontrol-init"
|
||||
)
|
||||
|
||||
for IMAGE in "${IMAGES[@]}"; do
|
||||
echo "Checking ${IMAGE}:${TAG}"
|
||||
docker manifest inspect "${IMAGE}:${TAG}" > /dev/null
|
||||
docker pull "${IMAGE}:${TAG}" > /dev/null
|
||||
done
|
||||
240
.github/workflows/pipeline.yaml
vendored
240
.github/workflows/pipeline.yaml
vendored
@ -1,240 +0,0 @@
|
||||
name: Console Service Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/pipeline.yaml"
|
||||
- "Dockerfile"
|
||||
- "deploy/single-node/**"
|
||||
- "package.json"
|
||||
- "yarn.lock"
|
||||
- "scripts/github-actions/build-and-push-frontend-image.sh"
|
||||
- "scripts/github-actions/compute-frontend-release-metadata.sh"
|
||||
- "scripts/github-actions/render-frontend-build-args.sh"
|
||||
- "scripts/github-actions/render-frontend-runtime-env.sh"
|
||||
- "scripts/github-actions/prepare-frontend-build-context.sh"
|
||||
- "scripts/github-actions/run-console-deploy-playbook.sh"
|
||||
- "scripts/github-actions/run-cloudflare-svc-plus-dns-playbook.sh"
|
||||
- "scripts/github-actions/verify-frontend-release-over-ssh.sh"
|
||||
- "scripts/github-actions/verify-frontend-release.sh"
|
||||
- "scripts/prebuild.sh"
|
||||
- "contentlayer.config.ts"
|
||||
- "next.config.js"
|
||||
- "next.config.mjs"
|
||||
- "src/**"
|
||||
- "public/**"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target_host:
|
||||
description: Ansible host or alias
|
||||
required: false
|
||||
default: "jp-xhttp-contabo.svc.plus"
|
||||
type: string
|
||||
run_apply:
|
||||
description: Apply deployment
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
concurrency:
|
||||
group: console-pipeline-${{ github.ref_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CANONICAL_DOMAIN: www.svc.plus
|
||||
SERVED_DOMAINS: www.svc.plus,console.svc.plus
|
||||
APP_BASE_URL: https://www.svc.plus
|
||||
NEXT_PUBLIC_APP_BASE_URL: https://www.svc.plus
|
||||
NEXT_PUBLIC_SITE_URL: https://www.svc.plus
|
||||
RUNTIME_HOSTNAME: www.svc.plus
|
||||
NEXT_RUNTIME_HOSTNAME: www.svc.plus
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT: prod
|
||||
NEXT_PUBLIC_RUNTIME_REGION: cn
|
||||
ACCOUNT_SERVICE_URL: https://accounts.svc.plus
|
||||
CLOUDFLARE_ZONE_TAG: bf3427f83a2c52c8285ab3d741a6ee27
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG: 0973e84ec8872c67c570f8072e92e21b
|
||||
CLOUDFLARE_ACCOUNT_ID: e71be5efb76a6c54f78f008da4404f00
|
||||
GHCR_REGISTRY: ghcr.io
|
||||
GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }}
|
||||
GHCR_PASSWORD: ${{ secrets.GHCR_TOKEN }}
|
||||
INTERNAL_SERVICE_TOKEN: ${{ secrets.INTERNAL_SERVICE_TOKEN }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
|
||||
|
||||
jobs:
|
||||
prep:
|
||||
name: Prep
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
target_host: ${{ steps.inputs.outputs.target_host }}
|
||||
run_apply: ${{ steps.inputs.outputs.run_apply }}
|
||||
image_tag: ${{ steps.metadata.outputs.image_tag }}
|
||||
image_ref: ${{ steps.metadata.outputs.image_ref }}
|
||||
image_latest_ref: ${{ steps.metadata.outputs.image_latest_ref }}
|
||||
ghcr_namespace: ${{ steps.metadata.outputs.ghcr_namespace }}
|
||||
push_latest: ${{ steps.push.outputs.push_latest }}
|
||||
steps:
|
||||
- name: Check Out Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Resolve Inputs
|
||||
id: inputs
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_TARGET_HOST: ${{ inputs.target_host }}
|
||||
INPUT_RUN_APPLY: ${{ inputs.run_apply }}
|
||||
run: bash scripts/github-actions/resolve-workflow-inputs.sh
|
||||
|
||||
- name: Compute Image Metadata
|
||||
id: metadata
|
||||
run: |
|
||||
bash scripts/github-actions/compute-frontend-release-metadata.sh
|
||||
|
||||
- name: Resolve Push Latest
|
||||
id: push
|
||||
env:
|
||||
REF: ${{ github.ref }}
|
||||
run: bash scripts/github-actions/resolve-push-latest.sh
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: prep
|
||||
outputs:
|
||||
image_ref: ${{ steps.publish.outputs.image_ref }}
|
||||
image_tag: ${{ steps.publish.outputs.image_tag }}
|
||||
steps:
|
||||
- name: Check Out Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Set Up Docker Buildx
|
||||
run: bash scripts/github-actions/setup-docker-buildx.sh
|
||||
|
||||
- name: Log In To GHCR
|
||||
env:
|
||||
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
|
||||
run: bash scripts/github-actions/login-ghcr.sh
|
||||
|
||||
- name: Publish Frontend Image
|
||||
id: publish
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.prep.outputs.image_ref }}
|
||||
IMAGE_TAG: ${{ needs.prep.outputs.image_tag }}
|
||||
IMAGE_LATEST_REF: ${{ needs.prep.outputs.image_latest_ref }}
|
||||
PUSH_LATEST: ${{ needs.prep.outputs.push_latest }}
|
||||
run: bash scripts/github-actions/publish-frontend-image.sh
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prep
|
||||
- build
|
||||
env:
|
||||
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
|
||||
RUN_APPLY: ${{ needs.prep.outputs.run_apply }}
|
||||
FRONTEND_IMAGE: ${{ needs.build.outputs.image_ref }}
|
||||
steps:
|
||||
- name: Check Out Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Check Out Playbooks Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
# Intentionally pinned: playbooks@main regressed deploy reliability on 2026-04-12.
|
||||
# Any future bump must pass a full Deploy + Validate run before becoming the default.
|
||||
repository: x-evor/playbooks
|
||||
ref: 80c545a95c3b16459f6494ed13d951faac57bfa8
|
||||
path: playbooks
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Set Up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install Ansible
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install ansible
|
||||
|
||||
- name: Configure SSH For Deploy Host
|
||||
env:
|
||||
SINGLE_NODE_VPS_SSH_PRIVATE_KEY: ${{ secrets.SINGLE_NODE_VPS_SSH_PRIVATE_KEY }}
|
||||
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
|
||||
run: bash scripts/github-actions/configure-ssh-for-deploy.sh
|
||||
|
||||
- name: Run Deploy Playbook
|
||||
working-directory: playbooks
|
||||
env:
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: bash ../scripts/github-actions/run-console-deploy-playbook.sh
|
||||
|
||||
validate:
|
||||
name: Validate
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prep
|
||||
- build
|
||||
- deploy
|
||||
if: ${{ always() && needs.deploy.result == 'success' }}
|
||||
env:
|
||||
EXPECTED_FRONTEND_IMAGE: ${{ needs.build.outputs.image_ref }}
|
||||
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
|
||||
SINGLE_NODE_VPS_SSH_PRIVATE_KEY: ${{ secrets.SINGLE_NODE_VPS_SSH_PRIVATE_KEY }}
|
||||
steps:
|
||||
- name: Check Out Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Configure SSH For Validate Host
|
||||
run: bash scripts/github-actions/configure-ssh-for-deploy.sh
|
||||
|
||||
- name: Verify Frontend Release On Host
|
||||
run: bash scripts/github-actions/verify-frontend-release-over-ssh.sh
|
||||
|
||||
update_dns:
|
||||
name: Update DNS
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prep
|
||||
- build
|
||||
- deploy
|
||||
if: ${{ always() && needs.deploy.result == 'success' }}
|
||||
continue-on-error: true
|
||||
env:
|
||||
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
|
||||
RUN_APPLY: ${{ needs.prep.outputs.run_apply }}
|
||||
FRONTEND_IMAGE: ${{ needs.build.outputs.image_ref }}
|
||||
steps:
|
||||
- name: Check Out Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Check Out Playbooks Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
repository: x-evor/playbooks
|
||||
ref: 80c545a95c3b16459f6494ed13d951faac57bfa8
|
||||
path: playbooks
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Set Up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install Ansible
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install ansible
|
||||
|
||||
- name: Update Cloudflare svc.plus DNS
|
||||
working-directory: playbooks
|
||||
env:
|
||||
ANSIBLE_HOST_KEY_CHECKING: "False"
|
||||
run: bash ../scripts/github-actions/run-cloudflare-svc-plus-dns-playbook.sh
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,13 +1,10 @@
|
||||
.env
|
||||
models/
|
||||
*.tsbuildinfo
|
||||
knowledge/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
ui/.DS_Store
|
||||
ui/*/.DS_Store
|
||||
src/content/
|
||||
|
||||
# Node.js dependencies
|
||||
node_modules/
|
||||
@ -19,7 +16,6 @@ public/_build/
|
||||
public/dl-index/
|
||||
.contentlayer/
|
||||
.dev-logs/
|
||||
.console-state/
|
||||
|
||||
# Contentlayer cache
|
||||
ui/docs/.contentlayer/
|
||||
@ -56,7 +52,6 @@ coverage/
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
deploy/single-node/.env.runtime
|
||||
|
||||
# Build artifacts
|
||||
build/
|
||||
|
||||
10
.yarnrc.yml
10
.yarnrc.yml
@ -4,12 +4,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmRegistryServer: "https://registry.npmjs.org"
|
||||
|
||||
packageExtensions:
|
||||
"next-contentlayer@*":
|
||||
peerDependencies:
|
||||
next: "*"
|
||||
"contentlayer@*":
|
||||
peerDependencies:
|
||||
next: "*"
|
||||
npmRegistryServer: "https://registry.npmmirror.com"
|
||||
|
||||
88
AGENTS.md
88
AGENTS.md
@ -28,8 +28,7 @@ yarn preview # Build and start production server
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
yarn lint # Run ESLint (currently fails under Next 16 CLI; use eslint command below)
|
||||
./node_modules/.bin/eslint . --no-eslintrc --config .eslintrc.json --resolve-plugins-relative-to . # Run ESLint directly (ignore parent configs)
|
||||
yarn lint # Run ESLint
|
||||
yarn typecheck # TypeScript type checking
|
||||
yarn format # Format code with Prettier
|
||||
```
|
||||
@ -51,20 +50,7 @@ yarn test:e2e path/to/spec.test.ts
|
||||
|
||||
---
|
||||
|
||||
## 2. Release Traceability Default Rule
|
||||
|
||||
For any change touching CI/CD, image tags, deploy contracts, `/api/ping`, or `validate` behavior:
|
||||
|
||||
- Treat `skills/release-traceability/SKILL.md` as the default reference before implementation.
|
||||
- Prefer release metadata that can be traced from `build` to `deploy` to `validate` without manual injection.
|
||||
- Keep the published image reference, runtime version, and validation output aligned.
|
||||
- Do not introduce a deploy path that rebuilds images on the target host.
|
||||
|
||||
When in doubt, follow the skill first and keep the release chain fully auditable end to end.
|
||||
|
||||
---
|
||||
|
||||
## 3. Repository Mental Model (Read This First)
|
||||
## 2. Repository Mental Model (Read This First)
|
||||
|
||||
This repository has **three clearly separated layers**:
|
||||
|
||||
@ -104,7 +90,7 @@ Used for build-time or runtime wiring only.
|
||||
|
||||
---
|
||||
|
||||
## 4. Import & Alias Rules (Critical)
|
||||
## 3. Import & Alias Rules (Critical)
|
||||
|
||||
### Dashboard code (src/\*\*)
|
||||
|
||||
@ -125,7 +111,7 @@ import { UserCard } from "@/components/UserCard";
|
||||
|
||||
---
|
||||
|
||||
## 5. TypeScript & Formatting Rules
|
||||
## 4. TypeScript & Formatting Rules
|
||||
|
||||
- Strict mode enabled
|
||||
- Use `type` for type definitions, `interface` for object shapes
|
||||
@ -136,7 +122,7 @@ import { UserCard } from "@/components/UserCard";
|
||||
|
||||
---
|
||||
|
||||
## 6. Naming Conventions
|
||||
## 5. Naming Conventions
|
||||
|
||||
- Components: PascalCase (`UserProfile.tsx`)
|
||||
- Files: kebab-case for utilities (`user-utils.ts`), PascalCase for components
|
||||
@ -146,7 +132,7 @@ import { UserCard } from "@/components/UserCard";
|
||||
|
||||
---
|
||||
|
||||
## 7. Error Handling & Logging
|
||||
## 6. Error Handling & Logging
|
||||
|
||||
- Use try/catch for async operations
|
||||
- Return Result types or throw errors consistently
|
||||
@ -155,7 +141,7 @@ import { UserCard } from "@/components/UserCard";
|
||||
|
||||
---
|
||||
|
||||
## 8. React Patterns
|
||||
## 7. React Patterns
|
||||
|
||||
- Use `'use client'` directive for client components
|
||||
- Prefer function components with hooks
|
||||
@ -164,7 +150,7 @@ import { UserCard } from "@/components/UserCard";
|
||||
|
||||
---
|
||||
|
||||
## 9. Global State Rules (Dashboard Only)
|
||||
## 8. Global State Rules (Dashboard Only)
|
||||
|
||||
✅ Zustand is the **only** allowed global state mechanism
|
||||
❌ React Context for shared/global state is forbidden
|
||||
@ -175,7 +161,7 @@ Rule: If state must survive navigation or be shared → it lives in Zustand.
|
||||
|
||||
---
|
||||
|
||||
## 10. URL-Synchronized State
|
||||
## 9. URL-Synchronized State
|
||||
|
||||
Anything involving:
|
||||
|
||||
@ -192,7 +178,7 @@ MUST be handled inside Zustand slices.
|
||||
|
||||
---
|
||||
|
||||
## 11. Component State Rules
|
||||
## 10. Component State Rules
|
||||
|
||||
Allowed:
|
||||
|
||||
@ -207,7 +193,7 @@ Forbidden:
|
||||
|
||||
---
|
||||
|
||||
## 12. packages/neurapress Rules (Very Important)
|
||||
## 11. packages/neurapress Rules (Very Important)
|
||||
|
||||
packages/neurapress is treated as a vendored internal library.
|
||||
|
||||
@ -227,7 +213,7 @@ MUST NOT:
|
||||
|
||||
---
|
||||
|
||||
## 13. Testing Guidelines
|
||||
## 12. Testing Guidelines
|
||||
|
||||
- Unit tests: Vitest with jsdom environment
|
||||
- E2E tests: Playwright
|
||||
@ -237,7 +223,7 @@ MUST NOT:
|
||||
|
||||
---
|
||||
|
||||
## 14. Environment & Runtime Config
|
||||
## 13. Environment & Runtime Config
|
||||
|
||||
- No new environment variables without approval
|
||||
- Runtime config must live in: src/config/runtime-service-config\*.yaml
|
||||
@ -246,13 +232,13 @@ MUST NOT:
|
||||
|
||||
---
|
||||
|
||||
## 15. Cursor / Copilot Rules
|
||||
## 14. Cursor / Copilot Rules
|
||||
|
||||
- No `.cursor/rules/`, `.cursorrules`, or `.github/copilot-instructions.md` found
|
||||
|
||||
---
|
||||
|
||||
## 16. TL;DR for AI Agents
|
||||
## 15. TL;DR for AI Agents
|
||||
|
||||
- dashboard = application
|
||||
- packages = libraries
|
||||
@ -260,47 +246,3 @@ MUST NOT:
|
||||
- No @/ imports inside packages
|
||||
- Never "fix" libraries by polluting the app
|
||||
- Always run `yarn lint` and `yarn typecheck` before completing tasks
|
||||
- If `yarn lint` fails with "Invalid project directory .../lint" (Next 16 CLI), use `./node_modules/.bin/eslint .` instead
|
||||
|
||||
---
|
||||
|
||||
# Agent Operating Rules
|
||||
|
||||
You are an AI agent working inside this repository.
|
||||
|
||||
## Role
|
||||
|
||||
- Act as a senior engineer and automation assistant.
|
||||
- Prioritize correctness, minimal changes, and reproducibility.
|
||||
- agent.md mirrors AGENTS.md; when in doubt, follow AGENTS.md as the source of truth.
|
||||
|
||||
## Global Rules
|
||||
|
||||
- Do not introduce new dependencies unless explicitly requested.
|
||||
- Do not change API contracts without explicit instruction.
|
||||
- Do not add new environment variables without approval.
|
||||
- Keep changes scoped to the request; avoid unrelated refactors.
|
||||
- Prefer minimal edits that preserve existing behavior and style.
|
||||
|
||||
## Repository Constraints (Quick View)
|
||||
|
||||
- App layer: src/app/**, src/components/**, src/lib/**, src/state/**, src/modules/**
|
||||
- Library layer: packages/** (no @/ aliases, no global state)
|
||||
- Global state: Zustand only, in src/state/** or src/app/store/**
|
||||
- URL-synced state must live in Zustand slices
|
||||
|
||||
## Testing Policy
|
||||
|
||||
- Follow mcp/testing.md for minimal verification.
|
||||
- Always run the minimal verification after a coherent change-set.
|
||||
|
||||
## Output Format
|
||||
|
||||
Always respond with:
|
||||
|
||||
1. Summary of changes
|
||||
2. Commands executed
|
||||
3. Result (success/failure)
|
||||
4. Next step
|
||||
|
||||
If these rules conflict with user instructions, ask once for clarification and proceed conservatively.
|
||||
|
||||
67
Dockerfile
67
Dockerfile
@ -4,27 +4,6 @@
|
||||
ARG NODE_BUILDER_IMAGE=node:22-bookworm
|
||||
ARG NODE_RUNTIME_IMAGE=node:22-slim
|
||||
ARG CONTENTLAYER_BUILD=true
|
||||
ARG NEXT_PUBLIC_APP_BASE_URL=
|
||||
ARG NEXT_PUBLIC_SITE_URL=
|
||||
ARG NEXT_PUBLIC_LOGIN_URL=
|
||||
ARG NEXT_PUBLIC_DOCS_BASE_URL=
|
||||
ARG NEXT_PUBLIC_RUNTIME_ENVIRONMENT=
|
||||
ARG NEXT_PUBLIC_RUNTIME_REGION=
|
||||
ARG NEXT_PUBLIC_GISCUS_REPO=
|
||||
ARG NEXT_PUBLIC_GISCUS_REPO_ID=
|
||||
ARG NEXT_PUBLIC_GISCUS_CATEGORY=
|
||||
ARG NEXT_PUBLIC_GISCUS_CATEGORY_ID=
|
||||
ARG NEXT_PUBLIC_PAYPAL_CLIENT_ID=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=
|
||||
ARG NEXT_PUBLIC_RELEASE_IMAGE=
|
||||
ARG NEXT_PUBLIC_RELEASE_TAG=
|
||||
ARG NEXT_PUBLIC_RELEASE_COMMIT=
|
||||
ARG NEXT_PUBLIC_RELEASE_VERSION=
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Stage 1 — Builder (Turbopack + standalone)
|
||||
@ -33,51 +12,8 @@ FROM ${NODE_BUILDER_IMAGE} AS builder
|
||||
|
||||
WORKDIR /app/dashboard
|
||||
|
||||
ARG NEXT_PUBLIC_APP_BASE_URL
|
||||
ARG NEXT_PUBLIC_SITE_URL
|
||||
ARG NEXT_PUBLIC_LOGIN_URL
|
||||
ARG NEXT_PUBLIC_DOCS_BASE_URL
|
||||
ARG NEXT_PUBLIC_RUNTIME_ENVIRONMENT
|
||||
ARG NEXT_PUBLIC_RUNTIME_REGION
|
||||
ARG NEXT_PUBLIC_GISCUS_REPO
|
||||
ARG NEXT_PUBLIC_GISCUS_REPO_ID
|
||||
ARG NEXT_PUBLIC_GISCUS_CATEGORY
|
||||
ARG NEXT_PUBLIC_GISCUS_CATEGORY_ID
|
||||
ARG NEXT_PUBLIC_PAYPAL_CLIENT_ID
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO
|
||||
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION
|
||||
ARG NEXT_PUBLIC_RELEASE_IMAGE
|
||||
ARG NEXT_PUBLIC_RELEASE_TAG
|
||||
ARG NEXT_PUBLIC_RELEASE_COMMIT
|
||||
ARG NEXT_PUBLIC_RELEASE_VERSION
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1 \
|
||||
NEXT_PRIVATE_TURBOPACK=1 \
|
||||
NEXT_PUBLIC_APP_BASE_URL=${NEXT_PUBLIC_APP_BASE_URL} \
|
||||
NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL} \
|
||||
NEXT_PUBLIC_LOGIN_URL=${NEXT_PUBLIC_LOGIN_URL} \
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=${NEXT_PUBLIC_DOCS_BASE_URL} \
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${NEXT_PUBLIC_RUNTIME_ENVIRONMENT} \
|
||||
NEXT_PUBLIC_RUNTIME_REGION=${NEXT_PUBLIC_RUNTIME_REGION} \
|
||||
NEXT_PUBLIC_GISCUS_REPO=${NEXT_PUBLIC_GISCUS_REPO} \
|
||||
NEXT_PUBLIC_GISCUS_REPO_ID=${NEXT_PUBLIC_GISCUS_REPO_ID} \
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY=${NEXT_PUBLIC_GISCUS_CATEGORY} \
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY_ID=${NEXT_PUBLIC_GISCUS_CATEGORY_ID} \
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID=${NEXT_PUBLIC_PAYPAL_CLIENT_ID} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO} \
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION} \
|
||||
NEXT_PUBLIC_RELEASE_IMAGE=${NEXT_PUBLIC_RELEASE_IMAGE} \
|
||||
NEXT_PUBLIC_RELEASE_TAG=${NEXT_PUBLIC_RELEASE_TAG} \
|
||||
NEXT_PUBLIC_RELEASE_COMMIT=${NEXT_PUBLIC_RELEASE_COMMIT} \
|
||||
NEXT_PUBLIC_RELEASE_VERSION=${NEXT_PUBLIC_RELEASE_VERSION}
|
||||
NEXT_PRIVATE_TURBOPACK=1
|
||||
|
||||
# ---------------------------
|
||||
# 基础镜像升级到最新
|
||||
@ -123,6 +59,7 @@ RUN apt-get update \
|
||||
COPY --from=builder /app/dashboard/.next/standalone ./
|
||||
COPY --from=builder /app/dashboard/.next/static ./static
|
||||
COPY --from=builder /app/dashboard/public ./public
|
||||
COPY --from=builder /app/dashboard/src/content/blog ./src/content/blog
|
||||
|
||||
# ---------------------------
|
||||
# 额外瘦身(可减少 15–40 MB)
|
||||
|
||||
810
LICENSE
810
LICENSE
@ -1,202 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
Preamble
|
||||
|
||||
1. Definitions.
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
0. Definitions.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
1. Source Code.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
Copyright (C) 2018-2026 Ruohang Feng, @Vonng (rh@vonng.com)
|
||||
2. Basic Permissions.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
2
Makefile
2
Makefile
@ -49,6 +49,7 @@ init:
|
||||
echo "❌ Unsupported OS. Please install Yarn manually."; exit 1; \
|
||||
fi; \
|
||||
fi
|
||||
yarn config set npmRegistryServer https://registry.npmmirror.com
|
||||
yarn install --immutable
|
||||
|
||||
ensure-deps:
|
||||
@ -82,6 +83,7 @@ test:
|
||||
@yarn test || echo "No tests configured"
|
||||
|
||||
build: init
|
||||
yarn config set npmRegistryServer https://registry.npmmirror.com
|
||||
@if [ -z "$(SKIP_SYNC)" ]; then \
|
||||
$(MAKE) sync-dl-index; \
|
||||
fi
|
||||
|
||||
175
README.md
175
README.md
@ -1,138 +1,89 @@
|
||||
# console.svc.plus
|
||||
|
||||
Cloud Neutral Toolkit 的开放云控制面板 (Open Cloud Control Panel).
|
||||
**工程师 · 开源 · 云中立**
|
||||
|
||||
面向 **Ops / Infra / AI** 的统一前端仪表盘,强调技术自由与可迁移性。
|
||||
关注 **Ops / Infra / AI** 与 **技术自由**。
|
||||
🏗️ 热衷于构建“逃生舱”,防止基础设施被厂商锁定。
|
||||
|
||||
> A unified dashboard for Ops / Infra / AI, built for technical freedom and portability.
|
||||
> **Accountable Engineer · Open Source · Cloud Neutral**
|
||||
>
|
||||
> Focus on **Ops / Infra / AI** and **Technical Freedom**.
|
||||
> 🏗️ Passionate about building "escape pods" to prevent infrastructure vendor lock-in.
|
||||
|
||||
## 部署要求 (Deployment Requirements)
|
||||
---
|
||||
|
||||
| 维度 | 要求 / 规格 | 说明 |
|
||||
| ----------- | ------------------ | ------------------------------------- |
|
||||
| Node.js | `>=18.17 <25` | 推荐使用 `.nvmrc` |
|
||||
| 包管理 | Yarn (推荐) 或 npm | Yarn 推荐配合 Corepack |
|
||||
| Git | 必需 | 用于拉取仓库 |
|
||||
| 部署 (可选) | Vercel / 自建 | 部署方式见 `docs/usage/deployment.md` |
|
||||
**console.svc.plus** 是 Cloud Neutral Toolkit 的**开放云控制面板**。
|
||||
|
||||
## 快速开始 (Quickstart)
|
||||
> **console.svc.plus** is the **Open Cloud Control Panel** for the Cloud Neutral Toolkit.
|
||||
|
||||
### 一键初始化 (Setup Script)
|
||||
## 项目简介 (About The Project)
|
||||
|
||||
本项目是 Cloud Neutral 生态系统的核心可视化界面(前端仪表盘)。它连接各个微服务,为管理云中立基础设施提供统一的控制平面。
|
||||
|
||||
> This repository serves as the central visual interface (Frontend Dashboard) for the Cloud Neutral ecosystem. It connects various micro-services to provide a unified control plane for managing your cloud-neutral infrastructure.
|
||||
|
||||
该生态系统目前包含多个专用的微后端和服务:
|
||||
|
||||
* **console.svc.plus**: (本项目) 主前端仪表盘。
|
||||
* **accounts.svc.plus**: 身份与账户管理服务。
|
||||
* **postgresql.svc.plus**: 带有专用扩展的 PostgreSQL 数据库服务。
|
||||
* **rag-server.svc.plus**: 检索增强生成 (RAG) 后端。
|
||||
* **page-reading-agent-backend**: 页面阅读智能体后端逻辑。
|
||||
* **page-reading-agent-dashboard**: 页面阅读智能体专用仪表盘。
|
||||
* **wechat-to-markdown.svc.plus**: 微信内容转 Markdown 工具服务。
|
||||
|
||||
## 技术栈 (Tech Stack)
|
||||
|
||||
本仪表盘使用现代 Web 技术构建:
|
||||
> This dashboard is built using modern web technologies:
|
||||
|
||||
* **框架**: [Next.js](https://nextjs.org/)
|
||||
* **语言**: TypeScript
|
||||
* **样式**: [Tailwind CSS](https://tailwindcss.com/)
|
||||
* **UI 组件**: [Radix UI](https://www.radix-ui.com/)
|
||||
* **内容管理**: [Contentlayer](https://contentlayer.dev/)
|
||||
|
||||
## 快速开始 (Getting Started)
|
||||
|
||||
### 前置要求 (Prerequisites)
|
||||
|
||||
* Node.js (`>=18.17 <25`)
|
||||
* Yarn (推荐) 或 npm
|
||||
|
||||
### 安装 (Installation)
|
||||
|
||||
```bash
|
||||
curl -fsSL "https://raw.githubusercontent.com/x-evor/console.svc.plus/main/scripts/setup.sh?$(date +%s)" \
|
||||
| bash -s -- console.svc.plus
|
||||
yarn install
|
||||
```
|
||||
|
||||
### 本地运行 (Local Dev)
|
||||
### 本地运行 (Running Locally)
|
||||
|
||||
启动开发服务器:
|
||||
> To start the development server:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
如果需要环境变量:
|
||||
此命令会运行设置脚本 (`scripts/Dev-MCP-Server.sh`) 并启动带有 TurboPack 的 Next.js 开发服务器。
|
||||
> This command runs the setup script (`scripts/Dev-MCP-Server.sh`) and starts the Next.js development server with TurboPack.
|
||||
|
||||
### 构建生产版本 (Building for Production)
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
如果你的工作区同时包含 `openclaw-deploy-example`,建议参考 `../openclaw-deploy-example/.env` 填写 AI 助手联调配置,并同时查看 `docs/getting-started/installation.md`。
|
||||
|
||||
## 主要入口 (Key Routes)
|
||||
|
||||
- `/services`:服务导航页,保留现有控制台布局。
|
||||
- `/xworkmate`:原生 Next.js 的 XWorkmate 在线工作区,底层通过 `xworkmate-bridge` 的 `/acp/rpc` 接入。
|
||||
- `/panel/api`:融合设置与集成页,用于配置和探测 OpenClaw Gateway、Vault Server、APISIX AI Gateway。
|
||||
|
||||
## AI 助手与集成能力 (Assistant & Integrations)
|
||||
|
||||
当前主页 AI 辅助功能已经基于本仓库原生实现,核心行为如下:
|
||||
|
||||
- 侧栏助手模式保留现有交互方式,但 `/xworkmate` 主工作区直接对接 `xworkmate-bridge`。
|
||||
- 最大化助手页面统一收敛到 `/xworkmate`,旧的 `/services/openclaw` 只保留兼容跳转,不再继续使用旧的 control UI 套壳。
|
||||
- 页面截图通过 assistant chat 附件模式发送,而不是单独的浏览器控制壳。
|
||||
- `/panel/api` 仍保留旧集成配置入口;`/xworkmate` 主路径不依赖它。
|
||||
- bridge 地址与令牌从服务端环境变量读取,前端组件不硬编码敏感配置。
|
||||
|
||||
## 环境变量 (Environment Variables)
|
||||
|
||||
以下变量用于 `/xworkmate` 主工作区的服务端 bridge 代理:
|
||||
|
||||
| 变量 | 用途 |
|
||||
| ------------------- | ------------------------------------- |
|
||||
| `BRIDGE_SERVER_URL` | XWorkmate bridge 服务根地址 |
|
||||
| `BRIDGE_AUTH_TOKEN` | XWorkmate bridge bearer token,服务端 |
|
||||
|
||||
以下变量用于旧助手和集成页的服务端默认值预填:
|
||||
|
||||
| 变量 | 用途 |
|
||||
| ----------------------------- | ------------------------------------ |
|
||||
| `OPENCLAW_GATEWAY_REMOTE_URL` | OpenClaw gateway 远端 WebSocket 地址 |
|
||||
| `OPENCLAW_GATEWAY_TOKEN` | OpenClaw gateway 访问令牌 |
|
||||
| `VAULT_SERVER_URL` | Vault 服务地址 |
|
||||
| `VAULT_NAMESPACE` | Vault namespace,可选 |
|
||||
| `VAULT_TOKEN` | Vault 探测令牌 |
|
||||
| `APISIX_AI_GATEWAY_URL` | APISIX AI Gateway 地址 |
|
||||
| `AI_GATEWAY_ACCESS_TOKEN` | APISIX AI Gateway 探测令牌 |
|
||||
|
||||
更多说明见 `docs/getting-started/installation.md` 和 `.env.example`。
|
||||
|
||||
## Stripe 配置 (Stripe Billing Setup)
|
||||
|
||||
`/prices`、产品页和账户中心的购买入口现在统一读取前端公开的 Stripe `price_id`:
|
||||
|
||||
| 变量 | 用途 |
|
||||
| -------------------------------------------------- | ------------------- |
|
||||
| `NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO` | Xstream 按量购买 |
|
||||
| `NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION` | Xstream 订阅 |
|
||||
| `NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO` | XScopeHub 按量购买 |
|
||||
| `NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION` | XScopeHub 订阅 |
|
||||
| `NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO` | XCloudFlow 按量购买 |
|
||||
| `NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION` | XCloudFlow 订阅 |
|
||||
|
||||
这些值应填写为 Stripe Dashboard 中对应套餐的 `price_...` 标识。联调步骤见 `docs/integrations/stripe-billing.md`。
|
||||
|
||||
## 核心特性 & 技术栈 (Features & Tech Stack)
|
||||
|
||||
核心特性:
|
||||
|
||||
- 统一控制面:汇聚 Cloud Neutral Toolkit 各微服务入口
|
||||
- 原生 AI 助手工作区:OpenClaw gateway 驱动的聊天、截图附件与会话体验
|
||||
- 融合集成设置:在 `/panel/api` 统一管理 OpenClaw、Vault、APISIX AI Gateway
|
||||
- 文档与内容系统:Contentlayer 驱动的 docs/content pipeline
|
||||
- 可扩展集成:OIDC、Cloudflare Web Analytics 等
|
||||
|
||||
技术栈:
|
||||
|
||||
- Next.js + TypeScript
|
||||
- Tailwind CSS + Radix UI
|
||||
- Zustand
|
||||
- Contentlayer
|
||||
|
||||
## 开发命令 (Useful Commands)
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
yarn build
|
||||
yarn typecheck
|
||||
./node_modules/.bin/eslint . --no-eslintrc --config .eslintrc.json --resolve-plugins-relative-to .
|
||||
```
|
||||
|
||||
## 说明文档 (Docs)
|
||||
## 开发指南 (Development Guidelines)
|
||||
|
||||
入口:
|
||||
有关详细的编码标准、架构规则和 Agent 特定说明,请参阅 [AGENTS.md](./AGENTS.md)。
|
||||
|
||||
- EN: `docs/README.md`
|
||||
- ZH: `docs/zh/README.md`
|
||||
> For detailed coding standards, architecture rules, and agent-specific instructions, please refer to [AGENTS.md](./AGENTS.md).
|
||||
|
||||
常用链接:
|
||||
## 脚本 (Scripts)
|
||||
|
||||
- OIDC: `docs/integrations/oidc-auth.md`
|
||||
- Cloudflare Web Analytics: `docs/integrations/cloudflare-web-analytics.md`
|
||||
- Stripe billing: `docs/integrations/stripe-billing.md`
|
||||
- Assistant / Integrations env setup: `docs/getting-started/installation.md`
|
||||
- Chinese installation guide: `docs/zh/getting-started/installation.md`
|
||||
|
||||
其他:
|
||||
|
||||
- Agent rules: `AGENTS.md`
|
||||
* `dev`: 启动开发服务器。
|
||||
* `build`: 构建生产版本应用。
|
||||
* `test`: 使用 Vitest 运行单元测试。
|
||||
* `test:e2e`: 使用 Playwright 运行端到端测试。
|
||||
* `lint`: 运行代码检查 (Linter)。
|
||||
|
||||
@ -1,230 +0,0 @@
|
||||
# RAG Server 数据库连接修复 Runbook
|
||||
|
||||
## 📋 概述
|
||||
|
||||
**问题**: RAG Server 无法连接到 PostgreSQL 数据库,导致 `/api/rag/query` 返回 404
|
||||
**影响范围**: RAG 检索功能、向量搜索、知识库同步
|
||||
**修复时间**: ~5 分钟
|
||||
**风险等级**: 🟡 中等(前端已实现降级,用户体验影响有限)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 问题诊断
|
||||
|
||||
### 错误症状
|
||||
|
||||
```
|
||||
ERROR: cannot parse `admin_password`: failed to parse as keyword/value (invalid keyword/value)
|
||||
WARN: postgres cache disabled; no database connection
|
||||
```
|
||||
|
||||
### 根本原因
|
||||
|
||||
1. ❌ `DATABASE_URL` 环境变量指向 Secret Manager 引用 `admin_password`,而不是实际的数据库连接字符串
|
||||
2. ❌ RAG 服务器无法连接到 PostgreSQL 数据库
|
||||
3. ❌ 导致 `/api/rag/query` 返回 404
|
||||
|
||||
### 架构图
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ Cloud Run │
|
||||
│ (RAG Server) │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ Stunnel │
|
||||
│ 127.0.0.1:5432 │
|
||||
└──────────┬──────────┘
|
||||
│ TLS Tunnel
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ postgresql │
|
||||
│ .onwalk.net:443 │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ PostgreSQL │
|
||||
│ Database │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 修复方案
|
||||
|
||||
### 方案 1: 使用 Stunnel (推荐) ⭐
|
||||
|
||||
#### 优势
|
||||
- ✅ 安全性更好(TLS 加密)
|
||||
- ✅ 不需要暴露数据库公网 IP
|
||||
- ✅ 符合现有架构设计
|
||||
- ✅ entrypoint.sh 已经支持 Stunnel
|
||||
|
||||
#### 步骤 1: 更新 Cloud Run 环境变量
|
||||
|
||||
```bash
|
||||
gcloud run services update rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008 \
|
||||
--update-env-vars="\
|
||||
DB_TLS_HOST=postgresql.onwalk.net,\
|
||||
DB_TLS_PORT=443,\
|
||||
POSTGRES_USER=postgres,\
|
||||
POSTGRES_PASSWORD=otdcRLTJamszk3AE,\
|
||||
POSTGRES_DB=knowledge_db,\
|
||||
NVIDIA_API_KEY=NVAPI_KEY_PLACEHOLDER" \
|
||||
--clear-env-vars=DATABASE_URL
|
||||
```
|
||||
|
||||
**关键参数说明**:
|
||||
- `DB_TLS_HOST` 和 `DB_TLS_PORT`: 触发 entrypoint.sh 启动 Stunnel
|
||||
- Stunnel 会在 `127.0.0.1:5432` 监听
|
||||
- `config/rag-server.yaml` 中的 `pgurl` 会自动使用 `127.0.0.1:5432`
|
||||
- `--clear-env-vars=DATABASE_URL`: 移除错误的环境变量
|
||||
|
||||
#### 步骤 2: 验证部署
|
||||
|
||||
**检查日志**:
|
||||
```bash
|
||||
gcloud logging read \
|
||||
"resource.type=cloud_run_revision AND resource.labels.service_name=rag-server-svc-plus" \
|
||||
--limit 20 \
|
||||
--project xzerolab-480008 \
|
||||
--format="table(timestamp,textPayload)"
|
||||
```
|
||||
|
||||
**期望输出**:
|
||||
```
|
||||
Starting Stunnel...
|
||||
Stunnel is up!
|
||||
INFO: Connected to PostgreSQL at 127.0.0.1:5432
|
||||
```
|
||||
|
||||
#### 步骤 3: 功能测试
|
||||
|
||||
**测试 RAG 查询**:
|
||||
```bash
|
||||
curl -X POST https://rag-server-svc-plus-HASH-an.a.run.app/api/rag/query \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "test query", "top_k": 5}'
|
||||
```
|
||||
|
||||
**期望响应**: HTTP 200,包含检索结果
|
||||
|
||||
---
|
||||
|
||||
### 方案 2: 直接数据库连接 (不推荐)
|
||||
|
||||
⚠️ **仅在 PostgreSQL 有公网 IP 且无法使用 Stunnel 时使用**
|
||||
|
||||
```bash
|
||||
# 更新 Secret Manager
|
||||
echo "postgres://postgres:otdcRLTJamszk3AE@<PUBLIC_IP>:5432/knowledge_db?sslmode=require" | \
|
||||
gcloud secrets versions add DATABASE_URL --data-file=- --project xzerolab-480008
|
||||
|
||||
# 更新 Cloud Run 使用 Secret
|
||||
gcloud run services update rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008 \
|
||||
--update-secrets=DATABASE_URL=DATABASE_URL:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 当前状态
|
||||
|
||||
### ✅ 工作的功能
|
||||
- `/api/askai` - 直接 AI 回答(不依赖数据库)
|
||||
- CORS 配置
|
||||
- NVIDIA API 集成
|
||||
|
||||
### ❌ 待修复的功能
|
||||
- `/api/rag/query` - RAG 检索(需要数据库连接)
|
||||
- 向量搜索
|
||||
- 知识库同步
|
||||
|
||||
---
|
||||
|
||||
## 🎯 前端降级策略
|
||||
|
||||
前端已经实现了优雅降级:
|
||||
|
||||
1. **首选**: 尝试 `/api/rag/query` (RAG 检索)
|
||||
2. **降级**: 如果失败,回退到 `/api/askai` (直接 AI)
|
||||
3. **结果**: 用户仍然可以获得答案,只是没有知识库上下文
|
||||
|
||||
---
|
||||
|
||||
## 🔄 回滚计划
|
||||
|
||||
如果修复后出现问题,执行以下回滚:
|
||||
|
||||
```bash
|
||||
# 恢复原有 DATABASE_URL
|
||||
gcloud run services update rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008 \
|
||||
--update-secrets=DATABASE_URL=admin_password:latest \
|
||||
--remove-env-vars=DB_TLS_HOST,DB_TLS_PORT,POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 验证清单
|
||||
|
||||
- [ ] Cloud Run 环境变量已更新
|
||||
- [ ] `DATABASE_URL` 已清除
|
||||
- [ ] 日志显示 "Stunnel is up!"
|
||||
- [ ] `/api/rag/query` 返回 200
|
||||
- [ ] 前端 RAG 功能正常
|
||||
- [ ] 向量搜索可用
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [Stunnel 配置文档](../docs/stunnel-setup.md)
|
||||
- [PostgreSQL 连接指南](../docs/postgres-connection.md)
|
||||
- [Cloud Run 环境变量管理](https://cloud.google.com/run/docs/configuring/environment-variables)
|
||||
|
||||
---
|
||||
|
||||
## 🆘 故障排查
|
||||
|
||||
### 问题: Stunnel 无法启动
|
||||
|
||||
**检查**:
|
||||
```bash
|
||||
# 查看 Stunnel 日志
|
||||
gcloud logging read "textPayload=~\"stunnel\"" --limit 50 --project xzerolab-480008
|
||||
```
|
||||
|
||||
**可能原因**:
|
||||
- DNS 解析失败
|
||||
- 防火墙阻止 443 端口
|
||||
- 证书验证失败
|
||||
|
||||
### 问题: 数据库连接超时
|
||||
|
||||
**检查**:
|
||||
```bash
|
||||
# 测试网络连通性
|
||||
gcloud run services describe rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008 \
|
||||
--format="value(status.url)"
|
||||
```
|
||||
|
||||
**可能原因**:
|
||||
- PostgreSQL 服务未运行
|
||||
- 密码错误
|
||||
- 数据库名称错误
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-01-26
|
||||
**负责人**: DevOps Team
|
||||
**审核人**: Tech Lead
|
||||
@ -1,123 +0,0 @@
|
||||
# RAG Server 数据库连接修复方案
|
||||
|
||||
## 问题诊断
|
||||
|
||||
### 当前错误
|
||||
```
|
||||
ERROR: cannot parse `admin_password`: failed to parse as keyword/value (invalid keyword/value)
|
||||
WARN: postgres cache disabled; no database connection
|
||||
```
|
||||
|
||||
### 根本原因
|
||||
1. `DATABASE_URL` 环境变量指向 Secret Manager 引用 `admin_password`,而不是实际的数据库连接字符串
|
||||
2. RAG 服务器无法连接到 PostgreSQL 数据库
|
||||
3. 导致 `/api/rag/query` 返回 404
|
||||
|
||||
## 架构说明
|
||||
|
||||
```
|
||||
Cloud Run (RAG Server)
|
||||
↓
|
||||
Stunnel (127.0.0.1:5432)
|
||||
↓ TLS Tunnel
|
||||
↓
|
||||
postgresql.onwalk.net:443
|
||||
↓
|
||||
PostgreSQL Database
|
||||
```
|
||||
|
||||
## 修复步骤
|
||||
|
||||
### 方案 1: 使用 Stunnel (推荐)
|
||||
|
||||
#### 1. 更新 Cloud Run 环境变量
|
||||
|
||||
```bash
|
||||
gcloud run services update rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008 \
|
||||
--set-env-vars="
|
||||
DB_TLS_HOST=postgresql.onwalk.net,
|
||||
DB_TLS_PORT=443,
|
||||
POSTGRES_USER=postgres,
|
||||
POSTGRES_PASSWORD=otdcRLTJamszk3AE,
|
||||
POSTGRES_DB=knowledge_db,
|
||||
NVIDIA_API_KEY=NVAPI_KEY_PLACEHOLDER"
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `DB_TLS_HOST` 和 `DB_TLS_PORT` 会触发 entrypoint.sh 启动 Stunnel
|
||||
- Stunnel 会在 `127.0.0.1:5432` 监听
|
||||
- `config/rag-server.yaml` 中的 `pgurl` 会自动使用 `127.0.0.1:5432`
|
||||
|
||||
#### 2. 验证配置
|
||||
|
||||
部署后检查日志:
|
||||
```bash
|
||||
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=rag-server-svc-plus" \
|
||||
--limit 20 --project xzerolab-480008
|
||||
```
|
||||
|
||||
应该看到:
|
||||
```
|
||||
Starting Stunnel...
|
||||
Stunnel is up!
|
||||
```
|
||||
|
||||
### 方案 2: 直接数据库连接 (不推荐,需要公网 IP)
|
||||
|
||||
如果 PostgreSQL 有公网 IP,可以直接连接:
|
||||
|
||||
```bash
|
||||
# 更新 Secret Manager
|
||||
echo "postgres://postgres:otdcRLTJamszk3AE@<PUBLIC_IP>:5432/knowledge_db?sslmode=require" | \
|
||||
gcloud secrets versions add DATABASE_URL --data-file=- --project xzerolab-480008
|
||||
```
|
||||
|
||||
## 当前状态
|
||||
|
||||
### 工作的功能
|
||||
- ✅ `/api/askai` - 直接 AI 回答(不依赖数据库)
|
||||
- ✅ CORS 配置
|
||||
- ✅ NVIDIA API 集成
|
||||
|
||||
### 待修复的功能
|
||||
- ❌ `/api/rag/query` - RAG 检索(需要数据库连接)
|
||||
- ❌ 向量搜索
|
||||
- ❌ 知识库同步
|
||||
|
||||
## 前端行为
|
||||
|
||||
前端已经实现了优雅降级:
|
||||
1. 首先尝试 `/api/rag/query` (RAG 检索)
|
||||
2. 如果失败,回退到 `/api/askai` (直接 AI)
|
||||
3. 用户仍然可以获得答案,只是没有知识库上下文
|
||||
|
||||
## 建议
|
||||
|
||||
**立即执行方案 1**,因为:
|
||||
1. 安全性更好(TLS 加密)
|
||||
2. 不需要暴露数据库公网 IP
|
||||
3. 符合现有架构设计
|
||||
4. entrypoint.sh 已经支持 Stunnel
|
||||
|
||||
## 部署命令
|
||||
|
||||
```bash
|
||||
# 完整部署命令
|
||||
gcloud run services update rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008 \
|
||||
--update-env-vars="
|
||||
DB_TLS_HOST=postgresql.onwalk.net,
|
||||
DB_TLS_PORT=443,
|
||||
POSTGRES_USER=postgres,
|
||||
POSTGRES_PASSWORD=otdcRLTJamszk3AE,
|
||||
POSTGRES_DB=knowledge_db,
|
||||
NVIDIA_API_KEY=NVAPI_KEY_PLACEHOLDER" \
|
||||
--clear-env-vars=DATABASE_URL
|
||||
```
|
||||
|
||||
注意:
|
||||
- 使用 `--update-env-vars` 添加新变量
|
||||
- 使用 `--clear-env-vars` 移除错误的 `DATABASE_URL`
|
||||
@ -1,48 +0,0 @@
|
||||
# Runbook 目录
|
||||
|
||||
本目录包含该项目的运维手册和故障排查文档。
|
||||
|
||||
## 📚 文档分类
|
||||
|
||||
### 🔧 部署相关
|
||||
- 部署流程
|
||||
- 环境配置
|
||||
- 依赖管理
|
||||
|
||||
### 🚨 故障排查
|
||||
- 常见问题
|
||||
- 错误诊断
|
||||
- 应急处理
|
||||
|
||||
### 🔄 运维操作
|
||||
- 日常维护
|
||||
- 备份恢复
|
||||
- 性能优化
|
||||
|
||||
### 📊 监控告警
|
||||
- 监控指标
|
||||
- 告警规则
|
||||
- 日志分析
|
||||
|
||||
## 📝 文档规范
|
||||
|
||||
每个 Runbook 应包含:
|
||||
|
||||
1. **问题描述**: 清晰描述问题现象
|
||||
2. **影响范围**: 说明影响的功能和用户
|
||||
3. **诊断步骤**: 详细的问题定位方法
|
||||
4. **修复方案**: 具体的解决步骤
|
||||
5. **验证方法**: 确认问题已解决的检查清单
|
||||
6. **回滚计划**: 如果修复失败的应急方案
|
||||
|
||||
## 🎯 命名规范
|
||||
|
||||
- 使用描述性的文件名
|
||||
- 格式: `[类型]-[简短描述].md`
|
||||
- 示例: `Deploy-Database-Migration.md`, `Fix-API-Timeout.md`
|
||||
|
||||
## 📅 维护说明
|
||||
|
||||
- 定期更新文档
|
||||
- 记录最后更新时间
|
||||
- 标注负责人和审核人
|
||||
@ -1,49 +0,0 @@
|
||||
# X (Twitter) Post Drafts for SVC.PLUS Update
|
||||
|
||||
Here are a few options for your X post, ranging from technical to marketing-focused.
|
||||
|
||||
## Option 1: Feature & Tech Highlight (Recommended)
|
||||
**Focus:** Transparency, Integration, and Performance.
|
||||
|
||||
🚀 **Update: Real-time Metrics Dashboard is Live!**
|
||||
|
||||
We've upgraded the svc.plus homepage stats with transparent, dynamic data sources:
|
||||
✅ **Registered Users**: Synced hourly from our Postgres DB
|
||||
✅ **24h/Weekly/Monthly Traffic**: Powered by Cloudflare GraphQL API
|
||||
|
||||
Tech stack: Next.js + Cloudflare Analytics + SWR Caching.
|
||||
Check it out! 👇
|
||||
[Insert Link]
|
||||
|
||||
#BuildInPublic #Nextjs #Cloudflare #SaaS #WebDev
|
||||
|
||||
---
|
||||
|
||||
## Option 2: Short & Punchy
|
||||
**Focus:** Quick announcement.
|
||||
|
||||
**Transparent metrics, delivered fast.** ⚡️
|
||||
|
||||
The new svc.plus dashboard now displays:
|
||||
🔹 Live User Count (Hourly sync)
|
||||
🔹 Global Traffic: 24h, Weekly, & Monthly stats (via Cloudflare)
|
||||
|
||||
See our growth in real-time: [Insert Link]
|
||||
|
||||
#OpenSource #CloudNative #DevOps
|
||||
|
||||
---
|
||||
|
||||
## Option 3: Chinese Version (中文版)
|
||||
**Focus:** Addressing the Chinese-speaking community foundation.
|
||||
|
||||
**🚀 svc.plus 首页看板升级完成!**
|
||||
|
||||
我们主打真实 transparent 的数据展示:
|
||||
📊 **注册用户数**:直连 PG 数据库,每小时动态更新
|
||||
🌍 **访问量数据**(24h/周/月):集成 Cloudflare Analytics API
|
||||
|
||||
拒绝虚假数据,每一位访客和注册用户都清晰可见。
|
||||
欢迎体验:[Insert Link]
|
||||
|
||||
#独立开发 #SaaS #Nextjs #Cloudflare
|
||||
46
agent.md
46
agent.md
@ -1,46 +0,0 @@
|
||||
# Agent Operating Rules
|
||||
|
||||
You are an AI agent working inside this repository.
|
||||
|
||||
## Role
|
||||
|
||||
- Act as a senior engineer and automation assistant.
|
||||
- Prioritize correctness, minimal changes, and reproducibility.
|
||||
- agent.md mirrors AGENTS.md; when in doubt, follow AGENTS.md as the source of truth.
|
||||
|
||||
## Global Rules
|
||||
|
||||
- Do not introduce new dependencies unless explicitly requested.
|
||||
- Do not change API contracts without explicit instruction.
|
||||
- Do not add new environment variables without approval.
|
||||
- Keep changes scoped to the request; avoid unrelated refactors.
|
||||
- Prefer minimal edits that preserve existing behavior and style.
|
||||
|
||||
## Release Traceability Default Rule
|
||||
|
||||
- For changes touching CI/CD, image tags, deploy contracts, `/api/ping`, or `validate`, treat `skills/release-traceability/SKILL.md` as the default reference first.
|
||||
- Keep build output, runtime version, and validate output aligned through the whole release chain.
|
||||
- Do not add a deploy path that rebuilds images on the target host.
|
||||
|
||||
## Repository Constraints (Quick View)
|
||||
|
||||
- App layer: src/app/**, src/components/**, src/lib/**, src/state/**, src/modules/\*\*
|
||||
- Library layer: packages/\*\* (no @/ aliases, no global state)
|
||||
- Global state: Zustand only, in src/state/** or src/app/store/**
|
||||
- URL-synced state must live in Zustand slices
|
||||
|
||||
## Testing Policy
|
||||
|
||||
- Follow mcp/testing.md for minimal verification.
|
||||
- Always run the minimal verification after a coherent change-set.
|
||||
|
||||
## Output Format
|
||||
|
||||
Always respond with:
|
||||
|
||||
1. Summary of changes
|
||||
2. Commands executed
|
||||
3. Result (success/failure)
|
||||
4. Next step
|
||||
|
||||
If these rules conflict with user instructions, ask once for clarification and proceed conservatively.
|
||||
@ -1,332 +0,0 @@
|
||||
# Feature flag inventory for the dashboard and public site.
|
||||
# This file is a human-readable catalog of page-level and module-level flags.
|
||||
|
||||
meta:
|
||||
app: console.svc.plus
|
||||
scope:
|
||||
- public-site
|
||||
- dashboard
|
||||
- xworkmate
|
||||
- cloud-iac
|
||||
- docs
|
||||
source:
|
||||
- src/config/feature-toggles.json
|
||||
- src/modules/extensions/builtin
|
||||
- src/app
|
||||
|
||||
runtime:
|
||||
current_implementation:
|
||||
toggles_json: src/config/feature-toggles.json
|
||||
loader: src/lib/featureToggles.ts
|
||||
extension_flags: src/lib/featureFlags.ts
|
||||
notes:
|
||||
- "feature-toggles.json is the active runtime source for path-based gating."
|
||||
- "This YAML is an inventory and planning file; it does not currently drive runtime behavior."
|
||||
|
||||
sections:
|
||||
globalNavigation:
|
||||
enabled: true
|
||||
description: Top-level navigation and auth entry points.
|
||||
default_channel: stable
|
||||
routes:
|
||||
- path: /
|
||||
status: enabled
|
||||
channel: stable
|
||||
- path: /docs
|
||||
status: enabled
|
||||
channel: beta
|
||||
- path: /blogs
|
||||
status: enabled
|
||||
note: Public content listing, not currently toggle-gated in code.
|
||||
- path: /services
|
||||
status: enabled
|
||||
note: Public service directory.
|
||||
- path: /prices
|
||||
status: enabled
|
||||
- path: /support
|
||||
status: enabled
|
||||
- path: /about
|
||||
status: enabled
|
||||
- path: /login
|
||||
status: enabled
|
||||
channel: stable
|
||||
- path: /register
|
||||
status: enabled
|
||||
channel: stable
|
||||
- path: /panel
|
||||
status: enabled
|
||||
channel: stable
|
||||
- path: /panel/management
|
||||
status: enabled
|
||||
note: Shown only to admin/operator users.
|
||||
- path: /cloud_iac
|
||||
status: enabled
|
||||
channel: develop
|
||||
- path: /download
|
||||
status: enabled
|
||||
channel: stable
|
||||
- path: /insight
|
||||
status: enabled
|
||||
channel: develop
|
||||
- path: /xworkmate
|
||||
status: enabled
|
||||
|
||||
appModules:
|
||||
enabled: true
|
||||
description: Path-based module gating used by route handlers and content loaders.
|
||||
routes:
|
||||
- path: /docs
|
||||
status: enabled
|
||||
uses: src/app/docs/page.tsx
|
||||
- path: /docs/[collection]
|
||||
status: enabled
|
||||
uses: src/app/docs/[collection]/page.tsx
|
||||
- path: /docs/[collection]/[...slug]
|
||||
status: enabled
|
||||
uses: src/app/docs/[collection]/[...slug]/page.tsx
|
||||
- path: /download
|
||||
status: enabled
|
||||
uses: src/app/download/page.tsx
|
||||
- path: /download/[...segments]
|
||||
status: enabled
|
||||
uses: src/app/download/[...segments]/page.tsx
|
||||
- path: /cloud_iac
|
||||
status: enabled
|
||||
uses: src/app/cloud_iac/page.tsx
|
||||
- path: /cloud_iac/[provider]
|
||||
status: enabled
|
||||
uses: src/app/cloud_iac/[provider]/page.tsx
|
||||
- path: /cloud_iac/[provider]/[service]
|
||||
status: enabled
|
||||
uses: src/app/cloud_iac/[provider]/[service]/page.tsx
|
||||
- path: /insight
|
||||
status: enabled
|
||||
uses: src/app/services/insight/page.tsx
|
||||
- path: /editor
|
||||
status: enabled
|
||||
uses: src/app/editor/page.tsx
|
||||
- path: /editor/wechat
|
||||
status: enabled
|
||||
- path: /editor/xiaohongshu
|
||||
status: enabled
|
||||
- path: /xworkmate
|
||||
status: enabled
|
||||
uses: src/app/xworkmate/page.tsx
|
||||
- path: /xworkmate/admin
|
||||
status: enabled
|
||||
uses: src/app/xworkmate/admin/page.tsx
|
||||
- path: /xworkmate/integrations
|
||||
status: enabled
|
||||
uses: src/app/xworkmate/integrations/page.tsx
|
||||
|
||||
cmsExperience:
|
||||
enabled: true
|
||||
description: CMS/homepage experience gating.
|
||||
routes:
|
||||
- path: /homepage
|
||||
status: enabled
|
||||
children:
|
||||
- path: /homepage/dynamic
|
||||
status: enabled
|
||||
|
||||
extensions:
|
||||
builtin.user-center:
|
||||
enabled: true
|
||||
description: Core dashboard user center and account management extension.
|
||||
routes:
|
||||
- path: /panel
|
||||
id: dashboard
|
||||
enabled: true
|
||||
sidebar_section: workspace
|
||||
- path: /panel/agent
|
||||
id: agents
|
||||
enabled: true
|
||||
env_var: NEXT_PUBLIC_FEATURE_AGENT_MODULE
|
||||
default_enabled: true
|
||||
sidebar_section: productivity
|
||||
- path: /panel/api
|
||||
id: apis
|
||||
enabled: true
|
||||
env_var: NEXT_PUBLIC_FEATURE_API_MODULE
|
||||
default_enabled: true
|
||||
sidebar_section: productivity
|
||||
- path: /panel/account
|
||||
id: accounts
|
||||
enabled: true
|
||||
sidebar_section: management
|
||||
- path: /panel/subscription
|
||||
id: subscription
|
||||
enabled: true
|
||||
env_var: NEXT_PUBLIC_FEATURE_SUBSCRIPTION_MODULE
|
||||
default_enabled: true
|
||||
sidebar_section: management
|
||||
- path: /panel/ldp
|
||||
id: ldp
|
||||
enabled: true
|
||||
env_var: NEXT_PUBLIC_FEATURE_LDP_MODULE
|
||||
default_enabled: false
|
||||
sidebar_section: management
|
||||
- path: /panel/appearance
|
||||
id: appearance
|
||||
enabled: true
|
||||
sidebar_section: preferences
|
||||
- path: /panel/management
|
||||
id: management
|
||||
enabled: true
|
||||
roles:
|
||||
- admin
|
||||
- operator
|
||||
permissions:
|
||||
- admin.settings.read
|
||||
- admin.users.metrics.read
|
||||
- admin.users.list.read
|
||||
- admin.agents.status.read
|
||||
- admin.blacklist.read
|
||||
sidebar_hidden: true
|
||||
|
||||
builtin.infra:
|
||||
enabled: true
|
||||
description: Infrastructure and ops extension.
|
||||
routes:
|
||||
- path: /panel/deployments
|
||||
id: deployments
|
||||
enabled: true
|
||||
sidebar_section: infra
|
||||
- path: /panel/resources
|
||||
id: resources
|
||||
enabled: true
|
||||
sidebar_section: infra
|
||||
- path: /panel/api-keys
|
||||
id: apiKeys
|
||||
enabled: true
|
||||
sidebar_section: infra
|
||||
- path: /panel/observability
|
||||
id: logs
|
||||
enabled: true
|
||||
sidebar_section: infra
|
||||
- path: /panel/settings
|
||||
id: settings
|
||||
enabled: true
|
||||
sidebar_section: preferences
|
||||
|
||||
pages:
|
||||
public:
|
||||
- path: /
|
||||
component: src/app/page.tsx
|
||||
status: enabled
|
||||
- path: /services
|
||||
component: src/app/services/page.tsx
|
||||
status: enabled
|
||||
- path: /about
|
||||
component: src/app/about/page.tsx
|
||||
status: enabled
|
||||
- path: /prices
|
||||
component: src/app/prices/page.tsx
|
||||
status: enabled
|
||||
- path: /support
|
||||
component: src/app/support/page.tsx
|
||||
status: enabled
|
||||
- path: /support/discussions
|
||||
component: src/app/support/discussions/page.tsx
|
||||
status: enabled
|
||||
- path: /blogs
|
||||
component: src/app/blogs/page.tsx
|
||||
status: enabled
|
||||
- path: /blogs/[...slug]
|
||||
component: src/app/blogs/[...slug]/page.tsx
|
||||
status: enabled
|
||||
- path: /terms
|
||||
component: src/app/terms/page.tsx
|
||||
status: enabled
|
||||
- path: /privacy
|
||||
component: src/app/privacy/page.tsx
|
||||
status: enabled
|
||||
- path: /download
|
||||
component: src/app/download/page.tsx
|
||||
status: gated_by_appModules
|
||||
- path: /download/[...segments]
|
||||
component: src/app/download/[...segments]/page.tsx
|
||||
status: gated_by_appModules
|
||||
- path: /docs
|
||||
component: src/app/docs/page.tsx
|
||||
status: gated_by_appModules
|
||||
- path: /docs/[collection]
|
||||
component: src/app/docs/[collection]/page.tsx
|
||||
status: gated_by_appModules
|
||||
- path: /docs/[collection]/[...slug]
|
||||
component: src/app/docs/[collection]/[...slug]/page.tsx
|
||||
status: gated_by_appModules
|
||||
- path: /cloud_iac
|
||||
component: src/app/cloud_iac/page.tsx
|
||||
status: gated_by_appModules
|
||||
- path: /cloud_iac/[provider]
|
||||
component: src/app/cloud_iac/[provider]/page.tsx
|
||||
status: gated_by_appModules
|
||||
- path: /cloud_iac/[provider]/[service]
|
||||
component: src/app/cloud_iac/[provider]/[service]/page.tsx
|
||||
status: gated_by_appModules
|
||||
- path: /editor
|
||||
component: src/app/editor/page.tsx
|
||||
status: redirect_external
|
||||
- path: /editor/wechat
|
||||
component: src/app/editor/wechat/page.tsx
|
||||
status: enabled
|
||||
- path: /editor/xiaohongshu
|
||||
component: src/app/editor/xiaohongshu/page.tsx
|
||||
status: enabled
|
||||
- path: /xworkmate
|
||||
component: src/app/xworkmate/page.tsx
|
||||
status: enabled
|
||||
- path: /xworkmate/admin
|
||||
component: src/app/xworkmate/admin/page.tsx
|
||||
status: enabled
|
||||
- path: /xworkmate/integrations
|
||||
component: src/app/xworkmate/integrations/page.tsx
|
||||
status: enabled
|
||||
|
||||
auth:
|
||||
- path: /login
|
||||
component: src/app/(auth)/login/page.tsx
|
||||
status: gated_by_globalNavigation
|
||||
- path: /register
|
||||
component: src/app/(auth)/register/page.tsx
|
||||
status: gated_by_globalNavigation
|
||||
- path: /email-verification
|
||||
component: src/app/(auth)/email-verification/page.tsx
|
||||
status: gated_by_globalNavigation
|
||||
- path: /logout
|
||||
component: src/app/logout/page.tsx
|
||||
status: enabled
|
||||
|
||||
panel:
|
||||
- path: /panel
|
||||
component: src/app/panel/page.tsx
|
||||
status: extension_route
|
||||
- path: /panel/account
|
||||
component: src/app/panel/account/page.tsx
|
||||
status: extension_route
|
||||
- path: /panel/agent
|
||||
component: src/app/panel/agent/page.tsx
|
||||
status: extension_route
|
||||
- path: /panel/api
|
||||
component: src/app/panel/api/page.tsx
|
||||
status: extension_route
|
||||
- path: /panel/appearance
|
||||
component: src/app/panel/appearance/page.tsx
|
||||
status: extension_route
|
||||
- path: /panel/ldp
|
||||
component: src/app/panel/ldp/page.tsx
|
||||
status: extension_route
|
||||
- path: /panel/management
|
||||
component: src/app/panel/management/page.tsx
|
||||
status: extension_route
|
||||
- path: /panel/subscription
|
||||
component: src/app/panel/subscription/page.tsx
|
||||
status: extension_route
|
||||
- path: /panel/[...segments]
|
||||
component: src/app/panel/[...segments]/page.tsx
|
||||
status: catch_all_extension
|
||||
|
||||
recommendations:
|
||||
- "If this file is intended to become runtime-configurable, wire it into src/lib/featureToggles.ts and keep feature-toggles.json as the generated artifact."
|
||||
- "If this file is intended only for documentation, keep it synchronized with feature-toggles.json and extension definitions."
|
||||
@ -1,41 +0,0 @@
|
||||
# Gitleaks configuration file
|
||||
# For more information, see https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml
|
||||
|
||||
[allowlist]
|
||||
description = "Global allowlist"
|
||||
paths = [
|
||||
'''vendor/''',
|
||||
'''node_modules/''',
|
||||
'''\.env''',
|
||||
'''\.env\..*$''',
|
||||
'''\.next/''',
|
||||
'''\.env\.example$''',
|
||||
'''\.env\.test$''',
|
||||
'''go\.sum$''',
|
||||
'''package-lock\.json$''',
|
||||
]
|
||||
stopwords = [
|
||||
"example",
|
||||
"placeholder",
|
||||
"test-password",
|
||||
]
|
||||
|
||||
[[rules]]
|
||||
id = "generic-api-key"
|
||||
description = "Generic API Key"
|
||||
regex = '''(?i)(api_key|apikey|secret|password|token)[-|_| ]*[=|\:][-|_| ]*['|"]([0-9a-zA-Z]{16,128})['|"]'''
|
||||
description_id = "potential_secret"
|
||||
entropy = 3.5
|
||||
keywords = ["api_key", "apikey", "secret", "password", "token"]
|
||||
|
||||
[[rules]]
|
||||
id = "github-pat"
|
||||
description = "GitHub Personal Access Token"
|
||||
regex = '''ghp_[0-9a-zA-Z]{36}'''
|
||||
keywords = ["ghp_"]
|
||||
|
||||
[[rules]]
|
||||
id = "google-oauth-client-secret"
|
||||
description = "Google OAuth Client Secret"
|
||||
regex = '''(?i)client_secret[-|_| ]*[=|\:][-|_| ]*['|"]([0-9a-zA-Z\-_]{24})['|"]'''
|
||||
keywords = ["client_secret"]
|
||||
@ -1,7 +1,24 @@
|
||||
import { makeSource } from 'contentlayer/source-files'
|
||||
import { defineDocumentType, makeSource } from 'contentlayer/source-files'
|
||||
|
||||
export const Workshop = defineDocumentType(() => ({
|
||||
name: 'Workshop',
|
||||
filePathPattern: '**/*.mdx',
|
||||
contentType: 'mdx',
|
||||
fields: {
|
||||
title: { type: 'string', required: true },
|
||||
summary: { type: 'string', required: true },
|
||||
level: { type: 'string', default: 'Intro' },
|
||||
duration: { type: 'string', required: false },
|
||||
tags: { type: 'list', of: { type: 'string' }, default: [] },
|
||||
updatedAt: { type: 'date', required: false },
|
||||
},
|
||||
computedFields: {
|
||||
slug: { type: 'string', resolve: (doc) => doc._raw.flattenedPath },
|
||||
url: { type: 'string', resolve: (doc) => `/workshop/${doc._raw.flattenedPath}` },
|
||||
},
|
||||
}))
|
||||
|
||||
export default makeSource({
|
||||
contentDirPath: 'src/content',
|
||||
documentTypes: [],
|
||||
contentDirPath: 'src/content/workshop',
|
||||
documentTypes: [Workshop],
|
||||
})
|
||||
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
# Compose settings
|
||||
FRONTEND_IMAGE=ghcr.io/cloud-neutral-toolkit/dashboard:replace-me
|
||||
CANONICAL_DOMAIN=www.svc.plus
|
||||
SERVED_DOMAINS=www.svc.plus,console.svc.plus
|
||||
|
||||
# Frontend runtime
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
RUNTIME_ENV=prod
|
||||
REGION=cn
|
||||
APP_BASE_URL=https://www.svc.plus
|
||||
NEXT_PUBLIC_APP_BASE_URL=https://www.svc.plus
|
||||
NEXT_PUBLIC_SITE_URL=https://www.svc.plus
|
||||
NEXT_PUBLIC_LOGIN_URL=https://www.svc.plus/login
|
||||
NEXT_PUBLIC_DOCS_BASE_URL=https://www.svc.plus/docs
|
||||
SESSION_COOKIE_SECURE=true
|
||||
NEXT_PUBLIC_SESSION_COOKIE_SECURE=true
|
||||
RUNTIME_HOSTNAME=www.svc.plus
|
||||
DEPLOYMENT_HOSTNAME=www.svc.plus
|
||||
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=prod
|
||||
NEXT_PUBLIC_RUNTIME_REGION=cn
|
||||
|
||||
# Upstream service URLs
|
||||
ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
NEXT_PUBLIC_ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
SERVER_SERVICE_URL=https://api.svc.plus
|
||||
NEXT_PUBLIC_SERVER_SERVICE_URL=https://api.svc.plus
|
||||
SERVER_SERVICE_INTERNAL_URL=
|
||||
|
||||
# Optional integrations
|
||||
OPENCLAW_GATEWAY_REMOTE_URL=
|
||||
OPENCLAW_GATEWAY_TOKEN=
|
||||
VAULT_SERVER_URL=
|
||||
VAULT_NAMESPACE=
|
||||
VAULT_TOKEN=
|
||||
APISIX_AI_GATEWAY_URL=
|
||||
AI_GATEWAY_ACCESS_TOKEN=
|
||||
INTERNAL_SERVICE_TOKEN=
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
|
||||
CLOUDFLARE_ZONE_TAG=
|
||||
ROOT_EMAIL_WHITELIST=admin@svc.plus
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID=
|
||||
NEXT_PUBLIC_GISCUS_REPO=cloud-neutral-toolkit/console.svc.plus
|
||||
NEXT_PUBLIC_GISCUS_REPO_ID=
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY=General
|
||||
NEXT_PUBLIC_GISCUS_CATEGORY_ID=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=
|
||||
@ -1,28 +0,0 @@
|
||||
{$SERVED_DOMAINS} {
|
||||
encode zstd gzip
|
||||
|
||||
handle_path /_next/static/* {
|
||||
root * /srv
|
||||
header Cache-Control "public, max-age=31536000, immutable"
|
||||
file_server
|
||||
}
|
||||
|
||||
@public_assets {
|
||||
file {
|
||||
root /srv/public
|
||||
try_files {path}
|
||||
}
|
||||
}
|
||||
handle @public_assets {
|
||||
root * /srv/public
|
||||
header Cache-Control "public, max-age=3600"
|
||||
file_server
|
||||
}
|
||||
|
||||
reverse_proxy dashboard:3000 {
|
||||
header_up Host {host}
|
||||
header_up X-Forwarded-Host {host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
services:
|
||||
frontend-assets:
|
||||
image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE in .env.runtime}
|
||||
restart: "no"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -eu
|
||||
rm -rf /assets/_next /assets/chunks /assets/public
|
||||
mkdir -p /assets /assets/public
|
||||
cp -R /app/dashboard/static/. /assets/
|
||||
cp -R /app/dashboard/public/. /assets/public
|
||||
volumes:
|
||||
- frontend_static:/assets
|
||||
|
||||
dashboard:
|
||||
image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE in .env.runtime}
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env.runtime
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
volumes:
|
||||
- frontend_static:/app/dashboard/.next/static:ro
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
caddy:
|
||||
image: caddy:2.10-alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- dashboard
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
SERVED_DOMAINS: ${SERVED_DOMAINS:?set SERVED_DOMAINS in .env.runtime}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- frontend_static:/srv:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
frontend_static:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
39
dev.log
39
dev.log
@ -1,39 +0,0 @@
|
||||
▲ Next.js 16.1.6 (Turbopack)
|
||||
|
||||
- Local: http://localhost:3000
|
||||
- Network: http://192.168.0.2:3000
|
||||
|
||||
✓ Starting...
|
||||
|
||||
Attention: Next.js now collects completely anonymous telemetry regarding usage.
|
||||
This information is used to shape Next.js' roadmap and prioritize features.
|
||||
|
||||
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
|
||||
https://nextjs.org/telemetry
|
||||
|
||||
|
||||
✓ Ready in 4.4s
|
||||
|
||||
○ Compiling / ...
|
||||
|
||||
GET / 200 in 11.8s (compile: 11.0s, render: 755ms)
|
||||
|
||||
GET / 200 in 513ms (compile: 159ms, render: 354ms)
|
||||
|
||||
GET /api/integrations/defaults 200 in 1276ms (compile: 1242ms, render: 35ms)
|
||||
|
||||
[runtime-config] Loaded env: PROD
|
||||
|
||||
GET /api/auth/session 200 in 2.2s (compile: 2.2s, render: 15ms)
|
||||
|
||||
GET /api/marketing/home-stats 200 in 1435ms (compile: 1425ms, render: 9ms)
|
||||
|
||||
GET / 200 in 150ms (compile: 5ms, render: 145ms)
|
||||
|
||||
GET /api/auth/session 200 in 16ms (compile: 4ms, render: 12ms)
|
||||
|
||||
GET /api/integrations/defaults 200 in 20ms (compile: 12ms, render: 8ms)
|
||||
|
||||
GET /api/marketing/home-stats 200 in 29ms (compile: 22ms, render: 7ms)
|
||||
|
||||
GET /api/blogs/latest?limit=7 200 in 37ms (compile: 29ms, render: 8ms)
|
||||
@ -1,14 +0,0 @@
|
||||
# Documentation Coverage Matrix
|
||||
|
||||
This matrix tracks the bilingual canonical documentation set for `console.svc.plus` and maps it back to the current codebase and older docs.
|
||||
|
||||
该矩阵用于跟踪 `console.svc.plus` 的双语规范文档,并将其与当前代码状态和历史文档对应起来。
|
||||
|
||||
| Category | EN | ZH | Current status | Existing references | Next check |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Architecture | Yes | Yes | Seeded from current codebase and existing docs. | `api/overview.md`<br>`architecture/components.md`<br>`architecture/design-decisions.md`<br>`architecture/overview.md`<br>`architecture/roadmap.md`<br>`development/code-structure.md`<br>`zh/api/overview.md`<br>`zh/architecture/components.md` | Keep diagrams and ownership notes synchronized with actual directories, services, and integration dependencies. |
|
||||
| Design | Yes | Yes | Seeded from current codebase and existing docs. | `SEO-WORK-SUMMARY.md`<br>`architecture/design-decisions.md`<br>`zh/SEO-WORK-SUMMARY.md`<br>`zh/architecture/design-decisions.md` | Promote one-off implementation notes into reusable design records when behavior, APIs, or deployment contracts change. |
|
||||
| Deployment | Yes | Yes | Seeded from current codebase and existing docs. | `development/dev-setup.md`<br>`getting-started/installation.md`<br>`getting-started/quickstart.md`<br>`governance/release-process.md`<br>`operations/runbooks/README.md`<br>`operations/runbooks/rag-server.md`<br>`usage/deployment.md`<br>`zh/development/dev-setup.md` | Verify deployment steps against current scripts, manifests, CI/CD flow, and environment contracts before each release. |
|
||||
| User Guide | Yes | Yes | Seeded from current codebase and existing docs. | `api/overview.md`<br>`architecture/overview.md`<br>`getting-started/concepts.md`<br>`getting-started/installation.md`<br>`getting-started/introduction.md`<br>`getting-started/quickstart.md`<br>`usage/cli.md`<br>`usage/config.md` | Prefer workflow-oriented examples and keep screenshots or terminal snippets aligned with the latest UI or CLI behavior. |
|
||||
| Developer Guide | Yes | Yes | Seeded from current codebase and existing docs. | `api/auth.md`<br>`api/endpoints.md`<br>`api/errors.md`<br>`api/overview.md`<br>`development/code-structure.md`<br>`development/contributing.md`<br>`development/dev-setup.md`<br>`development/testing.md` | Keep setup and test commands tied to actual package scripts, Make targets, or language toolchains in this repository. |
|
||||
| Vibe Coding Reference | Yes | Yes | Seeded from current codebase and existing docs. | `api/auth.md`<br>`api/endpoints.md`<br>`api/errors.md`<br>`api/overview.md`<br>`zh/api/auth.md`<br>`zh/api/endpoints.md`<br>`zh/api/errors.md`<br>`zh/api/overview.md` | Review prompt templates and repo rules whenever the project adds new subsystems, protected areas, or mandatory verification steps. |
|
||||
@ -1,11 +1,3 @@
|
||||
# Development Setup
|
||||
|
||||
## Purpose
|
||||
|
||||
- This document covers local development setup and tooling notes.
|
||||
|
||||
## Module System Migration
|
||||
|
||||
# 🔄 CommonJS → ES Module 迁移指南
|
||||
|
||||
## 📊 迁移概览
|
||||
@ -71,3 +63,4 @@ npm run build
|
||||
3. **静态分析**: IDE 支持更好
|
||||
4. **未来兼容**: ECMAScript 标准
|
||||
5. **性能提升**: 更好的模块加载
|
||||
|
||||
@ -1,42 +0,0 @@
|
||||
# Console Service Plus / 控制台服务
|
||||
|
||||
This `docs/` directory now has a bilingual canonical layer for the current repository state.
|
||||
|
||||
本 `docs/` 目录现已补齐双语规范层,用于承接当前仓库状态下的核心文档。
|
||||
|
||||
## Quick Entry / 快速入口
|
||||
|
||||
- Coverage checklist / 覆盖检查矩阵: `docs/DOC_COVERAGE.md`
|
||||
- English index / 英文入口: `docs/en/README.md`
|
||||
- 中文入口 / Chinese index: `docs/zh/README.md`
|
||||
|
||||
## Canonical Bilingual Pages / 双语规范页
|
||||
|
||||
- `docs/en/architecture.md` / `docs/zh/architecture.md`
|
||||
- `docs/en/design.md` / `docs/zh/design.md`
|
||||
- `docs/en/deployment.md` / `docs/zh/deployment.md`
|
||||
- `docs/en/user-guide.md` / `docs/zh/user-guide.md`
|
||||
- `docs/en/developer-guide.md` / `docs/zh/developer-guide.md`
|
||||
- `docs/en/vibe-coding-reference.md` / `docs/zh/vibe-coding-reference.md`
|
||||
|
||||
## Current Repo Context / 当前仓库背景
|
||||
|
||||
- Root README: `console.svc.plus`
|
||||
- Previous docs index: `Documentation`
|
||||
- Manifest evidence / 构建清单: package.json (`dashboard`)
|
||||
- Active code and ops directories / 当前主要目录: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
|
||||
## Existing Docs To Reconcile / 需要继续归并的现有文档
|
||||
|
||||
- `SEO-AUDIT-REPORT.md`
|
||||
- `SEO-WORK-SUMMARY.md`
|
||||
- `advanced/customization.md`
|
||||
- `advanced/performance.md`
|
||||
- `advanced/scalability.md`
|
||||
- `advanced/security.md`
|
||||
- `api/auth.md`
|
||||
- `api/endpoints.md`
|
||||
- `api/errors.md`
|
||||
- `api/overview.md`
|
||||
- `appendix/faq.md`
|
||||
- `appendix/glossary.md`
|
||||
@ -1,467 +0,0 @@
|
||||
# SEO Audit Report - console.svc.plus
|
||||
|
||||
**Date**: 2026-01-29
|
||||
**Audited By**: Antigravity AI
|
||||
**Scope**: SEO optimization without changing functionality
|
||||
|
||||
---
|
||||
|
||||
## 📊 Summary of Issues
|
||||
|
||||
Based on Google Search Console data:
|
||||
- **404 Errors**: 804 pages
|
||||
- **Duplicate Pages**: 299 instances
|
||||
- **Redirect Issues**: 8 instances
|
||||
- **5xx Errors**: 6 instances
|
||||
- **Soft 404**: 3 instances
|
||||
- **Missing noindex**: 1 instance
|
||||
- **robots.txt Blocked**: 1 instance
|
||||
- **401 Errors**: 1 instance
|
||||
- **Missing Index**: 235 instances
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Critical Issues
|
||||
|
||||
### 1. Dead Links (404 Errors) - 804 Pages
|
||||
|
||||
**Problem**: Numerous `href="#"` placeholders throughout the codebase
|
||||
|
||||
**Affected Files**:
|
||||
- `src/app/page.tsx` (line 259)
|
||||
- `src/components/Header.tsx` (lines 22, 25, 28)
|
||||
- `src/components/DownloadSection.tsx` (line 68)
|
||||
- `src/app/(auth)/login/LoginContent.tsx` (line 299)
|
||||
- `src/app/(auth)/login/LoginForm.tsx` (line 303)
|
||||
|
||||
**Impact**:
|
||||
- Poor user experience
|
||||
- Negative SEO ranking
|
||||
- Crawl budget waste
|
||||
|
||||
**Fix Priority**: 🔴 HIGH
|
||||
|
||||
---
|
||||
|
||||
### 2. Missing not-found.tsx
|
||||
|
||||
**Problem**: No custom 404 page at app root level
|
||||
|
||||
**Current State**:
|
||||
- Has `/404/page.tsx` but not `not-found.tsx`
|
||||
- Next.js 13+ App Router requires `not-found.tsx` for proper 404 handling
|
||||
|
||||
**Impact**:
|
||||
- Improper 404 handling
|
||||
- Missing SEO metadata on 404 pages
|
||||
|
||||
**Fix Priority**: 🔴 HIGH
|
||||
|
||||
---
|
||||
|
||||
### 3. Incomplete SEO Metadata
|
||||
|
||||
**Problem**: Root layout missing essential SEO tags
|
||||
|
||||
**Current State** (`src/app/layout.tsx`):
|
||||
```typescript
|
||||
export const metadata = {
|
||||
title: 'Cloud-Neutral',
|
||||
description: 'Unified tools for your cloud native stack',
|
||||
}
|
||||
```
|
||||
|
||||
**Missing**:
|
||||
- Open Graph tags
|
||||
- Twitter Card tags
|
||||
- Canonical URLs
|
||||
- Viewport meta tag
|
||||
- Theme color
|
||||
- Robots meta tag
|
||||
|
||||
**Fix Priority**: 🟡 MEDIUM
|
||||
|
||||
---
|
||||
|
||||
### 4. Anchor Links Without Proper Targets
|
||||
|
||||
**Problem**: Hash links (`#features`, `#docs`, etc.) without corresponding IDs
|
||||
|
||||
**Affected Files**:
|
||||
- `src/app/[slug]/Client.tsx` (lines 146-161)
|
||||
- `src/components/marketing/ProductScenarios.tsx` (lines 57, 71)
|
||||
- `src/components/marketing/ProductDownload.tsx` (line 88)
|
||||
|
||||
**Impact**:
|
||||
- Broken in-page navigation
|
||||
- Poor user experience
|
||||
- Potential crawl errors
|
||||
|
||||
**Fix Priority**: 🟡 MEDIUM
|
||||
|
||||
---
|
||||
|
||||
### 5. robots.txt Configuration Issues
|
||||
|
||||
**Problem**: Conflicting rules in robots.txt
|
||||
|
||||
**Current State**:
|
||||
```
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
Allow: /_next/static/
|
||||
Allow: /_next/image
|
||||
Disallow: /admin/
|
||||
Disallow: /api/
|
||||
Disallow: /internal/
|
||||
Disallow: /_next/ # ⚠️ Conflicts with Allow above
|
||||
```
|
||||
|
||||
**Fix Priority**: 🟡 MEDIUM
|
||||
|
||||
---
|
||||
|
||||
### 6. Missing Structured Data
|
||||
|
||||
**Problem**: No JSON-LD structured data for rich snippets
|
||||
|
||||
**Missing**:
|
||||
- Organization schema
|
||||
- WebSite schema
|
||||
- BreadcrumbList schema
|
||||
- Article schema (for blog posts)
|
||||
|
||||
**Fix Priority**: 🟢 LOW
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Recommended Fixes
|
||||
|
||||
### Fix 1: Replace All `href="#"` Links
|
||||
|
||||
**Action**: Replace placeholder links with actual URLs or remove them
|
||||
|
||||
```typescript
|
||||
// ❌ Before
|
||||
<a href="#">Learn more</a>
|
||||
|
||||
// ✅ After (Option 1: Real link)
|
||||
<Link href="/docs/getting-started">Learn more</Link>
|
||||
|
||||
// ✅ After (Option 2: Button if not navigating)
|
||||
<button onClick={handleAction}>Learn more</button>
|
||||
|
||||
// ✅ After (Option 3: Disabled state)
|
||||
<span className="text-muted cursor-not-allowed">Coming soon</span>
|
||||
```
|
||||
|
||||
**Files to Update**:
|
||||
1. `src/app/page.tsx`
|
||||
2. `src/components/Header.tsx`
|
||||
3. `src/components/DownloadSection.tsx`
|
||||
4. `src/app/(auth)/login/LoginContent.tsx`
|
||||
5. `src/app/(auth)/login/LoginForm.tsx`
|
||||
|
||||
---
|
||||
|
||||
### Fix 2: Add not-found.tsx
|
||||
|
||||
**Action**: Create proper 404 handler
|
||||
|
||||
**File**: `src/app/not-found.tsx`
|
||||
|
||||
```typescript
|
||||
import type { Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '404 - Page Not Found | Cloud-Neutral',
|
||||
description: 'The page you are looking for does not exist.',
|
||||
robots: {
|
||||
index: false,
|
||||
follow: false,
|
||||
},
|
||||
}
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-center bg-background px-4 py-24 text-center">
|
||||
<p className="text-sm font-semibold uppercase tracking-wide text-primary">404</p>
|
||||
<h1 className="mt-4 text-4xl font-bold text-heading">Page not found</h1>
|
||||
<p className="mt-3 max-w-md text-sm text-text-muted">
|
||||
The page you were looking for could not be found. Please return to the homepage.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="mt-6 inline-flex items-center rounded-full bg-primary px-5 py-2 text-sm font-semibold text-white shadow hover:bg-primary-hover"
|
||||
>
|
||||
Back to homepage
|
||||
</Link>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fix 3: Enhanced SEO Metadata
|
||||
|
||||
**Action**: Update root layout with comprehensive metadata
|
||||
|
||||
**File**: `src/app/layout.tsx`
|
||||
|
||||
```typescript
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://www.svc.plus'),
|
||||
title: {
|
||||
default: 'Cloud-Neutral | Unified Cloud Native Tools',
|
||||
template: '%s | Cloud-Neutral',
|
||||
},
|
||||
description: 'Unified tools for your cloud native stack. Manage infrastructure, deployments, and services across multiple cloud providers.',
|
||||
keywords: ['cloud native', 'kubernetes', 'infrastructure', 'devops', 'cloud management'],
|
||||
authors: [{ name: 'Cloud-Neutral Team' }],
|
||||
creator: 'Cloud-Neutral',
|
||||
publisher: 'Cloud-Neutral',
|
||||
formatDetection: {
|
||||
email: false,
|
||||
address: false,
|
||||
telephone: false,
|
||||
},
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
url: 'https://www.svc.plus',
|
||||
title: 'Cloud-Neutral | Unified Cloud Native Tools',
|
||||
description: 'Unified tools for your cloud native stack',
|
||||
siteName: 'Cloud-Neutral',
|
||||
images: [
|
||||
{
|
||||
url: '/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Cloud-Neutral Platform',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Cloud-Neutral | Unified Cloud Native Tools',
|
||||
description: 'Unified tools for your cloud native stack',
|
||||
images: ['/og-image.png'],
|
||||
creator: '@cloudneutral',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
'max-video-preview': -1,
|
||||
'max-image-preview': 'large',
|
||||
'max-snippet': -1,
|
||||
},
|
||||
},
|
||||
verification: {
|
||||
google: 'your-google-verification-code',
|
||||
},
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#6366f1" />
|
||||
<link rel="canonical" href="https://www.svc.plus" />
|
||||
{/* ... rest of head */}
|
||||
</head>
|
||||
{/* ... rest of layout */}
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fix 4: Add Section IDs for Anchor Links
|
||||
|
||||
**Action**: Add proper `id` attributes to sections
|
||||
|
||||
**File**: `src/app/[slug]/Client.tsx`
|
||||
|
||||
```typescript
|
||||
// Add IDs to sections
|
||||
<section id="features">
|
||||
{/* Features content */}
|
||||
</section>
|
||||
|
||||
<section id="editions">
|
||||
{/* Editions content */}
|
||||
</section>
|
||||
|
||||
<section id="scenarios">
|
||||
{/* Scenarios content */}
|
||||
</section>
|
||||
|
||||
<section id="download">
|
||||
{/* Download content */}
|
||||
</section>
|
||||
|
||||
<section id="docs">
|
||||
{/* Docs content */}
|
||||
</section>
|
||||
|
||||
<section id="faq">
|
||||
{/* FAQ content */}
|
||||
</section>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fix 5: Clean Up robots.txt
|
||||
|
||||
**Action**: Remove conflicting rules
|
||||
|
||||
**File**: `public/robots.txt`
|
||||
|
||||
```txt
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
Allow: /_next/static/
|
||||
Allow: /_next/image
|
||||
Disallow: /admin/
|
||||
Disallow: /api/
|
||||
Disallow: /internal/
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Allow: /_next/static/
|
||||
Allow: /_next/image
|
||||
Disallow: /admin/
|
||||
Disallow: /api/
|
||||
Disallow: /internal/
|
||||
|
||||
Sitemap: https://www.svc.plus/sitemap.xml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fix 6: Add Structured Data
|
||||
|
||||
**Action**: Add JSON-LD schemas
|
||||
|
||||
**File**: `src/app/layout.tsx`
|
||||
|
||||
```typescript
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const organizationSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: 'Cloud-Neutral',
|
||||
url: 'https://www.svc.plus',
|
||||
logo: 'https://www.svc.plus/logo.png',
|
||||
sameAs: [
|
||||
'https://twitter.com/cloudneutral',
|
||||
'https://github.com/x-evor',
|
||||
],
|
||||
}
|
||||
|
||||
const websiteSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'Cloud-Neutral',
|
||||
url: 'https://www.svc.plus',
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: 'https://www.svc.plus/search?q={search_term_string}',
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
{/* ... other head elements */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema) }}
|
||||
/>
|
||||
</head>
|
||||
{/* ... rest */}
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Implementation Checklist
|
||||
|
||||
### Phase 1: Critical Fixes (Week 1)
|
||||
- [ ] Replace all `href="#"` with proper URLs or buttons
|
||||
- [ ] Create `src/app/not-found.tsx`
|
||||
- [ ] Add section IDs for anchor links
|
||||
- [ ] Update root layout metadata
|
||||
|
||||
### Phase 2: Important Fixes (Week 2)
|
||||
- [ ] Clean up robots.txt
|
||||
- [ ] Add structured data (JSON-LD)
|
||||
- [ ] Create OG image (`public/og-image.png`)
|
||||
- [ ] Add canonical URLs to all pages
|
||||
|
||||
### Phase 3: Optimization (Week 3)
|
||||
- [ ] Add breadcrumb schema to docs pages
|
||||
- [ ] Add article schema to blog posts
|
||||
- [ ] Implement dynamic metadata for all pages
|
||||
- [ ] Add alt text to all images
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Expected Improvements
|
||||
|
||||
After implementing these fixes:
|
||||
|
||||
1. **404 Errors**: Should drop from 804 to <10
|
||||
2. **Crawl Efficiency**: Improved by ~60%
|
||||
3. **SEO Score**: Expected increase of 15-20 points
|
||||
4. **User Experience**: Significantly better navigation
|
||||
5. **Search Rankings**: Gradual improvement over 2-3 months
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
After deployment, monitor:
|
||||
|
||||
1. **Google Search Console**:
|
||||
- Coverage report
|
||||
- Core Web Vitals
|
||||
- Mobile usability
|
||||
|
||||
2. **Analytics**:
|
||||
- Bounce rate on 404 page
|
||||
- Navigation patterns
|
||||
- Search traffic
|
||||
|
||||
3. **Tools**:
|
||||
- Lighthouse CI
|
||||
- Ahrefs/SEMrush
|
||||
- Screaming Frog
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Resources
|
||||
|
||||
- [Next.js SEO Best Practices](https://nextjs.org/learn/seo/introduction-to-seo)
|
||||
- [Google Search Central](https://developers.google.com/search)
|
||||
- [Schema.org Documentation](https://schema.org/)
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**: Review this report and prioritize fixes based on business impact.
|
||||
@ -1,165 +0,0 @@
|
||||
# SEO 优化工作总结
|
||||
|
||||
**日期**: 2026-01-29
|
||||
**项目**: console.svc.plus + page-reading-agent 集成
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 1. console.svc.plus SEO 优化
|
||||
|
||||
#### 创建的文件:
|
||||
1. **`docs/SEO-AUDIT-REPORT.md`** - 完整的 SEO 审计报告
|
||||
- 发现 804 个 404 错误
|
||||
- 识别死链、缺失元数据等问题
|
||||
- 提供详细修复方案
|
||||
|
||||
2. **`src/app/not-found.tsx`** - 自定义 404 页面
|
||||
- 包含 SEO 元数据(noindex, nofollow)
|
||||
- 提供友好的用户体验
|
||||
- 包含导航链接
|
||||
|
||||
3. **`public/robots.txt`** - 修复后的 robots.txt
|
||||
- 移除冲突规则
|
||||
- 正确配置爬虫访问权限
|
||||
|
||||
4. **`src/app/layout.tsx`** - 增强的 SEO 元数据
|
||||
- Open Graph tags
|
||||
- Twitter Card tags
|
||||
- JSON-LD 结构化数据(Organization, WebSite)
|
||||
- Viewport 和 theme-color
|
||||
|
||||
5. **`scripts/check-seo-issues.js`** - SEO 检查脚本
|
||||
- 扫描死链 (href="#")
|
||||
- 检查缺失 alt 文本
|
||||
- 验证锚点链接
|
||||
|
||||
#### Git 状态:
|
||||
- ✅ 已提交到 console.svc.plus
|
||||
- ✅ Commit: `598b113 feat: add comprehensive SEO metadata...`
|
||||
|
||||
---
|
||||
|
||||
### 2. page-reading-agent-backend SEO 集成
|
||||
|
||||
#### 创建的文件:
|
||||
1. **`core/seo-audit.js`** - SEO 审计模块
|
||||
- 元数据分析
|
||||
- 标题结构分析
|
||||
- 图片 alt 文本检查
|
||||
- 链接分析(内部/外部/死链)
|
||||
- 结构化数据检测
|
||||
- 性能指标
|
||||
- 移动端适配检查
|
||||
- 内容分析
|
||||
- 评分系统
|
||||
- 问题分类(critical/warnings/suggestions)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 待完成的工作
|
||||
|
||||
### 1. page-reading-agent-backend API 集成
|
||||
|
||||
需要在 `main.js` 中添加 SEO 审计端点:
|
||||
|
||||
```javascript
|
||||
// 在 main.js 中添加
|
||||
import { auditSEO, generateSEOReport } from './core/seo-audit.js';
|
||||
|
||||
// 添加新的路由
|
||||
if (req.url.startsWith('/seo-audit')) {
|
||||
// ... SEO 审计逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### 2. page-reading-agent-dashboard 前端集成
|
||||
|
||||
需要创建:
|
||||
1. SEO 审计结果展示组件
|
||||
2. 右侧面板显示
|
||||
3. 报告导出功能
|
||||
4. MCP 查询接口
|
||||
|
||||
### 3. moltbot.svc.plus Git 同步
|
||||
|
||||
当前状态:
|
||||
- ⏳ Rebase 正在进行中
|
||||
- 📝 README.md 冲突已解决(采用上游版本)
|
||||
- 🔄 等待 rebase 完成后推送
|
||||
|
||||
---
|
||||
|
||||
## 📋 下一步行动计划
|
||||
|
||||
### 优先级 1: 完成 moltbot.svc.plus Git 同步
|
||||
```bash
|
||||
# 等待 rebase 完成
|
||||
# 然后推送
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 优先级 2: 集成 SEO 到 page-reading-agent-backend
|
||||
|
||||
1. **修改 main.js 添加 SEO 端点**:
|
||||
```javascript
|
||||
if (req.url.startsWith('/seo-audit')) {
|
||||
const audit = await auditSEO(page, currentTaskConfig.url);
|
||||
const report = generateSEOReport(audit);
|
||||
res.end(JSON.stringify(report));
|
||||
}
|
||||
```
|
||||
|
||||
2. **添加 MCP 工具支持**:
|
||||
- 创建 MCP 工具定义
|
||||
- 注册 SEO 审计功能
|
||||
|
||||
### 优先级 3: page-reading-agent-dashboard 前端
|
||||
|
||||
1. **创建 SEO 结果组件**:
|
||||
```typescript
|
||||
// components/SEOAuditPanel.tsx
|
||||
- 显示总分
|
||||
- 显示问题列表
|
||||
- 显示详细指标
|
||||
```
|
||||
|
||||
2. **添加导出功能**:
|
||||
```typescript
|
||||
// 导出为 JSON
|
||||
// 导出为 Markdown
|
||||
// 导出为 PDF(可选)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 预期效果
|
||||
|
||||
### SEO 优化后的改进:
|
||||
- 404 错误: 804 → <10
|
||||
- 爬虫效率: +60%
|
||||
- SEO 评分: +15-20 分
|
||||
- 用户体验: 显著改善
|
||||
- 搜索排名: 2-3 个月内逐步提升
|
||||
|
||||
### page-reading-agent 集成后的功能:
|
||||
- ✅ 自动化 SEO 审计
|
||||
- ✅ 实时问题检测
|
||||
- ✅ 可视化报告
|
||||
- ✅ 导出功能
|
||||
- ✅ MCP 查询支持
|
||||
|
||||
---
|
||||
|
||||
## 📝 备注
|
||||
|
||||
1. **console.svc.plus** 的 SEO 优化已完成并提交
|
||||
2. **page-reading-agent** 的 SEO 模块已创建,等待集成
|
||||
3. **moltbot.svc.plus** 正在进行 Git rebase,解决冲突中
|
||||
|
||||
---
|
||||
|
||||
**状态**: 🟡 进行中
|
||||
**完成度**: 60%
|
||||
**预计完成时间**: 1-2 小时
|
||||
@ -1,9 +0,0 @@
|
||||
# Customization
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Customization.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Performance
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Performance.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Scalability
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Scalability.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Security
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Security.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Auth
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Auth.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Endpoints
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Endpoints.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Errors
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Errors.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Overview
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Overview.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Faq
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Faq.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Glossary
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Glossary.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# References
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to References.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Components
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Components.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,63 +0,0 @@
|
||||
# Documentation Page Redesign Specification
|
||||
|
||||
## 1. Overview
|
||||
Refactor the `/docs` section of `console.svc.plus` to strictly load content from `cloud-neutral-workshop/knowledge/docs`. The UI will be redesigned to match the [Pigsty Documentation](https://pigsty.cc/docs/) layout while maintaining the existing `console.svc.plus` visual identity (Tailwind tokens, fonts, and colors).
|
||||
|
||||
## 2. Layout Structure
|
||||
|
||||
The layout will consist of a fixed header and a three-column content area (on desktop).
|
||||
|
||||
### 2.1. Global Header (Modified)
|
||||
- **Content**: Logo, Main Nav (Home, Docs, Blog, etc.), Search Bar, GitHub Link, User Profile.
|
||||
- **Additions**:
|
||||
- **Version Selector**: Dropdown to switch between documentation versions (if available).
|
||||
- **Search**: Integrated Algolia/Command-K search bar.
|
||||
|
||||
### 2.2. Left Sidebar (Navigation)
|
||||
- **Behavior**: Sticky, independently scrollable.
|
||||
- **Content**:
|
||||
- Tree-view navigation structure matching the directory structure of `knowledge/docs`.
|
||||
- **Expandable/Collapsible**: Categories should be collapsible folders.
|
||||
- **Active State**: clear visual indicator for current page.
|
||||
|
||||
### 2.3. Main Content Area (Center)
|
||||
- **Typography**: Optimized for long-form reading (prose, decent line-height).
|
||||
- **Elements**:
|
||||
- Breadcrumbs at the top.
|
||||
- H1 Title.
|
||||
- Last updated timestamp.
|
||||
- Content rendered via MDX/Markdown.
|
||||
- **Feedback Section (Footer)**: "Is this page helpful?" (Yes/No buttons) similar to Pigsty.
|
||||
- Prev/Next page navigation links.
|
||||
|
||||
### 2.4. Right Sidebar (Table of Contents & Meta)
|
||||
- **Behavior**: Sticky, hidden on mobile.
|
||||
- **Content**:
|
||||
- **On this Page**: Auto-generated TOC from H2/H3 headers.
|
||||
- **Metadata**:
|
||||
- "Module": Tags or categorization pills.
|
||||
- "Edit this page": Link to GitHub source.
|
||||
- "Contributors": List of contributors (optional).
|
||||
|
||||
## 3. Visual Style & Theming
|
||||
- **Colors**: Use existing `brand-*` and `surface-*` tokens from `console.svc.plus`.
|
||||
- Sidebar Background: `bg-surface-muted` or `bg-background` with right border.
|
||||
- Active Link: `text-primary` with `bg-primary/10` background.
|
||||
- **Responsiveness**:
|
||||
- **Mobile**: Hambergur menu to open Sidebar. TOC hidden or moved to top of content (Accordion).
|
||||
|
||||
## 4. Implementation Plan
|
||||
|
||||
### 4.1. Data Source
|
||||
- Ensure `scripts/sync-doc-content.sh` pulls specifically from `knowledge/docs`.
|
||||
- Update `contentlayer` or `next-mdx-remote` configuration to handle the nested structure of `docs/`.
|
||||
|
||||
### 4.2. Components
|
||||
1. **`DocsLayout`**: Wrapper for the 3-column grid.
|
||||
2. **`SidebarTree`**: Recursive component for navigation.
|
||||
3. **`TOC`**: Component to parse headings and display right sidebar.
|
||||
4. **`FeedbackWidget`**: Simple interactivity for user sentiment.
|
||||
|
||||
## 5. Reference
|
||||
- **Inspiration**: [Pigsty Docs](https://pigsty.cc/docs/)
|
||||
- **Theme**: Cloud-Neutral Toolkit (Dark/Light mode support).
|
||||
@ -1,9 +0,0 @@
|
||||
# Overview
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Overview.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Roadmap
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Roadmap.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,96 +0,0 @@
|
||||
# console.svc.plus Web Console Architecture
|
||||
|
||||
## Scope
|
||||
|
||||
`console.svc.plus` is the browser-facing control plane. It is a Next.js App Router application that combines public pages, docs browsing, account/admin panels, and a BFF layer that forwards requests to downstream services. It never reads PostgreSQL or Prometheus directly for billing or usage.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Pages["src/app pages"]
|
||||
Root["/ -> landing page"]
|
||||
Docs["/docs, /docs/[collection], /docs/[collection]/[...slug]\nDocs reader"]
|
||||
Auth["/login, /register, /email-verification, /logout\nAuth flows"]
|
||||
Panel["/panel/*\nUser / admin console"]
|
||||
Tools["/editor/*, /download/*, /cloud_iac/*, /xworkmate/*\nProduct tools"]
|
||||
Content["/blogs/*, /services/*, /support/*, /prices, /about, /privacy, /terms, /[slug]"]
|
||||
Admin["/dashboard/cms\nCMS/admin entry"]
|
||||
end
|
||||
|
||||
subgraph BFF["src/app/api route handlers"]
|
||||
AuthAPI["/api/auth/*"]
|
||||
AdminAPI["/api/admin/*"]
|
||||
AgentAPI["/api/agent-server/[...segments]\n/api/agent/[...segments]"]
|
||||
RagAPI["/api/rag/query\n/api/askai"]
|
||||
UtilAPI["/api/users\n/api/ping\n/api/content-meta\n/api/render-markdown\n/api/dl-index/*\n/api/marketing/home-stats\n/api/integrations/*\n/api/moltbot/chat\n/api/openclaw/assistant\n/api/task/[...segments]\n/api/xworkmate/profile"]
|
||||
GuestAPI["/api/guest/*"]
|
||||
end
|
||||
|
||||
Accounts["accounts.svc.plus"]
|
||||
Rag["rag-server.svc.plus"]
|
||||
DocsSvc["docs.svc.plus"]
|
||||
Grafana["observability.svc.plus / Grafana"]
|
||||
External["Other upstream services"]
|
||||
Subscription["/panel/subscription\nUsage / billing panel"]
|
||||
|
||||
AuthAPI --> Accounts
|
||||
AdminAPI --> Accounts
|
||||
AgentAPI --> Accounts
|
||||
RagAPI --> Rag
|
||||
UtilAPI --> DocsSvc
|
||||
UtilAPI --> External
|
||||
SandboxAPI --> Accounts
|
||||
Subscription --> Accounts
|
||||
Subscription -.-> Grafana
|
||||
```
|
||||
|
||||
## Frontend Routes
|
||||
|
||||
| Route family | Path | Purpose |
|
||||
| --- | --- | --- |
|
||||
| Home | `/` | Public landing page and site entry |
|
||||
| Docs | `/docs`, `/docs/[collection]`, `/docs/[collection]/[...slug]` | Documentation reader, sidebar, and TOC |
|
||||
| Auth | `/login`, `/register`, `/email-verification`, `/logout` | Sign in / sign up / email verification / session cleanup |
|
||||
| Panel | `/panel`, `/panel/account`, `/panel/agent`, `/panel/api`, `/panel/appearance`, `/panel/ldp`, `/panel/management`, `/panel/subscription`, `/panel/[...segments]` | Signed-in account and admin console |
|
||||
| Tools | `/editor`, `/editor/wechat`, `/editor/xiaohongshu`, `/download`, `/download/[...segments]`, `/cloud_iac`, `/cloud_iac/[provider]`, `/cloud_iac/[provider]/[service]`, `/xworkmate`, `/xworkmate/admin`, `/xworkmate/integrations` | Product tools and service explorers |
|
||||
| Content | `/blogs`, `/blogs/[...slug]`, `/services`, `/services/openclaw`, `/services/insight`, `/support`, `/support/discussions`, `/about`, `/prices`, `/privacy`, `/terms`, `/[slug]` | Marketing / informational pages |
|
||||
| Admin / CMS | `/dashboard/cms` | CMS or content-management entry |
|
||||
| Error pages | `/404`, `/500` | Static error surfaces |
|
||||
|
||||
## BFF / API Routes
|
||||
|
||||
| API family | Path | Purpose | Upstream target |
|
||||
| --- | --- | --- | --- |
|
||||
| Auth | `/api/auth/login`, `/api/auth/register`, `/api/auth/register/send`, `/api/auth/register/verify`, `/api/auth/verify-email`, `/api/auth/verify-email/send`, `/api/auth/session`, `/api/auth/token/exchange` | Login, registration, token exchange, session lookup | `accounts.svc.plus/api/auth/*` |
|
||||
| MFA | `/api/auth/mfa/status`, `/api/auth/mfa/setup`, `/api/auth/mfa/verify`, `/api/auth/mfa/disable` | TOTP setup and verification | `accounts.svc.plus/api/auth/*` |
|
||||
| OAuth / billing | `/api/auth/oauth/login/[provider]`, `/api/auth/stripe/checkout`, `/api/auth/stripe/portal`, `/api/auth/subscriptions`, `/api/auth/subscriptions/cancel` | OAuth redirects and billing actions | `accounts.svc.plus/api/auth/*` |
|
||||
| Admin | `/api/admin/settings`, `/api/admin/homepage-video`, `/api/admin/users/*`, `/api/admin/blacklist/*` | Account admin operations | `accounts.svc.plus/api/*` |
|
||||
| Agent bridge | `/api/agent-server/[...segments]`, `/api/agent/[...segments]` | Agent registry/status and legacy alias | `accounts.svc.plus` |
|
||||
| RAG | `/api/rag/query`, `/api/askai` | Retrieval and answer generation | `rag-server.svc.plus` |
|
||||
| Guest / demo runtime | `/api/guest/binding` | Guest read-only node resolution for demo access | `accounts.svc.plus/api/sandbox/binding` |
|
||||
| Content / docs | `/api/content-meta`, `/api/render-markdown`, `/api/blogs/latest`, `/api/dl-index/*` | Docs/content rendering and download manifests | docs / CDN / download service |
|
||||
| Integrations | `/api/integrations/defaults`, `/api/integrations/probe`, `/api/marketing/home-stats` | Integration defaults, health probes, marketing metrics | config-dependent external services |
|
||||
| Misc | `/api/ping`, `/api/users`, `/api/xworkmate/profile`, `/api/task/[...segments]`, `/api/openclaw/assistant`, `/api/moltbot/chat`, `/api/render-markdown` | Health, user lookup, profile, task and assistant proxies | `accounts.svc.plus`, internal API, task services |
|
||||
|
||||
## Auth and Session Notes
|
||||
|
||||
- Browser calls use the session cookie and BFF logic in `src/server/account/session.ts`.
|
||||
- Service-to-service calls use `INTERNAL_SERVICE_TOKEN` when configured.
|
||||
- `api/agent-server/[...segments]` keeps caller `Authorization` untouched when an agent token is already present.
|
||||
- `api/agent-server/[...segments]` forwards the dashboard session token for browser-driven calls.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `accounts.svc.plus` for identity, profile, sandbox, billing, and admin actions.
|
||||
- `accounts.svc.plus` for authoritative usage and billing summaries sourced from PostgreSQL.
|
||||
- `rag-server.svc.plus` for RAG query and AskAI.
|
||||
- `docs.svc.plus` for docs content and navigation data.
|
||||
- `observability.svc.plus` for Grafana dashboards and operational views only.
|
||||
- CDN / external providers for content, analytics, and integration checks.
|
||||
|
||||
## Notes
|
||||
|
||||
- Route groups in parentheses, such as `(auth)`, are Next.js organizational folders and do not appear in the public URL.
|
||||
- The BFF layer is the main place where console-specific auth shaping, cookie management, and upstream proxying happen.
|
||||
- The subscription panel displays usage and billing data from accounts only and treats Grafana as an embedded observability surface, not a billing source.
|
||||
@ -1,9 +0,0 @@
|
||||
# Code Structure
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Code Structure.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Contributing.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Testing
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Testing.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,34 +0,0 @@
|
||||
# Console Service Plus Documentation
|
||||
|
||||
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
|
||||
|
||||
## Current state snapshot
|
||||
|
||||
- Root README title: `console.svc.plus`
|
||||
- Build/runtime evidence: package.json (`dashboard`)
|
||||
- Primary directories detected: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- Existing docs count: 86
|
||||
|
||||
## Canonical pages
|
||||
|
||||
- [Architecture](architecture.md)
|
||||
- [Design](design.md)
|
||||
- [Deployment](deployment.md)
|
||||
- [User Guide](user-guide.md)
|
||||
- [Developer Guide](developer-guide.md)
|
||||
- [Vibe Coding Reference](vibe-coding-reference.md)
|
||||
|
||||
## Legacy docs to fold in
|
||||
|
||||
- `SEO-AUDIT-REPORT.md`
|
||||
- `SEO-WORK-SUMMARY.md`
|
||||
- `advanced/customization.md`
|
||||
- `advanced/performance.md`
|
||||
- `advanced/scalability.md`
|
||||
- `advanced/security.md`
|
||||
- `api/auth.md`
|
||||
- `api/endpoints.md`
|
||||
- `api/errors.md`
|
||||
- `api/overview.md`
|
||||
- `appendix/faq.md`
|
||||
- `appendix/glossary.md`
|
||||
@ -1,31 +0,0 @@
|
||||
# Architecture
|
||||
|
||||
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
|
||||
|
||||
Use this page as the canonical bilingual overview of system boundaries, major components, and repo ownership.
|
||||
|
||||
## Current code-aligned notes
|
||||
|
||||
- Documentation target: `console.svc.plus`
|
||||
- Repo kind: `frontend`
|
||||
- Manifest and build evidence: package.json (`dashboard`)
|
||||
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
|
||||
|
||||
## Existing docs to reconcile
|
||||
|
||||
- `api/overview.md`
|
||||
- `architecture/components.md`
|
||||
- `architecture/design-decisions.md`
|
||||
- `architecture/overview.md`
|
||||
- `architecture/roadmap.md`
|
||||
- `development/code-structure.md`
|
||||
- `zh/api/overview.md`
|
||||
- `zh/architecture/components.md`
|
||||
|
||||
## What this page should cover next
|
||||
|
||||
- Describe the current implementation rather than an aspirational future-only design.
|
||||
- Keep terminology aligned with the repository root README, manifests, and actual directories.
|
||||
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
|
||||
- Keep diagrams and ownership notes synchronized with actual directories, services, and integration dependencies.
|
||||
@ -1,42 +0,0 @@
|
||||
# Deployment
|
||||
|
||||
## Production Baseline
|
||||
|
||||
- Runtime: `Caddy + Docker Compose`
|
||||
- Deploy host: `root@jp-xhttp-contabo.svc.plus`
|
||||
- Public domains:
|
||||
- `www.svc.plus`
|
||||
- `console.svc.plus`
|
||||
- Canonical public origin: `https://www.svc.plus`
|
||||
- Frontend release workflow: `.github/workflows/pipeline.yaml`
|
||||
|
||||
## Operating Model
|
||||
|
||||
The frontend is built in GitHub Actions and shipped as a prebuilt `linux/amd64` image. The host only pulls the image and starts containers; it does not build locally.
|
||||
|
||||
`yarn prebuild` now generates only console-owned marketing artifacts. `/docs` and `/blogs` no longer bundle `knowledge/` or synced markdown content into the frontend image. Those routes fetch rendered content from `docs.svc.plus` at request time through the server-side `docsServiceClient`.
|
||||
|
||||
The stack is static-first:
|
||||
|
||||
- Caddy serves `/_next/static/*` and public assets from a shared read-only volume.
|
||||
- The Next.js standalone container serves dynamic HTML, auth endpoints, and API proxy routes. Static assets and hashed CSS/JS files are extracted by the `frontend-assets` helper task, so the runtime no longer needs to compile anything on the single-node host.
|
||||
- `docs.svc.plus` is the source of truth for rendered docs/blog pages; the browser does not call it directly.
|
||||
|
||||
Releases are orchestrated through `.github/workflows/pipeline.yaml`. That workflow builds/pushes the image, renders `.env.runtime` including `DOCS_SERVICE_URL` / `DOCS_SERVICE_INTERNAL_URL`, and ships `docker-compose.yml`, `Caddyfile`, and the runtime env file to the host. The control-plane DNS automation then updates Cloudflare DNS for the release domains (via `scripts/github-actions/update-release-dns.sh`) so both `www.svc.plus` and `console.svc.plus` resolve to the same environment.
|
||||
|
||||
The release contract now uses:
|
||||
|
||||
- `CANONICAL_DOMAIN=www.svc.plus`
|
||||
- `SERVED_DOMAINS=www.svc.plus,console.svc.plus`
|
||||
|
||||
Validation must pass for both domains. A release is incomplete if either host serves a different runtime version, static asset family, or `dashboardUrl`.
|
||||
|
||||
This baseline is intentional for the weak-IO single-node host `root@jp-xhttp-contabo.svc.plus`. No images are built on the target machine, keeping the deployment lightweight: the host only logs into GHCR, pulls the `dashboard` image, extracts assets into `frontend_static`, and starts `dashboard` plus `caddy` containers via `docker compose`.
|
||||
|
||||
`docs.svc.plus` is now the dedicated docs/blog service for the frontend delivery path.
|
||||
|
||||
## Related Docs
|
||||
|
||||
- `usage/deployment.md`
|
||||
- `governance/release-process.md`
|
||||
- `development/dev-setup.md`
|
||||
@ -1,27 +0,0 @@
|
||||
# Design
|
||||
|
||||
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
|
||||
|
||||
Use this page to consolidate design decisions, ADR-style tradeoffs, and roadmap-sensitive implementation notes.
|
||||
|
||||
## Current code-aligned notes
|
||||
|
||||
- Documentation target: `console.svc.plus`
|
||||
- Repo kind: `frontend`
|
||||
- Manifest and build evidence: package.json (`dashboard`)
|
||||
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
|
||||
|
||||
## Existing docs to reconcile
|
||||
|
||||
- `SEO-WORK-SUMMARY.md`
|
||||
- `architecture/design-decisions.md`
|
||||
- `zh/SEO-WORK-SUMMARY.md`
|
||||
- `zh/architecture/design-decisions.md`
|
||||
|
||||
## What this page should cover next
|
||||
|
||||
- Describe the current implementation rather than an aspirational future-only design.
|
||||
- Keep terminology aligned with the repository root README, manifests, and actual directories.
|
||||
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
|
||||
- Promote one-off implementation notes into reusable design records when behavior, APIs, or deployment contracts change.
|
||||
@ -1,31 +0,0 @@
|
||||
# Developer Guide
|
||||
|
||||
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
|
||||
|
||||
Use this page to document local setup, project structure, test surfaces, and contribution conventions tied to the current codebase.
|
||||
|
||||
## Current code-aligned notes
|
||||
|
||||
- Documentation target: `console.svc.plus`
|
||||
- Repo kind: `frontend`
|
||||
- Manifest and build evidence: package.json (`dashboard`)
|
||||
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
|
||||
|
||||
## Existing docs to reconcile
|
||||
|
||||
- `api/auth.md`
|
||||
- `api/endpoints.md`
|
||||
- `api/errors.md`
|
||||
- `api/overview.md`
|
||||
- `development/code-structure.md`
|
||||
- `development/contributing.md`
|
||||
- `development/dev-setup.md`
|
||||
- `development/testing.md`
|
||||
|
||||
## What this page should cover next
|
||||
|
||||
- Describe the current implementation rather than an aspirational future-only design.
|
||||
- Keep terminology aligned with the repository root README, manifests, and actual directories.
|
||||
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
|
||||
- Keep setup and test commands tied to actual package scripts, Make targets, or language toolchains in this repository.
|
||||
@ -1,31 +0,0 @@
|
||||
# User Guide
|
||||
|
||||
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
|
||||
|
||||
Use this page to document primary user/operator tasks, everyday workflows, and navigation to existing how-to material.
|
||||
|
||||
## Current code-aligned notes
|
||||
|
||||
- Documentation target: `console.svc.plus`
|
||||
- Repo kind: `frontend`
|
||||
- Manifest and build evidence: package.json (`dashboard`)
|
||||
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
|
||||
|
||||
## Existing docs to reconcile
|
||||
|
||||
- `api/overview.md`
|
||||
- `architecture/overview.md`
|
||||
- `getting-started/concepts.md`
|
||||
- `getting-started/installation.md`
|
||||
- `getting-started/introduction.md`
|
||||
- `getting-started/quickstart.md`
|
||||
- `usage/cli.md`
|
||||
- `usage/config.md`
|
||||
|
||||
## What this page should cover next
|
||||
|
||||
- Describe the current implementation rather than an aspirational future-only design.
|
||||
- Keep terminology aligned with the repository root README, manifests, and actual directories.
|
||||
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
|
||||
- Prefer workflow-oriented examples and keep screenshots or terminal snippets aligned with the latest UI or CLI behavior.
|
||||
@ -1,31 +0,0 @@
|
||||
# Vibe Coding Reference
|
||||
|
||||
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
|
||||
|
||||
Use this page to align AI-assisted coding prompts, repo boundaries, safe edit rules, and documentation update expectations.
|
||||
|
||||
## Current code-aligned notes
|
||||
|
||||
- Documentation target: `console.svc.plus`
|
||||
- Repo kind: `frontend`
|
||||
- Manifest and build evidence: package.json (`dashboard`)
|
||||
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
|
||||
|
||||
## Existing docs to reconcile
|
||||
|
||||
- `api/auth.md`
|
||||
- `api/endpoints.md`
|
||||
- `api/errors.md`
|
||||
- `api/overview.md`
|
||||
- `zh/api/auth.md`
|
||||
- `zh/api/endpoints.md`
|
||||
- `zh/api/errors.md`
|
||||
- `zh/api/overview.md`
|
||||
|
||||
## What this page should cover next
|
||||
|
||||
- Describe the current implementation rather than an aspirational future-only design.
|
||||
- Keep terminology aligned with the repository root README, manifests, and actual directories.
|
||||
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
|
||||
- Review prompt templates and repo rules whenever the project adds new subsystems, protected areas, or mandatory verification steps.
|
||||
@ -1,9 +0,0 @@
|
||||
# Concepts
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Concepts.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,45 +0,0 @@
|
||||
# Installation
|
||||
|
||||
## Purpose
|
||||
|
||||
- Set up the local development environment for `console.svc.plus`.
|
||||
- Define the assistant and integrations defaults without hardcoding gateway values into the UI.
|
||||
|
||||
## Environment setup
|
||||
|
||||
1. Copy the example file:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Use `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-deploy-example/.env` as the reference source for deployment-aligned values when available.
|
||||
|
||||
## Assistant and integrations variables
|
||||
|
||||
These variables are read on the server side and used to prefill:
|
||||
|
||||
- the homepage AI assistant
|
||||
- the sidebar assistant dialog
|
||||
- the `/panel/api` integrations page
|
||||
|
||||
| Variable | Used by | Notes |
|
||||
|---|---|---|
|
||||
| `OPENCLAW_GATEWAY_REMOTE_URL` | OpenClaw assistant | Preferred remote WebSocket endpoint, for example `wss://openclaw.svc.plus:443` |
|
||||
| `OPENCLAW_GATEWAY_TOKEN` | OpenClaw assistant | Gateway token used by the server-side assistant bridge |
|
||||
| `VAULT_SERVER_URL` | Vault integration | Base Vault address for connectivity checks and defaults |
|
||||
| `VAULT_NAMESPACE` | Vault integration | Optional namespace when Vault Enterprise namespaces are used |
|
||||
| `VAULT_TOKEN` | Vault integration | Token used for Vault probe requests |
|
||||
| `APISIX_AI_GATEWAY_URL` | APISIX AI Gateway integration | Base HTTP(S) endpoint for AI gateway probing |
|
||||
| `AI_GATEWAY_ACCESS_TOKEN` | APISIX AI Gateway integration | Access token used for gateway probe requests |
|
||||
|
||||
## Behavior
|
||||
|
||||
- These values are not hardcoded into React components.
|
||||
- UI forms can still be overridden per request or per session when needed.
|
||||
- Empty values simply disable prefill; they do not break the page layout.
|
||||
|
||||
## Related documents
|
||||
|
||||
- `../README.md`
|
||||
- `../usage/config.md`
|
||||
@ -1,9 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Introduction.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Quickstart
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Quickstart.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# License
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to License.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,53 +0,0 @@
|
||||
# Release Process
|
||||
|
||||
This page tracks release summaries for published versions of the public web console served under `www.svc.plus` and `console.svc.plus`.
|
||||
|
||||
## Current Release
|
||||
|
||||
### v0.2
|
||||
|
||||
Release tag: `v0.2`
|
||||
Release branch: `release/v0.2`
|
||||
Published commit: `0fab89e`
|
||||
|
||||
#### Highlights
|
||||
|
||||
- Introduced the new XWorkmate workspace with a denser assistant layout, cleaner shell, and improved entry flow.
|
||||
- Added the OpenClaw assistant workspace and pairing bridge, including configurable origin override and more stable pairing fallback behavior.
|
||||
- Unified navigation and AI entry points with a persistent assistant sidebar and refined panel routing.
|
||||
- Added the latest blog shortcuts on the homepage and improved guest and registration messaging.
|
||||
- Expanded docs with bilingual structure updates, stronger OIDC guidance, and setup/readme cleanup.
|
||||
- Fixed build stability issues, including `next-mdx-remote` vulnerability-related build failures and Yarn dependency metadata alignment.
|
||||
|
||||
#### New Features
|
||||
|
||||
- Launched the XWorkmate workspace and polished its workspace entry and layout.
|
||||
- Added OpenClaw assistant integration, pairing bridge support, integration probe API, and integration defaults handling.
|
||||
- Added XScopeHub MCP visibility on the services page.
|
||||
- Displayed the latest 7 blog article titles in homepage shortcuts.
|
||||
|
||||
#### Improvements
|
||||
|
||||
- Split observability into a tri-view workspace and refined panel assistant routing.
|
||||
- Unified navigation structure and persistent AI sidebar behavior.
|
||||
- Improved login and registration flows by using server-resolved account service URLs.
|
||||
- Guest and demo access must not expose any backing account identity in public UI or session payloads.
|
||||
- Added vault-backed token lookup for integrations.
|
||||
|
||||
#### Docs And Setup
|
||||
|
||||
- Added bilingual docs coverage and restructured the docs entry points.
|
||||
- Rewrote the OIDC authentication guide with fuller setup instructions.
|
||||
- Updated setup guidance and simplified README structure.
|
||||
|
||||
#### Build And Dependency Fixes
|
||||
|
||||
- Updated and aligned `next-mdx-remote` usage for secure builds.
|
||||
- Removed conflicting npm lockfile state and aligned Yarn dependency metadata for reproducible builds.
|
||||
|
||||
## Notes
|
||||
|
||||
- GitHub Release: `https://github.com/x-evor/console.svc.plus/releases/tag/v0.2`
|
||||
- Related docs: `docs/README.md`, `docs/en/README.md`, `docs/zh/README.md`
|
||||
- Release validation must verify both `www.svc.plus` and `console.svc.plus` against the same `releaseImageRef`, `releaseImageTag`, and `releaseCommit`.
|
||||
- `www.svc.plus` is the canonical public domain for metadata, sitemap, `dashboardUrl`, and shared links.
|
||||
@ -1,9 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Security Policy.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Ai Providers
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Ai Providers.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Cloud
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Cloud.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,79 +0,0 @@
|
||||
# Cloudflare Web Analytics 集成配置
|
||||
|
||||
本页说明首页统计接口 `/api/marketing/home-stats` 依赖的 3 个 Cloudflare 环境变量如何获取,以及应配置到哪里。
|
||||
|
||||
## 需要的环境变量
|
||||
|
||||
```bash
|
||||
CLOUDFLARE_API_TOKEN=
|
||||
CLOUDFLARE_ACCOUNT_ID=
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
|
||||
```
|
||||
|
||||
## 变量获取方式
|
||||
|
||||
### 1) `CLOUDFLARE_API_TOKEN`
|
||||
|
||||
用途:服务端调用 Cloudflare GraphQL API 读取访问量。
|
||||
|
||||
获取路径:
|
||||
|
||||
1. 打开 Cloudflare 控制台,右上角头像 -> **My Profile**
|
||||
2. 进入 **API Tokens**
|
||||
3. 点击 **Create Token**
|
||||
4. 建议创建仅只读 token,至少包含:**Account Analytics:Read**(作用域限定到目标 Account)
|
||||
5. 复制生成后的 token(只显示一次)
|
||||
|
||||
### 2) `CLOUDFLARE_ACCOUNT_ID`
|
||||
|
||||
用途:GraphQL 查询时定位账号。
|
||||
|
||||
获取方式(任选其一):
|
||||
|
||||
- 在 Cloudflare 控制台 URL 中,账号路径段通常就是 account id。
|
||||
- 在账号总览页面(Overview)侧边栏/页面信息中复制 Account ID。
|
||||
|
||||
### 3) `CLOUDFLARE_WEB_ANALYTICS_SITE_TAG`
|
||||
|
||||
用途:定位具体 Web Analytics 站点。
|
||||
|
||||
获取方式(任选其一):
|
||||
|
||||
- 你当前这类链接中可直接看到:
|
||||
`.../web-analytics/overview?siteTag~in=<SITE_TAG>&excludeBots=Yes`
|
||||
其中 `<SITE_TAG>` 就是变量值。
|
||||
- 在 Cloudflare Web Analytics 的站点设置/安装脚本中,`siteTag`(或 beacon token)即对应值。
|
||||
|
||||
## 配置写入位置
|
||||
|
||||
### 本地开发
|
||||
|
||||
写入当前前端仓库的 `.env.local`:
|
||||
|
||||
```bash
|
||||
CLOUDFLARE_API_TOKEN=...
|
||||
CLOUDFLARE_ACCOUNT_ID=...
|
||||
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=...
|
||||
```
|
||||
|
||||
### 线上部署
|
||||
|
||||
把同名变量写入前端部署环境。
|
||||
|
||||
> 注意:这些变量属于服务端密钥,不要暴露到 `NEXT_PUBLIC_*`。
|
||||
|
||||
## 联调验证
|
||||
|
||||
部署后访问:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://www.svc.plus/api/marketing/home-stats
|
||||
```
|
||||
|
||||
期望返回中 `visits.daily/weekly/monthly` 为数字(非 `null`)。
|
||||
|
||||
如果是 `null`,优先检查:
|
||||
|
||||
1. token 权限是否包含 Analytics Read
|
||||
2. Account ID 是否与 siteTag 属于同一账号
|
||||
3. 环境变量是否已在当前运行实例生效(重启/重新部署后再测)
|
||||
@ -1,9 +0,0 @@
|
||||
# Databases
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Databases.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,244 +0,0 @@
|
||||
# OIDC Authentication Configuration Guide
|
||||
|
||||
This guide describes how to configure GitHub and Google OAuth login for the Cloud Neutral Toolkit, enabling any user to sign in with their own GitHub or Google account.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||
│ Browser │ │ www.svc.plus │ │accounts.svc.plus │
|
||||
│ (User) │ │ (Frontend) │ │ (Backend) │
|
||||
└──────┬───────┘ └────────┬─────────┘ └────────┬─────────┘
|
||||
│ 1. Click "Login │ │
|
||||
│ with GitHub" │ │
|
||||
│──────────────────────>│ │
|
||||
│ │ │
|
||||
│ 2. Redirect to │ │
|
||||
│ accounts /api/ │ │
|
||||
│ auth/oauth/login/ │ │
|
||||
│ github │ │
|
||||
│<──────────────────────│ │
|
||||
│ │ │
|
||||
│ 3. accounts redirects to GitHub/Google │
|
||||
│ with client_id & callback URL │
|
||||
│<────────────────────────────────────────────────│
|
||||
│ │ │
|
||||
│ 4. User authorizes │ │
|
||||
│ on GitHub/Google │ │
|
||||
│ │ │
|
||||
│ 5. GitHub/Google redirects back to │
|
||||
│ accounts /api/auth/oauth/callback/github │
|
||||
│─────────────────────────────────────────────────>
|
||||
│ │ │
|
||||
│ 6. accounts exchanges code for token, │
|
||||
│ creates/links user, redirects to console │
|
||||
│<────────────────────────────────────────────────│
|
||||
│ │ │
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A GitHub account with access to **Settings > Developer Settings**
|
||||
- A Google account with access to [Google Cloud Console](https://console.cloud.google.com/)
|
||||
- Running `accounts.svc.plus` and the frontend served under `www.svc.plus` / `console.svc.plus`
|
||||
|
||||
---
|
||||
|
||||
## 1. GitHub OAuth App
|
||||
|
||||
### 1.1 Create OAuth App
|
||||
|
||||
1. Go to [GitHub Developer Settings > OAuth Apps](https://github.com/settings/developers)
|
||||
2. Click **"OAuth Apps"** tab, then **"New OAuth App"**
|
||||
3. Fill in the form:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Application name** | `Cloud Neutral Console` |
|
||||
| **Homepage URL** | `https://www.svc.plus` |
|
||||
| **Authorization callback URL** | `https://accounts.svc.plus/api/auth/oauth/callback/github` |
|
||||
| **Enable Device Flow** | ☐ (unchecked) |
|
||||
|
||||
4. Click **"Register application"**
|
||||
|
||||
### 1.2 Generate Client Secret
|
||||
|
||||
1. On the app detail page, copy the **Client ID** (displayed at the top)
|
||||
2. Click **"Generate a new client secret"**
|
||||
3. **Immediately copy the Client Secret** — it will only be shown once
|
||||
|
||||
### 1.3 Record Credentials
|
||||
|
||||
```
|
||||
GitHub Client ID: Ov23li...
|
||||
GitHub Client Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
Callback URL: https://accounts.svc.plus/api/auth/oauth/callback/github
|
||||
```
|
||||
|
||||
> ⚠️ **Security**: Never commit Client Secret to version control. Store it as an environment variable or in a secret manager.
|
||||
|
||||
### 1.4 GitHub OAuth Scopes
|
||||
|
||||
The OAuth App requests these scopes by default:
|
||||
- `user:email` — Read the user's email addresses (used for account binding)
|
||||
|
||||
No additional GitHub permissions are required.
|
||||
|
||||
---
|
||||
|
||||
## 2. Google OAuth Client ID
|
||||
|
||||
### 2.1 Configure OAuth Consent Screen
|
||||
|
||||
> This step is required before creating credentials. If already configured, skip to 2.2.
|
||||
|
||||
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
||||
2. Select or create a project
|
||||
3. Navigate to **APIs & Services > OAuth consent screen**
|
||||
4. Choose **External** user type (allows any Google user to sign in)
|
||||
5. Fill in the required fields:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **App name** | `Cloud Neutral Console` |
|
||||
| **User support email** | your email address |
|
||||
| **Developer contact email** | your email address |
|
||||
|
||||
6. Add scopes: `email`, `profile`, `openid`
|
||||
7. Click **Save and Continue** through the remaining steps
|
||||
8. Under **Publishing status**, click **"Publish App"** to move out of testing mode
|
||||
- In testing mode, only manually added test users can sign in
|
||||
|
||||
### 2.2 Create OAuth Client ID
|
||||
|
||||
1. Go to **APIs & Services > Credentials**
|
||||
2. Click **"Create Credentials" > "OAuth client ID"**
|
||||
3. Fill in the form:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Application type** | `Web application` |
|
||||
| **Name** | `Cloud Neutral Console` |
|
||||
| **Authorized JavaScript origins** | `https://www.svc.plus` |
|
||||
| **Authorized redirect URIs** | `https://accounts.svc.plus/api/auth/oauth/callback/google` |
|
||||
|
||||
4. Click **"Create"**
|
||||
5. Copy the **Client ID** and **Client Secret** from the popup
|
||||
|
||||
### 2.3 Record Credentials
|
||||
|
||||
```
|
||||
Google Client ID: xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
|
||||
Google Client Secret: GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
Callback URL: https://accounts.svc.plus/api/auth/oauth/callback/google
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend Configuration (accounts.svc.plus)
|
||||
|
||||
Set the following environment variables for **accounts.svc.plus**:
|
||||
|
||||
```bash
|
||||
# ── GitHub OAuth ──
|
||||
GITHUB_CLIENT_ID=<your_github_client_id>
|
||||
GITHUB_CLIENT_SECRET=<your_github_client_secret>
|
||||
|
||||
# ── Google OAuth ──
|
||||
GOOGLE_CLIENT_ID=<your_google_client_id>
|
||||
GOOGLE_CLIENT_SECRET=<your_google_client_secret>
|
||||
|
||||
# ── General OAuth ──
|
||||
OAUTH_REDIRECT_URL=https://accounts.svc.plus/api/auth/oauth/callback
|
||||
OAUTH_FRONTEND_URL=https://www.svc.plus
|
||||
```
|
||||
|
||||
These variables are referenced in `config/account.yaml`:
|
||||
|
||||
```yaml
|
||||
auth:
|
||||
oauth:
|
||||
redirectUrl: "${OAUTH_REDIRECT_URL}"
|
||||
frontendUrl: "${OAUTH_FRONTEND_URL:-https://www.svc.plus}"
|
||||
github:
|
||||
clientId: "${GITHUB_CLIENT_ID}"
|
||||
clientSecret: "${GITHUB_CLIENT_SECRET}"
|
||||
google:
|
||||
clientId: "${GOOGLE_CLIENT_ID}"
|
||||
clientSecret: "${GOOGLE_CLIENT_SECRET}"
|
||||
```
|
||||
|
||||
> **Note**: The backend automatically appends `/{provider}` to `OAUTH_REDIRECT_URL` (e.g. `.../callback/github`) if a provider-specific redirect URL is not set.
|
||||
|
||||
---
|
||||
|
||||
## 4. Frontend Configuration (`www.svc.plus` canonical, `console.svc.plus` secondary)
|
||||
|
||||
The frontend resolves the accounts service URL **server-side** via `getAccountServiceBaseUrl()`, which reads:
|
||||
|
||||
```bash
|
||||
# Set in accounts.svc.plus deployment environment
|
||||
ACCOUNT_SERVICE_URL=https://accounts.svc.plus
|
||||
```
|
||||
|
||||
If not set, the function falls back to a runtime default. **No `NEXT_PUBLIC_*` env var is needed** — the OAuth login URLs are constructed server-side and passed to the client components as props.
|
||||
|
||||
### OAuth Login URLs (auto-generated)
|
||||
|
||||
| Provider | Login URL |
|
||||
|---|---|
|
||||
| GitHub | `{accountServiceBaseUrl}/api/auth/oauth/login/github` |
|
||||
| Google | `{accountServiceBaseUrl}/api/auth/oauth/login/google` |
|
||||
|
||||
### OAuth Callback URLs (handled by accounts.svc.plus)
|
||||
|
||||
| Provider | Callback URL |
|
||||
|---|---|
|
||||
| GitHub | `https://accounts.svc.plus/api/auth/oauth/callback/github` |
|
||||
| Google | `https://accounts.svc.plus/api/auth/oauth/callback/google` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Troubleshooting
|
||||
|
||||
### `undefined/api/auth/oauth/login/github`
|
||||
|
||||
**Cause**: OAuth URLs were using a client-side env var (`NEXT_PUBLIC_ACCOUNTS_SVC_URL`) that was not set.
|
||||
|
||||
**Fix** (applied in commit `4ce4147`): OAuth URLs now use the server-resolved `accountServiceBaseUrl` prop.
|
||||
|
||||
### OAuth login redirects to wrong domain
|
||||
|
||||
Check that `OAUTH_FRONTEND_URL` in accounts.svc.plus matches the canonical public domain where users should be redirected after authentication. The current default is `https://www.svc.plus`.
|
||||
|
||||
### Google "Access blocked: This app's request is invalid"
|
||||
|
||||
Ensure the **Authorized redirect URI** in Google Cloud Console **exactly** matches:
|
||||
```
|
||||
https://accounts.svc.plus/api/auth/oauth/callback/google
|
||||
```
|
||||
Trailing slashes or mismatched protocols will cause this error.
|
||||
|
||||
### GitHub "The redirect_uri MUST match the registered callback URL"
|
||||
|
||||
Ensure the **Authorization callback URL** in GitHub Developer Settings **exactly** matches:
|
||||
```
|
||||
https://accounts.svc.plus/api/auth/oauth/callback/github
|
||||
```
|
||||
|
||||
### Google OAuth in "Testing" mode — only test users can sign in
|
||||
|
||||
Go to **OAuth consent screen > Publishing status** and click **"Publish App"** to allow any Google user to sign in.
|
||||
|
||||
---
|
||||
|
||||
## 6. Quick Reference
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| GitHub OAuth App Settings | https://github.com/settings/developers |
|
||||
| Google Cloud Credentials | https://console.cloud.google.com/apis/credentials |
|
||||
| GitHub Callback URL | `https://accounts.svc.plus/api/auth/oauth/callback/github` |
|
||||
| Google Callback URL | `https://accounts.svc.plus/api/auth/oauth/callback/google` |
|
||||
| Backend Config File | `accounts.svc.plus/config/account.yaml` |
|
||||
| Frontend URL Resolution | `getAccountServiceBaseUrl()` in `src/server/serviceConfig.ts` |
|
||||
@ -1,51 +0,0 @@
|
||||
# Stripe Billing Integration
|
||||
|
||||
This console now routes all purchase entry points through Stripe:
|
||||
|
||||
- `/prices`
|
||||
- product detail pages
|
||||
- `/panel/subscription`
|
||||
|
||||
The browser only needs public Stripe `price_id` values. Secret keys stay in `accounts.svc.plus`.
|
||||
|
||||
## Required Environment Variables
|
||||
|
||||
Set these in `console.svc.plus`:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=price_xxx
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=price_xxx
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=price_xxx
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=price_xxx
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=price_xxx
|
||||
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=price_xxx
|
||||
```
|
||||
|
||||
If a value is missing, the related purchase button stays visible but reports that Stripe pricing is not configured.
|
||||
|
||||
## Local Integration Checklist
|
||||
|
||||
1. Configure all `NEXT_PUBLIC_STRIPE_PRICE_*` values with Stripe test-mode `price_...` ids.
|
||||
2. Start `accounts.svc.plus` with Stripe server-side settings.
|
||||
3. Start this console with `yarn dev`.
|
||||
4. Sign in with a normal user account.
|
||||
5. Open `/prices` or `/panel/subscription` and start checkout.
|
||||
6. Complete a Stripe test payment.
|
||||
7. Confirm the browser returns to `/panel/subscription?checkout=success...`.
|
||||
8. Confirm the subscription record appears in the subscription panel.
|
||||
9. Open "Manage Stripe billing" and confirm the customer portal opens.
|
||||
|
||||
## Expected Flow
|
||||
|
||||
1. The console calls `/api/auth/stripe/checkout`.
|
||||
2. The BFF proxies the request to `accounts.svc.plus` using the current account session.
|
||||
3. `accounts.svc.plus` creates the Stripe Checkout Session.
|
||||
4. Stripe redirects back to the console.
|
||||
5. Stripe webhooks update the account service subscription record.
|
||||
6. The console reads the final state from `/api/auth/subscriptions`.
|
||||
|
||||
## Notes
|
||||
|
||||
- The console does not store Stripe secret keys.
|
||||
- Sensitive payment methods such as crypto QR flows are intentionally removed from the purchase UI.
|
||||
- Use Stripe test mode first; do not validate this flow against live prices until webhook delivery is confirmed.
|
||||
@ -1,9 +0,0 @@
|
||||
# Backup
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Backup.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Logging
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Logging.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Monitoring
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Monitoring.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,9 +0,0 @@
|
||||
# Runbooks
|
||||
|
||||
## Scope
|
||||
|
||||
- Operational runbooks for services used by `console.svc.plus`.
|
||||
|
||||
## Index
|
||||
|
||||
- `rag-server.md`
|
||||
@ -1,426 +0,0 @@
|
||||
# Cloud-Neutral Toolkit - RAG Server Runbook
|
||||
|
||||
## 📚 文档概述
|
||||
|
||||
**文档类型**: 运维手册 (Runbook)
|
||||
**服务名称**: RAG Server (rag-server-svc-plus)
|
||||
**维护团队**: Cloud-Neutral Toolkit Team
|
||||
**最后更新**: 2026-01-25
|
||||
**版本**: 1.0
|
||||
|
||||
## 🎯 服务概述
|
||||
|
||||
### 服务信息
|
||||
- **服务名称**: rag-server-svc-plus
|
||||
- **部署平台**: Google Cloud Run
|
||||
- **区域**: asia-northeast1
|
||||
- **项目 ID**: xzerolab-480008
|
||||
- **服务 URL**: https://rag-server-svc-plus-266500572462.asia-northeast1.run.app
|
||||
- **代码仓库**: https://github.com/x-evor/rag-server.svc.plus
|
||||
|
||||
### 服务功能
|
||||
RAG (Retrieval-Augmented Generation) 服务器提供以下功能:
|
||||
1. **向量检索** (`/api/rag/query`) - 从知识库中检索相关文档
|
||||
2. **AI 问答** (`/api/askai`) - 使用 LLM 生成答案
|
||||
3. **文档索引** (`/api/rag/upsert`) - 向知识库添加文档
|
||||
|
||||
### 依赖服务
|
||||
- **LLM Provider**: NVIDIA AI (integrate.api.nvidia.com)
|
||||
- **向量数据库**: PostgreSQL with pgvector
|
||||
- **认证服务**: accounts-svc-plus
|
||||
- **前端服务**: console.svc.plus (Vercel)
|
||||
|
||||
## 🏗️ 架构说明
|
||||
|
||||
### 系统架构
|
||||
```
|
||||
用户 (https://www.svc.plus)
|
||||
↓
|
||||
Console Frontend (Vercel)
|
||||
↓
|
||||
Next.js API Routes (/api/askai, /api/rag/query)
|
||||
↓
|
||||
RAG Server (Cloud Run)
|
||||
├─→ NVIDIA AI API (LLM)
|
||||
└─→ PostgreSQL (向量数据库)
|
||||
```
|
||||
|
||||
### 配置文件结构
|
||||
```
|
||||
rag-server.svc.plus/
|
||||
├── config/
|
||||
│ └── rag-server.yaml # 主配置文件
|
||||
├── api/
|
||||
│ ├── askai.go # AI 问答端点
|
||||
│ ├── rag.go # RAG 检索端点
|
||||
│ └── register.go # 路由注册
|
||||
├── cmd/
|
||||
│ └── rag-server/
|
||||
│ └── main.go # 主程序入口
|
||||
├── Dockerfile # Docker 镜像构建
|
||||
└── entrypoint.sh # 容器启动脚本
|
||||
```
|
||||
|
||||
## 🚀 部署流程
|
||||
|
||||
### 标准部署流程
|
||||
|
||||
#### 1. 代码变更
|
||||
```bash
|
||||
# 克隆仓库
|
||||
git clone https://github.com/x-evor/rag-server.svc.plus.git
|
||||
cd rag-server.svc.plus
|
||||
|
||||
# 创建功能分支
|
||||
git checkout -b feature/your-feature-name
|
||||
|
||||
# 进行代码修改
|
||||
# ... 编辑文件 ...
|
||||
|
||||
# 提交变更
|
||||
git add .
|
||||
git commit -m "feat: your feature description"
|
||||
git push -u origin feature/your-feature-name
|
||||
```
|
||||
|
||||
#### 2. 创建 Pull Request
|
||||
1. 访问 GitHub 仓库
|
||||
2. 创建 PR: `feature/your-feature-name` → `main`
|
||||
3. 等待 CI/CD 检查通过
|
||||
4. 请求代码审查
|
||||
5. 合并到 main 分支
|
||||
|
||||
#### 3. 部署到 Cloud Run
|
||||
|
||||
**方式 A: 使用 gcloud CLI (推荐用于紧急修复)**
|
||||
```bash
|
||||
cd /path/to/rag-server.svc.plus
|
||||
|
||||
# 部署到 Cloud Run
|
||||
gcloud run deploy rag-server-svc-plus \
|
||||
--source . \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008 \
|
||||
--platform managed \
|
||||
--allow-unauthenticated \
|
||||
--set-env-vars NVIDIA_API_KEY='your-api-key-here'
|
||||
|
||||
# 部署通常需要 3-5 分钟
|
||||
```
|
||||
|
||||
**方式 B: 通过 Cloud Build (自动化)**
|
||||
```bash
|
||||
# 触发 Cloud Build
|
||||
gcloud builds submit \
|
||||
--config cloudbuild.yaml \
|
||||
--project xzerolab-480008
|
||||
```
|
||||
|
||||
### 环境变量配置
|
||||
|
||||
必需的环境变量:
|
||||
```bash
|
||||
NVIDIA_API_KEY=nvapi-xxx... # NVIDIA AI API 密钥
|
||||
DATABASE_URL=postgres://... # PostgreSQL 连接字符串
|
||||
POSTGRES_USER=postgres # 数据库用户名
|
||||
POSTGRES_PASSWORD=xxx # 数据库密码
|
||||
```
|
||||
|
||||
可选的环境变量:
|
||||
```bash
|
||||
PORT=8080 # 服务端口(Cloud Run 自动设置)
|
||||
LOG_LEVEL=info # 日志级别 (debug/info/warn/error)
|
||||
CONFIG_PATH=/etc/rag-server/rag-server.yaml # 配置文件路径
|
||||
```
|
||||
|
||||
## 🔍 监控和日志
|
||||
|
||||
### 查看 Cloud Run 日志
|
||||
|
||||
**使用 Google Cloud Console**:
|
||||
```
|
||||
https://console.cloud.google.com/logs/query;query=resource.type%20%3D%20%22cloud_run_revision%22%0Aresource.labels.service_name%20%3D%20%22rag-server-svc-plus%22
|
||||
```
|
||||
|
||||
**使用 gcloud CLI**:
|
||||
```bash
|
||||
# 查看最近 50 条日志
|
||||
gcloud logging read \
|
||||
"resource.type=cloud_run_revision AND resource.labels.service_name=rag-server-svc-plus" \
|
||||
--limit 50 \
|
||||
--project xzerolab-480008 \
|
||||
--format json
|
||||
|
||||
# 实时查看日志
|
||||
gcloud logging tail \
|
||||
"resource.type=cloud_run_revision AND resource.labels.service_name=rag-server-svc-plus" \
|
||||
--project xzerolab-480008
|
||||
```
|
||||
|
||||
### 关键指标监控
|
||||
|
||||
1. **请求成功率**: 应该 > 95%
|
||||
2. **响应时间**: P95 < 5s, P99 < 10s
|
||||
3. **错误率**: < 5%
|
||||
4. **实例数量**: 根据负载自动扩缩容
|
||||
|
||||
### 告警设置
|
||||
|
||||
建议设置以下告警:
|
||||
- 错误率 > 10% 持续 5 分钟
|
||||
- P99 延迟 > 15s 持续 5 分钟
|
||||
- 实例启动失败
|
||||
- 配置文件加载失败
|
||||
|
||||
## 🐛 故障排查
|
||||
|
||||
### 常见问题和解决方案
|
||||
|
||||
#### 问题 1: `/api/askai` 返回 500 错误
|
||||
|
||||
**症状**:
|
||||
```json
|
||||
{
|
||||
"error": "Post \"\": unsupported protocol scheme \"\"",
|
||||
"config": {"timeout": 30, "retries": 3}
|
||||
}
|
||||
```
|
||||
|
||||
**原因**: ConfigPath 配置错误,无法读取 LLM endpoint
|
||||
|
||||
**解决方案**:
|
||||
1. 检查 `api/askai.go` 中的 `ConfigPath` 变量
|
||||
2. 确保指向正确的配置文件: `config/rag-server.yaml`
|
||||
3. 验证配置文件中的 `models.generator.endpoint` 不为空
|
||||
|
||||
**修复代码**:
|
||||
```go
|
||||
// api/askai.go
|
||||
var ConfigPath = filepath.Join("config", "rag-server.yaml")
|
||||
```
|
||||
|
||||
#### 问题 2: 数据库连接失败
|
||||
|
||||
**症状**:
|
||||
```
|
||||
postgres connect error: connection refused
|
||||
```
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查 `DATABASE_URL` 环境变量是否正确
|
||||
2. 验证 PostgreSQL 服务是否运行
|
||||
3. 检查网络连接和防火墙规则
|
||||
4. 如果使用 Stunnel,检查 TLS 隧道是否正常
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 测试数据库连接
|
||||
psql "$DATABASE_URL"
|
||||
|
||||
# 检查 Stunnel 状态
|
||||
ps aux | grep stunnel
|
||||
netstat -an | grep 5432
|
||||
```
|
||||
|
||||
#### 问题 3: NVIDIA API 调用失败
|
||||
|
||||
**症状**:
|
||||
```
|
||||
askai request failed: 401 Unauthorized
|
||||
```
|
||||
|
||||
**排查步骤**:
|
||||
1. 验证 `NVIDIA_API_KEY` 环境变量是否设置
|
||||
2. 检查 API key 是否有效
|
||||
3. 确认 API quota 未超限
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 测试 NVIDIA API
|
||||
curl -X POST https://integrate.api.nvidia.com/v1/chat/completions \
|
||||
-H "Authorization: Bearer $NVIDIA_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model": "minimaxai/minimax-m2", "messages": [{"role": "user", "content": "test"}]}'
|
||||
```
|
||||
|
||||
#### 问题 4: 配置文件未找到
|
||||
|
||||
**症状**:
|
||||
```
|
||||
load config err: open config/rag-server.yaml: no such file or directory
|
||||
```
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查 Dockerfile 是否正确复制配置文件
|
||||
2. 验证 entrypoint.sh 是否正确设置配置路径
|
||||
3. 检查容器内文件系统
|
||||
|
||||
**解决方案**:
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
COPY config/rag-server.yaml /etc/rag-server/rag-server.yaml
|
||||
```
|
||||
|
||||
```bash
|
||||
# entrypoint.sh
|
||||
CONFIG_FILE="${CONFIG_PATH:-/etc/rag-server/rag-server.yaml}"
|
||||
```
|
||||
|
||||
### 调试命令
|
||||
|
||||
```bash
|
||||
# 1. 检查服务状态
|
||||
gcloud run services describe rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008
|
||||
|
||||
# 2. 测试健康检查
|
||||
curl https://rag-server-svc-plus-266500572462.asia-northeast1.run.app/health
|
||||
|
||||
# 3. 测试 RAG 查询
|
||||
curl -X POST https://rag-server-svc-plus-266500572462.asia-northeast1.run.app/api/rag/query \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "test"}'
|
||||
|
||||
# 4. 测试 AI 问答
|
||||
curl -X POST https://rag-server-svc-plus-266500572462.asia-northeast1.run.app/api/askai \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "Hello"}'
|
||||
|
||||
# 5. 查看最新部署版本
|
||||
gcloud run revisions list \
|
||||
--service rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008
|
||||
|
||||
# 6. 回滚到上一个版本
|
||||
gcloud run services update-traffic rag-server-svc-plus \
|
||||
--to-revisions REVISION_NAME=100 \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008
|
||||
```
|
||||
|
||||
## 🔄 回滚流程
|
||||
|
||||
### 紧急回滚
|
||||
|
||||
如果新部署导致严重问题:
|
||||
|
||||
```bash
|
||||
# 1. 列出所有版本
|
||||
gcloud run revisions list \
|
||||
--service rag-server-svc-plus \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008
|
||||
|
||||
# 2. 回滚到上一个稳定版本
|
||||
gcloud run services update-traffic rag-server-svc-plus \
|
||||
--to-revisions PREVIOUS_REVISION=100 \
|
||||
--region asia-northeast1 \
|
||||
--project xzerolab-480008
|
||||
|
||||
# 3. 验证回滚成功
|
||||
curl https://rag-server-svc-plus-266500572462.asia-northeast1.run.app/health
|
||||
```
|
||||
|
||||
### Git 回滚
|
||||
|
||||
```bash
|
||||
# 回滚到上一个提交
|
||||
git revert HEAD
|
||||
git push origin main
|
||||
|
||||
# 或者硬回滚(谨慎使用)
|
||||
git reset --hard HEAD~1
|
||||
git push -f origin main
|
||||
```
|
||||
|
||||
## 📊 性能优化
|
||||
|
||||
### 配置优化建议
|
||||
|
||||
1. **调整超时时间**:
|
||||
```yaml
|
||||
# config/rag-server.yaml
|
||||
api:
|
||||
askai:
|
||||
timeout: 100 # 秒
|
||||
retries: 3
|
||||
```
|
||||
|
||||
2. **数据库连接池**:
|
||||
```yaml
|
||||
vector_db:
|
||||
max_connections: 20
|
||||
idle_timeout: 300s
|
||||
```
|
||||
|
||||
3. **Cloud Run 资源配置**:
|
||||
```bash
|
||||
gcloud run services update rag-server-svc-plus \
|
||||
--memory 2Gi \
|
||||
--cpu 2 \
|
||||
--max-instances 10 \
|
||||
--min-instances 1 \
|
||||
--region asia-northeast1
|
||||
```
|
||||
|
||||
## 📝 维护检查清单
|
||||
|
||||
### 每日检查
|
||||
- [ ] 查看错误日志,确认无异常
|
||||
- [ ] 检查服务响应时间
|
||||
- [ ] 验证 API 端点可访问性
|
||||
|
||||
### 每周检查
|
||||
- [ ] 审查性能指标
|
||||
- [ ] 检查资源使用情况
|
||||
- [ ] 更新依赖包(如有安全更新)
|
||||
|
||||
### 每月检查
|
||||
- [ ] 审查和优化配置
|
||||
- [ ] 清理旧的 Cloud Run 版本
|
||||
- [ ] 更新文档
|
||||
|
||||
## 🔐 安全最佳实践
|
||||
|
||||
1. **API 密钥管理**:
|
||||
- 使用 Google Secret Manager 存储敏感信息
|
||||
- 定期轮换 API 密钥
|
||||
- 不要在代码中硬编码密钥
|
||||
|
||||
2. **访问控制**:
|
||||
- 限制 Cloud Run 服务账号权限
|
||||
- 使用 IAM 角色管理访问
|
||||
- 启用 Cloud Armor 防护
|
||||
|
||||
3. **数据安全**:
|
||||
- 使用 TLS 加密传输
|
||||
- 启用数据库加密
|
||||
- 定期备份数据
|
||||
|
||||
## 📞 联系信息
|
||||
|
||||
### 团队联系方式
|
||||
- **技术负责人**: [姓名]
|
||||
- **运维团队**: [邮箱]
|
||||
- **紧急联系**: [电话]
|
||||
|
||||
### 相关链接
|
||||
- **GitHub**: https://github.com/x-evor/rag-server.svc.plus
|
||||
- **Cloud Console**: https://console.cloud.google.com/run/detail/asia-northeast1/rag-server-svc-plus
|
||||
- **监控面板**: [Monitoring Dashboard URL]
|
||||
- **文档**: [Documentation URL]
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [API 文档](./API.md)
|
||||
- [配置指南](./CONFIG.md)
|
||||
- [开发指南](./DEVELOPMENT.md)
|
||||
- [故障排查指南](./debug-report.md)
|
||||
|
||||
---
|
||||
|
||||
**文档维护**: 请在每次重大变更后更新此文档
|
||||
**审核周期**: 每季度审核一次
|
||||
**版本历史**: 见 Git 提交记录
|
||||
@ -1,9 +0,0 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Troubleshooting.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,281 +0,0 @@
|
||||
# Console Frontend Single-Node Deployment Design
|
||||
|
||||
## Scope
|
||||
|
||||
- Repository: `console.svc.plus`
|
||||
- Target host: `root@cn-console.svc.plus`
|
||||
- Public domains:
|
||||
- `cn-console.svc.plus`
|
||||
- `cn-console.onwalk.net`
|
||||
- Delivery mode: `GitHub Actions + GHCR + Caddy + Docker Compose`
|
||||
|
||||
This document defines the deployment baseline for the China-facing frontend node. The source of truth is this upstream repository. The control-plane repository may consume the repo through git submodule, but should not become the primary place where this deployment design lives.
|
||||
|
||||
## Objective
|
||||
|
||||
Provide an independent frontend deployment pipeline for `console.svc.plus` that fits the current host constraints:
|
||||
|
||||
- the host IO is weak
|
||||
- the host must not build Docker images locally
|
||||
- the frontend should run in a static-first mode where possible
|
||||
- deployment logic should stay in checked-in scripts, not be embedded in GitHub Actions YAML
|
||||
|
||||
The result should support repeatable releases, quick rollback by image tag, and minimal work on the target machine.
|
||||
|
||||
## Constraints
|
||||
|
||||
### Host constraints
|
||||
|
||||
- `cn-console.svc.plus` is a single-node host
|
||||
- deployment user is `root`
|
||||
- local image build on the host is explicitly disallowed
|
||||
- IO pressure should be minimized during release
|
||||
|
||||
### Application constraints
|
||||
|
||||
- `console.svc.plus` is not a purely static site
|
||||
- auth routes, same-origin API proxy routes, and selected dynamic pages still require a running Next.js server
|
||||
- some `NEXT_PUBLIC_*` variables are compiled into the frontend bundle at image build time
|
||||
- `prebuild` pulls documentation and `knowledge` content, so CI must prepare those inputs before building the image
|
||||
|
||||
### Repository constraints
|
||||
|
||||
- workflow YAML should remain orchestration-only
|
||||
- service-local operational notes should remain in this repo
|
||||
- downstream control repos can reference this repo through submodule updates after upstream changes are pushed
|
||||
|
||||
## Recommended Topology
|
||||
|
||||
### 1. CI build on GitHub Actions
|
||||
|
||||
The workflow builds a single `linux/amd64` image in GitHub Actions and pushes it to GHCR.
|
||||
|
||||
Reasons:
|
||||
|
||||
- matches the target host architecture
|
||||
- avoids multi-arch overhead for this single-node release path
|
||||
- avoids local host build IO and CPU pressure
|
||||
- keeps release artifacts immutable and rollback-friendly
|
||||
|
||||
### 2. Runtime on the host
|
||||
|
||||
Use `docker compose` with three services:
|
||||
|
||||
- `dashboard`: Next.js standalone runtime
|
||||
- `frontend-assets`: one-shot container that copies static files from the image into a Docker volume
|
||||
- `caddy`: TLS termination, redirect handling, static file serving, and reverse proxy
|
||||
|
||||
This keeps the host work limited to:
|
||||
|
||||
- image pull
|
||||
- asset extraction from the image
|
||||
- container restart
|
||||
|
||||
### 3. Static-first request flow
|
||||
|
||||
Caddy serves:
|
||||
|
||||
- `/_next/static/*`
|
||||
- checked-in `public/` assets
|
||||
|
||||
Next.js serves:
|
||||
|
||||
- HTML responses
|
||||
- `/api/*` routes
|
||||
- auth/session flows
|
||||
- dynamic pages that still depend on server runtime
|
||||
|
||||
This reduces repeat disk reads and network hops for the bulk of frontend traffic while preserving the dynamic behavior the app still needs.
|
||||
|
||||
## Build-Time vs Runtime Configuration
|
||||
|
||||
### Build-time config
|
||||
|
||||
These values must be available during Docker build because the frontend bundle reads them directly:
|
||||
|
||||
- `NEXT_PUBLIC_APP_BASE_URL`
|
||||
- `NEXT_PUBLIC_SITE_URL`
|
||||
- `NEXT_PUBLIC_LOGIN_URL`
|
||||
- `NEXT_PUBLIC_DOCS_BASE_URL`
|
||||
- `NEXT_PUBLIC_RUNTIME_ENVIRONMENT`
|
||||
- `NEXT_PUBLIC_RUNTIME_REGION`
|
||||
- `NEXT_PUBLIC_GISCUS_*`
|
||||
- `NEXT_PUBLIC_PAYPAL_CLIENT_ID`
|
||||
- `NEXT_PUBLIC_STRIPE_*`
|
||||
|
||||
These are injected in GitHub Actions as Docker build args.
|
||||
|
||||
### Runtime config
|
||||
|
||||
These values are rendered into `.env.runtime` and copied to the host:
|
||||
|
||||
- upstream service URLs such as `ACCOUNT_SERVICE_URL`
|
||||
- tokens used only on the server side
|
||||
- Cloudflare analytics credentials
|
||||
- internal service token
|
||||
- runtime hostname hints
|
||||
|
||||
This separation avoids rebuilding for purely server-side secret or endpoint changes when the public frontend bundle does not change.
|
||||
|
||||
## Knowledge and Docs Handling
|
||||
|
||||
Current decision:
|
||||
|
||||
- `knowledge/` is cloned during CI
|
||||
- the cloned content is included in the image build context
|
||||
- the built image contains the resulting content needed by the current frontend
|
||||
|
||||
Reason:
|
||||
|
||||
- `prebuild` depends on this material
|
||||
- the host should not fetch or generate content during deployment
|
||||
|
||||
Temporary nature:
|
||||
|
||||
- today the frontend still carries docs-related payload
|
||||
- later, when `docs.svc.plus` becomes an API/service, docs delivery should move out of the frontend image
|
||||
- that future change should reduce image size and simplify the runtime responsibilities of `console.svc.plus`
|
||||
|
||||
## Domain Handling
|
||||
|
||||
Primary domain:
|
||||
|
||||
- `cn-console.svc.plus`
|
||||
|
||||
Secondary domain:
|
||||
|
||||
- `cn-console.onwalk.net`
|
||||
|
||||
Current routing decision:
|
||||
|
||||
- Caddy accepts both domains
|
||||
- requests for `cn-console.onwalk.net` are redirected permanently to `cn-console.svc.plus`
|
||||
|
||||
Reason:
|
||||
|
||||
- avoid duplicate canonical origins
|
||||
- keep cookie and login behavior centered on one primary host
|
||||
- simplify SEO and observability interpretation
|
||||
|
||||
## Release Workflow
|
||||
|
||||
### Trigger
|
||||
|
||||
Independent workflow:
|
||||
|
||||
- `.github/workflows/service_release_frontend-deploy.yml`
|
||||
|
||||
### Steps
|
||||
|
||||
1. check out repository
|
||||
2. clone `knowledge`
|
||||
3. build and push `ghcr.io/<owner>/dashboard:<tag>`
|
||||
4. render `.env.runtime`
|
||||
5. upload compose/caddy/env files to the host
|
||||
6. log in to GHCR on the host
|
||||
7. pull the new image
|
||||
8. run `frontend-assets`
|
||||
9. start or refresh `dashboard` and `caddy`
|
||||
10. verify both domains
|
||||
|
||||
### Why separate from the existing image workflow
|
||||
|
||||
The existing image workflow is broader and oriented toward generic image publishing. This single-node frontend workflow needs tighter control over:
|
||||
|
||||
- build-time public env injection
|
||||
- production deployment sequencing
|
||||
- SSH-based single-host rollout
|
||||
- host-specific runtime file rendering
|
||||
|
||||
So the frontend release path should remain explicit and independent.
|
||||
|
||||
## Rollback Model
|
||||
|
||||
Rollback unit:
|
||||
|
||||
- image tag reference in `.env.runtime`
|
||||
|
||||
Rollback steps:
|
||||
|
||||
1. set `FRONTEND_IMAGE` to a previous known-good tag
|
||||
2. rerun `frontend-assets`
|
||||
3. restart `dashboard` and `caddy`
|
||||
4. verify `cn-console.svc.plus`
|
||||
|
||||
This avoids rebuilding and keeps rollback cheap on the weak-IO host.
|
||||
|
||||
## Security and Secret Handling
|
||||
|
||||
Secrets must not be committed to the repo. The workflow should consume:
|
||||
|
||||
- `SINGLE_NODE_VPS_SSH_PRIVATE_KEY`
|
||||
- service tokens
|
||||
- vault tokens
|
||||
- internal service token
|
||||
- optional Cloudflare credentials
|
||||
|
||||
Public defaults and non-secret values belong in checked-in examples or GitHub repository/environment variables. Secret-only values stay in GitHub Secrets and are rendered into the host runtime env during deployment.
|
||||
|
||||
## Operational Risks
|
||||
|
||||
### Risk 1: build-time public env mismatch
|
||||
|
||||
If GitHub environment variables are incomplete, the image may build successfully but the frontend can render wrong links or lose third-party integration IDs.
|
||||
|
||||
Mitigation:
|
||||
|
||||
- keep `.env.example` aligned
|
||||
- document required GitHub `vars`
|
||||
- keep the build args list explicit
|
||||
|
||||
### Risk 2: image layout drift
|
||||
|
||||
If the Docker image no longer contains `/app/dashboard/static` or `/app/dashboard/public`, the `frontend-assets` step fails.
|
||||
|
||||
Mitigation:
|
||||
|
||||
- keep asset extraction paths documented
|
||||
- update deploy scripts whenever Dockerfile output layout changes
|
||||
|
||||
### Risk 3: docs payload growth
|
||||
|
||||
Bundling docs and `knowledge` into the frontend image increases image size.
|
||||
|
||||
Mitigation:
|
||||
|
||||
- accept it temporarily
|
||||
- revisit once `docs.svc.plus` is externalized
|
||||
|
||||
### Risk 4: single-node blast radius
|
||||
|
||||
The host handles both reverse proxy and app runtime. Misconfiguration affects the whole frontend surface.
|
||||
|
||||
Mitigation:
|
||||
|
||||
- keep compose simple
|
||||
- keep Caddy config minimal
|
||||
- use image-tag rollback
|
||||
|
||||
## Future Follow-Up
|
||||
|
||||
### Near term
|
||||
|
||||
- populate required GitHub `vars` and `secrets`
|
||||
- run the workflow against `root@cn-console.svc.plus`
|
||||
- validate DNS, TLS, static assets, login flow, and upstream API proxy behavior
|
||||
|
||||
### Later
|
||||
|
||||
- move docs delivery out of the frontend image after `docs.svc.plus` is service/API based
|
||||
- consider splitting static assets to object storage or CDN if traffic grows
|
||||
- evaluate whether the host should keep only Caddy plus one app container, or whether docs can be removed entirely from this runtime
|
||||
|
||||
## Source of Truth Rule
|
||||
|
||||
For this deployment design:
|
||||
|
||||
- upstream repo source of truth: `console.svc.plus`
|
||||
- service-local design note location: `docs/plans/`
|
||||
- control-plane repo role: consume via git submodule after upstream commit is pushed
|
||||
|
||||
Do not move the primary design ownership to the control-plane repository.
|
||||
@ -1,5 +0,0 @@
|
||||
# Plans
|
||||
|
||||
This directory stores service-local design notes and implementation plans for `console.svc.plus`.
|
||||
|
||||
The source of truth stays in this upstream repository. Control-plane repositories may reference these documents through git submodule updates after upstream changes are pushed.
|
||||
@ -1,218 +0,0 @@
|
||||
# UI 主题与风格重构方案
|
||||
|
||||
作为资深 Web UI 设计师和前端工程师,针对我们 SaaS 控制台当前的 Next.js + Tailwind CSS 架构,结合 WCAG 可访问性标准和跨端响应式需求,特制定此 UI 重构方案。方案旨在提升整体视觉一致性、深色模式的可访问性,并优化桌面与移动端的用户体验(特别是 iOS/Android 浏览器的触控和渲染差异)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 审查现有界面
|
||||
|
||||
在审查当前的界面代码(如 `tailwind.config.js`, `src/app/globals.css`, `src/components/theme/` 及 `Navbar.tsx`)后,我发现了以下改进点:
|
||||
|
||||
* **色彩硬编码与对比度**:在部分文件(如 `designTokens.ts` 和 `Navbar.tsx` 的内联类名)中仍然存在类似 `#3467e9`, `bg-[#f6f7f9]` 的硬编码颜色,这破坏了主题切换的完整性。同时,深色模式下的次级文本(如 `text-muted` `#cbd5f5`)在深色背景(`#0f172a`)上的对比度可能无法满足 WCAG AA 级 4.5:1 的标准。
|
||||
* **语义化不足**:`Navbar.tsx` 中的菜单项过度使用了 `<div>` 和普通的 `<a>` 标签,缺少 `<nav>`, `<ul>`, `<li>` 结构,并且缺乏管理下拉/折叠状态的 `aria-expanded` 属性。
|
||||
* **触控目标尺寸**:移动端的某些交互元素(如链接和图标按钮)没有保证至少 44px × 44px 的物理点击区域,这在 iOS/Android 设备上容易造成误触。
|
||||
* **深色模式层级**:深色模式主要依赖背景色区分层级(如 `surface-muted`),缺乏细微的边框(Border)或发光阴影(Glow/Shadow)来凸显浮动面板(如 Dropdown 菜单)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 定义设计系统 Token
|
||||
|
||||
我们需要收敛硬编码颜色,改用语义化的 CSS 变量(Token),并对深/浅色模式设定严格的对比度要求。
|
||||
|
||||
### 颜色 Token 规划
|
||||
|
||||
* **背景色**:
|
||||
* 浅色:纯白 `#ffffff` (Surface) 或极浅灰 `#f8fafc` (Background)。
|
||||
* 深色:避免纯黑,使用 `#0f172a` (Background) 和微亮的暗灰 `#1e293b` (Surface),减轻视觉疲劳。
|
||||
* **文本色**:
|
||||
* 主要文本 (`--color-text`):浅色模式 `#0f172a`,深色模式 `#f8fafc`。
|
||||
* 次要文本 (`--color-text-muted`):确保在深浅背景下对比度均大于 4.5:1。浅色推荐 `#475569`,深色推荐 `#94a3b8`。
|
||||
* **主色与交互色**:
|
||||
* `--color-primary`:统一使用高对比度的主题蓝(如 `#2563eb`),确保按钮上的白色文字(`--color-primary-foreground`)对比度达标。
|
||||
|
||||
### Tailwind / CSS 变量伪代码
|
||||
|
||||
```css
|
||||
/* src/app/globals.css 补充与覆盖 */
|
||||
:root {
|
||||
/* 基础结构色 */
|
||||
--bg-color: #f8fafc;
|
||||
--surface-color: #ffffff;
|
||||
/* 文本色 */
|
||||
--text-color: #0f172a;
|
||||
--secondary-color: #475569;
|
||||
/* 主色调 */
|
||||
--primary: #2563eb;
|
||||
--primary-foreground: #ffffff;
|
||||
/* 边框与分隔线 */
|
||||
--border-color: #e2e8f0;
|
||||
}
|
||||
|
||||
:root[data-theme="dark"],
|
||||
.dark {
|
||||
--bg-color: #0f172a;
|
||||
--surface-color: #1e293b;
|
||||
--text-color: #f8fafc;
|
||||
--secondary-color: #94a3b8; /* 保证对比度 > 4.5:1 */
|
||||
--primary: #3b82f6; /* 在深色背景下稍微提亮主色 */
|
||||
--primary-foreground: #ffffff;
|
||||
--border-color: #334155;
|
||||
/* 深色模式下的特殊视觉补偿 */
|
||||
--shadow-elevation: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 设计排版体系
|
||||
|
||||
排版需兼顾多语言(中英文混合)和多端阅读体验。
|
||||
|
||||
* **字体选择**:保留现有的 `Geist`,同时后备配置 `Inter` 或 `Noto Sans SC` 以优化中文显示:
|
||||
`font-family: var(--font-geist-sans), 'Inter', 'Noto Sans SC', sans-serif;`
|
||||
* **字号范围 (Fluid Typography / 断点响应)**:
|
||||
* **移动端**:基础字号 16px(防止 iOS Safari 输入框自动缩放),正文 `16px - 18px`,大标题约 `24px - 28px`。
|
||||
* **桌面端**:正文 `16px - 20px`,大标题可达 `32px - 48px`。
|
||||
* **行高与间距**:正文行高 `1.5` 至 `1.6`(150%-160%),段落间距使用 `margin-bottom: 1.5em`。标题行高缩紧至 `1.2`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 构建全局样式与主题切换
|
||||
|
||||
当前系统已通过 Zustand (`store.ts`) 和 `ThemeProvider.tsx` 实现了基于 `localStorage` 和 `data-theme` 的切换。
|
||||
|
||||
* **优化防闪烁 (FOUC)**:由于在 Next.js 中客户端 Hydration 会有延迟,需要确保在 `<head>` 中注入一个同步脚本读取 `localStorage` 和 `prefers-color-scheme`,在 React 渲染前就应用 `dark` 类或 `data-theme`。
|
||||
* **深色模式下的层级分离**:避免仅仅改变背景色。对于弹窗、Dropdown 等元素,在深色模式下增加一个极细的亮色边框:
|
||||
```css
|
||||
.dark .surface-elevated {
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
box-shadow: var(--shadow-elevation);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 响应式布局策略
|
||||
|
||||
* **断点定义**:
|
||||
* 移动端:`< 768px` (Tailwind 默认 `md` 以下)。
|
||||
* 平板/桌面端:`>= 768px` (`md` 及以上)。
|
||||
* **移动端 (iOS/Android) 策略**:
|
||||
* 隐藏复杂的侧边栏(Sidebar),顶部导航精简为 Logo 和汉堡菜单。
|
||||
* 所有的按钮 (`button`, `a`) 必须拥有至少 `min-h-[44px] min-w-[44px]` 的触控区域(可以使用 padding 撑开)。
|
||||
* **桌面端策略**:
|
||||
* 最大化利用横向空间,采用 Sidebar + Main Content 或全宽 Header 的多列布局。
|
||||
|
||||
---
|
||||
|
||||
## 6. 重构导航菜单 (示例)
|
||||
|
||||
当前 `Navbar.tsx` 使用了平铺的链接。我们需要使用语义化标签,并通过 `aria-expanded` 增强可访问性,并分离移动端的汉堡菜单和桌面端菜单。
|
||||
|
||||
### 菜单组件伪代码
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function SemanticNavbar() {
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full bg-surface border-b border-border">
|
||||
<nav className="max-w-7xl mx-auto px-4 min-h-[64px] flex items-center justify-between" aria-label="Main Navigation">
|
||||
{/* Logo */}
|
||||
<div className="flex-shrink-0">
|
||||
<a href="/" className="font-bold text-text-color text-lg flex items-center min-h-[44px]">Logo</a>
|
||||
</div>
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<ul className="hidden md:flex items-center gap-6">
|
||||
<li>
|
||||
<a href="/dashboard" className="text-secondary-color hover:text-text-color transition-colors py-2">
|
||||
控制台
|
||||
</a>
|
||||
</li>
|
||||
<li className="relative group">
|
||||
{/* 父菜单如果是功能性展开按钮,使用 button */}
|
||||
<button
|
||||
aria-expanded={isDropdownOpen}
|
||||
aria-controls="services-dropdown"
|
||||
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
||||
className="flex items-center gap-1 text-secondary-color hover:text-text-color py-2"
|
||||
>
|
||||
服务 <ChevronDownIcon />
|
||||
</button>
|
||||
{/* Dropdown 弹窗 */}
|
||||
<ul
|
||||
id="services-dropdown"
|
||||
className={`absolute top-full left-0 mt-2 w-48 bg-surface-elevated border border-border rounded-md shadow-md ${isDropdownOpen ? 'block' : 'hidden'}`}
|
||||
>
|
||||
<li>
|
||||
{/* 保证键盘 Tab 键能聚焦到这里 */}
|
||||
<a href="/service/1" className="block px-4 py-3 text-text-color hover:bg-surface-hover">服务一</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{/* Mobile Hamburger Button */}
|
||||
<div className="md:hidden">
|
||||
<button
|
||||
aria-controls="mobile-menu"
|
||||
aria-expanded={isMobileMenuOpen}
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="p-2 min-w-[44px] min-h-[44px] text-text-color"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<HamburgerIcon />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Mobile Menu Panel */}
|
||||
<div
|
||||
id="mobile-menu"
|
||||
className={`md:hidden overflow-hidden transition-[max-height] duration-300 ease-in-out ${isMobileMenuOpen ? 'max-h-screen' : 'max-h-0'}`}
|
||||
>
|
||||
<ul className="px-4 pb-4 space-y-2 bg-surface">
|
||||
<li>
|
||||
<a href="/dashboard" className="block py-3 text-text-color">控制台</a>
|
||||
</li>
|
||||
{/* 移动端子菜单可以直接平铺或使用手风琴折叠 */}
|
||||
<li>
|
||||
<span className="block py-3 text-secondary-color font-semibold">服务</span>
|
||||
<ul className="pl-4 space-y-2 border-l-2 border-border ml-2">
|
||||
<li><a href="/service/1" className="block py-3 text-text-color">服务一</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 针对移动端和桌面端的优化差异
|
||||
|
||||
* **iOS/Android 浏览器差异处理**:
|
||||
* **iOS Safari 底部安全区**:在主内容区底部增加 `padding-bottom: env(safe-area-inset-bottom);`,避免按钮被 Home Indicator 遮挡。
|
||||
* **字体抗锯齿 (Anti-aliasing)**:在 `globals.css` 中的 `body` 标签已设置 `-webkit-font-smoothing: antialiased;`,在深色模式下这能让细小的亮色文字边缘更加平滑,没有晕影。
|
||||
* **长文本限制**:在博客或文档页面,容器设置 `max-w-prose` (相当于 `max-width: 65ch`),确保每行不超过 60 个字符,提升阅读体验。
|
||||
* **桌面端强化**:
|
||||
* 可利用 Hover 状态提供丰富的反馈(如背景变色、细微的 `transform: translateY(-1px)`)。
|
||||
* 在宽屏上显示侧边栏和次要信息列(多列网格布局)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 可访问性测试与迭代流程
|
||||
|
||||
为确保重构后的界面符合标准,开发团队应在提交代码前进行以下检查:
|
||||
|
||||
1. **对比度扫描**:使用 Chrome DevTools 的 Lighthouse 或 WebAIM 对比度检查器,确保所有的正文/背景对比度 >= 4.5:1,大号文本 >= 3.0:1。
|
||||
2. **重排与缩放测试**:使用浏览器的缩放功能放大页面到 `200%` 和 `400%`。确保在 `400%` 下页面自动转为单列移动端布局,且文字不出现截断或相互重叠(开启 CSS 文本重排 `text-wrap: balance` 或避免固定高度)。
|
||||
3. **键盘导航测试**:不使用鼠标,仅用 `Tab` 和 `Enter` 遍历界面。确保所有交互元素有明显的 `:focus-visible` 外框线(Tailwind `focus-visible:ring-2 focus-visible:ring-primary`)。二级菜单不能在 Tab 聚焦到父元素时自动弹出(除非是用按钮触发),以避免用户被迫穿过无数个子菜单才能到达下一个主栏目。
|
||||
4. **屏幕阅读器体验**:开启 VoiceOver (Mac/iOS) 或 TalkBack (Android),验证 `aria-expanded`, `aria-controls` 等状态是否能被正确播报。
|
||||
@ -1,9 +0,0 @@
|
||||
# Cli
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Cli.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,154 +0,0 @@
|
||||
# Deployment Runbook
|
||||
|
||||
## Scope
|
||||
|
||||
- Runtime: `console.svc.plus`
|
||||
- Topology: `Caddy + Docker Compose + GitHub Actions`
|
||||
- Deploy host: `root@jp-xhttp-contabo.svc.plus`
|
||||
- Public domains:
|
||||
- `https://www.svc.plus`
|
||||
- `https://console.svc.plus`
|
||||
- Canonical public origin: `https://www.svc.plus`
|
||||
|
||||
## Current Delivery Model
|
||||
|
||||
The production frontend is deployed as a prebuilt container image from GitHub Actions.
|
||||
|
||||
- The target host does not build images locally.
|
||||
- The workflow builds an `linux/amd64` image and pushes it to `ghcr.io/<owner>/dashboard:<sha>`.
|
||||
- The host only performs `docker login`, `docker compose pull`, static asset extraction, and `docker compose up`.
|
||||
- `/docs` and `/blogs` fetch their content from `docs.svc.plus` at runtime; the frontend image no longer packs `knowledge/` or synced markdown payloads.
|
||||
- Static assets are extracted from the image into a shared Docker volume so Caddy can serve `/_next/static/*` and checked-in public files directly.
|
||||
|
||||
This is intentionally static-first for the current weak-IO single-node host. Dynamic HTML, auth routes, and API proxy routes still run through the Next.js container, but docs/blog content delivery is now delegated to `docs.svc.plus`.
|
||||
|
||||
## Control Plane & DNS Stage
|
||||
|
||||
The control repo (`github-org-x-evor`) tracks `console.svc.plus` through `console.svc.plus.code-workspace` and keeps the `subrepos/accounts.svc.plus` pointer in sync via `skills/cross-repo-upstream-submodule-sync`. Releases resolve metadata with that workspace and the `config/single-node-release` manifests. After `.github/workflows/pipeline.yaml` finishes pushing the new image, the control-plane DNS automation calls `scripts/github-actions/update-release-dns.sh` to update Cloudflare DNS so the new endpoint is reachable through the current production host `jp-xhttp-contabo.svc.plus`.
|
||||
|
||||
## Runtime Layout
|
||||
|
||||
Remote directory:
|
||||
|
||||
```bash
|
||||
/opt/console-svc-plus
|
||||
```
|
||||
|
||||
Files deployed there:
|
||||
|
||||
```bash
|
||||
docker-compose.yml
|
||||
Caddyfile
|
||||
.env.runtime
|
||||
```
|
||||
|
||||
Containers:
|
||||
|
||||
- `dashboard`: Next.js standalone runtime on port `3000`
|
||||
- `frontend-assets`: one-shot task that copies `static/` and `public/` into a shared volume
|
||||
- `caddy`: TLS termination and reverse proxy
|
||||
|
||||
## GitHub Actions Inputs
|
||||
|
||||
Workflow:
|
||||
|
||||
```text
|
||||
.github/workflows/pipeline.yaml
|
||||
```
|
||||
|
||||
Secrets required:
|
||||
|
||||
- `SINGLE_NODE_VPS_SSH_PRIVATE_KEY`
|
||||
- `OPENCLAW_GATEWAY_TOKEN` if used
|
||||
- `VAULT_TOKEN` if used
|
||||
- `AI_GATEWAY_ACCESS_TOKEN` if used
|
||||
- `INTERNAL_SERVICE_TOKEN` if used
|
||||
- `CLOUDFLARE_DNS_API_TOKEN` for the Cloudflare DNS stage
|
||||
- `CLOUDFLARE_API_TOKEN` if homepage Cloudflare analytics are enabled at runtime
|
||||
|
||||
Repository/environment variables recommended:
|
||||
|
||||
- `CANONICAL_DOMAIN`
|
||||
- `SERVED_DOMAINS`
|
||||
- `APP_BASE_URL`
|
||||
- `NEXT_PUBLIC_APP_BASE_URL`
|
||||
- `NEXT_PUBLIC_SITE_URL`
|
||||
- `NEXT_PUBLIC_LOGIN_URL`
|
||||
- `NEXT_PUBLIC_DOCS_BASE_URL`
|
||||
- `ACCOUNT_SERVICE_URL`
|
||||
- `NEXT_PUBLIC_ACCOUNT_SERVICE_URL`
|
||||
- `SERVER_SERVICE_URL`
|
||||
- `NEXT_PUBLIC_SERVER_SERVICE_URL`
|
||||
- `RUNTIME_HOSTNAME`
|
||||
- `DEPLOYMENT_HOSTNAME`
|
||||
- `DOCS_SERVICE_URL`
|
||||
- `DOCS_SERVICE_INTERNAL_URL`
|
||||
- `NEXT_PUBLIC_RUNTIME_ENVIRONMENT`
|
||||
- `NEXT_PUBLIC_RUNTIME_REGION`
|
||||
- `NEXT_PUBLIC_GISCUS_*`
|
||||
- `NEXT_PUBLIC_STRIPE_*`
|
||||
- `NEXT_PUBLIC_PAYPAL_CLIENT_ID`
|
||||
- `CLOUDFLARE_ZONE_TAG` if homepage Cloudflare analytics are enabled at runtime
|
||||
- `CLOUDFLARE_DNS_ZONE_TAG` only for single-domain manual DNS override; the GitHub Actions DNS stage resolves zones from each domain automatically
|
||||
|
||||
## Release Flow
|
||||
|
||||
1. GitHub Actions checks out the repo.
|
||||
2. Docker builds the frontend image with the public `NEXT_PUBLIC_*` values needed at build time.
|
||||
3. The image is pushed to GHCR.
|
||||
4. The workflow updates Cloudflare DNS for the release domain.
|
||||
5. The workflow renders `.env.runtime`, including the canonical public origin and both served frontend domains.
|
||||
6. The workflow uploads `docker-compose.yml`, `Caddyfile`, and `.env.runtime` to the host.
|
||||
7. The host pulls the new image, refreshes the static asset volume, and starts `dashboard + caddy`.
|
||||
8. The workflow verifies both `www.svc.plus` and `console.svc.plus`, and fails the release if either domain serves a different runtime version.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
Local syntax checks:
|
||||
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus
|
||||
bash -n scripts/github-actions/render-frontend-runtime-env.sh
|
||||
bash -n scripts/github-actions/deploy-frontend-single-node.sh
|
||||
cp deploy/single-node/.env.runtime.example deploy/single-node/.env.runtime
|
||||
docker compose -f deploy/single-node/docker-compose.yml --env-file deploy/single-node/.env.runtime config >/tmp/console-compose.rendered.yaml
|
||||
rm -f deploy/single-node/.env.runtime
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
yaml.safe_load(Path('.github/workflows/pipeline.yaml').read_text())
|
||||
print('workflow yaml ok')
|
||||
PY
|
||||
```
|
||||
|
||||
Remote checks:
|
||||
|
||||
```bash
|
||||
ssh root@jp-xhttp-contabo.svc.plus "cd /opt/console-svc-plus && docker compose --env-file .env.runtime ps"
|
||||
ssh root@jp-xhttp-contabo.svc.plus "curl -fsSI -H 'Host: www.svc.plus' http://127.0.0.1/"
|
||||
ssh root@jp-xhttp-contabo.svc.plus "curl -fsSI -H 'Host: console.svc.plus' http://127.0.0.1/"
|
||||
curl -fsSIL https://www.svc.plus
|
||||
curl -fsSIL https://console.svc.plus
|
||||
```
|
||||
|
||||
## Failure Signatures
|
||||
|
||||
- `docker login ghcr.io` fails
|
||||
The workflow token or package visibility is wrong.
|
||||
- `frontend-assets` fails
|
||||
The image layout changed and no longer contains `/app/dashboard/static` or `/app/dashboard/public`.
|
||||
- `www.svc.plus` or `console.svc.plus` returns `502`
|
||||
Caddy is up, but the `dashboard` container failed or is not reachable on port `3000`.
|
||||
|
||||
## Rollback
|
||||
|
||||
1. Re-run the workflow with a previous known-good image tag.
|
||||
2. Or update `/opt/console-svc-plus/.env.runtime` and set `FRONTEND_IMAGE=ghcr.io/<owner>/dashboard:<previous-tag>`.
|
||||
3. Restart the services:
|
||||
|
||||
```bash
|
||||
ssh root@jp-xhttp-contabo.svc.plus "cd /opt/console-svc-plus && docker compose --env-file .env.runtime run --rm frontend-assets"
|
||||
ssh root@jp-xhttp-contabo.svc.plus "cd /opt/console-svc-plus && docker compose --env-file .env.runtime up -d dashboard caddy"
|
||||
```
|
||||
|
||||
4. Verify `https://www.svc.plus` and `https://console.svc.plus` again before closing the incident.
|
||||
@ -1,9 +0,0 @@
|
||||
# Examples
|
||||
|
||||
## Purpose
|
||||
|
||||
- TODO: Add content specific to Examples.
|
||||
|
||||
## Notes
|
||||
|
||||
- TODO: Link to related documents in this section.
|
||||
@ -1,34 +0,0 @@
|
||||
# 控制台服务 文档
|
||||
|
||||
该仓库以 Web 前端体验为主,文档需要覆盖产品流程、界面边界与集成触点。
|
||||
|
||||
## 当前状态快照
|
||||
|
||||
- 根 README 标题: `console.svc.plus`
|
||||
- 构建与运行时证据: package.json (`dashboard`)
|
||||
- 自动识别的主要目录: `src/`, `scripts/`, `tests/`, `config/`, `public/`
|
||||
- 现有文档数量: 86
|
||||
|
||||
## 核心双语文档
|
||||
|
||||
- [架构](architecture.md)
|
||||
- [设计](design.md)
|
||||
- [部署](deployment.md)
|
||||
- [使用手册](user-guide.md)
|
||||
- [开发手册](developer-guide.md)
|
||||
- [Vibe Coding 参考](vibe-coding-reference.md)
|
||||
|
||||
## 待归并的历史文档
|
||||
|
||||
- `SEO-AUDIT-REPORT.md`
|
||||
- `SEO-WORK-SUMMARY.md`
|
||||
- `advanced/customization.md`
|
||||
- `advanced/performance.md`
|
||||
- `advanced/scalability.md`
|
||||
- `advanced/security.md`
|
||||
- `api/auth.md`
|
||||
- `api/endpoints.md`
|
||||
- `api/errors.md`
|
||||
- `api/overview.md`
|
||||
- `appendix/faq.md`
|
||||
- `appendix/glossary.md`
|
||||
@ -1,11 +0,0 @@
|
||||
# SEO Audit Report - console.svc.plus (ZH)
|
||||
|
||||
> English: `../SEO-AUDIT-REPORT.md`
|
||||
|
||||
## 目的
|
||||
|
||||
- TODO: 补充中文内容。
|
||||
|
||||
## 备注
|
||||
|
||||
- TODO: 链接到本章节相关文档。
|
||||
@ -1,11 +0,0 @@
|
||||
# SEO 优化工作总结 (ZH)
|
||||
|
||||
> English: `../SEO-WORK-SUMMARY.md`
|
||||
|
||||
## 目的
|
||||
|
||||
- TODO: 补充中文内容。
|
||||
|
||||
## 备注
|
||||
|
||||
- TODO: 链接到本章节相关文档。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user