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

@@ -126,8 +126,11 @@ export function BuildsPage() {
[isDemoMode, buildsQuery.data?.builds],
);
const subscribedBuildIds = useMemo(() => builds.map((build) => build.id), [builds]);
const liveConnected = useBuildUpdates(subscribedBuildIds, ({ channel }) => {
const subscribedBuildIds = useMemo(
() => (isDemoMode ? [] : builds.map((build) => build.id)),
[isDemoMode, builds],
);
const liveStatus = useBuildUpdates(subscribedBuildIds, ({ channel }) => {
queryClient.invalidateQueries({ queryKey: ['builds-page'] });
queryClient.invalidateQueries({ queryKey: ['usage-builds'] });
if (selectedBuild && channel === `build:${selectedBuild.id}`) {
@@ -149,9 +152,6 @@ export function BuildsPage() {
queryFn: () => getBuildLogs(selectedBuild!.id),
});
const isCancellingBuild = (buildId: string): boolean =>
cancelMutation.isPending && cancelMutation.variables === buildId;
// Stats
const stats = useMemo(() => {
const running = builds.filter(b => b.status === 'running' || b.status === 'pending').length;
@@ -173,9 +173,9 @@ export function BuildsPage() {
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 text-sm">
<div className={`w-2 h-2 rounded-full ${liveConnected ? 'bg-[var(--success)] animate-pulse' : 'bg-[var(--text-muted)]'}`} />
<span className={liveConnected ? 'text-[var(--success)]' : 'text-[var(--text-muted)]'}>
{liveConnected ? 'Live' : 'Offline'}
<div className={`w-2 h-2 rounded-full ${liveStatus === 'live' ? 'bg-[var(--success)] animate-pulse' : 'bg-[var(--text-muted)]'}`} />
<span className={liveStatus === 'live' ? 'text-[var(--success)]' : 'text-[var(--text-muted)]'}>
{liveStatus === 'live' ? 'Live' : liveStatus === 'offline' ? 'Reconnecting...' : 'Polling'}
</span>
</div>
<button