remake init data
json dump
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||
import {User} from '../../models/user.model';
|
||||
import {GlobalConstants} from '../../global-constants';
|
||||
import {Table} from '../../models/table';
|
||||
import {Role} from '../../models/role';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -13,8 +15,26 @@ export class AdminModuleService {
|
||||
constructor(private http: HttpClient) {
|
||||
|
||||
}
|
||||
|
||||
updateUserRoles(fileName: any,data: User): Observable<any> {
|
||||
return this.http.put(`${this.baseUrl}/users/${fileName}`, data);
|
||||
getUsers(): Observable<User[]> {
|
||||
return this.http.get<User[]>(`${this.baseUrl}/users`);
|
||||
}
|
||||
|
||||
getAllRoles(): Observable<Role[]> {
|
||||
return this.http.get<Role[]>(`${this.baseUrl}/roles`);
|
||||
}
|
||||
|
||||
updateUserRoles(id: any,data: User): Observable<any> {
|
||||
return this.http.put(`${this.baseUrl}/users/${id}`, data);
|
||||
}
|
||||
/*
|
||||
|
||||
getUserTables(schema?: string): Observable<Table[]> {
|
||||
let params = new HttpParams();
|
||||
if (schema) {
|
||||
params = params.set('schema', schema);
|
||||
}
|
||||
return this.http.get<Table[]>(`${this.baseUrl}/admin/json-dump/get-tables`, { params });
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<app-side-bar-admin
|
||||
class="pc-sidebar"
|
||||
>
|
||||
</app-side-bar-admin>
|
||||
<div class="pc-container">
|
||||
<app-breadcrumbs></app-breadcrumbs>
|
||||
<router-outlet />
|
||||
|
||||
<app-vertical-nav
|
||||
[routes]="adminRoutes"
|
||||
[basePath]="basePath"
|
||||
(collapsedChange)="navCollapsedChanged($event)">
|
||||
</app-vertical-nav>
|
||||
|
||||
<div class="app-breadcrumb" [class.collapsed]="isCollapsed">
|
||||
<app-breadcrumbs></app-breadcrumbs>
|
||||
</div>
|
||||
|
||||
<div class="app-container" [class.collapsed]="isCollapsed">
|
||||
<div class="page-component">
|
||||
<router-outlet />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
//---------------
|
||||
.pc-sidebar{
|
||||
top: 65px;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(153, 153, 153, 0.16);
|
||||
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.pc-container{
|
||||
top: 0px;
|
||||
.app-container{
|
||||
top: -12px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
margin-left: 240px;
|
||||
padding-top: 1px;
|
||||
position: relative;
|
||||
&.collapsed{
|
||||
margin-left: 64px;
|
||||
}
|
||||
}
|
||||
.app-breadcrumb{
|
||||
position: fixed;
|
||||
left: 240px;
|
||||
width: 100%;
|
||||
background-color: rgba(153, 153, 153, 0.16);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 100;
|
||||
&.collapsed{
|
||||
left: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-component{
|
||||
height: 100%;
|
||||
margin-top: 65px;
|
||||
margin-bottom: 50px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
background: aliceblue;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,69 +1,31 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||
import {Router, RouterOutlet} from "@angular/router";
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {SideBarAdminComponent} from '../side-bar-admin.component/side-bar-admin.component';
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {RouterOutlet} from "@angular/router";
|
||||
import {BreadcrumbsComponent} from '../../../components/breadcrumbs.component/breadcrumbs.component';
|
||||
import {VerticalNavComponent} from '../../../components/vertical-nav.component/vertical-nav.component';
|
||||
import {ADMIN_ROUTES} from '../admin.routing';
|
||||
@Component({
|
||||
selector: 'app-admin.component',
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
SideBarAdminComponent,
|
||||
BreadcrumbsComponent
|
||||
BreadcrumbsComponent,
|
||||
VerticalNavComponent
|
||||
],
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA],
|
||||
templateUrl: './admin.component.html',
|
||||
styleUrl: './admin.component.scss'
|
||||
})
|
||||
export class AdminComponent implements OnInit {
|
||||
private document = inject<Document>(DOCUMENT);
|
||||
height = 0;
|
||||
export class AdminComponent{
|
||||
|
||||
constructor(private router: Router) {
|
||||
this.height = 0
|
||||
//adminRoutes = (ADMIN_ROUTES[0]?.children ?? []).filter(r => r.path && r.data?.['nav']);
|
||||
adminRoutes = (ADMIN_ROUTES[0]?.children ?? []).filter(r => r.path && r.data?.['nav']);
|
||||
|
||||
// If your admin module is mounted at '/admin' in the app routes, keep this as '/admin'.
|
||||
// If it's at root, set this to ''.
|
||||
basePath = '/main/admin';
|
||||
|
||||
isCollapsed = false;
|
||||
|
||||
navCollapsedChanged($event: boolean) {
|
||||
this.isCollapsed = $event;
|
||||
}
|
||||
|
||||
// public props
|
||||
navCollapsed: boolean = false;
|
||||
navCollapsedMob: boolean = false;
|
||||
|
||||
// public method
|
||||
navMobClick() {
|
||||
if (this.navCollapsedMob && !document.querySelector('app-navigation.pc-sidebar')?.classList.contains('mob-open')) {
|
||||
this.navCollapsedMob = !this.navCollapsedMob;
|
||||
setTimeout(() => {
|
||||
this.navCollapsedMob = !this.navCollapsedMob;
|
||||
}, 100);
|
||||
} else {
|
||||
this.navCollapsedMob = !this.navCollapsedMob;
|
||||
}
|
||||
if (document.querySelector('app-navigation.pc-sidebar')?.classList.contains('navbar-collapsed')) {
|
||||
document.querySelector('app-navigation.pc-sidebar')?.classList.remove('navbar-collapsed');
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(event: KeyboardEvent): void {
|
||||
if (event.key === 'Escape') {
|
||||
this.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
if (document.querySelector('app-navigation.pc-sidebar')?.classList.contains('mob-open')) {
|
||||
document.querySelector('app-navigation.pc-sidebar')?.classList.remove('mob-open');
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
if(this.document.defaultView !== null || this.document.defaultView !== undefined) {
|
||||
// @ts-ignore
|
||||
this.height = this.document.defaultView.innerHeight;
|
||||
}
|
||||
else
|
||||
this.height = 0;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {AdminComponent} from './admin.component/admin.component';
|
||||
|
||||
const ADMIN_ROUTES: Routes = [
|
||||
export const ADMIN_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AdminComponent,
|
||||
data:{breadcrumb: 'Admin'},
|
||||
loadComponent: () => import('../admin-module/admin.component/admin.component').then((c) => c.AdminComponent),
|
||||
data:{title: 'Admin', icon:'', nav:true},
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'admin-welcome' },
|
||||
{
|
||||
path: 'admin-welcome',
|
||||
loadComponent: () => import('../admin-module/admin-welcome.component/admin-welcome.component').then((c) => c.AdminWelcomeComponent),
|
||||
data:{breadcrumb: 'Welcome'}
|
||||
data:{title: 'Welcome', icon:'dashboard', nav:true}
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
loadComponent: () => import('../admin-module/settings.component/settings.component').then((c) => c.SettingsComponent),
|
||||
data:{breadcrumb: 'Settings'}
|
||||
data:{title: 'Settings', icon:'settings', nav:true}
|
||||
},
|
||||
{
|
||||
path: 'system',
|
||||
loadComponent: () => import('../admin-module/system.component/system.component').then((c) => c.SystemComponent),
|
||||
data:{breadcrumb: 'System'}
|
||||
data:{title: 'System', icon:'component_exchange', nav:true}
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
loadComponent: () => import('../admin-module/users.component/users.component').then((c) => c.UsersComponent),
|
||||
data:{breadcrumb: 'Users'}
|
||||
data:{title: 'Users', icon:'', nav:true}
|
||||
},
|
||||
{
|
||||
path:"data-base",
|
||||
loadComponent: () => import('../admin-module/data-base.component/data-base.component').then((c) => c.DataBaseComponent),
|
||||
data:{title: 'Data', icon:'data_table', nav:true}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<div class="json-dump-container" *ngIf="!loading; else loadingTpl">
|
||||
<h2>JSON Dump Management</h2>
|
||||
|
||||
<!-- Entity-specific Export/Import -->
|
||||
<div class="entity-actions" *ngFor="let entity of entityTypes">
|
||||
<h3>{{ entity | titlecase }}</h3>
|
||||
<button mat-raised-button color="primary" (click)="exportEntity(entity)">
|
||||
<mat-icon>download</mat-icon> Export {{ entity }}
|
||||
</button>
|
||||
<label mat-raised-button color="accent">
|
||||
<mat-icon>upload</mat-icon> Import {{ entity }}
|
||||
<input type="file" hidden accept=".json" (change)="onFileSelected($event, entity)">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Files Table -->
|
||||
<h3>Available JSON Files</h3>
|
||||
<mat-table [dataSource]="files">
|
||||
<ng-container matColumnDef="fileName">
|
||||
<mat-header-cell *matHeaderCellDef> File Name </mat-header-cell>
|
||||
<mat-cell *matCellDef="let file">{{ file.fileName }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="size">
|
||||
<mat-header-cell *matHeaderCellDef> Size </mat-header-cell>
|
||||
<mat-cell *matCellDef="let file">{{ jsonDumpService.formatFileSize(file.size) }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="lastModified">
|
||||
<mat-header-cell *matHeaderCellDef> Last Modified </mat-header-cell>
|
||||
<mat-cell *matCellDef="let file">{{ jsonDumpService.formatDate(file.lastModified) }}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell *matHeaderCellDef> Actions </mat-header-cell>
|
||||
<mat-cell *matCellDef="let file">
|
||||
<button mat-icon-button (click)="downloadFile(file.fileName)" matTooltip="Download">
|
||||
<mat-icon>download</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="deleteFile(file.fileName)" matTooltip="Delete">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="['fileName', 'size', 'lastModified', 'actions']"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: ['fileName', 'size', 'lastModified', 'actions']"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<!-- Archives -->
|
||||
<h3>Archives</h3>
|
||||
<mat-list>
|
||||
<mat-list-item *ngFor="let archive of archives">{{ archive }}</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
|
||||
<ng-template #loadingTpl>
|
||||
<mat-spinner></mat-spinner>
|
||||
</ng-template>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
.json-dump-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.export-buttons,
|
||||
.import-section {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-bottom: 16px;
|
||||
|
||||
button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
mat-table {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
+6
-6
@@ -1,18 +1,18 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SideBarAdminComponent } from './side-bar-admin.component';
|
||||
import { JsonDumpComponent } from './json-dump.component';
|
||||
|
||||
describe('SideBarAdminComponent', () => {
|
||||
let component: SideBarAdminComponent;
|
||||
let fixture: ComponentFixture<SideBarAdminComponent>;
|
||||
describe('JsonDumpComponent', () => {
|
||||
let component: JsonDumpComponent;
|
||||
let fixture: ComponentFixture<JsonDumpComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SideBarAdminComponent]
|
||||
imports: [JsonDumpComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SideBarAdminComponent);
|
||||
fixture = TestBed.createComponent(JsonDumpComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core';
|
||||
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import {MatList, MatListItem} from '@angular/material/list';
|
||||
import {
|
||||
MatCell, MatCellDef,
|
||||
MatColumnDef,
|
||||
MatHeaderCell, MatHeaderCellDef,
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef, MatTable
|
||||
} from '@angular/material/table';
|
||||
import {MatIconButton} from '@angular/material/button';
|
||||
import {NgForOf, NgIf, TitleCasePipe} from '@angular/common';
|
||||
import {MatProgressSpinner} from '@angular/material/progress-spinner';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {ArchivesResponse, FileInfo, FilesResponse, JsonDumpService} from '../../json-dump.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-json-dump',
|
||||
templateUrl: './json-dump.component.html',
|
||||
imports: [
|
||||
MatProgressSpinner,
|
||||
MatListItem,
|
||||
MatList,
|
||||
MatHeaderRow,
|
||||
MatRow,
|
||||
MatHeaderRowDef,
|
||||
MatRowDef,
|
||||
MatIcon,
|
||||
MatHeaderCell,
|
||||
MatCell,
|
||||
MatIconButton,
|
||||
MatTooltip,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatCellDef,
|
||||
MatTable,
|
||||
TitleCasePipe,
|
||||
NgIf,
|
||||
NgForOf
|
||||
],
|
||||
styleUrls: ['./json-dump.component.scss'],
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class JsonDumpComponent implements OnInit {
|
||||
files: FileInfo[] = [];
|
||||
archives: string[] = [];
|
||||
loading = false;
|
||||
|
||||
entityTypes = ['users', 'roles', 'tutorials'];
|
||||
|
||||
constructor(
|
||||
public jsonDumpService: JsonDumpService,
|
||||
private snackBar: MatSnackBar
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadFiles();
|
||||
this.loadArchives();
|
||||
}
|
||||
|
||||
loadFiles(): void {
|
||||
this.loading = true;
|
||||
this.jsonDumpService.getFiles().subscribe({
|
||||
next: (res: FilesResponse) => {
|
||||
this.files = res.files || [];
|
||||
this.loading = false;
|
||||
},
|
||||
error: (err: { message: any; }) => {
|
||||
this.loading = false;
|
||||
this.showMessage(`Error loading files: ${err.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadArchives(): void {
|
||||
this.jsonDumpService.getArchivedFiles().subscribe({
|
||||
next: (res: ArchivesResponse) => {
|
||||
this.archives = res.archives || [];
|
||||
},
|
||||
error: (err: { message: any; }) => {
|
||||
this.showMessage(`Error loading archives: ${err.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exportEntity(entity: string): void {
|
||||
this.jsonDumpService.exportEntity(entity).subscribe({
|
||||
next: () => {
|
||||
this.showMessage(`Exported ${entity} successfully`);
|
||||
this.loadFiles();
|
||||
},
|
||||
error: (err: { message: any; }) => this.showMessage(`Export ${entity} failed: ${err.message}`)
|
||||
});
|
||||
}
|
||||
|
||||
importEntity(entity: string, file: File): void {
|
||||
this.jsonDumpService.importEntity(entity, file).subscribe({
|
||||
next: () => this.showMessage(`Imported ${entity} successfully`),
|
||||
error: (err: { message: any; }) => this.showMessage(`Import ${entity} failed: ${err.message}`)
|
||||
});
|
||||
}
|
||||
|
||||
onFileSelected(event: Event, entity: string): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
if (input.files && input.files.length > 0) {
|
||||
this.importEntity(entity, input.files[0]);
|
||||
input.value = ''; // reset input
|
||||
}
|
||||
}
|
||||
|
||||
deleteFile(fileName: string): void {
|
||||
this.jsonDumpService.deleteFile(fileName).subscribe({
|
||||
next: () => {
|
||||
this.showMessage(`Deleted file: ${fileName}`);
|
||||
this.loadFiles();
|
||||
},
|
||||
error: (err: { message: any; }) => this.showMessage(`Delete failed: ${err.message}`)
|
||||
});
|
||||
}
|
||||
|
||||
downloadFile(fileName: string): void {
|
||||
this.jsonDumpService.downloadFile(fileName).subscribe({
|
||||
next: (blob: any) => this.jsonDumpService.downloadBlob(blob, fileName),
|
||||
error: (err: { message: any; }) => this.showMessage(`Download failed: ${err.message}`)
|
||||
});
|
||||
}
|
||||
|
||||
private showMessage(msg: string): void {
|
||||
this.snackBar.open(msg, 'Close', { duration: 3000 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<p>data-base.component works!</p>
|
||||
<ul>
|
||||
@for (table of tables; track table){
|
||||
<li>{{ table.schema }}.{{ table.table }}</li>
|
||||
}
|
||||
|
||||
</ul>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DataBaseComponent } from './data-base.component';
|
||||
|
||||
describe('DataBaseComponent', () => {
|
||||
let component: DataBaseComponent;
|
||||
let fixture: ComponentFixture<DataBaseComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DataBaseComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DataBaseComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Table} from '../../../models/table';
|
||||
import {AdminModuleService} from '../admin-module.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-data-base.component',
|
||||
imports: [],
|
||||
templateUrl: './data-base.component.html',
|
||||
styleUrl: './data-base.component.scss'
|
||||
})
|
||||
export class DataBaseComponent {
|
||||
tables: Table[] = [];
|
||||
|
||||
constructor(private adminService:AdminModuleService) {
|
||||
this.getTables();
|
||||
}
|
||||
|
||||
getTables(){
|
||||
// this.adminService.getUserTables(/* optionally: 'public' */).subscribe({
|
||||
// next: data => (this.tables = data),
|
||||
// error: err => console.error('Failed to load tables', err)
|
||||
// });
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { JsonDumpService } from './json-dump.service';
|
||||
|
||||
describe('JsonDumpService', () => {
|
||||
let service: JsonDumpService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(JsonDumpService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,449 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import {GlobalConstants} from '../../global-constants';
|
||||
|
||||
// Interfaces
|
||||
export interface ApiResponse<T = any> {
|
||||
success: boolean;
|
||||
message: string;
|
||||
timestamp?: string;
|
||||
data?: T;
|
||||
}
|
||||
|
||||
export interface ExportResponse extends ApiResponse {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface ImportResponse extends ApiResponse {
|
||||
timestamp: string;
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
fileName: string;
|
||||
size: number;
|
||||
lastModified: string;
|
||||
//path: string;
|
||||
}
|
||||
|
||||
export interface FilesResponse extends ApiResponse {
|
||||
files: FileInfo[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface ArchivesResponse extends ApiResponse {
|
||||
archives: string[];
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface ExtractResponse extends ApiResponse {
|
||||
fileName: string;
|
||||
targetDirectory: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class JsonDumpService {
|
||||
private readonly baseUrl = `${GlobalConstants.API_URL}/admin/json-dump`;
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
exportEntity(entity: string) {
|
||||
return this.http.post<{message: string}>(`${this.baseUrl}/export/${entity}`, {});
|
||||
}
|
||||
|
||||
importEntity(entity: string, file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return this.http.post(`${this.baseUrl}/import/${entity}`, formData);
|
||||
}
|
||||
|
||||
// ==================== EXPORT METHODS ====================
|
||||
|
||||
/**
|
||||
* Export all entities (users, tutorials, roles)
|
||||
*/
|
||||
exportAll(): Observable<ExportResponse> {
|
||||
return this.http.post<ExportResponse>(`${this.baseUrl}/export/all`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export users only
|
||||
*/
|
||||
exportUsers(): Observable<ExportResponse> {
|
||||
return this.http.post<ExportResponse>(`${this.baseUrl}/export/users`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export tutorials only
|
||||
*/
|
||||
exportTutorials(): Observable<ExportResponse> {
|
||||
return this.http.post<ExportResponse>(`${this.baseUrl}/export/tutorials`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export roles only
|
||||
*/
|
||||
exportRoles(): Observable<ExportResponse> {
|
||||
return this.http.post<ExportResponse>(`${this.baseUrl}/export/roles`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== IMPORT METHODS ====================
|
||||
|
||||
/**
|
||||
* Import users from latest JSON file
|
||||
*/
|
||||
importUsers(): Observable<ImportResponse> {
|
||||
return this.http.post<ImportResponse>(`${this.baseUrl}/import/users`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import tutorials from latest JSON file
|
||||
*/
|
||||
importTutorials(): Observable<ImportResponse> {
|
||||
return this.http.post<ImportResponse>(`${this.baseUrl}/import/tutorials`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import roles from latest JSON file
|
||||
*/
|
||||
importRoles(): Observable<ImportResponse> {
|
||||
return this.http.post<ImportResponse>(`${this.baseUrl}/import/roles`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import users from uploaded file
|
||||
*/
|
||||
importUsersFromFile(file: File): Observable<ImportResponse> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return this.http.post<ImportResponse>(`${this.baseUrl}/import/users/file`, formData)
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import tutorials from uploaded file
|
||||
*/
|
||||
importTutorialsFromFile(file: File): Observable<ImportResponse> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return this.http.post<ImportResponse>(`${this.baseUrl}/import/tutorials/file`, formData)
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== ARCHIVE METHODS ====================
|
||||
|
||||
/**
|
||||
* Get list of all archived files
|
||||
*/
|
||||
getArchivedFiles(): Observable<ArchivesResponse> {
|
||||
return this.http.get<ArchivesResponse>(`${this.baseUrl}/archives`)
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
archiveOldFiles(): Observable<ApiResponse> {
|
||||
return this.http.post<ApiResponse>(`${this.baseUrl}/archives/archive-old-files`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract archived file
|
||||
*/
|
||||
extractArchive(fileName: string): Observable<ExtractResponse> {
|
||||
return this.http.post<ExtractResponse>(`${this.baseUrl}/archives/${fileName}/extract`, {})
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download archived file
|
||||
*/
|
||||
downloadArchive(fileName: string): Observable<Blob> {
|
||||
return this.http.get(`${this.baseUrl}/archives/${fileName}/download`, {
|
||||
responseType: 'blob'
|
||||
}).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== FILE MANAGEMENT METHODS ====================
|
||||
|
||||
/**
|
||||
* Get list of all JSON files with details
|
||||
*/
|
||||
getFiles(): Observable<FilesResponse> {
|
||||
return this.http.get<FilesResponse>(`${this.baseUrl}/files`)
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download JSON file
|
||||
*/
|
||||
downloadFile(fileName: string): Observable<Blob> {
|
||||
return this.http.get(`${this.baseUrl}/files/${fileName}/download`, {
|
||||
responseType: 'blob'
|
||||
}).pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete JSON file
|
||||
*/
|
||||
deleteFile(fileName: string): Observable<ApiResponse> {
|
||||
return this.http.delete<ApiResponse>(`${this.baseUrl}/files/${fileName}`)
|
||||
.pipe(
|
||||
catchError(this.handleError)
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== UTILITY METHODS ====================
|
||||
|
||||
/**
|
||||
* Download blob as file
|
||||
*/
|
||||
downloadBlob(blob: Blob, fileName: string): void {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size for display
|
||||
*/
|
||||
formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date for display
|
||||
*/
|
||||
formatDate(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate JSON file
|
||||
*/
|
||||
isValidJsonFile(file: File): boolean {
|
||||
return file.type === 'application/json' || file.name.toLowerCase().endsWith('.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity type from file name
|
||||
*/
|
||||
getEntityTypeFromFileName(fileName: string): string {
|
||||
if (fileName.includes('users_')) return 'users';
|
||||
if (fileName.includes('tutorials_')) return 'tutorials';
|
||||
if (fileName.includes('roles_')) return 'roles';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file is recent (less than 7 days old)
|
||||
*/
|
||||
isRecentFile(lastModified: string): boolean {
|
||||
const fileDate = new Date(lastModified);
|
||||
const sevenDaysAgo = new Date();
|
||||
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
||||
return fileDate > sevenDaysAgo;
|
||||
}
|
||||
|
||||
// ==================== BATCH OPERATIONS ====================
|
||||
|
||||
/**
|
||||
* Export all entities and return combined status
|
||||
*/
|
||||
// exportAllSeparately(): Observable<{ users: ExportResponse; tutorials: ExportResponse; roles: ExportResponse }> {
|
||||
// const users$ = this.exportUsers();
|
||||
// const tutorials$ = this.exportTutorials();
|
||||
// const roles$ = this.exportRoles();
|
||||
//
|
||||
// return new Observable(observer => {
|
||||
// const results: any = {};
|
||||
// let completed = 0;
|
||||
//
|
||||
// const checkCompletion = () => {
|
||||
// if (completed === 3) {
|
||||
// observer.next(
|
||||
// value:{users:users$,tutorial:tutorials$,roles:roles$},
|
||||
// );
|
||||
// observer.complete();
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// users$.subscribe({
|
||||
// next: (result) => {
|
||||
// results.users = result;
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// },
|
||||
// error: (error) => {
|
||||
// results.users = { success: false, message: error.message, timestamp: new Date().toISOString() };
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// tutorials$.subscribe({
|
||||
// next: (result) => {
|
||||
// results.tutorials = result;
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// },
|
||||
// error: (error) => {
|
||||
// results.tutorials = { success: false, message: error.message, timestamp: new Date().toISOString() };
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// roles$.subscribe({
|
||||
// next: (result) => {
|
||||
// results.roles = result;
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// },
|
||||
// error: (error) => {
|
||||
// results.roles = { success: false, message: error.message, timestamp: new Date().toISOString() };
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Import all entities from latest files
|
||||
*/
|
||||
// importAll(): Observable<{ users: ImportResponse; tutorials: ImportResponse; roles: ImportResponse }> {
|
||||
// const users$ = this.importUsers();
|
||||
// const tutorials$ = this.importTutorials();
|
||||
// const roles$ = this.importRoles();
|
||||
//
|
||||
// return new Observable(observer => {
|
||||
// const results: any = {};
|
||||
// let completed = 0;
|
||||
//
|
||||
// const checkCompletion = () => {
|
||||
// if (completed === 3) {
|
||||
// observer.next(
|
||||
// value:{user:}
|
||||
// );
|
||||
// observer.complete();
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// users$.subscribe({
|
||||
// next: (result) => {
|
||||
// results.users = result;
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// },
|
||||
// error: (error) => {
|
||||
// results.users = { success: false, message: error.message, timestamp: new Date().toISOString() };
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// tutorials$.subscribe({
|
||||
// next: (result) => {
|
||||
// results.tutorials = result;
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// },
|
||||
// error: (error) => {
|
||||
// results.tutorials = { success: false, message: error.message, timestamp: new Date().toISOString() };
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// roles$.subscribe({
|
||||
// next: (result) => {
|
||||
// results.roles = result;
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// },
|
||||
// error: (error) => {
|
||||
// results.roles = { success: false, message: error.message, timestamp: new Date().toISOString() };
|
||||
// completed++;
|
||||
// checkCompletion();
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// ==================== ERROR HANDLING ====================
|
||||
|
||||
private handleError = (error: HttpErrorResponse): Observable<never> => {
|
||||
let errorMessage = 'An unknown error occurred';
|
||||
|
||||
if (error.error instanceof ErrorEvent) {
|
||||
// Client-side error
|
||||
errorMessage = error.error.message;
|
||||
} else {
|
||||
// Server-side error
|
||||
if (error.error && error.error.message) {
|
||||
errorMessage = error.error.message;
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
} else {
|
||||
errorMessage = `Server returned code ${error.status}: ${error.statusText}`;
|
||||
}
|
||||
}
|
||||
|
||||
console.error('JsonDumpService Error:', errorMessage, error);
|
||||
return throwError(() => new Error(errorMessage));
|
||||
};
|
||||
}
|
||||
@@ -25,3 +25,5 @@
|
||||
</mat-tab-group>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-divider></mat-divider>
|
||||
<app-json-dump></app-json-dump>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@angular/material/table';
|
||||
import {SystemService} from '../../../services/system.service';
|
||||
import {NameValueItem} from '../../../models/name-value-item';
|
||||
import {JsonDumpComponent} from '../components/json-dump.component/json-dump.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings.component',
|
||||
@@ -32,7 +33,8 @@ import {NameValueItem} from '../../../models/name-value-item';
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef
|
||||
MatRowDef,
|
||||
JsonDumpComponent
|
||||
],
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrl: './settings.component.scss'
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
<mat-nav-list>
|
||||
<a mat-list-item routerLink="admin-welcome">
|
||||
<span class="entry">
|
||||
<mat-icon>house</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Dashboard</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="users">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Users</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="settings">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Settings</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="system">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >System</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
.entry{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding:0.75rem;
|
||||
color: rgba(24, 255, 255, 0.96);
|
||||
|
||||
}
|
||||
|
||||
a.mdc-list-item
|
||||
{
|
||||
|
||||
background-color: rgba(24,255,255,0.04);
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {MatListItem, MatNavList} from '@angular/material/list';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'app-side-bar-admin',
|
||||
imports: [
|
||||
MatIcon,
|
||||
MatListItem,
|
||||
MatNavList,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './side-bar-admin.component.html',
|
||||
styleUrl: './side-bar-admin.component.scss',
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class SideBarAdminComponent {
|
||||
isCollapsed = false;
|
||||
}
|
||||
@@ -53,15 +53,15 @@ export class UsersComponent {
|
||||
displayedColumns: string[] = UserColumns.map((col) => col.key)
|
||||
columnsSchema: any = UserColumns
|
||||
|
||||
constructor(private userService: UserService,
|
||||
private roleService: RolesService,
|
||||
constructor(
|
||||
|
||||
private adminService:AdminModuleService) {
|
||||
this.getAllRoles();
|
||||
this.getUsers();
|
||||
}
|
||||
|
||||
getAllRoles():any{
|
||||
this.roleService.getAllRoles().subscribe(
|
||||
this.adminService.getAllRoles().subscribe(
|
||||
(data : any) => {
|
||||
this.allRoles = data;
|
||||
console.log(data);
|
||||
@@ -73,7 +73,7 @@ export class UsersComponent {
|
||||
}
|
||||
|
||||
getUsers(){
|
||||
this.userService.getAdminBoard().subscribe(
|
||||
this.adminService.getUsers().subscribe(
|
||||
(data : any) => {
|
||||
this.users = data;
|
||||
console.log(data);
|
||||
@@ -84,7 +84,7 @@ export class UsersComponent {
|
||||
);
|
||||
}
|
||||
|
||||
saveUserRoles(user:User){
|
||||
saveUserRoles(user:User){
|
||||
this.adminService.updateUserRoles(user.id, user).subscribe(
|
||||
(data:any) => {
|
||||
console.log(data);
|
||||
@@ -114,6 +114,8 @@ export class UsersComponent {
|
||||
isDisabled(element:User, role: Role) {
|
||||
return role.name?.includes(<string>element.username?.toUpperCase());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export const UserColumns = [
|
||||
|
||||
Reference in New Issue
Block a user