Overhaul: real metrics, Git integration UI, open-source hygiene, root cleanup

Backend:
- GET /services/:id/metrics with real Docker container stats
- DELETE /git/providers/:id and GET repo branches endpoint

Frontend:
- Service detail shows real telemetry or honest empty states
- Live status distinguishes idle/polling/reconnecting
- Git providers section in Settings; repo+branch pickers in Add Service
- Real logo, compact Projects header, canvas fitView fix
- index.css deduplicated (1725 -> 1384 lines)
- Fixed smoke tests, lint errors, ToastProvider/context unification

Repo hygiene:
- Added LICENSE (MIT), CONTRIBUTING.md, SECURITY.md; rewrote README
- Untracked .env.prod and data/letsencrypt secrets; fixed .gitignore
- Removed legacy root src/internal/cmd and stale configs/docs/artifacts
- Moved guides and design references under docs/

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-15 18:59:40 +02:00
1 parent 355a97bab4
commit 9f3b196032
499 files changed
+4471 -94235

No files matched your search

+77 -351
View File
@@ -84,6 +84,76 @@
--bg-0: var(--bg-void);
--bg-1: var(--bg-base);
--bg-2: var(--bg-surface);
--bg-primary: var(--bg-base);
}
:root[data-theme='light'] {
--bg-void: #f4f6fb;
--bg-base: #ffffff;
--bg-surface: #eef1f7;
--bg-elevated: #ffffff;
--bg-overlay: #ffffff;
--surface-card: #ffffff;
--surface-card-hover: #eef1f7;
--surface-muted: rgba(17, 18, 23, 0.05);
--surface-glass: rgba(255, 255, 255, 0.72);
--surface-base: #ffffff;
--text-primary: #151821;
--text-secondary: #4f5668;
--text-tertiary: #7b8498;
--text-muted: #7b8498;
--accent-primary: #d92d64;
--accent-primary-soft: rgba(217, 45, 100, 0.12);
--accent-primary-glow: rgba(217, 45, 100, 0.22);
--accent-secondary: #7357d8;
--accent-tertiary: #2f67d8;
--success: #168a55;
--success-soft: rgba(22, 138, 85, 0.12);
--success-glow: rgba(22, 138, 85, 0.2);
--warning: #c7511f;
--warning-soft: rgba(199, 81, 31, 0.12);
--warning-glow: rgba(199, 81, 31, 0.2);
--error: #c9362b;
--error-soft: rgba(201, 54, 43, 0.12);
--error-glow: rgba(201, 54, 43, 0.2);
--info: #2f67d8;
--info-soft: rgba(47, 103, 216, 0.12);
--border-subtle: rgba(21, 24, 33, 0.1);
--border-default: rgba(21, 24, 33, 0.16);
--border-strong: rgba(217, 45, 100, 0.35);
--border-glow: rgba(217, 45, 100, 0.45);
--shadow-sm: 0 1px 2px rgba(17, 18, 23, 0.08);
--shadow-md: 0 4px 12px rgba(17, 18, 23, 0.1);
--shadow-lg: 0 8px 30px rgba(17, 18, 23, 0.12);
--shadow-xl: 0 16px 48px rgba(17, 18, 23, 0.16);
--shadow-glow: 0 0 20px var(--accent-primary-glow);
--shadow-glow-lg: 0 0 40px var(--accent-primary-glow);
--text: var(--text-primary);
--text-dim: var(--text-secondary);
--line: var(--border-default);
--line-strong: var(--border-strong);
--accent: var(--accent-primary);
--accent-soft: var(--accent-primary-soft);
--ok: var(--success);
--warn: var(--warning);
--bad: var(--error);
--surface: var(--bg-elevated);
--surface-soft: var(--surface-card);
--surface-muted: var(--bg-surface);
--bg-0: var(--bg-void);
--bg-1: var(--bg-base);
--bg-2: var(--bg-surface);
--bg-primary: var(--bg-base);
}
* {
@@ -100,7 +170,7 @@ body,
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
color: var(--text-primary);
background: #16171c;
background: var(--bg-void);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -115,7 +185,7 @@ body {
.app-shell {
min-height: 100vh;
position: relative;
background: #16171c;
background: var(--bg-void);
}
/* Ambient glow effects for premium depth */
@@ -162,56 +232,6 @@ body {
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideOutRight {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(20px);
}
}
/* Animations from self.html */
@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Live pulse animation for status indicators */
@keyframes livePulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.15);
}
}
.live-pulse {
animation: livePulse 2s ease-in-out infinite;
}
/* Subtle glow pulse for active elements */
@keyframes glowPulse {
0%, 100% {
@@ -226,22 +246,6 @@ body {
animation: glowPulse 3s ease-in-out infinite;
}
/* Slide in from right - for toasts and modals */
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.animate-slide-in-right {
animation: slideInRight 0.3s ease-out;
}
/* Fade in scale - for modals and overlays */
@keyframes fadeInScale {
from {
@@ -270,28 +274,6 @@ body {
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideOutRight {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(20px);
}
}
a {
color: inherit;
text-decoration: none;
@@ -365,177 +347,6 @@ textarea {
border-radius: 16px;
}
/* Card icon - matching self.html */
.card-icon {
width: 34px;
height: 34px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.07);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.card-icon svg {
width: 16px;
height: 16px;
stroke: #9295a4;
fill: none;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Arrow button - matching self.html */
.arrow-btn {
width: 30px;
height: 30px;
border-radius: 9px;
background: #e8316a;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
transition: box-shadow 0.15s, transform 0.15s;
}
.arrow-btn:hover {
transform: scale(1.07);
}
.arrow-btn svg {
width: 13px;
height: 13px;
stroke: white;
fill: none;
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Pill group - matching self.html */
.pill-group {
display: flex;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 10px;
overflow: hidden;
}
.pill {
padding: 5px 14px;
font-size: 12.5px;
font-weight: 500;
color: #6b6e7d;
cursor: pointer;
transition: all 0.15s;
user-select: none;
}
.pill.active {
background: rgba(255, 255, 255, 0.1);
color: #e8e9f0;
}
.pill:hover:not(.active) {
color: #9295a4;
}
/* Flag - matching self.html */
.flag {
width: 26px;
height: 26px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
background: rgba(255, 255, 255, 0.05);
}
/* Stat dot - matching self.html */
.stat-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
/* Cache segment - matching self.html */
.cache-seg {
height: 32px;
transition: opacity 0.2s, filter 0.2s;
cursor: pointer;
}
.cache-seg:hover {
filter: brightness(1.15);
}
/* Speed row - matching self.html */
.speed-row {
display: flex;
align-items: center;
gap: 5px;
font-size: 12.5px;
font-weight: 600;
}
.speed-row svg {
width: 12px;
height: 12px;
stroke: currentColor;
fill: none;
stroke-width: 2.5;
stroke-linecap: round;
}
/* Live indicator - matching self.html */
.live-dot {
width: 8px;
height: 8px;
background: #3dd68c;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
animation: pulse-dot 2s infinite;
}
.live-dot.stopped {
background: #6b6e7d;
animation: none;
}
/* Badge - matching self.html */
.badge-active {
padding: 2px 8px;
border-radius: 6px;
background: rgba(61, 214, 140, 0.15);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.7px;
color: #3dd68c;
text-transform: uppercase;
transition: all 0.3s;
display: inline-flex;
align-items: center;
}
.badge-stopped {
padding: 2px 8px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.08);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.7px;
color: #6b6e7d;
text-transform: uppercase;
display: inline-flex;
align-items: center;
}
/* Subtle grid background - Railway-style dots */
.subtle-grid {
background-image:
@@ -589,18 +400,7 @@ textarea {
100% { transform: scale(2); opacity: 0; }
}
@keyframes pulse-dot {
0% { box-shadow: 0 0 0 0 rgba(61, 214, 140, 0.7); }
70% { box-shadow: 0 0 0 6px rgba(61, 214, 140, 0); }
100% { box-shadow: 0 0 0 0 rgba(61, 214, 140, 0); }
}
/* Card animations - matching self.html */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-up {
animation: fadeUp 0.4s ease both;
}
@@ -718,17 +518,6 @@ textarea {
100% { background-position: 200% 0; }
}
.shimmer {
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.03) 25%,
rgba(255, 255, 255, 0.08) 50%,
rgba(255, 255, 255, 0.03) 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
/* Hover lift effect for cards */
.hover-lift {
transition: transform 0.2s ease, box-shadow 0.2s ease;
@@ -778,18 +567,6 @@ textarea {
animation: gradient-rotate 3s ease infinite;
}
/* Ripple effect for buttons */
@keyframes ripple {
0% {
transform: scale(0);
opacity: 0.5;
}
100% {
transform: scale(4);
opacity: 0;
}
}
.ripple-container {
position: relative;
overflow: hidden;
@@ -1073,8 +850,8 @@ textarea {
display: flex;
align-items: center;
gap: 8px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
background: var(--surface-muted);
border: 1px solid var(--border-subtle);
border-radius: 10px;
padding: 0 12px;
height: 34px;
@@ -1084,7 +861,7 @@ textarea {
.search-box svg {
width: 14px;
height: 14px;
stroke: #6b6e7d;
stroke: var(--text-tertiary);
fill: none;
stroke-width: 2;
stroke-linecap: round;
@@ -1095,14 +872,14 @@ textarea {
background: none;
border: none;
outline: none;
color: #9295a4;
color: var(--text-secondary);
font-size: 13px;
width: 100%;
font-family: inherit;
}
.search-box input::placeholder {
color: #6b6e7d;
color: var(--text-tertiary);
}
/* Tab - self.html exact match */
@@ -1172,47 +949,6 @@ textarea {
color: #9295a4;
}
/* Arrow Button - self.html style (pink circular) */
.arrow-btn {
width: 30px;
height: 30px;
border-radius: 9px;
background: var(--accent-primary);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
transition: box-shadow 0.15s, transform 0.15s;
}
.arrow-btn:hover {
transform: scale(1.07);
}
.arrow-btn svg {
width: 13px;
height: 13px;
stroke: white;
fill: none;
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
}
/* Live Dot Pulse - self.html style */
@keyframes pulse-dot {
0% {
box-shadow: 0 0 0 0 rgba(61, 214, 140, 0.7);
}
70% {
box-shadow: 0 0 0 6px rgba(61, 214, 140, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(61, 214, 140, 0);
}
}
.live-dot {
width: 8px;
height: 8px;
@@ -1489,16 +1225,6 @@ textarea {
animation: ripple 0.4s ease-out;
}
/* Shimmer loading effect */
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
.shimmer {
background: linear-gradient(
90deg,