Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b2609e98f | |||
| fa6d82d6e5 | |||
| 7efd1bbba7 | |||
| 3bf94a5d7f |
@@ -15,5 +15,5 @@ type: application
|
||||
# appVersion and image.tag in values.yaml no longer agree, and that is not an oversight:
|
||||
# image.tag stays "latest", which is what a local install actually pulls. appVersion is
|
||||
# metadata and drives nothing.
|
||||
version: 0.36.0
|
||||
appVersion: "v0.36.0"
|
||||
version: 0.37.1
|
||||
appVersion: "v0.37.1"
|
||||
|
||||
@@ -6,20 +6,19 @@ metadata:
|
||||
labels:
|
||||
{{- include "terdut-server.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "terdut-server.selectorLabels" . | nindent 6 }}
|
||||
# Recreate, not RollingUpdate, even though the PVC that forced it is gone:
|
||||
# the sweeper, notifier and migration runner take a Postgres advisory lock
|
||||
# each, and new-incident creation on the first webhook for a brand-new
|
||||
# groupKey resolves its own insert conflict — so two replicas overlapping
|
||||
# during a rollout no longer double-page, race a migration, or drop a
|
||||
# webhook payload. Nothing left here actually requires Recreate anymore;
|
||||
# it stays the default pending a deliberate decision to raise replicas
|
||||
# above 1 and move to RollingUpdate.
|
||||
# RollingUpdate, not Recreate: the sweeper, notifier and migration runner
|
||||
# each take a Postgres advisory lock around their own pass, and new-incident
|
||||
# creation on the first webhook for a brand-new groupKey resolves its own
|
||||
# insert conflict -- so two replicas overlapping during a rollout no longer
|
||||
# double-page, race a migration, or drop a webhook payload (v0.36.0). No
|
||||
# explicit maxUnavailable/maxSurge: the 25%/25% default rounds to 0/1 at
|
||||
# replicaCount: 2, which is zero-downtime already.
|
||||
strategy:
|
||||
type: Recreate
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
# Safe above 1 since v0.36.0: the sweeper, notifier and migration runner each
|
||||
# take a Postgres advisory lock around their own pass, and a webhook that
|
||||
# loses the race to open a brand-new incident attaches to the winner's row
|
||||
# instead of dropping its payload. An image older than v0.36.0 does not have
|
||||
# these guards -- do not raise this against one.
|
||||
replicaCount: 2
|
||||
|
||||
networking:
|
||||
hostname: "terdut.example.com"
|
||||
servicePort: 8080
|
||||
|
||||
@@ -583,6 +583,10 @@ details[open] > summary { margin-bottom: 8px; }
|
||||
}
|
||||
.actionbar .btn { min-height: 48px; }
|
||||
.actionbar .btn-primary { flex: 1; font-size: 16px; }
|
||||
/* The extra actions that only phones hide behind "More" — see actionBar() and
|
||||
extraActions() in incident.js. Hidden by default; the desktop block below
|
||||
shows them and hides the now-redundant More button instead. */
|
||||
.action-extra { display: none; }
|
||||
|
||||
.detail-placeholder {
|
||||
display: grid; place-items: center; height: 100%;
|
||||
@@ -669,7 +673,7 @@ details[open] > summary { margin-bottom: 8px; }
|
||||
caught by the unrelated .label > span styling meant for label chips. */
|
||||
.week-nav .week-label { font-size: 14px; font-weight: 650; min-width: 11.5em; text-align: center; white-space: nowrap; }
|
||||
.week-label small { color: var(--faint); font-weight: 600; font-size: 11px; margin-left: 2px; }
|
||||
.days { list-style: none; margin: 0; padding: 0; }
|
||||
.days { list-style: none; margin: 0 auto; padding: 0; }
|
||||
.day { display: grid; grid-template-columns: 3.2em 4.2em 1fr; align-items: center; gap: 8px; min-height: 50px; padding: 0 14px; }
|
||||
.day + .day { border-top: 1px solid var(--border); }
|
||||
.day-name { font-weight: 650; }
|
||||
@@ -773,7 +777,15 @@ kbd {
|
||||
.pane-list .chips { flex-wrap: wrap; overflow-x: visible; }
|
||||
.pane-list .chip-sep { display: none; }
|
||||
.chips-fade { display: none; }
|
||||
.view-queue:not(.has-detail) .pane-detail { display: block; }
|
||||
/* With nothing selected there is no detail to show next to, so the list
|
||||
takes the whole row instead of leaving the second column as dead space
|
||||
around the placeholder text. Selecting an incident (.has-detail) drops
|
||||
back to the base minmax(340,420) 1fr rule above. */
|
||||
.view-queue:not(.has-detail) { grid-template-columns: 1fr; }
|
||||
.view-queue:not(.has-detail) .pane-list { border-right: 0; }
|
||||
/* Full width reads better capped than edge-to-edge on a very wide monitor,
|
||||
matching .detail's own cap below. */
|
||||
.view-queue:not(.has-detail) .list { max-width: 900px; margin: 0 auto; }
|
||||
|
||||
/* On desktop the list stays visible next to the detail. */
|
||||
.app.detail-open .nav { display: flex; }
|
||||
@@ -787,8 +799,12 @@ kbd {
|
||||
.actionbar {
|
||||
position: sticky; bottom: 0;
|
||||
padding: 12px 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.actionbar .btn-primary { flex: 0 1 240px; }
|
||||
/* Room enough to show every action, so More is fully redundant here. */
|
||||
.action-extra { display: inline-flex; }
|
||||
.more-btn { display: none; }
|
||||
|
||||
.sheet {
|
||||
width: min(440px, calc(100% - 32px));
|
||||
|
||||
@@ -33,7 +33,9 @@ function render() {
|
||||
h('div', { class: 'card' }, shortcuts())),
|
||||
|
||||
h('div', { class: 'page-head' }),
|
||||
h('button', { class: 'btn btn-block', type: 'button', onclick: signOut }, icon('logout'), 'Sign out'),
|
||||
// Wrapped in a div: .btn is inline-flex, and only a block-level element
|
||||
// picks up .view-page > *'s margin:auto centering (see app.css:316).
|
||||
h('div', {}, h('button', { class: 'btn btn-block', type: 'button', onclick: signOut }, icon('logout'), 'Sign out')),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,9 @@ function render() {
|
||||
}
|
||||
|
||||
function backLink() {
|
||||
return h('a', { class: 'back-link', href: '/admin/teams' }, icon('chevronLeft'), h('span', { text: 'Teams' }));
|
||||
// Wrapped in a div: .back-link is inline-flex, and only a block-level
|
||||
// element picks up .view-page > *'s margin:auto centering (app.css:316).
|
||||
return h('div', {}, h('a', { class: 'back-link', href: '/admin/teams' }, icon('chevronLeft'), h('span', { text: 'Teams' })));
|
||||
}
|
||||
|
||||
// --- identity --------------------------------------------------------------
|
||||
|
||||
@@ -83,7 +83,9 @@ function render() {
|
||||
}
|
||||
|
||||
function backLink() {
|
||||
return h('a', { class: 'back-link', href: '/admin/users' }, icon('chevronLeft'), h('span', { text: 'Users' }));
|
||||
// Wrapped in a div: .back-link is inline-flex, and only a block-level
|
||||
// element picks up .view-page > *'s margin:auto centering (app.css:316).
|
||||
return h('div', {}, h('a', { class: 'back-link', href: '/admin/users' }, icon('chevronLeft'), h('span', { text: 'Users' })));
|
||||
}
|
||||
|
||||
// --- identity --------------------------------------------------------------
|
||||
|
||||
@@ -453,9 +453,25 @@ function quickActions() {
|
||||
return div;
|
||||
}
|
||||
|
||||
// Desktop has room to show what a phone folds into the More sheet below — see
|
||||
// the .action-extra/.more-btn rules in app.css. Resolved/archived incidents
|
||||
// already say everything via primaryAction()/secondaryAction(), so there is
|
||||
// nothing extra to surface for them.
|
||||
function extraActions() {
|
||||
if (!isOpen()) return [];
|
||||
const out = [
|
||||
h('button', { class: 'btn btn-sm action-extra', type: 'button', onclick: assign }, icon('user'), 'Assign…'),
|
||||
h('button', { class: 'btn btn-sm action-extra', type: 'button', onclick: addNote }, icon('note'), 'Add note…'),
|
||||
];
|
||||
out.push(inc.status === 'acknowledged'
|
||||
? h('button', { class: 'btn btn-sm action-extra', type: 'button', onclick: unacknowledge }, icon('undo'), 'Clear ack')
|
||||
: h('button', { class: 'btn btn-sm action-extra', type: 'button', onclick: resolve }, icon('checkCircle'), 'Resolve…'));
|
||||
return out;
|
||||
}
|
||||
|
||||
function actionBar() {
|
||||
const more = h('button', { class: 'btn', type: 'button', 'aria-label': 'More actions', onclick: moreMenu }, icon('more'), 'More');
|
||||
const bar = h('div', { class: 'actionbar' }, primaryAction(), secondaryAction(), more);
|
||||
const more = h('button', { class: 'btn more-btn', type: 'button', 'aria-label': 'More actions', onclick: moreMenu }, icon('more'), 'More');
|
||||
const bar = h('div', { class: 'actionbar' }, primaryAction(), secondaryAction(), ...extraActions(), more);
|
||||
if (busy) for (const b of bar.querySelectorAll('button')) b.disabled = true;
|
||||
return bar;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user