mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
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:
1 parent
355a97bab4
commit
9f3b196032
499 files changed
+4471
-94235
No files matched your search
+14
-183
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user