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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: CI

# 运行时机:
# - PR 提交到 main / next / beta / develop 时验证
# - push 到这些分支时也跑(配合 release.yml 互补,release.yml 跑发布,这个跑校验)
on:
pull_request:
branches: [main, next, beta, develop]
paths-ignore:
- 'docs/**'
- '**.md'
push:
branches: [main, next, beta, develop]
paths-ignore:
- 'docs/**'
- '**.md'

# 同一 PR/branch 多次 push 时,取消旧 run,只跑最新那次
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build:
name: Build & Type-check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- name: Cache Bun dependencies
uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
node_modules
apps/*/node_modules
packages/*/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Build (turbo build — both admin + user apps)
run: bun run build

- name: Upload build artifacts
if: success()
uses: actions/upload-artifact@v4
with:
name: dist-${{ github.sha }}
path: |
apps/admin/dist
apps/user/dist
retention-days: 7
if-no-files-found: warn
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ npm-debug.log*
# Misc
.DS_Store
*.pem

# Auto-generated icon bundle (regenerated on every dev/build)
packages/ui/src/composed/icons-bundle.json
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,67 @@ This document records all notable changes to ShadCN Admin.
---


## [4.3.0](https://github.com/perfect-panel/frontend/compare/v1.4.2...v4.3.0) (2026-04-26)

### 🔥 Breaking Changes / 重大变更

* **billing model / 计费模型:** Migrate from IP-based concurrent limit to device-slot model. Each user subscription now manages N device slots; each device gets its own subscribe URL (token + UUID). 从「IP 并发限制」迁移到「设备槽位」模型。每个订阅管理 N 个设备槽,每台设备拥有独立的订阅 URL(token + UUID)
* **i18n keys / 翻译键:** Renamed `connectedDevices` semantics from "concurrent IP" to "devices"; Chinese text changes from「同时连接 IP 数 / 并发设备」to「设备数」/ EN: "Connected Devices" → "Devices"

### ✨ Features / 新功能

* **dashboard / 仪表盘:** Per-device cards with online indicator, smart device-type icons (mobile/laptop/tablet/router), today traffic, last seen, last IP / 设备卡:在线指示灯、按设备名智能图标、今日流量、最近上线、上次 IP
* **dashboard / 仪表盘:** Multi-line subscribe URL support with line selector (主线 + 备用 + CDN 多线路) / 多订阅域名支持,卡片内可切换线路
* **dashboard / 仪表盘:** Plan-level "Import to Client" section — 11 supported clients (v2rayN / Clash / Hiddify / Surge / Stash / FlClash / Shadowrocket / etc.) with per-client tutorial sheet
* **dashboard / 仪表盘:** Add Device / Add Traffic / Reset All / Renew action dialogs with prorated pricing
* **dashboard / 仪表盘:** Addon device support — user-purchased extra slots, deletable, separate pricing; base devices remain locked / 加购设备:用户购买的额外槽位可删除,套餐基础设备不可删
* **subscribe / 订阅:** Auto-update interval — admin sets hours, backend smart-injects per UA: Profile-Update-Interval header (Clash family / Hiddify) or #!MANAGED-CONFIG directive (Surge / Stash) / 自动更新间隔:按 UA 智能下发
* **glass UI / 玻璃化:** Full glassmorphism redesign — gradient background, backdrop-blur cards, frosted sidebar/header/dialogs, dark mode adapted / 玻璃感全站升级
* **sidebar / 侧边栏:** Colored nav icons + new label scheme (首页 / 账号 / 我的服务 / 账单与钱包 / 帮助中心)
* **right sidebar / 右侧栏:** Hero balance card + small gift/commission cards + invite-earn CTA / 余额 hero 卡 + 辅助小卡 + 邀请返利
* **admin / 管理后台:** Per-client tutorial editor linked to `site_content` CMS; supports per-language fallback
* **admin / 管理后台:** Subscription detail view with device 类型 column (base / addon)

### ⚡️ Performance / 性能

* **bundle / 包体积:** Removed all CDN dependencies (jsdelivr, jsdmirror, monaco CDN, iconify API) — fully offline
* **bundle / 包体积:** Replaced mathjs (1.5MB) with native + Function evaluator
* **bundle / 包体积:** Monaco editor lazy-loaded via dynamic import; Vite `optimizeDeps.exclude`
* **bundle / 包体积:** date-fns subpath import (`date-fns/locale/zh-CN`) saves ~988KB
* **bundle / 包体积:** Lottie animation lazy-loaded
* **icon / 图标:** Pre-bundled ~134 used icons into 62KB JSON, replaces 12MB iconify API runtime fetch

### 🎨 Style / 样式

* **toast:** Large size (340-420px) + glass + type-tinted backgrounds (success/error/warning/info)
* **tabs:** Selected state with primary border + glow + lift animation
* **cards:** Hover micro-interactions (lift + shadow + glow)
* **buttons:** Outline buttons enhanced contrast on glass surface; primary buttons with brand-color glow
* **selected device card:** Primary tint background + ring + badge / 选中设备卡:主色背景 + ring + 徽章

### 🐛 Bug Fixes / 问题修复

* **mobile / 移动端:** Header logo wrap, action buttons compress on iPhone, horizontal overflow (grid-cols-1 missing) / 头部 logo 换行、操作按钮压缩、grid-cols-1 缺失导致横向溢出
* **rename UX / 重命名:** Device name no longer triggers rename on accidental click — separate pencil button / 设备名误触改名修复 — 独立铅笔按钮
* **iOS icon / iOS 图标:** Replaced `simple-icons:ios` (renders as text "iOS") with `mdi:cellphone-iphone`
* **refresh / 刷新:** Visual feedback on refresh — spinning icon + disabled state + success toast
* **purchase race / 支付竞态:** Fixed OrderStatusError toast spam during webhook race
* **balance copy / 复制提示:** Custom server messages now flow through (was masked by generic "Param Error")
* **subscribe URL / 订阅链接:** Fixed `BuildSubscribeURL` stub — now uses configured `SubscribeDomain` + `SubscribePath`
* **DeepCopy fields:** Added missing V4.3 fields (`DeviceCount`, `TrafficAddon`, `IsAddon`, etc.) to admin response types

### ♻️ Refactoring / 重构

* **i18n / 国际化:** Full bilingual coverage — 0 missing keys across `dashboard.json`, `subscribe.json`, `layout.json`, `components.json`, `auth.json`, `order.json`, `user.json`, `system.json`, `servers.json`, `nodes.json`, `tool.json`, `document.json`, `menu.json`, `log.json`
* **i18n / 国际化:** Migrated all hardcoded English placeholders (auth forms, profile, area-code, editors) to `t()` calls
* **shared components / 共享组件:** MarkdownEditor / MonacoEditor / HTMLEditor / JSONEditor / GoTemplateEditor / Combobox / ColumnFilter / AreaCodeSelect / Pagination — all i18n-enabled via `components` namespace
* **client / 客户端区:** Client section moved from per-device duplication to plan-level (single instance, selected device drives URL) / 客户端区从设备级移到套餐级,只渲染一次

### 🔧 Chores / 杂项

* **terminology / 术语:** Hysteria 2 naming consistent across 3 projects (was mixed `Hysteria` / `Hy2` / `hysteria2`)


## [1.4.2](https://github.com/perfect-panel/frontend/compare/v1.4.1...v1.4.2) (2026-04-06)

### 🐛 Bug Fixes / 问题修复
Expand Down
9 changes: 5 additions & 4 deletions apps/admin/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ VITE_API_BASE_URL=
# API prefix path
VITE_API_PREFIX=

# CDN URL for static assets
VITE_CDN_URL=https://cdn.jsdmirror.com
# CDN URL — leave EMPTY to disable all CDN-backed features (sponsor card, remote tutorial).
# Only set this if you self-host a mirror of perfect-panel/ppanel-assets.
VITE_CDN_URL=

# Enable tutorial document feature (true/false)
VITE_TUTORIAL_DOCUMENT=true
# Enable tutorial document feature (true/false). Requires VITE_CDN_URL to be set.
VITE_TUTORIAL_DOCUMENT=false

# Default login credentials (for development only)
VITE_USER_EMAIL=
Expand Down
4 changes: 3 additions & 1 deletion apps/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port 3001",
"predev": "bun --cwd ../../packages/ui run icons:bundle",
"dev": "vite --host --port 3001",
"prebuild": "bun --cwd ../../packages/ui run icons:bundle",
"build": "vite build && tsc",
"serve": "vite preview",
"lint": "biome lint",
Expand Down
38 changes: 36 additions & 2 deletions apps/admin/public/assets/locales/en-US/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,50 @@
"90015": "This account has reached the limit of sending times today, please try again tomorrow.",
"unknown": "An error occurred in the system, please try again later."
},
"combobox": {
"search": "Search...",
"select": "Select..."
},
"editor": {
"markdownDescription": "Support markdown and html syntax",
"markdownTitle": "Markdown Editor",
"startTyping": "Start typing...",
"title": "Editor",
"goTemplateDescriptionSprig": "Go text/template syntax with Sprig functions",
"goTemplateDescription": "Go text/template syntax",
"goTemplateTitle": "Go Template Editor",
"goTemplatePlaceholder": "Enter your Go template here...",
"htmlDescription": "Support HTML",
"htmlTitle": "HTML Editor",
"htmlPreviewTitle": "HTML Preview",
"jsonTitle": "Edit JSON"
},
"language": "Language",
"pagination": {
"pageInfo": "Page {{page}} of {{total}}",
"rowsPerPage": "Rows per page"
"rowsPerPage": "Rows per page",
"firstPage": "Go to first page",
"previousPage": "Go to previous page",
"selectPage": "Select page number",
"nextPage": "Go to next page",
"lastPage": "Go to last page"
},
"theme": {
"dark": "Dark",
"light": "Light",
"system": "System",
"toggle": "Toggle theme"
},
"unlimited": "Unlimited"
"timezone": {
"all": "All",
"current": "Current",
"recommended": "Recommended",
"search": "Search timezone...",
"server": "Server"
},
"unlimited": "Unlimited",
"areaCode": {
"select": "Select Area Code",
"search": "Search area code..."
}
}
4 changes: 3 additions & 1 deletion apps/admin/public/assets/locales/en-US/document.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@
"tags": "Tags",
"title": "Title",
"updatedAt": "Updated At",
"updateSuccess": "Updated successfully"
"updateSuccess": "Updated successfully",
"tabDocuments": "Documents",
"tabSiteContent": "Site Content (Terms / Tutorials)"
}
9 changes: 7 additions & 2 deletions apps/admin/public/assets/locales/en-US/log.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
"upload": "Upload",
"user": "User",
"userAgent": "User Agent",
"userId": "User ID"
"userId": "User ID",
"actor": "Actor",
"action": "Action",
"target": "Target",
"detail": "Detail"
},
"detail": "Detail",
"failed": "Failed",
Expand All @@ -42,7 +46,8 @@
"serverTraffic": "Server Traffic Log",
"subscribe": "Subscribe Log",
"subscribeTraffic": "Subscribe Traffic Log",
"trafficDetails": "Traffic Details"
"trafficDetails": "Traffic Details",
"audit": "Audit Log"
},
"type": {
"231": "Auto Reset",
Expand Down
3 changes: 2 additions & 1 deletion apps/admin/public/assets/locales/en-US/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@
"Ticket Management": "Ticket Management",
"Traffic Details": "Traffic Details",
"User Management": "User Management",
"Users & Support": "Users & Support"
"Users & Support": "Users & Support",
"Audit": "Audit"
}
9 changes: 8 additions & 1 deletion apps/admin/public/assets/locales/en-US/nodes.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,12 @@
"tags": "Tags",
"tags_description": "Permission grouping tag (incl. plan binding and delivery policies).",
"tags_placeholder": "Use Enter or comma (,) to add multiple tags",
"updated": "Updated"
"updated": "Updated",
"errors": {
"nameRequired": "Please enter a name",
"serverRequired": "Please select a server",
"protocolRequired": "Please select a protocol",
"serverAddrRequired": "Please enter an entry address",
"portRange": "Port must be between 1 and 65535"
}
}
4 changes: 2 additions & 2 deletions apps/admin/public/assets/locales/en-US/product.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"delete": "Delete",
"deleteSuccess": "Delete Successful",
"deleteWarning": "Data cannot be recovered after deletion. Please proceed with caution.",
"deviceLimit": "IP Limit",
"deviceLimit": "Device Limit",
"edit": "Edit",
"editSubscribe": "Edit Subscription",
"sortSuccess": "Sort completed successfully",
Expand All @@ -23,7 +23,7 @@
"deductionRatio": "Automatic/Manual Deduction Configuration",
"deductionRatioDescription": "Used for deduction. By default, the system adopts an automatic calculation algorithm. When a manual ratio is provided, the system calculates proportions based on the time and traffic ratio, ensuring the total equals 100%.",
"description": "Description",
"deviceLimit": "IP Limit",
"deviceLimit": "Device Limit",
"discount": "Discount",
"discount_price": "Discount Price",
"discountDescription": "Set discount based on unit price",
Expand Down
15 changes: 14 additions & 1 deletion apps/admin/public/assets/locales/en-US/servers.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,18 @@
"unlimited": "Unlimited",
"up_mbps": "Upload Bandwidth",
"updated": "Updated",
"user": "User"
"user": "User",
"saved": "Saved",
"directList": "Direct List",
"directListTitle": "Direct Allowlist",
"directListDesc": "Domains the client connects directly (not via proxy). One per line. Includes panel/payment domains so users can recharge while throttled.",
"directListField": "Direct domains",
"entries": "entries",
"directListHint": "Suggested: panel domain, subscribe domain, payment gateways (Stripe / PayPal / Alipay / WeChat Pay).",
"save": "Save",
"validation": {
"requiredNumberField": "{{field}} is required and must be between {{min}} and {{max}}",
"requiredSelectField": "{{field}} is required",
"requiredField": "{{field}} is required"
}
}
33 changes: 29 additions & 4 deletions apps/admin/public/assets/locales/en-US/subscribe.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"save": "Save",
"saveFailed": "Save failed",
"update": "Update",
"updateSuccess": "Updated successfully"
"updateSuccess": "Updated successfully",
"batchDeleteSuccess": "Successfully deleted {count} clients",
"batchDeleteWarning": "Are you sure you want to delete the selected {count} clients?"
},
"config": {
"description": "Manage subscription system settings",
Expand All @@ -39,7 +41,9 @@
"userAgentListDescription": "Allowed {{userAgent}} for subscription access, one per line. Configured application {{userAgent}} will be automatically included",
"userAgentListPlaceholder": "Enter allowed {{userAgent}}, one per line",
"wildcardResolution": "Wildcard Resolution",
"wildcardResolutionDescription": "Enable wildcard domain resolution for subscriptions"
"wildcardResolutionDescription": "Enable wildcard domain resolution for subscriptions",
"updateInterval": "Subscription Auto-Update Interval (hours)",
"updateIntervalDescription": "After import, clients will auto-update subscription on this schedule. 0 = disabled. Smart UA detection: Clash family / Hiddify / Mihomo Party use Profile-Update-Interval header; Surge / Stash use #!MANAGED-CONFIG injection; v2rayN / Shadowrocket / QuanX / Loon do not support — user must set manually."
},
"form": {
"addTitle": "Add Client",
Expand Down Expand Up @@ -85,7 +89,12 @@
"tabs": {
"basic": "Basic Info",
"download": "Downloads",
"template": "Templates"
"template": "Templates",
"tutorial": "Tutorial"
},
"validation": {
"nameRequired": "Client name is required",
"userAgentRequiredSuffix": "is required"
}
},
"outputFormats": {
Expand All @@ -104,7 +113,8 @@
"description": "Description",
"name": "Client Name",
"outputFormat": "Output Format",
"supportedPlatforms": "Supported Platforms"
"supportedPlatforms": "Supported Platforms",
"enabled": "Enabled"
}
},
"templatePreview": {
Expand All @@ -117,5 +127,20 @@
"loading": "Loading...",
"preview": "Preview",
"title": "Template Preview"
},
"tutorial": {
"keyRequired": "Please set a tutorial key first",
"saved": "Tutorial saved",
"keyLabel": "Tutorial Key",
"keyDescription": "site_content row key. Same key shared across languages — switch the language at the top-right to edit a different translation.",
"titleLabel": "Title",
"titlePlaceholder": "e.g. 导入教程",
"bodyLabel": "Content",
"bodyPlaceholder": "Markdown / HTML content. Variables: {{.SubscribeUrl}}, {{.AppScheme}}, {{.AppName}}, {{.SiteName}}",
"variableHint": "Template variables auto-replaced when shown to users: {{.SubscribeUrl}} / {{.AppScheme}} / {{.AppName}} / {{.SiteName}}",
"loading": "Loading…",
"editingLang": "Editing: {{lang}}",
"saving": "Saving…",
"save": "Save Tutorial"
}
}
Loading