@model Dpz.Core.Web.Jobs.Hangfire.Build.SshConfiguration?
@using System.Text.Json
@{
Layout = null;
var problems = (ViewData["ConfigProblems"] as List<string>) ?? new List<string>();
object? clientConfig = null;
if (Model != null)
{
clientConfig = new
{
build = new { host = Model.Build.Host, workspace = Model.Build.Workspace },
registry = Model.Registry,
servers =
Model.Servers?.Select(s => new
{
s.Key,
s.Value.Host,
Project = (int)s.Value.Project,
}).ToList() ?? [],
};
}
var clientConfigJson = JsonSerializer.Serialize(clientConfig);
}
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="dark" />
<title>发布流水线 - Dpz Jobs</title>
<link rel="stylesheet" href="https://dpangzi.com/library/fontawesome/css/all.min.css" />
<link rel="stylesheet" href="~/styles/pipeline.css" asp-append-version="true" />
</head>
<body class="pipeline-page">
<header class="pipeline-topbar">
<a class="pipeline-topbar__brand" asp-controller="Pipeline" asp-action="Index">
<span class="pipeline-topbar__mark" aria-hidden="true">
<i class="fa-solid fa-rocket"></i>
</span>
<span>
<span class="pipeline-topbar__name">发布流水线</span>
<span class="pipeline-topbar__subtitle">CI/CD Pipeline</span>
</span>
</a>
<nav class="pipeline-topbar__nav" aria-label="站点导航">
<a class="pipeline-topbar__link" asp-controller="Home" asp-action="Index">
<i class="fa-solid fa-house" aria-hidden="true"></i>
作业中心
</a>
<a class="pipeline-topbar__link" asp-controller="FileTransfer" asp-action="Send">
<i class="fa-solid fa-arrows-up-down-left-right" aria-hidden="true"></i>
文件传输
</a>
<a class="pipeline-topbar__link" href="@Program.ConsoleUrl">
<i class="fa-solid fa-gauge-high" aria-hidden="true"></i>
仪表盘
</a>
</nav>
</header>
<main class="pipeline-shell">
@if (problems.Count == 0 && Model != null)
{
<section class="pipeline-health pipeline-health--ok" data-health="ok">
<span class="pipeline-health__icon" aria-hidden="true">
<i class="fa-solid fa-circle-check"></i>
</span>
<div class="pipeline-health__body">
<strong class="pipeline-health__title">SSH 配置正常</strong>
<p class="pipeline-health__meta">
构建机 <b>@Model.Build.Host</b> · 工作目录 <b>@Model.Build.Workspace</b> ·
仓库 <b>@Model.Registry</b> · 部署服务器 <b>@(Model.Servers?.Count ?? 0)</b> 台
</p>
</div>
<button class="pipeline-button pipeline-button--ghost" type="button" data-reload>
<i class="fa-solid fa-rotate" aria-hidden="true"></i>
重新检查
</button>
</section>
}
else
{
<section class="pipeline-health pipeline-health--warn" data-health="warn">
<span class="pipeline-health__icon" aria-hidden="true">
<i class="fa-solid fa-triangle-exclamation"></i>
</span>
<div class="pipeline-health__body">
<strong class="pipeline-health__title">SSH 配置无效,发布功能已停用</strong>
<p class="pipeline-health__meta">
请检查配置中心 <b>ssh</b> 节点,修复以下问题后刷新页面重新检查:
</p>
<ul class="pipeline-health__list">
@foreach (var problem in problems)
{
<li>@problem</li>
}
</ul>
</div>
<button class="pipeline-button pipeline-button--ghost" type="button" data-reload>
<i class="fa-solid fa-rotate" aria-hidden="true"></i>
重新检查
</button>
</section>
}
<div class="pipeline-grid">
<form class="pipeline-card pipeline-control" id="run-form">
<div class="pipeline-card__header">
<p class="pipeline-card__eyebrow">发布控制</p>
<h2 class="pipeline-card__title">
<i class="fa-solid fa-bolt" aria-hidden="true"></i>
启动一次发布
</h2>
</div>
<fieldset class="pipeline-field">
<legend class="pipeline-field__legend">部署项目</legend>
<div class="pipeline-projects" role="group" aria-label="部署项目">
<label
class="pipeline-project pipeline-project--core is-active"
role="checkbox"
aria-checked="true"
tabindex="0"
data-project-toggle
>
<input class="pipeline-project__input" type="checkbox" name="Project" value="2" checked />
<i class="fa-solid fa-globe" aria-hidden="true"></i>
<span>Dpz.Core</span>
<small>主站</small>
</label>
<label
class="pipeline-project pipeline-project--api is-active"
role="checkbox"
aria-checked="true"
tabindex="0"
data-project-toggle
>
<input class="pipeline-project__input" type="checkbox" name="Project" value="4" checked />
<i class="fa-solid fa-plug" aria-hidden="true"></i>
<span>Dpz.WebApi</span>
<small>REST API</small>
</label>
<label
class="pipeline-project pipeline-project--auth is-active"
role="checkbox"
aria-checked="true"
tabindex="0"
data-project-toggle
>
<input class="pipeline-project__input" type="checkbox" name="Project" value="8" checked />
<i class="fa-solid fa-shield-halved" aria-hidden="true"></i>
<span>Dpz.Core.Auth</span>
<small>SSO 认证</small>
</label>
</div>
</fieldset>
<fieldset class="pipeline-field">
<legend class="pipeline-field__legend">版本策略</legend>
<div class="pipeline-version">
<label class="pipeline-version__option">
<input type="radio" name="versionStrategy" value="auto" checked data-version-auto />
<span>
<i class="fa-solid fa-wand-magic-sparkles" aria-hidden="true"></i>
自动版本
</span>
<small>从构建机 csproj 读取最新版本号</small>
</label>
<label class="pipeline-version__option">
<input type="radio" name="versionStrategy" value="manual" data-version-manual />
<span>
<i class="fa-solid fa-hashtag" aria-hidden="true"></i>
手动指定
</span>
<small>为所有项目使用同一 Tag,例如 1.2.3</small>
</label>
<input
class="pipeline-version__tag"
type="text"
name="Tag"
placeholder="输入版本号,如 1.2.3"
data-tag-input
aria-label="指定版本号"
/>
</div>
</fieldset>
<div class="pipeline-control__footer">
<p class="pipeline-control__hint" data-run-hint>
<i class="fa-solid fa-circle-info" aria-hidden="true"></i>
当前没有进行中的构建,可以安全启动
</p>
<button
class="pipeline-button pipeline-button--primary pipeline-button--run"
type="submit"
data-run-button
>
<i class="fa-solid fa-play" aria-hidden="true"></i>
启动发布
</button>
</div>
@Html.AntiForgeryToken()
</form>
<section class="pipeline-card pipeline-overview" aria-labelledby="overview-title">
<div class="pipeline-card__header">
<div>
<p class="pipeline-card__eyebrow">运行总览</p>
<h2 class="pipeline-card__title" id="overview-title">
<i class="fa-solid fa-chart-line" aria-hidden="true"></i>
实时状态
</h2>
</div>
<span class="pipeline-status" data-status>空闲</span>
</div>
<ol class="pipeline-stages" data-stages>
<li class="pipeline-stage" data-stage="1">
<span class="pipeline-stage__index">1</span>
<span class="pipeline-stage__label">拉取代码</span>
</li>
<li class="pipeline-stage" data-stage="2">
<span class="pipeline-stage__index">2</span>
<span class="pipeline-stage__label">构建镜像</span>
</li>
<li class="pipeline-stage" data-stage="3">
<span class="pipeline-stage__index">3</span>
<span class="pipeline-stage__label">部署发布</span>
</li>
<li class="pipeline-stage" data-stage="4">
<span class="pipeline-stage__index">4</span>
<span class="pipeline-stage__label">完成</span>
</li>
</ol>
<div class="pipeline-meta">
<div class="pipeline-meta__row">
<span class="pipeline-meta__label">RUN ID</span>
<span class="pipeline-meta__run" data-run-id>----</span>
<button class="pipeline-icon-button" type="button" data-copy-run title="复制 RUN ID">
<i class="fa-regular fa-copy" aria-hidden="true"></i>
</button>
</div>
<div class="pipeline-meta__grid">
<div class="pipeline-meta__item">
<span class="pipeline-meta__label">已选项目</span>
<div class="pipeline-meta__chips" data-overview-projects></div>
</div>
<div class="pipeline-meta__item">
<span class="pipeline-meta__label">启动时间</span>
<strong data-started>--:--:--</strong>
</div>
<div class="pipeline-meta__item">
<span class="pipeline-meta__label">已运行</span>
<strong data-elapsed>00:00</strong>
</div>
</div>
</div>
</section>
</div>
<section class="pipeline-card pipeline-logs" aria-labelledby="logs-title">
<div class="pipeline-card__header pipeline-logs__header">
<div>
<p class="pipeline-card__eyebrow">实时输出</p>
<h2 class="pipeline-card__title" id="logs-title">
<i class="fa-solid fa-terminal" aria-hidden="true"></i>
构建日志
</h2>
</div>
<div class="pipeline-logs__tools">
<label class="pipeline-switch">
<input type="checkbox" checked data-autoscroll />
<span class="pipeline-switch__track" aria-hidden="true"></span>
自动滚动
</label>
<button class="pipeline-button pipeline-button--ghost" type="button" data-copy-log>
<i class="fa-regular fa-copy" aria-hidden="true"></i>
复制日志
</button>
<button class="pipeline-button pipeline-button--ghost" type="button" data-clear-log>
<i class="fa-solid fa-eraser" aria-hidden="true"></i>
清空
</button>
</div>
</div>
<nav class="pipeline-servers" data-server-tabs aria-label="服务器"></nav>
<nav class="pipeline-chips" data-project-tabs aria-label="项目"></nav>
<div class="pipeline-terminal" data-panes>
<div class="pipeline-terminal__empty">
<i class="fa-solid fa-tower-broadcast" aria-hidden="true"></i>
<strong data-empty-title>等待发布任务</strong>
<p data-empty-text>选择项目并点击「启动发布」,终端输出将实时显示在这里</p>
</div>
</div>
</section>
<section class="pipeline-card pipeline-history" aria-labelledby="history-title">
<div class="pipeline-card__header pipeline-history__header">
<div>
<p class="pipeline-card__eyebrow">只读审计</p>
<h2 class="pipeline-card__title" id="history-title">
<i class="fa-solid fa-clock-rotate-left" aria-hidden="true"></i>
历史记录
</h2>
</div>
<button class="pipeline-button pipeline-button--ghost" type="button" data-history-refresh>
<i class="fa-solid fa-rotate" aria-hidden="true"></i>
刷新
</button>
</div>
<div class="pipeline-history__summary" data-history-summary aria-live="polite">正在读取最近的 CI/CD 记录…</div>
<div class="pipeline-history__list" data-history-list>
<div class="pipeline-history__empty">
<i class="fa-solid fa-hourglass-start" aria-hidden="true"></i>
<strong>正在加载历史记录</strong>
</div>
</div>
</section>
</main>
<div class="pipeline-drawer" data-history-drawer hidden>
<button class="pipeline-drawer__backdrop" type="button" data-history-close aria-label="关闭历史详情"></button>
<aside class="pipeline-drawer__panel" role="dialog" aria-modal="true" aria-labelledby="history-detail-title">
<header class="pipeline-drawer__header">
<div>
<p class="pipeline-card__eyebrow">执行快照 · 只读</p>
<h2 class="pipeline-card__title" id="history-detail-title">历史详情</h2>
</div>
<button class="pipeline-icon-button" type="button" data-history-close title="关闭详情">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
</button>
</header>
<div class="pipeline-drawer__body">
<div class="pipeline-history-detail" data-history-detail>
<div class="pipeline-history__empty">
<i class="fa-solid fa-circle-notch fa-spin" aria-hidden="true"></i>
<strong>正在读取详情</strong>
</div>
</div>
</div>
</aside>
</div>
<div class="pipeline-confirm" data-version-confirm hidden>
<div class="pipeline-confirm__backdrop" aria-hidden="true"></div>
<div
class="pipeline-confirm__panel"
role="dialog"
aria-modal="true"
aria-labelledby="version-confirm-title"
aria-describedby="version-confirm-lead"
>
@Html.AntiForgeryToken()
<header class="pipeline-confirm__header">
<span class="pipeline-confirm__icon" aria-hidden="true">
<i class="fa-solid fa-triangle-exclamation"></i>
</span>
<div>
<p class="pipeline-card__eyebrow">二次确认</p>
<h2 class="pipeline-card__title" id="version-confirm-title">即将发布的版本与线上一致</h2>
</div>
</header>
<p class="pipeline-confirm__lead" id="version-confirm-lead">
以下项目当前运行版本与本次即将发布的版本相同。确认后才会继续构建和部署。
</p>
<ul class="pipeline-confirm__list" data-version-matches></ul>
<p class="pipeline-confirm__timeout" data-version-timeout></p>
<div class="pipeline-confirm__actions">
<button class="pipeline-button pipeline-button--ghost" type="button" data-version-cancel>
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
取消发布
</button>
<button class="pipeline-button pipeline-button--primary" type="button" data-version-continue>
<i class="fa-solid fa-play" aria-hidden="true"></i>
确认继续
</button>
</div>
</div>
</div>
<script id="pipeline-config" type="application/json">@Html.Raw(clientConfigJson)</script>
<script src="~/lib/signalr/signalr.min.js" asp-append-version="true"></script>
<script type="module" src="~/scripts/pipeline/page.js" asp-append-version="true"></script>
</body>
</html>
AI 正在分析代码…
评论加载中...