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

+14 -183
View File
@@ -1,6 +1,4 @@
import { betterAuth } from 'better-auth';
import { magicLink } from 'better-auth/plugins/magic-link';
import { genericOAuth } from 'better-auth/plugins/generic-oauth';
import { Pool } from 'pg';
import { PostgresDialect } from 'kysely';
import { getMigrations } from 'better-auth/db/migration';
@@ -50,41 +48,22 @@ const database = new PostgresDialect({
const githubClientId = env.GITHUB_CLIENT_ID?.trim() || '';
const githubClientSecret = env.GITHUB_CLIENT_SECRET?.trim() || '';
const gitlabClientId = env.GITLAB_CLIENT_ID?.trim() || 'PLACEHOLDER_GITLAB_CLIENT_ID';
const gitlabClientSecret = env.GITLAB_CLIENT_SECRET?.trim() || 'PLACEHOLDER_GITLAB_CLIENT_SECRET';
const gitlabAuthorizeUrl =
env.GITLAB_OAUTH_AUTHORIZE_URL?.trim() || 'https://gitlab.com/oauth/authorize';
const gitlabTokenUrl = env.GITLAB_OAUTH_TOKEN_URL?.trim() || 'https://gitlab.com/oauth/token';
const gitlabUserUrl = env.GITLAB_OAUTH_USERINFO_URL?.trim() || 'https://gitlab.com/api/v4/user';
const giteaClientId = env.GITEA_CLIENT_ID?.trim() || 'PLACEHOLDER_GITEA_CLIENT_ID';
const giteaClientSecret = env.GITEA_CLIENT_SECRET?.trim() || 'PLACEHOLDER_GITEA_CLIENT_SECRET';
const giteaAuthorizeUrl =
env.GITEA_OAUTH_AUTHORIZE_URL?.trim() ||
'https://gitea.example.com/login/oauth/authorize';
const giteaTokenUrl =
env.GITEA_OAUTH_TOKEN_URL?.trim() ||
'https://gitea.example.com/login/oauth/access_token';
const giteaUserUrl = env.GITEA_OAUTH_USERINFO_URL?.trim() || 'https://gitea.example.com/api/v1/user';
const bitbucketClientId = env.BITBUCKET_CLIENT_ID?.trim() || 'PLACEHOLDER_BITBUCKET_CLIENT_ID';
const bitbucketClientSecret =
env.BITBUCKET_CLIENT_SECRET?.trim() || 'PLACEHOLDER_BITBUCKET_CLIENT_SECRET';
const bitbucketAuthorizeUrl =
env.BITBUCKET_OAUTH_AUTHORIZE_URL?.trim() || 'https://bitbucket.org/site/oauth2/authorize';
const bitbucketTokenUrl =
env.BITBUCKET_OAUTH_TOKEN_URL?.trim() || 'https://bitbucket.org/site/oauth2/access_token';
const bitbucketUserUrl =
env.BITBUCKET_OAUTH_USERINFO_URL?.trim() || 'https://api.bitbucket.org/2.0/user';
const bitbucketEmailsUrl =
env.BITBUCKET_OAUTH_EMAILS_URL?.trim() || 'https://api.bitbucket.org/2.0/user/emails';
const googleClientId = env.GOOGLE_CLIENT_ID?.trim() || '';
const googleClientSecret = env.GOOGLE_CLIENT_SECRET?.trim() || '';
const socialProviders = {};
if (githubClientId && githubClientSecret) {
socialProviders.github = {
clientId: githubClientId,
clientSecret: githubClientSecret,
disableImplicitSignUp: true,
};
}
if (googleClientId && googleClientSecret) {
socialProviders.google = {
clientId: googleClientId,
clientSecret: googleClientSecret,
disableImplicitSignUp: true,
};
}
@@ -97,6 +76,10 @@ const trustedOrigins = [
'http://localhost:3000',
'http://localhost:8082',
'http://localhost:3001',
'http://localhost:5173',
'http://localhost:5174',
'http://127.0.0.1:5173',
'http://127.0.0.1:5174',
]
.map((origin) => (origin || '').trim())
.filter(Boolean);
@@ -130,158 +113,6 @@ export const auth = betterAuth({
maxPasswordLength: 128,
},
socialProviders: Object.keys(socialProviders).length > 0 ? socialProviders : undefined,
plugins: [
magicLink({
sendMagicLink: async ({ email, url, token, metadata }) => {
const payload = {
email,
url,
token,
metadata,
};
console.log('[better-auth] magic-link', JSON.stringify(payload));
},
disableSignUp: false,
}),
genericOAuth({
config: [
{
providerId: 'gitlab',
clientId: gitlabClientId,
clientSecret: gitlabClientSecret,
authorizationUrl: gitlabAuthorizeUrl,
tokenUrl: gitlabTokenUrl,
userInfoUrl: gitlabUserUrl,
scopes: ['read_user', 'read_api'],
getUserInfo: async (tokens) => {
if (!tokens.accessToken) {
return null;
}
const response = await fetch(gitlabUserUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${tokens.accessToken}`,
},
});
if (!response.ok) {
return null;
}
const profile = await response.json();
if (!profile || (!profile.id && !profile.username)) {
return null;
}
return {
id: profile.id || profile.username,
email: profile.email || null,
name: profile.name || profile.username || 'GitLab User',
image: profile.avatar_url || null,
emailVerified: Boolean(profile.email),
};
},
},
{
providerId: 'gitea',
clientId: giteaClientId,
clientSecret: giteaClientSecret,
authorizationUrl: giteaAuthorizeUrl,
tokenUrl: giteaTokenUrl,
userInfoUrl: giteaUserUrl,
scopes: ['read:user', 'read:repository'],
getUserInfo: async (tokens) => {
if (!tokens.accessToken) {
return null;
}
const response = await fetch(giteaUserUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `token ${tokens.accessToken}`,
},
});
if (!response.ok) {
return null;
}
const profile = await response.json();
if (!profile || (!profile.id && !profile.login)) {
return null;
}
return {
id: profile.id || profile.login,
email: profile.email || null,
name: profile.full_name || profile.login || 'Gitea User',
image: profile.avatar_url || null,
emailVerified: Boolean(profile.email),
};
},
},
{
providerId: 'bitbucket',
clientId: bitbucketClientId,
clientSecret: bitbucketClientSecret,
authorizationUrl: bitbucketAuthorizeUrl,
tokenUrl: bitbucketTokenUrl,
userInfoUrl: bitbucketUserUrl,
scopes: ['account', 'email', 'repository'],
getUserInfo: async (tokens) => {
if (!tokens.accessToken) {
return null;
}
const userResponse = await fetch(bitbucketUserUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${tokens.accessToken}`,
},
});
if (!userResponse.ok) {
return null;
}
const user = await userResponse.json();
if (!user || (!user.account_id && !user.username && !user.nickname)) {
return null;
}
let primaryEmail = null;
const emailsResponse = await fetch(bitbucketEmailsUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${tokens.accessToken}`,
},
});
if (emailsResponse.ok) {
const emailsPayload = await emailsResponse.json();
const values = Array.isArray(emailsPayload?.values) ? emailsPayload.values : [];
const primary = values.find((entry) => entry?.is_primary) || values[0];
if (primary && typeof primary.email === 'string') {
primaryEmail = primary.email;
}
}
return {
id: user.account_id || user.username || user.nickname,
email: primaryEmail,
name: user.display_name || user.nickname || 'Bitbucket User',
image: user?.links?.avatar?.href || null,
emailVerified: Boolean(primaryEmail),
};
},
},
],
}),
],
});
export async function runAuthMigrations() {