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

+33 -28
View File
@@ -16,6 +16,12 @@ export type AuthSessionPayload = {
session: AuthSession;
};
export type AuthBootstrap = {
hasUsers: boolean;
userCount: number;
mode: 'register' | 'login';
};
export class AuthError extends Error {
readonly status: number;
@@ -33,6 +39,30 @@ export function getAuthBaseUrl(): string {
return AUTH_BASE;
}
export async function getAuthBootstrap(): Promise<AuthBootstrap> {
const apiBase = ((import.meta.env.VITE_API_URL as string | undefined) ?? 'http://localhost:8082').replace(/\/$/, '');
const normalizedApiBase = /\/api\/v1$/.test(apiBase) ? apiBase : `${apiBase}/api/v1`;
const response = await fetch(`${normalizedApiBase}/auth/bootstrap`, {
method: 'GET',
credentials: 'include',
});
const payload = (await response.json().catch(() => null)) as Record<string, unknown> | null;
if (!response.ok) {
const message =
(payload?.message as string | undefined) ??
(payload?.error as string | undefined) ??
`Auth bootstrap request failed with status ${response.status}`;
throw new AuthError(message, response.status);
}
return {
hasUsers: Boolean(payload?.has_users),
userCount: typeof payload?.user_count === 'number' ? payload.user_count : 0,
mode: payload?.mode === 'register' ? 'register' : 'login',
};
}
function normalizeSessionPayload(payload: unknown): AuthSessionPayload | null {
if (!payload || typeof payload !== 'object') {
return null;
@@ -160,19 +190,6 @@ export async function signUpWithEmail(name: string, email: string, password: str
return normalizeSessionPayload(payload);
}
export async function requestMagicLinkInvite(email: string, callbackURL: string): Promise<void> {
await authRequest('/sign-in/magic-link', {
method: 'POST',
body: JSON.stringify({
email,
callbackURL,
metadata: {
invite: true,
},
}),
});
}
export async function startGitHubSignIn(callbackURL: string): Promise<void> {
const payload = await authRequest('/sign-in/social', {
method: 'POST',
@@ -189,11 +206,11 @@ export async function startGitHubSignIn(callbackURL: string): Promise<void> {
}
}
async function startOAuth2ProviderSignIn(providerId: string, callbackURL: string): Promise<void> {
const payload = await authRequest('/sign-in/oauth2', {
export async function startGoogleSignIn(callbackURL: string): Promise<void> {
const payload = await authRequest('/sign-in/social', {
method: 'POST',
body: JSON.stringify({
providerId,
provider: 'google',
callbackURL,
disableRedirect: true,
}),
@@ -205,18 +222,6 @@ async function startOAuth2ProviderSignIn(providerId: string, callbackURL: string
}
}
export async function startGitLabSignIn(callbackURL: string): Promise<void> {
await startOAuth2ProviderSignIn('gitlab', callbackURL);
}
export async function startBitbucketSignIn(callbackURL: string): Promise<void> {
await startOAuth2ProviderSignIn('bitbucket', callbackURL);
}
export async function startGiteaSignIn(callbackURL: string): Promise<void> {
await startOAuth2ProviderSignIn('gitea', callbackURL);
}
export async function signOutAuthSession(): Promise<void> {
await authRequest('/sign-out', {
method: 'POST',