Skip to content

Commit c8cf4dc

Browse files
VSB-TUO/Created feature for updating configuration files (#1008)
* Working, added a new page, component to be possible update configuration files which are loaded from the BE. * Small changes to make it prettier and mor intuitive * Removed hardcoded icons * Updated messages
1 parent d9e2bde commit c8cf4dc

11 files changed

Lines changed: 1078 additions & 0 deletions

src/app/admin/admin-routing-paths.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { URLCombiner } from '../core/url-combiner/url-combiner';
22
import { getAdminModuleRoute } from '../app-routing-paths';
33

44
export const REGISTRIES_MODULE_PATH = 'registries';
5+
export const UPDATE_CONFIG_MODULE_PATH = 'update-config';
56

67
export function getRegistriesModuleRoute() {
78
return new URLCombiner(getAdminModuleRoute(), REGISTRIES_MODULE_PATH).toString();
89
}
10+
11+
export function getUpdateConfigModuleRoute() {
12+
return new URLCombiner(getAdminModuleRoute(), UPDATE_CONFIG_MODULE_PATH).toString();
13+
}

src/app/admin/admin-routing.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ import { ThemedAdminWorkflowPageComponent } from './admin-workflow-page/themed-a
5353
loadChildren: () => import('../system-wide-alert/system-wide-alert.module').then((m) => m.SystemWideAlertModule),
5454
data: {title: 'admin.system-wide-alert.title', breadcrumbKey: 'admin.system-wide-alert'}
5555
},
56+
{
57+
path: 'update-config',
58+
resolve: { breadcrumb: I18nBreadcrumbResolver },
59+
loadChildren: () => import('./admin-update-config/admin-update-config.module').then((m) => m.AdminUpdateConfigModule),
60+
data: { title: 'admin.update-config.title', breadcrumbKey: 'admin.update-config' }
61+
},
5662
])
5763
],
5864
providers: [
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { NgModule } from '@angular/core';
2+
import { RouterModule, Routes } from '@angular/router';
3+
import { AdminUpdateConfigComponent } from './admin-update-config.component';
4+
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
5+
import { SiteAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
6+
7+
const routes: Routes = [
8+
{
9+
path: '',
10+
component: AdminUpdateConfigComponent,
11+
resolve: { breadcrumb: I18nBreadcrumbResolver },
12+
canActivate: [SiteAdministratorGuard],
13+
data: { title: 'admin.update-config.title', breadcrumbKey: 'admin.update-config' }
14+
}
15+
];
16+
17+
@NgModule({
18+
imports: [RouterModule.forChild(routes)],
19+
exports: [RouterModule]
20+
})
21+
export class AdminUpdateConfigRoutingModule { }
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<div class="container-fluid">
2+
<div class="row">
3+
<div class="col-12">
4+
<!-- Header -->
5+
<div class="d-flex justify-content-between align-items-center mb-4">
6+
<h2>
7+
<i class="fas fa-cogs me-2"></i>
8+
{{ 'admin.update-config.title' | translate }}
9+
</h2>
10+
<button
11+
class="btn btn-outline-secondary"
12+
(click)="loadConfigFiles()"
13+
[disabled]="loading"
14+
title="{{ 'admin.update-config.refresh.tooltip' | translate }}">
15+
<i class="fas fa-sync-alt" [class.fa-spin]="loading"></i>
16+
{{ 'admin.update-config.refresh' | translate }}
17+
</button>
18+
</div>
19+
20+
<!-- Loading indicator -->
21+
<div *ngIf="loading && configFiles.length === 0" class="text-center py-4">
22+
<div class="spinner-border" role="status">
23+
<span class="visually-hidden">{{ 'admin.update-config.loading' | translate }}</span>
24+
</div>
25+
<p class="mt-2">{{ 'admin.update-config.loading-files' | translate }}</p>
26+
</div>
27+
28+
<!-- File Selection Card -->
29+
<div class="card mb-4">
30+
<div class="card-body">
31+
<h5 class="card-title">
32+
<i class="fas fa-folder-open me-2"></i>
33+
{{ 'admin.update-config.file-selection.title' | translate }}
34+
</h5>
35+
<p class="card-text text-muted">
36+
{{ 'admin.update-config.description' | translate }}
37+
</p>
38+
39+
<div class="row align-items-center">
40+
<div class="col-md-6">
41+
<label for="fileSelect" class="form-label fw-bold">
42+
<i class="fas fa-file-code me-2 text-primary"></i>
43+
{{ 'admin.update-config.file-selection.label' | translate }}
44+
</label>
45+
<select
46+
id="fileSelect"
47+
class="form-select form-select-lg"
48+
[(ngModel)]="selectedFile"
49+
(ngModelChange)="onFileSelect($event)"
50+
[disabled]="loading || configFiles.length === 0">
51+
<option [ngValue]="null">{{ getFileSelectionText() }}</option>
52+
<option *ngFor="let file of configFiles" [ngValue]="file">
53+
<i class="fas fa-file me-1"></i>{{ file.fileName || '[No Name]' }}
54+
</option>
55+
</select>
56+
</div>
57+
<div class="col-md-6" *ngIf="selectedFile">
58+
<div class="file-info mt-3 mt-md-0 p-3 bg-light rounded">
59+
<h6 class="mb-2">
60+
<i class="fas fa-info-circle text-info me-1"></i>
61+
{{ 'admin.update-config.file-info.title' | translate }}
62+
</h6>
63+
<small class="text-muted">
64+
<strong>{{ 'admin.update-config.file-info.name' | translate }}:</strong> {{ selectedFile.fileName }}<br>
65+
<strong>{{ 'admin.update-config.file-info.size' | translate }}:</strong> {{ selectedFile.size | number }} {{ 'admin.update-config.file-info.bytes' | translate }}<br>
66+
<strong>{{ 'admin.update-config.file-info.modified' | translate }}:</strong> {{ selectedFile.lastModified | date:'short' }}<br>
67+
<strong>{{ 'admin.update-config.file-info.status' | translate }}:</strong>
68+
<span class="badge bg-success">{{ 'admin.update-config.file-info.ready' | translate }}</span>
69+
</small>
70+
</div>
71+
</div>
72+
</div>
73+
</div>
74+
</div>
75+
76+
<!-- File Content Loading -->
77+
<div *ngIf="loading && selectedFile" class="text-center py-4">
78+
<div class="spinner-border spinner-border-sm" role="status">
79+
<span class="visually-hidden">{{ 'admin.update-config.loading' | translate }}</span>
80+
</div>
81+
<p class="mt-2">{{ 'admin.update-config.loading-content' | translate }}</p>
82+
</div>
83+
84+
<!-- Configuration Editor Card -->
85+
<div *ngIf="selectedFile && !loading" class="card">
86+
<div class="card-body">
87+
<div class="d-flex justify-content-between align-items-center mb-3">
88+
<h5 class="card-title mb-0">
89+
<i class="fas fa-code me-2"></i>
90+
{{ 'admin.update-config.editor.title' | translate }}: {{ selectedFile.fileName }}
91+
</h5>
92+
93+
<!-- Action Buttons -->
94+
<div class="d-flex flex-wrap gap-2" role="group">
95+
<button
96+
class="btn btn-outline-secondary btn-lg"
97+
(click)="resetContent()"
98+
[disabled]="!hasUnsavedChanges() || loading || saving"
99+
[title]="'admin.update-config.buttons.undo.tooltip' | translate">
100+
<i class="fas fa-undo me-2"></i>
101+
{{ 'admin.update-config.buttons.undo' | translate }}
102+
</button>
103+
<button
104+
class="btn btn-outline-warning btn-lg"
105+
(click)="resetToOriginalFile()"
106+
[disabled]="loading || saving"
107+
[title]="'admin.update-config.buttons.reload.tooltip' | translate">
108+
<i class="fas fa-sync me-2"></i>
109+
{{ 'admin.update-config.buttons.reload' | translate }}
110+
</button>
111+
<button
112+
class="btn btn-success btn-lg px-4"
113+
(click)="saveFile()"
114+
[disabled]="!hasUnsavedChanges() || loading || saving"
115+
[title]="'admin.update-config.buttons.save.tooltip' | translate">
116+
<i class="fas fa-save me-2" *ngIf="!saving"></i>
117+
<i class="fas fa-spinner fa-spin me-2" *ngIf="saving"></i>
118+
<strong>{{ saving ? ('admin.update-config.buttons.saving' | translate) : ('admin.update-config.buttons.save' | translate) }}</strong>
119+
</button>
120+
</div>
121+
</div>
122+
123+
<!-- Config File Editor Notice -->
124+
<div class="alert alert-info mb-3 d-flex align-items-center">
125+
<i class="fas fa-cogs me-2 text-primary"></i>
126+
<div>
127+
<strong>{{ 'admin.update-config.editor.notice.title' | translate }}</strong><br>
128+
<small>{{ 'admin.update-config.editor.notice.description' | translate:{ fileName: selectedFile?.fileName } }}</small>
129+
</div>
130+
</div>
131+
132+
<div class="mb-3">
133+
<div *ngIf="hasUnsavedChanges()" class="alert alert-warning py-2">
134+
<i class="fas fa-edit me-1"></i>
135+
{{ 'admin.update-config.validation.unsaved-changes' | translate }}
136+
</div>
137+
</div>
138+
139+
<!-- Text Editor -->
140+
<div class="position-relative">
141+
<label for="xmlContent" class="form-label">
142+
{{ 'admin.update-config.editor.content' | translate }}
143+
</label>
144+
<textarea
145+
id="xmlContent"
146+
class="form-control font-monospace config-editor"
147+
[(ngModel)]="fileContent"
148+
(ngModelChange)="onContentChange()"
149+
placeholder="{{ 'admin.update-config.editor.placeholder' | translate }}"
150+
rows="25"
151+
[disabled]="saving"
152+
spellcheck="false">
153+
</textarea>
154+
155+
<!-- Character count -->
156+
<small class="text-muted position-absolute" style="bottom: 10px; right: 15px; background: rgba(255,255,255,0.8); padding: 2px 6px; border-radius: 3px;">
157+
{{ fileContent.length }} {{ 'admin.update-config.editor.characters' | translate }}
158+
</small>
159+
</div>
160+
</div>
161+
</div>
162+
163+
164+
</div>
165+
</div>
166+
</div>
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
.container-fluid {
2+
padding: 1rem;
3+
}
4+
5+
.card {
6+
border: none;
7+
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
8+
border-radius: 0.5rem;
9+
}
10+
11+
.card-title {
12+
color: #495057;
13+
font-weight: 600;
14+
}
15+
16+
.config-editor {
17+
font-family: 'Courier New', Courier, monospace;
18+
font-size: 0.875rem;
19+
line-height: 1.5;
20+
background-color: #f8f9fa;
21+
border: 1px solid #dee2e6;
22+
resize: vertical;
23+
min-height: 300px;
24+
}
25+
26+
.config-editor:focus {
27+
background-color: #ffffff;
28+
border-color: #80bdff;
29+
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
30+
outline: none;
31+
}
32+
33+
.config-editor.is-invalid {
34+
border-color: #dc3545;
35+
background-color: #fff5f5;
36+
}
37+
38+
.config-editor.is-invalid:focus {
39+
border-color: #dc3545;
40+
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
41+
}
42+
43+
.form-control.is-invalid {
44+
border-color: #dc3545;
45+
background-image: none;
46+
}
47+
48+
.form-control.is-invalid:focus {
49+
border-color: #dc3545;
50+
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
51+
}
52+
53+
.alert {
54+
border: none;
55+
border-radius: 0.375rem;
56+
}
57+
58+
.alert-info {
59+
background-color: #e7f3ff;
60+
border-left: 4px solid #17a2b8;
61+
color: #0c5460;
62+
}
63+
64+
.alert-danger {
65+
background-color: #f8d7da;
66+
border-left: 4px solid #dc3545;
67+
color: #721c24;
68+
}
69+
70+
.alert-warning {
71+
background-color: #fff3cd;
72+
border-left: 4px solid #ffc107;
73+
color: #856404;
74+
}
75+
76+
.badge {
77+
font-size: 0.75rem;
78+
font-weight: 500;
79+
}
80+
81+
.btn {
82+
border-radius: 0.375rem;
83+
font-weight: 500;
84+
}
85+
86+
.btn:disabled {
87+
opacity: 0.65;
88+
}
89+
90+
.btn-sm {
91+
padding: 0.375rem 0.75rem;
92+
font-size: 0.875rem;
93+
}
94+
95+
.form-select {
96+
border-radius: 0.375rem;
97+
}
98+
99+
.form-select:focus {
100+
border-color: #80bdff;
101+
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
102+
}
103+
104+
/* Loading spinner animation */
105+
.fa-spin {
106+
animation: fa-spin 2s infinite linear;
107+
}
108+
109+
@keyframes fa-spin {
110+
0% {
111+
transform: rotate(0deg);
112+
}
113+
100% {
114+
transform: rotate(359deg);
115+
}
116+
}
117+
118+
/* Responsive adjustments */
119+
@media (max-width: 768px) {
120+
.container-fluid {
121+
padding: 0.5rem;
122+
}
123+
124+
.alert.d-flex {
125+
flex-direction: column;
126+
align-items: flex-start !important;
127+
}
128+
129+
.alert.d-flex > div:last-child {
130+
margin-top: 1rem;
131+
width: 100%;
132+
}
133+
134+
.btn-sm {
135+
width: 100%;
136+
margin-bottom: 0.5rem;
137+
}
138+
139+
.btn-sm:last-child {
140+
margin-bottom: 0;
141+
}
142+
}

0 commit comments

Comments
 (0)