remake init data
json dump
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
<!-- html -->
|
||||
<nav aria-label="Breadcrumb" class="breadcrumb">
|
||||
@let breadcrumbs = breadcrumbs$ | async;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-bottom: inherit;
|
||||
padding-left: 25px;
|
||||
}
|
||||
.breadcrumb .sep {
|
||||
font-size: 16px;
|
||||
@@ -12,4 +13,5 @@
|
||||
min-width: auto;
|
||||
padding: 0 4px;
|
||||
text-transform: none;
|
||||
color:cyan;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA} from '@angular/core';
|
||||
import {ActivatedRoute, NavigationEnd, Router, RouterLink} from '@angular/router';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {AsyncPipe, NgForOf, NgIf} from '@angular/common';
|
||||
import {AsyncPipe} from '@angular/common';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {Observable, startWith} from 'rxjs';
|
||||
import {filter, map} from 'rxjs/operators';
|
||||
@@ -15,9 +15,9 @@ type Breadcrumb = { label: string; url: string };
|
||||
RouterLink,
|
||||
MatIcon,
|
||||
MatButton,
|
||||
NgIf,
|
||||
|
||||
AsyncPipe,
|
||||
NgForOf,
|
||||
|
||||
MatDivider
|
||||
],
|
||||
templateUrl: './breadcrumbs.component.html',
|
||||
@@ -47,7 +47,7 @@ export class BreadcrumbsComponent {
|
||||
const routeURL = child.snapshot.url.map(s => s.path).join('/');
|
||||
if (routeURL) url += `/${routeURL}`;
|
||||
|
||||
const label = child.snapshot.data['breadcrumb'] as string | undefined;
|
||||
const label = child.snapshot.data['title'] as string | undefined;
|
||||
if (label) crumbs.push({ label, url });
|
||||
|
||||
return this.build(child, url, crumbs);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
<app-vertical-nav
|
||||
[routes]="routes"
|
||||
[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>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,18 +1,18 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SideBarAdminComponent } from './side-bar-admin.component';
|
||||
import { InnerLayoutComponent } from './inner-layout.component';
|
||||
|
||||
describe('SideBarAdminComponent', () => {
|
||||
let component: SideBarAdminComponent;
|
||||
let fixture: ComponentFixture<SideBarAdminComponent>;
|
||||
describe('InnerLayoutComponent', () => {
|
||||
let component: InnerLayoutComponent;
|
||||
let fixture: ComponentFixture<InnerLayoutComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SideBarAdminComponent]
|
||||
imports: [InnerLayoutComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SideBarAdminComponent);
|
||||
fixture = TestBed.createComponent(InnerLayoutComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {BreadcrumbsComponent} from "../breadcrumbs.component/breadcrumbs.component";
|
||||
import {Route, RouterOutlet} from "@angular/router";
|
||||
import {VerticalNavComponent} from "../vertical-nav.component/vertical-nav.component";
|
||||
import {ADMIN_ROUTES} from '../../modules/admin-module/admin.routing';
|
||||
|
||||
@Component({
|
||||
selector: 'app-inner-layout',
|
||||
imports: [
|
||||
BreadcrumbsComponent,
|
||||
RouterOutlet,
|
||||
VerticalNavComponent
|
||||
],
|
||||
templateUrl: './inner-layout.component.html',
|
||||
styleUrl: './inner-layout.component.scss'
|
||||
})
|
||||
export class InnerLayoutComponent implements OnInit{
|
||||
|
||||
|
||||
//adminRoutes = (ADMIN_ROUTES[0]?.children ?? []).filter(r => r.path && r.data?.['nav']);
|
||||
//adminRoutes = (ADMIN_ROUTES[0]?.children ?? []).filter(r => r.path && r.data?.['nav']);
|
||||
routes:Route[] = [];
|
||||
|
||||
@Input() moduleRoutes:Route[] = [];
|
||||
|
||||
// If your admin module is mounted at '/admin' in the app routes, keep this as '/admin'.
|
||||
// If it's at root, set this to ''.
|
||||
@Input() basePath = '';
|
||||
|
||||
isCollapsed = false;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.routes = (this.moduleRoutes[0]?.children ?? []).filter(r => r.path && r.data?.['nav']);
|
||||
}
|
||||
|
||||
navCollapsedChanged($event: boolean) {
|
||||
this.isCollapsed = $event;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<div class="nav-container" [class.collapsed]="!opened()">
|
||||
<div class="nav-header">
|
||||
<button mat-icon-button (click)="toggle()" aria-label="Toggle sidebar">
|
||||
<mat-icon>{{ opened() ? 'chevron_left' : 'chevron_right' }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-nav-list class="nav-list">
|
||||
@for (item of (items || navItems()); track item) {
|
||||
<a
|
||||
|
||||
mat-list-item
|
||||
[routerLink]="item.path"
|
||||
[routerLinkActive]="'active'"
|
||||
[routerLinkActiveOptions]="{ exact: item.exact !== false }"
|
||||
[matTooltip]="opened() ? '' : item.title"
|
||||
matTooltipPosition="right"
|
||||
>
|
||||
<mat-icon matListItemIcon>{{ item.icon }}</mat-icon>
|
||||
<span matListItemTitle class="label">{{ item.title }}</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
</mat-nav-list>
|
||||
</div>
|
||||
@@ -0,0 +1,68 @@
|
||||
.nav-container {
|
||||
width: 240px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--mat-sys-outline-variant, rgba(0,0,0,0.12));
|
||||
background-color: rgba(153,153,153,0.16);
|
||||
backdrop-filter: blur(8px);
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
&.collapsed {
|
||||
width: 64px;
|
||||
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
// border-bottom: 1px solid var(--mat-sys-outline-variant, rgba(0,0,0,0.12));
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
|
||||
a.mat-mdc-list-item {
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
margin: 4px 4px;
|
||||
color: cyan;
|
||||
&.active {
|
||||
background: color-mix(in oklab, var(--mat-sys-primary, #3f51b5) 16%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-list{
|
||||
//background-color: #f5f7fb; /* your color */
|
||||
background-color: rgba(153,153,153,0.16);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.nav-list .mdc-list-item {
|
||||
background-color: rgba(153,153,153,0.16);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.nav-list .mdc-list-item:hover,
|
||||
.nav-list .mdc-list-item:focus {
|
||||
background-color: rgba(153,153,153,0.16);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.nav-list .mdc-list-item--selected {
|
||||
background-color: rgba(153, 153, 153, 0.50);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* Primary and secondary text */
|
||||
.nav-list .mdc-list-item__primary-text,
|
||||
.nav-list .mdc-list-item__secondary-text {
|
||||
color: cyan;
|
||||
}
|
||||
|
||||
/* Optional: icons inside list items */
|
||||
.nav-list .mat-icon {
|
||||
color: cyan;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VerticalNavComponent } from './vertical-nav.component';
|
||||
|
||||
describe('VerticalNavComponent', () => {
|
||||
let component: VerticalNavComponent;
|
||||
let fixture: ComponentFixture<VerticalNavComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VerticalNavComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VerticalNavComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
EventEmitter,
|
||||
inject,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import {Route, Router, RouterLink, RouterLinkActive} from '@angular/router';
|
||||
import {MatListItem, MatListItemIcon, MatListItemTitle, MatNavList} from '@angular/material/list';
|
||||
import {NgForOf} from '@angular/common';
|
||||
import {MatIconButton} from '@angular/material/button';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {adminRouting} from '../../modules/admin-module/admin.routing';
|
||||
|
||||
export interface NavItem {
|
||||
path: string; // absolute path like /dashboard
|
||||
title: string; // label to show
|
||||
icon?: string; // material icon name
|
||||
exact?: boolean; // whether to match exactly
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-vertical-nav',
|
||||
imports: [
|
||||
MatTooltip,
|
||||
MatIcon,
|
||||
MatListItemIcon,
|
||||
MatListItemTitle,
|
||||
RouterLinkActive,
|
||||
RouterLink,
|
||||
MatListItem,
|
||||
MatNavList,
|
||||
NgForOf,
|
||||
MatIconButton,
|
||||
|
||||
],
|
||||
templateUrl: './vertical-nav.component.html',
|
||||
styleUrl: './vertical-nav.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class VerticalNavComponent implements OnInit{
|
||||
private router = inject(Router);
|
||||
|
||||
// Optional: if you want to pass items directly instead of deriving from routes
|
||||
@Input() items: NavItem[] | null = null;
|
||||
|
||||
// Optional: pass a specific Route[] (e.g., ADMIN_ROUTES). If not provided, uses router.config.
|
||||
@Input() routes: Route[] = [];
|
||||
|
||||
// Optional: prepend a base path (e.g., '/admin') when using feature routes.
|
||||
@Input() basePath = '';
|
||||
|
||||
// Sidebar state (expanded/collapsed)
|
||||
opened = signal(true);
|
||||
|
||||
navItems = signal<NavItem[]>([]);
|
||||
readonly hasItems = computed(() => (this.items?.length ?? this.navItems().length) > 0);
|
||||
|
||||
@Output() collapsedChange = new EventEmitter<boolean>();
|
||||
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.navItems.set(this.deriveFromRoutes(this.routes, this.basePath));
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
this.opened.update(v => !v);
|
||||
this.collapsedChange.emit(!this.opened());
|
||||
}
|
||||
|
||||
private deriveFromRoutes(routes: Route[], base: string): NavItem[] {
|
||||
// Flatten one level; you can make this recursive if needed
|
||||
const items: NavItem[] = [];
|
||||
for (const r of routes) {
|
||||
if (!r || r.path === '**') continue;
|
||||
|
||||
const data = (r as any).data || {};
|
||||
const seg = r.path || '';
|
||||
const nextBase = [base, seg].filter(Boolean).join('/');
|
||||
|
||||
/* // Recurse into children if present (common in feature modules)
|
||||
if (r.children && r.children.length) {
|
||||
items.push(...this.deriveFromRoutes(r.children, nextBase));
|
||||
}
|
||||
*/
|
||||
if (data.nav === false) continue;
|
||||
|
||||
const title = data.title as string | undefined;
|
||||
if (!title) continue;
|
||||
|
||||
const icon = data.icon as string | undefined;
|
||||
const full = '/' + nextBase.replace(/\/+/g, '/');
|
||||
items.push({ path: full, title, icon, exact: true });
|
||||
}
|
||||
|
||||
return items;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Table } from './table';
|
||||
|
||||
describe('Table', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new Table()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
export class Table {
|
||||
schema?: string;
|
||||
table?: string;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { JsonDumpComponent } from './json-dump.component';
|
||||
|
||||
describe('JsonDumpComponent', () => {
|
||||
let component: JsonDumpComponent;
|
||||
let fixture: ComponentFixture<JsonDumpComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [JsonDumpComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(JsonDumpComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+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 = [
|
||||
|
||||
@@ -1,60 +1,63 @@
|
||||
<mat-toolbar class="fixed-top">
|
||||
<button mat-button routerLink="">
|
||||
{{title}}
|
||||
</button>
|
||||
<button mat-raised-button routerLink="./ai/generate-image">
|
||||
Generate Image
|
||||
</button>
|
||||
<button mat-raised-button routerLink="./tutorials/tutorials-all">
|
||||
Tutorials
|
||||
</button>
|
||||
|
||||
<span class="menu-spacer"></span>
|
||||
|
||||
@if (!isLoggedIn) {
|
||||
<button matMiniFab matTooltip="Sign Up" aria-label="Sign Up" (click)="openSignupDialog('100ms', '5ms')">
|
||||
<mat-icon>person_add</mat-icon>
|
||||
<!-- <mat-toolbar-row>-->
|
||||
<button mat-button routerLink="">
|
||||
{{title}}
|
||||
</button>
|
||||
<button mat-raised-button routerLink="./ai/generate-image">
|
||||
Generate Image
|
||||
</button>
|
||||
<button mat-raised-button routerLink="./tutorials/tutorials-all">
|
||||
Tutorials
|
||||
</button>
|
||||
<button matMiniFab matTooltip="Login" aria-label="Login" (click)="openLoginDialog('100ms', '5ms')" >
|
||||
<mat-icon>login</mat-icon>
|
||||
</button>
|
||||
}@else {
|
||||
<button matButton="elevated" aria-label="Account" [matMenuTriggerFor]="menu">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
<mat-label>{{username}}</mat-label>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
@if (showAdminBoard) {
|
||||
<button mat-menu-item routerLink="admin/admin-welcome">
|
||||
<mat-icon>admin_panel_settings</mat-icon>
|
||||
Admin Panel
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (showUserBoard) {
|
||||
<button mat-menu-item routerLink="user/user-welcome">
|
||||
<mat-icon>space_dashboard</mat-icon>
|
||||
User tools
|
||||
</button>
|
||||
}
|
||||
@if (showModeratorBoard) {
|
||||
<button mat-menu-item routerLink="moderator/moderator-welcome">
|
||||
<mat-icon>space_dashboard</mat-icon>
|
||||
Moderator board
|
||||
</button>
|
||||
}
|
||||
<span class="menu-spacer"></span>
|
||||
|
||||
<button mat-menu-item routerLink="profile">
|
||||
<mat-icon>person</mat-icon>
|
||||
Profile
|
||||
@if (!isLoggedIn) {
|
||||
<button matMiniFab matTooltip="Sign Up" aria-label="Sign Up" (click)="openSignupDialog('100ms', '5ms')">
|
||||
<mat-icon>person_add</mat-icon>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="logout()">
|
||||
<mat-icon>logout</mat-icon>
|
||||
Logout
|
||||
<button matMiniFab matTooltip="Login" aria-label="Login" (click)="openLoginDialog('100ms', '5ms')" >
|
||||
<mat-icon>login</mat-icon>
|
||||
</button>
|
||||
</mat-menu>
|
||||
}
|
||||
}@else {
|
||||
<button matButton="elevated" aria-label="Account" [matMenuTriggerFor]="menu">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
<mat-label>{{username}}</mat-label>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
@if (showAdminBoard) {
|
||||
<button mat-menu-item routerLink="admin/admin-welcome">
|
||||
<mat-icon>admin_panel_settings</mat-icon>
|
||||
Admin Panel
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (showUserBoard) {
|
||||
<button mat-menu-item routerLink="user/user-welcome">
|
||||
<mat-icon>space_dashboard</mat-icon>
|
||||
User tools
|
||||
</button>
|
||||
}
|
||||
@if (showModeratorBoard) {
|
||||
<button mat-menu-item routerLink="moderator/moderator-welcome">
|
||||
<mat-icon>space_dashboard</mat-icon>
|
||||
Moderator board
|
||||
</button>
|
||||
}
|
||||
|
||||
<button mat-menu-item routerLink="profile">
|
||||
<mat-icon>person</mat-icon>
|
||||
Profile
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="logout()">
|
||||
<mat-icon>logout</mat-icon>
|
||||
Logout
|
||||
</button>
|
||||
</mat-menu>
|
||||
}
|
||||
<!-- </mat-toolbar-row>-->
|
||||
|
||||
</mat-toolbar>
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ mat-toolbar{
|
||||
margin-top: 65px;
|
||||
height: 100%;
|
||||
min-height: fit-content;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.component{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {MainComponent} from './main.component/main.component';
|
||||
|
||||
const MAIN_ROUTES: Routes =[
|
||||
export const MAIN_ROUTES: Routes =[
|
||||
{
|
||||
path: '',
|
||||
component: MainComponent,
|
||||
loadComponent: () => import('./main.component/main.component').then((c) => c.MainComponent),
|
||||
children: [
|
||||
{
|
||||
path: 'tutorials',
|
||||
@@ -21,6 +21,7 @@ const MAIN_ROUTES: Routes =[
|
||||
path: 'admin',
|
||||
loadChildren: () =>
|
||||
import('../admin-module/admin.module').then((m) => m.AdminModule),
|
||||
data:{nav:true}
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
|
||||
+2
-5
@@ -1,5 +1,2 @@
|
||||
<app-side-bar-moderator class="pc-sidebar" ></app-side-bar-moderator>
|
||||
<div class="pc-container">
|
||||
<app-breadcrumbs></app-breadcrumbs>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
<app-inner-layout [moduleRoutes]="moderatorRouts" [basePath]="basePath"></app-inner-layout>
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
//---------------
|
||||
.pc-sidebar{
|
||||
top: 65px;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(153, 153, 153, 0.16);
|
||||
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.pc-container{
|
||||
top: 0px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
+5
-4
@@ -2,20 +2,21 @@ import { Component } from '@angular/core';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
import {SideBarModeratorComponent} from '../side-bar-moderator.component/side-bar-moderator.component';
|
||||
import {BreadcrumbsComponent} from '../../../components/breadcrumbs.component/breadcrumbs.component';
|
||||
import {InnerLayoutComponent} from '../../../components/inner-layout.component/inner-layout.component';
|
||||
import {MODERATOR_ROUTS} from '../moderator.routing';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-moderator.component',
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
SideBarModeratorComponent,
|
||||
BreadcrumbsComponent,
|
||||
InnerLayoutComponent,
|
||||
|
||||
],
|
||||
templateUrl: './moderator.component.html',
|
||||
styleUrl: './moderator.component.scss'
|
||||
})
|
||||
export class ModeratorComponent {
|
||||
|
||||
moderatorRouts = MODERATOR_ROUTS;
|
||||
basePath = '/main/moderator';
|
||||
}
|
||||
|
||||
@@ -2,30 +2,30 @@ import {RouterModule, Routes} from '@angular/router';
|
||||
|
||||
import {ModeratorComponent} from './moderator.component/moderator.component';
|
||||
|
||||
const MODERATOR_ROUTES: Routes = [
|
||||
export const MODERATOR_ROUTS: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ModeratorComponent,
|
||||
data:{breadcrumb: 'Moderator'},
|
||||
data:{title: 'Moderator'},
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'moderator-welcome' }, // default redirect
|
||||
{
|
||||
path: 'moderator-welcome',
|
||||
loadComponent: () => import('../moderator-module/moderator-welcome.component/moderator-welcome.component').then((c) => c.ModeratorWelcomeComponent),
|
||||
data:{breadcrumb: 'Welcome'}
|
||||
data:{title: 'Welcome', icon:'dashboard', nav:true}
|
||||
},
|
||||
{
|
||||
path: 'tutorials-list',
|
||||
loadComponent: () => import('../moderator-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent),
|
||||
data:{breadcrumb: 'Tutorials List'}
|
||||
data:{title: 'Tutorials List', icon:'list', nav:true}
|
||||
},
|
||||
{
|
||||
path: 'tutorial-preview',
|
||||
loadComponent: () => import('../moderator-module/tutorial-preview.component/tutorial-preview.component').then((c) => c.TutorialPreviewComponent),
|
||||
data:{breadcrumb: 'Tutorial Preview'}
|
||||
data:{title: 'Tutorial Preview'}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const moderatorRouting = RouterModule.forChild(MODERATOR_ROUTES);
|
||||
export const moderatorRouting = RouterModule.forChild(MODERATOR_ROUTS);
|
||||
|
||||
@@ -1,10 +1,2 @@
|
||||
<app-side-bar-user
|
||||
class="pc-sidebar"
|
||||
>
|
||||
</app-side-bar-user>
|
||||
|
||||
<div class="pc-container">
|
||||
<app-breadcrumbs></app-breadcrumbs>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
<app-inner-layout [moduleRoutes]="userRouts" [basePath]="basePath"></app-inner-layout>
|
||||
|
||||
@@ -4,43 +4,24 @@ import {MatSidenav} from '@angular/material/sidenav';
|
||||
import {BreakpointObserver} from '@angular/cdk/layout';
|
||||
import {SideBarUserComponent} from '../side-bar-user.component/side-bar-user.component';
|
||||
import {BreadcrumbsComponent} from '../../../components/breadcrumbs.component/breadcrumbs.component';
|
||||
import {InnerLayoutComponent} from '../../../components/inner-layout.component/inner-layout.component';
|
||||
import {MODERATOR_ROUTS} from '../../moderator-module/moderator.routing';
|
||||
import {USER_ROUTS} from '../user.routing';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user.component',
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
SideBarUserComponent,
|
||||
BreadcrumbsComponent
|
||||
BreadcrumbsComponent,
|
||||
InnerLayoutComponent
|
||||
],
|
||||
templateUrl: './user.component.html',
|
||||
styleUrl: './user.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class UserComponent implements OnInit {
|
||||
@ViewChild(MatSidenav)
|
||||
sidenav!: MatSidenav;
|
||||
isMobile= true;
|
||||
isCollapsed = true;
|
||||
export class UserComponent {
|
||||
|
||||
|
||||
constructor(private observer: BreakpointObserver) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.observer.observe(['(max-width: 800px)']).subscribe((screenSize) => {
|
||||
this.isMobile = screenSize.matches;
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
toggleMenu() {
|
||||
if(this.isMobile){
|
||||
this.sidenav.toggle();
|
||||
this.isCollapsed = false;
|
||||
} else {
|
||||
this.sidenav.open();
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
}
|
||||
}
|
||||
userRouts = USER_ROUTS;
|
||||
basePath = '/main/user';
|
||||
}
|
||||
|
||||
@@ -2,47 +2,47 @@ import {RouterModule, Routes} from '@angular/router';
|
||||
|
||||
import {UserComponent} from './user.component/user.component';
|
||||
|
||||
const USER_ROUTES: Routes = [
|
||||
export const USER_ROUTS: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: UserComponent,
|
||||
|
||||
data:{breadcrumb: 'User'},
|
||||
data:{title: 'User'},
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'user-welcome' }, // default redirect
|
||||
|
||||
{
|
||||
path: 'user-welcome',
|
||||
loadComponent: () => import('../user-module/user-welcome.component/user-welcome.component').then((c) => c.UserWelcomeComponent),
|
||||
data:{breadcrumb: 'Welcome'}
|
||||
data:{title: 'Welcome', icon:'dashboard', nav:true}
|
||||
},
|
||||
{
|
||||
path: 'tutorials-list',
|
||||
loadComponent: () => import('../user-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent),
|
||||
data:{breadcrumb: 'Tutorials List'}
|
||||
data:{title: 'Tutorials List', icon:'list', nav:true}
|
||||
},
|
||||
{
|
||||
path: 'tutorial-add',
|
||||
loadComponent: () => import('../user-module/tutorial-add.component/tutorial-add.component').then((c) => c.TutorialAddComponent),
|
||||
data:{breadcrumb: 'Add Tutorial'}
|
||||
data:{title: 'Add Tutorial'}
|
||||
},
|
||||
{
|
||||
path: 'tutorial-edit',
|
||||
loadComponent: () => import('../user-module/tutorial-edit.component/tutorial-edit.component').then((c) => c.TutorialEditComponent),
|
||||
data:{breadcrumb: 'Edit Tutorial'}
|
||||
data:{title: 'Edit Tutorial'}
|
||||
},
|
||||
{
|
||||
path: 'ai-models',
|
||||
loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent),
|
||||
data:{breadcrumb: 'AI'}
|
||||
data:{title: 'AI'}
|
||||
},
|
||||
{
|
||||
path: 'images',
|
||||
loadComponent: () => import('../user-module/images.component/images.component').then((c) => c.ImagesComponent),
|
||||
data:{breadcrumb: 'Images'}
|
||||
data:{title: 'Images', icon: 'imagesmode', nav:true}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const userRouting = RouterModule.forChild(USER_ROUTES);
|
||||
export const userRouting = RouterModule.forChild(USER_ROUTS);
|
||||
|
||||
@@ -10,10 +10,10 @@ export class SpinnerService {
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visibility.next(true);
|
||||
this.visibility.next(false);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.visibility.next(false);
|
||||
this.visibility.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ export class UserService {
|
||||
return this.http.get(API_URL + '/mod', { responseType: 'text' });
|
||||
}
|
||||
|
||||
getAdminBoard(): Observable<User[]> {
|
||||
return this.http.get<User[]>(API_URL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family= Jersey 20 Charted:wght@300;400;500&display=swap">
|
||||
<!--<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family= Jersey 20 Charted:wght@300;400;500&display=swap">-->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<!--<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family= Jersey 20 Charted:wght@300;400;500&display=swap">-->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body class="mat-typography">
|
||||
|
||||
@@ -49,11 +49,11 @@ body {
|
||||
}
|
||||
.pc-container{
|
||||
top: 0;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
|
||||
position: relative;
|
||||
margin-left: 260px;
|
||||
margin-left: 240px;
|
||||
min-height: calc(100vh - 113px);
|
||||
background: #fafafbb3;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user