feat: move configuration from .env to DB with Admin UI management
Replace hardcoded .env configuration with database-backed settings
manageable through the Admin web interface. This reduces .env to
bootstrap-only variables (DB, Keycloak, encryption keys).
Backend:
- Add SystemSetting Prisma model with category, valueType, isSecret
- Add system-settings NestJS module (CRUD, 60s cache, encryption)
- Refactor all 7 connectors to lazy-load credentials from DB via
CredentialsService.findActiveByType() instead of ConfigService
- Add event-driven credential reload (@nestjs/event-emitter)
- Dynamic CORS origins and conditional Swagger from DB settings
- Fix JWT strategy: use Keycloak JWKS (RS256) instead of symmetric secret
- Add SYSTEM_SETTINGS_VIEW/MANAGE permissions
- Seed 13 default settings (sync intervals, features, branding, CORS)
- Add env-to-db migration script (prisma/migrate-env-to-db.ts)
Frontend:
- Add use-credentials hook (full CRUD for integration credentials)
- Add use-system-settings hook (read/update system settings)
- Wire admin-integrations page to real API (create/update/test/toggle)
- Add admin system-settings page with 4 tabs (Branding, CORS, Sync, Features)
- Fix sidebar double-highlighting with exactMatch flag
- Fix integration detail fallback when API unavailable
- Fix API client to unwrap backend's {success, data} envelope
- Update NEXT_PUBLIC_API_URL to include /v1 version prefix
- Fix activity-widget hydration error
- Add i18n keys for systemSettings (de + en)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ interface NavItem {
|
||||
icon: LucideIcon;
|
||||
requiredRoles?: UserRole[];
|
||||
children?: NavItem[];
|
||||
exactMatch?: boolean;
|
||||
}
|
||||
|
||||
/** Main navigation structure with role requirements */
|
||||
@@ -115,6 +116,7 @@ const mainNavItems: NavItem[] = [
|
||||
key: 'overview',
|
||||
href: '/integrations',
|
||||
icon: Plug,
|
||||
exactMatch: true,
|
||||
},
|
||||
{
|
||||
key: 'plentyOne',
|
||||
@@ -152,6 +154,11 @@ const bottomNavItems: NavItem[] = [
|
||||
href: '/admin/integrations',
|
||||
icon: Plug,
|
||||
},
|
||||
{
|
||||
key: 'systemSettings',
|
||||
href: '/admin/settings',
|
||||
icon: Settings,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -209,14 +216,15 @@ export function Sidebar({ locale }: SidebarProps) {
|
||||
const filteredMainNav = filterNavItems(mainNavItems, userRoles);
|
||||
const filteredBottomNav = filterNavItems(bottomNavItems, userRoles);
|
||||
|
||||
const isActive = (href: string) => {
|
||||
const isActive = (href: string, exactMatch?: boolean) => {
|
||||
const localePath = `/${locale}${href}`;
|
||||
if (exactMatch) return pathname === localePath;
|
||||
return pathname === localePath || pathname.startsWith(`${localePath}/`);
|
||||
};
|
||||
|
||||
const isParentActive = (item: NavItem) => {
|
||||
if (isActive(item.href)) return true;
|
||||
return item.children?.some((child) => isActive(child.href)) || false;
|
||||
return item.children?.some((child) => isActive(child.href, child.exactMatch)) || false;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -307,7 +315,7 @@ interface NavItemComponentProps {
|
||||
item: NavItem;
|
||||
locale: string;
|
||||
isExpanded: boolean;
|
||||
isActive: (href: string) => boolean;
|
||||
isActive: (href: string, exactMatch?: boolean) => boolean;
|
||||
isParentActive: (item: NavItem) => boolean;
|
||||
t: (key: string) => string;
|
||||
}
|
||||
@@ -321,7 +329,7 @@ function NavItemComponent({
|
||||
t,
|
||||
}: NavItemComponentProps) {
|
||||
const Icon = item.icon;
|
||||
const active = isActive(item.href);
|
||||
const active = isActive(item.href, item.exactMatch);
|
||||
const parentActive = isParentActive(item);
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
|
||||
@@ -383,7 +391,7 @@ function NavItemComponent({
|
||||
href={`/${locale}${child.href}`}
|
||||
className={cn(
|
||||
'rounded px-2 py-1 text-sm transition-colors hover:bg-accent',
|
||||
isActive(child.href) && 'bg-accent font-medium'
|
||||
isActive(child.href, child.exactMatch) && 'bg-accent font-medium'
|
||||
)}
|
||||
>
|
||||
{t(child.key)}
|
||||
@@ -426,7 +434,7 @@ function NavItemComponent({
|
||||
>
|
||||
{item.children?.map((child) => {
|
||||
const ChildIcon = child.icon;
|
||||
const childActive = isActive(child.href);
|
||||
const childActive = isActive(child.href, child.exactMatch);
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
||||
Reference in New Issue
Block a user