feat: multi-Oracle server support with runtime switching

Complete implementation of multi-server Oracle database support:

Backend:
- Multi-pool Oracle with lazy loading per server
- Email-to-server cache for automatic server discovery
- JWT tokens include server_id claim
- /auth/check-identity and /auth/check-email endpoints
- /auth/my-servers endpoint for listing user's accessible servers
- Server switch with password re-authentication

Frontend:
- New ServerSelector component for header dropdown
- Multi-step login flow (identity → server → password)
- Server switching from header with password modal
- Mobile drawer menu with server selection
- Dark mode support for all new components
- URL bookmark support with ?server= query param

Scripts:
- Unified start.sh replacing start-prod.sh/start-test.sh
- Unified ssh-tunnel.sh with multi-server support
- Updated status.sh for new architecture

Tests:
- E2E tests for multi-server and single-server login flows
- Backend unit tests for all new endpoints
- Oracle multi-pool integration tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-26 22:39:06 +00:00
parent 5f99ee2fd0
commit b137e80b71
102 changed files with 9398 additions and 2787 deletions

View File

@@ -132,6 +132,38 @@
background-color: rgba(255, 255, 255, 0.1);
}
/* Server Badge (US-026) */
.server-badge {
display: flex;
align-items: center;
gap: var(--space-xs, 4px);
padding: var(--space-xs, 4px) var(--space-sm, 8px);
background: var(--primary-100, #dbeafe);
color: var(--primary-700, #1d4ed8);
border-radius: var(--radius-sm, 4px);
font-size: 0.75rem;
font-weight: var(--font-semibold, 600);
}
.server-badge i {
font-size: 0.75rem;
}
/* Dark mode support for server badge */
[data-theme="dark"] .server-badge {
background: var(--primary-900, #1e3a8a);
color: var(--primary-200, #bfdbfe);
}
/* Gradient header server badge */
.header-container--gradient .server-badge {
background: rgba(255, 255, 255, 0.2);
color: white;
}
/* Server Switch - ServerSelector component has its own scoped styles */
/* Only header-specific overrides here if needed */
/* Theme Toggle Button */
.theme-toggle-btn {
display: flex;
@@ -202,3 +234,129 @@
font-size: 1.5rem;
}
}
/* Server Switch Password Modal (US-009) */
.server-switch-modal .server-switch-modal-content {
display: flex;
flex-direction: column;
gap: var(--space-md, 16px);
}
.server-switch-modal .form-field {
display: flex;
flex-direction: column;
gap: var(--space-xs, 4px);
}
.server-switch-modal .switch-error {
display: flex;
align-items: center;
gap: var(--space-sm, 8px);
padding: var(--space-sm, 8px) var(--space-md, 12px);
background: var(--red-50, #fef2f2);
border: 1px solid var(--red-200, #fecaca);
border-radius: var(--radius-sm, 4px);
color: var(--red-700, #b91c1c);
font-size: 0.875rem;
}
.server-switch-modal .switch-error i {
color: var(--red-500, #ef4444);
}
/* Dark mode support for modal */
[data-theme="dark"] .server-switch-modal .switch-error {
background: var(--red-900, #7f1d1d);
border-color: var(--red-700, #b91c1c);
color: var(--red-200, #fecaca);
}
[data-theme="dark"] .server-switch-modal .switch-error i {
color: var(--red-400, #f87171);
}
/* ===== Server Switch Password Modal Dialog ===== */
.server-switch-modal .p-dialog {
border-radius: var(--radius-lg) !important;
box-shadow: var(--shadow-lg) !important;
background: var(--surface-card) !important;
border: 1px solid var(--surface-border) !important;
min-width: 320px !important;
}
.server-switch-modal .p-dialog-header {
background: var(--surface-card) !important;
border-bottom: 1px solid var(--surface-border) !important;
padding: var(--space-md) var(--space-lg) !important;
}
.server-switch-modal .p-dialog-content {
background: var(--surface-card) !important;
padding: var(--space-lg) !important;
}
.server-switch-modal .p-dialog-footer {
background: var(--surface-card) !important;
border-top: 1px solid var(--surface-border) !important;
padding: var(--space-md) var(--space-lg) !important;
}
/* Dark mode support for modal dialog */
[data-theme="dark"] .server-switch-modal .p-dialog {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4) !important;
background: var(--surface-card) !important;
}
[data-theme="dark"] .server-switch-modal .p-dialog-header {
background: var(--surface-card) !important;
color: var(--text-color) !important;
}
[data-theme="dark"] .server-switch-modal .p-dialog-header .p-dialog-title {
color: var(--text-color) !important;
}
[data-theme="dark"] .server-switch-modal .p-dialog-content {
background: var(--surface-card) !important;
}
[data-theme="dark"] .server-switch-modal .p-dialog-footer {
background: var(--surface-card) !important;
}
/* Dark mode support for Password input inside modal */
[data-theme="dark"] .server-switch-modal .p-password .p-inputtext {
background: var(--surface-overlay, #374151) !important;
color: var(--text-color, #f9fafb) !important;
border-color: var(--surface-border, #4b5563) !important;
}
[data-theme="dark"] .server-switch-modal .p-password .p-inputtext::placeholder {
color: var(--text-color-secondary, #9ca3af) !important;
}
[data-theme="dark"] .server-switch-modal .p-password .p-inputtext:focus {
border-color: var(--primary-400, #60a5fa) !important;
}
/* Password toggle icon dark mode */
[data-theme="dark"] .server-switch-modal .p-password-toggle-icon {
color: var(--text-color-secondary, #9ca3af) !important;
}
/* System preference dark mode support for Password input */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .server-switch-modal .p-password .p-inputtext {
background: var(--surface-overlay, #374151) !important;
color: var(--text-color, #f9fafb) !important;
border-color: var(--surface-border, #4b5563) !important;
}
:root:not([data-theme]) .server-switch-modal .p-password .p-inputtext::placeholder {
color: var(--text-color-secondary, #9ca3af) !important;
}
:root:not([data-theme]) .server-switch-modal .p-password-toggle-icon {
color: var(--text-color-secondary, #9ca3af) !important;
}
}

View File

@@ -89,8 +89,23 @@
.login-footer {
text-align: center;
padding: 1rem 2rem;
background-color: var(--surface-50);
border-top: 1px solid var(--surface-200);
background-color: var(--surface-ground);
border-top: 1px solid var(--surface-border);
}
.login-footer small {
color: var(--text-color-secondary);
}
/* Dark mode support */
[data-theme="dark"] .login-footer {
background-color: var(--surface-ground);
}
[data-theme="dark"] .login-error-message {
background-color: rgba(239, 68, 68, 0.15);
color: var(--red-300);
border-color: var(--red-800);
}
/* Responsive design */