mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-04 21:31:23 +08:00
Add comprehensive documentation for CLICD features and operations
- Introduced Container Management documentation covering lifecycle operations, resource management, and console access. - Added Dashboard documentation detailing metrics and related APIs. - Created Host Report documentation summarizing host environment and resource status. - Included Image Management documentation for template handling and management actions. - Documented Networking and Routing features including NAT4 and IPv6 management. - Added Security Alerts documentation outlining alert scenarios and API usage. - Created Snapshot Management documentation for snapshot operations and scheduling. - Documented Sub-user management for granting access to specific containers. - Added Configuration guide detailing runtime settings and security recommendations. - Created Installation guide for setting up CLICD with requirements and steps. - Added Introduction and Quick Start guides for new users. - Documented Upgrade process with version checking and pre-upgrade checklist. - Created Deployment guide for service exposure and firewall recommendations. - Added FAQ section addressing common questions and concerns. - Documented Troubleshooting steps for common issues encountered.
This commit is contained in:
@@ -110,7 +110,8 @@ func HandleRoutingIPv4Scan(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func handleRoutingGet(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireScope(w, r, "routing:read") {
|
||||
if !hasAnyScope(r, "routing:read", "routing:write") {
|
||||
jsonResponse(w, http.StatusForbidden, APIResponse{Success: false, Message: "Insufficient API key scope"})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"clicd/internal/config"
|
||||
)
|
||||
|
||||
func TestHandleRoutingGetAllowsRoutingWriteScope(t *testing.T) {
|
||||
config.AppConfig = &config.ClicdConfig{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/routing", nil)
|
||||
req = withAuthContext(req, AuthContext{
|
||||
Type: authTypeAPIKey,
|
||||
Scopes: []string{"routing:write"},
|
||||
})
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handleRoutingGet(rec, req)
|
||||
|
||||
if rec.Code == http.StatusForbidden {
|
||||
t.Fatal("routing:write scope should be able to receive the routing response after updates")
|
||||
}
|
||||
}
|
||||
+137
-48
@@ -1,5 +1,105 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
|
||||
const zhNav = [
|
||||
{ text: '指南', link: '/guide/introduction' },
|
||||
{ text: '功能', link: '/features/dashboard' },
|
||||
{ text: '运维', link: '/operations/deployment' },
|
||||
{ text: '开发', link: '/developer/architecture' },
|
||||
]
|
||||
|
||||
const enNav = [
|
||||
{ text: 'Guide', link: '/en/guide/introduction' },
|
||||
{ text: 'Features', link: '/en/features/dashboard' },
|
||||
{ text: 'Operations', link: '/en/operations/deployment' },
|
||||
{ text: 'Developer', link: '/en/developer/architecture' },
|
||||
]
|
||||
|
||||
const zhSidebar = [
|
||||
{
|
||||
text: '开始',
|
||||
items: [
|
||||
{ text: '项目介绍', link: '/guide/introduction' },
|
||||
{ text: '安装', link: '/guide/installation' },
|
||||
{ text: '升级', link: '/guide/upgrade' },
|
||||
{ text: '快速上手', link: '/guide/quick-start' },
|
||||
{ text: '配置说明', link: '/guide/configuration' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '功能',
|
||||
items: [
|
||||
{ text: '控制面板', link: '/features/dashboard' },
|
||||
{ text: '容器管理', link: '/features/containers' },
|
||||
{ text: '镜像管理', link: '/features/images' },
|
||||
{ text: '网络与路由', link: '/features/networking' },
|
||||
{ text: '快照管理', link: '/features/snapshots' },
|
||||
{ text: '安全告警', link: '/features/security' },
|
||||
{ text: '子用户', link: '/features/sub-users' },
|
||||
{ text: 'API 集成', link: '/features/api' },
|
||||
{ text: '主机报告', link: '/features/host-report' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '运维',
|
||||
items: [
|
||||
{ text: '部署建议', link: '/operations/deployment' },
|
||||
{ text: '故障排查', link: '/operations/troubleshooting' },
|
||||
{ text: '常见问题', link: '/operations/faq' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '开发',
|
||||
items: [
|
||||
{ text: '系统架构', link: '/developer/architecture' },
|
||||
{ text: '本地构建', link: '/developer/build' },
|
||||
{ text: '发布流程', link: '/developer/release' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const enSidebar = [
|
||||
{
|
||||
text: 'Get Started',
|
||||
items: [
|
||||
{ text: 'Introduction', link: '/en/guide/introduction' },
|
||||
{ text: 'Installation', link: '/en/guide/installation' },
|
||||
{ text: 'Upgrade', link: '/en/guide/upgrade' },
|
||||
{ text: 'Quick Start', link: '/en/guide/quick-start' },
|
||||
{ text: 'Configuration', link: '/en/guide/configuration' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Features',
|
||||
items: [
|
||||
{ text: 'Dashboard', link: '/en/features/dashboard' },
|
||||
{ text: 'Containers', link: '/en/features/containers' },
|
||||
{ text: 'Images', link: '/en/features/images' },
|
||||
{ text: 'Networking & Routing', link: '/en/features/networking' },
|
||||
{ text: 'Snapshots', link: '/en/features/snapshots' },
|
||||
{ text: 'Security Alerts', link: '/en/features/security' },
|
||||
{ text: 'Sub-users', link: '/en/features/sub-users' },
|
||||
{ text: 'API Integration', link: '/en/features/api' },
|
||||
{ text: 'Host Report', link: '/en/features/host-report' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Operations',
|
||||
items: [
|
||||
{ text: 'Deployment', link: '/en/operations/deployment' },
|
||||
{ text: 'Troubleshooting', link: '/en/operations/troubleshooting' },
|
||||
{ text: 'FAQ', link: '/en/operations/faq' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Developer',
|
||||
items: [
|
||||
{ text: 'Architecture', link: '/en/developer/architecture' },
|
||||
{ text: 'Local Build', link: '/en/developer/build' },
|
||||
{ text: 'Release Process', link: '/en/developer/release' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default defineConfig({
|
||||
title: 'CLICD',
|
||||
description: '面向 LXC/KVM 的轻量虚拟化管理面板文档',
|
||||
@@ -10,59 +110,48 @@ export default defineConfig({
|
||||
head: [
|
||||
['link', { rel: 'icon', href: '/favicon.svg' }],
|
||||
],
|
||||
locales: {
|
||||
root: {
|
||||
label: '简体中文',
|
||||
lang: 'zh-CN',
|
||||
description: '面向 LXC/KVM 的轻量虚拟化管理面板文档',
|
||||
themeConfig: {
|
||||
nav: zhNav,
|
||||
sidebar: zhSidebar,
|
||||
outline: {
|
||||
label: '页面导航',
|
||||
},
|
||||
darkModeSwitchLabel: '外观',
|
||||
sidebarMenuLabel: '菜单',
|
||||
returnToTopLabel: '返回顶部',
|
||||
},
|
||||
},
|
||||
en: {
|
||||
label: 'English',
|
||||
lang: 'en-US',
|
||||
link: '/en/',
|
||||
description: 'Documentation for the lightweight LXC/KVM virtualization management panel.',
|
||||
themeConfig: {
|
||||
nav: enNav,
|
||||
sidebar: enSidebar,
|
||||
outline: {
|
||||
label: 'On This Page',
|
||||
},
|
||||
darkModeSwitchLabel: 'Appearance',
|
||||
sidebarMenuLabel: 'Menu',
|
||||
returnToTopLabel: 'Return to Top',
|
||||
footer: {
|
||||
message: 'CLICD documentation for deployment, usage, operations, and integration.',
|
||||
copyright: 'Copyright © CLICD contributors',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
themeConfig: {
|
||||
logo: '/favicon.svg',
|
||||
search: {
|
||||
provider: 'local',
|
||||
},
|
||||
nav: [
|
||||
{ text: '指南', link: '/guide/introduction' },
|
||||
{ text: '功能', link: '/features/dashboard' },
|
||||
{ text: '运维', link: '/operations/deployment' },
|
||||
{ text: '开发', link: '/developer/architecture' },
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
text: '开始',
|
||||
items: [
|
||||
{ text: '项目介绍', link: '/guide/introduction' },
|
||||
{ text: '安装', link: '/guide/installation' },
|
||||
{ text: '升级', link: '/guide/upgrade' },
|
||||
{ text: '快速上手', link: '/guide/quick-start' },
|
||||
{ text: '配置说明', link: '/guide/configuration' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '功能',
|
||||
items: [
|
||||
{ text: '控制面板', link: '/features/dashboard' },
|
||||
{ text: '容器管理', link: '/features/containers' },
|
||||
{ text: '镜像管理', link: '/features/images' },
|
||||
{ text: '网络与路由', link: '/features/networking' },
|
||||
{ text: '快照管理', link: '/features/snapshots' },
|
||||
{ text: '安全告警', link: '/features/security' },
|
||||
{ text: '子用户', link: '/features/sub-users' },
|
||||
{ text: 'API 集成', link: '/features/api' },
|
||||
{ text: '主机报告', link: '/features/host-report' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '运维',
|
||||
items: [
|
||||
{ text: '部署建议', link: '/operations/deployment' },
|
||||
{ text: '故障排查', link: '/operations/troubleshooting' },
|
||||
{ text: '常见问题', link: '/operations/faq' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '开发',
|
||||
items: [
|
||||
{ text: '系统架构', link: '/developer/architecture' },
|
||||
{ text: '本地构建', link: '/developer/build' },
|
||||
{ text: '发布流程', link: '/developer/release' },
|
||||
],
|
||||
},
|
||||
],
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/MengMengCode/CLICD' },
|
||||
],
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Architecture
|
||||
|
||||
CLICD consists of a Go backend, a React frontend, and host virtualization capabilities.
|
||||
|
||||
## Backend
|
||||
|
||||
The backend entry point is `backend/main.go`, and HTTP routes are centralized in `backend/internal/server/server.go`. Main modules:
|
||||
|
||||
- `internal/api`: HTTP APIs for the web panel and `/api/v1`.
|
||||
- `internal/config`: configuration and SQLite storage.
|
||||
- `internal/lxc`: LXC container management.
|
||||
- `internal/kvm`: KVM/libvirt virtual machine management.
|
||||
- `internal/cli`: command-line management entry point.
|
||||
- `internal/server`: embedded frontend assets and HTTP service.
|
||||
- `internal/version`: version number.
|
||||
|
||||
## Frontend
|
||||
|
||||
The frontend entry point is `frontend/src/main.tsx`. Pages live in `frontend/src/pages`, and shared components live in `frontend/src/components`.
|
||||
|
||||
Main pages:
|
||||
|
||||
- Dashboard: `Dashboard.tsx`
|
||||
- Container list: `Containers.tsx`
|
||||
- Container details: `ContainerDetail.tsx`
|
||||
- Image Management: `ImageManagement.tsx`
|
||||
- Security Alerts: `Security.tsx`
|
||||
- Snapshot Management: `Snapshots.tsx`
|
||||
- Routing Management: `Routing.tsx`
|
||||
- API Integration: `ApiIntegration.tsx`
|
||||
- Host Report: `HostReport.tsx`
|
||||
- Sub-user Management: `SubUserManagement.tsx`
|
||||
|
||||
## Frontend Embedding
|
||||
|
||||
For production builds, frontend artifacts are placed in `backend/internal/server/web`. The backend serves them through Go embed and returns the SPA entry for non-API routes.
|
||||
|
||||
## API Layers
|
||||
|
||||
- `/api/*`: web panel and compatibility APIs.
|
||||
- `/api/v1/*`: versioned APIs recommended for external automation.
|
||||
- WebSSH and WebVNC use short-lived tickets before opening WebSocket connections.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Local Build
|
||||
|
||||
## Frontend Build
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Build output is written to `frontend/dist`.
|
||||
|
||||
## Backend Build
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
go test ./...
|
||||
go build -o ../build/clicd .
|
||||
```
|
||||
|
||||
To package the embedded web panel, sync the frontend build output into the backend embed directory first.
|
||||
|
||||
## One-command Build
|
||||
|
||||
The project root provides a build script:
|
||||
|
||||
```bash
|
||||
bash build.sh
|
||||
```
|
||||
|
||||
The script chains frontend build, static asset sync, and Go binary build.
|
||||
|
||||
## Docs Build
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
npm install
|
||||
npm run dev
|
||||
npm run build
|
||||
```
|
||||
|
||||
`npm run dev` starts a local preview, and `npm run build` generates static documentation.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Release Process
|
||||
|
||||
CLICD installation and upgrade rely on GitHub Release artifacts. Use semantic version tags such as `v1.1.6`.
|
||||
|
||||
## Version Number
|
||||
|
||||
Check the version in:
|
||||
|
||||
- `backend/internal/version/version.go`
|
||||
- `frontend/package.json`
|
||||
- Release tag.
|
||||
|
||||
## Release Artifacts
|
||||
|
||||
The installer first tries to download the Linux AMD64 archive:
|
||||
|
||||
```text
|
||||
clicd-linux-amd64.tar.gz
|
||||
```
|
||||
|
||||
In some cases, it may also try the standalone binary:
|
||||
|
||||
```text
|
||||
clicd-linux-amd64
|
||||
```
|
||||
|
||||
## Installer Behavior
|
||||
|
||||
- `CLICD_VERSION=latest`: use GitHub `releases/latest`.
|
||||
- `CLICD_VERSION=vX.Y.Z`: download artifacts from the specified release tag.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
CLICD_VERSION=v1.1.6 sh install.sh
|
||||
```
|
||||
|
||||
## Post-release Verification
|
||||
|
||||
- The installer can download the new version.
|
||||
- `systemctl status clicd` is healthy.
|
||||
- `/api/version` returns the new version.
|
||||
- The web panel can load frontend assets.
|
||||
- Container list, task queue, and API Key pages open correctly.
|
||||
@@ -0,0 +1,674 @@
|
||||
# API Integration
|
||||
|
||||
CLICD remains compatible with legacy `/api` endpoints, so existing integrations do not need to change. New integrations should use `/api/v1`; the list below is all v1, and the recommended container list endpoint is `GET /api/v1/containers`.
|
||||
|
||||
## Authentication
|
||||
|
||||
API keys can be created and managed from the API Integration page. Requests support either of these headers:
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: YOUR_API_KEY" https://panel.example.com/api/v1/containers
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer YOUR_API_KEY" https://panel.example.com/api/v1/dashboard
|
||||
```
|
||||
|
||||
## Response Shape
|
||||
|
||||
All APIs use the same response envelope:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "OK",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
Integrations should read only the business fields they need. New capabilities are added as optional fields where possible, without requiring existing plugins to rename current fields.
|
||||
|
||||
## Creation and Reinstall
|
||||
|
||||
Container creation, batch creation, reinstall, and batch reinstall support mixed NAT, public IPv4, IPv6 networking, plus Linux SSH login configuration. Public IPv4/IPv6 pools can be viewed with `GET /api/v1/routing` and updated with `PUT /api/v1/routing`.
|
||||
|
||||
Create container example:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "demo-lxc-01",
|
||||
"virtualization": "lxc",
|
||||
"template_id": "debian-bookworm",
|
||||
"vcpu": 1,
|
||||
"ram_mb": 512,
|
||||
"disk_gb": 10,
|
||||
"assign_nat": true,
|
||||
"port_mapping_count": 2,
|
||||
"assign_ipv4": false,
|
||||
"ipv4_count": 1,
|
||||
"public_ipv4s": [],
|
||||
"assign_ipv6": true,
|
||||
"ipv6_count": 1,
|
||||
"ipv6_addresses": [],
|
||||
"ssh_auth_mode": "auto_password",
|
||||
"ssh_password": "",
|
||||
"ssh_public_key": "",
|
||||
"expires_at": ""
|
||||
}
|
||||
```
|
||||
|
||||
Field notes:
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `assign_nat` | Whether to allocate NAT port mappings. If omitted, default NAT behavior is preserved. |
|
||||
| `assign_ipv4` | Whether to allocate public IPv4. |
|
||||
| `ipv4_count` | Number of public IPv4 addresses to allocate automatically. |
|
||||
| `public_ipv4s` | Explicit public IPv4 address list. |
|
||||
| `assign_ipv6` | Whether to allocate IPv6. |
|
||||
| `ipv6_count` | Number of IPv6 addresses to allocate automatically. |
|
||||
| `ipv6_addresses` | Explicit IPv6 address list. |
|
||||
| `ssh_auth_mode` | Linux creation supports `auto_password`, `password`, and `key`; reinstall also supports `keep`. |
|
||||
| `ssh_password` | Custom password for `password` mode. It must be 8-64 characters, include letters and digits, and contain no whitespace. |
|
||||
| `ssh_public_key` | One-line SSH public key for `key` mode. |
|
||||
|
||||
Reinstall example:
|
||||
|
||||
```json
|
||||
{
|
||||
"template_id": "debian-bookworm",
|
||||
"ssh_auth_mode": "keep",
|
||||
"ssh_password": "",
|
||||
"ssh_public_key": ""
|
||||
}
|
||||
```
|
||||
|
||||
`keep` is only for reinstall and keeps the current SSH password. Windows KVM images ignore Linux SSH public key fields.
|
||||
|
||||
## Python Example
|
||||
|
||||
Fetch containers:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
BASE_URL = "https://panel.example.com"
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
|
||||
session = requests.Session()
|
||||
session.headers.update({
|
||||
"X-API-Key": API_KEY,
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
|
||||
resp = session.get(f"{BASE_URL}/api/v1/containers", timeout=15)
|
||||
resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
Create a port mapping:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
BASE_URL = "https://panel.example.com"
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
CONTAINER_ID = "example-vm"
|
||||
|
||||
payload = {
|
||||
"protocol": "tcp",
|
||||
"host_port": 18080,
|
||||
"container_port": 80,
|
||||
"description": "web",
|
||||
}
|
||||
|
||||
resp = requests.post(
|
||||
f"{BASE_URL}/api/v1/containers/{CONTAINER_ID}/port-mappings",
|
||||
headers={"X-API-Key": API_KEY},
|
||||
json=payload,
|
||||
timeout=15,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
## Endpoint List
|
||||
|
||||
### Overview
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/dashboard` | Dashboard statistics |
|
||||
| GET | `/api/v1/host-info` | Host resources |
|
||||
| GET | `/api/v1/routing` | NAT/IPv4/IPv6 routing |
|
||||
| PUT | `/api/v1/routing` | Update public IPv4/IPv6 pools |
|
||||
| POST | `/api/v1/routing/ipv4-scan` | Scan a public IPv4 segment |
|
||||
| GET | `/api/v1/ipv6/status` | IPv6 status |
|
||||
| GET | `/api/v1/tasks` | Task queue |
|
||||
| DELETE | `/api/v1/tasks/{task_id}` | Delete a task |
|
||||
|
||||
### Containers
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/containers` | Container list |
|
||||
| POST | `/api/v1/containers/list` | Compatible POST form for container list |
|
||||
| POST | `/api/v1/containers` | Create container |
|
||||
| GET | `/api/v1/containers/{id|uuid|name}` | Container details |
|
||||
| POST | `/api/v1/containers/{id}/start` | Start |
|
||||
| POST | `/api/v1/containers/{id}/stop` | Stop |
|
||||
| POST | `/api/v1/containers/{id}/restart` | Restart |
|
||||
| POST | `/api/v1/containers/{id}/reinstall` | Reinstall |
|
||||
| DELETE | `/api/v1/containers/{id}/delete` | Delete |
|
||||
| GET | `/api/v1/containers/{id}/usage` | Resource usage |
|
||||
| GET | `/api/v1/containers/{id}/traffic` | Traffic statistics |
|
||||
| POST | `/api/v1/containers/{id}/traffic-reset` | Reset traffic |
|
||||
| PUT | `/api/v1/containers/{id}/traffic-limit` | Update traffic limits |
|
||||
| PUT | `/api/v1/containers/{id}/resource-limit` | Update resource limits |
|
||||
| PUT | `/api/v1/containers/{id}/expiry` | Update expiration time |
|
||||
| POST | `/api/v1/containers/{id}/reset-password` | Reset SSH password |
|
||||
| POST | `/api/v1/containers/{id}/ipv6` | Assign IPv6 |
|
||||
|
||||
### Ports and Snapshots
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/containers/{id}/random-port` | Random available port |
|
||||
| POST | `/api/v1/containers/{id}/port-mappings` | Add port mapping |
|
||||
| PUT | `/api/v1/containers/{id}/port-mappings/{index}` | Update port mapping |
|
||||
| DELETE | `/api/v1/containers/{id}/port-mappings/{index}` | Delete port mapping |
|
||||
| GET | `/api/v1/snapshots` | Snapshot overview |
|
||||
| GET | `/api/v1/containers/{id}/snapshots` | Container snapshots |
|
||||
| POST | `/api/v1/containers/{id}/snapshots` | Create snapshot |
|
||||
| DELETE | `/api/v1/containers/{id}/snapshots/{snapshot_id}` | Delete snapshot |
|
||||
| POST | `/api/v1/containers/{id}/snapshots/{snapshot_id}/restore` | Restore snapshot |
|
||||
| POST | `/api/v1/containers/{id}/snapshots/schedule` | Schedule snapshots |
|
||||
| PUT | `/api/v1/containers/{id}/snapshots/quota` | Snapshot quota |
|
||||
|
||||
### Platform Management
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/templates` | Template list |
|
||||
| GET | `/api/v1/images` | Image management list |
|
||||
| POST | `/api/v1/images/download` | Download image |
|
||||
| POST | `/api/v1/images/cancel` | Cancel image download |
|
||||
| DELETE | `/api/v1/images/delete` | Delete image cache |
|
||||
| PUT | `/api/v1/images/toggle` | Enable or disable image |
|
||||
| GET | `/api/v1/security/alerts` | Security alerts |
|
||||
| POST | `/api/v1/security/check` | Run security check |
|
||||
| GET | `/api/v1/security/logs?container={name}` | Security connection logs |
|
||||
| GET | `/api/v1/security/summary` | Security summary |
|
||||
| GET | `/api/v1/security/settings` | Security settings |
|
||||
| PUT | `/api/v1/security/settings` | Update security settings |
|
||||
| GET | `/api/v1/swap` | Swap information |
|
||||
| POST | `/api/v1/swap` | Adjust Swap |
|
||||
| POST | `/api/v1/batch-create` | Batch create containers |
|
||||
| POST | `/api/v1/batch-action` | Batch power action, delete, or reinstall |
|
||||
| POST | `/api/v1/ssh-ticket` | Create WebSSH ticket |
|
||||
| POST | `/api/v1/vnc-ticket` | Create WebVNC ticket |
|
||||
|
||||
### Accounts and Logs
|
||||
|
||||
| Method | Path | Description |
|
||||
| --- | --- | --- |
|
||||
| POST | `/api/v1/sub-user/create` | Create sub-user link |
|
||||
| GET | `/api/v1/sub-users` | Sub-user list |
|
||||
| POST | `/api/v1/sub-users/{id}/rotate-password` | Rotate sub-user password |
|
||||
| GET | `/api/v1/sub-users/{id}/audit-logs` | Sub-user audit logs |
|
||||
| GET | `/api/v1/sub-users/{id}/login-logs` | Sub-user login logs |
|
||||
| GET | `/api/v1/audit-logs` | Audit logs |
|
||||
| GET | `/api/v1/login-logs` | Login logs |
|
||||
| GET | `/api/v1/api-keys` | API key list |
|
||||
| POST | `/api/v1/api-keys` | Create API key |
|
||||
| PATCH | `/api/v1/api-keys/{id}` | Update API key |
|
||||
| DELETE | `/api/v1/api-keys/{id}` | Delete API key |
|
||||
|
||||
## Response Samples
|
||||
|
||||
The samples below are grouped by endpoint path. Resource numbers, task IDs, container IDs, timestamps, IP addresses, and keys will differ in real environments. Passwords, tickets, and API keys are masked.
|
||||
|
||||
### Overview
|
||||
|
||||
```json
|
||||
{
|
||||
"GET /api/v1/dashboard": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"running": 31,
|
||||
"stopped": 0,
|
||||
"total_containers": 31
|
||||
}
|
||||
},
|
||||
"GET /api/v1/host-info": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"cpu": { "cores": 8, "usage_pct": 1.16 },
|
||||
"ram": { "total_mb": 31825, "used_mb": 1275, "free_mb": 30550 },
|
||||
"disk": { "total_gb": 1750.49, "used_gb": 123.98, "free_gb": 1626.51 },
|
||||
"network": {
|
||||
"public_ipv4": "203.0.113.10",
|
||||
"public_ipv4_interface": "eth0",
|
||||
"public_ipv6": "2001:db8:100::2",
|
||||
"public_ipv6_interface": "eth0"
|
||||
},
|
||||
"load": { "load1": 0.01, "load5": 0.03, "load15": 0.01 }
|
||||
}
|
||||
},
|
||||
"GET /api/v1/routing": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"nat4": { "used": 62, "remaining": "45474", "total": "45536" },
|
||||
"ipv4": { "used": 1, "remaining": "3", "total": "4" },
|
||||
"ipv6": { "used": 31, "remaining": "large", "total": "large" },
|
||||
"public_ipv4_addresses": [
|
||||
{ "address": "203.0.113.10", "interface": "eth0", "prefix_len": 32, "gateway": "203.0.113.1" }
|
||||
],
|
||||
"ipv4_assignments": [
|
||||
{ "container_id": 5, "container_name": "example-vm", "address": "203.0.113.10", "interface": "eth0", "prefix_len": 32, "gateway": "203.0.113.1" }
|
||||
],
|
||||
"nat4_mappings": [
|
||||
{ "container_id": 5, "container_name": "example-vm", "status": "running", "ip": "10.0.0.10", "host_port": 22004, "container_port": 22, "protocol": "tcp" }
|
||||
],
|
||||
"ipv6_assignments": [
|
||||
{ "container_id": 5, "container_name": "example-vm", "address": "2001:db8:100::1005", "prefix_len": 64, "interface": "eth0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"PUT /api/v1/routing": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"ipv4": { "used": 1, "remaining": "3", "total": "4" },
|
||||
"public_ipv4_addresses": [
|
||||
{ "address": "203.0.113.10", "interface": "eth0", "prefix_len": 32, "gateway": "203.0.113.1" }
|
||||
],
|
||||
"ipv6_prefixes": [
|
||||
{ "interface": "eth0", "address": "2001:db8:100::2", "prefix": "2001:db8:100::/64", "prefix_len": 64, "gateway": "2001:db8:100::1" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"POST /api/v1/routing/ipv4-scan": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "address": "203.0.113.10", "interface": "eth0", "prefix_len": 32, "gateway": "203.0.113.1", "status": "available", "usable": true, "reason": "" }
|
||||
]
|
||||
},
|
||||
"GET /api/v1/ipv6/status": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"available": true,
|
||||
"reachable": true,
|
||||
"reason": "usable public IPv6 prefix detected",
|
||||
"prefixes": [
|
||||
{ "interface": "eth0", "address": "2001:db8:100::2", "prefix": "2001:db8:100::/64", "prefix_len": 64, "gateway": "2001:db8:100::1" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"GET /api/v1/tasks": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"DELETE /api/v1/tasks/{task_id}": {
|
||||
"success": true,
|
||||
"message": "Task deleted"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Containers
|
||||
|
||||
```json
|
||||
{
|
||||
"GET /api/v1/containers": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 5,
|
||||
"uuid": "00000000-0000-4000-8000-000000000005",
|
||||
"name": "example-vm",
|
||||
"virtualization": "lxc",
|
||||
"template": "debian-bullseye",
|
||||
"vcpu": 1,
|
||||
"ram_mb": 512,
|
||||
"disk_gb": 10,
|
||||
"status": "running",
|
||||
"ip": "10.0.0.10",
|
||||
"ipv6": "2001:db8:100::1005",
|
||||
"ssh_port": 22004,
|
||||
"ssh_password": "***",
|
||||
"port_mappings": [
|
||||
{ "container_port": 22, "host_port": 22004, "protocol": "tcp", "description": "SSH" },
|
||||
{ "container_port": 20000, "host_port": 20000, "protocol": "tcp", "description": "Port-20000" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"POST /api/v1/containers/list": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": 5, "uuid": "00000000-0000-4000-8000-000000000005", "name": "example-vm", "status": "running", "ip": "10.0.0.10" }
|
||||
]
|
||||
},
|
||||
"POST /api/v1/containers": {
|
||||
"success": true,
|
||||
"message": "Container created successfully"
|
||||
},
|
||||
"GET /api/v1/containers/{id|uuid|name}": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 5,
|
||||
"uuid": "00000000-0000-4000-8000-000000000005",
|
||||
"name": "example-vm",
|
||||
"status": "running",
|
||||
"ip": "10.0.0.10",
|
||||
"ipv6": "2001:db8:100::1005",
|
||||
"ssh_port": 22004,
|
||||
"ssh_password": "***",
|
||||
"policy_blocked": false
|
||||
}
|
||||
},
|
||||
"POST /api/v1/containers/{id}/start": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "start" }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/stop": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "stop" }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/restart": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "restart" }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/reinstall": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "reinstall" }
|
||||
},
|
||||
"DELETE /api/v1/containers/{id}/delete": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "delete" }
|
||||
},
|
||||
"GET /api/v1/containers/{id}/usage": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"cpu_usage_pct": 0,
|
||||
"cpu_usage_usec": 3908852,
|
||||
"memory_usage_bytes": 29331456,
|
||||
"disk_usage_bytes": 515100672,
|
||||
"network_rx_bytes": 131232,
|
||||
"network_tx_bytes": 16828,
|
||||
"load1": 0.1,
|
||||
"load5": 0.06,
|
||||
"load15": 0.01
|
||||
}
|
||||
},
|
||||
"GET /api/v1/containers/{id}/traffic": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"mode": "total",
|
||||
"limit_gb": 0,
|
||||
"in_limit_gb": 0,
|
||||
"out_limit_gb": 0,
|
||||
"total_used_bytes": 142082,
|
||||
"rx_used_bytes": 127212,
|
||||
"tx_used_bytes": 14870,
|
||||
"used_pct": 0,
|
||||
"reset_date": "2026-06"
|
||||
}
|
||||
},
|
||||
"POST /api/v1/containers/{id}/traffic-reset": {
|
||||
"success": true,
|
||||
"message": "Traffic reset"
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/traffic-limit": {
|
||||
"success": true,
|
||||
"message": "Traffic limit updated"
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/resource-limit": {
|
||||
"success": true,
|
||||
"message": "Resource limits updated"
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/expiry": {
|
||||
"success": true,
|
||||
"message": "Expiry updated"
|
||||
},
|
||||
"POST /api/v1/containers/{id}/reset-password": {
|
||||
"success": true,
|
||||
"message": "SSH password reset successfully",
|
||||
"data": { "password": "***" }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/ipv6": {
|
||||
"success": true,
|
||||
"message": "IPv6 assigned",
|
||||
"data": { "id": 5, "name": "example-vm", "ipv6": "2001:db8:100::1005" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Ports and Snapshots
|
||||
|
||||
```json
|
||||
{
|
||||
"GET /api/v1/containers/{id}/random-port": {
|
||||
"success": true,
|
||||
"data": { "port": 61320 }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/port-mappings": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "container_port": 22, "host_port": 22004, "protocol": "tcp", "description": "SSH" },
|
||||
{ "container_port": 8080, "host_port": 61320, "protocol": "tcp", "description": "HTTP" }
|
||||
]
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/port-mappings/{index}": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "container_port": 8081, "host_port": 61320, "protocol": "tcp", "description": "HTTP" }
|
||||
]
|
||||
},
|
||||
"DELETE /api/v1/containers/{id}/port-mappings/{index}": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"GET /api/v1/snapshots": {
|
||||
"success": true,
|
||||
"data": null
|
||||
},
|
||||
"GET /api/v1/containers/{id}/snapshots": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"quota": 1,
|
||||
"schedule": { "enabled": false, "interval_hours": 0, "last_run": "", "next_run": "", "time": "", "created_by": "" },
|
||||
"snapshots": []
|
||||
}
|
||||
},
|
||||
"POST /api/v1/containers/{id}/snapshots": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "snap-20260608-001",
|
||||
"container_id": 5,
|
||||
"container_name": "example-vm",
|
||||
"created_at": "2026-06-08 16:00:00",
|
||||
"created_by": "api:Automation",
|
||||
"scheduled": false,
|
||||
"size_bytes": 10485760
|
||||
}
|
||||
},
|
||||
"DELETE /api/v1/containers/{id}/snapshots/{snapshot_id}": {
|
||||
"success": true,
|
||||
"message": "Snapshot deleted"
|
||||
},
|
||||
"POST /api/v1/containers/{id}/snapshots/{snapshot_id}/restore": {
|
||||
"success": true,
|
||||
"message": "Snapshot restored"
|
||||
},
|
||||
"POST /api/v1/containers/{id}/snapshots/schedule": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"container": { "id": 5, "name": "example-vm", "snapshot_schedule_enabled": true, "snapshot_schedule_interval_hours": 24, "snapshot_schedule_time": "03:00" }
|
||||
}
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/snapshots/quota": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"quota": 2,
|
||||
"container": { "id": 5, "name": "example-vm", "snapshot_limit": 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Platform Management
|
||||
|
||||
```json
|
||||
{
|
||||
"GET /api/v1/templates": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": "ubuntu-noble", "name": "Ubuntu 24.04", "distro": "ubuntu", "release": "noble", "arch": "amd64", "description": "Ubuntu 24.04 LTS" },
|
||||
{ "id": "debian-bookworm", "name": "Debian 12", "distro": "debian", "release": "bookworm", "arch": "amd64", "description": "Debian 12 (Bookworm)" }
|
||||
]
|
||||
},
|
||||
"GET /api/v1/images": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": "ubuntu-noble", "name": "Ubuntu 24.04", "type": "lxc", "downloaded": true, "enabled": true, "downloading": false, "progress": 0, "size_bytes": 135005452 }
|
||||
]
|
||||
},
|
||||
"POST /api/v1/images/download": {
|
||||
"success": true,
|
||||
"message": "Already downloaded"
|
||||
},
|
||||
"POST /api/v1/images/cancel": {
|
||||
"success": true,
|
||||
"message": "Cancel requested"
|
||||
},
|
||||
"DELETE /api/v1/images/delete": {
|
||||
"success": true,
|
||||
"message": "Deleted"
|
||||
},
|
||||
"PUT /api/v1/images/toggle": {
|
||||
"success": true,
|
||||
"message": "OK"
|
||||
},
|
||||
"GET /api/v1/security/alerts": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"POST /api/v1/security/check": {
|
||||
"success": true,
|
||||
"message": "Security check completed"
|
||||
},
|
||||
"GET /api/v1/security/logs?container={name}": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"GET /api/v1/security/summary": {
|
||||
"success": true,
|
||||
"data": { "critical": 0, "high": 0, "medium": 0, "low": 0, "total_alerts": 0 }
|
||||
},
|
||||
"GET /api/v1/security/settings": {
|
||||
"success": true,
|
||||
"data": { "auto_shutdown": false }
|
||||
},
|
||||
"PUT /api/v1/security/settings": {
|
||||
"success": true,
|
||||
"data": { "auto_shutdown": false }
|
||||
},
|
||||
"GET /api/v1/swap": {
|
||||
"success": true,
|
||||
"data": { "total_mb": 16383, "used_mb": 0, "free_mb": 16383, "enabled": true, "swap_file": "/swapfile" }
|
||||
},
|
||||
"POST /api/v1/swap": {
|
||||
"success": true,
|
||||
"message": "SWAP 已调整为 16384 MB",
|
||||
"data": { "total_mb": 16383, "used_mb": 0, "free_mb": 16383, "enabled": true, "swap_file": "/swapfile" }
|
||||
},
|
||||
"POST /api/v1/batch-create": {
|
||||
"success": true,
|
||||
"data": ["task-12"]
|
||||
},
|
||||
"POST /api/v1/batch-action": {
|
||||
"success": true,
|
||||
"data": ["task-13"]
|
||||
},
|
||||
"POST /api/v1/ssh-ticket": {
|
||||
"success": true,
|
||||
"data": { "ticket": "***60 seconds valid***" }
|
||||
},
|
||||
"POST /api/v1/vnc-ticket": {
|
||||
"success": true,
|
||||
"data": { "ticket": "***60 seconds valid***" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Accounts and Logs
|
||||
|
||||
```json
|
||||
{
|
||||
"POST /api/v1/sub-user/create": {
|
||||
"success": true,
|
||||
"message": "Sub-user created",
|
||||
"data": {
|
||||
"id": "sub-xxxxxxxx",
|
||||
"username": "user-xxxxxxxx",
|
||||
"password": "***",
|
||||
"container_names": ["example-vm"],
|
||||
"access_code": "********",
|
||||
"created_at": "2026-06-08 16:00:00"
|
||||
}
|
||||
},
|
||||
"GET /api/v1/sub-users": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"POST /api/v1/sub-users/{id}/rotate-password": {
|
||||
"success": true,
|
||||
"data": { "username": "user-xxxxxxxx", "password": "***", "access_code": "********" }
|
||||
},
|
||||
"GET /api/v1/sub-users/{id}/audit-logs": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"GET /api/v1/sub-users/{id}/login-logs": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"GET /api/v1/audit-logs": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "time": "2026-06-08 15:44:40", "action": "apikey.create", "target": "Test", "detail": "scopes=*", "user": "admin", "success": true }
|
||||
]
|
||||
},
|
||||
"GET /api/v1/login-logs": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "time": "2026-06-08 08:24:00 UTC", "username": "admin", "ip": "198.51.100.23", "user_agent": "Mozilla/5.0 ...", "success": true }
|
||||
]
|
||||
},
|
||||
"GET /api/v1/api-keys": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": "c271023f", "name": "Test", "prefix": "clicd_sk_dd9d...", "ip_whitelist": "", "created_at": "2026-06-08 15:44:40", "last_used": "2026-06-08 15:46:10", "scopes": ["*"], "last_used_ip": "198.51.100.23" }
|
||||
]
|
||||
},
|
||||
"POST /api/v1/api-keys": {
|
||||
"success": true,
|
||||
"message": "API key created. Save this key now - it won't be shown again.",
|
||||
"data": { "id": "a1b2c3d4", "name": "Automation", "key": "clicd_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "prefix": "clicd_sk_xxxx...", "scopes": ["dashboard:read", "container:read"] }
|
||||
},
|
||||
"PATCH /api/v1/api-keys/{id}": {
|
||||
"success": true,
|
||||
"data": { "id": "a1b2c3d4", "name": "Automation", "prefix": "clicd_sk_xxxx...", "scopes": ["dashboard:read", "container:read"], "disabled": false }
|
||||
},
|
||||
"DELETE /api/v1/api-keys/{id}": {
|
||||
"success": true,
|
||||
"message": "API key deleted"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,83 @@
|
||||
# Container Management
|
||||
|
||||
Container Management is the core CLICD module. It covers creation, lifecycle operations, resource limits, network mappings, traffic statistics, password resets, and console access.
|
||||
|
||||
## Container List
|
||||
|
||||
The list page scans container status. Administrators can view all containers. Sub-users only see containers within their authorization scope.
|
||||
|
||||
Common fields include:
|
||||
|
||||
- ID, UUID, and name.
|
||||
- Virtualization type.
|
||||
- Runtime status.
|
||||
- IP and IPv6.
|
||||
- CPU, memory, and disk limits.
|
||||
- Traffic usage and traffic limits.
|
||||
- Expiration time.
|
||||
|
||||
## Create Containers
|
||||
|
||||
Creation requires a template and resource quotas. Batch creation is available from the panel or API and is useful for issuing multiple containers at once.
|
||||
|
||||
```http
|
||||
POST /api/v1/containers
|
||||
POST /api/v1/batch-create
|
||||
```
|
||||
|
||||
Linux containers and Linux KVM virtual machines support SSH login configuration during creation:
|
||||
|
||||
- `auto_password`: generate a root SSH password automatically.
|
||||
- `password`: use a custom `ssh_password`.
|
||||
- `key`: write a one-line `ssh_public_key`; a password is still kept for WebSSH.
|
||||
|
||||
Network allocation can combine NAT, public IPv4, and IPv6 as needed. API fields such as `assign_nat`, `assign_ipv4`, `public_ipv4s`, `assign_ipv6`, and `ipv6_addresses` are optional. If they are omitted, default behavior is preserved.
|
||||
|
||||
## Lifecycle Operations
|
||||
|
||||
```http
|
||||
POST /api/v1/containers/{id}/start
|
||||
POST /api/v1/containers/{id}/stop
|
||||
POST /api/v1/containers/{id}/restart
|
||||
POST /api/v1/containers/{id}/reinstall
|
||||
DELETE /api/v1/containers/{id}/delete
|
||||
```
|
||||
|
||||
Start, stop, reinstall, and delete actions enter the task queue. Call `GET /api/v1/tasks` afterwards to check execution status.
|
||||
|
||||
When reinstalling a Linux system, you may pass `ssh_auth_mode`, `ssh_password`, and `ssh_public_key`. `ssh_auth_mode=keep` keeps the current SSH password. If these fields are omitted, the old behavior is preserved.
|
||||
|
||||
## Resources and Traffic
|
||||
|
||||
The container details page supports resource usage, traffic limit changes, resource limit changes, and expiration changes.
|
||||
|
||||
```http
|
||||
GET /api/v1/containers/{id}/usage
|
||||
GET /api/v1/containers/{id}/traffic
|
||||
POST /api/v1/containers/{id}/traffic-reset
|
||||
PUT /api/v1/containers/{id}/traffic-limit
|
||||
PUT /api/v1/containers/{id}/resource-limit
|
||||
PUT /api/v1/containers/{id}/expiry
|
||||
```
|
||||
|
||||
## NAT Port Management
|
||||
|
||||
The NAT port management section supports adding, editing, and deleting mappings. Add and edit actions use a dialog so name, protocol, external port, and internal port can be filled in together.
|
||||
|
||||
```http
|
||||
GET /api/v1/containers/{id}/random-port
|
||||
POST /api/v1/containers/{id}/port-mappings
|
||||
PUT /api/v1/containers/{id}/port-mappings/{index}
|
||||
DELETE /api/v1/containers/{id}/port-mappings/{index}
|
||||
```
|
||||
|
||||
In sub-user mode, administrators can limit sub-users to changing only the internal port, preventing changes to the host-facing port and protocol.
|
||||
|
||||
## Remote Console
|
||||
|
||||
```http
|
||||
POST /api/v1/ssh-ticket
|
||||
POST /api/v1/vnc-ticket
|
||||
```
|
||||
|
||||
Tickets are short-lived. Use them immediately for WebSSH or WebVNC and do not persist them.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Dashboard
|
||||
|
||||
The dashboard shows the overall state of the host and virtualization resources.
|
||||
|
||||
## Metrics
|
||||
|
||||
- Total containers, running containers, and stopped containers.
|
||||
- CPU, memory, disk, and Swap overview.
|
||||
- Entry points for host network and routing status.
|
||||
- Task queue status.
|
||||
- Security alert summary.
|
||||
|
||||
## Related APIs
|
||||
|
||||
```http
|
||||
GET /api/v1/dashboard
|
||||
GET /api/v1/host-info
|
||||
GET /api/v1/routing
|
||||
GET /api/v1/ipv6/status
|
||||
GET /api/v1/tasks
|
||||
```
|
||||
|
||||
API requests must include an API key:
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: YOUR_API_KEY" https://panel.example.com/api/v1/dashboard
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
# Host Report
|
||||
|
||||
The host report summarizes the host runtime environment, resource status, and virtualization dependencies. It is useful for post-installation checks, troubleshooting, or sharing environment information with maintainers.
|
||||
|
||||
## Contents
|
||||
|
||||
- System version and kernel information.
|
||||
- CPU, memory, disk, and Swap.
|
||||
- Network status.
|
||||
- LXC/KVM dependency status.
|
||||
- CLICD service status.
|
||||
|
||||
## Related APIs
|
||||
|
||||
```http
|
||||
GET /api/v1/host-report
|
||||
GET /api/v1/host-info
|
||||
GET /api/v1/swap
|
||||
```
|
||||
|
||||
Before sending a report externally, check whether it contains public IPs, private networks, usernames, keys, tickets, or business domains.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Image Management
|
||||
|
||||
Image Management maintains templates used to create containers or virtual machines.
|
||||
|
||||
## Supported Template Types
|
||||
|
||||
The project includes common Linux distribution templates such as Debian, Ubuntu, Alpine, CentOS, Fedora, Arch Linux, and Rocky Linux. KVM templates use the corresponding distribution cloud image resources.
|
||||
|
||||
## Management Actions
|
||||
|
||||
```http
|
||||
GET /api/v1/templates
|
||||
GET /api/v1/images
|
||||
POST /api/v1/images/download
|
||||
POST /api/v1/images/cancel
|
||||
DELETE /api/v1/images/delete
|
||||
PUT /api/v1/images/toggle
|
||||
```
|
||||
|
||||
- `templates` returns available template definitions.
|
||||
- `images` returns local image status.
|
||||
- `download` downloads a specific template.
|
||||
- `cancel` cancels a download task.
|
||||
- `delete` removes the local image cache.
|
||||
- `toggle` controls whether a template can be used during creation.
|
||||
|
||||
## Windows Images
|
||||
|
||||
This project does not distribute Windows system images and does not provide features to bypass or avoid Windows activation. Windows download links should point to official Microsoft resources, and users must obtain valid licenses themselves.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Networking and Routing
|
||||
|
||||
CLICD provides NAT4 port mapping, random available ports, public IPv4 assignment, IPv6 status checks, and IPv6 assignment. During container creation, you can use NAT only, public IPv4 only, IPv6 only, or a mixed network setup.
|
||||
|
||||
## NAT4
|
||||
|
||||
NAT4 forwards host ports to container internal ports. Common uses include:
|
||||
|
||||
- Forwarding SSH.
|
||||
- Exposing web services.
|
||||
- Assigning fixed external ports to sub-users.
|
||||
|
||||
Port mappings include:
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| Name | A purpose label such as `ssh` or `web`. |
|
||||
| Protocol | `tcp` or `udp`. |
|
||||
| External port | The host port exposed to the outside. |
|
||||
| Internal port | The service port inside the container. |
|
||||
|
||||
## IPv6
|
||||
|
||||
IPv6 assignment requires the host to have a routable IPv6 prefix, plus correct routing, neighbor discovery, or proxy configuration.
|
||||
|
||||
```http
|
||||
GET /api/v1/ipv6/status
|
||||
POST /api/v1/containers/{id}/ipv6
|
||||
```
|
||||
|
||||
If the host has no public IPv6 or the upstream network is not routing the prefix correctly, assigned addresses will not be reachable from the public internet.
|
||||
|
||||
## Public IPv4
|
||||
|
||||
Public IPv4 assignment selects from public IPv4 addresses detected on the host, or from `public_ipv4s` specified through the API. Creation fields include:
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `assign_nat` | Whether to enable NAT port mappings. |
|
||||
| `assign_ipv4` | Whether to assign public IPv4. |
|
||||
| `ipv4_count` | Number of public IPv4 addresses to allocate automatically. |
|
||||
| `public_ipv4s` | Explicit public IPv4 address list. |
|
||||
| `assign_ipv6` | Whether to assign IPv6. |
|
||||
| `ipv6_count` | Number of IPv6 addresses to allocate automatically. |
|
||||
| `ipv6_addresses` | Explicit IPv6 address list. |
|
||||
|
||||
Public address pool APIs:
|
||||
|
||||
```http
|
||||
GET /api/v1/routing
|
||||
PUT /api/v1/routing
|
||||
POST /api/v1/routing/ipv4-scan
|
||||
```
|
||||
|
||||
## Routing Status
|
||||
|
||||
```http
|
||||
GET /api/v1/routing
|
||||
```
|
||||
|
||||
This endpoint shows runtime status for NAT, IPv4, IPv6, and port capacity.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Security Alerts
|
||||
|
||||
CLICD includes lightweight security alerts based on connection behavior. It does not keep full normal connection logs; it focuses on abnormal behavior and high-risk patterns.
|
||||
|
||||
## Covered Scenarios
|
||||
|
||||
- Port scanning.
|
||||
- Lateral scanning.
|
||||
- Brute-force tendencies.
|
||||
- SMTP abuse.
|
||||
- UDP reflection risk.
|
||||
- Suspicious ports related to mining, proxies, VPNs, Tor, and similar services.
|
||||
|
||||
## APIs
|
||||
|
||||
```http
|
||||
GET /api/v1/security/alerts
|
||||
POST /api/v1/security/check
|
||||
GET /api/v1/security/logs?container={name}
|
||||
GET /api/v1/security/summary
|
||||
GET /api/v1/security/settings
|
||||
PUT /api/v1/security/settings
|
||||
```
|
||||
|
||||
## Automatic Shutdown
|
||||
|
||||
Security settings can enable automatic shutdown after alerts. Before enabling it, observe for a while and make sure the rules do not affect normal services.
|
||||
|
||||
## Logging Advice
|
||||
|
||||
Security alerts are risk signals. They should not replace a professional firewall, intrusion detection, or centralized logging system. For public services, still combine them with security groups, firewall rules, Fail2ban, and similar tools.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Snapshot Management
|
||||
|
||||
Snapshots save the current state of a container so it can be rolled back before upgrades, configuration changes, or delivery.
|
||||
|
||||
## Global Overview
|
||||
|
||||
```http
|
||||
GET /api/v1/snapshots
|
||||
```
|
||||
|
||||
Use this endpoint to view snapshot summaries for all containers.
|
||||
|
||||
## Container Snapshots
|
||||
|
||||
```http
|
||||
GET /api/v1/containers/{id}/snapshots
|
||||
POST /api/v1/containers/{id}/snapshots
|
||||
DELETE /api/v1/containers/{id}/snapshots/{snapshot_id}
|
||||
POST /api/v1/containers/{id}/snapshots/{snapshot_id}/restore
|
||||
```
|
||||
|
||||
Restoring a snapshot changes container state. In production, confirm that the current workload can be interrupted first.
|
||||
|
||||
## Scheduled Snapshots and Quotas
|
||||
|
||||
```http
|
||||
POST /api/v1/containers/{id}/snapshots/schedule
|
||||
PUT /api/v1/containers/{id}/snapshots/quota
|
||||
```
|
||||
|
||||
Scheduled snapshots are useful for long-running containers. Quotas prevent snapshots from growing without limit and filling the host disk.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Sub-users
|
||||
|
||||
Sub-users let administrators grant specific container access to other users. They are useful for temporary delivery, shared-host allocation, teaching labs, or multi-user host scenarios.
|
||||
|
||||
## Create an Access Link
|
||||
|
||||
After selecting a container, the administrator can create a sub-user link:
|
||||
|
||||
```http
|
||||
POST /api/v1/sub-user/create
|
||||
```
|
||||
|
||||
The response may include a username, initial password, access code, or access link. When sharing externally, mask sensitive values and send the real values only to the intended user.
|
||||
|
||||
## Manage Sub-users
|
||||
|
||||
```http
|
||||
GET /api/v1/sub-users
|
||||
POST /api/v1/sub-users/{id}/rotate-password
|
||||
GET /api/v1/sub-users/{id}/audit-logs
|
||||
GET /api/v1/sub-users/{id}/login-logs
|
||||
```
|
||||
|
||||
Rotating the password invalidates old credentials. Audit logs and login logs help investigate mistakes or abnormal access.
|
||||
|
||||
## Permission Scope
|
||||
|
||||
Sub-users can only manage authorized containers. Global configuration, image management, security policy, API keys, and other administrator features are not exposed to sub-users.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Configuration
|
||||
|
||||
After installation, CLICD runs as a systemd service. Runtime configuration and the database are stored locally on the host. The exact path may vary with installer options, but the default installation should mainly be checked under `/root/.clicd/`.
|
||||
|
||||
## Common Settings
|
||||
|
||||
| Setting | Description |
|
||||
| --- | --- |
|
||||
| Web port | Defaults to `8999`, listening on `0.0.0.0:8999`. |
|
||||
| Administrator account | Used to log in to the web panel and manage API keys. |
|
||||
| Database | SQLite storage for container metadata, sub-users, audit logs, API keys, and more. |
|
||||
| NAT port range | Used for random ports and port mapping allocation. |
|
||||
| IPv6 prefixes | Used when the host has routable IPv6 prefixes. |
|
||||
| Security alerts | Policies such as automatic shutdown can be configured. |
|
||||
|
||||
## Service Commands
|
||||
|
||||
```bash
|
||||
systemctl status clicd
|
||||
systemctl restart clicd
|
||||
journalctl -u clicd -n 100 --no-pager
|
||||
```
|
||||
|
||||
## Security Recommendations
|
||||
|
||||
- Do not expose the web panel directly to untrusted networks.
|
||||
- Use a strong administrator password and rotate it regularly.
|
||||
- Split API keys by purpose and avoid long-lived full-access keys.
|
||||
- WebSSH and WebVNC tickets are short-lived credentials and should not be written to logs or shared publicly.
|
||||
- Do not paste real IPs, passwords, API keys, or tickets into public docs, screenshots, or support tickets.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Installation
|
||||
|
||||
CLICD provides a one-line installer. By default, it installs the latest version from GitHub Releases. You can also pin a specific version with an environment variable.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux x86_64 host.
|
||||
- Root privileges.
|
||||
- systemd.
|
||||
- Network access to GitHub Release downloads.
|
||||
- LXC runtime support if you want to use LXC.
|
||||
- KVM virtualization enabled with libvirt/QEMU installed if you want to use KVM.
|
||||
|
||||
## Install the Latest Version
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo sh
|
||||
```
|
||||
|
||||
The script defaults to `CLICD_VERSION=latest`, which downloads `clicd-linux-amd64.tar.gz` from `releases/latest`.
|
||||
|
||||
## Install a Specific Version
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo CLICD_VERSION=v1.1.6 sh
|
||||
```
|
||||
|
||||
Replace `v1.1.6` with the release tag you want to install.
|
||||
|
||||
## Open the Panel
|
||||
|
||||
After installation, open:
|
||||
|
||||
```text
|
||||
http://YOUR_SERVER_IP:8999
|
||||
```
|
||||
|
||||
Use the administrator credentials printed by the installer for the first login. In production, restrict access at the firewall or reverse proxy layer and change the default username and password as soon as possible.
|
||||
|
||||
## Uninstall
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo sh -s -- uninstall
|
||||
```
|
||||
|
||||
Before uninstalling, decide whether you need to keep containers, image cache, database files, or configuration files.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Introduction
|
||||
|
||||
CLICD is a lightweight virtualization management panel for LXC and KVM. It brings common host operations into a web console and CLI, making it suitable for small VPS nodes, dedicated servers, and scenarios where container access needs to be distributed in batches.
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
- Manage LXC containers and KVM virtual machines.
|
||||
- Create, start, stop, restart, reinstall, and delete containers.
|
||||
- Configure CPU, memory, disk, traffic limits, and expiration time.
|
||||
- Manage NAT4 port mappings, public IPv4 assignment, and public IPv6 assignment when the host network supports it.
|
||||
- Open WebSSH or WebVNC from the browser.
|
||||
- Manage image downloads, enablement, and local cache.
|
||||
- Create, restore, and delete snapshots, plus scheduled snapshots and quotas.
|
||||
- Generate security alerts based on connection behavior and keep audit logs.
|
||||
- Create sub-user access links for specific containers.
|
||||
- Integrate automation through API keys and `/api/v1`.
|
||||
|
||||
## Use Cases
|
||||
|
||||
- Quickly allocate multiple Linux containers on one host.
|
||||
- Give users temporary access to a container console, SSH, VNC, or NAT port management.
|
||||
- Automate container creation, resource changes, password resets, or resource cleanup through the API.
|
||||
- Use a panel that is clearer than pure CLI workflows without becoming a heavy platform.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Backend: Go, `net/http`, SQLite, systemd, LXC, KVM/libvirt, cgroup v2, iptables, conntrack.
|
||||
- Frontend: React, TypeScript, Vite, Tailwind CSS, lucide-react, xterm.js, noVNC.
|
||||
- Release: GitHub Actions builds Linux AMD64 release artifacts. The installer fetches the latest release by default.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Quick Start
|
||||
|
||||
This is a common path from a fresh installation to your first container.
|
||||
|
||||
## 1. Log In
|
||||
|
||||
Open `http://YOUR_SERVER_IP:8999` and sign in with the administrator account.
|
||||
|
||||
After entering the panel, check:
|
||||
|
||||
- Whether the dashboard shows host resources.
|
||||
- Whether Image Management can list templates.
|
||||
- Whether NAT and IPv6 status in Routing match your host network.
|
||||
|
||||
## 2. Download an Image
|
||||
|
||||
Open Image Management, choose a template, and download it. On small hosts, lightweight images such as Alpine or Debian are a good first choice.
|
||||
|
||||
Image downloads run asynchronously. You can watch progress in the task queue.
|
||||
|
||||
## 3. Create a Container
|
||||
|
||||
Open Container Management and click Create:
|
||||
|
||||
- Select virtualization type and template.
|
||||
- Set CPU, memory, and disk.
|
||||
- Set traffic limits and expiration time.
|
||||
- If external access is required, add NAT port mappings or assign IPv6 from the container details page after creation.
|
||||
|
||||
## 4. Open a Terminal
|
||||
|
||||
After the container is created, open WebSSH from the details page. KVM virtual machines can use WebVNC for console access.
|
||||
|
||||
## 5. Share with a Sub-user
|
||||
|
||||
If another user needs to manage a container, create an access link in Sub-user Management. The sub-user only sees authorized containers and is limited by the operation scope configured by the administrator.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Upgrade
|
||||
|
||||
The CLICD installer and CLI are built around GitHub Release artifacts. Before upgrading, check the current version and back up configuration and database files.
|
||||
|
||||
## Check the Version
|
||||
|
||||
The current version is shown at the bottom of the web panel sidebar. You can also run:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8999/api/version
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"version": "1.1.6"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Upgrade with the Installer
|
||||
|
||||
The installer uses the latest release by default:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo sh
|
||||
```
|
||||
|
||||
Install a specific version:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo CLICD_VERSION=v1.1.6 sh
|
||||
```
|
||||
|
||||
## Pre-upgrade Checklist
|
||||
|
||||
- Back up `/root/.clicd/` or the actual configuration directory.
|
||||
- Make sure no critical tasks are currently running.
|
||||
- If an image download or snapshot restore is running, wait for it to finish first.
|
||||
- After upgrading, check `systemctl status clicd` and the version shown in the web panel.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: CLICD
|
||||
text: Lightweight LXC/KVM Virtualization Panel
|
||||
tagline: Web console, CLI, container orchestration, NAT/IPv4/IPv6 routing, snapshots, security alerts, sub-users, and API automation.
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Install
|
||||
link: /en/guide/installation
|
||||
- theme: alt
|
||||
text: API Reference
|
||||
link: /en/features/api
|
||||
|
||||
features:
|
||||
- title: Built for Small Hosts
|
||||
details: Manage LXC containers and KVM virtual machines on a single VPS or dedicated server.
|
||||
- title: Web and CLI Together
|
||||
details: Use the web panel for daily operations, or drop into the clicd CLI for maintenance tasks.
|
||||
- title: Automation Friendly
|
||||
details: /api/v1 exposes containers, images, snapshots, security, logs, sub-users, and API key management.
|
||||
---
|
||||
@@ -0,0 +1,46 @@
|
||||
# Deployment
|
||||
|
||||
CLICD can run directly on the host or behind a reverse proxy. In production, set up access control before exposing it to administrators.
|
||||
|
||||
## Service Exposure
|
||||
|
||||
The default web port is `8999`:
|
||||
|
||||
```text
|
||||
http://YOUR_SERVER_IP:8999
|
||||
```
|
||||
|
||||
Recommendations:
|
||||
|
||||
- Allow only fixed administrator IPs.
|
||||
- Use a reverse proxy with HTTPS.
|
||||
- Do not expose the real login URL in public docs or screenshots.
|
||||
|
||||
## systemd
|
||||
|
||||
Common commands:
|
||||
|
||||
```bash
|
||||
systemctl status clicd
|
||||
systemctl restart clicd
|
||||
systemctl enable clicd
|
||||
journalctl -u clicd -f
|
||||
```
|
||||
|
||||
## Firewall
|
||||
|
||||
At minimum, confirm:
|
||||
|
||||
- The panel port is open only to trusted sources.
|
||||
- NAT mapped ports are opened only as needed.
|
||||
- The SSH management port does not conflict with container mappings.
|
||||
- IPv6 firewall rules are planned together with IPv4 rules.
|
||||
|
||||
## Backups
|
||||
|
||||
Back up regularly:
|
||||
|
||||
- CLICD configuration directory.
|
||||
- SQLite database.
|
||||
- Container configuration.
|
||||
- Snapshots or external data backups for important containers.
|
||||
@@ -0,0 +1,29 @@
|
||||
# FAQ
|
||||
|
||||
## Which version does the installer install by default?
|
||||
|
||||
It installs the latest version from GitHub Releases. The script default is `CLICD_VERSION=latest`, which downloads the Linux AMD64 artifact from `releases/latest`.
|
||||
|
||||
## Can I pin a specific version?
|
||||
|
||||
Yes:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/MengMengCode/CLICD/main/install.sh | sudo CLICD_VERSION=v1.1.6 sh
|
||||
```
|
||||
|
||||
## Can sub-users see every container?
|
||||
|
||||
No. Sub-users only see containers authorized by the administrator.
|
||||
|
||||
## Is an API key the same as the login password?
|
||||
|
||||
No. API keys are created on the API Integration page for programmatic access. The login password is used for the web panel.
|
||||
|
||||
## What happens after a container reaches its traffic limit?
|
||||
|
||||
The container is automatically shut down to avoid further overage. The administrator can adjust the limit or reset traffic usage.
|
||||
|
||||
## Why is IPv6 unreachable after assignment?
|
||||
|
||||
IPv6 reachability depends on the host and upstream network. Confirm that the host has a routable IPv6 prefix and that routing, firewall, neighbor discovery, or proxy configuration is correct.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Service Not Reachable
|
||||
|
||||
Check service status:
|
||||
|
||||
```bash
|
||||
systemctl status clicd
|
||||
journalctl -u clicd -n 100 --no-pager
|
||||
```
|
||||
|
||||
Check port listening:
|
||||
|
||||
```bash
|
||||
ss -lntp | grep 8999
|
||||
```
|
||||
|
||||
If a reverse proxy is used, check proxy logs and upstream address settings as well.
|
||||
|
||||
## Image Download Failed
|
||||
|
||||
- Make sure the host can access image sources and GitHub Releases.
|
||||
- Check disk space.
|
||||
- Review the failure reason in the task queue.
|
||||
- If a download is stuck, cancel it and start again.
|
||||
|
||||
## Container Cannot Access the Network
|
||||
|
||||
- Check host NAT and forwarding rules.
|
||||
- Confirm that the container IP was assigned successfully.
|
||||
- Check whether the firewall is blocking forwarded traffic.
|
||||
- For IPv6, confirm that the upstream network routes the prefix to the host.
|
||||
|
||||
## WebSSH or WebVNC Connection Failed
|
||||
|
||||
- Confirm that the container or virtual machine is running.
|
||||
- WebSSH requires SSH service inside the container.
|
||||
- WebVNC requires the KVM console to be reachable.
|
||||
- Tickets expire quickly. Create a new ticket after expiration.
|
||||
|
||||
## API Returns Unauthorized
|
||||
|
||||
- Confirm that the API key is not disabled.
|
||||
- Use `X-API-Key` or `Authorization: Bearer`.
|
||||
- Confirm that the key scope covers the target endpoint.
|
||||
- Do not use the panel login password as an API key.
|
||||
+600
-62
@@ -1,6 +1,6 @@
|
||||
# API 集成
|
||||
|
||||
CLICD 对外推荐使用 `/api/v1` 接口。旧版未带版本号的接口主要用于 Web 面板和兼容场景,新接入请优先使用 `/api/v1`。
|
||||
CLICD 继续兼容旧版 `/api` 接口,已有对接无需修改。新接入推荐使用 `/api/v1` 接口,下面的清单均为 v1;容器列表推荐 `GET /api/v1/containers`。
|
||||
|
||||
## 认证
|
||||
|
||||
@@ -14,8 +14,81 @@ curl -H "X-API-Key: YOUR_API_KEY" https://panel.example.com/api/v1/containers
|
||||
curl -H "Authorization: Bearer YOUR_API_KEY" https://panel.example.com/api/v1/dashboard
|
||||
```
|
||||
|
||||
## 响应结构
|
||||
|
||||
所有接口保持统一响应包裹:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "OK",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
对接时建议只读取业务所需字段。新增能力会优先追加可选字段,不会要求已有插件改掉现有字段名。
|
||||
|
||||
## 创建与重装
|
||||
|
||||
创建容器、批量创建、重装和批量重装已支持 NAT、公网 IPv4、IPv6 混合网络,以及 Linux SSH 登录方式配置。公网 IPv4/IPv6 地址池可通过 `GET /api/v1/routing` 查看,并可通过 `PUT /api/v1/routing` 更新。
|
||||
|
||||
创建容器示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "demo-lxc-01",
|
||||
"virtualization": "lxc",
|
||||
"template_id": "debian-bookworm",
|
||||
"vcpu": 1,
|
||||
"ram_mb": 512,
|
||||
"disk_gb": 10,
|
||||
"assign_nat": true,
|
||||
"port_mapping_count": 2,
|
||||
"assign_ipv4": false,
|
||||
"ipv4_count": 1,
|
||||
"public_ipv4s": [],
|
||||
"assign_ipv6": true,
|
||||
"ipv6_count": 1,
|
||||
"ipv6_addresses": [],
|
||||
"ssh_auth_mode": "auto_password",
|
||||
"ssh_password": "",
|
||||
"ssh_public_key": "",
|
||||
"expires_at": ""
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `assign_nat` | 是否分配 NAT 端口映射;不传时保持默认 NAT 行为。 |
|
||||
| `assign_ipv4` | 是否分配公网 IPv4。 |
|
||||
| `ipv4_count` | 自动分配公网 IPv4 数量。 |
|
||||
| `public_ipv4s` | 指定公网 IPv4 地址列表。 |
|
||||
| `assign_ipv6` | 是否分配 IPv6。 |
|
||||
| `ipv6_count` | 自动分配 IPv6 数量。 |
|
||||
| `ipv6_addresses` | 指定 IPv6 地址列表。 |
|
||||
| `ssh_auth_mode` | Linux 创建支持 `auto_password`、`password`、`key`;重装额外支持 `keep`。 |
|
||||
| `ssh_password` | `password` 模式下的自定义密码;8-64 位,至少包含字母和数字,不能包含空白字符。 |
|
||||
| `ssh_public_key` | `key` 模式下的一行 SSH 公钥。 |
|
||||
|
||||
重装示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"template_id": "debian-bookworm",
|
||||
"ssh_auth_mode": "keep",
|
||||
"ssh_password": "",
|
||||
"ssh_public_key": ""
|
||||
}
|
||||
```
|
||||
|
||||
`keep` 仅用于重装,表示沿用当前 SSH 密码。Windows KVM 镜像会忽略 Linux SSH 公钥相关字段。
|
||||
|
||||
## Python 示例
|
||||
|
||||
获取容器列表:
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
@@ -30,9 +103,7 @@ session.headers.update({
|
||||
|
||||
resp = session.get(f"{BASE_URL}/api/v1/containers", timeout=15)
|
||||
resp.raise_for_status()
|
||||
containers = resp.json()
|
||||
|
||||
print(containers)
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
创建端口映射:
|
||||
@@ -45,10 +116,10 @@ API_KEY = "YOUR_API_KEY"
|
||||
CONTAINER_ID = "example-vm"
|
||||
|
||||
payload = {
|
||||
"name": "web",
|
||||
"protocol": "tcp",
|
||||
"host_port": 18080,
|
||||
"container_port": 80,
|
||||
"description": "web",
|
||||
}
|
||||
|
||||
resp = requests.post(
|
||||
@@ -61,76 +132,543 @@ resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
## 返回结构示例
|
||||
## 接口清单
|
||||
|
||||
容器列表:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 5,
|
||||
"uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"name": "example-vm",
|
||||
"status": "running",
|
||||
"ip": "10.0.3.25",
|
||||
"ipv6": "2001:db8:100::1005",
|
||||
"cpu_limit": 2,
|
||||
"memory_limit": 2048,
|
||||
"disk_limit": 20480,
|
||||
"traffic_limit": 107374182400,
|
||||
"expires_at": "2026-12-31 23:59:59"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
任务队列:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "task-13",
|
||||
"type": "restart",
|
||||
"status": "running",
|
||||
"created_at": "2026-06-09T10:00:00+08:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
WebSSH 票据:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"ticket": "***60秒有效票据***"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常用接口
|
||||
### 总览
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/dashboard` | 控制面板统计 |
|
||||
| GET | `/api/v1/host-info` | 主机资源 |
|
||||
| GET | `/api/v1/routing` | NAT/IPv4/IPv6 路由 |
|
||||
| PUT | `/api/v1/routing` | 更新公网 IPv4/IPv6 池 |
|
||||
| POST | `/api/v1/routing/ipv4-scan` | 扫描公网 IPv4 段 |
|
||||
| GET | `/api/v1/ipv6/status` | IPv6 状态 |
|
||||
| GET | `/api/v1/tasks` | 任务队列 |
|
||||
| DELETE | `/api/v1/tasks/{task_id}` | 删除任务 |
|
||||
|
||||
### 容器
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/containers` | 容器列表 |
|
||||
| POST | `/api/v1/containers/list` | 容器列表兼容 POST 写法 |
|
||||
| POST | `/api/v1/containers` | 创建容器 |
|
||||
| GET | `/api/v1/containers/{id|uuid|name}` | 容器详情 |
|
||||
| POST | `/api/v1/containers/{id}/start` | 开机 |
|
||||
| POST | `/api/v1/containers/{id}/stop` | 关机 |
|
||||
| POST | `/api/v1/containers/{id}/restart` | 重启 |
|
||||
| POST | `/api/v1/containers/{id}/reinstall` | 重装 |
|
||||
| DELETE | `/api/v1/containers/{id}/delete` | 删除 |
|
||||
| GET | `/api/v1/tasks` | 任务队列 |
|
||||
| GET | `/api/v1/containers/{id}/usage` | 资源用量 |
|
||||
| GET | `/api/v1/containers/{id}/traffic` | 流量统计 |
|
||||
| POST | `/api/v1/containers/{id}/traffic-reset` | 重置流量 |
|
||||
| PUT | `/api/v1/containers/{id}/traffic-limit` | 调整流量限制 |
|
||||
| PUT | `/api/v1/containers/{id}/resource-limit` | 调整资源限制 |
|
||||
| PUT | `/api/v1/containers/{id}/expiry` | 调整到期时间 |
|
||||
| POST | `/api/v1/containers/{id}/reset-password` | 重置 SSH 密码 |
|
||||
| POST | `/api/v1/containers/{id}/ipv6` | 分配 IPv6 |
|
||||
|
||||
### 端口与快照
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/containers/{id}/random-port` | 随机可用端口 |
|
||||
| POST | `/api/v1/containers/{id}/port-mappings` | 添加端口映射 |
|
||||
| PUT | `/api/v1/containers/{id}/port-mappings/{index}` | 更新端口映射 |
|
||||
| DELETE | `/api/v1/containers/{id}/port-mappings/{index}` | 删除端口映射 |
|
||||
| GET | `/api/v1/snapshots` | 快照总览 |
|
||||
| GET | `/api/v1/containers/{id}/snapshots` | 容器快照 |
|
||||
| POST | `/api/v1/containers/{id}/snapshots` | 创建快照 |
|
||||
| DELETE | `/api/v1/containers/{id}/snapshots/{snapshot_id}` | 删除快照 |
|
||||
| POST | `/api/v1/containers/{id}/snapshots/{snapshot_id}/restore` | 恢复快照 |
|
||||
| POST | `/api/v1/containers/{id}/snapshots/schedule` | 计划快照 |
|
||||
| PUT | `/api/v1/containers/{id}/snapshots/quota` | 快照配额 |
|
||||
|
||||
### 平台管理
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| GET | `/api/v1/templates` | 模板列表 |
|
||||
| GET | `/api/v1/images` | 镜像管理列表 |
|
||||
| GET | `/api/v1/snapshots` | 快照总览 |
|
||||
| POST | `/api/v1/images/download` | 下载镜像 |
|
||||
| POST | `/api/v1/images/cancel` | 取消镜像下载 |
|
||||
| DELETE | `/api/v1/images/delete` | 删除镜像缓存 |
|
||||
| PUT | `/api/v1/images/toggle` | 启用/禁用镜像 |
|
||||
| GET | `/api/v1/security/alerts` | 安全告警 |
|
||||
| GET | `/api/v1/audit-logs` | 操作日志 |
|
||||
| GET | `/api/v1/api-keys` | API Key 列表 |
|
||||
| POST | `/api/v1/security/check` | 立即安全检查 |
|
||||
| GET | `/api/v1/security/logs?container={name}` | 安全连接日志 |
|
||||
| GET | `/api/v1/security/summary` | 安全汇总 |
|
||||
| GET | `/api/v1/security/settings` | 安全设置 |
|
||||
| PUT | `/api/v1/security/settings` | 更新安全设置 |
|
||||
| GET | `/api/v1/swap` | Swap 信息 |
|
||||
| POST | `/api/v1/swap` | 调整 Swap |
|
||||
| POST | `/api/v1/batch-create` | 批量创建容器 |
|
||||
| POST | `/api/v1/batch-action` | 批量开关机/删除/重装 |
|
||||
| POST | `/api/v1/ssh-ticket` | 创建 WebSSH 票据 |
|
||||
| POST | `/api/v1/vnc-ticket` | 创建 WebVNC 票据 |
|
||||
|
||||
完整接口清单请以面板内“API 集成”页面为准。
|
||||
### 账号与日志
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| POST | `/api/v1/sub-user/create` | 创建子用户链接 |
|
||||
| GET | `/api/v1/sub-users` | 子用户列表 |
|
||||
| POST | `/api/v1/sub-users/{id}/rotate-password` | 轮换子用户密码 |
|
||||
| GET | `/api/v1/sub-users/{id}/audit-logs` | 子用户操作日志 |
|
||||
| GET | `/api/v1/sub-users/{id}/login-logs` | 子用户登录日志 |
|
||||
| GET | `/api/v1/audit-logs` | 操作日志 |
|
||||
| GET | `/api/v1/login-logs` | 登录日志 |
|
||||
| GET | `/api/v1/api-keys` | API Key 列表 |
|
||||
| POST | `/api/v1/api-keys` | 创建 API Key |
|
||||
| PATCH | `/api/v1/api-keys/{id}` | 更新 API Key |
|
||||
| DELETE | `/api/v1/api-keys/{id}` | 删除 API Key |
|
||||
|
||||
## 返回样例
|
||||
|
||||
以下样例按接口路径分组。真实环境中的资源数值、任务 ID、容器 ID、时间、IP 和密钥会不同,示例中的密码、票据和 API Key 均已脱敏。
|
||||
|
||||
### 总览
|
||||
|
||||
```json
|
||||
{
|
||||
"GET /api/v1/dashboard": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"running": 31,
|
||||
"stopped": 0,
|
||||
"total_containers": 31
|
||||
}
|
||||
},
|
||||
"GET /api/v1/host-info": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"cpu": { "cores": 8, "usage_pct": 1.16 },
|
||||
"ram": { "total_mb": 31825, "used_mb": 1275, "free_mb": 30550 },
|
||||
"disk": { "total_gb": 1750.49, "used_gb": 123.98, "free_gb": 1626.51 },
|
||||
"network": {
|
||||
"public_ipv4": "203.0.113.10",
|
||||
"public_ipv4_interface": "eth0",
|
||||
"public_ipv6": "2001:db8:100::2",
|
||||
"public_ipv6_interface": "eth0"
|
||||
},
|
||||
"load": { "load1": 0.01, "load5": 0.03, "load15": 0.01 }
|
||||
}
|
||||
},
|
||||
"GET /api/v1/routing": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"nat4": { "used": 62, "remaining": "45474", "total": "45536" },
|
||||
"ipv4": { "used": 1, "remaining": "3", "total": "4" },
|
||||
"ipv6": { "used": 31, "remaining": "large", "total": "large" },
|
||||
"public_ipv4_addresses": [
|
||||
{ "address": "203.0.113.10", "interface": "eth0", "prefix_len": 32, "gateway": "203.0.113.1" }
|
||||
],
|
||||
"ipv4_assignments": [
|
||||
{ "container_id": 5, "container_name": "example-vm", "address": "203.0.113.10", "interface": "eth0", "prefix_len": 32, "gateway": "203.0.113.1" }
|
||||
],
|
||||
"nat4_mappings": [
|
||||
{ "container_id": 5, "container_name": "example-vm", "status": "running", "ip": "10.0.0.10", "host_port": 22004, "container_port": 22, "protocol": "tcp" }
|
||||
],
|
||||
"ipv6_assignments": [
|
||||
{ "container_id": 5, "container_name": "example-vm", "address": "2001:db8:100::1005", "prefix_len": 64, "interface": "eth0" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"PUT /api/v1/routing": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"ipv4": { "used": 1, "remaining": "3", "total": "4" },
|
||||
"public_ipv4_addresses": [
|
||||
{ "address": "203.0.113.10", "interface": "eth0", "prefix_len": 32, "gateway": "203.0.113.1" }
|
||||
],
|
||||
"ipv6_prefixes": [
|
||||
{ "interface": "eth0", "address": "2001:db8:100::2", "prefix": "2001:db8:100::/64", "prefix_len": 64, "gateway": "2001:db8:100::1" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"POST /api/v1/routing/ipv4-scan": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "address": "203.0.113.10", "interface": "eth0", "prefix_len": 32, "gateway": "203.0.113.1", "status": "available", "usable": true, "reason": "" }
|
||||
]
|
||||
},
|
||||
"GET /api/v1/ipv6/status": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"available": true,
|
||||
"reachable": true,
|
||||
"reason": "usable public IPv6 prefix detected",
|
||||
"prefixes": [
|
||||
{ "interface": "eth0", "address": "2001:db8:100::2", "prefix": "2001:db8:100::/64", "prefix_len": 64, "gateway": "2001:db8:100::1" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"GET /api/v1/tasks": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"DELETE /api/v1/tasks/{task_id}": {
|
||||
"success": true,
|
||||
"message": "Task deleted"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 容器
|
||||
|
||||
```json
|
||||
{
|
||||
"GET /api/v1/containers": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 5,
|
||||
"uuid": "00000000-0000-4000-8000-000000000005",
|
||||
"name": "example-vm",
|
||||
"virtualization": "lxc",
|
||||
"template": "debian-bullseye",
|
||||
"vcpu": 1,
|
||||
"ram_mb": 512,
|
||||
"disk_gb": 10,
|
||||
"status": "running",
|
||||
"ip": "10.0.0.10",
|
||||
"ipv6": "2001:db8:100::1005",
|
||||
"ssh_port": 22004,
|
||||
"ssh_password": "***",
|
||||
"port_mappings": [
|
||||
{ "container_port": 22, "host_port": 22004, "protocol": "tcp", "description": "SSH" },
|
||||
{ "container_port": 20000, "host_port": 20000, "protocol": "tcp", "description": "Port-20000" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"POST /api/v1/containers/list": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": 5, "uuid": "00000000-0000-4000-8000-000000000005", "name": "example-vm", "status": "running", "ip": "10.0.0.10" }
|
||||
]
|
||||
},
|
||||
"POST /api/v1/containers": {
|
||||
"success": true,
|
||||
"message": "Container created successfully"
|
||||
},
|
||||
"GET /api/v1/containers/{id|uuid|name}": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 5,
|
||||
"uuid": "00000000-0000-4000-8000-000000000005",
|
||||
"name": "example-vm",
|
||||
"status": "running",
|
||||
"ip": "10.0.0.10",
|
||||
"ipv6": "2001:db8:100::1005",
|
||||
"ssh_port": 22004,
|
||||
"ssh_password": "***",
|
||||
"policy_blocked": false
|
||||
}
|
||||
},
|
||||
"POST /api/v1/containers/{id}/start": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "start" }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/stop": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "stop" }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/restart": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "restart" }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/reinstall": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "reinstall" }
|
||||
},
|
||||
"DELETE /api/v1/containers/{id}/delete": {
|
||||
"success": true,
|
||||
"message": "Task queued",
|
||||
"data": { "task_id": "task-10", "container_name": "example-vm", "status": "pending", "action": "delete" }
|
||||
},
|
||||
"GET /api/v1/containers/{id}/usage": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"cpu_usage_pct": 0,
|
||||
"cpu_usage_usec": 3908852,
|
||||
"memory_usage_bytes": 29331456,
|
||||
"disk_usage_bytes": 515100672,
|
||||
"network_rx_bytes": 131232,
|
||||
"network_tx_bytes": 16828,
|
||||
"load1": 0.1,
|
||||
"load5": 0.06,
|
||||
"load15": 0.01
|
||||
}
|
||||
},
|
||||
"GET /api/v1/containers/{id}/traffic": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"mode": "total",
|
||||
"limit_gb": 0,
|
||||
"in_limit_gb": 0,
|
||||
"out_limit_gb": 0,
|
||||
"total_used_bytes": 142082,
|
||||
"rx_used_bytes": 127212,
|
||||
"tx_used_bytes": 14870,
|
||||
"used_pct": 0,
|
||||
"reset_date": "2026-06"
|
||||
}
|
||||
},
|
||||
"POST /api/v1/containers/{id}/traffic-reset": {
|
||||
"success": true,
|
||||
"message": "Traffic reset"
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/traffic-limit": {
|
||||
"success": true,
|
||||
"message": "Traffic limit updated"
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/resource-limit": {
|
||||
"success": true,
|
||||
"message": "Resource limits updated"
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/expiry": {
|
||||
"success": true,
|
||||
"message": "Expiry updated"
|
||||
},
|
||||
"POST /api/v1/containers/{id}/reset-password": {
|
||||
"success": true,
|
||||
"message": "SSH password reset successfully",
|
||||
"data": { "password": "***" }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/ipv6": {
|
||||
"success": true,
|
||||
"message": "IPv6 assigned",
|
||||
"data": { "id": 5, "name": "example-vm", "ipv6": "2001:db8:100::1005" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 端口与快照
|
||||
|
||||
```json
|
||||
{
|
||||
"GET /api/v1/containers/{id}/random-port": {
|
||||
"success": true,
|
||||
"data": { "port": 61320 }
|
||||
},
|
||||
"POST /api/v1/containers/{id}/port-mappings": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "container_port": 22, "host_port": 22004, "protocol": "tcp", "description": "SSH" },
|
||||
{ "container_port": 8080, "host_port": 61320, "protocol": "tcp", "description": "HTTP" }
|
||||
]
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/port-mappings/{index}": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "container_port": 8081, "host_port": 61320, "protocol": "tcp", "description": "HTTP" }
|
||||
]
|
||||
},
|
||||
"DELETE /api/v1/containers/{id}/port-mappings/{index}": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"GET /api/v1/snapshots": {
|
||||
"success": true,
|
||||
"data": null
|
||||
},
|
||||
"GET /api/v1/containers/{id}/snapshots": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"quota": 1,
|
||||
"schedule": { "enabled": false, "interval_hours": 0, "last_run": "", "next_run": "", "time": "", "created_by": "" },
|
||||
"snapshots": []
|
||||
}
|
||||
},
|
||||
"POST /api/v1/containers/{id}/snapshots": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "snap-20260608-001",
|
||||
"container_id": 5,
|
||||
"container_name": "example-vm",
|
||||
"created_at": "2026-06-08 16:00:00",
|
||||
"created_by": "api:Automation",
|
||||
"scheduled": false,
|
||||
"size_bytes": 10485760
|
||||
}
|
||||
},
|
||||
"DELETE /api/v1/containers/{id}/snapshots/{snapshot_id}": {
|
||||
"success": true,
|
||||
"message": "Snapshot deleted"
|
||||
},
|
||||
"POST /api/v1/containers/{id}/snapshots/{snapshot_id}/restore": {
|
||||
"success": true,
|
||||
"message": "Snapshot restored"
|
||||
},
|
||||
"POST /api/v1/containers/{id}/snapshots/schedule": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"container": { "id": 5, "name": "example-vm", "snapshot_schedule_enabled": true, "snapshot_schedule_interval_hours": 24, "snapshot_schedule_time": "03:00" }
|
||||
}
|
||||
},
|
||||
"PUT /api/v1/containers/{id}/snapshots/quota": {
|
||||
"success": true,
|
||||
"data": {
|
||||
"quota": 2,
|
||||
"container": { "id": 5, "name": "example-vm", "snapshot_limit": 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 平台管理
|
||||
|
||||
```json
|
||||
{
|
||||
"GET /api/v1/templates": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": "ubuntu-noble", "name": "Ubuntu 24.04", "distro": "ubuntu", "release": "noble", "arch": "amd64", "description": "Ubuntu 24.04 LTS" },
|
||||
{ "id": "debian-bookworm", "name": "Debian 12", "distro": "debian", "release": "bookworm", "arch": "amd64", "description": "Debian 12 (Bookworm)" }
|
||||
]
|
||||
},
|
||||
"GET /api/v1/images": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": "ubuntu-noble", "name": "Ubuntu 24.04", "type": "lxc", "downloaded": true, "enabled": true, "downloading": false, "progress": 0, "size_bytes": 135005452 }
|
||||
]
|
||||
},
|
||||
"POST /api/v1/images/download": {
|
||||
"success": true,
|
||||
"message": "Already downloaded"
|
||||
},
|
||||
"POST /api/v1/images/cancel": {
|
||||
"success": true,
|
||||
"message": "Cancel requested"
|
||||
},
|
||||
"DELETE /api/v1/images/delete": {
|
||||
"success": true,
|
||||
"message": "Deleted"
|
||||
},
|
||||
"PUT /api/v1/images/toggle": {
|
||||
"success": true,
|
||||
"message": "OK"
|
||||
},
|
||||
"GET /api/v1/security/alerts": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"POST /api/v1/security/check": {
|
||||
"success": true,
|
||||
"message": "Security check completed"
|
||||
},
|
||||
"GET /api/v1/security/logs?container={name}": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"GET /api/v1/security/summary": {
|
||||
"success": true,
|
||||
"data": { "critical": 0, "high": 0, "medium": 0, "low": 0, "total_alerts": 0 }
|
||||
},
|
||||
"GET /api/v1/security/settings": {
|
||||
"success": true,
|
||||
"data": { "auto_shutdown": false }
|
||||
},
|
||||
"PUT /api/v1/security/settings": {
|
||||
"success": true,
|
||||
"data": { "auto_shutdown": false }
|
||||
},
|
||||
"GET /api/v1/swap": {
|
||||
"success": true,
|
||||
"data": { "total_mb": 16383, "used_mb": 0, "free_mb": 16383, "enabled": true, "swap_file": "/swapfile" }
|
||||
},
|
||||
"POST /api/v1/swap": {
|
||||
"success": true,
|
||||
"message": "SWAP 已调整为 16384 MB",
|
||||
"data": { "total_mb": 16383, "used_mb": 0, "free_mb": 16383, "enabled": true, "swap_file": "/swapfile" }
|
||||
},
|
||||
"POST /api/v1/batch-create": {
|
||||
"success": true,
|
||||
"data": ["task-12"]
|
||||
},
|
||||
"POST /api/v1/batch-action": {
|
||||
"success": true,
|
||||
"data": ["task-13"]
|
||||
},
|
||||
"POST /api/v1/ssh-ticket": {
|
||||
"success": true,
|
||||
"data": { "ticket": "***60秒有效票据***" }
|
||||
},
|
||||
"POST /api/v1/vnc-ticket": {
|
||||
"success": true,
|
||||
"data": { "ticket": "***60秒有效票据***" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 账号与日志
|
||||
|
||||
```json
|
||||
{
|
||||
"POST /api/v1/sub-user/create": {
|
||||
"success": true,
|
||||
"message": "Sub-user created",
|
||||
"data": {
|
||||
"id": "sub-xxxxxxxx",
|
||||
"username": "user-xxxxxxxx",
|
||||
"password": "***",
|
||||
"container_names": ["example-vm"],
|
||||
"access_code": "********",
|
||||
"created_at": "2026-06-08 16:00:00"
|
||||
}
|
||||
},
|
||||
"GET /api/v1/sub-users": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"POST /api/v1/sub-users/{id}/rotate-password": {
|
||||
"success": true,
|
||||
"data": { "username": "user-xxxxxxxx", "password": "***", "access_code": "********" }
|
||||
},
|
||||
"GET /api/v1/sub-users/{id}/audit-logs": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"GET /api/v1/sub-users/{id}/login-logs": {
|
||||
"success": true,
|
||||
"data": []
|
||||
},
|
||||
"GET /api/v1/audit-logs": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "time": "2026-06-08 15:44:40", "action": "apikey.create", "target": "Test", "detail": "scopes=*", "user": "admin", "success": true }
|
||||
]
|
||||
},
|
||||
"GET /api/v1/login-logs": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "time": "2026-06-08 08:24:00 UTC", "username": "admin", "ip": "198.51.100.23", "user_agent": "Mozilla/5.0 ...", "success": true }
|
||||
]
|
||||
},
|
||||
"GET /api/v1/api-keys": {
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "id": "c271023f", "name": "Test", "prefix": "clicd_sk_dd9d...", "ip_whitelist": "", "created_at": "2026-06-08 15:44:40", "last_used": "2026-06-08 15:46:10", "scopes": ["*"], "last_used_ip": "198.51.100.23" }
|
||||
]
|
||||
},
|
||||
"POST /api/v1/api-keys": {
|
||||
"success": true,
|
||||
"message": "API key created. Save this key now - it won't be shown again.",
|
||||
"data": { "id": "a1b2c3d4", "name": "Automation", "key": "clicd_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "prefix": "clicd_sk_xxxx...", "scopes": ["dashboard:read", "container:read"] }
|
||||
},
|
||||
"PATCH /api/v1/api-keys/{id}": {
|
||||
"success": true,
|
||||
"data": { "id": "a1b2c3d4", "name": "Automation", "prefix": "clicd_sk_xxxx...", "scopes": ["dashboard:read", "container:read"], "disabled": false }
|
||||
},
|
||||
"DELETE /api/v1/api-keys/{id}": {
|
||||
"success": true,
|
||||
"message": "API key deleted"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -25,6 +25,14 @@ POST /api/v1/containers
|
||||
POST /api/v1/batch-create
|
||||
```
|
||||
|
||||
Linux 容器和 Linux KVM 虚拟机创建时支持配置 SSH 登录方式:
|
||||
|
||||
- `auto_password`:自动生成 root SSH 密码。
|
||||
- `password`:使用自定义 `ssh_password`。
|
||||
- `key`:写入一行 `ssh_public_key`,仍会保留可用于 WebSSH 的密码。
|
||||
|
||||
网络分配可以按需组合 NAT、公网 IPv4 和 IPv6。API 字段保持为 `assign_nat`、`assign_ipv4`、`public_ipv4s`、`assign_ipv6`、`ipv6_addresses` 等可选字段,未传时沿用默认行为。
|
||||
|
||||
## 生命周期操作
|
||||
|
||||
```http
|
||||
@@ -37,6 +45,8 @@ DELETE /api/v1/containers/{id}/delete
|
||||
|
||||
开关机、重装、删除等操作会进入任务队列。调用后可通过 `GET /api/v1/tasks` 查看执行状态。
|
||||
|
||||
重装 Linux 系统时可传 `ssh_auth_mode`、`ssh_password`、`ssh_public_key`。`ssh_auth_mode=keep` 表示沿用当前 SSH 密码;不传这些字段时保持旧行为。
|
||||
|
||||
## 资源与流量
|
||||
|
||||
容器详情页支持查看资源用量,调整流量限制、资源限制和到期时间。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 网络与路由
|
||||
|
||||
CLICD 提供 NAT4 端口映射、随机可用端口、IPv6 状态检查和 IPv6 分配能力。
|
||||
CLICD 提供 NAT4 端口映射、随机可用端口、公网 IPv4 分配、IPv6 状态检查和 IPv6 分配能力。创建容器时可以只分配 NAT、只分配公网 IPv4、只分配 IPv6,或按需混合使用。
|
||||
|
||||
## NAT4
|
||||
|
||||
@@ -30,6 +30,28 @@ POST /api/v1/containers/{id}/ipv6
|
||||
|
||||
如果宿主机没有公网 IPv6 或上游没有正确路由,面板中分配出的地址也无法从公网访问。
|
||||
|
||||
## 公网 IPv4
|
||||
|
||||
公网 IPv4 分配会从主机检测到的可用公网 IPv4 中选择地址,或使用 API 指定的 `public_ipv4s`。创建容器时可使用:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `assign_nat` | 是否启用 NAT 端口映射。 |
|
||||
| `assign_ipv4` | 是否分配公网 IPv4。 |
|
||||
| `ipv4_count` | 自动分配公网 IPv4 数量。 |
|
||||
| `public_ipv4s` | 指定公网 IPv4 地址列表。 |
|
||||
| `assign_ipv6` | 是否分配 IPv6。 |
|
||||
| `ipv6_count` | 自动分配 IPv6 数量。 |
|
||||
| `ipv6_addresses` | 指定 IPv6 地址列表。 |
|
||||
|
||||
公网地址池相关接口:
|
||||
|
||||
```http
|
||||
GET /api/v1/routing
|
||||
PUT /api/v1/routing
|
||||
POST /api/v1/routing/ipv4-scan
|
||||
```
|
||||
|
||||
## 路由状态
|
||||
|
||||
```http
|
||||
|
||||
@@ -63,8 +63,10 @@ const scopeGroups = [
|
||||
['dashboard:read', '控制面板'],
|
||||
['host:read', '主机资源'],
|
||||
['routing:read', '路由信息'],
|
||||
['routing:write', '路由配置'],
|
||||
['ipv6:read', 'IPv6 状态'],
|
||||
['task:read', '任务列表'],
|
||||
['task:delete', '删除任务'],
|
||||
['image:read', '镜像列表'],
|
||||
],
|
||||
},
|
||||
@@ -137,7 +139,9 @@ const endpointGroups: Array<{ title: string; endpoints: EndpointTuple[] }> = [
|
||||
endpoints: [
|
||||
['GET', '/api/v1/dashboard', '控制面板统计'],
|
||||
['GET', '/api/v1/host-info', '主机资源'],
|
||||
['GET', '/api/v1/routing', 'NAT/IPv6 路由'],
|
||||
['GET', '/api/v1/routing', 'NAT/IPv4/IPv6 路由'],
|
||||
['PUT', '/api/v1/routing', '更新公网 IPv4/IPv6 池'],
|
||||
['POST', '/api/v1/routing/ipv4-scan', '扫描公网 IPv4 段'],
|
||||
['GET', '/api/v1/ipv6/status', 'IPv6 状态'],
|
||||
['GET', '/api/v1/tasks', '任务队列'],
|
||||
['DELETE', '/api/v1/tasks/{task_id}', '删除任务'],
|
||||
@@ -147,7 +151,7 @@ const endpointGroups: Array<{ title: string; endpoints: EndpointTuple[] }> = [
|
||||
title: '容器',
|
||||
endpoints: [
|
||||
['GET', '/api/v1/containers', '容器列表'],
|
||||
['POST', '/api/v1/containers/list', '容器列表(兼容旧接口)'],
|
||||
['POST', '/api/v1/containers/list', '容器列表(兼容 POST 写法)'],
|
||||
['POST', '/api/v1/containers', '创建容器'],
|
||||
['GET', '/api/v1/containers/{id|uuid|name}', '容器详情'],
|
||||
['POST', '/api/v1/containers/{id}/start', '开机'],
|
||||
@@ -501,7 +505,6 @@ export default function ApiIntegration() {
|
||||
<div className="rounded-lg bg-gray-900 p-4 font-mono text-xs text-gray-100">
|
||||
<div>curl -X GET {BASE_URL}/api/v1/containers -H "X-API-Key: clicd_sk_xxxx"</div>
|
||||
<div className="mt-2 text-gray-400">curl -X GET {BASE_URL}/api/v1/dashboard -H "Authorization: Bearer clicd_sk_xxxx"</div>
|
||||
<div className="mt-2 text-amber-300">旧版 /api/containers/list 已兼容,但新接入请使用 GET /api/v1/containers</div>
|
||||
</div>
|
||||
|
||||
{endpointGroups.map(group => (
|
||||
@@ -732,8 +735,14 @@ const requestBodySamples: Record<string, Record<string, unknown>> = {
|
||||
io_speed_mbps: 0,
|
||||
extra_ports: [8080],
|
||||
port_mapping_count: 2,
|
||||
assign_nat: true,
|
||||
snapshot_limit: 1,
|
||||
assign_ipv4: false,
|
||||
ipv4_count: 1,
|
||||
public_ipv4s: [],
|
||||
assign_ipv6: true,
|
||||
ipv6_count: 1,
|
||||
ipv6_addresses: [],
|
||||
ssh_auth_mode: 'auto_password',
|
||||
ssh_password: '',
|
||||
ssh_public_key: '',
|
||||
@@ -781,6 +790,32 @@ const requestBodySamples: Record<string, Record<string, unknown>> = {
|
||||
'POST /api/v1/images/cancel': { template_id: 'debian-bookworm' },
|
||||
'DELETE /api/v1/images/delete': { template_id: 'debian-bookworm' },
|
||||
'PUT /api/v1/images/toggle': { template_id: 'debian-bookworm', enabled: true },
|
||||
'PUT /api/v1/routing': {
|
||||
items: [
|
||||
{
|
||||
address: '203.0.113.10',
|
||||
interface: 'eth0',
|
||||
prefix_len: 32,
|
||||
gateway: '203.0.113.1',
|
||||
},
|
||||
],
|
||||
ipv6_prefixes: [
|
||||
{
|
||||
address: '2001:db8:100::2',
|
||||
prefix: '2001:db8:100::/64',
|
||||
prefix_len: 64,
|
||||
interface: 'eth0',
|
||||
gateway: '2001:db8:100::1',
|
||||
},
|
||||
],
|
||||
},
|
||||
'POST /api/v1/routing/ipv4-scan': {
|
||||
cidr: '203.0.113.0/29',
|
||||
interface: 'eth0',
|
||||
gateway: '203.0.113.1',
|
||||
verify: true,
|
||||
limit: 64,
|
||||
},
|
||||
'POST /api/v1/security/check': { container_name: 'example-vm' },
|
||||
'PUT /api/v1/security/settings': { auto_shutdown: false },
|
||||
'POST /api/v1/swap': { action: 'resize', size_mb: 16384 },
|
||||
@@ -793,15 +828,28 @@ const requestBodySamples: Record<string, Record<string, unknown>> = {
|
||||
vcpu: 1,
|
||||
ram_mb: 512,
|
||||
disk_gb: 10,
|
||||
assign_nat: true,
|
||||
port_mapping_count: 2,
|
||||
snapshot_limit: 1,
|
||||
assign_ipv4: false,
|
||||
ipv4_count: 1,
|
||||
public_ipv4s: [],
|
||||
assign_ipv6: true,
|
||||
ipv6_count: 1,
|
||||
ipv6_addresses: [],
|
||||
ssh_auth_mode: 'key',
|
||||
ssh_public_key: 'ssh-ed25519 AAAA... user@example',
|
||||
},
|
||||
],
|
||||
},
|
||||
'POST /api/v1/batch-action': { action: 'restart', containers: [5], template_id: '' },
|
||||
'POST /api/v1/batch-action': {
|
||||
action: 'reinstall',
|
||||
containers: [5],
|
||||
template_id: 'debian-bookworm',
|
||||
ssh_auth_mode: 'keep',
|
||||
ssh_password: '',
|
||||
ssh_public_key: '',
|
||||
},
|
||||
'POST /api/v1/ssh-ticket': { container_name: 'example-vm' },
|
||||
'POST /api/v1/vnc-ticket': { container_name: 'kvm-demo' },
|
||||
'POST /api/v1/sub-user/create': { container_name: 'example-vm' },
|
||||
@@ -845,13 +893,30 @@ const responseSamples: Record<string, unknown> = {
|
||||
success: true,
|
||||
data: {
|
||||
nat4: { used: 62, remaining: '45474', total: '45536' },
|
||||
ipv4: { used: 1, remaining: '3', total: '4' },
|
||||
ipv6: { used: 31, remaining: 'large', total: 'large' },
|
||||
public_ipv4_addresses: [{ address: '203.0.113.10', interface: 'eth0', prefix_len: 32, gateway: '203.0.113.1' }],
|
||||
ipv4_assignments: [{ container_id: 5, container_name: 'example-vm', address: '203.0.113.10', interface: 'eth0', prefix_len: 32, gateway: '203.0.113.1' }],
|
||||
nat4_mappings: [
|
||||
{ container_id: 5, container_name: 'example-vm', status: 'running', ip: '10.0.0.10', host_port: 22004, container_port: 22, protocol: 'tcp' },
|
||||
],
|
||||
ipv6_assignments: [{ container_id: 5, container_name: 'example-vm', address: '2001:db8:100::1005', prefix_len: 64, interface: 'eth0' }],
|
||||
},
|
||||
},
|
||||
'PUT /api/v1/routing': {
|
||||
success: true,
|
||||
data: {
|
||||
ipv4: { used: 1, remaining: '3', total: '4' },
|
||||
public_ipv4_addresses: [{ address: '203.0.113.10', interface: 'eth0', prefix_len: 32, gateway: '203.0.113.1' }],
|
||||
ipv6_prefixes: [{ interface: 'eth0', address: '2001:db8:100::2', prefix: '2001:db8:100::/64', prefix_len: 64, gateway: '2001:db8:100::1' }],
|
||||
},
|
||||
},
|
||||
'POST /api/v1/routing/ipv4-scan': {
|
||||
success: true,
|
||||
data: [
|
||||
{ address: '203.0.113.10', interface: 'eth0', prefix_len: 32, gateway: '203.0.113.1', status: 'available', usable: true, reason: '' },
|
||||
],
|
||||
},
|
||||
'GET /api/v1/ipv6/status': {
|
||||
success: true,
|
||||
data: {
|
||||
@@ -1064,10 +1129,29 @@ function examplePathFor(path: string) {
|
||||
}
|
||||
|
||||
function endpointNoteFor(key: string) {
|
||||
if (key.includes('/vnc-ticket')) return 'WebVNC 仅适用于 KVM 虚拟机;LXC 容器会返回 VNC console is only available for KVM VMs。'
|
||||
if (key.includes('/containers/{id}/delete') || key.includes('/batch-action')) return '该接口会进入任务队列,请随后调用 GET /api/v1/tasks 查看执行状态。'
|
||||
if (key.includes('/reset-password') || key.includes('/api-keys') || key.includes('/sub-user')) return '样例中的密钥、密码和票据已脱敏;创建类接口的完整密钥只在创建响应中出现一次。'
|
||||
return ''
|
||||
const notes: string[] = []
|
||||
if (key === 'POST /api/v1/containers') {
|
||||
notes.push('Linux 创建支持 ssh_auth_mode=auto_password|password|key;公网 IPv4、IPv6 与 NAT 可通过 assign_nat、assign_ipv4、assign_ipv6 组合使用。')
|
||||
}
|
||||
if (key === 'POST /api/v1/containers/{id}/reinstall') {
|
||||
notes.push('重装支持 ssh_auth_mode=keep|auto_password|password|key;keep 仅用于重装,未传 SSH 字段时保持原有行为。')
|
||||
}
|
||||
if (key === 'POST /api/v1/batch-create') {
|
||||
notes.push('批量创建的单个 containers[] 项支持与 POST /api/v1/containers 相同的网络和 SSH 认证字段。')
|
||||
}
|
||||
if (key === 'POST /api/v1/batch-action') {
|
||||
notes.push('action=reinstall 时可追加 template_id、ssh_auth_mode、ssh_password、ssh_public_key;其他 action 会忽略这些重装字段。')
|
||||
}
|
||||
if (key === 'PUT /api/v1/routing') {
|
||||
notes.push('更新公网地址池需要 routing:write;已分配给容器的地址不能从池中移除。')
|
||||
}
|
||||
if (key === 'POST /api/v1/routing/ipv4-scan') {
|
||||
notes.push('扫描公网 IPv4 段需要 routing:write;verify=true 时会尝试校验地址可用性。')
|
||||
}
|
||||
if (key.includes('/vnc-ticket')) notes.push('WebVNC 仅适用于 KVM 虚拟机;LXC 容器会返回 VNC console is only available for KVM VMs。')
|
||||
if (key.includes('/containers/{id}/delete') || key.includes('/batch-action')) notes.push('该接口会进入任务队列,请随后调用 GET /api/v1/tasks 查看执行状态。')
|
||||
if (key.includes('/reset-password') || key.includes('/api-keys') || key.includes('/sub-user')) notes.push('样例中的密钥、密码和票据已脱敏;创建类接口的完整密钥只在创建响应中出现一次。')
|
||||
return notes.join(' ')
|
||||
}
|
||||
|
||||
function defaultResponseFor(method: HttpMethod) {
|
||||
|
||||
@@ -648,7 +648,7 @@ const exact: Record<string, string> = {
|
||||
'删除容器': 'Delete Container',
|
||||
'WebSSH 票据': 'WebSSH Ticket',
|
||||
'WebVNC 票据': 'WebVNC Ticket',
|
||||
'容器列表(兼容旧接口)': 'Container List (legacy-compatible API)',
|
||||
'容器列表(兼容 POST 写法)': 'Container List (compatible POST form)',
|
||||
'调整到期时间': 'Adjust Expiration Time',
|
||||
'镜像管理列表': 'Image Management List',
|
||||
'批量创建容器': 'Batch Create Containers',
|
||||
@@ -829,7 +829,6 @@ const replacements: Array<[RegExp, string]> = [
|
||||
[/拍摄快照需要先关机,完成后会自动重启容器\s*(.+?)。是否继续?/g, 'Taking a snapshot requires shutdown first. Container $1 will restart automatically afterward. Continue?'],
|
||||
[/确定删除\s*(.+?)\s*的快照吗?/g, 'Delete snapshot $1?'],
|
||||
[/确定恢复到\s*(.+?)\s*的快照吗?当前容器数据会被覆盖。/g, 'Restore to snapshot $1? Current container data will be overwritten.'],
|
||||
[/旧版\s*\/api\/containers\/list\s*已兼容,但新接入请使用\s*GET\s*\/api\/v1\/containers/g, 'Legacy /api/containers/list remains compatible, but new integrations should use GET /api/v1/containers'],
|
||||
[/到期\s*(.+)$/g, 'Expires $1'],
|
||||
[/支持\s*\((.+?)\)/g, 'Supported ($1)'],
|
||||
[/下载中\s*(.+)$/g, 'Downloading $1'],
|
||||
|
||||
Reference in New Issue
Block a user