diff --git a/backend/config/application.tmpl b/backend/config/application.tmpl index e22578c..6239247 100644 --- a/backend/config/application.tmpl +++ b/backend/config/application.tmpl @@ -46,16 +46,12 @@ spring: default-property-inclusion: non_null security: oauth2: - resourceserver: - jwt: - issuer-uri: {{ .Env.KEYCLOAK_AUTH_URL }}realms/{{ .Env.KEYCLOAK_REALM }} client: registration: keycloak: authorization-grant-type: authorization_code client-id: {{ .Env.KEYCLOAK_CLIENT_ID }} client-secret: {{ .Env.KEYCLOAK_CLIENT_SECRET }} - redirect-uri: "http://localhost:8090/services/login/oauth2/code/keycloak" scope: openid provider: keycloak: @@ -63,8 +59,8 @@ spring: user-name-attribute: preferred_username app: frontend: - auth-callback-url: {{ .Env.FRONTEND_URL }}/auth-callback - base-url: {{ .Env.FRONTEND_URL }} + auth-callback-url: {{ .Env.PUBLIC_HOST }}/auth-callback + base-url: {{ .Env.PUBLIC_HOST }} data_quality_tool: json-to-excel-url: {{ .Env.DQT_URL }}/json-to-excel excel-to-json-url: {{ .Env.DQT_URL }}/excel-to-json \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 8e9fd89..3d42f08 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,5 +1,3 @@ - - services: data_quality_tool: build: @@ -41,7 +39,7 @@ services: DB_USER: postgres DB_PASSWORD: test ### Frontend ### - FRONTEND_URL: http://localhost + PUBLIC_HOST: http://localhost DQT_URL: http://data_quality_tool:8000 ### Keycloak ### AUTHENTICATION: 1 diff --git a/frontend/src/app/pages/account-page/account-page.component.css b/frontend/src/app/pages/account-page/account-page.component.css index 93e5c52..bf0812c 100644 --- a/frontend/src/app/pages/account-page/account-page.component.css +++ b/frontend/src/app/pages/account-page/account-page.component.css @@ -3,98 +3,65 @@ /* General Styling */ .account-page { display: flex; + justify-content: center; + align-items: center; height: 100vh; - margin-top: 20px; font-family: 'Roboto', sans-serif; -} - -.sidebar { - width: 250px; - background-color: #f5f5f5; - transition: width 0.3s ease-in-out; - overflow: hidden; - position: relative; -} - -.sidebar.collapsed { - width: 4%; /* Collapsed width for smaller sidebar */ -} - -.sidebar-toggle { - cursor: pointer; - padding: 10px; - text-align: center; - background-color: #18a2b8; - color: white; -} - -.sidebar-nav { - list-style: none; - padding: 20px 0; + background-color: #f4f6f8; margin: 0; } -.sidebar-nav li { - margin: 10px 0; -} - -.sidebar-nav li a { - display: flex; - align-items: center; - padding: 10px 15px; - text-decoration: none; - color: #333; - transition: background-color 0.2s; -} - -.sidebar-nav li a i { - font-size: 1.5em; -} - -.sidebar-nav li a span { - margin-left: 10px; -} - -.sidebar-nav li a:hover, .sidebar-nav li a.active { - background-color: #18a2b8; - color: white; -} - /* Main Content Styling */ .content { - flex-grow: 1; + width: 90%; + max-width: 600px; padding: 20px; background-color: #ffffff; - overflow-y: auto; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + border-radius: 10px; + text-align: center; } .account-header { display: flex; justify-content: space-between; align-items: center; + margin-bottom: 20px; +} + +h1 { + font-size: 1.8rem; + color: #333333; } .sign-out-btn { background-color: #18a2b8; - color: white; + color: #ffffff; border: none; - padding: 10px 15px; + padding: 10px 20px; + font-size: 1rem; cursor: pointer; border-radius: 5px; + transition: background-color 0.3s; +} + +.sign-out-btn:hover { + background-color: #12798e; } .account-details { display: flex; + flex-direction: column; align-items: center; - margin: 20px 0; + gap: 15px; } .profile-picture { - width: 100px; - height: 100px; + width: 120px; + height: 120px; border-radius: 50%; overflow: hidden; - margin-right: 20px; + border: 2px solid #18a2b8; } .profile-picture img { @@ -103,66 +70,36 @@ } .user-info h2 { - margin: 0; -} - -.personal-info { - margin-top: 30px; -} - -.info-cards { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 20px; - margin-top: 20px; + margin: 10px 0 5px; + font-size: 1.5rem; + color: #333333; } -.info-card { - background-color: #f9f9f9; - border: 1px solid #eee; - border-radius: 8px; - padding: 15px; -} - -.info-card h3 { - margin: 0 0 10px 0; +.user-info p { + margin: 0; + font-size: 1rem; + color: #666666; } /* Responsive Design */ @media (max-width: 768px) { - .sidebar { - position: absolute; - width: 80px; - } - - .sidebar.collapsed { - width: 0; - } - .content { - margin-left: 80px; - } - - .sidebar-nav li a span { - display: none; - } - - .info-cards { - grid-template-columns: 1fr; /* One column for mobile view */ + width: 100%; + padding: 15px; } } @media (max-width: 480px) { .content { - margin-left: 0; padding: 10px; } - .sidebar { - width: 60px; + h1 { + font-size: 1.5rem; } - .sidebar.collapsed { - width: 0; + .sign-out-btn { + padding: 8px 15px; + font-size: 0.9rem; } } diff --git a/frontend/src/app/pages/account-page/account-page.component.html b/frontend/src/app/pages/account-page/account-page.component.html index cf7c77c..a62b530 100644 --- a/frontend/src/app/pages/account-page/account-page.component.html +++ b/frontend/src/app/pages/account-page/account-page.component.html @@ -1,38 +1,5 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/app/pages/data-models-page/data-models-page.component.css b/frontend/src/app/pages/data-models-page/data-models-page.component.css index 639efe7..8865987 100644 --- a/frontend/src/app/pages/data-models-page/data-models-page.component.css +++ b/frontend/src/app/pages/data-models-page/data-models-page.component.css @@ -18,14 +18,9 @@ /* Dropdown container styling */ .dropdown-container { - margin: 0; /* Ensure no extra space */ display: flex; gap: 8px; flex-wrap: wrap; + margin-bottom: 4px; } - -/* Node info container */ -.node-info-container { - margin-top: 10px; /* Add spacing above node info */ -} diff --git a/frontend/src/app/pages/data-models-page/data-models-page.component.html b/frontend/src/app/pages/data-models-page/data-models-page.component.html index 229eef3..6db7fc1 100644 --- a/frontend/src/app/pages/data-models-page/data-models-page.component.html +++ b/frontend/src/app/pages/data-models-page/data-models-page.component.html @@ -1,55 +1,57 @@ + @if(!isChildRouteActive()){ -
-
- - + + + + @if(!menuVisible()){ +
+ @if(selectedNode){ + + } +
+ } +
} diff --git a/frontend/src/app/pages/data-models-page/data-models-page.component.ts b/frontend/src/app/pages/data-models-page/data-models-page.component.ts index df27f38..aad14e4 100644 --- a/frontend/src/app/pages/data-models-page/data-models-page.component.ts +++ b/frontend/src/app/pages/data-models-page/data-models-page.component.ts @@ -20,6 +20,7 @@ import {ErrorService} from "./services/error.service"; import {ConfirmationDialogComponent} from "./confirmation-dialog/confirmation-dialog.component"; import {MatDialog} from "@angular/material/dialog"; import {DataModelFormComponent} from "./data-model-form/data-model-form.component"; +import {GuidePopupComponent} from "./guide-popup/guide-popup.component"; @Component({ @@ -40,7 +41,8 @@ import {DataModelFormComponent} from "./data-model-form/data-model-form.componen NodeInfoComponent, DataModelSelectorComponent, ExportOptionsComponent, - RouterOutlet + RouterOutlet, + GuidePopupComponent, ], standalone: true }) @@ -60,7 +62,6 @@ export class DataModelsPageComponent implements OnInit{ longitudinalModels: DataModel[] = []; menuVisible = signal(false); - constructor( private federationService: FederationService, private dataModelService: DataModelService, diff --git a/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.css b/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.css new file mode 100644 index 0000000..bb8c479 --- /dev/null +++ b/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.css @@ -0,0 +1,70 @@ +.guide-popup { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 400px; + background: #fff; + border-radius: 10px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + animation: fadeIn 0.3s ease-in-out; + z-index: 1000; +} +.popup-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 15px 20px; + background: linear-gradient(90deg, #0056b3, #007bff); + color: #ffffff; + font-size: 18px; + font-weight: 600; + border-radius: 10px 10px 0 0; +} + +.popup-header h2 { + margin: 0; + font-size: 20px; + font-weight: bold; +} + +.popup-body { + padding: 20px; + font-size: 14px; + line-height: 1.6; + color: #333; +} + +.popup-footer { + padding: 10px 20px; + border-top: 1px solid #ddd; + text-align: right; +} + +.close-btn { + font-size: 18px; + cursor: pointer; + background: none; + border: none; + color: #ffffff; +} + +.close-btn:hover { + color: #ffcccc; +} + +.highlight { + color: #ffcc00; + font-weight: bold; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translate(-50%, -60%); + } + to { + opacity: 1; + transform: translate(-50%, -50%); + } +} diff --git a/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.html b/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.html new file mode 100644 index 0000000..8d85a1a --- /dev/null +++ b/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.html @@ -0,0 +1,23 @@ +
+ + + +
diff --git a/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.ts b/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.ts new file mode 100644 index 0000000..01913b5 --- /dev/null +++ b/frontend/src/app/pages/data-models-page/guide-popup/guide-popup.component.ts @@ -0,0 +1,33 @@ +import {Component, OnInit} from '@angular/core'; +import {FormsModule} from "@angular/forms"; +import {NgIf} from "@angular/common"; + +@Component({ + selector: 'app-guide-popup', + templateUrl: './guide-popup.component.html', + styleUrls: ['./guide-popup.component.css'], + imports: [ + FormsModule, + NgIf + ], + standalone: true +}) +export class GuidePopupComponent implements OnInit{ + showPopup = true; + dontShowAgain = false; + + closePopup() { + this.showPopup = false; + + if (this.dontShowAgain) { + localStorage.setItem('hideGuidePopup', 'true'); + } + } + + ngOnInit() { + const hideGuidePopup = localStorage.getItem('hideGuidePopup'); + if (hideGuidePopup === 'true') { + this.showPopup = false; + } + } +} diff --git a/frontend/src/app/pages/data-models-page/visualization/tidy-tree.ts b/frontend/src/app/pages/data-models-page/visualization/tidy-tree.ts index f514103..b3e5105 100644 --- a/frontend/src/app/pages/data-models-page/visualization/tidy-tree.ts +++ b/frontend/src/app/pages/data-models-page/visualization/tidy-tree.ts @@ -70,9 +70,25 @@ export function createTidyTree( const dynamicHeight = Math.max(baseHeight, x1 - x0 + dx * 2); const dynamicWidth = y1 - y0 + dy * 2; - const offsetX = Math.max(0, (baseWidth - dynamicWidth) / 3) - y0; + const offsetFactor = dynamicWidth / baseWidth; // Calculate a ratio based on size + const leftBias = Math.min(0.25, offsetFactor / 2); // Bias more for larger charts + + const offsetX = (baseWidth - dynamicWidth) / 2 * (1 - leftBias) - y0; + + // Ensure offset doesn't push too far left for large diagrams + const adjustedOffsetX = Math.max(offsetX, -y0) + 50; + + + console.log("offsetX", offsetX) + + const paddingX = 10, paddingY = 10; + const verticalOffset = (2 * baseHeight - dynamicHeight)/ 2; + console.log(dynamicHeight) + console.log(baseHeight) + + // Ensure the offset is non-negative (only for smaller diagrams) + const adjustedPaddingY = Math.max(paddingY, verticalOffset); - const paddingX = 50, paddingY = 20; const viewBoxWidth = dynamicWidth + paddingX * 2; const viewBoxHeight = dynamicHeight + paddingY * 2; @@ -83,7 +99,7 @@ export function createTidyTree( .attr('style', 'max-width: 100%; height: auto; font: 10px sans-serif;'); const g = svg.append('g') - .attr('transform', `translate(${offsetX}, ${paddingY})`); + .attr('transform', `translate(${adjustedOffsetX}, ${adjustedPaddingY})`); // Render links g.append('g') @@ -122,7 +138,7 @@ export function createTidyTree( const newAvailableDepths = calculateMaxDepth(d); // Calculate available depths for the new root - if (newAvailableDepths <= 0) { + if (newAvailableDepths <= 1) { console.log('Double-clicked node has no depth, breadcrumb unchanged.'); return; // If the node has no depth, do nothing and leave the breadcrumb unchanged } @@ -134,20 +150,20 @@ export function createTidyTree( }); node.append('circle') - .attr('filter', d => (d.depth === 0 ? null : (d.depth > 0 && calculateMaxDepth(d) > 0 ? 'url(#glow)' : null))) // No glow for root + .attr('filter', d => (d.depth === 0 ? null : (d.depth > 0 && calculateMaxDepth(d) > 1 ? 'url(#glow)' : null))) // No glow for root .attr('title', d => `Name: ${d.data.name}\nDepth: ${d.depth}`) - .attr('fill', d => (d.depth === 0 ? '#4caf50' : (d.depth > 0 && calculateMaxDepth(d) > 0 ? '#007acc' : '#555'))) // Root gets green color - .attr('stroke', d => (d.depth === 0 ? '#2e7d32' : (d.depth > 0 && calculateMaxDepth(d) > 0 ? '#ffcc00' : null))) // Root gets dark green stroke - .attr('r', d => (d.depth === 0 ? 8 : (d.depth > 0 && calculateMaxDepth(d) > 0 ? 5 : 2.5))); // Root has a larger radius + .attr('fill', d => (d.depth === 0 ? '#4caf50' : (d.depth > 0 && calculateMaxDepth(d) > 1 ? '#007acc' : '#555'))) // Root gets green color + .attr('stroke', d => (d.depth === 0 ? '#2e7d32' : (d.depth > 0 && calculateMaxDepth(d) > 1 ? '#ffcc00' : null))) // Root gets dark green stroke + .attr('r', d => (d.depth === 0 ? 8 : (d.depth > 0 && calculateMaxDepth(d) > 1 ? 5 : 2.5))); // Root has a larger radius node.append('text') .attr('dy', '0.31em') .attr('x', d => (d.children ? -6 : 6)) .attr('text-anchor', d => (d.children ? 'end' : 'start')) .text(d => d.data.name) - .attr('stroke', d => (d.depth === 0 ? '#ffffff' : (d.depth > 0 && calculateMaxDepth(d) > 0 ? 'yellow' : 'white'))) // Root text gets white stroke + .attr('stroke', d => (d.depth === 0 ? '#ffffff' : (d.depth > 0 && calculateMaxDepth(d) > 1 ? 'yellow' : 'white'))) // Root text gets white stroke .attr('paint-order', 'stroke') - .style('font-size', d => (d.depth === 0 ? `${14}px` : (d.depth > 0 && calculateMaxDepth(d) > 0 ? `${12}px` : `${10}px`))) // Root has larger font size + .style('font-size', d => (d.depth === 0 ? `${14}px` : (d.depth > 0 && calculateMaxDepth(d) > 1 ? `${12}px` : `${10}px`))) // Root has larger font size .style('font-weight', d => (d.depth === 0 ? 'bold' : 'normal')); // Bold font for root if (svg.node() !== null) { diff --git a/frontend/src/app/pages/data-models-page/visualization/visualization.component.css b/frontend/src/app/pages/data-models-page/visualization/visualization.component.css index b2b1280..e2b6cca 100644 --- a/frontend/src/app/pages/data-models-page/visualization/visualization.component.css +++ b/frontend/src/app/pages/data-models-page/visualization/visualization.component.css @@ -7,7 +7,7 @@ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); overflow-x: auto; overflow-y: auto; - height: calc(100vh - 280px); /* Reduced height */ + height: calc(100vh - 239px); /* Reduced height */ width: calc(87% - 30px); display: inline-block; vertical-align: top; @@ -38,7 +38,6 @@ color: #555; } - @media (max-width: 768px) { #chart-container { height: auto; /* Adjust height dynamically */ @@ -48,14 +47,3 @@ font-size: 12px; } } - - -.disclaimer { - margin-top: 5px; /* Reduce top margin of the disclaimer */ - padding: 8px; /* Reduce padding */ - background-color: #f9f9f9; - border: 1px solid #ddd; - border-radius: 5px; - font-size: 14px; - color: #333; -} diff --git a/frontend/src/app/pages/data-models-page/visualization/visualization.component.html b/frontend/src/app/pages/data-models-page/visualization/visualization.component.html index 71aea1f..fa51321 100644 --- a/frontend/src/app/pages/data-models-page/visualization/visualization.component.html +++ b/frontend/src/app/pages/data-models-page/visualization/visualization.component.html @@ -1,11 +1,3 @@ -
- Note: All nodes can be clicked to access their information. Only nodes with the - - yellow effect - - can be double-clicked to become the new root. Additionally, the node must have a depth of at least 3 to be eligible as a root. -
-