repos
/ status-django master

status-django

mirror archived upstream

Self-hostable uptime monitor and status page on Django: HTTP checks, Lighthouse audits, SEO crawls, and email and Discord alerts.

djangodockerhandcodedpythonself-hostedsqlitestatus-pageuptime-monitoringvite

17.1 KB · 813 lines · SCSS Raw History
  1@use "variables" as *;
  2
  3// ── CSS variable overrides ──
  4// Bootstrap 5.3 writes defaults into `:root,[data-bs-theme=light]`. Use
  5// `html:root` so we outrank the default block and enforce the dark palette.
  6html:root {
  7  --bs-body-bg: #{$body-bg};
  8  --bs-body-color: #{$body-color};
  9  --bs-body-bg-rgb: 14, 13, 10;
 10  --bs-body-color-rgb: 221, 215, 205;
 11  --bs-emphasis-color: #{$white};
 12  --bs-secondary-bg: #13120e;
 13  --bs-tertiary-bg: #18160f;
 14  --bs-border-color: rgba(107, 158, 120, 0.12);
 15  --bs-link-color-rgb: 125, 184, 140;
 16  --bs-link-hover-color-rgb: 149, 204, 162;
 17  --bs-heading-color: #{$white};
 18}
 19
 20// ── Global ──
 21
 22body {
 23  min-height: 100vh;
 24  display: flex;
 25  flex-direction: column;
 26  -webkit-font-smoothing: antialiased;
 27  -moz-osx-font-smoothing: grayscale;
 28  letter-spacing: 0.01em;
 29  background-color: $body-bg !important;
 30  color: $body-color;
 31}
 32
 33main { flex: 1; }
 34
 35.vh-100 { height: 100vh; }
 36
 37// ── Logo (uptime-bars mark) ──
 38// Rendered as an inline SVG in the template so it scales cleanly. Matches the
 39// favicon exactly.
 40
 41.logo {
 42  display: inline-flex;
 43  align-items: center;
 44  justify-content: center;
 45  width: 28px;
 46  height: 28px;
 47  text-decoration: none !important;
 48  transition: transform 250ms ease;
 49
 50  svg {
 51    width: 100%;
 52    height: 100%;
 53    display: block;
 54  }
 55
 56  &:hover {
 57    transform: translateY(-1px);
 58  }
 59
 60  &.logo-sm {
 61    width: 18px;
 62    height: 18px;
 63  }
 64}
 65
 66// ── Navbar ──
 67
 68.navbar {
 69  height: 52px;
 70  padding: 0;
 71  background: #090806 !important;
 72  border-bottom: 1px solid rgba(107, 158, 120, 0.06);
 73
 74  .navbar-brand {
 75    display: flex;
 76    align-items: center;
 77    gap: 0.6rem;
 78    color: $white;
 79    font-weight: 600;
 80    letter-spacing: 0.1em;
 81    text-transform: uppercase;
 82    font-size: 0.78rem;
 83    text-decoration: none;
 84
 85    .brand-text { color: $white; }
 86    .brand-dot {
 87      display: inline-block;
 88      width: 6px; height: 6px; border-radius: 50%;
 89      background: $green;
 90    }
 91  }
 92
 93  .nav-link {
 94    font-size: 0.8rem;
 95    font-weight: 500;
 96    letter-spacing: 0.05em;
 97    text-transform: lowercase;
 98    padding-left: 1rem !important;
 99    padding-right: 1rem !important;
100    color: rgba(221, 215, 205, 0.6);
101    position: relative;
102    transition: color 200ms ease;
103
104    &::after {
105      content: '';
106      position: absolute;
107      bottom: 2px;
108      left: 1rem;
109      right: 1rem;
110      height: 1px;
111      background: $green;
112      transform: scaleX(0);
113      transition: transform 250ms ease;
114    }
115
116    &:hover,
117    &.active { color: $white; }
118
119    &:hover::after,
120    &.active::after { transform: scaleX(1); }
121  }
122}
123
124// ── Breadcrumb bar ──
125
126.breadcrumb-bar {
127  background: rgba(107, 158, 120, 0.02);
128  border-bottom: 1px solid rgba(107, 158, 120, 0.04);
129
130  .breadcrumb { font-family: $font-family-monospace; }
131}
132
133// ── Section labels — amber caps, no prompt glyph ──
134
135.section-label {
136  font-size: 0.7rem;
137  font-weight: 600;
138  letter-spacing: 0.12em;
139  text-transform: uppercase;
140  color: rgba(201, 168, 76, 0.75);
141  font-family: $font-family-monospace;
142}
143
144// ── Cards (base) ──
145
146.card {
147  background: $card-bg;
148  border: 1px solid $card-border-color;
149  transition: border-color 250ms ease;
150
151  &:hover { border-color: rgba(107, 158, 120, 0.2); }
152}
153
154// ── Status dot ──
155
156.status-dot {
157  display: inline-block;
158  width: 8px; height: 8px;
159  border-radius: 50%;
160  margin-right: 0.55rem;
161  vertical-align: middle;
162  flex-shrink: 0;
163
164  &.status-dot-lg {
165    width: 14px; height: 14px;
166    margin-right: 0;
167  }
168
169  &.is-up { background: $green; box-shadow: 0 0 0 3px rgba(107, 158, 120, 0.12); }
170  &.is-down { background: $terracotta; box-shadow: 0 0 0 3px rgba(196, 112, 85, 0.12); }
171  &.is-warn { background: $amber; box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.12); }
172  &.is-idle { background: #4a443c; }
173}
174
175// ── Visibility toggle (segmented pill) ──
176// Replaces the Bootstrap form-switch which was near-invisible on the dark
177// theme. Clear two-state UI — active half highlighted, inactive half dim.
178
179.toggle-label {
180  font-family: $font-family-monospace;
181  font-size: 0.68rem;
182  font-weight: 600;
183  letter-spacing: 0.12em;
184  text-transform: uppercase;
185  color: #847c72;
186  cursor: help;
187}
188
189.toggle-pill {
190  display: inline-flex;
191  align-items: stretch;
192  border: 1px solid rgba(107, 158, 120, 0.22);
193  border-radius: 999px;
194  overflow: hidden;
195  cursor: pointer;
196  background: #0e0d0a;
197  margin: 0;
198  user-select: none;
199  line-height: 1;
200
201  input {
202    position: absolute;
203    opacity: 0;
204    pointer-events: none;
205    width: 0; height: 0;
206  }
207
208  .toggle-pill-seg {
209    padding: 0.45rem 0.9rem;
210    font-family: $font-family-monospace;
211    font-size: 0.7rem;
212    font-weight: 600;
213    letter-spacing: 0.08em;
214    text-transform: uppercase;
215    color: #665f56;
216    transition: background 180ms ease, color 180ms ease;
217  }
218
219  // Default (unchecked) = Private is active
220  input ~ .toggle-pill-private {
221    background: rgba(201, 168, 76, 0.12);
222    color: #ddc06a;
223  }
224
225  // Checked = Public is active
226  input:checked ~ .toggle-pill-private {
227    background: transparent;
228    color: #665f56;
229  }
230  input:checked ~ .toggle-pill-public {
231    background: $green-dim;
232    color: $green-bright;
233  }
234
235  &:hover {
236    border-color: rgba(107, 158, 120, 0.35);
237  }
238}
239
240// ── Metric tile ──
241// Every tile fills its grid cell so a row of them all share one height, even
242// when content length varies (e.g. one tile has a sub-line, others don't).
243
244.metric-tile {
245  background: $card-bg;
246  border: 1px solid $card-border-color;
247  border-radius: $border-radius;
248  padding: 1rem 1.15rem;
249  height: 100%;
250  display: flex;
251  flex-direction: column;
252
253  .metric-label {
254    font-size: 0.68rem;
255    font-weight: 600;
256    letter-spacing: 0.12em;
257    text-transform: uppercase;
258    color: #847c72;
259  }
260
261  .metric-value {
262    font-size: 1.75rem;
263    font-weight: 700;
264    line-height: 1.1;
265    color: $white;
266    margin-top: 0.35rem;
267    letter-spacing: -0.01em;
268  }
269
270  .metric-sub {
271    font-size: 0.72rem;
272    color: #847c72;
273    margin-top: auto;  // Pins sub to the bottom so all tiles baseline-align
274    padding-top: 0.35rem;
275    letter-spacing: 0.02em;
276  }
277
278  &.metric-accent-green {
279    border-color: $green-border;
280    .metric-value { color: $green-bright; }
281  }
282  &.metric-accent-amber {
283    border-color: $amber-border;
284    .metric-value { color: #ddc06a; }
285  }
286  &.metric-accent-danger {
287    border-color: $terracotta-border;
288    .metric-value { color: #d88870; }
289  }
290}
291
292// ── Chips (inline status/score) ──
293
294.chip {
295  display: inline-flex;
296  align-items: center;
297  gap: 0.4rem;
298  font-family: $font-family-monospace;
299  font-size: 0.7rem;
300  font-weight: 600;
301  letter-spacing: 0.05em;
302  text-transform: uppercase;
303  padding: 0.3rem 0.6rem;
304  border-radius: 2px;
305  border: 1px solid transparent;
306  line-height: 1;
307
308  &.chip-ok {
309    background: $green-dim;
310    color: $green-bright;
311    border-color: $green-border;
312  }
313  &.chip-down {
314    background: $terracotta-dim;
315    color: #e38871;
316    border-color: $terracotta-border;
317  }
318  &.chip-warn {
319    background: $amber-dim;
320    color: #ddc06a;
321    border-color: $amber-border;
322  }
323  &.chip-info {
324    background: rgba(126, 170, 184, 0.08);
325    color: #9ec5d2;
326    border-color: rgba(126, 170, 184, 0.22);
327  }
328  &.chip-muted {
329    background: rgba(221, 215, 205, 0.04);
330    color: #847c72;
331    border-color: rgba(221, 215, 205, 0.08);
332  }
333}
334
335// ── Footer ──
336
337footer {
338  padding-top: 4rem;
339  padding-bottom: 3rem;
340  position: relative;
341  background: #090806;
342  border-top: 1px solid rgba(107, 158, 120, 0.06);
343
344  .h5 {
345    font-size: 0.7rem;
346    letter-spacing: 0.12em;
347    text-transform: uppercase;
348    color: rgba(201, 168, 76, 0.55);
349    font-weight: 600;
350  }
351
352  p {
353    color: rgba(221, 215, 205, 0.5);
354    font-size: 0.85rem;
355    line-height: 1.75;
356
357    a {
358      color: rgba(221, 215, 205, 0.75);
359      text-decoration: underline;
360      text-underline-offset: 2px;
361
362      &:hover { color: $white; }
363    }
364  }
365
366  .links {
367    padding-top: 2rem;
368    padding-bottom: 1rem;
369  }
370}
371
372.footer-bar {
373  background: #050403;
374  border-top: 1px solid rgba(107, 158, 120, 0.04);
375  padding: 0.8rem 0;
376  color: rgba(221, 215, 205, 0.3);
377  font-size: 0.78rem;
378
379  small { color: rgba(221, 215, 205, 0.3); }
380
381  &-link {
382    color: rgba(221, 215, 205, 0.3);
383    transition: color 200ms ease;
384
385    &:hover { color: $white; }
386  }
387}
388
389// ── Uptime strip ──
390
391.uptime-strip {
392  display: flex;
393  gap: 2px;
394  align-items: flex-end;
395  height: 20px;
396
397  .uptime-tick {
398    flex: 1 1 auto;
399    min-width: 2px;
400    height: 100%;
401    background: #211e1a;
402    border-radius: 1px;
403
404    &.is-up { background: $green; opacity: 0.7; }
405    &.is-down { background: $terracotta; opacity: 0.85; }
406    &.is-warn { background: $amber; opacity: 0.7; }
407    &.is-none { background: #18160f; }
408  }
409}
410
411// ── Terminal-style block ──
412
413.terminal-block {
414  background: rgba(9, 8, 6, 0.7);
415  border: 1px solid rgba(107, 158, 120, 0.15);
416  border-radius: $border-radius;
417  font-family: $font-family-monospace;
418  font-size: 0.8rem;
419  color: $green-bright;
420  padding: 1rem 1.2rem;
421  line-height: 1.8;
422
423  .t-line { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
424  .t-prompt { color: $amber; margin-right: 0.4rem; }
425  .t-out { color: $gray-200; }
426  .t-comment { color: #665f56; }
427  .t-key { color: $amber; }
428  .t-val { color: $green-bright; }
429  .t-cursor {
430    display: inline-block; width: 7px; height: 0.9em; vertical-align: -2px;
431    background: $green-bright;
432    animation: t-blink 1s steps(1) infinite;
433  }
434}
435
436@keyframes t-blink {
437  0%, 50% { opacity: 1; }
438  51%, 100% { opacity: 0; }
439}
440
441// ── Property row ──
442
443.property-row {
444  display: grid;
445  grid-template-columns: minmax(0, 1fr) auto;
446  gap: 1rem 1.5rem;
447  background: $card-bg;
448  border: 1px solid $card-border-color;
449  border-radius: $border-radius;
450  padding: 1rem 1.15rem;
451  margin-bottom: 0.75rem;
452  transition: border-color 200ms ease;
453
454  &:hover { border-color: rgba(107, 158, 120, 0.22); }
455
456  &.is-down { border-color: rgba(196, 112, 85, 0.3); }
457
458  .pr-main {
459    min-width: 0;
460    display: flex;
461    flex-direction: column;
462    gap: 0.35rem;
463  }
464
465  .pr-title {
466    font-weight: 600;
467    color: $white;
468    display: flex;
469    align-items: center;
470    min-width: 0;
471
472    a {
473      color: inherit;
474      text-decoration: none;
475      min-width: 0;
476      overflow: hidden;
477      text-overflow: ellipsis;
478      white-space: nowrap;
479
480      &:hover { color: $green-bright; }
481    }
482  }
483
484  .pr-url {
485    font-size: 0.73rem;
486    color: #847c72;
487    overflow: hidden;
488    text-overflow: ellipsis;
489    white-space: nowrap;
490  }
491
492  .pr-side {
493    display: flex;
494    flex-direction: column;
495    justify-content: space-between;
496    align-items: flex-end;
497    gap: 0.75rem;
498    min-height: 68px; // Matches the left column's natural height so actions pin to bottom
499  }
500
501  // Uniform stat grid — each cell is label-over-chip so every column aligns
502  // vertically regardless of whether the value is a pill or a number.
503  .pr-meta {
504    display: flex;
505    gap: 0.85rem;
506    align-items: flex-end;
507    font-family: $font-family-monospace;
508
509    .pr-meta-cell {
510      display: flex;
511      flex-direction: column;
512      align-items: flex-start;
513      gap: 0.2rem;
514      min-width: 0;
515    }
516
517    .pr-meta-k {
518      color: #665f56;
519      text-transform: uppercase;
520      font-size: 0.6rem;
521      letter-spacing: 0.1em;
522      line-height: 1;
523    }
524  }
525
526  .pr-actions {
527    display: flex;
528    gap: 0.4rem;
529  }
530}
531
532@media (max-width: 767px) {
533  .property-row {
534    grid-template-columns: 1fr;
535
536    .pr-side {
537      min-height: 0;
538      align-items: stretch;
539    }
540    .pr-meta {
541      flex-wrap: wrap;
542      justify-content: flex-start;
543    }
544    .pr-actions {
545      justify-content: flex-end;
546    }
547  }
548}
549
550// ── Search form ──
551
552.search-form {
553  position: relative;
554
555  .search-icon {
556    position: absolute;
557    left: 0.9rem;
558    top: 50%;
559    transform: translateY(-50%);
560    color: #665f56;
561    pointer-events: none;
562    z-index: 2;
563  }
564
565  .search-input {
566    padding-left: 2.5rem;
567    font-size: 0.85rem;
568    border: 1px solid rgba(107, 158, 120, 0.15);
569    background: rgba(19, 18, 14, 0.7);
570    color: $body-color;
571    transition: border-color 200ms ease;
572
573    &::placeholder {
574      color: #665f56;
575      font-style: normal;
576    }
577
578    &:focus {
579      border-color: rgba(107, 158, 120, 0.4);
580      box-shadow: 0 0 0 2px rgba(107, 158, 120, 0.1);
581      background: #13120e;
582    }
583  }
584}
585
586// ── Dashboard toolbar ──
587// Input + button sharing one visual line — both get the same height (38px)
588// so the CTA doesn't read as a different size from the field next to it.
589
590.dashboard-toolbar {
591  display: flex;
592  gap: 0.5rem;
593  align-items: stretch;
594
595  .form-control {
596    height: 38px;
597    font-size: 0.85rem;
598  }
599
600  .btn {
601    height: 38px;
602    padding: 0 1.1rem;
603    display: inline-flex;
604    align-items: center;
605    white-space: nowrap;
606  }
607}
608
609// ── Chart panel ──
610
611.chart-panel {
612  background: $card-bg;
613  border: 1px solid $card-border-color;
614  border-radius: $border-radius;
615  padding: 1rem 1.15rem;
616
617  .chart-panel-header {
618    display: flex;
619    align-items: center;
620    justify-content: space-between;
621    margin-bottom: 0.75rem;
622  }
623
624  .chart-panel-title {
625    font-size: 0.68rem;
626    font-weight: 600;
627    letter-spacing: 0.12em;
628    text-transform: uppercase;
629    color: rgba(201, 168, 76, 0.75);
630    font-family: $font-family-monospace;
631  }
632}
633
634// ── Security list ──
635
636.check-list {
637  background: $card-bg;
638  border: 1px solid $card-border-color;
639  border-radius: $border-radius;
640  overflow: hidden;
641
642  .check-list-header {
643    background: rgba(107, 158, 120, 0.04);
644    padding: 0.6rem 1rem;
645    font-size: 0.68rem;
646    font-weight: 600;
647    letter-spacing: 0.12em;
648    text-transform: uppercase;
649    color: rgba(201, 168, 76, 0.75);
650    font-family: $font-family-monospace;
651    border-bottom: 1px solid rgba(107, 158, 120, 0.1);
652  }
653
654  .check-list-item {
655    display: flex;
656    justify-content: space-between;
657    align-items: center;
658    padding: 0.55rem 1rem;
659    font-size: 0.85rem;
660    border-bottom: 1px solid rgba(107, 158, 120, 0.04);
661
662    &:last-child { border-bottom: none; }
663
664    .check-name {
665      color: $gray-300;
666      display: flex;
667      align-items: center;
668      gap: 0.5rem;
669    }
670  }
671}
672
673// ── Monitor card ──
674// When used inside a flex/grid row, `h-100` class stretches the card to its
675// cell height. The body grows to fill any extra space so the two cards next
676// to each other stay visually balanced even with different dl row counts.
677
678.monitor-card {
679  background: $card-bg;
680  border: 1px solid $card-border-color;
681  border-radius: $border-radius;
682  overflow: hidden;
683  display: flex;
684  flex-direction: column;
685
686  .monitor-header {
687    display: flex;
688    justify-content: space-between;
689    align-items: center;
690    padding: 0.7rem 1rem;
691    background: rgba(107, 158, 120, 0.04);
692    border-bottom: 1px solid rgba(107, 158, 120, 0.08);
693    flex-shrink: 0;
694
695    .monitor-title {
696      font-family: $font-family-monospace;
697      font-size: 0.78rem;
698      font-weight: 600;
699      letter-spacing: 0.06em;
700      text-transform: uppercase;
701      color: $white;
702    }
703  }
704
705  .monitor-body {
706    padding: 0.9rem 1rem;
707    flex: 1 1 auto;
708
709    dl {
710      margin: 0;
711      display: grid;
712      grid-template-columns: max-content 1fr;
713      gap: 0.4rem 1rem;
714    }
715
716    dt {
717      font-size: 0.7rem;
718      letter-spacing: 0.06em;
719      text-transform: uppercase;
720      color: #665f56;
721      font-weight: 500;
722    }
723
724    dd {
725      margin: 0;
726      font-size: 0.8rem;
727      color: $gray-200;
728      font-family: $font-family-monospace;
729    }
730  }
731}
732
733// ── Insight row (lighthouse opportunities / crawler issues) ──
734
735.insight-header {
736  display: grid;
737  grid-template-columns: 110px 1fr 1fr 140px;
738  gap: 1rem;
739  padding: 0.55rem 1rem;
740  background: rgba(107, 158, 120, 0.04);
741  border: 1px solid rgba(107, 158, 120, 0.08);
742  border-radius: $border-radius;
743  font-family: $font-family-monospace;
744  font-size: 0.68rem;
745  letter-spacing: 0.08em;
746  text-transform: uppercase;
747  color: rgba(201, 168, 76, 0.75);
748  margin-bottom: 0.6rem;
749}
750
751.insight-row {
752  display: grid;
753  grid-template-columns: 110px 1fr 1fr 140px;
754  gap: 1rem;
755  padding: 0.65rem 1rem;
756  background: $card-bg;
757  border: 1px solid $card-border-color;
758  border-radius: $border-radius;
759  margin-bottom: 0.4rem;
760  font-size: 0.85rem;
761  align-items: center;
762
763  &:hover { border-color: rgba(107, 158, 120, 0.2); }
764
765  .insight-col-trunc {
766    min-width: 0;
767    overflow: hidden;
768    text-overflow: ellipsis;
769    white-space: nowrap;
770  }
771}
772
773@media (max-width: 767px) {
774  .insight-header { display: none; }
775  .insight-row {
776    grid-template-columns: 1fr;
777    gap: 0.4rem;
778  }
779}
780
781// ── Auth split view ──
782
783.auth-shell {
784  min-height: calc(100vh - 52px);
785  display: grid;
786  grid-template-columns: 1fr 1fr;
787
788  @media (max-width: 991px) {
789    grid-template-columns: 1fr;
790  }
791
792  .auth-form-col {
793    padding: 3rem 2rem;
794    display: flex;
795    align-items: center;
796    justify-content: center;
797  }
798
799  .auth-visual-col {
800    position: relative;
801    overflow: hidden;
802    background: linear-gradient(145deg, #090806 0%, #0e0d0a 60%, #13120e 100%);
803    border-left: 1px solid rgba(107, 158, 120, 0.08);
804
805    @media (max-width: 991px) { display: none; }
806  }
807
808  .auth-form-wrap {
809    width: 100%;
810    max-width: 380px;
811  }
812}