mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:58:14 +03:00
be builders
This commit is contained in:
@@ -1,6 +1,65 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
var base = document.body.getAttribute('data-base-path') || '';
|
var base = document.body.getAttribute('data-base-path') || '';
|
||||||
|
|
||||||
|
function buildStatusIconHtml(status) {
|
||||||
|
var s = String(status || '').toLowerCase();
|
||||||
|
if (s === 'running' || s === 'queued') {
|
||||||
|
return '<span class="build-status-icon build-status-icon--running" role="img" aria-label="In progress">'
|
||||||
|
+ '<span class="build-status-spinner"></span></span>';
|
||||||
|
}
|
||||||
|
if (s === 'passed') {
|
||||||
|
return '<span class="build-status-icon build-status-icon--passed" role="img" aria-label="Succeeded"></span>';
|
||||||
|
}
|
||||||
|
return '<span class="build-status-icon build-status-icon--failed" role="img" aria-label="Failed or cancelled">'
|
||||||
|
+ '<span class="build-status-fail-mark" aria-hidden="true">!</span></span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBuildStatusCell(cell, status, phase) {
|
||||||
|
if (!cell) return;
|
||||||
|
var icon = cell.querySelector('.build-status-icon');
|
||||||
|
var label = cell.querySelector('.build-status-label');
|
||||||
|
var html = buildStatusIconHtml(status);
|
||||||
|
if (icon) {
|
||||||
|
icon.outerHTML = html;
|
||||||
|
} else {
|
||||||
|
cell.insertAdjacentHTML('afterbegin', html);
|
||||||
|
}
|
||||||
|
if (label) {
|
||||||
|
label.textContent = status || '';
|
||||||
|
}
|
||||||
|
if (phase !== undefined) {
|
||||||
|
var row = cell.closest('tr');
|
||||||
|
if (row) {
|
||||||
|
var phaseCell = row.querySelector('.build-phase-cell');
|
||||||
|
if (phaseCell) {
|
||||||
|
phaseCell.textContent = phase || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshRecentBuilds() {
|
||||||
|
var tbody = document.getElementById('builds-recent-tbody');
|
||||||
|
if (!tbody) return;
|
||||||
|
fetch(base + '/api/builds.php', { credentials: 'same-origin' })
|
||||||
|
.then(function (r) { return r.json(); })
|
||||||
|
.then(function (j) {
|
||||||
|
if (!j.ok || !j.builds) return;
|
||||||
|
j.builds.forEach(function (b) {
|
||||||
|
var cell = tbody.querySelector('[data-build-status-cell="' + b.id + '"]');
|
||||||
|
updateBuildStatusCell(cell, b.status, b.phase);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function () {})
|
||||||
|
.then(function () {
|
||||||
|
setTimeout(refreshRecentBuilds, 4000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.body.getAttribute('data-view') === 'home' && document.getElementById('builds-recent-tbody')) {
|
||||||
|
setTimeout(refreshRecentBuilds, 4000);
|
||||||
|
}
|
||||||
var themeSel = document.getElementById('theme-select');
|
var themeSel = document.getElementById('theme-select');
|
||||||
if (themeSel) {
|
if (themeSel) {
|
||||||
var cur = localStorage.getItem('crash_console_theme') || 'dark';
|
var cur = localStorage.getItem('crash_console_theme') || 'dark';
|
||||||
@@ -180,6 +239,22 @@
|
|||||||
offset = j.log.offset || offset;
|
offset = j.log.offset || offset;
|
||||||
renderLog();
|
renderLog();
|
||||||
}
|
}
|
||||||
|
if (j.build) {
|
||||||
|
var iconEl = document.getElementById('build-status-icon');
|
||||||
|
var textEl = document.getElementById('build-status-text');
|
||||||
|
if (iconEl) {
|
||||||
|
iconEl.innerHTML = buildStatusIconHtml(j.build.status);
|
||||||
|
}
|
||||||
|
if (textEl) {
|
||||||
|
var errEl = document.getElementById('build-error-message');
|
||||||
|
var err = j.build.error_message ? (' · <span class="error-text" id="build-error-message">'
|
||||||
|
+ String(j.build.error_message).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
+ '</span>') : '';
|
||||||
|
textEl.innerHTML = 'Status: <strong>' + (j.build.status || '') + '</strong> · Phase: '
|
||||||
|
+ (j.build.phase || '') + err;
|
||||||
|
}
|
||||||
|
document.body.setAttribute('data-build-status', j.build.status || '');
|
||||||
|
}
|
||||||
var running = j.build && j.build.status === 'running';
|
var running = j.build && j.build.status === 'running';
|
||||||
var eof = j.log && j.log.eof;
|
var eof = j.log && j.log.eof;
|
||||||
if (running || !eof) {
|
if (running || !eof) {
|
||||||
|
|||||||
@@ -54,3 +54,35 @@ function json_out(array $data, int $code = 200): void {
|
|||||||
function assets_base(): string {
|
function assets_base(): string {
|
||||||
return rtrim((string) cfg('links.crashes', '/app/androidcast_project/crashes/'), '/');
|
return rtrim((string) cfg('links.crashes', '/app/androidcast_project/crashes/'), '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return 'running'|'passed'|'failed' */
|
||||||
|
function build_status_kind(string $status): string {
|
||||||
|
$s = strtolower(trim($status));
|
||||||
|
if (in_array($s, ['running', 'queued'], true)) {
|
||||||
|
return 'running';
|
||||||
|
}
|
||||||
|
if ($s === 'passed') {
|
||||||
|
return 'passed';
|
||||||
|
}
|
||||||
|
return 'failed';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inline status glyph: spinner (in progress), green check, or red alert triangle. */
|
||||||
|
function build_status_icon(string $status): string {
|
||||||
|
$kind = build_status_kind($status);
|
||||||
|
$labels = [
|
||||||
|
'running' => 'In progress',
|
||||||
|
'passed' => 'Succeeded',
|
||||||
|
'failed' => 'Failed or cancelled',
|
||||||
|
];
|
||||||
|
$label = h($labels[$kind] ?? 'Unknown');
|
||||||
|
if ($kind === 'running') {
|
||||||
|
return '<span class="build-status-icon build-status-icon--running" role="img" aria-label="' . $label . '">'
|
||||||
|
. '<span class="build-status-spinner"></span></span>';
|
||||||
|
}
|
||||||
|
if ($kind === 'passed') {
|
||||||
|
return '<span class="build-status-icon build-status-icon--passed" role="img" aria-label="' . $label . '"></span>';
|
||||||
|
}
|
||||||
|
return '<span class="build-status-icon build-status-icon--failed" role="img" aria-label="' . $label . '">'
|
||||||
|
. '<span class="build-status-fail-mark" aria-hidden="true">!</span></span>';
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,10 +20,13 @@ $stepLabels = [
|
|||||||
];
|
];
|
||||||
?>
|
?>
|
||||||
<h1>Build <?= h($build['build_code'] ?? '') ?></h1>
|
<h1>Build <?= h($build['build_code'] ?? '') ?></h1>
|
||||||
<p class="muted">Status: <strong><?= h($status) ?></strong> · Phase: <?= h($phase) ?>
|
<p class="muted build-status-line" id="build-status-line">
|
||||||
|
<span id="build-status-icon"><?= build_status_icon($status) ?></span>
|
||||||
|
<span id="build-status-text">Status: <strong><?= h($status) ?></strong> · Phase: <?= h($phase) ?>
|
||||||
<?php if (!empty($build['error_message'])): ?>
|
<?php if (!empty($build['error_message'])): ?>
|
||||||
· <span class="error-text"><?= h($build['error_message']) ?></span>
|
· <span class="error-text" id="build-error-message"><?= h($build['error_message']) ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<?php
|
<?php
|
||||||
$healthDocker = ($health['docker'] ?? '') === 'ok';
|
$healthDocker = ($health['docker'] ?? '') === 'ok';
|
||||||
|
|||||||
@@ -51,12 +51,17 @@
|
|||||||
<th>Created</th>
|
<th>Created</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="builds-recent-tbody">
|
||||||
<?php foreach ($builds as $b): ?>
|
<?php foreach ($builds as $b): ?>
|
||||||
<tr>
|
<tr data-build-id="<?= (int) $b['id'] ?>">
|
||||||
<td><a href="<?= h(Auth::basePath()) ?>/?view=build&id=<?= (int) $b['id'] ?>"><?= h($b['build_code']) ?></a></td>
|
<td><a href="<?= h(Auth::basePath()) ?>/?view=build&id=<?= (int) $b['id'] ?>"><?= h($b['build_code']) ?></a></td>
|
||||||
<td><?= h($b['status']) ?></td>
|
<td>
|
||||||
<td><?= h($b['phase']) ?></td>
|
<span class="build-status-cell-inner" data-build-status-cell="<?= (int) $b['id'] ?>">
|
||||||
|
<?= build_status_icon((string) ($b['status'] ?? '')) ?>
|
||||||
|
<span class="build-status-label"><?= h($b['status']) ?></span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="build-phase-cell" data-build-phase-cell="<?= (int) $b['id'] ?>"><?= h($b['phase']) ?></td>
|
||||||
<td><?= h($b['git_ref'] ?? $b['branch'] ?? '—') ?></td>
|
<td><?= h($b['git_ref'] ?? $b['branch'] ?? '—') ?></td>
|
||||||
<td><?= h($b['ota_channel'] ?? '—') ?></td>
|
<td><?= h($b['ota_channel'] ?? '—') ?></td>
|
||||||
<td><?= h($b['created_at'] ?? '') ?></td>
|
<td><?= h($b['created_at'] ?? '') ?></td>
|
||||||
|
|||||||
@@ -1206,6 +1206,82 @@ button.report-tag--filter:hover {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Builder status glyphs (list + detail) */
|
||||||
|
.build-status-line,
|
||||||
|
.build-status-cell-inner {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.build-status-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.build-status-spinner {
|
||||||
|
display: block;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border: 2px solid rgba(61, 139, 253, .22);
|
||||||
|
border-top-color: var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: build-status-spin .85s linear infinite;
|
||||||
|
box-shadow: 0 0 10px rgba(61, 139, 253, .35);
|
||||||
|
}
|
||||||
|
[data-theme="light"] .build-status-spinner {
|
||||||
|
border-color: rgba(37, 99, 235, .18);
|
||||||
|
border-top-color: var(--accent);
|
||||||
|
box-shadow: 0 0 8px rgba(37, 99, 235, .28);
|
||||||
|
}
|
||||||
|
@keyframes build-status-spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
.build-status-icon--passed {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(145deg, #34d399, #16a34a);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(34, 197, 94, .45),
|
||||||
|
0 0 12px rgba(34, 197, 94, .4);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.build-status-icon--passed::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 5px;
|
||||||
|
top: 3px;
|
||||||
|
width: 4px;
|
||||||
|
height: 7px;
|
||||||
|
border: solid #fff;
|
||||||
|
border-width: 0 2px 2px 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.build-status-icon--failed {
|
||||||
|
width: 18px;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
clip-path: polygon(50% 0%, 6% 92%, 94% 92%);
|
||||||
|
background: linear-gradient(180deg, #fb7185, var(--danger));
|
||||||
|
box-shadow: 0 0 12px rgba(248, 113, 113, .45);
|
||||||
|
}
|
||||||
|
.build-status-fail-mark {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 1px 0 rgba(0, 0, 0, .25);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.tag-edit-btn {
|
.tag-edit-btn {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
|
|||||||
Reference in New Issue
Block a user