ciyidogan commited on
Commit
9eba098
·
verified ·
1 Parent(s): 90e33ec

Upload 44 files

Browse files
Files changed (26) hide show
  1. flare-ui/angular.json +42 -3
  2. flare-ui/package.json +11 -9
  3. flare-ui/proxy.conf.json +18 -0
  4. flare-ui/src/app/app.module.ts +0 -0
  5. flare-ui/src/app/components/apis/apis.component.html +0 -115
  6. flare-ui/src/app/components/apis/apis.component.scss +144 -144
  7. flare-ui/src/app/components/apis/apis.component.ts +298 -179
  8. flare-ui/src/app/components/projects/projects.component.html +0 -0
  9. flare-ui/src/app/components/projects/projects.component.scss +181 -0
  10. flare-ui/src/app/components/user-info/user-info.component.html +0 -77
  11. flare-ui/src/app/components/user-info/user-info.component.scss +59 -59
  12. flare-ui/src/app/components/user-info/user-info.component.ts +82 -109
  13. flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.html +0 -0
  14. flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.scss +208 -0
  15. flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.ts +0 -0
  16. flare-ui/src/app/dialogs/confirm-dialog/confirm-dialog.component.html +0 -0
  17. flare-ui/src/app/dialogs/confirm-dialog/confirm-dialog.component.scss +12 -0
  18. flare-ui/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts +0 -0
  19. flare-ui/src/app/dialogs/project-edit-dialog/project-edit-dialog.component.html +0 -0
  20. flare-ui/src/app/dialogs/project-edit-dialog/project-edit-dialog.component.scss +48 -0
  21. flare-ui/src/app/dialogs/project-edit-dialog/project-edit-dialog.component.ts +0 -0
  22. flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.html +0 -0
  23. flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.scss +42 -0
  24. flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.ts +0 -0
  25. flare-ui/src/environments/environment.prod.ts +4 -0
  26. flare-ui/src/environments/environment.ts +4 -0
flare-ui/angular.json CHANGED
@@ -30,6 +30,7 @@
30
  "src/assets"
31
  ],
32
  "styles": [
 
33
  "src/styles.scss"
34
  ],
35
  "scripts": []
@@ -48,6 +49,12 @@
48
  "maximumError": "4kb"
49
  }
50
  ],
 
 
 
 
 
 
51
  "outputHashing": "all"
52
  },
53
  "development": {
@@ -65,15 +72,47 @@
65
  "builder": "@angular-devkit/build-angular:dev-server",
66
  "configurations": {
67
  "production": {
68
- "buildTarget": "flare-ui:build:production"
69
  },
70
  "development": {
71
- "buildTarget": "flare-ui:build:development"
72
  }
73
  },
74
- "defaultConfiguration": "development"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
  }
77
  }
 
 
 
78
  }
79
  }
 
30
  "src/assets"
31
  ],
32
  "styles": [
33
+ "@angular/material/prebuilt-themes/indigo-pink.css",
34
  "src/styles.scss"
35
  ],
36
  "scripts": []
 
49
  "maximumError": "4kb"
50
  }
51
  ],
52
+ "fileReplacements": [
53
+ {
54
+ "replace": "src/environments/environment.ts",
55
+ "with": "src/environments/environment.prod.ts"
56
+ }
57
+ ],
58
  "outputHashing": "all"
59
  },
60
  "development": {
 
72
  "builder": "@angular-devkit/build-angular:dev-server",
73
  "configurations": {
74
  "production": {
75
+ "browserTarget": "flare-ui:build:production"
76
  },
77
  "development": {
78
+ "browserTarget": "flare-ui:build:development"
79
  }
80
  },
81
+ "defaultConfiguration": "development",
82
+ "options": {
83
+ "proxyConfig": "src/proxy.conf.json"
84
+ }
85
+ },
86
+ "extract-i18n": {
87
+ "builder": "@angular-devkit/build-angular:extract-i18n",
88
+ "options": {
89
+ "browserTarget": "flare-ui:build"
90
+ }
91
+ },
92
+ "test": {
93
+ "builder": "@angular-devkit/build-angular:karma",
94
+ "options": {
95
+ "polyfills": [
96
+ "zone.js",
97
+ "zone.js/testing"
98
+ ],
99
+ "tsConfig": "tsconfig.spec.json",
100
+ "inlineStyleLanguage": "scss",
101
+ "assets": [
102
+ "src/favicon.ico",
103
+ "src/assets"
104
+ ],
105
+ "styles": [
106
+ "@angular/material/prebuilt-themes/indigo-pink.css",
107
+ "src/styles.scss"
108
+ ],
109
+ "scripts": []
110
+ }
111
  }
112
  }
113
  }
114
+ },
115
+ "cli": {
116
+ "analytics": false
117
  }
118
  }
flare-ui/package.json CHANGED
@@ -10,17 +10,19 @@
10
  },
11
  "private": true,
12
  "dependencies": {
13
- "@angular/animations": "^17.0.0",
14
- "@angular/common": "^17.0.0",
15
- "@angular/compiler": "^17.0.0",
16
- "@angular/core": "^17.0.0",
17
- "@angular/forms": "^17.0.0",
18
- "@angular/platform-browser": "^17.0.0",
19
- "@angular/platform-browser-dynamic": "^17.0.0",
20
- "@angular/router": "^17.0.0",
 
 
21
  "rxjs": "~7.8.0",
22
  "tslib": "^2.3.0",
23
- "zone.js": "~0.14.2"
24
  },
25
  "devDependencies": {
26
  "@angular-devkit/build-angular": "^17.0.0",
 
10
  },
11
  "private": true,
12
  "dependencies": {
13
+ "@angular/animations": "^16.2.0",
14
+ "@angular/cdk": "^16.2.0",
15
+ "@angular/common": "^16.2.0",
16
+ "@angular/compiler": "^16.2.0",
17
+ "@angular/core": "^16.2.0",
18
+ "@angular/forms": "^16.2.0",
19
+ "@angular/material": "^16.2.0",
20
+ "@angular/platform-browser": "^16.2.0",
21
+ "@angular/platform-browser-dynamic": "^16.2.0",
22
+ "@angular/router": "^16.2.0",
23
  "rxjs": "~7.8.0",
24
  "tslib": "^2.3.0",
25
+ "zone.js": "~0.13.0"
26
  },
27
  "devDependencies": {
28
  "@angular-devkit/build-angular": "^17.0.0",
flare-ui/proxy.conf.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "/api/*": {
3
+ "target": "http://localhost:7860",
4
+ "secure": false,
5
+ "changeOrigin": true,
6
+ "logLevel": "debug"
7
+ },
8
+ "/start_session": {
9
+ "target": "http://localhost:7860",
10
+ "secure": false,
11
+ "changeOrigin": true
12
+ },
13
+ "/chat": {
14
+ "target": "http://localhost:7860",
15
+ "secure": false,
16
+ "changeOrigin": true
17
+ }
18
+ }
flare-ui/src/app/app.module.ts ADDED
File without changes
flare-ui/src/app/components/apis/apis.component.html CHANGED
@@ -1,115 +0,0 @@
1
- <div class="apis-container">
2
- <div class="toolbar">
3
- <div class="toolbar-left">
4
- <button mat-raised-button color="primary" (click)="createAPI()">
5
- <mat-icon>add</mat-icon>
6
- New API
7
- </button>
8
- <button mat-button (click)="importAPIs()">
9
- <mat-icon>upload</mat-icon>
10
- Import
11
- </button>
12
- <button mat-button (click)="exportAPIs()">
13
- <mat-icon>download</mat-icon>
14
- Export
15
- </button>
16
- </div>
17
-
18
- <div class="toolbar-right">
19
- <mat-form-field appearance="outline" class="search-field">
20
- <mat-label>Search APIs</mat-label>
21
- <input matInput [(ngModel)]="searchTerm" (ngModelChange)="onSearchChange()">
22
- <mat-icon matSuffix>search</mat-icon>
23
- </mat-form-field>
24
-
25
- <mat-checkbox [(ngModel)]="showDeleted" (change)="onShowDeletedChange()">
26
- Show Deleted
27
- </mat-checkbox>
28
- </div>
29
- </div>
30
-
31
- <div class="table-container">
32
- <mat-progress-bar mode="indeterminate" *ngIf="loading"></mat-progress-bar>
33
-
34
- <table mat-table [dataSource]="filteredApis" class="apis-table">
35
- <!-- Name Column -->
36
- <ng-container matColumnDef="name">
37
- <th mat-header-cell *matHeaderCellDef>Name</th>
38
- <td mat-cell *matCellDef="let api">
39
- {{api.name}}
40
- <mat-icon *ngIf="api.deleted" class="deleted-icon"
41
- matTooltip="This API is deleted">delete</mat-icon>
42
- </td>
43
- </ng-container>
44
-
45
- <!-- URL Column -->
46
- <ng-container matColumnDef="url">
47
- <th mat-header-cell *matHeaderCellDef>URL</th>
48
- <td mat-cell *matCellDef="let api" class="url-cell">
49
- <span class="url-text" [matTooltip]="api.url">{{api.url}}</span>
50
- </td>
51
- </ng-container>
52
-
53
- <!-- Method Column -->
54
- <ng-container matColumnDef="method">
55
- <th mat-header-cell *matHeaderCellDef>Method</th>
56
- <td mat-cell *matCellDef="let api">
57
- <span class="method-badge" [class.method-{{api.method.toLowerCase()}}">
58
- {{api.method}}
59
- </span>
60
- </td>
61
- </ng-container>
62
-
63
- <!-- Timeout Column -->
64
- <ng-container matColumnDef="timeout">
65
- <th mat-header-cell *matHeaderCellDef>Timeout</th>
66
- <td mat-cell *matCellDef="let api">{{api.timeout_seconds}}s</td>
67
- </ng-container>
68
-
69
- <!-- Auth Column -->
70
- <ng-container matColumnDef="auth">
71
- <th mat-header-cell *matHeaderCellDef>Auth</th>
72
- <td mat-cell *matCellDef="let api">
73
- <mat-icon *ngIf="api.auth?.enabled" class="auth-icon" color="primary">lock</mat-icon>
74
- <mat-icon *ngIf="!api.auth?.enabled" class="auth-icon">lock_open</mat-icon>
75
- </td>
76
- </ng-container>
77
-
78
- <!-- Actions Column -->
79
- <ng-container matColumnDef="actions">
80
- <th mat-header-cell *matHeaderCellDef>Actions</th>
81
- <td mat-cell *matCellDef="let api">
82
- <button mat-icon-button (click)="editAPI(api)"
83
- matTooltip="Edit API"
84
- [disabled]="api.deleted">
85
- <mat-icon>edit</mat-icon>
86
- </button>
87
- <button mat-icon-button (click)="testAPI(api)"
88
- matTooltip="Test API"
89
- [disabled]="api.deleted">
90
- <mat-icon>play_arrow</mat-icon>
91
- </button>
92
- <button mat-icon-button (click)="duplicateAPI(api)"
93
- matTooltip="Duplicate API"
94
- [disabled]="api.deleted">
95
- <mat-icon>content_copy</mat-icon>
96
- </button>
97
- <button mat-icon-button (click)="deleteAPI(api)"
98
- matTooltip="Delete API"
99
- [disabled]="api.deleted">
100
- <mat-icon>delete</mat-icon>
101
- </button>
102
- </td>
103
- </ng-container>
104
-
105
- <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
106
- <tr mat-row *matRowDef="let row; columns: displayedColumns;"
107
- [class.deleted-row]="row.deleted"></tr>
108
- </table>
109
-
110
- <div class="no-data" *ngIf="!loading && filteredApis.length === 0">
111
- <mat-icon>cloud_off</mat-icon>
112
- <p>No APIs found</p>
113
- </div>
114
- </div>
115
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
flare-ui/src/app/components/apis/apis.component.scss CHANGED
@@ -1,145 +1,145 @@
1
- .apis-container {
2
- height: 100%;
3
- display: flex;
4
- flex-direction: column;
5
-
6
- .toolbar {
7
- display: flex;
8
- justify-content: space-between;
9
- align-items: center;
10
- margin-bottom: 20px;
11
- flex-wrap: wrap;
12
- gap: 16px;
13
-
14
- .toolbar-left {
15
- display: flex;
16
- gap: 8px;
17
- align-items: center;
18
- }
19
-
20
- .toolbar-right {
21
- display: flex;
22
- gap: 16px;
23
- align-items: center;
24
-
25
- .search-field {
26
- width: 300px;
27
- }
28
- }
29
- }
30
-
31
- .table-container {
32
- flex: 1;
33
- overflow: auto;
34
- background: white;
35
- border-radius: 8px;
36
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
37
-
38
- .apis-table {
39
- width: 100%;
40
-
41
- .url-cell {
42
- .url-text {
43
- max-width: 300px;
44
- overflow: hidden;
45
- text-overflow: ellipsis;
46
- white-space: nowrap;
47
- display: inline-block;
48
- vertical-align: middle;
49
- }
50
- }
51
-
52
- .method-badge {
53
- padding: 4px 12px;
54
- border-radius: 12px;
55
- font-size: 12px;
56
- font-weight: 500;
57
- text-transform: uppercase;
58
-
59
- &.method-get {
60
- background-color: #e3f2fd;
61
- color: #1976d2;
62
- }
63
-
64
- &.method-post {
65
- background-color: #e8f5e9;
66
- color: #388e3c;
67
- }
68
-
69
- &.method-put {
70
- background-color: #fff3e0;
71
- color: #f57c00;
72
- }
73
-
74
- &.method-patch {
75
- background-color: #fce4ec;
76
- color: #c2185b;
77
- }
78
-
79
- &.method-delete {
80
- background-color: #ffebee;
81
- color: #d32f2f;
82
- }
83
- }
84
-
85
- .auth-icon {
86
- font-size: 18px;
87
- height: 18px;
88
- width: 18px;
89
- }
90
-
91
- .deleted-icon {
92
- color: #f44336;
93
- font-size: 16px;
94
- vertical-align: middle;
95
- margin-left: 8px;
96
- }
97
-
98
- .deleted-row {
99
- opacity: 0.5;
100
- background-color: #fafafa;
101
- }
102
- }
103
-
104
- .no-data {
105
- text-align: center;
106
- padding: 60px 20px;
107
- color: #999;
108
-
109
- mat-icon {
110
- font-size: 64px;
111
- height: 64px;
112
- width: 64px;
113
- margin-bottom: 16px;
114
- }
115
-
116
- p {
117
- font-size: 16px;
118
- margin: 0;
119
- }
120
- }
121
- }
122
-
123
- mat-progress-bar {
124
- position: absolute;
125
- top: 0;
126
- left: 0;
127
- right: 0;
128
- }
129
- }
130
-
131
- @media (max-width: 768px) {
132
- .apis-container {
133
- .toolbar {
134
- .toolbar-left,
135
- .toolbar-right {
136
- width: 100%;
137
- justify-content: center;
138
- }
139
-
140
- .search-field {
141
- width: 100%;
142
- }
143
- }
144
- }
145
  }
 
1
+ .apis-container {
2
+ height: 100%;
3
+ display: flex;
4
+ flex-direction: column;
5
+
6
+ .toolbar {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ align-items: center;
10
+ margin-bottom: 20px;
11
+ flex-wrap: wrap;
12
+ gap: 16px;
13
+
14
+ .toolbar-left {
15
+ display: flex;
16
+ gap: 8px;
17
+ align-items: center;
18
+ }
19
+
20
+ .toolbar-right {
21
+ display: flex;
22
+ gap: 16px;
23
+ align-items: center;
24
+
25
+ .search-field {
26
+ width: 300px;
27
+ }
28
+ }
29
+ }
30
+
31
+ .table-container {
32
+ flex: 1;
33
+ overflow: auto;
34
+ background: white;
35
+ border-radius: 8px;
36
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
37
+
38
+ .apis-table {
39
+ width: 100%;
40
+
41
+ .url-cell {
42
+ .url-text {
43
+ max-width: 300px;
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
47
+ display: inline-block;
48
+ vertical-align: middle;
49
+ }
50
+ }
51
+
52
+ .method-badge {
53
+ padding: 4px 12px;
54
+ border-radius: 12px;
55
+ font-size: 12px;
56
+ font-weight: 500;
57
+ text-transform: uppercase;
58
+
59
+ &.method-get {
60
+ background-color: #e3f2fd;
61
+ color: #1976d2;
62
+ }
63
+
64
+ &.method-post {
65
+ background-color: #e8f5e9;
66
+ color: #388e3c;
67
+ }
68
+
69
+ &.method-put {
70
+ background-color: #fff3e0;
71
+ color: #f57c00;
72
+ }
73
+
74
+ &.method-patch {
75
+ background-color: #fce4ec;
76
+ color: #c2185b;
77
+ }
78
+
79
+ &.method-delete {
80
+ background-color: #ffebee;
81
+ color: #d32f2f;
82
+ }
83
+ }
84
+
85
+ .auth-icon {
86
+ font-size: 18px;
87
+ height: 18px;
88
+ width: 18px;
89
+ }
90
+
91
+ .deleted-icon {
92
+ color: #f44336;
93
+ font-size: 16px;
94
+ vertical-align: middle;
95
+ margin-left: 8px;
96
+ }
97
+
98
+ .deleted-row {
99
+ opacity: 0.5;
100
+ background-color: #fafafa;
101
+ }
102
+ }
103
+
104
+ .no-data {
105
+ text-align: center;
106
+ padding: 60px 20px;
107
+ color: #999;
108
+
109
+ mat-icon {
110
+ font-size: 64px;
111
+ height: 64px;
112
+ width: 64px;
113
+ margin-bottom: 16px;
114
+ }
115
+
116
+ p {
117
+ font-size: 16px;
118
+ margin: 0;
119
+ }
120
+ }
121
+ }
122
+
123
+ mat-progress-bar {
124
+ position: absolute;
125
+ top: 0;
126
+ left: 0;
127
+ right: 0;
128
+ }
129
+ }
130
+
131
+ @media (max-width: 768px) {
132
+ .apis-container {
133
+ .toolbar {
134
+ .toolbar-left,
135
+ .toolbar-right {
136
+ width: 100%;
137
+ justify-content: center;
138
+ }
139
+
140
+ .search-field {
141
+ width: 100%;
142
+ }
143
+ }
144
+ }
145
  }
flare-ui/src/app/components/apis/apis.component.ts CHANGED
@@ -1,180 +1,299 @@
1
- import { Component, OnInit, ViewChild } from '@angular/core';
2
- import { MatDialog } from '@angular/material/dialog';
3
- import { MatSnackBar } from '@angular/material/snack-bar';
4
- import { MatTable } from '@angular/material/table';
5
- import { ApiService } from '../../services/api.service';
6
- import { ApiEditDialogComponent } from '../../dialogs/api-edit-dialog/api-edit-dialog.component';
7
- import { ConfirmDialogComponent } from '../../dialogs/confirm-dialog/confirm-dialog.component';
8
-
9
- export interface APIConfig {
10
- name: string;
11
- url: string;
12
- method: string;
13
- timeout_seconds: number;
14
- auth?: {
15
- enabled: boolean;
16
- };
17
- deleted?: boolean;
18
- last_update_date?: string;
19
- last_update_user?: string;
20
- }
21
-
22
- @Component({
23
- selector: 'app-apis',
24
- templateUrl: './apis.component.html',
25
- styleUrls: ['./apis.component.scss']
26
- })
27
- export class ApisComponent implements OnInit {
28
- @ViewChild(MatTable) table!: MatTable<APIConfig>;
29
-
30
- apis: APIConfig[] = [];
31
- filteredApis: APIConfig[] = [];
32
- displayedColumns: string[] = ['name', 'url', 'method', 'timeout', 'auth', 'actions'];
33
- searchTerm = '';
34
- showDeleted = false;
35
- loading = false;
36
-
37
- constructor(
38
- private apiService: ApiService,
39
- private dialog: MatDialog,
40
- private snackBar: MatSnackBar
41
- ) {}
42
-
43
- ngOnInit() {
44
- this.loadAPIs();
45
- }
46
-
47
- async loadAPIs() {
48
- this.loading = true;
49
- try {
50
- this.apis = await this.apiService.getAPIs(this.showDeleted).toPromise();
51
- this.applyFilter();
52
- } catch (error) {
53
- this.snackBar.open('Failed to load APIs', 'Close', { duration: 5000 });
54
- } finally {
55
- this.loading = false;
56
- }
57
- }
58
-
59
- applyFilter() {
60
- this.filteredApis = this.apis.filter(api => {
61
- const matchesSearch = !this.searchTerm ||
62
- api.name.toLowerCase().includes(this.searchTerm.toLowerCase()) ||
63
- api.url.toLowerCase().includes(this.searchTerm.toLowerCase());
64
-
65
- const matchesDeleted = this.showDeleted || !api.deleted;
66
-
67
- return matchesSearch && matchesDeleted;
68
- });
69
- }
70
-
71
- onSearchChange() {
72
- this.applyFilter();
73
- }
74
-
75
- onShowDeletedChange() {
76
- this.loadAPIs();
77
- }
78
-
79
- createAPI() {
80
- const dialogRef = this.dialog.open(ApiEditDialogComponent, {
81
- width: '800px',
82
- data: { mode: 'create' }
83
- });
84
-
85
- dialogRef.afterClosed().subscribe(result => {
86
- if (result) {
87
- this.loadAPIs();
88
- }
89
- });
90
- }
91
-
92
- editAPI(api: APIConfig) {
93
- const dialogRef = this.dialog.open(ApiEditDialogComponent, {
94
- width: '800px',
95
- data: { mode: 'edit', api: { ...api } }
96
- });
97
-
98
- dialogRef.afterClosed().subscribe(result => {
99
- if (result) {
100
- this.loadAPIs();
101
- }
102
- });
103
- }
104
-
105
- async testAPI(api: APIConfig) {
106
- try {
107
- const result = await this.apiService.testAPI(api).toPromise();
108
-
109
- if (result.success) {
110
- this.snackBar.open(
111
- `API test successful! Status: ${result.status_code}, Time: ${result.response_time_ms}ms`,
112
- 'Close',
113
- { duration: 5000, panelClass: 'success-snackbar' }
114
- );
115
- } else {
116
- this.snackBar.open(
117
- `API test failed: ${result.error}`,
118
- 'Close',
119
- { duration: 5000, panelClass: 'error-snackbar' }
120
- );
121
- }
122
- } catch (error) {
123
- this.snackBar.open('Failed to test API', 'Close', { duration: 5000 });
124
- }
125
- }
126
-
127
- duplicateAPI(api: APIConfig) {
128
- const newApi = { ...api };
129
- delete newApi.last_update_date;
130
- delete newApi.last_update_user;
131
- delete newApi.deleted;
132
- newApi.name = `${api.name}_copy`;
133
-
134
- const dialogRef = this.dialog.open(ApiEditDialogComponent, {
135
- width: '800px',
136
- data: { mode: 'create', api: newApi }
137
- });
138
-
139
- dialogRef.afterClosed().subscribe(result => {
140
- if (result) {
141
- this.loadAPIs();
142
- }
143
- });
144
- }
145
-
146
- async deleteAPI(api: APIConfig) {
147
- const dialogRef = this.dialog.open(ConfirmDialogComponent, {
148
- width: '400px',
149
- data: {
150
- title: 'Delete API',
151
- message: `Are you sure you want to delete "${api.name}"?`,
152
- confirmText: 'Delete',
153
- confirmColor: 'warn'
154
- }
155
- });
156
-
157
- dialogRef.afterClosed().subscribe(async confirmed => {
158
- if (confirmed) {
159
- try {
160
- await this.apiService.deleteAPI(api.name).toPromise();
161
- this.snackBar.open('API deleted successfully', 'Close', { duration: 3000 });
162
- this.loadAPIs();
163
- } catch (error: any) {
164
- const message = error.error?.detail || 'Failed to delete API';
165
- this.snackBar.open(message, 'Close', { duration: 5000, panelClass: 'error-snackbar' });
166
- }
167
- }
168
- });
169
- }
170
-
171
- importAPIs() {
172
- // TODO: Implement import functionality
173
- this.snackBar.open('Import functionality coming soon', 'Close', { duration: 3000 });
174
- }
175
-
176
- exportAPIs() {
177
- // TODO: Implement export functionality
178
- this.snackBar.open('Export functionality coming soon', 'Close', { duration: 3000 });
179
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  }
 
1
+ import { Component, inject, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { ApiService, API } from '../../services/api.service';
5
+
6
+ @Component({
7
+ selector: 'app-apis',
8
+ standalone: true,
9
+ imports: [CommonModule, FormsModule],
10
+ template: `
11
+ <div class="apis-container">
12
+ <div class="toolbar">
13
+ <h2>API Definitions</h2>
14
+ <div class="toolbar-actions">
15
+ <button class="btn btn-primary" (click)="createAPI()">
16
+ New API
17
+ </button>
18
+ <button class="btn btn-secondary" disabled>
19
+ Import
20
+ </button>
21
+ <button class="btn btn-secondary" disabled>
22
+ Export
23
+ </button>
24
+ <input
25
+ type="text"
26
+ placeholder="Search APIs..."
27
+ [(ngModel)]="searchTerm"
28
+ (input)="filterAPIs()"
29
+ class="search-input"
30
+ >
31
+ <label class="checkbox-label">
32
+ <input
33
+ type="checkbox"
34
+ [(ngModel)]="showDeleted"
35
+ (change)="loadAPIs()"
36
+ >
37
+ Display Deleted
38
+ </label>
39
+ </div>
40
+ </div>
41
+
42
+ @if (loading) {
43
+ <div class="loading">
44
+ <span class="spinner"></span> Loading APIs...
45
+ </div>
46
+ } @else if (filteredAPIs.length === 0) {
47
+ <div class="empty-state">
48
+ <p>No APIs found.</p>
49
+ <button class="btn btn-primary" (click)="createAPI()">
50
+ Create your first API
51
+ </button>
52
+ </div>
53
+ } @else {
54
+ <table class="table">
55
+ <thead>
56
+ <tr>
57
+ <th>Name</th>
58
+ <th>URL</th>
59
+ <th>Method</th>
60
+ <th>Timeout</th>
61
+ <th>Auth</th>
62
+ <th>Deleted</th>
63
+ <th>Actions</th>
64
+ </tr>
65
+ </thead>
66
+ <tbody>
67
+ @for (api of filteredAPIs; track api.name) {
68
+ <tr [class.deleted]="api.deleted">
69
+ <td>{{ api.name }}</td>
70
+ <td class="url-cell">{{ api.url }}</td>
71
+ <td>
72
+ <span class="method-badge" [class]="'method-' + api.method.toLowerCase()">
73
+ {{ api.method }}
74
+ </span>
75
+ </td>
76
+ <td>{{ api.timeout_seconds }}s</td>
77
+ <td>
78
+ @if (api.auth?.enabled) {
79
+ <span class="status-badge enabled">✓</span>
80
+ } @else {
81
+ <span class="status-badge">✗</span>
82
+ }
83
+ </td>
84
+ <td>
85
+ @if (api.deleted) {
86
+ <span class="status-badge deleted">✓</span>
87
+ } @else {
88
+ <span class="status-badge">✗</span>
89
+ }
90
+ </td>
91
+ <td class="actions">
92
+ <button class="action-btn" title="Edit" (click)="editAPI(api)">
93
+ 🖊️
94
+ </button>
95
+ <button class="action-btn" title="Test" (click)="testAPI(api)">
96
+ 🧪
97
+ </button>
98
+ <button class="action-btn" title="Duplicate" (click)="duplicateAPI(api)">
99
+ 📋
100
+ </button>
101
+ @if (!api.deleted) {
102
+ <button class="action-btn danger" title="Delete" (click)="deleteAPI(api)">
103
+ 🗑️
104
+ </button>
105
+ }
106
+ </td>
107
+ </tr>
108
+ }
109
+ </tbody>
110
+ </table>
111
+ }
112
+
113
+ @if (message) {
114
+ <div class="alert" [class.alert-success]="!isError" [class.alert-danger]="isError">
115
+ {{ message }}
116
+ </div>
117
+ }
118
+ </div>
119
+ `,
120
+ styles: [`
121
+ .apis-container {
122
+ .toolbar {
123
+ display: flex;
124
+ justify-content: space-between;
125
+ align-items: center;
126
+ margin-bottom: 1.5rem;
127
+
128
+ h2 {
129
+ margin: 0;
130
+ }
131
+
132
+ .toolbar-actions {
133
+ display: flex;
134
+ gap: 0.5rem;
135
+ align-items: center;
136
+ }
137
+ }
138
+
139
+ .search-input {
140
+ padding: 0.375rem 0.75rem;
141
+ border: 1px solid #ced4da;
142
+ border-radius: 0.25rem;
143
+ width: 200px;
144
+ }
145
+
146
+ .checkbox-label {
147
+ display: flex;
148
+ align-items: center;
149
+ gap: 0.25rem;
150
+ cursor: pointer;
151
+ }
152
+
153
+ .loading, .empty-state {
154
+ text-align: center;
155
+ padding: 3rem;
156
+ background-color: white;
157
+ border-radius: 0.25rem;
158
+
159
+ p {
160
+ margin-bottom: 1rem;
161
+ color: #6c757d;
162
+ }
163
+ }
164
+
165
+ .url-cell {
166
+ max-width: 300px;
167
+ overflow: hidden;
168
+ text-overflow: ellipsis;
169
+ white-space: nowrap;
170
+ }
171
+
172
+ .method-badge {
173
+ padding: 0.25rem 0.5rem;
174
+ border-radius: 0.25rem;
175
+ font-size: 0.75rem;
176
+ font-weight: 600;
177
+
178
+ &.method-get { background-color: #28a745; color: white; }
179
+ &.method-post { background-color: #007bff; color: white; }
180
+ &.method-put { background-color: #ffc107; color: #333; }
181
+ &.method-patch { background-color: #17a2b8; color: white; }
182
+ &.method-delete { background-color: #dc3545; color: white; }
183
+ }
184
+
185
+ .status-badge {
186
+ &.enabled { color: #28a745; }
187
+ &.deleted { color: #dc3545; }
188
+ }
189
+
190
+ .actions {
191
+ display: flex;
192
+ gap: 0.25rem;
193
+ }
194
+
195
+ .action-btn {
196
+ background: none;
197
+ border: none;
198
+ cursor: pointer;
199
+ font-size: 1.1rem;
200
+ padding: 0.25rem;
201
+ border-radius: 0.25rem;
202
+
203
+ &:hover {
204
+ background-color: #f8f9fa;
205
+ }
206
+
207
+ &.danger:hover {
208
+ background-color: #f8d7da;
209
+ }
210
+ }
211
+
212
+ tr.deleted {
213
+ opacity: 0.6;
214
+ background-color: #f8f9fa;
215
+ }
216
+ }
217
+ `]
218
+ })
219
+ export class ApisComponent implements OnInit {
220
+ private apiService = inject(ApiService);
221
+
222
+ apis: API[] = [];
223
+ filteredAPIs: API[] = [];
224
+ loading = true;
225
+ showDeleted = false;
226
+ searchTerm = '';
227
+ message = '';
228
+ isError = false;
229
+
230
+ ngOnInit() {
231
+ this.loadAPIs();
232
+ }
233
+
234
+ loadAPIs() {
235
+ this.loading = true;
236
+ this.apiService.getAPIs(this.showDeleted).subscribe({
237
+ next: (apis) => {
238
+ this.apis = apis;
239
+ this.filterAPIs();
240
+ this.loading = false;
241
+ },
242
+ error: (err) => {
243
+ this.showMessage('Failed to load APIs', true);
244
+ this.loading = false;
245
+ }
246
+ });
247
+ }
248
+
249
+ filterAPIs() {
250
+ const term = this.searchTerm.toLowerCase();
251
+ this.filteredAPIs = this.apis.filter(api =>
252
+ api.name.toLowerCase().includes(term) ||
253
+ api.url.toLowerCase().includes(term)
254
+ );
255
+ }
256
+
257
+ createAPI() {
258
+ // TODO: Open create dialog
259
+ console.log('Create API - not implemented yet');
260
+ }
261
+
262
+ editAPI(api: API) {
263
+ // TODO: Open edit dialog
264
+ console.log('Edit API:', api.name);
265
+ }
266
+
267
+ testAPI(api: API) {
268
+ // TODO: Test API
269
+ console.log('Test API:', api.name);
270
+ }
271
+
272
+ duplicateAPI(api: API) {
273
+ // TODO: Duplicate API
274
+ console.log('Duplicate API:', api.name);
275
+ }
276
+
277
+ deleteAPI(api: API) {
278
+ if (confirm(`Are you sure you want to delete "${api.name}"?`)) {
279
+ this.apiService.deleteAPI(api.name).subscribe({
280
+ next: () => {
281
+ this.showMessage(`API "${api.name}" deleted successfully`, false);
282
+ this.loadAPIs();
283
+ },
284
+ error: (err) => {
285
+ this.showMessage(err.error?.detail || 'Failed to delete API', true);
286
+ }
287
+ });
288
+ }
289
+ }
290
+
291
+ private showMessage(message: string, isError: boolean) {
292
+ this.message = message;
293
+ this.isError = isError;
294
+
295
+ setTimeout(() => {
296
+ this.message = '';
297
+ }, 5000);
298
+ }
299
  }
flare-ui/src/app/components/projects/projects.component.html ADDED
File without changes
flare-ui/src/app/components/projects/projects.component.scss ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .projects-container {
2
+ height: 100%;
3
+ display: flex;
4
+ flex-direction: column;
5
+
6
+ .toolbar {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ align-items: center;
10
+ margin-bottom: 20px;
11
+ flex-wrap: wrap;
12
+ gap: 16px;
13
+
14
+ .toolbar-left {
15
+ display: flex;
16
+ gap: 8px;
17
+ align-items: center;
18
+ }
19
+
20
+ .toolbar-right {
21
+ display: flex;
22
+ gap: 16px;
23
+ align-items: center;
24
+
25
+ .search-field {
26
+ width: 300px;
27
+ }
28
+
29
+ .view-toggle {
30
+ border: 1px solid #e0e0e0;
31
+ border-radius: 4px;
32
+ }
33
+ }
34
+ }
35
+
36
+ mat-progress-bar {
37
+ margin-bottom: 20px;
38
+ }
39
+
40
+ .projects-grid {
41
+ display: grid;
42
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
43
+ gap: 20px;
44
+ padding-bottom: 20px;
45
+
46
+ .project-card {
47
+ transition: all 0.3s ease;
48
+
49
+ &:hover {
50
+ transform: translateY(-2px);
51
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
52
+ }
53
+
54
+ &.disabled {
55
+ opacity: 0.7;
56
+
57
+ .project-icon {
58
+ background-color: #999 !important;
59
+ }
60
+ }
61
+
62
+ &.deleted {
63
+ opacity: 0.5;
64
+ background-color: #fafafa;
65
+ }
66
+
67
+ .project-icon {
68
+ background-color: #3f51b5;
69
+ color: white;
70
+ }
71
+
72
+ mat-card-title {
73
+ font-size: 18px;
74
+ font-weight: 500;
75
+ }
76
+
77
+ mat-card-subtitle {
78
+ margin-top: 4px;
79
+ }
80
+
81
+ .project-info {
82
+ margin-top: 16px;
83
+
84
+ .info-item {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 8px;
88
+ margin-bottom: 8px;
89
+ color: #666;
90
+
91
+ mat-icon {
92
+ font-size: 18px;
93
+ width: 18px;
94
+ height: 18px;
95
+ color: #999;
96
+ }
97
+
98
+ span {
99
+ font-size: 14px;
100
+ }
101
+ }
102
+ }
103
+
104
+ mat-card-actions {
105
+ padding: 8px 16px;
106
+ display: flex;
107
+ justify-content: space-between;
108
+
109
+ button:last-child {
110
+ margin-left: auto;
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ .table-container {
117
+ flex: 1;
118
+ overflow: auto;
119
+ background: white;
120
+ border-radius: 8px;
121
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
122
+
123
+ .projects-table {
124
+ width: 100%;
125
+
126
+ .deleted-icon {
127
+ color: #f44336;
128
+ font-size: 16px;
129
+ vertical-align: middle;
130
+ margin-left: 8px;
131
+ }
132
+
133
+ .deleted-row {
134
+ opacity: 0.5;
135
+ background-color: #fafafa;
136
+ }
137
+
138
+ mat-chip {
139
+ font-size: 12px;
140
+ }
141
+ }
142
+ }
143
+
144
+ .no-data {
145
+ text-align: center;
146
+ padding: 60px 20px;
147
+ color: #999;
148
+
149
+ mat-icon {
150
+ font-size: 64px;
151
+ height: 64px;
152
+ width: 64px;
153
+ margin-bottom: 16px;
154
+ }
155
+
156
+ p {
157
+ font-size: 16px;
158
+ margin: 0 0 24px 0;
159
+ }
160
+ }
161
+ }
162
+
163
+ @media (max-width: 768px) {
164
+ .projects-container {
165
+ .toolbar {
166
+ .toolbar-left,
167
+ .toolbar-right {
168
+ width: 100%;
169
+ justify-content: center;
170
+ }
171
+
172
+ .search-field {
173
+ width: 100%;
174
+ }
175
+ }
176
+
177
+ .projects-grid {
178
+ grid-template-columns: 1fr;
179
+ }
180
+ }
181
+ }
flare-ui/src/app/components/user-info/user-info.component.html CHANGED
@@ -1,77 +0,0 @@
1
- <div class="user-info-container">
2
- <h2>Change Password</h2>
3
-
4
- <form [formGroup]="passwordForm" (ngSubmit)="onSubmit()">
5
- <mat-form-field appearance="outline" class="full-width">
6
- <mat-label>Current Password</mat-label>
7
- <input matInput
8
- [type]="hideCurrentPassword ? 'password' : 'text'"
9
- formControlName="currentPassword"
10
- autocomplete="current-password">
11
- <button mat-icon-button matSuffix
12
- (click)="hideCurrentPassword = !hideCurrentPassword"
13
- type="button">
14
- <mat-icon>{{hideCurrentPassword ? 'visibility_off' : 'visibility'}}</mat-icon>
15
- </button>
16
- <mat-error *ngIf="passwordForm.get('currentPassword')?.hasError('required')">
17
- Current password is required
18
- </mat-error>
19
- </mat-form-field>
20
-
21
- <mat-form-field appearance="outline" class="full-width">
22
- <mat-label>New Password</mat-label>
23
- <input matInput
24
- [type]="hideNewPassword ? 'password' : 'text'"
25
- formControlName="newPassword"
26
- autocomplete="new-password">
27
- <button mat-icon-button matSuffix
28
- (click)="hideNewPassword = !hideNewPassword"
29
- type="button">
30
- <mat-icon>{{hideNewPassword ? 'visibility_off' : 'visibility'}}</mat-icon>
31
- </button>
32
- <mat-error *ngIf="passwordForm.get('newPassword')?.hasError('required')">
33
- New password is required
34
- </mat-error>
35
- <mat-error *ngIf="passwordForm.get('newPassword')?.hasError('minlength')">
36
- Password must be at least 8 characters
37
- </mat-error>
38
- <mat-error *ngIf="passwordForm.get('newPassword')?.hasError('pattern')">
39
- Password must contain uppercase, lowercase and numbers
40
- </mat-error>
41
- </mat-form-field>
42
-
43
- <div class="password-strength" *ngIf="passwordForm.get('newPassword')?.value">
44
- <div class="strength-label">
45
- Password Strength: <span [class]="'strength-' + passwordStrengthColor">{{passwordStrengthText}}</span>
46
- </div>
47
- <mat-progress-bar [value]="passwordStrength" [color]="passwordStrengthColor"></mat-progress-bar>
48
- </div>
49
-
50
- <mat-form-field appearance="outline" class="full-width">
51
- <mat-label>Confirm New Password</mat-label>
52
- <input matInput
53
- [type]="hideConfirmPassword ? 'password' : 'text'"
54
- formControlName="confirmPassword"
55
- autocomplete="new-password">
56
- <button mat-icon-button matSuffix
57
- (click)="hideConfirmPassword = !hideConfirmPassword"
58
- type="button">
59
- <mat-icon>{{hideConfirmPassword ? 'visibility_off' : 'visibility'}}</mat-icon>
60
- </button>
61
- <mat-error *ngIf="passwordForm.get('confirmPassword')?.hasError('required')">
62
- Please confirm your password
63
- </mat-error>
64
- <mat-error *ngIf="passwordForm.get('confirmPassword')?.hasError('passwordMismatch')">
65
- Passwords do not match
66
- </mat-error>
67
- </mat-form-field>
68
-
69
- <div class="form-actions">
70
- <button mat-raised-button color="primary"
71
- type="submit"
72
- [disabled]="passwordForm.invalid">
73
- Save
74
- </button>
75
- </div>
76
- </form>
77
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
flare-ui/src/app/components/user-info/user-info.component.scss CHANGED
@@ -1,60 +1,60 @@
1
- .user-info-container {
2
- max-width: 500px;
3
- margin: 0 auto;
4
-
5
- h2 {
6
- margin-bottom: 24px;
7
- color: #333;
8
- }
9
-
10
- form {
11
- display: flex;
12
- flex-direction: column;
13
- gap: 16px;
14
- }
15
-
16
- .full-width {
17
- width: 100%;
18
- }
19
-
20
- .password-strength {
21
- margin-top: -12px;
22
- margin-bottom: 8px;
23
-
24
- .strength-label {
25
- font-size: 12px;
26
- margin-bottom: 4px;
27
- color: #666;
28
-
29
- span {
30
- font-weight: 500;
31
-
32
- &.strength-warn {
33
- color: #f44336;
34
- }
35
-
36
- &.strength-accent {
37
- color: #ff9800;
38
- }
39
-
40
- &.strength-primary {
41
- color: #4caf50;
42
- }
43
- }
44
- }
45
-
46
- mat-progress-bar {
47
- height: 6px;
48
- border-radius: 3px;
49
- }
50
- }
51
-
52
- .form-actions {
53
- margin-top: 16px;
54
- text-align: right;
55
-
56
- button {
57
- min-width: 120px;
58
- }
59
- }
60
  }
 
1
+ .user-info-container {
2
+ max-width: 500px;
3
+ margin: 0 auto;
4
+
5
+ h2 {
6
+ margin-bottom: 24px;
7
+ color: #333;
8
+ }
9
+
10
+ form {
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: 16px;
14
+ }
15
+
16
+ .full-width {
17
+ width: 100%;
18
+ }
19
+
20
+ .password-strength {
21
+ margin-top: -12px;
22
+ margin-bottom: 8px;
23
+
24
+ .strength-label {
25
+ font-size: 12px;
26
+ margin-bottom: 4px;
27
+ color: #666;
28
+
29
+ span {
30
+ font-weight: 500;
31
+
32
+ &.strength-warn {
33
+ color: #f44336;
34
+ }
35
+
36
+ &.strength-accent {
37
+ color: #ff9800;
38
+ }
39
+
40
+ &.strength-primary {
41
+ color: #4caf50;
42
+ }
43
+ }
44
+ }
45
+
46
+ mat-progress-bar {
47
+ height: 6px;
48
+ border-radius: 3px;
49
+ }
50
+ }
51
+
52
+ .form-actions {
53
+ margin-top: 16px;
54
+ text-align: right;
55
+
56
+ button {
57
+ min-width: 120px;
58
+ }
59
+ }
60
  }
flare-ui/src/app/components/user-info/user-info.component.ts CHANGED
@@ -1,110 +1,83 @@
1
- import { Component } from '@angular/core';
2
- import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3
- import { MatSnackBar } from '@angular/material/snack-bar';
4
- import { ApiService } from '../../services/api.service';
5
-
6
- @Component({
7
- selector: 'app-user-info',
8
- templateUrl: './user-info.component.html',
9
- styleUrls: ['./user-info.component.scss']
10
- })
11
- export class UserInfoComponent {
12
- passwordForm: FormGroup;
13
- hideCurrentPassword = true;
14
- hideNewPassword = true;
15
- hideConfirmPassword = true;
16
- passwordStrength = 0;
17
- passwordStrengthText = '';
18
- passwordStrengthColor = '';
19
-
20
- constructor(
21
- private fb: FormBuilder,
22
- private apiService: ApiService,
23
- private snackBar: MatSnackBar
24
- ) {
25
- this.passwordForm = this.fb.group({
26
- currentPassword: ['', Validators.required],
27
- newPassword: ['', [
28
- Validators.required,
29
- Validators.minLength(8),
30
- Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/)
31
- ]],
32
- confirmPassword: ['', Validators.required]
33
- }, { validators: this.passwordMatchValidator });
34
-
35
- // Watch password changes for strength indicator
36
- this.passwordForm.get('newPassword')?.valueChanges.subscribe(password => {
37
- this.updatePasswordStrength(password);
38
- });
39
- }
40
-
41
- passwordMatchValidator(form: FormGroup) {
42
- const newPassword = form.get('newPassword');
43
- const confirmPassword = form.get('confirmPassword');
44
-
45
- if (newPassword && confirmPassword && newPassword.value !== confirmPassword.value) {
46
- confirmPassword.setErrors({ passwordMismatch: true });
47
- } else {
48
- confirmPassword.setErrors(null);
49
- }
50
- return null;
51
- }
52
-
53
- updatePasswordStrength(password: string) {
54
- if (!password) {
55
- this.passwordStrength = 0;
56
- this.passwordStrengthText = '';
57
- return;
58
- }
59
-
60
- let strength = 0;
61
-
62
- // Length check
63
- if (password.length >= 8) strength += 20;
64
- if (password.length >= 12) strength += 20;
65
-
66
- // Character variety
67
- if (/[a-z]/.test(password)) strength += 20;
68
- if (/[A-Z]/.test(password)) strength += 20;
69
- if (/\d/.test(password)) strength += 20;
70
- if (/[^a-zA-Z0-9]/.test(password)) strength += 20;
71
-
72
- this.passwordStrength = Math.min(strength, 100);
73
-
74
- if (this.passwordStrength < 40) {
75
- this.passwordStrengthText = 'Weak';
76
- this.passwordStrengthColor = 'warn';
77
- } else if (this.passwordStrength < 70) {
78
- this.passwordStrengthText = 'Medium';
79
- this.passwordStrengthColor = 'accent';
80
- } else {
81
- this.passwordStrengthText = 'Strong';
82
- this.passwordStrengthColor = 'primary';
83
- }
84
- }
85
-
86
- async onSubmit() {
87
- if (this.passwordForm.invalid) {
88
- return;
89
- }
90
-
91
- try {
92
- await this.apiService.changePassword(
93
- this.passwordForm.value.currentPassword,
94
- this.passwordForm.value.newPassword
95
- ).toPromise();
96
-
97
- this.snackBar.open('Password changed successfully', 'Close', {
98
- duration: 3000,
99
- panelClass: 'success-snackbar'
100
- });
101
-
102
- this.passwordForm.reset();
103
- } catch (error) {
104
- this.snackBar.open('Failed to change password', 'Close', {
105
- duration: 5000,
106
- panelClass: 'error-snackbar'
107
- });
108
- }
109
- }
110
  }
 
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+
5
+ @Component({
6
+ selector: 'app-user-info',
7
+ standalone: true,
8
+ imports: [CommonModule, FormsModule],
9
+ template: `
10
+ <div class="user-info-container">
11
+ <h2>User Information</h2>
12
+
13
+ <div class="card">
14
+ <div class="card-body">
15
+ <p class="text-muted">Password change functionality coming soon...</p>
16
+
17
+ <form (ngSubmit)="changePassword()" #passwordForm="ngForm">
18
+ <div class="form-group">
19
+ <label for="currentPassword">Current Password</label>
20
+ <input
21
+ type="password"
22
+ id="currentPassword"
23
+ name="currentPassword"
24
+ [(ngModel)]="currentPassword"
25
+ required
26
+ disabled
27
+ >
28
+ </div>
29
+
30
+ <div class="form-group">
31
+ <label for="newPassword">New Password</label>
32
+ <input
33
+ type="password"
34
+ id="newPassword"
35
+ name="newPassword"
36
+ [(ngModel)]="newPassword"
37
+ required
38
+ disabled
39
+ >
40
+ </div>
41
+
42
+ <div class="form-group">
43
+ <label for="confirmPassword">Confirm New Password</label>
44
+ <input
45
+ type="password"
46
+ id="confirmPassword"
47
+ name="confirmPassword"
48
+ [(ngModel)]="confirmPassword"
49
+ required
50
+ disabled
51
+ >
52
+ </div>
53
+
54
+ <button type="submit" class="btn btn-primary" disabled>
55
+ Change Password
56
+ </button>
57
+ </form>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ `,
62
+ styles: [`
63
+ .user-info-container {
64
+ h2 {
65
+ margin-bottom: 1.5rem;
66
+ }
67
+ }
68
+
69
+ .text-muted {
70
+ color: #6c757d;
71
+ margin-bottom: 1rem;
72
+ }
73
+ `]
74
+ })
75
+ export class UserInfoComponent {
76
+ currentPassword = '';
77
+ newPassword = '';
78
+ confirmPassword = '';
79
+
80
+ changePassword() {
81
+ console.log('Password change not implemented yet');
82
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  }
flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.html ADDED
File without changes
flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.scss ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ::ng-host {
2
+ display: block;
3
+ width: 800px;
4
+ max-width: 90vw;
5
+ }
6
+
7
+ mat-dialog-content {
8
+ max-height: 70vh;
9
+ padding: 0;
10
+ }
11
+
12
+ .tab-content {
13
+ padding: 24px;
14
+ }
15
+
16
+ .full-width {
17
+ width: 100%;
18
+ }
19
+
20
+ .json-field {
21
+ margin: 16px 0;
22
+
23
+ label {
24
+ display: block;
25
+ margin-bottom: 8px;
26
+ font-weight: 500;
27
+ color: rgba(0,0,0,0.87);
28
+ }
29
+
30
+ .json-textarea {
31
+ width: 100%;
32
+ min-height: 120px;
33
+ padding: 12px;
34
+ border: 1px solid #ccc;
35
+ border-radius: 4px;
36
+ font-family: 'Courier New', monospace;
37
+ font-size: 13px;
38
+ resize: vertical;
39
+
40
+ &:focus {
41
+ outline: none;
42
+ border-color: #3f51b5;
43
+ }
44
+ }
45
+
46
+ .json-help {
47
+ display: flex;
48
+ gap: 8px;
49
+ margin-top: 8px;
50
+ }
51
+
52
+ .json-error {
53
+ color: #f44336;
54
+ font-size: 12px;
55
+ margin-top: 4px;
56
+ }
57
+ }
58
+
59
+ .template-vars-menu {
60
+ padding: 16px;
61
+ max-width: 300px;
62
+
63
+ p {
64
+ margin: 0 0 8px 0;
65
+ }
66
+
67
+ code {
68
+ display: block;
69
+ padding: 4px 8px;
70
+ margin: 4px 0;
71
+ background: #f5f5f5;
72
+ border-radius: 4px;
73
+ font-size: 12px;
74
+ }
75
+ }
76
+
77
+ .retry-section {
78
+ margin: 24px 0;
79
+
80
+ h4 {
81
+ margin-bottom: 16px;
82
+ color: rgba(0,0,0,0.87);
83
+ }
84
+
85
+ .retry-fields {
86
+ display: flex;
87
+ gap: 16px;
88
+
89
+ mat-form-field {
90
+ flex: 1;
91
+ }
92
+ }
93
+ }
94
+
95
+ .headers-section {
96
+ .add-header {
97
+ display: flex;
98
+ gap: 16px;
99
+ align-items: flex-start;
100
+ margin-bottom: 24px;
101
+
102
+ .header-key {
103
+ flex: 1;
104
+ }
105
+
106
+ .header-value {
107
+ flex: 2;
108
+ }
109
+
110
+ button {
111
+ margin-top: 8px;
112
+ }
113
+ }
114
+
115
+ .headers-list {
116
+ h4 {
117
+ margin-bottom: 16px;
118
+ color: rgba(0,0,0,0.87);
119
+ }
120
+
121
+ .headers-table {
122
+ width: 100%;
123
+ border: 1px solid #e0e0e0;
124
+ border-radius: 4px;
125
+
126
+ .table-field {
127
+ width: 100%;
128
+ margin: 0;
129
+
130
+ ::ng-deep .mat-form-field-wrapper {
131
+ padding: 0;
132
+ margin: 0;
133
+ }
134
+
135
+ ::ng-deep .mat-form-field-underline {
136
+ display: none;
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ .auth-fields {
144
+ margin-top: 16px;
145
+
146
+ h4 {
147
+ margin: 24px 0 16px 0;
148
+ color: rgba(0,0,0,0.87);
149
+ }
150
+
151
+ .section-divider {
152
+ margin: 24px 0;
153
+ }
154
+ }
155
+
156
+ .test-section {
157
+ h4 {
158
+ margin-bottom: 16px;
159
+ color: rgba(0,0,0,0.87);
160
+ }
161
+
162
+ button {
163
+ margin: 16px 0;
164
+ min-width: 180px;
165
+
166
+ mat-spinner {
167
+ display: inline-block;
168
+ margin-right: 8px;
169
+ }
170
+ }
171
+
172
+ .test-result {
173
+ margin-top: 24px;
174
+
175
+ .result-info {
176
+ padding: 12px;
177
+ border-radius: 4px;
178
+ margin-bottom: 16px;
179
+ font-size: 14px;
180
+
181
+ &.success {
182
+ background-color: #e8f5e9;
183
+ color: #2e7d32;
184
+ }
185
+
186
+ &.error {
187
+ background-color: #ffebee;
188
+ color: #c62828;
189
+ }
190
+ }
191
+
192
+ .response-body {
193
+ background: #f5f5f5;
194
+ padding: 16px;
195
+ border-radius: 4px;
196
+ overflow-x: auto;
197
+ font-size: 12px;
198
+ max-height: 300px;
199
+ overflow-y: auto;
200
+ }
201
+ }
202
+ }
203
+
204
+ mat-dialog-actions {
205
+ padding: 16px 24px;
206
+ margin: 0;
207
+ border-top: 1px solid #e0e0e0;
208
+ }
flare-ui/src/app/dialogs/api-edit-dialog/api-edit-dialog.component.ts ADDED
File without changes
flare-ui/src/app/dialogs/confirm-dialog/confirm-dialog.component.html ADDED
File without changes
flare-ui/src/app/dialogs/confirm-dialog/confirm-dialog.component.scss ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ mat-dialog-content {
2
+ p {
3
+ margin: 0;
4
+ color: rgba(0,0,0,0.87);
5
+ line-height: 1.5;
6
+ }
7
+ }
8
+
9
+ mat-dialog-actions {
10
+ margin-top: 20px;
11
+ padding: 8px 0;
12
+ }
flare-ui/src/app/dialogs/confirm-dialog/confirm-dialog.component.ts ADDED
File without changes
flare-ui/src/app/dialogs/project-edit-dialog/project-edit-dialog.component.html ADDED
File without changes
flare-ui/src/app/dialogs/project-edit-dialog/project-edit-dialog.component.scss ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ mat-dialog-content {
2
+ padding: 24px;
3
+ min-width: 400px;
4
+
5
+ .full-width {
6
+ width: 100%;
7
+ margin-bottom: 16px;
8
+ }
9
+
10
+ .metadata {
11
+ margin-top: 24px;
12
+ padding: 16px;
13
+ background-color: #f5f5f5;
14
+ border-radius: 4px;
15
+
16
+ .metadata-item {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 8px;
20
+ margin-bottom: 8px;
21
+
22
+ &:last-child {
23
+ margin-bottom: 0;
24
+ }
25
+
26
+ mat-icon {
27
+ color: #666;
28
+ font-size: 20px;
29
+ width: 20px;
30
+ height: 20px;
31
+ }
32
+
33
+ span {
34
+ color: #666;
35
+ font-size: 14px;
36
+
37
+ strong {
38
+ color: #333;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ mat-dialog-actions {
46
+ padding: 16px 24px;
47
+ margin: 0;
48
+ }
flare-ui/src/app/dialogs/project-edit-dialog/project-edit-dialog.component.ts ADDED
File without changes
flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.html ADDED
File without changes
flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.scss ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ::ng-host {
2
+ display: block;
3
+ }
4
+
5
+ mat-dialog-content {
6
+ min-width: 600px;
7
+ min-height: 400px;
8
+
9
+ .version-management-placeholder {
10
+ text-align: center;
11
+ padding: 60px 20px;
12
+
13
+ mat-icon {
14
+ font-size: 64px;
15
+ width: 64px;
16
+ height: 64px;
17
+ color: #666;
18
+ margin-bottom: 20px;
19
+ }
20
+
21
+ h3 {
22
+ color: #333;
23
+ margin-bottom: 16px;
24
+ }
25
+
26
+ p {
27
+ color: #666;
28
+ margin-bottom: 20px;
29
+ }
30
+
31
+ ul {
32
+ text-align: left;
33
+ max-width: 400px;
34
+ margin: 0 auto;
35
+ color: #666;
36
+
37
+ li {
38
+ margin-bottom: 8px;
39
+ }
40
+ }
41
+ }
42
+ }
flare-ui/src/app/dialogs/version-edit-dialog/version-edit-dialog.component.ts ADDED
File without changes
flare-ui/src/environments/environment.prod.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export const environment = {
2
+ production: true,
3
+ apiUrl: '' // In production, use relative path (empty string means same domain)
4
+ };
flare-ui/src/environments/environment.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export const environment = {
2
+ production: false,
3
+ apiUrl: 'http://localhost:7860' // Update to match your backend URL
4
+ };