@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>
评论加载中...