mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-05-15 06:30:26 +02:00
error logging improvement, regex fix
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+20
-1
@@ -4702,4 +4702,23 @@ textarea::selection {
|
||||
}
|
||||
.file.folder.truncated > a {
|
||||
color: #d39e00;
|
||||
}
|
||||
}
|
||||
/* Errors admin */
|
||||
.errors-table .error-when time { font-variant-numeric: tabular-nums; color: #555; cursor: help; }
|
||||
.errors-table .error-msg-line { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; }
|
||||
.errors-table .error-chip {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
font-size: 0.78rem; padding: 1px 6px; border-radius: 999px;
|
||||
background: #eef0f3; color: #333; border: 1px solid #dde0e4;
|
||||
max-width: 36em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.errors-table .error-chip .chip-label { color: #777; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.03em; }
|
||||
.errors-table .error-chip.chip-err { background: #fdecec; border-color: #f5c2c2; color: #8a1f1f; }
|
||||
.errors-table .error-chip.chip-warn { background: #fff5e1; border-color: #f3d9a4; color: #7a4d00; }
|
||||
.errors-table .error-chip.chip-ok { background: #e9f6ec; border-color: #b8dfc1; color: #1f6b32; }
|
||||
.errors-table .error-chip.chip-mono .chip-value { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.78rem; }
|
||||
.errors-table .pill-module { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.78rem; background: #eef0f3; color: #333; padding: 1px 6px; border-radius: 4px; }
|
||||
.errors-table .error-details { margin-top: 6px; }
|
||||
.errors-table .error-details summary { cursor: pointer; color: #666; font-size: 0.82rem; }
|
||||
.errors-table .error-details pre { background: #fafafa; border: 1px solid #ececec; border-radius: 4px; padding: 8px; font-size: 0.78rem; max-height: 18em; overflow: auto; }
|
||||
.errors-table .error-context { color: #888; font-size: 0.78rem; font-style: italic; margin-left: 4px; }
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<a href="/admin/users"><i class="fas fa-users"></i> Users</a>
|
||||
<a href="/admin/conferences" class="active"><i class="fas fa-chalkboard-teacher"></i> Conferences</a>
|
||||
<a href="/admin/queues"><i class="fas fa-tasks"></i> Queues</a>
|
||||
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
|
||||
</nav>
|
||||
|
||||
<div class="admin-summary">
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<div class="container paper-page admin-page">
|
||||
<div class="paper-crumbs">Admin / <span class="here">Errors</span></div>
|
||||
<h1 class="paper-page-title">Errors</h1>
|
||||
|
||||
<nav class="admin-nav">
|
||||
<a href="/admin/"><i class="fas fa-code-branch"></i> Repositories</a>
|
||||
<a href="/admin/users"><i class="fas fa-users"></i> Users</a>
|
||||
<a href="/admin/conferences"><i class="fas fa-chalkboard-teacher"></i> Conferences</a>
|
||||
<a href="/admin/queues"><i class="fas fa-tasks"></i> Queues</a>
|
||||
<a href="/admin/errors" class="active"><i class="fas fa-bug"></i> Errors</a>
|
||||
</nav>
|
||||
|
||||
<div class="admin-summary">
|
||||
<span class="summary-pill error">{{filtered.length}} shown</span>
|
||||
<span class="summary-pill">{{entries.length}} captured</span>
|
||||
<span class="summary-pill" ng-if="!available">redis sink unavailable</span>
|
||||
</div>
|
||||
|
||||
<form class="w-100 admin-filter-toolbar" aria-label="Error filters">
|
||||
<div class="admin-filter-row">
|
||||
<div class="search-wrap">
|
||||
<input type="search" class="form-control" placeholder="Search message, module, or url…" ng-model="query.search" autocomplete="off" />
|
||||
</div>
|
||||
<span class="admin-filter-inline">
|
||||
<label>Module</label>
|
||||
<select class="form-control form-control-sm" ng-model="query.module">
|
||||
<option value="">Any</option>
|
||||
<option ng-repeat="m in modules" value="{{m}}">{{m}}</option>
|
||||
</select>
|
||||
</span>
|
||||
<span class="admin-filter-spacer"></span>
|
||||
<label class="admin-filter-inline" style="cursor:pointer;">
|
||||
<input type="checkbox" ng-model="query.autoRefresh" />
|
||||
Auto-refresh
|
||||
</label>
|
||||
<button class="btn btn-sm" type="button" ng-click="refreshNow()" title="Refresh now"><i class="fas fa-sync"></i></button>
|
||||
<button class="btn btn-sm btn-danger" type="button" ng-click="clearAll()" title="Clear all errors"><i class="fas fa-trash"></i> Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div ng-if="!filtered.length" class="admin-empty">No errors captured.</div>
|
||||
|
||||
<table class="table errors-table" ng-if="filtered.length">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 9em;">When</th>
|
||||
<th style="width: 9em;">Module</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="e in filtered track by $index">
|
||||
<td class="error-when">
|
||||
<time title="{{absTime(e.ts)}}">{{relTime(e.ts)}}</time>
|
||||
</td>
|
||||
<td><span class="pill pill-module">{{e.module}}</span></td>
|
||||
<td class="error-msg">
|
||||
<div class="error-msg-line">
|
||||
<strong>{{e.displayMessage}}</strong>
|
||||
<span class="error-context" ng-if="e.displayContext && e.displayContext !== e.displayMessage">{{e.displayContext}}</span>
|
||||
<span class="error-chip"
|
||||
ng-repeat="c in e._chips track by $index"
|
||||
ng-class="{'chip-err': c.kind === 'err', 'chip-warn': c.kind === 'warn', 'chip-ok': c.kind === 'ok', 'chip-mono': c.mono}"
|
||||
title="{{c.label}}: {{c.value}}">
|
||||
<span class="chip-label">{{c.label}}</span>
|
||||
<span class="chip-value">{{c.value}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<details ng-if="e._detailJson" class="error-details">
|
||||
<summary>raw</summary>
|
||||
<pre>{{e._detailJson}}</pre>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -7,6 +7,7 @@
|
||||
<a href="/admin/users"><i class="fas fa-users"></i> Users</a>
|
||||
<a href="/admin/conferences"><i class="fas fa-chalkboard-teacher"></i> Conferences</a>
|
||||
<a href="/admin/queues" class="active"><i class="fas fa-tasks"></i> Queues</a>
|
||||
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
|
||||
</nav>
|
||||
|
||||
<div class="admin-summary">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<a href="/admin/users"><i class="fas fa-users"></i> Users</a>
|
||||
<a href="/admin/conferences"><i class="fas fa-chalkboard-teacher"></i> Conferences</a>
|
||||
<a href="/admin/queues"><i class="fas fa-tasks"></i> Queues</a>
|
||||
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
|
||||
</nav>
|
||||
|
||||
<div class="admin-summary">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<a href="/admin/users" class="active"><i class="fas fa-users"></i> Users</a>
|
||||
<a href="/admin/conferences"><i class="fas fa-chalkboard-teacher"></i> Conferences</a>
|
||||
<a href="/admin/queues"><i class="fas fa-tasks"></i> Queues</a>
|
||||
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
|
||||
</nav>
|
||||
|
||||
<div class="user-detail-card" ng-if="userInfo">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<a href="/admin/users" class="active"><i class="fas fa-users"></i> Users</a>
|
||||
<a href="/admin/conferences"><i class="fas fa-chalkboard-teacher"></i> Conferences</a>
|
||||
<a href="/admin/queues"><i class="fas fa-tasks"></i> Queues</a>
|
||||
<a href="/admin/errors"><i class="fas fa-bug"></i> Errors</a>
|
||||
</nav>
|
||||
|
||||
<div class="admin-summary">
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
<h1 class="paper-page-title pr-title">
|
||||
<span ng-if="details.description" ng-bind="details.description"></span>
|
||||
<span ng-if="!details.description" class="text-muted">Untitled gist</span>
|
||||
<a
|
||||
ng-if="options.isAdmin || options.isOwner"
|
||||
ng-href="/gist-anonymize/{{gistId}}"
|
||||
class="btn btn-sm"
|
||||
aria-label="Edit"
|
||||
><i class="far fa-edit"></i><span class="d-none d-md-inline"> Edit</span></a
|
||||
>
|
||||
</h1>
|
||||
<div class="pr-header-meta">
|
||||
<span class="paper-pill" ng-class="{'good': details.isPublic, 'warn': !details.isPublic}">
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
<h1 class="paper-page-title pr-title">
|
||||
<span ng-if="details.title" ng-bind="details.title"></span>
|
||||
<span ng-if="!details.title" class="text-muted">Untitled pull request</span>
|
||||
<a
|
||||
ng-if="options.isAdmin || options.isOwner"
|
||||
ng-href="/pull-request-anonymize/{{pullRequestId}}"
|
||||
class="btn btn-sm"
|
||||
aria-label="Edit"
|
||||
><i class="far fa-edit"></i><span class="d-none d-md-inline"> Edit</span></a
|
||||
>
|
||||
</h1>
|
||||
<div class="pr-header-meta">
|
||||
<span class="paper-pill" ng-class="{'good': details.merged, 'warn': details.state == 'open', 'bad': details.state == 'closed' && !details.merged}">
|
||||
|
||||
@@ -849,4 +849,150 @@ angular
|
||||
);
|
||||
$scope.$watch("query.state", getQueues);
|
||||
},
|
||||
])
|
||||
.controller("errorsAdminController", [
|
||||
"$scope",
|
||||
"$http",
|
||||
"$location",
|
||||
"$interval",
|
||||
function ($scope, $http, $location, $interval) {
|
||||
$scope.$watch("user.status", () => {
|
||||
if ($scope.user == null) {
|
||||
$location.url("/");
|
||||
}
|
||||
});
|
||||
if ($scope.user == null) {
|
||||
$location.url("/");
|
||||
}
|
||||
|
||||
$scope.entries = [];
|
||||
$scope.filtered = [];
|
||||
$scope.modules = [];
|
||||
$scope.available = true;
|
||||
$scope.query = {
|
||||
search: "",
|
||||
module: "",
|
||||
autoRefresh: true,
|
||||
};
|
||||
|
||||
$scope.relTime = (iso) => {
|
||||
if (!iso) return "";
|
||||
const t = new Date(iso).getTime();
|
||||
if (isNaN(t)) return iso;
|
||||
const diff = Math.max(0, Date.now() - t);
|
||||
const s = Math.floor(diff / 1000);
|
||||
if (s < 5) return "just now";
|
||||
if (s < 60) return `${s}s ago`;
|
||||
const m = Math.floor(s / 60);
|
||||
if (m < 60) return `${m}m ago`;
|
||||
const h = Math.floor(m / 60);
|
||||
if (h < 24) return `${h}h ago`;
|
||||
const d = Math.floor(h / 24);
|
||||
if (d < 7) return `${d}d ago`;
|
||||
return new Date(iso).toLocaleDateString();
|
||||
};
|
||||
$scope.absTime = (iso) => {
|
||||
if (!iso) return "";
|
||||
const d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return iso;
|
||||
return d.toLocaleString();
|
||||
};
|
||||
|
||||
// Decorate each entry once with derived display fields (chips + json).
|
||||
// Returning a fresh array from a template-bound function each digest
|
||||
// cycle triggers Angular's $rootScope:infdig — so we precompute on load.
|
||||
function statusKind(s) {
|
||||
const n = parseInt(s, 10);
|
||||
if (!n) return "";
|
||||
if (n >= 500) return "err";
|
||||
if (n >= 400) return "warn";
|
||||
return "ok";
|
||||
}
|
||||
// snake_case identifier looking like an error key (e.g. "repo_not_found").
|
||||
const errorKeyRe = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/;
|
||||
function decorate(e) {
|
||||
const chips = [];
|
||||
const detail = (e.raw || []).find(
|
||||
(a) => a && typeof a === "object" && !Array.isArray(a)
|
||||
);
|
||||
if (detail) {
|
||||
// Prefer the structured error key (e.g. "pull_request_not_found")
|
||||
// over the generic logger message ("anonymous error", "http error").
|
||||
if (detail.message && errorKeyRe.test(detail.message)) {
|
||||
e.displayMessage = detail.message;
|
||||
e.displayContext = e.message;
|
||||
} else if (detail.code && errorKeyRe.test(String(detail.code))) {
|
||||
e.displayMessage = String(detail.code);
|
||||
e.displayContext = e.message;
|
||||
} else {
|
||||
e.displayMessage = e.message;
|
||||
}
|
||||
if (detail.httpStatus) chips.push({ label: "status", value: detail.httpStatus, kind: statusKind(detail.httpStatus) });
|
||||
else if (detail.status) chips.push({ label: "status", value: detail.status, kind: statusKind(detail.status) });
|
||||
if (detail.method) chips.push({ label: "method", value: detail.method });
|
||||
if (detail.url) chips.push({ label: "url", value: detail.url, mono: true });
|
||||
if (detail.repoId) chips.push({ label: "repo", value: detail.repoId, mono: true });
|
||||
if (detail.code && detail.code !== detail.message && detail.code !== e.displayMessage) {
|
||||
chips.push({ label: "code", value: detail.code });
|
||||
}
|
||||
} else {
|
||||
e.displayMessage = e.message;
|
||||
}
|
||||
const tail = (e.raw || []).slice(1);
|
||||
const detailJson = !tail.length
|
||||
? ""
|
||||
: tail.length === 1
|
||||
? JSON.stringify(tail[0], null, 2)
|
||||
: JSON.stringify(tail, null, 2);
|
||||
e._chips = chips;
|
||||
e._detailJson = detailJson;
|
||||
return e;
|
||||
}
|
||||
|
||||
function applyFilter() {
|
||||
const q = ($scope.query.search || "").toLowerCase();
|
||||
const mod = $scope.query.module || "";
|
||||
$scope.filtered = $scope.entries.filter((e) => {
|
||||
if (mod && e.module !== mod) return false;
|
||||
if (!q) return true;
|
||||
const hay = (
|
||||
(e.displayMessage || e.message || "") +
|
||||
" " +
|
||||
e.module +
|
||||
" " +
|
||||
JSON.stringify(e.raw || [])
|
||||
).toLowerCase();
|
||||
return hay.indexOf(q) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
function load() {
|
||||
$http.get("/api/admin/errors").then(
|
||||
(res) => {
|
||||
$scope.entries = (res.data.entries || []).map(decorate);
|
||||
$scope.available = !!res.data.available;
|
||||
const set = new Set();
|
||||
$scope.entries.forEach((e) => e.module && set.add(e.module));
|
||||
$scope.modules = Array.from(set).sort();
|
||||
applyFilter();
|
||||
},
|
||||
(err) => console.error(err)
|
||||
);
|
||||
}
|
||||
|
||||
$scope.refreshNow = load;
|
||||
$scope.clearAll = () => {
|
||||
if (!confirm("Clear all captured errors?")) return;
|
||||
$http.delete("/api/admin/errors").then(load, (err) => console.error(err));
|
||||
};
|
||||
|
||||
load();
|
||||
const stop = $interval(() => {
|
||||
if ($scope.query.autoRefresh) load();
|
||||
}, 5000);
|
||||
$scope.$on("$destroy", () => $interval.cancel(stop));
|
||||
|
||||
$scope.$watch("query.search", applyFilter);
|
||||
$scope.$watch("query.module", applyFilter);
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -137,6 +137,11 @@ angular
|
||||
controller: "queuesAdminController",
|
||||
title: "Admin · Queues – Anonymous GitHub",
|
||||
})
|
||||
.when("/admin/errors", {
|
||||
templateUrl: "/partials/admin/errors.htm",
|
||||
controller: "errorsAdminController",
|
||||
title: "Admin · Errors – Anonymous GitHub",
|
||||
})
|
||||
.when("/404", {
|
||||
templateUrl: "/partials/404.htm",
|
||||
title: "Page not found – Anonymous GitHub",
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user