Compare commits

..

47 Commits

Author SHA1 Message Date
xiaojunnuo
831871d37f build: publish 2026-04-11 23:48:07 +08:00
xiaojunnuo
6072550ec1 build: trigger build image 2026-04-11 23:47:55 +08:00
xiaojunnuo
112a565bf7 v1.39.10 2026-04-11 23:46:27 +08:00
xiaojunnuo
59e5c76286 build: prepare to build 2026-04-11 23:43:16 +08:00
xiaojunnuo
21620ac6bd perf: 流水线修改编辑之后,增加未保存提示 2026-04-11 23:41:20 +08:00
xiaojunnuo
d05129ec67 perf: 部署到1panel面板支持mux模式 2026-04-11 23:20:19 +08:00
xiaojunnuo
0998de4ae6 chore: 首页时间动态刷新 2026-04-11 23:10:51 +08:00
xiaojunnuo
2bdf1832da perf: 增加域名管理 子域名检查提醒 2026-04-11 22:43:42 +08:00
xiaojunnuo
a846c4b66e chore: 1 2026-04-11 22:21:02 +08:00
xiaojunnuo
ee535895a3 perf: 修复检查全部某些情况下无效的bug,优化公共触发站点证书检查定时逻辑 2026-04-11 21:50:44 +08:00
xiaojunnuo
1e549dfd43 fix: 修复流水线任务编辑页面复制粘贴按钮在夜间模式显示问题 2026-04-11 21:07:23 +08:00
xiaojunnuo
6ee718a252 perf: 站点监控域名气泡增加端口显示 2026-04-11 21:02:31 +08:00
xiaojunnuo
557e98c33f fix: 修复用户管理添加用户无法上传头像的bug 2026-04-11 20:56:51 +08:00
xiaojunnuo
7a9eec88e8 perf: 1panel支持先上传证书再选择证书 2026-04-10 00:08:10 +08:00
xiaojunnuo
a7a4f66633 chore: 资源迁移到项目提示优化 2026-04-09 18:55:05 +08:00
xiaojunnuo
a88d0a6ae1 fix: 修复创建流水线无法选择通知的bug 2026-04-09 18:43:57 +08:00
xiaojunnuo
db87bc770e chore: 1 2026-04-09 18:20:36 +08:00
xiaojunnuo
7b6b71cd4b chore: 1 2026-04-08 10:36:02 +08:00
xiaojunnuo
df98463325 fix: 修复自定义插件删除后没有反注册的bug 2026-04-07 23:36:05 +08:00
xiaojunnuo
f7492db8bd docs: admin mode docs 2026-04-07 23:07:12 +08:00
xiaojunnuo
70b46d4a8f fix: 修复spaceship创建record报错的bug
https://github.com/certd/certd/issues/705
2026-04-07 22:29:30 +08:00
xiaojunnuo
411486e1e7 build: release 2026-04-06 01:53:40 +08:00
xiaojunnuo
6f81305232 build: publish 2026-04-06 01:26:21 +08:00
xiaojunnuo
79bc22d8ce build: trigger build image 2026-04-06 01:26:09 +08:00
xiaojunnuo
1c634a702a v1.39.9 2026-04-06 01:24:46 +08:00
xiaojunnuo
909a9e4050 build: prepare to build 2026-04-06 01:21:18 +08:00
xiaojunnuo
b5cc794061 perf(monitor): 支持查看监控执行记录
- 新增监控任务执行记录页面及相关API
- 添加数据库表结构及多数据库支持
- 完善国际化翻译
- 实现批量删除功能
- 优化站点监控服务逻辑
2026-04-06 01:17:02 +08:00
xiaojunnuo
73b8e85976 fix: 修复cn域名获取不到到期时间的问题 2026-04-06 00:33:08 +08:00
xiaojunnuo
282b5d6893 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-04-05 23:49:30 +08:00
xiaojunnuo
c6628e7311 perf: 支持域名到期时间监控通知 2026-04-05 23:49:25 +08:00
xiaojunnuo
6b109d172f perf: 腾讯云CLB大区增加台北 2026-04-03 11:02:39 +08:00
xiaojunnuo
6b29972399 chore: 修复可选链操作符和DNS管理插件问题
修复多处可选链操作符访问问题,避免潜在的空指针异常
优化DNS管理插件,移除重复的id字段并修正域名匹配逻辑
添加getDomainListPage方法以支持分页查询域名列表
2026-04-03 00:32:00 +08:00
xiaojunnuo
0fcd3c09fd Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-04-03 00:14:14 +08:00
xiaojunnuo
af503442b8 perf(plugin-dnsmgr): 添加彩虹DNS插件支持
实现彩虹DNS管理系统的插件集成,包括DNS记录创建、查询和删除功能
2026-04-03 00:14:08 +08:00
xiaojunnuo
c875971b71 perf: 优化腾讯云CLB插件支持选择证书id 2026-04-02 23:27:10 +08:00
xiaojunnuo
d1a65922d7 fix: 修复某些情况下报无法修改通知的问题 2026-04-02 16:28:14 +08:00
xiaojunnuo
6ef34f95d5 Merge branch 'v2-dev' of https://github.com/certd/certd into v2-dev 2026-04-02 14:51:54 +08:00
xiaojunnuo
8b79022179 chore: 1 2026-04-02 09:05:13 +08:00
xiaojunnuo
21aec77e5c perf(spaceship): 新增Spaceship DNS插件和授权模块
添加Spaceship DNS提供商插件和授权模块,支持域名解析管理
更新相关文档和技能说明,优化错误处理和日志记录
移除调试日志,更新README项目列表
2026-04-02 00:10:28 +08:00
xiaojunnuo
74c5259af8 build: release 2026-04-01 00:29:52 +08:00
xiaojunnuo
a3e7d4414d build: publish 2026-03-31 23:59:12 +08:00
xiaojunnuo
986d32eb81 build: trigger build image 2026-03-31 23:59:00 +08:00
xiaojunnuo
de0ae14544 v1.39.8 2026-03-31 23:57:25 +08:00
xiaojunnuo
6b52276fb6 build: prepare to build 2026-03-31 23:54:34 +08:00
xiaojunnuo
a19ea7489c perf: 支持部署证书到百度CCE 2026-03-31 23:52:12 +08:00
xiaojunnuo
14229c2f00 chore: delete agents 2026-03-31 22:27:42 +08:00
xiaojunnuo
6eb20a1f2e fix: 修复上传头像退出登录的bug 2026-03-31 15:42:02 +08:00
152 changed files with 3492 additions and 1273 deletions

View File

@@ -1,115 +0,0 @@
---
name: using-superpowers
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
---
<SUBAGENT-STOP>
If you were dispatched as a subagent to execute a specific task, skip this skill.
</SUBAGENT-STOP>
<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
</EXTREMELY-IMPORTANT>
## Instruction Priority
Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
2. **Superpowers skills** — override default system behavior where they conflict
3. **Default system prompt** — lowest priority
If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
## How to Access Skills
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
**In other environments:** Check your platform's documentation for how skills are loaded.
## Platform Adaptation
Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
# Using Skills
## The Rule
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
```dot
digraph skill_flow {
"User message received" [shape=doublecircle];
"About to EnterPlanMode?" [shape=doublecircle];
"Already brainstormed?" [shape=diamond];
"Invoke brainstorming skill" [shape=box];
"Might any skill apply?" [shape=diamond];
"Invoke Skill tool" [shape=box];
"Announce: 'Using [skill] to [purpose]'" [shape=box];
"Has checklist?" [shape=diamond];
"Create TodoWrite todo per item" [shape=box];
"Follow skill exactly" [shape=box];
"Respond (including clarifications)" [shape=doublecircle];
"About to EnterPlanMode?" -> "Already brainstormed?";
"Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
"Already brainstormed?" -> "Might any skill apply?" [label="yes"];
"Invoke brainstorming skill" -> "Might any skill apply?";
"User message received" -> "Might any skill apply?";
"Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
"Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
"Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
"Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
"Has checklist?" -> "Follow skill exactly" [label="no"];
"Create TodoWrite todo per item" -> "Follow skill exactly";
}
```
## Red Flags
These thoughts mean STOP—you're rationalizing:
| Thought | Reality |
|---------|---------|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
## Skill Priority
When multiple skills could apply, use this order:
1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
"Let's build X" → brainstorming first, then implementation skills.
"Fix this bug" → debugging first, then domain-specific skills.
## Skill Types
**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
**Flexible** (patterns): Adapt principles to context.
The skill itself tells you which.
## User Instructions
Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

View File

@@ -1,100 +0,0 @@
# Codex Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Codex equivalent |
|-----------------|------------------|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
| Task returns result | `wait` |
| Task completes automatically | `close_agent` to free slot |
| `TodoWrite` (task tracking) | `update_plan` |
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
| `Read`, `Write`, `Edit` (files) | Use your native file tools |
| `Bash` (run commands) | Use your native shell tools |
## Subagent dispatch requires multi-agent support
Add to your Codex config (`~/.codex/config.toml`):
```toml
[features]
multi_agent = true
```
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
## Named agent dispatch
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
Codex does not have a named agent registry — `spawn_agent` creates generic agents
from built-in roles (`default`, `explorer`, `worker`).
When a skill says to dispatch a named agent type:
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
local prompt template like `code-quality-reviewer-prompt.md`)
2. Read the prompt content
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
4. Spawn a `worker` agent with the filled content as the `message`
| Skill instruction | Codex equivalent |
|-------------------|------------------|
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
### Message framing
The `message` parameter is user-level input, not a system prompt. Structure it
for maximum instruction adherence:
```
Your task is to perform the following. Follow the instructions below exactly.
<agent-instructions>
[filled prompt content from the agent's .md file]
</agent-instructions>
Execute this now. Output ONLY the structured response following the format
specified in the instructions above.
```
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
- End with an explicit execution directive to prevent summarization of the instructions
### When this workaround can be removed
This approach compensates for Codex's plugin system not yet supporting an `agents`
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
skills can dispatch named agent types directly.
## Environment Detection
Skills that create worktrees or finish branches should detect their
environment with read-only git commands before proceeding:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
Step 1 for how each skill uses these signals.
## Codex App Finishing
When the sandbox blocks branch/push operations (detached HEAD in an
externally managed worktree), the agent commits all work and informs
the user to use the App's native controls:
- **"Create branch"** — names the branch, then commit/push/PR via App UI
- **"Hand off to local"** — transfers work to the user's local checkout
The agent can still run tests, stage files, and output suggested branch
names, commit messages, and PR descriptions for the user to copy.

View File

@@ -1,33 +0,0 @@
# Gemini CLI Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Gemini CLI equivalent |
|-----------------|----------------------|
| `Read` (file reading) | `read_file` |
| `Write` (file creation) | `write_file` |
| `Edit` (file editing) | `replace` |
| `Bash` (run commands) | `run_shell_command` |
| `Grep` (search file content) | `grep_search` |
| `Glob` (search files by name) | `glob` |
| `TodoWrite` (task tracking) | `write_todos` |
| `Skill` tool (invoke a skill) | `activate_skill` |
| `WebSearch` | `google_web_search` |
| `WebFetch` | `web_fetch` |
| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
## No subagent support
Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
## Additional Gemini CLI tools
These tools are available in Gemini CLI but have no Claude Code equivalent:
| Tool | Purpose |
|------|---------|
| `list_directory` | List files and subdirectories |
| `save_memory` | Persist facts to GEMINI.md across sessions |
| `ask_user` | Request structured input from the user |
| `tracker_create_task` | Rich task management (create, update, list, visualize) |
| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |

View File

@@ -1,115 +0,0 @@
---
name: using-superpowers
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
---
<SUBAGENT-STOP>
If you were dispatched as a subagent to execute a specific task, skip this skill.
</SUBAGENT-STOP>
<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
</EXTREMELY-IMPORTANT>
## Instruction Priority
Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
2. **Superpowers skills** — override default system behavior where they conflict
3. **Default system prompt** — lowest priority
If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
## How to Access Skills
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
**In other environments:** Check your platform's documentation for how skills are loaded.
## Platform Adaptation
Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
# Using Skills
## The Rule
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
```dot
digraph skill_flow {
"User message received" [shape=doublecircle];
"About to EnterPlanMode?" [shape=doublecircle];
"Already brainstormed?" [shape=diamond];
"Invoke brainstorming skill" [shape=box];
"Might any skill apply?" [shape=diamond];
"Invoke Skill tool" [shape=box];
"Announce: 'Using [skill] to [purpose]'" [shape=box];
"Has checklist?" [shape=diamond];
"Create TodoWrite todo per item" [shape=box];
"Follow skill exactly" [shape=box];
"Respond (including clarifications)" [shape=doublecircle];
"About to EnterPlanMode?" -> "Already brainstormed?";
"Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
"Already brainstormed?" -> "Might any skill apply?" [label="yes"];
"Invoke brainstorming skill" -> "Might any skill apply?";
"User message received" -> "Might any skill apply?";
"Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
"Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
"Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
"Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
"Has checklist?" -> "Follow skill exactly" [label="no"];
"Create TodoWrite todo per item" -> "Follow skill exactly";
}
```
## Red Flags
These thoughts mean STOP—you're rationalizing:
| Thought | Reality |
|---------|---------|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
## Skill Priority
When multiple skills could apply, use this order:
1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
"Let's build X" → brainstorming first, then implementation skills.
"Fix this bug" → debugging first, then domain-specific skills.
## Skill Types
**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
**Flexible** (patterns): Adapt principles to context.
The skill itself tells you which.
## User Instructions
Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

View File

@@ -1,100 +0,0 @@
# Codex Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Codex equivalent |
|-----------------|------------------|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
| Task returns result | `wait` |
| Task completes automatically | `close_agent` to free slot |
| `TodoWrite` (task tracking) | `update_plan` |
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
| `Read`, `Write`, `Edit` (files) | Use your native file tools |
| `Bash` (run commands) | Use your native shell tools |
## Subagent dispatch requires multi-agent support
Add to your Codex config (`~/.codex/config.toml`):
```toml
[features]
multi_agent = true
```
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
## Named agent dispatch
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
Codex does not have a named agent registry — `spawn_agent` creates generic agents
from built-in roles (`default`, `explorer`, `worker`).
When a skill says to dispatch a named agent type:
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
local prompt template like `code-quality-reviewer-prompt.md`)
2. Read the prompt content
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
4. Spawn a `worker` agent with the filled content as the `message`
| Skill instruction | Codex equivalent |
|-------------------|------------------|
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
### Message framing
The `message` parameter is user-level input, not a system prompt. Structure it
for maximum instruction adherence:
```
Your task is to perform the following. Follow the instructions below exactly.
<agent-instructions>
[filled prompt content from the agent's .md file]
</agent-instructions>
Execute this now. Output ONLY the structured response following the format
specified in the instructions above.
```
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
- End with an explicit execution directive to prevent summarization of the instructions
### When this workaround can be removed
This approach compensates for Codex's plugin system not yet supporting an `agents`
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
skills can dispatch named agent types directly.
## Environment Detection
Skills that create worktrees or finish branches should detect their
environment with read-only git commands before proceeding:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
Step 1 for how each skill uses these signals.
## Codex App Finishing
When the sandbox blocks branch/push operations (detached HEAD in an
externally managed worktree), the agent commits all work and informs
the user to use the App's native controls:
- **"Create branch"** — names the branch, then commit/push/PR via App UI
- **"Hand off to local"** — transfers work to the user's local checkout
The agent can still run tests, stage files, and output suggested branch
names, commit messages, and PR descriptions for the user to copy.

View File

@@ -1,33 +0,0 @@
# Gemini CLI Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Gemini CLI equivalent |
|-----------------|----------------------|
| `Read` (file reading) | `read_file` |
| `Write` (file creation) | `write_file` |
| `Edit` (file editing) | `replace` |
| `Bash` (run commands) | `run_shell_command` |
| `Grep` (search file content) | `grep_search` |
| `Glob` (search files by name) | `glob` |
| `TodoWrite` (task tracking) | `write_todos` |
| `Skill` tool (invoke a skill) | `activate_skill` |
| `WebSearch` | `google_web_search` |
| `WebFetch` | `web_fetch` |
| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
## No subagent support
Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
## Additional Gemini CLI tools
These tools are available in Gemini CLI but have no Claude Code equivalent:
| Tool | Purpose |
|------|---------|
| `list_directory` | List files and subdirectories |
| `save_memory` | Persist facts to GEMINI.md across sessions |
| `ask_user` | Request structured input from the user |
| `tracker_create_task` | Rich task management (create, update, list, visualize) |
| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |

View File

@@ -163,6 +163,16 @@ async doRequest(req: { action: string, data?: any }) {
}
```
--- 开发技巧:实现统一的 API 请求封装
**好处:**
- **代码复用**:避免在每个 API 方法中重复编写相同的 header 设置和错误处理逻辑
- **错误处理一致**:统一捕获和处理各种错误情况,确保错误信息格式统一
- **日志记录完善**:集中记录详细的错误信息,便于调试和问题排查
- **接口调用简化**:调用方只需关注业务逻辑,无需关心底层请求细节
- **易于维护**:统一修改 API 调用方式时,只需修改一处代码
## 注意事项
1. **插件命名**:插件名称应简洁明了,反映其功能。
@@ -170,9 +180,12 @@ async doRequest(req: { action: string, data?: any }) {
3. **日志输出**:必须使用 `this.ctx.logger` 输出日志,而不是 `console`
4. **错误处理**API 调用失败时应抛出明确的错误信息。
5. **测试方法**:实现 `onTestRequest` 方法,以便用户可以测试授权是否正常。
6. **统一接口调用方法**:封装统一的 API 请求方法,避免在每个 API 方法调用中重复编写错误处理逻辑。
## 完整示例
### 示例 1: 通用授权插件
```typescript
import { AccessInput, BaseAccess, IsAccess, Pager, PageRes, PageSearch } from '@certd/pipeline';
import { DomainRecord } from '@certd/plugin-lib';

View File

@@ -6,9 +6,8 @@ Access存储用户的第三放应用的授权数据比如用户名密码
Task 部署任务插件它继承AbstractTaskPlugin类被流水线调用execute方法将证书部署到对应的应用上
DnsProvider: DNS提供商插件它用于在ACME申请证书时给域名添加txt解析记录。
在开始工作前,请阅读并加载.trae/skills下面的技能根据skills进行相应的插件开发
当开发过程中遇到问题需要参考plugins目录下的其他插件或者用户提醒你更好的做法时你需要总结经验更新相应的skills让skills越来越完善能够在以后得新插件开发中具备指导意义。
一般调用的api接口文档会比较复杂你不知道接口是什么时请务必询问用户让用户提供API接口文档
完成开发后无需测试,通知用户自己去测试
注意事项:
1、使用技能在开始工作前请阅读并加载.trae/skills下面的技能根据skills进行相应的插件开发
2、迭代技能当开发过程用户提醒你更好的做法时你需要总结经验更新相应的skills让skills越来越完善能够在以后得新插件开发中具备指导意义。
3、一般调用的api接口文档会比较复杂你不知道接口是什么时请务必询问用户让用户提供API接口文档
4、完成开发后无需测试通知用户自己去测试

View File

@@ -126,6 +126,8 @@ if (isDev()) {
## 完整示例
### 示例:通用 DNS Provider
```typescript
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { DemoAccess } from './access.js';

View File

@@ -1,115 +0,0 @@
---
name: using-superpowers
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
---
<SUBAGENT-STOP>
If you were dispatched as a subagent to execute a specific task, skip this skill.
</SUBAGENT-STOP>
<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
</EXTREMELY-IMPORTANT>
## Instruction Priority
Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
2. **Superpowers skills** — override default system behavior where they conflict
3. **Default system prompt** — lowest priority
If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
## How to Access Skills
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
**In other environments:** Check your platform's documentation for how skills are loaded.
## Platform Adaptation
Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
# Using Skills
## The Rule
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
```dot
digraph skill_flow {
"User message received" [shape=doublecircle];
"About to EnterPlanMode?" [shape=doublecircle];
"Already brainstormed?" [shape=diamond];
"Invoke brainstorming skill" [shape=box];
"Might any skill apply?" [shape=diamond];
"Invoke Skill tool" [shape=box];
"Announce: 'Using [skill] to [purpose]'" [shape=box];
"Has checklist?" [shape=diamond];
"Create TodoWrite todo per item" [shape=box];
"Follow skill exactly" [shape=box];
"Respond (including clarifications)" [shape=doublecircle];
"About to EnterPlanMode?" -> "Already brainstormed?";
"Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
"Already brainstormed?" -> "Might any skill apply?" [label="yes"];
"Invoke brainstorming skill" -> "Might any skill apply?";
"User message received" -> "Might any skill apply?";
"Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
"Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
"Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
"Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
"Has checklist?" -> "Follow skill exactly" [label="no"];
"Create TodoWrite todo per item" -> "Follow skill exactly";
}
```
## Red Flags
These thoughts mean STOP—you're rationalizing:
| Thought | Reality |
|---------|---------|
| "This is just a simple question" | Questions are tasks. Check for skills. |
| "I need more context first" | Skill check comes BEFORE clarifying questions. |
| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
| "Let me gather information first" | Skills tell you HOW to gather information. |
| "This doesn't need a formal skill" | If a skill exists, use it. |
| "I remember this skill" | Skills evolve. Read current version. |
| "This doesn't count as a task" | Action = task. Check for skills. |
| "The skill is overkill" | Simple things become complex. Use it. |
| "I'll just do this one thing first" | Check BEFORE doing anything. |
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
## Skill Priority
When multiple skills could apply, use this order:
1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
"Let's build X" → brainstorming first, then implementation skills.
"Fix this bug" → debugging first, then domain-specific skills.
## Skill Types
**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
**Flexible** (patterns): Adapt principles to context.
The skill itself tells you which.
## User Instructions
Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.

View File

@@ -1,100 +0,0 @@
# Codex Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Codex equivalent |
|-----------------|------------------|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
| Task returns result | `wait` |
| Task completes automatically | `close_agent` to free slot |
| `TodoWrite` (task tracking) | `update_plan` |
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
| `Read`, `Write`, `Edit` (files) | Use your native file tools |
| `Bash` (run commands) | Use your native shell tools |
## Subagent dispatch requires multi-agent support
Add to your Codex config (`~/.codex/config.toml`):
```toml
[features]
multi_agent = true
```
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
## Named agent dispatch
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
Codex does not have a named agent registry — `spawn_agent` creates generic agents
from built-in roles (`default`, `explorer`, `worker`).
When a skill says to dispatch a named agent type:
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
local prompt template like `code-quality-reviewer-prompt.md`)
2. Read the prompt content
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
4. Spawn a `worker` agent with the filled content as the `message`
| Skill instruction | Codex equivalent |
|-------------------|------------------|
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
### Message framing
The `message` parameter is user-level input, not a system prompt. Structure it
for maximum instruction adherence:
```
Your task is to perform the following. Follow the instructions below exactly.
<agent-instructions>
[filled prompt content from the agent's .md file]
</agent-instructions>
Execute this now. Output ONLY the structured response following the format
specified in the instructions above.
```
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
- End with an explicit execution directive to prevent summarization of the instructions
### When this workaround can be removed
This approach compensates for Codex's plugin system not yet supporting an `agents`
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
skills can dispatch named agent types directly.
## Environment Detection
Skills that create worktrees or finish branches should detect their
environment with read-only git commands before proceeding:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
Step 1 for how each skill uses these signals.
## Codex App Finishing
When the sandbox blocks branch/push operations (detached HEAD in an
externally managed worktree), the agent commits all work and informs
the user to use the App's native controls:
- **"Create branch"** — names the branch, then commit/push/PR via App UI
- **"Hand off to local"** — transfers work to the user's local checkout
The agent can still run tests, stage files, and output suggested branch
names, commit messages, and PR descriptions for the user to copy.

View File

@@ -1,33 +0,0 @@
# Gemini CLI Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Gemini CLI equivalent |
|-----------------|----------------------|
| `Read` (file reading) | `read_file` |
| `Write` (file creation) | `write_file` |
| `Edit` (file editing) | `replace` |
| `Bash` (run commands) | `run_shell_command` |
| `Grep` (search file content) | `grep_search` |
| `Glob` (search files by name) | `glob` |
| `TodoWrite` (task tracking) | `write_todos` |
| `Skill` tool (invoke a skill) | `activate_skill` |
| `WebSearch` | `google_web_search` |
| `WebFetch` | `web_fetch` |
| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
## No subagent support
Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
## Additional Gemini CLI tools
These tools are available in Gemini CLI but have no Claude Code equivalent:
| Tool | Purpose |
|------|---------|
| `list_directory` | List files and subdirectories |
| `save_memory` | Persist facts to GEMINI.md across sessions |
| `ask_user` | Request structured input from the user |
| `tracker_create_task` | Rich task management (create, update, list, visualize) |
| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |

View File

@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
### Bug Fixes
* 修复创建流水线无法选择通知的bug ([a88d0a6](https://github.com/certd/certd/commit/a88d0a6ae15cb6170d0b36e21daf89f0dbd5f681))
* 修复流水线任务编辑页面复制粘贴按钮在夜间模式显示问题 ([1e549df](https://github.com/certd/certd/commit/1e549dfd431ed74e2bcdfce63e5f640c51603af3))
* 修复用户管理添加用户无法上传头像的bug ([557e98c](https://github.com/certd/certd/commit/557e98c33f5462167d8f6289f70dad68bb114a97))
* 修复自定义插件删除后没有反注册的bug ([df98463](https://github.com/certd/certd/commit/df9846332596d2afaba53e66d2897aa1c598f9c4))
* 修复spaceship创建record报错的bug ([70b46d4](https://github.com/certd/certd/commit/70b46d4a8f89cf8eded21ebb237e8c8ce6c40d30))
### Performance Improvements
* 1panel支持先上传证书再选择证书 ([7a9eec8](https://github.com/certd/certd/commit/7a9eec88e8eddf40dba055c072b5b2b0f67c1407))
* 部署到1panel面板支持mux模式 ([d05129e](https://github.com/certd/certd/commit/d05129ec67893b0b639003a4bca6878d128f56ad))
* 流水线修改编辑之后,增加未保存提示 ([21620ac](https://github.com/certd/certd/commit/21620ac6bdeb57e43509156a77037fc07c44282a))
* 修复检查全部某些情况下无效的bug优化公共触发站点证书检查定时逻辑 ([ee53589](https://github.com/certd/certd/commit/ee535895a3166c6f9046963e28fa8f22f018b574))
* 增加域名管理 子域名检查提醒 ([2bdf183](https://github.com/certd/certd/commit/2bdf1832da73a3728f3ac415837bc26e70531cd6))
* 站点监控域名气泡增加端口显示 ([6ee718a](https://github.com/certd/certd/commit/6ee718a25265a9db2115343af9a1a01958f34b81))
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Bug Fixes
* 修复cn域名获取不到到期时间的问题 ([73b8e85](https://github.com/certd/certd/commit/73b8e859766097b5251fc4e5051593d686669eb2))
* 修复某些情况下报无法修改通知的问题 ([d1a6592](https://github.com/certd/certd/commit/d1a65922d7e152d6edcf6c53b70079f16b54a0d3))
### Performance Improvements
* 腾讯云CLB大区增加台北 ([6b109d1](https://github.com/certd/certd/commit/6b109d172f0c7b6ce6ec164dc196d646a65f529f))
* 优化腾讯云CLB插件支持选择证书id ([c875971](https://github.com/certd/certd/commit/c875971b71dc6d392e56f0a7605281c40d9bb405))
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
* **plugin-dnsmgr:** 添加彩虹DNS插件支持 ([af50344](https://github.com/certd/certd/commit/af503442b8298c5b89d11cf2ea351d62e66a609e))
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -211,3 +211,4 @@ https://certd.handfree.work/
| --------- |--------- |----------- |
| [fast-crud](https://gitee.com/fast-crud/fast-crud/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/fast-crud/fast-crud?logo=github"/> | 基于vue3的crud快速开发框架 |
| [dev-sidecar](https://github.com/docmirror/dev-sidecar/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/docmirror/dev-sidecar?logo=github"/> | 直连访问github工具无需FQ解决github无法访问的问题 |
| [winsvc-manager](https://github.com/greper/winsvc-manager/) | <img alt="GitHub stars" src="https://img.shields.io/github/stars/greper/winsvc-manager?logo=github"/> | 可视化包装应用成为一个Windows服务使其后台运行 |

View File

@@ -124,6 +124,7 @@ export default defineConfig({
{text: "子域名托管", link: "/guide/use/cert/subdomain.md"},
{text: "流水线有效期", link: "/guide/use/pipeline/valid.md"},
{text: "IP证书申请", link: "/guide/use/cert/ip.md"},
{text: "企业模式", link: "/guide/use/mode/enterprise.md"},
{text: "插件开发", link: "/guide/use/dev/plugin.md"},
]
},

View File

@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
### Bug Fixes
* 修复创建流水线无法选择通知的bug ([a88d0a6](https://github.com/certd/certd/commit/a88d0a6ae15cb6170d0b36e21daf89f0dbd5f681))
* 修复流水线任务编辑页面复制粘贴按钮在夜间模式显示问题 ([1e549df](https://github.com/certd/certd/commit/1e549dfd431ed74e2bcdfce63e5f640c51603af3))
* 修复用户管理添加用户无法上传头像的bug ([557e98c](https://github.com/certd/certd/commit/557e98c33f5462167d8f6289f70dad68bb114a97))
* 修复自定义插件删除后没有反注册的bug ([df98463](https://github.com/certd/certd/commit/df9846332596d2afaba53e66d2897aa1c598f9c4))
* 修复spaceship创建record报错的bug ([70b46d4](https://github.com/certd/certd/commit/70b46d4a8f89cf8eded21ebb237e8c8ce6c40d30))
### Performance Improvements
* 1panel支持先上传证书再选择证书 ([7a9eec8](https://github.com/certd/certd/commit/7a9eec88e8eddf40dba055c072b5b2b0f67c1407))
* 部署到1panel面板支持mux模式 ([d05129e](https://github.com/certd/certd/commit/d05129ec67893b0b639003a4bca6878d128f56ad))
* 流水线修改编辑之后,增加未保存提示 ([21620ac](https://github.com/certd/certd/commit/21620ac6bdeb57e43509156a77037fc07c44282a))
* 修复检查全部某些情况下无效的bug优化公共触发站点证书检查定时逻辑 ([ee53589](https://github.com/certd/certd/commit/ee535895a3166c6f9046963e28fa8f22f018b574))
* 增加域名管理 子域名检查提醒 ([2bdf183](https://github.com/certd/certd/commit/2bdf1832da73a3728f3ac415837bc26e70531cd6))
* 站点监控域名气泡增加端口显示 ([6ee718a](https://github.com/certd/certd/commit/6ee718a25265a9db2115343af9a1a01958f34b81))
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Bug Fixes
* 修复cn域名获取不到到期时间的问题 ([73b8e85](https://github.com/certd/certd/commit/73b8e859766097b5251fc4e5051593d686669eb2))
* 修复某些情况下报无法修改通知的问题 ([d1a6592](https://github.com/certd/certd/commit/d1a65922d7e152d6edcf6c53b70079f16b54a0d3))
### Performance Improvements
* 腾讯云CLB大区增加台北 ([6b109d1](https://github.com/certd/certd/commit/6b109d172f0c7b6ce6ec164dc196d646a65f529f))
* 优化腾讯云CLB插件支持选择证书id ([c875971](https://github.com/certd/certd/commit/c875971b71dc6d392e56f0a7605281c40d9bb405))
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
* **plugin-dnsmgr:** 添加彩虹DNS插件支持 ([af50344](https://github.com/certd/certd/commit/af503442b8298c5b89d11cf2ea351d62e66a609e))
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -28,51 +28,53 @@
| 24.| **中国移动CND授权** | |
| 25.| **授权插件示例** | 这是一个示例授权插件,用于演示如何实现一个授权插件 |
| 26.| **dns.la授权** | |
| 27.| **多吉云** | |
| 28.| **Dokploy授权** | |
| 29.| **farcdn授权** | |
| 30.| **FlexCDN授权** | |
| 31.| **Gcore** | Gcore |
| 32.| **Github授权** | |
| 33.| **godaddy授权** | |
| 34.| **金山云授权** | |
| 35.| **FTP授权** | |
| 36.| **七牛OSS授权** | |
| 37.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
| 38.| **s3/minio授权** | S3/minio oss授权 |
| 39.| **namesilo授权** | |
| 40.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
| 41.| **1panel授权** | 账号和密码 |
| 42.| **支付宝** | |
| 43.| **白山云授权** | |
| 44.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
| 45.| **cdnfly授权** | |
| 46.| **k8s授权** | |
| 47.| **括彩云cdn授权** | 括彩云CDN每月免费30G[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
| 48.| **LeCDN授权** | |
| 49.| **lucky** | |
| 50.| **猫云授权** | |
| 51.| **plesk授权** | |
| 52.| **长亭雷池授权** | |
| 53.| **群晖登录授权** | |
| 54.| **uniCloud** | unicloud授权 |
| 55.| **微信支付** | |
| 56.| **易盾rcdn授权** | 易盾CDN每月免费30G[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 57.| **易发云短信** | sms.yfyidc.cn/ |
| 58.| **易盾DCDN授权** | https://user.yiduncdn.com |
| 59.| **易支付** | |
| 60.| **proxmox** | |
| 61.| **UCloud授权** | 优刻得授权 |
| 62.| **又拍云** | |
| 63.| **网宿授权** | |
| 64.| **西部数码授权** | |
| 65.| **我爱云授权** | 我爱云CDN |
| 66.| **新网授权(代理方式)** | |
| 67.| **新网授权** | |
| 68.| **新网互联授权** | 仅支持代理账号ip需要加入白名单 |
| 69.| **Zenlayer授权** | Zenlayer授权 |
| 70.| **GoEdge授权** | |
| 71.| **雨云授权** | https://app.rainyun.com/ |
| 27.| **彩虹DNS** | 彩虹DNS管理系统授权 |
| 28.| **多吉云** | |
| 29.| **Dokploy授权** | |
| 30.| **farcdn授权** | |
| 31.| **FlexCDN授权** | |
| 32.| **Gcore** | Gcore |
| 33.| **Github授权** | |
| 34.| **godaddy授权** | |
| 35.| **金山云授权** | |
| 36.| **FTP授权** | |
| 37.| **七牛OSS授权** | |
| 38.| **腾讯云COS授权** | 腾讯云对象存储授权,包含地域和存储桶 |
| 39.| **s3/minio授权** | S3/minio oss授权 |
| 40.| **namesilo授权** | |
| 41.| **Next Terminal 授权** | 用于访问 Next Terminal API 的授权配置 |
| 42.| **1panel授权** | 账号和密码 |
| 43.| **支付宝** | |
| 44.| **白山云授权** | |
| 45.| **宝塔云WAF授权** | 用于连接和管理宝塔云WAF服务的授权配置 |
| 46.| **cdnfly授权** | |
| 47.| **k8s授权** | |
| 48.| **括彩云cdn授权** | 括彩云CDN每月免费30G[注册即领](https://kuocaicdn.com/register?code=8mn536rrzfbf8) |
| 49.| **LeCDN授权** | |
| 50.| **lucky** | |
| 51.| **猫云授权** | |
| 52.| **plesk授权** | |
| 53.| **长亭雷池授权** | |
| 54.| **群晖登录授权** | |
| 55.| **uniCloud** | unicloud授权 |
| 56.| **微信支付** | |
| 57.| **易盾rcdn授权** | 易盾CDN每月免费30G[注册即领](https://rhcdn.yiduncdn.com/register?code=8mn536rrzfbf8) |
| 58.| **易发云短信** | sms.yfyidc.cn/ |
| 59.| **易盾DCDN授权** | https://user.yiduncdn.com |
| 60.| **易支付** | |
| 61.| **proxmox** | |
| 62.| **Spaceship.com 授权** | Spaceship.com API 授权插件 |
| 63.| **UCloud授权** | 优刻得授权 |
| 64.| **又拍云** | |
| 65.| **网宿授权** | |
| 66.| **西部数码授权** | |
| 67.| **我爱云授权** | 我爱云CDN |
| 68.| **新网授权(代理方式)** | |
| 69.| **新网授权** | |
| 70.| **新网互联授权** | 仅支持代理账号ip需要加入白名单 |
| 71.| **Zenlayer授权** | Zenlayer授权 |
| 72.| **GoEdge授权** | |
| 73.| **雨云授权** | https://app.rainyun.com/ |
<style module>
table th:first-of-type {

View File

@@ -1,5 +1,5 @@
# 任务插件
`128` 款任务插件
`129` 款任务插件
## 1. 证书申请
| 序号 | 名称 | 说明 |
@@ -155,8 +155,9 @@
| 序号 | 名称 | 说明 |
|-----|-----|-----|
| 1.| **百度云-部署证书到负载均衡** | 部署到百度云负载均衡包括BLB、APPBLB |
| 2.| **百度云-部署证书到CDN** | 部署到百度云CDN |
| 3.| **百度云-上传到证书托管** | 上传证书到百度云证书托管中心 |
| 2.| **百度云-部署到CCE** | 部署到百度云CCE集群Ingress等通过Secret管理证书的应用 |
| 3.| **百度云-部署证书到CDN** | 部署到百度云CDN |
| 4.| **百度云-上传到证书托管** | 上传证书到百度云证书托管中心 |
## 12. 七牛云
| 序号 | 名称 | 说明 |

View File

@@ -20,8 +20,10 @@
| 16.| **腾讯云EO DNS** | 腾讯云EO DNS解析提供者 |
| 17.| **西部数码** | west dns provider |
| 18.| **Dns提供商Demo** | dns provider示例 |
| 19.| **51dns** | 51DNS |
| 20.| **新网互联** | 新网互联 |
| 19.| **彩虹DNS** | 彩虹DNS管理系统 |
| 20.| **Spaceship** | Spaceship 域名解析 |
| 21.| **51dns** | 51DNS |
| 22.| **新网互联** | 新网互联 |
<style module>
table th:first-of-type {

View File

@@ -0,0 +1,40 @@
# 企业模式(项目管理)
## 模式简介
Certd支持两种管理模式`SaaS模式默认``企业模式`
![](../../../../packages/ui/certd-client/public/static/images/ent/admin_mode.png)
## SaaS模式
* 默认的模式,每个用户管理自己的流水线和授权资源,每个用户独立使用。
* Certd系统作为SaaS提供证书自动申请部署服务您的客户注册即可使用无需自己部署
## 企业模式
* 通过项目合作管理流水线证书和授权资源,所有用户视为企业内部员工。
* 当你想在企业内部使用,企业内部有多个项目,各个项目成员共同管理项目资源和证书时可以启用此模式
* 需要在"系统设置->管理模式"中开启`企业模式`
![](./images/admin_mode.jpg)
::: warning
* 建议在开始使用时固定一个合适的模式,之后就不要随意切换了。
* 商业版不能使用企业模式因为商业版提供功能价值在于SaaS服务与企业模式冲突
:::
### 数据迁移
模式之间数据不互通,您可以通过个人数据迁移功能将数据转到项目之下
#### 个人数据迁移到项目
注意:此操作不可逆,请谨慎操作
![](./images/transfer.jpg)
#### 流水线数据转到其他项目
项目之间流水线数据可以转移,依赖的授权数据会同步复制一份
![](./images/move.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.39.7"
"version": "1.39.10"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/publishlab/node-acme-client/compare/v1.39.9...v1.39.10) (2026-04-11)
**Note:** Version bump only for package @certd/acme-client
## [1.39.9](https://github.com/publishlab/node-acme-client/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/acme-client
## [1.39.8](https://github.com/publishlab/node-acme-client/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/acme-client
## [1.39.7](https://github.com/publishlab/node-acme-client/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.39.7",
"version": "1.39.10",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.39.7",
"@certd/basic": "^1.39.10",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.9.0",
@@ -70,5 +70,5 @@
"bugs": {
"url": "https://github.com/publishlab/node-acme-client/issues"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
### Performance Improvements
* 流水线修改编辑之后,增加未保存提示 ([21620ac](https://github.com/certd/certd/commit/21620ac6bdeb57e43509156a77037fc07c44282a))
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Performance Improvements
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/basic
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
01:02
23:43

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.39.7",
"version": "1.39.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -47,5 +47,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -126,7 +126,7 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
if (config.skipSslVerify || config.httpProxy) {
let rejectUnauthorized = true;
if (config.skipSslVerify) {
logger.info("跳过SSL验");
logger.info("忽略接口请求的SSL验");
rejectUnauthorized = false;
}
const proxy: any = {};
@@ -271,7 +271,7 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
}
const originalRequest = error.config || {};
logger.info(`config`, originalRequest);
// logger.info(`config`, originalRequest);
const retry = originalRequest.retry || {};
if (retry.status && retry.status.includes(status)) {
if (retry.max > 0 && retry.count < retry.max) {

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
**Note:** Version bump only for package @certd/pipeline
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Performance Improvements
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.39.7",
"version": "1.39.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -18,8 +18,8 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.39.7",
"@certd/plus-core": "^1.39.7",
"@certd/basic": "^1.39.10",
"@certd/plus-core": "^1.39.10",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"
@@ -45,5 +45,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -7,6 +7,7 @@ import { IEmailService } from "../service/index.js";
export type NotificationBody = {
userId?: number;
projectId?: number;
title: string;
content: string;
pipeline?: Pipeline;
@@ -20,6 +21,7 @@ export type NotificationBody = {
pipelineResult?: string;
pipelineTitle?: string;
errors?: string;
[key: string]: any; // 其他templateData
};
export type NotificationRequestHandleReqInput<T = any> = {

View File

@@ -1,17 +1,16 @@
import { Registrable } from "../registry/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
import { FileStore } from "../core/file-store.js";
import { accessRegistry, IAccessService } from "../access/index.js";
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
import { domainUtils, HttpRequestConfig, ILogger, logger, optionsUtils, utils } from "@certd/basic";
import { HttpClient } from "@certd/basic";
import { domainUtils, HttpClient, HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
import dayjs from "dayjs";
import { IPluginConfigService } from "../service/config.js";
import { cloneDeep, upperFirst } from "lodash-es";
import { INotificationService } from "../notification/index.js";
import { TaskEmitter } from "../service/emit.js";
import { accessRegistry, IAccessService } from "../access/index.js";
import { PageSearch } from "../context/index.js";
import { FileStore } from "../core/file-store.js";
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
import { FileItem, FormItemProps, Pipeline, Runnable, Step } from "../dt/index.js";
import { INotificationService } from "../notification/index.js";
import { Registrable } from "../registry/index.js";
import { IPluginConfigService } from "../service/config.js";
import { TaskEmitter } from "../service/emit.js";
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
export type PluginRequestHandleReq<T = any> = {
typeName: string;

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
**Note:** Version bump only for package @certd/lib-huawei
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/lib-huawei
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-huawei
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.39.7",
"version": "1.39.10",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",
@@ -24,5 +24,5 @@
"prettier": "^2.8.8",
"tslib": "^2.8.1"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
**Note:** Version bump only for package @certd/lib-iframe
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/lib-iframe
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-iframe
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.39.7",
"version": "1.39.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -31,5 +31,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
**Note:** Version bump only for package @certd/jdcloud
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/jdcloud
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/jdcloud
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/jdcloud

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.39.7",
"version": "1.39.10",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
@@ -56,5 +56,5 @@
"fetch"
]
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
**Note:** Version bump only for package @certd/lib-k8s
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/lib-k8s
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/lib-k8s

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.39.7",
"version": "1.39.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -14,10 +14,11 @@
"build3": "rollup -c",
"build2": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"pub": "npm publish"
"pub": "npm publish",
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/basic": "^1.39.7",
"@certd/basic": "^1.39.10",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {
@@ -32,5 +33,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -59,9 +59,9 @@ export class K8sClient {
const yml = loadYaml<KubernetesObject>(manifest);
const client = this.getKubeClient();
try {
this.logger.info("apply yaml:", yml);
await client.create(yml);
} catch (e) {
this.logger.error("apply error", e.response?.body);
if (e.response?.body?.reason === "AlreadyExists") {
//patch
this.logger.info("patch existing resource: ", yml.metadata?.name);
@@ -70,13 +70,26 @@ export class K8sClient {
yml.metadata = {};
}
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
await client.patch(yml);
return;
const res = await client.patch(yml);
return res?.body;
}
throw e;
}
}
async applyPatch(manifest: string) {
const yml = loadYaml<KubernetesObject>(manifest);
const client = this.getKubeClient();
this.logger.info("patch yaml:", yml);
const existing = await client.read(yml as any);
if (!yml.metadata) {
yml.metadata = {};
}
yml.metadata.resourceVersion = existing.body.metadata.resourceVersion;
const res = await client.patch(yml);
return res?.body;
}
/**
*
* @param localRecords { [domain]:{ip:'xxx.xx.xxx'} }
@@ -112,6 +125,7 @@ export class K8sClient {
*/
async createSecret(opts: { namespace: string; body: V1Secret }) {
const namespace = opts.namespace || "default";
this.logger.info("create secret:", opts.body.metadata);
const created = await this.client.createNamespacedSecret(namespace, opts.body);
this.logger.info("new secrets:", opts.body.metadata);
return created.body;
@@ -152,6 +166,8 @@ export class K8sClient {
this.logger.info(`secret ${secretName} 已创建`);
return res;
}
throw new Error(`secret ${secretName} 不存在`);
}
throw e;
}

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
### Bug Fixes
* 修复自定义插件删除后没有反注册的bug ([df98463](https://github.com/certd/certd/commit/df9846332596d2afaba53e66d2897aa1c598f9c4))
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/lib-server
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/lib-server
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.39.7",
"version": "1.39.10",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -28,11 +28,11 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.39.7",
"@certd/basic": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/plugin-lib": "^1.39.7",
"@certd/plus-core": "^1.39.7",
"@certd/acme-client": "^1.39.10",
"@certd/basic": "^1.39.10",
"@certd/pipeline": "^1.39.10",
"@certd/plugin-lib": "^1.39.10",
"@certd/plus-core": "^1.39.10",
"@midwayjs/cache": "3.14.0",
"@midwayjs/core": "3.20.11",
"@midwayjs/i18n": "3.20.13",
@@ -64,5 +64,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -76,10 +76,12 @@ export abstract class BaseService<T> {
* @param where
*/
async delete(ids: string | any[], where?: any) {
const idArr = this.resolveIdArr(ids);
let idArr = this.resolveIdArr(ids);
idArr = this.filterIds(idArr);
if (idArr.length === 0) {
return;
}
await this.getRepository().delete({
id: In(idArr),
...where,
@@ -94,7 +96,9 @@ export abstract class BaseService<T> {
}
if (typeof ids === 'string') {
return ids.split(',');
} else {
} else if(!Array.isArray(ids)){
return [ids];
}else {
return ids;
}
}
@@ -217,6 +221,7 @@ export abstract class BaseService<T> {
if (!Array.isArray(ids)) {
ids = [ids];
}
ids = this.filterIds(ids);
const res = await this.getRepository().find({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
@@ -234,7 +239,16 @@ export abstract class BaseService<T> {
throw new PermissionException('权限不足');
}
filterIds(ids: any[]) {
if (!ids) {
throw new ValidateException('ids不能为空');
}
return ids.filter((item) => {
return item!=null && item != ""
});
}
async batchDelete(ids: number[], userId: number,projectId?:number) {
ids = this.filterIds(ids);
if(userId!=null){
const list = await this.getRepository().find({
where: {

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.39.7",
"version": "1.39.10",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -46,5 +46,5 @@
"typeorm": "^0.3.11",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
**Note:** Version bump only for package @certd/plugin-cert
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/plugin-cert
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
**Note:** Version bump only for package @certd/plugin-cert
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/plugin-cert

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.39.7",
"version": "1.39.10",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,10 +17,10 @@
"compile": "tsc --skipLibCheck --watch"
},
"dependencies": {
"@certd/acme-client": "^1.39.7",
"@certd/basic": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/plugin-lib": "^1.39.7",
"@certd/acme-client": "^1.39.10",
"@certd/basic": "^1.39.10",
"@certd/pipeline": "^1.39.10",
"@certd/plugin-lib": "^1.39.10",
"psl": "^1.9.0",
"punycode.js": "^2.3.1"
},
@@ -38,5 +38,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
### Performance Improvements
* 1panel支持先上传证书再选择证书 ([7a9eec8](https://github.com/certd/certd/commit/7a9eec88e8eddf40dba055c072b5b2b0f67c1407))
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
**Note:** Version bump only for package @certd/plugin-lib
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Performance Improvements
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/plugin-lib

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.39.7",
"version": "1.39.10",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -22,10 +22,10 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.11",
"@aws-sdk/client-s3": "^3.964.0",
"@certd/acme-client": "^1.39.7",
"@certd/basic": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/plus-core": "^1.39.7",
"@certd/acme-client": "^1.39.10",
"@certd/basic": "^1.39.10",
"@certd/pipeline": "^1.39.10",
"@certd/plus-core": "^1.39.10",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",
@@ -57,5 +57,5 @@
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"gitHead": "adc3e6118b941818926705c3536babfca117c247"
"gitHead": "112a565bf74c50e16c27a2c0a716588f84f39789"
}

View File

@@ -7,6 +7,9 @@ import { ILogger } from "@certd/basic";
import dayjs from "dayjs";
import { uniq } from "lodash-es";
export interface ICertInfoGetter {
getByPipelineId: (pipelineId: number) => Promise<CertInfo>;
}
export type CertInfo = {
crt: string; //fullchain证书
key: string; //私钥

View File

@@ -1,2 +1,2 @@
export const CertApplyPluginNames = [":cert:"];
export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success";
export const EVENT_CERT_APPLY_SUCCESS = "CertApply.success";

View File

@@ -1,4 +1,4 @@
export * from "./convert.js";
export * from "./cert-reader.js";
export * from "./consts.js";
export * from "./dns-provider/index.js";
export * from "./dns-provider/index.js";

View File

@@ -44,6 +44,10 @@ export function createRemoteSelectInputDefine(opts?: {
component?: any;
value?: any;
pageSize?: number;
uploadCert?: {
title?: string;
columns?: Record<string, any>;
};
}) {
const title = opts?.title || "请选择";
const certDomainsInputKey = opts?.certDomainsInputKey || "certDomains";
@@ -74,6 +78,7 @@ export function createRemoteSelectInputDefine(opts?: {
multi,
pageSize: opts?.pageSize,
watches: [certDomainsInputKey, accessIdInputKey, ...watches],
uploadCert: opts?.uploadCert,
...opts.component,
},
value: opts.value,

View File

@@ -3,6 +3,45 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
### Bug Fixes
* 修复创建流水线无法选择通知的bug ([a88d0a6](https://github.com/certd/certd/commit/a88d0a6ae15cb6170d0b36e21daf89f0dbd5f681))
* 修复流水线任务编辑页面复制粘贴按钮在夜间模式显示问题 ([1e549df](https://github.com/certd/certd/commit/1e549dfd431ed74e2bcdfce63e5f640c51603af3))
* 修复用户管理添加用户无法上传头像的bug ([557e98c](https://github.com/certd/certd/commit/557e98c33f5462167d8f6289f70dad68bb114a97))
* 修复自定义插件删除后没有反注册的bug ([df98463](https://github.com/certd/certd/commit/df9846332596d2afaba53e66d2897aa1c598f9c4))
### Performance Improvements
* 1panel支持先上传证书再选择证书 ([7a9eec8](https://github.com/certd/certd/commit/7a9eec88e8eddf40dba055c072b5b2b0f67c1407))
* 流水线修改编辑之后,增加未保存提示 ([21620ac](https://github.com/certd/certd/commit/21620ac6bdeb57e43509156a77037fc07c44282a))
* 修复检查全部某些情况下无效的bug优化公共触发站点证书检查定时逻辑 ([ee53589](https://github.com/certd/certd/commit/ee535895a3166c6f9046963e28fa8f22f018b574))
* 增加域名管理 子域名检查提醒 ([2bdf183](https://github.com/certd/certd/commit/2bdf1832da73a3728f3ac415837bc26e70531cd6))
* 站点监控域名气泡增加端口显示 ([6ee718a](https://github.com/certd/certd/commit/6ee718a25265a9db2115343af9a1a01958f34b81))
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Bug Fixes
* 修复某些情况下报无法修改通知的问题 ([d1a6592](https://github.com/certd/certd/commit/d1a65922d7e152d6edcf6c53b70079f16b54a0d3))
### Performance Improvements
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复上传头像退出登录的bug ([6eb20a1](https://github.com/certd/certd/commit/6eb20a1f2e31d984d9135edbf39c97cdd15621f9))
### Performance Improvements
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
**Note:** Version bump only for package @certd/ui-client

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.39.7",
"version": "1.39.10",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -106,8 +106,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.39.7",
"@certd/pipeline": "^1.39.7",
"@certd/lib-iframe": "^1.39.10",
"@certd/pipeline": "^1.39.10",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -13,13 +13,11 @@
</template>
<script lang="ts" setup>
import parser from "cron-parser";
import { computed, ref } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { getCronNextTimes } from "/@/components/cron-editor/utils";
const { t } = useI18n();
import { getCronNextTimes } from "/@/components/cron-editor/utils";
defineOptions({
name: "CronEditor",
});

View File

@@ -2,7 +2,7 @@
<div class="domains-verify-plan-editor" :class="{ fullscreen }">
<div class="fullscreen-modal" @click="fullscreenExit"></div>
<div class="plan-wrapper">
<div class="plan-box">
<div class="plan-box bg-white dark:bg-neutral-700">
<div class="fullscreen-button pointer flex-center" @click="fullscreen = !fullscreen">
<span v-if="!fullscreen" style="font-size: 10px" class="flex-center">
这里可以放大
@@ -273,7 +273,7 @@ watch(
left: 0;
width: 100%;
height: 100%;
background-color: rgba(74, 74, 74, 0.78);
// background-color: rgba(74, 74, 74, 0.78);
z-index: 1000;
margin: auto;
display: flex;
@@ -287,7 +287,6 @@ watch(
.plan-box {
position: relative;
margin: auto;
background-color: #fff;
}
}
@@ -315,7 +314,7 @@ watch(
height: 100%;
//table-layout: fixed;
th {
background-color: #f5f5f5;
// background-color: #f5f5f5;
border-top: 1px solid #e8e8e8;
border-left: 1px solid #e8e8e8;
border-bottom: 1px solid #e8e8e8;

View File

@@ -33,7 +33,7 @@ export default {
async function onCreate() {
await pluginStore.init();
options.value = pluginStore.group.getPreStepOutputOptions({
pipeline: pipeline.value,
pipeline: pipeline?.value,
currentStageIndex: currentStageIndex.value,
currentTaskIndex: currentTaskIndex.value,
currentStepIndex: currentStepIndex.value,

View File

@@ -25,8 +25,9 @@
</div>
</template>
</a-select>
<div class="ml-5">
<div class="ml-5 flex flex-row no-wrap">
<fs-button :loading="loading" title="刷新选项" icon="ion:refresh-outline" @click="refreshOptions"></fs-button>
<UploadCert v-if="uploadCert" class="ml-5" v-bind="uploadCert" @submit="refreshOptions"></UploadCert>
</div>
</div>
<div class="helper" :class="{ error: hasError }">
@@ -39,6 +40,8 @@ import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils";
import UploadCert from "./upload-cert.vue";
import { UploadCertProps } from "./types";
defineOptions({
name: "RemoteSelect",
@@ -65,9 +68,10 @@ const props = defineProps<
pager?: boolean;
multi?: boolean;
pageSize?: number;
uploadCert?: UploadCertProps;
} & ComponentPropsType
>();
debugger;
const emit = defineEmits<{
"update:value": any;
}>();

View File

@@ -0,0 +1,5 @@
export interface UploadCertProps {
title?: string;
columns?: Record<string, any>;
button?: any;
}

View File

@@ -0,0 +1,101 @@
<template>
<div class="upload-cert">
<fs-button v-model:loading="loading" type="primary" text="上传" v-bind="props.button" @click="openUploadCertDialog"></fs-button>
</div>
</template>
<script lang="ts" setup>
import { message } from "ant-design-vue";
import { useFormDialog } from "../../../use/use-dialog";
import { computed, inject, ref } from "vue";
import { doRequest } from "../lib";
import { getInputFromForm } from "./utils";
import { UploadCertProps } from "./types";
import { merge } from "lodash-es";
const props = defineProps<UploadCertProps>();
const loading = ref(false);
const emit = defineEmits(["submit"]);
const { openFormDialog } = useFormDialog();
const pipeline = inject("pipeline", null);
const getCurrentPluginDefine: any = inject("getCurrentPluginDefine", () => {
return {};
});
const getScope: any = inject("get:scope", () => {
return {};
});
const getPluginType: any = inject("get:plugin:type", () => {
return "plugin";
});
const title = computed(() => props.title || "上传证书");
function openUploadCertDialog() {
const columns = merge(
{
certName: {
title: "证书名称",
form: {
component: {
name: "a-input",
vModel: "value",
},
helper: "上传后证书显示名称",
},
},
},
props.columns
);
openFormDialog({
title: title.value,
columns: {
certName: {
title: "证书名称",
form: {
component: {
name: "a-input",
vModel: "value",
},
},
},
...props.columns,
},
onSubmit: async (form: any) => {
const pluginType = getPluginType();
const scope = getScope();
const { input, record } = getInputFromForm(scope.form, pluginType);
loading.value = true;
try {
const res = await doRequest(
{
type: pluginType,
typeName: scope.form.type,
action: "onUploadCert",
input,
record,
data: {
pipelineId: pipeline?.value?.id,
...form,
},
},
{
// onError(err: any) {
// message.error(err.message);
// },
showErrorNotify: true,
}
);
message.success("上传成功");
emit("submit");
} finally {
loading.value = false;
}
},
});
}
</script>
<style lang="less">
.upload-cert {
display: flex;
align-items: center;
}
</style>

View File

@@ -4,7 +4,7 @@
<div class="step-item overflow-hidden">
<div class="text">
<h3 class="title">{{ number }} {{ currentStepItem.title }}</h3>
<h3 class="title font-bold">{{ number }} {{ currentStepItem.title }}</h3>
<div class="description mt-5">
<div v-for="(desc, index) of currentStepItem.descriptions" :key="index">{{ desc }}</div>
</div>
@@ -247,6 +247,7 @@ function previewMask() {
<style lang="less">
.tutorial-steps {
display: flex;
.step-item {
display: flex !important;
flex-direction: row;

View File

@@ -251,7 +251,7 @@ function openUpgrade() {
class: "vip-modal",
maskClosable: true,
okText: t("vip.close"),
width: 1100,
width: 1180,
content: () => {
return <VipModalContent placeholder={placeholder} isPlus={isPlus} productInfo={productInfo} goBuyPlusPage={goBuyPlusPage} goBuyCommPage={goBuyCommPage} openStarModal={openStarModal} modalRef={modalRef} />;
},

View File

@@ -65,6 +65,11 @@
</div>
<div class="get-show">
<template v-if="item.type === 'plus'">
<span v-if="todayOrderCount.showVipTotal" class="mr-5">
已有
<span class="color-red"> {{ todayOrderCount.vipTotal }}</span>
位伙伴支持
</span>
<a-tooltip :title="t('vip.afdian_support_vip')">
<a-button size="small" type="primary" @click="goBuyPlusPage">
{{ t("vip.get_after_support") }}
@@ -204,13 +209,13 @@ const vipTypeDefine: any = {
desc: t("vip.community_free_version"),
type: "free",
icon: "lucide:package-open",
privilege: [t("vip.unlimited_certificate_application"), t("vip.unlimited_domain_count"), t("vip.unlimited_certificate_pipelines"), t("vip.common_deployment_plugins"), t("vip.email_webhook_notifications")],
privilege: t("vip.free_privilege").split("\n"),
},
plus: {
title: t("vip.professional_edition"),
desc: t("vip.open_source_support"),
type: "plus",
privilege: [t("vip.vip_group_priority"), t("vip.unlimited_site_certificate_monitoring"), t("vip.more_notification_methods"), t("vip.plugins_fully_open")],
privilege: t("vip.plus_privilege").split("\n"),
trial: {
title: t("vip.click_to_get_7_day_trial"),
click: () => {
@@ -227,7 +232,7 @@ const vipTypeDefine: any = {
desc: t("vip.commercial_license"),
type: "comm",
icon: "vaadin:handshake",
privilege: [t("vip.all_pro_privileges"), t("vip.allow_commercial_use_modify_logo_title"), t("vip.data_statistics"), t("vip.plugin_management"), t("vip.unlimited_multi_users"), t("vip.support_user_payment")],
privilege: t("vip.comm_privilege").split("\n"),
priceText: props.productInfo.comm.priceText || `¥${props.productInfo.comm.price}/${t("vip.years")}`,
discountText: props.productInfo.comm.discountText || `¥${props.productInfo.comm.price3}/3${t("vip.years")}`,
tooltip: props.productInfo.comm.tooltip,
@@ -260,12 +265,16 @@ const todayOrderCount = computed(() => {
const lastStage = countInfo?.stages?.[countInfo?.stages?.length - 1] || {};
lastStage.orderCount = orderCount;
const vipTotal = countInfo?.vipTotal || 0;
const showVipTotal = countInfo?.showVipTotal || false;
const userTotal = countInfo?.userTotal || 0;
const stages: any = [];
stages.push({
title: countInfo.title,
vipTotal: countInfo?.vipTotal || 0,
orderCount: orderCount,
bg: lastStage.bg,
showVipTotal: showVipTotal,
});
if (lastStage.orderCount > 0) {
stages.push(lastStage);
@@ -273,6 +282,9 @@ const todayOrderCount = computed(() => {
return {
enabled: enabled,
stages: stages,
showVipTotal: showVipTotal,
vipTotal: vipTotal,
userTotal: userTotal,
};
});

View File

@@ -221,6 +221,7 @@ export default {
projectJoin: "Join Project",
currentProject: "Current Project",
projectMemberManager: "Project Member",
domainMonitorSetting: "Domain Monitor Settings",
},
certificateRepo: {
title: "Certificate Repository",

View File

@@ -64,10 +64,41 @@ export default {
dnsServerHelper: "Use a custom domain name resolution server, such as: 1.1.1.1 , support multiple",
certValidDays: "Certificate Valid Days",
certValidDaysHelper: "Number of days before expiration to send a notification",
domain: {
monitorSettings: "Domain Monitor Settings",
enabled: "Enable Domain Monitor",
enabledHelper: "Enable to monitor all domain registration expiration time",
notificationChannel: "Notification Channel",
setNotificationChannel: "Set the notification channel",
willExpireDays: "Will Expire Days",
willExpireDaysHelper: "Number of days before expiration to send a notification",
monitorCronSetting: "Monitoring Schedule",
cronTrigger: "Scheduled trigger for monitoring",
},
},
cert: {
expired: "Expired",
expiring: "Expiring",
noExpired: "Not Expired",
},
history: {
title: "Monitoring Execution Records",
description: "Monitoring execution records",
resultTitle: "Status",
contentTitle: "Content",
titleTitle: "Title",
jobTypeTitle: "Job Type",
startAtTitle: "Start Time",
endAtTitle: "End Time",
jobResultTitle: "Result",
jobResult: {
done: "Done",
start: "Start",
},
jobType: {
domainExpirationCheck: "Domain Expiration Check",
siteCertMonitor: "Site Certificate Monitor",
},
},
};

View File

@@ -53,6 +53,7 @@ export default {
unlimited_certificate_pipelines: "Unlimited certificate pipelines",
common_deployment_plugins: "Common host, cloud platform, CDN, Baota, 1Panel deployment plugins",
email_webhook_notifications: "Email, webhook notification methods",
free_privilege: "Unlimited certificate applications\nUnlimited domain count\nUnlimited certificate pipelines\nCommon host, cloud platform, CDN, Baota, 1Panel deployment plugins\nEmail, webhook notification methods",
professional_edition: "Professional Edition",
open_source_support: "Open source requires your sponsorship support",
@@ -60,10 +61,11 @@ export default {
unlimited_site_certificate_monitoring: "Unlimited site certificate monitoring",
more_notification_methods: "More notification methods",
plugins_fully_open: "All plugins open, including Synology and more",
plus_privilege: "Access to VIP group, your requests will have priority\nUnlimited site certificate monitoring\nMore notification methods\nAll plugins open, including Synology and more",
click_to_get_7_day_trial: "Click to get 7-day trial",
years: "years",
afdian_support_vip: "Obtain the permanent professional version coupon",
get_after_support: "Get after sponsoring",
get_after_support: "sponsoring",
business_edition: "Business Edition",
commercial_license: "Commercial license, allowed for external operation",
@@ -73,6 +75,7 @@ export default {
plugin_management: "Plugin management",
unlimited_multi_users: "Unlimited multi-users",
support_user_payment: "Supports user payments",
comm_privilege: "All professional edition privileges\nAllows commercial use, can modify logo and title\nData statistics\nPlugin management\nUnlimited multi-users\nSupports user payments",
contact_author_for_trial: "Buy It Now",
activate: "Activate",
get_pro_code_after_support: "Go to sponsoring",

View File

@@ -226,6 +226,8 @@ export default {
projectJoin: "加入项目",
currentProject: "当前项目",
projectMemberManager: "项目成员管理",
domainMonitorSetting: "域名监控设置",
jobHistory: "监控执行记录",
},
certificateRepo: {
title: "证书仓库",

View File

@@ -33,7 +33,7 @@ export default {
activateCertDesc2: "让证书生效",
taskSuccessTitle: "部署任务添加成功",
taskSuccessDesc: "现在可以运行",
pluginsTitle: "本系统提供茫茫多的部署插件",
pluginsTitle: "本系统提供海量的部署插件",
pluginsDesc: "您可以根据自身需求将证书部署到各种应用和平台",
},
},

View File

@@ -68,10 +68,40 @@ export default {
dnsServerHelper: "使用自定义的域名解析服务器1.1.1.1 , 支持多个",
certValidDays: "证书到期前天数",
certValidDaysHelper: "证书到期前多少天发送通知",
domain: {
monitorSettings: "域名监控设置",
enabled: "启用域名监控",
enabledHelper: "启用后,监控“域名管理”中域名的过期时间,到期前通知提醒",
notificationChannel: "通知渠道",
setNotificationChannel: "设置通知渠道",
willExpireDays: "到期前天数",
willExpireDaysHelper: "域名有效期到期前多少天发送通知",
monitorCronSetting: "监控定时设置",
cronTrigger: "定时触发监控",
},
},
cert: {
expired: "已过期",
expiring: "即将过期",
noExpired: "未过期",
},
history: {
title: "监控执行记录",
description: "站点证书、域名等监控任务的执行记录",
resultTitle: "状态",
contentTitle: "内容",
titleTitle: "标题",
jobTypeTitle: "任务类型",
startAtTitle: "开始时间",
endAtTitle: "结束时间",
jobResultTitle: "任务结果",
jobResult: {
done: "完成",
start: "开始",
},
jobType: {
domainExpirationCheck: "域名到期检查",
siteCertMonitor: "站点证书监控",
},
},
};

View File

@@ -53,6 +53,7 @@ export default {
unlimited_certificate_pipelines: "证书流水线数量无限制",
common_deployment_plugins: "常用的主机、云平台、cdn、宝塔、1Panel等部署插件",
email_webhook_notifications: "邮件、webhook通知方式",
free_privilege: "证书申请无限制\n域名数量无限制\n证书流水线数量无限制\n常用的主机、云平台、cdn、宝塔、1Panel等部署插件\n邮件、webhook通知方式",
professional_edition: "专业版",
open_source_support: "开源需要您的赞助支持,个人和企业内部使用",
@@ -60,6 +61,7 @@ export default {
unlimited_site_certificate_monitoring: "站点证书监控无限制",
more_notification_methods: "更多通知方式",
plugins_fully_open: "插件全开放,群晖等更多插件",
plus_privilege: "可加VIP群您的需求将优先实现\n站点证书监控无限制\n更多通知方式\n插件全开放群晖等更多插件\n企业模式项目管理\n域名到期监控\n第三方登录PassKey登录",
click_to_get_7_day_trial: "点击获取7天试用",
years: "年",
afdian_support_vip: "新用户开通永久专业版立享50优惠券",
@@ -73,6 +75,7 @@ export default {
plugin_management: "插件管理",
unlimited_multi_users: "多用户无限制",
support_user_payment: "支持用户支付(购买套餐,按流水线条数、域名数量、部署次数计费)",
comm_privilege: "拥有专业版所有特权\n允许商用可修改logo、标题\n数据统计\n插件管理\n多用户无限制\n支持用户支付购买套餐按流水线条数、域名数量、部署次数计费",
activate: "激活",
get_pro_code_after_support: "前往获取",
business_contact_author: "",

View File

@@ -241,6 +241,28 @@ export const certdResources = [
isMenu: true,
},
},
{
title: "certd.sysResources.domainMonitorSetting",
name: "DomainMonitorSetting",
path: "/certd/cert/domain/setting",
component: "/certd/cert/domain/setting/index.vue",
meta: {
icon: "ion:stopwatch-outline",
auth: true,
isMenu: true,
},
},
{
title: "certd.sysResources.jobHistory",
name: "JobHistory",
path: "/certd/monitor/history",
component: "/certd/monitor/history/index.vue",
meta: {
icon: "ion:barcode-outline",
auth: true,
isMenu: true,
},
},
{
title: "certd.userSecurity",
name: "UserSecurity",

View File

@@ -130,4 +130,16 @@ button.ant-btn.ant-btn-default.isPlus{
background-color: rgba(50, 54, 57, 0.04);
box-shadow: none;
}
}
.dark{
button.ant-btn.ant-btn-default.isPlus{
color: #c5913f;
border: 1px solid #c5913f;
&:disabled {
border-color: hsl(0, 0%, 31%);
color: rgba(233, 233, 233, 0.25);
}
}
}

View File

@@ -21,7 +21,8 @@ import { defineComponent, reactive, ref, watch, inject } from "vue";
import CertAccessModal from "./access/index.vue";
import { createAccessApi } from "../api";
import { message } from "ant-design-vue";
import { useUserStore } from "/@/store/user";
import { useProjectStore } from "/@/store/project";
export default defineComponent({
name: "AccessSelector",
components: { CertAccessModal },
@@ -71,11 +72,27 @@ export default defineComponent({
emitValue(null);
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value) {
if (pipeline && pipeline?.value && target?.value && pipeline.value.userId !== target.value.userId) {
message.error("对不起,您不能修改他人流水线的授权");
return;
const userId = userStore.userInfo.id;
const isEnterprice = projectStore.isEnterprise;
if (pipeline?.value) {
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error(`对不起,您不能修改其他项目流水线的授权`);
return;
}
} else {
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的授权`);
return;
}
}
}
if (value == null) {
selectedId.value = "";
target.value = null;

View File

@@ -48,6 +48,7 @@ import createCrudOptions from "../crud";
import { addonProvide } from "../common";
import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
const { t } = useI18n();
@@ -127,13 +128,24 @@ function clear() {
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value: any) {
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
return;
if (pipeline?.value) {
const userId = userStore.userInfo.id;
const isEnterprice = projectStore.isEnterprise;
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error(`对不起,您不能修改其他项目流水线的${props.addonType}设置`);
return;
}
} else {
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error(`对不起,您不能修改他人流水线的${props.addonType}设置`);
return;
}
}
}
emit("change", value);
emit("update:modelValue", value);

View File

@@ -99,3 +99,11 @@ export async function SyncExpirationStatus() {
method: "post",
});
}
export async function IsSubdomain(body: any) {
return await request({
url: apiPrefix + "/isSubdomain",
method: "post",
data: body,
});
}

View File

@@ -52,6 +52,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
});
const openDomainImportManageDialog = useDomainImportManage();
const subdomainConfirmed = ref(false);
return {
crudOptions: {
settings: {
@@ -85,10 +87,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
fixed: "right",
},
form: {
beforeSubmit({ form }) {
async beforeSubmit({ form }) {
if (form.challengeType === "cname") {
throw new Error("CNAME方式请前往CNAME记录页面进行管理");
}
if (form.challengeType === "dns") {
const isSubdomain = await api.IsSubdomain({ domain: form.domain });
if (isSubdomain && !subdomainConfirmed.value) {
Modal.confirm({
title: "子域名确认",
content: `检测到${form.domain}为子域名,只有托管子域名和免费二级子域名才需要在此处维护,否则会导致申请证书失败,请确认是否继续?`,
okText: "确认",
okType: "danger",
onOk: () => {
subdomainConfirmed.value = true;
crudExpose.getFormWrapperRef().submit();
},
});
return false;
}
}
},
afterSubmit({ form }) {
subdomainConfirmed.value = false;
},
},
actionbar: {
@@ -128,6 +149,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, 2000);
},
},
monitorSettingSave: {
show: hasActionPermission("write"),
title: "域名过期监控设置",
type: "primary",
icon: "ion:save-outline",
text: "域名过期监控设置",
click: async () => {
router.push({
path: "/certd/cert/domain/setting",
});
},
},
},
},
columns: {
@@ -151,6 +184,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
form: {
required: true,
helper: "注意DNS校验方式下子域名不需要在此处维护否则会影响证书申请子域名托管或免费二级域名除外",
},
editForm: {
component: {

View File

@@ -0,0 +1,27 @@
// @ts-ignore
import { request } from "/src/api/service";
const apiPrefix = "/cert/domain/setting";
export type UserDomainMonitorSetting = {
enabled?: boolean;
notificationId?: number;
cron?: string;
willExpireDays?: number;
};
export async function DomainMonitorSettingsGet() {
const res = await request({
url: apiPrefix + "/get",
method: "post",
});
if (!res) {
return {};
}
return res as UserDomainMonitorSetting;
}
export async function DomainMonitorSettingsSave(data: UserDomainMonitorSetting) {
await request({
url: apiPrefix + "/save",
method: "post",
data: data,
});
}

View File

@@ -0,0 +1,100 @@
<template>
<fs-page class="page-user-settings page-domain-monitor-setting">
<template #header>
<div class="title">{{ t("monitor.setting.domain.monitorSettings") }}</div>
</template>
<div class="user-settings-form settings-form">
<a-form :model="formState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off">
<a-form-item :label="t('monitor.setting.domain.enabled')" :name="['enabled']">
<div class="flex flex-baseline">
<a-switch v-model:checked="formState.enabled" :disabled="!settingsStore.isPlus" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">{{ t("monitor.setting.domain.enabledHelper") }}</div>
</a-form-item>
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.notificationChannel')" :name="['notificationId']">
<div class="flex">
<NotificationSelector v-model="formState.notificationId" />
</div>
<div class="helper">{{ t("monitor.setting.domain.setNotificationChannel") }}</div>
</a-form-item>
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.willExpireDays')" :name="['willExpireDays']">
<div class="flex">
<a-input-number v-model:value="formState.willExpireDays" />
</div>
<div class="helper">{{ t("monitor.setting.domain.willExpireDaysHelper") }}</div>
</a-form-item>
<a-form-item v-if="formState.enabled" :label="t('monitor.setting.domain.monitorCronSetting')" :name="['cron']">
<div class="flex flex-baseline">
<cron-editor v-model="formState.cron" :disabled="!settingsStore.isPlus" :allow-every-min="userStore.isAdmin" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">{{ t("monitor.setting.domain.cronTrigger") }}</div>
</a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
<loading-button type="primary" html-type="button" :click="doSave" :disabled="!hasActionPermission('write')">{{ t("certd.save") }}</loading-button>
</a-form-item>
</a-form>
</div>
</fs-page>
</template>
<script setup lang="tsx">
import { notification } from "ant-design-vue";
import { merge } from "lodash-es";
import { reactive } from "vue";
import * as api from "./api";
import { UserDomainMonitorSetting } from "./api";
import { useUserStore } from "/@/store/user";
import { utils } from "/@/utils";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { useI18n } from "/src/locales";
import { useSettingStore } from "/src/store/settings";
import { useCrudPermission } from "/@/plugin/permission";
const { t } = useI18n();
const settingsStore = useSettingStore();
const userStore = useUserStore();
defineOptions({
name: "DomainMonitorSetting",
});
const randomHour = Math.floor(Math.random() * 9);
const randomMin = Math.floor(Math.random() * 59);
const randomCron = `0 ${randomMin} ${randomHour} * * *`;
const formState = reactive<Partial<UserDomainMonitorSetting>>({
enabled: false,
notificationId: 0,
cron: randomCron,
willExpireDays: 30,
});
async function loadUserSettings() {
const data: any = await api.DomainMonitorSettingsGet();
merge(formState, data);
}
const { hasActionPermission } = useCrudPermission({ permission: { isProjectPermission: true } });
loadUserSettings();
const doSave = async (form: any) => {
await utils.sleep(300);
await api.DomainMonitorSettingsSave({
...formState,
});
notification.success({
message: t("certd.saveSuccess"),
});
};
</script>
<style lang="less">
.page-domain-monitor-setting {
.settings-form {
width: 700px;
margin: 20px;
}
}
</style>

View File

@@ -66,7 +66,7 @@ export function useUserProfile() {
width: "auto",
},
buildUrl(key: string) {
return `api/basic/file/download?&key=` + key;
return `api/basic/file/download?token=${userStore.getToken}&key=` + key;
},
},
},
@@ -82,7 +82,7 @@ export function useUserProfile() {
onReady: null,
uploader: {
type: "form",
action: "/basic/file/upload",
action: "/basic/file/upload?token=" + userStore.getToken,
name: "file",
headers: {
Authorization: "Bearer " + userStore.getToken,
@@ -92,7 +92,7 @@ export function useUserProfile() {
},
},
buildUrl(key: string) {
return `api/basic/file/download?&key=` + key;
return `api/basic/file/download?token=${userStore.getToken}&key=` + key;
},
},
},

View File

@@ -147,6 +147,7 @@ import { isEmpty } from "lodash-es";
import { dict } from "@fast-crud/fast-crud";
import dayjs from "dayjs";
import { useRouter } from "vue-router";
import { useUserStore } from "/@/store/user";
const { t } = useI18n();
@@ -351,7 +352,7 @@ const checkPasskeySupport = () => {
passkeySupported.value = true;
}
};
const userStore = useUserStore();
const userAvatar = computed(() => {
if (isEmpty(userInfo.value.avatar)) {
return "";
@@ -360,7 +361,7 @@ const userAvatar = computed(() => {
return userInfo.value.avatar;
}
return "api/basic/file/download?&key=" + userInfo.value.avatar;
return `api/basic/file/download?token=${userStore.getToken}&key=${userInfo.value.avatar}`;
});
onMounted(async () => {

View File

@@ -0,0 +1,37 @@
import { request } from "/src/api/service";
const apiPrefix = "/monitor/job-history";
export const jobHistoryApi = {
async GetList(query: any) {
return await request({
url: apiPrefix + "/page",
method: "post",
data: query,
});
},
async DelObj(id: number) {
return await request({
url: apiPrefix + "/delete",
method: "post",
params: { id },
});
},
async BatchDelObj(ids: number[]) {
return await request({
url: apiPrefix + "/batchDelete",
method: "post",
data: { ids },
});
},
async GetObj(id: number) {
return await request({
url: apiPrefix + "/info",
method: "post",
params: { id },
});
},
};

View File

@@ -0,0 +1,257 @@
// @ts-ignore
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
import { message, Modal } from "ant-design-vue";
import { ref } from "vue";
import { createGroupDictRef } from "../../basic/group/api";
import { useDicts } from "../../dicts";
import { jobHistoryApi } from "./api";
import { useCrudPermission } from "/@/plugin/permission";
import { useProjectStore } from "/@/store/project";
import { useI18n } from "/src/locales";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { t } = useI18n();
const api = jobHistoryApi;
const { crudBinding } = crudExpose;
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
return await api.GetList(query);
};
const editRequest = async (req: EditReq) => {};
const delRequest = async (req: DelReq) => {
const { row } = req;
return await api.DelObj(row.id);
};
const addRequest = async (req: AddReq) => {};
const { myProjectDict } = useDicts();
const historyResultDict = dict({
data: [
{ label: t("monitor.history.jobResult.done"), value: "done", color: "green" },
{ label: t("monitor.history.jobResult.start"), value: "start", color: "blue" },
],
});
const jobTypeDict = dict({
data: [
{ label: t("monitor.history.jobType.domainExpirationCheck"), value: "domainExpirationCheck", color: "green" },
{ label: t("monitor.history.jobType.siteCertMonitor"), value: "siteCertMonitor", color: "blue" },
],
});
const selectedRowKeys = ref([]);
const handleBatchDelete = () => {
if (selectedRowKeys.value?.length > 0) {
Modal.confirm({
title: "确认",
content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`,
async onOk() {
await api.BatchDelObj(selectedRowKeys.value);
message.info("删除成功");
crudExpose.doRefresh();
selectedRowKeys.value = [];
},
});
} else {
message.error("请先勾选记录");
}
};
context.handleBatchDelete = handleBatchDelete;
const GroupTypeSite = "site";
const groupDictRef = createGroupDictRef(GroupTypeSite);
function getDefaultGroupId() {
const searchFrom = crudExpose.getSearchValidatedFormData();
if (searchFrom.groupId) {
return searchFrom.groupId;
}
}
const projectStore = useProjectStore();
const { hasActionPermission } = useCrudPermission({ permission: context.permission });
return {
id: "jobHistoryCrud",
crudOptions: {
request: {
pageRequest,
addRequest,
editRequest,
delRequest,
},
// tabs: {
// name: "groupId",
// show: true,
// },
toolbar: {
buttons: {
export: {
show: true,
},
},
},
pagination: {
pageSizeOptions: ["10", "20", "50", "100", "200"],
},
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
props: {
multiple: true,
crossPage: false,
selectedRowKeys: () => {
return selectedRowKeys;
},
},
},
},
},
form: {
labelCol: {
//固定label宽度
span: null,
style: {
width: "100px",
},
},
col: {
span: 22,
},
wrapper: {
width: 600,
},
},
actionbar: {
buttons: {
add: {
show: false,
},
},
},
rowHandle: {
fixed: "right",
width: 280,
buttons: {
edit: {
show: false,
},
},
},
// tabs: {
// name: "disabled",
// show: true,
// },
search: {
initialForm: {
...projectStore.getSearchForm(),
},
},
columns: {
id: {
title: "ID",
key: "id",
type: "number",
search: {
show: false,
},
column: {
width: 80,
align: "center",
},
form: {
show: false,
},
},
type: {
title: t("monitor.history.jobTypeTitle"),
search: {
show: true,
},
type: "dict-select",
dict: jobTypeDict,
form: {
show: false,
},
column: {
width: 120,
},
},
title: {
title: t("monitor.history.titleTitle"),
search: {
show: true,
},
type: "text",
column: {
width: 200,
},
},
content: {
title: t("monitor.history.contentTitle"),
search: {
show: true,
},
type: "text",
column: {
width: 460,
ellipsis: true,
},
},
result: {
title: t("monitor.history.resultTitle"),
search: {
show: false,
},
type: "dict-select",
dict: historyResultDict,
form: {
show: false,
},
column: {
width: 100,
align: "center",
sorter: true,
cellRender({ value, row }) {
return (
<a-tooltip title={row.error}>
<fs-values-format v-model={value} dict={historyResultDict}></fs-values-format>
</a-tooltip>
);
},
},
},
startAt: {
title: t("monitor.history.startAtTitle"),
search: {
show: true,
},
type: "datetime",
column: {
width: 160,
},
},
endAt: {
title: t("monitor.history.endAtTitle"),
search: {
show: true,
},
type: "datetime",
column: {
width: 160,
},
},
projectId: {
title: t("certd.fields.projectName"),
type: "dict-select",
dict: myProjectDict,
form: {
show: false,
},
},
},
},
};
}

View File

@@ -0,0 +1,48 @@
<template>
<fs-page>
<template #header>
<div class="title flex items-center">
{{ t("monitor.history.title") }}
<div class="sub flex-1">
<div>
{{ t("monitor.history.description") }}
</div>
</div>
</div>
</template>
<fs-crud ref="crudRef" v-bind="crudBinding">
<template #pagination-left>
<a-tooltip title="批量删除">
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
</a-tooltip>
</template>
</fs-crud>
</fs-page>
</template>
<script lang="ts" setup>
import { useFs } from "@fast-crud/fast-crud";
import { onActivated, onMounted } from "vue";
import createCrudOptions from "./crud";
import { useI18n } from "/src/locales";
const { t } = useI18n();
defineOptions({
name: "JobHistory",
});
const context: any = {
permission: {
isProjectPermission: true,
},
};
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
const handleBatchDelete = context.handleBatchDelete;
// 页面打开后获取列表数据
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
</script>

View File

@@ -346,12 +346,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
width: 230,
sorter: true,
cellRender({ value, row }) {
const url = `https://${value}:${row.httpsPort}`;
const domainPort = value + ":" + row.httpsPort;
const url = `https://${domainPort}`;
return (
<a-tooltip title={value} placement="left">
<fs-copyable modelValue={value}>
<a-tooltip title={domainPort} placement="left">
<fs-copyable modelValue={domainPort} title={domainPort}>
<a target="_blank" href={url}>
{value}:{row.httpsPort}
{domainPort}
</a>
</fs-copyable>
</a-tooltip>

View File

@@ -42,6 +42,7 @@ import createCrudOptions from "../crud";
import { notificationProvide } from "/@/views/certd/notification/common";
import { useUserStore } from "/@/store/user";
import { useI18n } from "/src/locales";
import { useProjectStore } from "/@/store/project";
const { t } = useI18n();
@@ -127,14 +128,27 @@ function clear() {
}
const userStore = useUserStore();
const projectStore = useProjectStore();
async function emitValue(value: any) {
// target.value = optionsDictRef.dataMap[value];
const userId = userStore.userInfo.id;
if (pipeline?.value && pipeline.value.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
const isEnterprice = projectStore.isEnterprise;
if (pipeline?.value) {
if (isEnterprice) {
const projectId = projectStore.currentProjectId;
if (pipeline?.value?.projectId !== projectId) {
message.error("对不起,您不能修改其他项目流水线的通知");
return;
}
} else {
if (pipeline?.value?.userId !== userId) {
message.error("对不起,您不能修改他人流水线的通知");
return;
}
}
}
emit("change", value);
emit("update:modelValue", value);
}

View File

@@ -24,6 +24,9 @@
</a-tabs>
<template #footer>
<fs-button v-if="settingsStore.sysPublic.aiChatEnabled !== false" key="aiChat" :tooltip="{ title: 'AI分析异常' }" type="primary" icon="ion:color-wand-outline" @click="taskModal.onAiChat">AI分析</fs-button>
<!-- <fs-button v-if="!settingsStore.isComm && currentStatus === 'error'" key="1v1" :tooltip="{ title: '升级专业版,获得一对一分析服务,为您排忧解难' }" class="isPlus" icon="imingcute:vip-1-line" @click="callService">
呼叫专家
</fs-button> -->
<fs-button key="rerun" type="primary" :tooltip="{ title: '强制重新执行此步骤' }" text="重新运行" icon="icon-park-outline:replay-music" @click="triggerRun(activeKey)"></fs-button>
<fs-button key="downloadLogs" type="primary" :tooltip="{ title: '当前任务日志下载' }" icon="ion:arrow-down-circle-outline" @click="taskModal.onDownloadLogs">下载日志</fs-button>
<fs-button key="cancel" :tooltip="{ title: '关闭窗口' }" icon="ion:close-circle-outline" @click="taskModal.onOk">关闭</fs-button>
@@ -39,6 +42,7 @@ import PiStatusShow from "/@/views/certd/pipeline/pipeline/component/status-show
import { usePreferences } from "/@/vben/preferences";
import { useSettingStore } from "/@/store/settings/index";
import { notification } from "ant-design-vue";
import { mitter } from "/@/utils/util.mitt";
export default {
name: "PiTaskView",
components: { PiStatusShow },
@@ -196,6 +200,19 @@ export default {
taskModal.value.open = false;
}
const currentNode = computed(() => {
return detail.value?.nodes?.find(item => item.node.id === activeKey.value);
});
const currentStatus = computed(() => {
return currentNode.value?.node?.status?.result || "";
});
function callService() {
if (!settingsStore.isPlus) {
mitter.emit("openVipModal");
}
}
const settingsStore = useSettingStore();
return {
detail,
@@ -206,6 +223,9 @@ export default {
tabPosition,
triggerRun,
settingsStore,
currentNode,
currentStatus,
callService,
};
},
};

View File

@@ -334,7 +334,7 @@ import { useCertViewer } from "/@/views/certd/pipeline/use";
import { useI18n } from "/@/locales";
import TriggerIcon from "./component/trigger-icon.vue";
import { useCrudPermission } from "/@/plugin/permission";
import { onBeforeRouteLeave } from "vue-router";
export default defineComponent({
name: "PipelineEdit",
// eslint-disable-next-line vue/no-unused-components
@@ -372,10 +372,22 @@ export default defineComponent({
},
emits: ["update:modelValue", "update:editMode"],
setup(props, ctx) {
onBeforeRouteLeave((to, from) => {
const newPipelineStr = JSON.stringify(pipeline.value || {});
if (pipelineOriginStr.value && pipelineOriginStr.value !== newPipelineStr) {
const answer = window.confirm("流水线还未保存,确定要离开吗?");
if (!answer) {
return false; // 返回 false 即可阻止本次路由跳转
}
return true;
}
});
const { t } = useI18n();
//右侧选中的pipeline
const currentPipeline: Ref<any> = ref({});
const pipeline: Ref<any> = ref({});
const pipelineOriginStr = ref("");
const pipelineDetail: Ref<any> = ref({});
const histories: Ref<RunHistory[]> = ref([]);
@@ -423,6 +435,7 @@ export default defineComponent({
await loadCurrentHistoryDetail();
pipeline.value = currentHistory.value.pipeline;
currentPipeline.value = currentHistory.value.pipeline;
pipelineOriginStr.value = JSON.stringify(pipeline.value);
};
async function loadHistoryList(reload = false, historyId: number = null) {
@@ -880,6 +893,7 @@ export default defineComponent({
pipeline.value.version = version;
currentPipeline.value.version = version;
}
pipelineOriginStr.value = JSON.stringify(pipeline.value);
}
if (offEdit) {
toggleEditMode(false);

View File

@@ -118,6 +118,10 @@ export function useTransfer() {
<div class="text-2xl font-bold"> </div>
<div>"{projectStore.currentProject?.name}"</div>
</div>
<div class="text-center m-4">
<p class="text-red-500"></p>
</div>
<div class="flex flex-row items-center justify-center w-full">
<a-button type="primary" onClick={doTransfer}>

View File

@@ -3,7 +3,7 @@
<div class="header-profile flex-wrap bg-white dark:bg-black">
<div class="flex flex-1">
<div class="avatar">
<a-avatar v-if="userInfo.avatar" size="large" :src="'api/basic/file/download?&key=' + userInfo.avatar" style="background-color: #eee"> </a-avatar>
<a-avatar v-if="userInfo.avatar" size="large" :src="avatar" style="background-color: #eee"> </a-avatar>
<a-avatar v-else size="large" style="background-color: #00b4f5">
{{ userInfo.username }}
</a-avatar>
@@ -228,10 +228,24 @@ const userStore = useUserStore();
const userInfo: ComputedRef<UserInfoRes> = computed(() => {
return userStore.getUserInfo;
});
const avatar = computed(() => {
const avt = userStore.getUserInfo?.avatar;
if (!avt) {
return "";
}
if (avt.startsWith("http")) {
return avt;
}
return `/api/basic/file/download?key=${avt}`;
});
const dateNow = ref(Date.now());
const now = computed(() => {
const serverTime = Date.now() - settingStore.app.deltaTime;
const serverTime = dateNow.value - settingStore.app.deltaTime;
return dayjs(serverTime).format("YYYY-MM-DD HH:mm:ss");
});
setInterval(() => {
dateNow.value = Date.now();
}, 5000);
const deltaTimeWarning = computed(() => {
return Math.abs(settingStore.app.deltaTime) > 1000 * 60 * 4;

View File

@@ -172,7 +172,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
width: "auto",
},
buildUrl(key: string) {
return `api/basic/file/download?&key=` + key;
return `api/basic/file/download?token=${userStore.getToken}&key=` + key;
},
},
},
@@ -188,7 +188,7 @@ export default function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOpti
onReady: null,
uploader: {
type: "form",
action: "/basic/file/upload",
action: "/basic/file/upload?token=" + userStore.getToken,
name: "file",
headers: {
Authorization: "Bearer " + userStore.getToken,

View File

@@ -9,6 +9,7 @@ import yaml from "js-yaml";
import { usePluginImport } from "./use-import";
import { usePluginConfig } from "./use-config";
import { useSettingStore } from "/src/store/settings/index";
import { usePluginStore } from "/@/store/plugin";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
@@ -43,6 +44,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
const { openConfigDialog } = usePluginConfig();
const settingStore = useSettingStore();
const pluginStore = usePluginStore();
return {
crudOptions: {
settings: {
@@ -83,6 +85,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
},
table: {
rowKey: "name",
remove: {
afterRemove: async context => {
await pluginStore.reload();
},
confirmMessage: "确定要删除吗?如果该插件已被使用,删除可能会导致流水线执行失败!",
},
},
rowHandle: {
show: true,
minWidth: 200,
@@ -142,9 +153,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
},
},
table: {
rowKey: "name",
},
tabs: {
name: "type",
show: true,

View File

@@ -29,7 +29,7 @@ import { SysSettings } from "/@/views/sys/settings/api";
import * as api from "/@/views/sys/settings/api";
import { merge } from "lodash-es";
import { useSettingStore } from "/@/store/settings";
import { notification } from "ant-design-vue";
import { Modal, notification } from "ant-design-vue";
import { useI18n } from "/src/locales";
import { dict } from "@fast-crud/fast-crud";
import { useProjectStore } from "/@/store/project";
@@ -80,6 +80,24 @@ const onFinish = async (form: any) => {
notification.success({
message: t("certd.saveSuccess"),
});
if (formState.public.adminMode === "enterprise") {
Modal.confirm({
title: "数据迁移",
okText: "去迁移",
content: () => {
return (
<div>
<div>设置为企业模式之后之前创建的个人数据不会显示</div>
<div>是否前往迁移数据到项目? </div>
</div>
);
},
onOk: () => {
goCurrentProject();
},
});
}
} finally {
saveLoading.value = false;
}

View File

@@ -2,7 +2,7 @@
import { request } from "/src/api/service";
const apiPrefix = "/sys/site";
export async function SettingsGet(key: string) {
export async function SettingsGet() {
return await request({
url: apiPrefix + "/get",
method: "post",

View File

@@ -88,7 +88,7 @@ const onFinish = async (form: any) => {
const userStore = useUserStore();
const uploaderConfig = ref({
type: "form",
action: "/basic/file/upload",
action: "/basic/file/upload?token=" + userStore.getToken,
name: "file",
headers: {
Authorization: "Bearer " + userStore.getToken,

View File

@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.39.10](https://github.com/certd/certd/compare/v1.39.9...v1.39.10) (2026-04-11)
### Bug Fixes
* 修复自定义插件删除后没有反注册的bug ([df98463](https://github.com/certd/certd/commit/df9846332596d2afaba53e66d2897aa1c598f9c4))
* 修复spaceship创建record报错的bug ([70b46d4](https://github.com/certd/certd/commit/70b46d4a8f89cf8eded21ebb237e8c8ce6c40d30))
### Performance Improvements
* 1panel支持先上传证书再选择证书 ([7a9eec8](https://github.com/certd/certd/commit/7a9eec88e8eddf40dba055c072b5b2b0f67c1407))
* 部署到1panel面板支持mux模式 ([d05129e](https://github.com/certd/certd/commit/d05129ec67893b0b639003a4bca6878d128f56ad))
* 流水线修改编辑之后,增加未保存提示 ([21620ac](https://github.com/certd/certd/commit/21620ac6bdeb57e43509156a77037fc07c44282a))
* 修复检查全部某些情况下无效的bug优化公共触发站点证书检查定时逻辑 ([ee53589](https://github.com/certd/certd/commit/ee535895a3166c6f9046963e28fa8f22f018b574))
* 增加域名管理 子域名检查提醒 ([2bdf183](https://github.com/certd/certd/commit/2bdf1832da73a3728f3ac415837bc26e70531cd6))
## [1.39.9](https://github.com/certd/certd/compare/v1.39.8...v1.39.9) (2026-04-05)
### Bug Fixes
* 修复cn域名获取不到到期时间的问题 ([73b8e85](https://github.com/certd/certd/commit/73b8e859766097b5251fc4e5051593d686669eb2))
### Performance Improvements
* 腾讯云CLB大区增加台北 ([6b109d1](https://github.com/certd/certd/commit/6b109d172f0c7b6ce6ec164dc196d646a65f529f))
* 优化腾讯云CLB插件支持选择证书id ([c875971](https://github.com/certd/certd/commit/c875971b71dc6d392e56f0a7605281c40d9bb405))
* 支持域名到期时间监控通知 ([c6628e7](https://github.com/certd/certd/commit/c6628e7311d6c43c2a784581fb25ec37b29c168d))
* **monitor:** 支持查看监控执行记录 ([b5cc794](https://github.com/certd/certd/commit/b5cc794061c11b7200b669473c25c4bbfc944b61))
* **plugin-dnsmgr:** 添加彩虹DNS插件支持 ([af50344](https://github.com/certd/certd/commit/af503442b8298c5b89d11cf2ea351d62e66a609e))
* **spaceship:** 新增Spaceship DNS插件和授权模块 ([21aec77](https://github.com/certd/certd/commit/21aec77e5c3307b5973d4185baba33edcb28926f))
## [1.39.8](https://github.com/certd/certd/compare/v1.39.7...v1.39.8) (2026-03-31)
### Bug Fixes
* 修复某些情况下报没有匹配到任何校验方式的bug ([fe02ce7](https://github.com/certd/certd/commit/fe02ce7b64cf23c4dc4c30daccd5330059a35e9a))
### Performance Improvements
* 阿里云CDN部署支持根据证书域名自动匹配部署 ([a68301e](https://github.com/certd/certd/commit/a68301e4dcea8b7391ad751aa57555d566297ad9))
* 阿里云dcdn支持根据证书域名匹配模式 ([df012de](https://github.com/certd/certd/commit/df012dec90590ecba85a69ed6355cfa8382c1da3))
* 支持部署证书到百度CCE ([a19ea74](https://github.com/certd/certd/commit/a19ea7489c01cdbf795fb51f804bd6d00389f604))
* dcdn自动匹配部署支持新增域名感知 ([c6a988b](https://github.com/certd/certd/commit/c6a988bc925886bd7163c1270f2b7a10a57b1c5b))
## [1.39.7](https://github.com/certd/certd/compare/v1.39.6...v1.39.7) (2026-03-25)
### Bug Fixes

View File

@@ -0,0 +1,24 @@
CREATE TABLE `cd_job_history`
(
`id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL,
`user_id` bigint NOT NULL,
`project_id` bigint ,
`type` varchar(100) NOT NULL,
`title` varchar(512) NOT NULL,
`related_id` varchar(100),
`result` varchar(100) NOT NULL,
`content` longtext ,
`start_at` bigint NOT NULL,
`end_at` bigint ,
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX `index_job_history_user_id` ON `cd_job_history` (`user_id`);
CREATE INDEX `index_job_history_project_id` ON `cd_job_history` (`project_id`);
CREATE INDEX `index_job_history_type` ON `cd_job_history` (`type`);
ALTER TABLE `cd_job_history` ENGINE = InnoDB;

Some files were not shown because too many files have changed in this diff Show More