Upgrade to angular 18, Replace gui-grid with PrimeNG table since gui-grid not developed anymore,Fix typo , Fix minor ui/ux bugs

This commit is contained in:
sepehr 2026-03-29 16:43:30 +03:00
parent e95304af3e
commit 9298dd80b6
74 changed files with 4845 additions and 3913 deletions

View file

@ -61,7 +61,7 @@
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "6kb"
"maximumError": "10kb"
}
],
"outputHashing": "all"

View file

@ -19,17 +19,17 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^17.3.5",
"@angular/animations": "^18.2.14",
"@angular/cdk": "^16.2.9",
"@angular/common": "^17.3.5",
"@angular/compiler": "^17.3.5",
"@angular/core": "^17.3.5",
"@angular/forms": "^17.3.5",
"@angular/language-service": "^17.3.5",
"@angular/common": "^18.2.14",
"@angular/compiler": "^18.2.14",
"@angular/core": "^18.2.14",
"@angular/forms": "^18.2.14",
"@angular/language-service": "^18.2.14",
"@angular/material": "^17.3.5",
"@angular/platform-browser": "^17.3.5",
"@angular/platform-browser-dynamic": "^17.3.5",
"@angular/router": "^17.3.5",
"@angular/platform-browser": "^18.2.14",
"@angular/platform-browser-dynamic": "^18.2.14",
"@angular/router": "^18.2.14",
"@coreui/angular": "~4.5.27",
"@coreui/angular-chartjs": "~4.5.27",
"@coreui/chartjs": "^3.1.2",
@ -42,6 +42,7 @@
"@generic-ui/fabric": "^0.19.0",
"@generic-ui/hermes": "^0.19.0",
"@generic-ui/ngx-grid": "^0.19.0",
"@primeuix/themes": "^2.0.3",
"chart.js": "^3.9.1",
"date-fns": "^3.6.0",
"date-fns-jalali": "^3.6.0-0",
@ -61,17 +62,21 @@
"ngx-material-date-fns-adapter": "^18.0.0",
"ngx-scrollbar": "^13.0.3",
"ngx-super-select": "^3.17.0",
"primeicons": "^7.0.0",
"primeng": "^18.0.2",
"rxjs": "~7.8.1",
"tslib": "^2.3.0",
"vis-data": "^7.1.9",
"vis-network": "^9.1.9",
"zone.js": "~0.14.4"
"vis-util": "^6.0.0",
"zone.js": "~0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.5",
"@angular/cli": "^17.3.5",
"@angular/compiler-cli": "^17.3.5",
"@angular/localize": "^17.3.5",
"@angular-devkit/build-angular": "^18.2.21",
"@angular/cli": "^18.2.21",
"@angular/compiler-cli": "^18.2.14",
"@angular/localize": "^18.2.14",
"@types/hammerjs": "^2.0.46",
"@types/jasmine": "^5.1.1",
"@types/lodash-es": "^4.17.10",
"@types/node": "^18.19.34",

View file

@ -2,10 +2,13 @@ import { NgModule ,APP_INITIALIZER} from '@angular/core';
import { HashLocationStrategy, LocationStrategy, PathLocationStrategy } from '@angular/common';
import { BrowserModule, Title } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeuix/themes/aura';
import { ReactiveFormsModule,FormsModule } from '@angular/forms';
import { NgScrollbarModule } from 'ngx-scrollbar';
import { HttpClientModule } from '@angular/common/http';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
// Import routing module
import { AppRoutingModule } from './app-routing.module';
@ -54,60 +57,63 @@ const APP_CONTAINERS = [
export function loginStatusProviderFactory(provider: loginChecker) {
return () => provider.load();
}
@NgModule({
declarations: [AppComponent, ...APP_CONTAINERS],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
AvatarModule,
BreadcrumbModule,
FooterModule,
DropdownModule,
GridModule,
HeaderModule,
SidebarModule,
IconModule,
NavModule,
HttpClientModule,
ButtonModule,
FormModule,
UtilitiesModule,
ButtonGroupModule,
ReactiveFormsModule,
FormsModule,
SidebarModule,
SharedModule,
TabsModule,
ListGroupModule,
ProgressModule,
BadgeModule,
ListGroupModule,
CardModule,
NgScrollbarModule,
ModalModule,
FontAwesomeModule,
TableModule
],
providers: [
{
provide: LocationStrategy,
useClass: HashLocationStrategy
},
MikroWizardProvider,
dataProvider,
loginChecker,
IconSetService,
provideDateFnsAdapter(),
{
provide: APP_INITIALIZER,
useFactory: loginStatusProviderFactory,
deps: [loginChecker],
multi: true,
},
Title
],
bootstrap: [AppComponent]
})
@NgModule({ declarations: [AppComponent, ...APP_CONTAINERS],
bootstrap: [AppComponent], imports: [BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
AvatarModule,
BreadcrumbModule,
FooterModule,
DropdownModule,
GridModule,
HeaderModule,
SidebarModule,
IconModule,
NavModule,
ButtonModule,
FormModule,
UtilitiesModule,
ButtonGroupModule,
ReactiveFormsModule,
FormsModule,
SidebarModule,
SharedModule,
TabsModule,
ListGroupModule,
ProgressModule,
BadgeModule,
ListGroupModule,
CardModule,
NgScrollbarModule,
ModalModule,
FontAwesomeModule,
TableModule], providers: [
{
provide: LocationStrategy,
useClass: HashLocationStrategy
},
MikroWizardProvider,
dataProvider,
loginChecker,
IconSetService,
provideDateFnsAdapter(),
provideAnimationsAsync(),
providePrimeNG({
theme: {
preset: Aura,
options: {
darkModeSelector: 'none'
}
}
}),
{
provide: APP_INITIALIZER,
useFactory: loginStatusProviderFactory,
deps: [loginChecker],
multi: true,
},
Title,
provideHttpClient(withInterceptorsFromDi())
] })
export class AppModule {
}

View file

@ -18,9 +18,10 @@ export const navItems: INavData[] = [
name: 'Device Managment'
},
{
name: 'VPN Server',
name: 'WireGuard Server',
url: '/vpn',
icon: 'fa-solid fa-network-wired'
icon: 'fa-solid fa-network-wired',
attributes: { 'pro': true }
},
{
name: 'Devices',

View file

@ -1,6 +1,6 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient,HttpResponse } from '@angular/common/http';
import { HttpClient, HttpResponse } from '@angular/common/http';
class Cookies { // cookies doesn't work with Android default browser / Ionic
private session_id: string = "";

View file

@ -1,10 +1,10 @@
<c-row>
<c-col xs>
<c-card class="mb-4" [ngStyle]="component_devid && {'border-top': 'none'}">
<c-card class="mb-4" [ngStyle]="component_devid && {'border-top': 'none'}">
<c-card-header>
<c-row>
<c-col xs [lg]="11" style="display: flex;flex-direction: column;align-items: flex-start;">
<h5>Accunting Logs
<h5>Accounting Logs
<a style="cursor: pointer;" (click)="reinitgrid('none','none')"><i
*ngIf="devid!=0 && component_devid && !reloading" class="fa-solid fa-arrows-rotate"
style="color: #74C0FC;"></i>
@ -14,8 +14,10 @@
</h5>
<c-badge color="warning" *ngIf="devid!=0 && !component_devid">Filtered Result For Device ID
{{devid}}</c-badge>
<c-alert color="warning" style="padding-top: 5px!important;font-size: 0.8rem;display: inline-block;" *ngIf="!filters['start_time'] && !filters['end_time']">
<i class="fa-solid fa-triangle-exclamation mx-1"></i>Showing <strong>last 24 hours logs</strong> by default. Use filters to modify the date and time.
<c-alert color="warning" style="padding-top: 5px!important;font-size: 0.8rem;display: inline-block;"
*ngIf="!filters['start_time'] && !filters['end_time']">
<i class="fa-solid fa-triangle-exclamation mx-1"></i>Showing <strong>last 24 hours logs</strong> by
default. Use filters to modify the date and time.
</c-alert>
</c-col>
<c-col xs [lg]="1">
@ -73,49 +75,143 @@
</c-col>
</div>
</c-row>
<gui-grid [rowDetail]="rowDetail" [source]="source" [columnMenu]="columnMenu" [paging]="paging"
[sorting]="sorting" [infoPanel]="infoPanel" [autoResizeWidth]=true>
<gui-grid-column header="#No" type="NUMBER" field="index" width=25 align="CENTER">
<ng-template let-value="item.index" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Device Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Device IP" field="devip">
<ng-template let-value="item.devip" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Username" field="username">
<ng-template let-value="item.username" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Action" field="action">
<ng-template let-value="item.action" let-item="item" let-index="index">
<div>{{value}}</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Section" field="section">
<ng-template let-value="item.section" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Date" field="created">
<ng-template let-value="item.created" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Message" field="message" [enabled]="false">
<ng-template let-value="item.message" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search commands/users..."
(input)="applyFilterGlobal($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['username', 'name', 'address', 'config', 'section', 'action']" selectionMode="single"
(onRowSelect)="showLogDetails($event.data)">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="index" style="width: 5rem" pResizableColumn>
<div class="justify-between">
<span>#No</span>
<p-sortIcon field="index"></p-sortIcon>
</div>
</th>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Device Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="devip" pResizableColumn>
<div class="justify-between">
<span>Device IP</span>
<span>
<p-sortIcon field="devip"></p-sortIcon>
<p-columnFilter type="text" field="devip" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="username" pResizableColumn>
<div class="justify-between">
<span>User Name</span>
<span>
<p-sortIcon field="username"></p-sortIcon>
<p-columnFilter type="text" field="username" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="action" pResizableColumn>
<div class="justify-between">
<span>Action</span>
<span>
<p-sortIcon field="action"></p-sortIcon>
<p-columnFilter type="text" field="action" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="section" pResizableColumn>
<div class="justify-between">
<span>Section</span>
<span>
<p-sortIcon field="section"></p-sortIcon>
<p-columnFilter type="text" field="section" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="created" pResizableColumn>
<div class="justify-between">
<span>Date</span>
<span>
<p-sortIcon field="created"></p-sortIcon>
<p-columnFilter type="text" field="created" display="menu" class="ms-auto" />
</span>
</div>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr [pSelectableRow]="item" style="cursor: pointer;">
<td class="text-center">{{item.index}}</td>
<td class="fw-bold">{{item.name}}</td>
<td>{{item.devip}}</td>
<td>{{item.username}}</td>
<td>{{item.action}}</td>
<td>{{item.section}}</td>
<td class="small">{{item.created}}</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="7" class="text-center p-4">No accounting logs found.</td>
</tr>
</ng-template>
</p-table>
<!-- Details Drawer -->
<p-drawer [(visible)]="detailsVisible" position="right" [style]="{width: '500px'}"
header="Accounting Log Details">
<div *ngIf="selectedLog" class="p-3">
<h5 class="border-bottom pb-2 mb-3"><i class="pi pi-desktop me-2 text-primary"></i>Device Info</h5>
<div class="mb-3">
<div class="h4 m-0 text-primary">{{selectedLog.name}}</div>
<div class="text-muted">{{selectedLog.devip}}</div>
</div>
<h5 class="border-bottom pb-2 mb-3 mt-4"><i class="pi pi-user me-2 text-primary"></i>Session Details</h5>
<div class="row mb-2">
<div class="col-5 fw-bold">User Name:</div>
<div class="col-7">{{selectedLog.username}}</div>
</div>
<div class="row mb-2">
<div class="col-5 fw-bold">User IP:</div>
<div class="col-7">{{selectedLog.address}}</div>
</div>
<div class="row mb-2">
<div class="col-5 fw-bold">Section:</div>
<div class="col-7">{{selectedLog.section}}</div>
</div>
<div class="row mb-2">
<div class="col-5 fw-bold">Action:</div>
<div class="col-7">{{selectedLog.action}}</div>
</div>
<div class="row mb-2">
<div class="col-5 fw-bold">Exec time:</div>
<div class="col-7 small">{{selectedLog.created}}</div>
</div>
<h5 class="border-bottom pb-2 mb-3 mt-4"><i class="pi pi-code me-2 text-primary"></i>Executed Config</h5>
<div class="bg-dark text-success p-3 rounded shadow-inner"
style="max-height: 400px; overflow-y: auto; font-family: 'Consolas', 'Monaco', 'Lucida Console', monospace; font-size: 0.9rem; border: 1px solid #333;">
<pre class="m-0" style="white-space: pre-wrap;">{{selectedLog.config}}</pre>
</div>
</div>
</p-drawer>
</c-card-body>
</c-card>
</c-col>

View file

@ -1,19 +1,8 @@
import { Component, OnInit, ViewEncapsulation,Input } from "@angular/core";
import { Component, OnInit, ViewChild, ViewEncapsulation, Input } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router, ActivatedRoute } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiRowDetail,
GuiSelectedRow,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { formatInTimeZone } from "date-fns-tz";
@ -26,10 +15,14 @@ import { formatInTimeZone } from "date-fns-tz";
})
export class AccComponent implements OnInit {
@Input() component_devid: any=false;
public uid: number;
public uname: string;
public tz: string;
public filterText: string;
public uid!: number;
public uname!: string;
public tz!: string;
public filterText!: string;
public detailsVisible: boolean = false;
public selectedLog: any = null;
@ViewChild('dt') table!: Table;
public reloading: boolean = false;
public filters: any = {
devid: false,
@ -75,78 +68,20 @@ export class AccComponent implements OnInit {
}
}
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public Selectedrows: any;
public selected_rows: any[] = [];
public Selectedrows: any[] = [];
public devid: number = 0;
public sorting = {
enabled: true,
multiSorting: true,
};
rowDetail: GuiRowDetail = {
enabled: true,
template: (item) => {
return `
<div class='log-detail' style="width: 355px;">
<h1>${item.name}</h1>
<small>${item.devip}</small>
<table>
<tr>
<td>User Address</td>
<td>${item.address}</td>
</tr>
<tr>
<td>User Name</td>
<td>${item.username}</td>
</tr>
<tr>
<td>Connection Type</td>
<td>${item.ctype}</td>
</tr>
<tr>
<td>Section</td>
<td>${item.section}</td>
</tr>
<tr>
<td>Exec time</td>
<td>${item.created}</td>
</tr>
</table>
<div class="code-title">Executed Config</div>
<code>
${item.config}
</code>
</div>`;
},
};
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
showLogDetails(log: any) {
this.selectedLog = log;
this.detailsVisible = true;
}
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
reinitgrid(field: string, $event: any) {
if (field == "start") this.filters["start_time"] = $event.target.value;
@ -170,9 +105,10 @@ export class AccComponent implements OnInit {
this.initGridTable();
}
OnDestroy(): void {}
onSelectedRows(rows: Array<GuiSelectedRow>): void {
this.rows = rows;
this.Selectedrows = rows.map((m: GuiSelectedRow) => m.source.id);
onSelectionChange(value: any[]) {
this.selected_rows = value;
this.Selectedrows = value.map(item => item.id);
this.rows = value;
}
removefilter(filter: any) {

View file

@ -13,7 +13,9 @@ import {
import { AccRoutingModule } from "./acc-routing.module";
import { AccComponent } from "./acc.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule } from 'primeng/table';
import { DrawerModule } from 'primeng/drawer';
import { InputTextModule } from 'primeng/inputtext';
import { MatDatepickerModule } from "@angular/material/datepicker";
import { MatInputModule } from "@angular/material/input";
@ -32,7 +34,9 @@ import { FormsModule } from "@angular/forms";
ButtonModule,
FormModule,
ButtonModule,
GuiGridModule,
TableModule,
DrawerModule,
InputTextModule,
CollapseModule,
MatFormFieldModule,
MatInputModule,

View file

@ -1,6 +1,6 @@
<c-row>
<c-col xs>
<c-card class="mb-4" [ngStyle]="component_devid && {'border-top': 'none'}">
<c-card class="mb-4" [ngStyle]="component_devid && {'border-top': 'none'}">
<c-card-header>
<c-row>
<c-col xs [lg]="11" style="display: flex;flex-direction: column;align-items: flex-start;">
@ -14,8 +14,10 @@
</h5>
<c-badge color="warning" *ngIf="devid!=0 && !component_devid">Filtered Result For Device ID
{{devid}}</c-badge>
<c-alert color="warning" style="padding-top: 5px!important;font-size: 0.8rem;display: inline-block;" *ngIf="!filters['start_time'] && !filters['end_time']">
<i class="fa-solid fa-triangle-exclamation mx-1"></i>Showing <strong>last 24 hours logs</strong> by default. Use filters to modify the date and time.
<c-alert color="warning" style="padding-top: 5px!important;font-size: 0.8rem;display: inline-block;"
*ngIf="!filters['start_time'] && !filters['end_time']">
<i class="fa-solid fa-triangle-exclamation mx-1"></i>Showing <strong>last 24 hours logs</strong> by
default. Use filters to modify the date and time.
</c-alert>
</c-col>
<c-col xs [lg]="1">
@ -95,64 +97,180 @@
</div>
</c-row>
<gui-grid [source]="source" [paging]="paging" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [autoResizeWidth]=true>
<gui-grid-column header="#No" type="NUMBER" field="index" width=25 align="CENTER">
<ng-template let-value="item.index" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Device Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<i *ngIf="item.stype=='local'" cTooltip="local user"
style="color: rgb(255, 42, 0); margin-right: 3px;font-size: .7em;" class="fa-solid fa-user-tie"></i>
<i *ngIf="item.stype=='radius'" cTooltip="Update failed"
style="color: rgb(9, 97, 20); margin-right: 3px;font-size: .7em;" class="fa-solid fa-server"></i>
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Device IP" field="devip">
<ng-template let-value="item.devip" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Username" field="username">
<ng-template let-value="item.username" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="With" field="by">
<ng-template let-value="item.by" let-item="item" let-index="index">
<div>{{value}}</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<div class="mb-3 d-flex justify-content-end gap-2">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search logs..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<gui-grid-column header="Time/Msg" field="duration">
<ng-template let-value="item.duration" let-item="item" let-index="index">
<span *ngIf="item.ltype!='failed'">{{value}}</span>
<span *ngIf="item.ltype=='failed'">{{item.message}}</span>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['username', 'name', 'ip', 'devip', 'by']" [(selection)]="selected_rows"
(onRowSelect)="showLogDetails($event.data)" selectionMode="single">
</ng-template>
</gui-grid-column>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="index" style="width: 5rem" pResizableColumn>
<div class="justify-between">
<span>#No</span>
<p-sortIcon field="index"></p-sortIcon>
</div>
</th>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Device Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="devip" pResizableColumn>
<div class="justify-between">
<span>Device IP</span>
<span>
<p-sortIcon field="devip"></p-sortIcon>
<p-columnFilter type="text" field="devip" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="username" pResizableColumn>
<div class="justify-between">
<span>Username</span>
<span>
<p-sortIcon field="username"></p-sortIcon>
<p-columnFilter type="text" field="username" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="by" pResizableColumn>
<div class="justify-between">
<span>With</span>
<span>
<p-sortIcon field="by"></p-sortIcon>
<p-columnFilter type="text" field="by" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="ip" pResizableColumn>
<div class="justify-between">
<span>IP Address</span>
<span>
<p-sortIcon field="ip"></p-sortIcon>
<p-columnFilter type="text" field="ip" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="duration" pResizableColumn>
<div class="justify-between">
<span>Time/Msg</span>
<span>
<p-sortIcon field="duration"></p-sortIcon>
<p-columnFilter type="text" field="duration" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="ltype" style="width: 120px" pResizableColumn>
<div class="justify-between">
<span>State</span>
<p-sortIcon field="ltype"></p-sortIcon>
<p-columnFilter type="text" field="ltype" display="menu" class="ms-auto" />
</div>
</th>
<th pSortableColumn="created" pResizableColumn>
<div class="justify-between">
<span>Date</span>
<p-sortIcon field="created"></p-sortIcon>
<p-columnFilter type="text" field="created" display="menu" class="ms-auto" />
</div>
</th>
</tr>
</ng-template>
<gui-grid-column header="State" field="ltype" [width]="110">
<ng-template let-value="item.ltype" let-item="item.id" let-index="index">
<c-badge color="success" *ngIf="value=='loggedin'"> Logged In</c-badge>
<c-badge color="warning" *ngIf="value=='loggedout'"> Logged Out</c-badge>
<c-badge color="danger" *ngIf="value=='failed'"> Failed</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Date" field="created">
<ng-template let-value="item.created" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
</gui-grid>
<ng-template pTemplate="body" let-item>
<tr [pSelectableRow]="item" style="cursor: pointer;">
<td class="text-center">{{item.index}}</td>
<td>
<i *ngIf="item.stype=='local'" pTooltip="local user" style="color: #ff2a00; margin-right: 3px;"
class="fa-solid fa-user-tie"></i>
<i *ngIf="item.stype=='radius'" pTooltip="radius user" style="color: #096114; margin-right: 3px;"
class="fa-solid fa-server"></i>
{{item.name}}
</td>
<td>{{item.devip}}</td>
<td>{{item.username}}</td>
<td>{{item.by}}</td>
<td>{{item.ip}}</td>
<td>
<span *ngIf="item.ltype!='failed'">{{item.duration}}</span>
<span *ngIf="item.ltype=='failed'" class="text-muted small">{{item.message}}</span>
</td>
<td class="text-center">
<c-badge color="success" *ngIf="item.ltype=='loggedin'"> Logged In</c-badge>
<c-badge color="warning" *ngIf="item.ltype=='loggedout'"> Logged Out</c-badge>
<c-badge color="danger" *ngIf="item.ltype=='failed'"> Failed</c-badge>
</td>
<td class="small">{{item.created}}</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="9" class="text-center p-4">No authentication logs found.</td>
</tr>
</ng-template>
</p-table>
<!-- Details Drawer -->
<p-drawer [(visible)]="detailsVisible" position="right" [style]="{width: '450px'}" header="Auth Log Details">
<div *ngIf="selectedLog" class="p-3">
<h5 class="border-bottom pb-2 mb-3"><i class="pi pi-shield me-2 text-primary"></i>Session Info</h5>
<div class="row mb-2">
<div class="col-4 fw-bold">Device:</div>
<div class="col-8">{{selectedLog.name}} ({{selectedLog.devip}})</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">User:</div>
<div class="col-8">{{selectedLog.username}}</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">Method:</div>
<div class="col-8">{{selectedLog.by}}</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">Status:</div>
<div class="col-8">
<c-badge color="success" *ngIf="selectedLog.ltype=='loggedin'"> Logged In</c-badge>
<c-badge color="warning" *ngIf="selectedLog.ltype=='loggedout'"> Logged Out</c-badge>
<c-badge color="danger" *ngIf="selectedLog.ltype=='failed'"> Failed</c-badge>
</div>
</div>
<h5 class="border-bottom pb-2 mb-3 mt-4"><i class="pi pi-map-marker me-2 text-primary"></i>Connection</h5>
<div class="row mb-2">
<div class="col-4 fw-bold">IP/MAC:</div>
<div class="col-8">{{selectedLog.ip}}</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">Duration:</div>
<div class="col-8">{{selectedLog.duration}}</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">Date:</div>
<div class="col-8 small">{{selectedLog.created}}</div>
</div>
<div *ngIf="selectedLog.message" class="mt-4">
<h5 class="border-bottom pb-2 mb-3"><i class="pi pi-envelope me-2 text-primary"></i>System Message</h5>
<div class="bg-light p-2 rounded small border text-break">
{{selectedLog.message}}
</div>
</div>
</div>
</p-drawer>
</c-card-body>
</c-card>
</c-col>

View file

@ -1,18 +1,8 @@
import { Component, OnInit, ViewEncapsulation,Input } from "@angular/core";
import { Component, OnInit, ViewChild, ViewEncapsulation, Input } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router, ActivatedRoute } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiSelectedRow,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { formatInTimeZone } from "date-fns-tz";
interface IUser {
@ -37,10 +27,14 @@ interface IUser {
})
export class AuthComponent implements OnInit {
@Input() component_devid: any=false;
public uid: number;
public uname: string;
public uid!: number;
public uname!: string;
public tz: string = "UTC";
public filterText: string;
public filterText!: string;
public detailsVisible: boolean = false;
public selectedLog: any = null;
@ViewChild('dt') table!: Table;
public devid: number = 0;
public reloading: boolean = false;
public filters: any = {
@ -87,42 +81,19 @@ export class AuthComponent implements OnInit {
}
}
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public Selectedrows: any;
public selected_rows: any[] = []; // Used by p-table selection
public Selectedrows: any[] = []; // ID array for legacy actions
public sorting = {
enabled: true,
multiSorting: true,
};
showLogDetails(log: any) {
this.selectedLog = log;
this.detailsVisible = true;
}
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
reinitgrid(field: string, $event: any) {
if (field == "start") this.filters["start_time"] = $event.target.value;
@ -179,9 +150,10 @@ export class AuthComponent implements OnInit {
}
this.initGridTable();
}
onSelectedRows(rows: Array<GuiSelectedRow>): void {
this.rows = rows;
this.Selectedrows = rows.map((m: GuiSelectedRow) => m.source.id);
onSelectionChange(value: any[]) {
this.selected_rows = value;
this.Selectedrows = value.map(item => item.id);
this.rows = value;
}
removefilter(filter: any) {

View file

@ -11,7 +11,10 @@ import {
} from '@coreui/angular';
import { AuthRoutingModule } from './auth-routing.module';
import { AuthComponent } from './auth.component';
import { GuiGridModule } from '@generic-ui/ngx-grid';
import { TableModule } from 'primeng/table';
import { DrawerModule } from 'primeng/drawer';
import { InputTextModule } from 'primeng/inputtext';
import { TooltipModule } from 'primeng/tooltip';
import { FormsModule } from '@angular/forms';
@ -27,7 +30,10 @@ import {MatSelectModule} from '@angular/material/select';
GridModule,
FormsModule,
ButtonModule,
GuiGridModule,
TableModule,
DrawerModule,
InputTextModule,
TooltipModule,
CollapseModule,
MatFormFieldModule,
MatInputModule,

View file

@ -20,15 +20,14 @@
<i class="fa-solid fa-code-compare me-1"></i>{{compareitems.length}} Selected
</c-badge>
<div class="selected-items d-flex gap-1">
<c-badge color="secondary" *ngFor="let item of compareitems; index as i"
<c-badge color="secondary" *ngFor="let item of compareitems; index as i"
class="selected-item d-flex align-items-center">
<span class="me-1">{{item.devname}}</span>
<i class="fa-solid fa-times cursor-pointer" (click)="delete_compare(i)"
<i class="fa-solid fa-times cursor-pointer" (click)="delete_compare(i)"
title="Remove from comparison"></i>
</c-badge>
</div>
<button *ngIf="compareitems.length > 1" (click)="start_compare()"
cButton color="success" size="sm">
<button *ngIf="compareitems.length > 1" (click)="start_compare()" cButton color="success" size="sm">
<i class="fa-solid fa-code-compare me-1"></i>Compare
</button>
<button (click)="clearAllCompare()" cButton color="secondary" size="sm" variant="outline">
@ -39,7 +38,7 @@
<!-- Filter Toggle -->
<button (click)="toggleCollapse()" cButton color="primary" variant="outline">
<i class="fa-solid fa-filter me-1"></i>Filters
<i class="fa-solid" [class]="filters_visible ? 'fa-chevron-up' : 'fa-chevron-down'"
<i class="fa-solid" [class]="filters_visible ? 'fa-chevron-up' : 'fa-chevron-down'"
style="margin-left: 0.5rem; font-size: 0.8rem;"></i>
</button>
</div>
@ -84,8 +83,8 @@
<i class="fa-solid fa-magnifying-glass me-1 text-primary"></i>Config Search
</label>
<mat-form-field appearance="outline" class="w-100">
<input (ngModelChange)="reinitgrid('search',$event)" [(ngModel)]="filters['search']"
matInput placeholder="Search in configurations...">
<input (ngModelChange)="reinitgrid('search',$event)" [(ngModel)]="filters['search']" matInput
placeholder="Search in configurations...">
</mat-form-field>
</div>
</c-col>
@ -93,65 +92,104 @@
</c-card-body>
</c-card>
</div>
<gui-grid [source]="source" [paging]="paging" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[autoResizeWidth]=true>
<gui-grid-column header="#No" type="NUMBER" field="index" width=25 align="CENTER">
<ng-template let-value="item.index" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Device Name" field="devname">
<ng-template let-value="item.devname" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Device IP" field="devip">
<ng-template let-value="item.devip" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="backup Time" field="createdC">
<ng-template let-value="item.createdC" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="File Size" field="filesize">
<ng-template let-value="item.filesize" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="MAC" field="devmac" [enabled]="false">
<ng-template let-value="item.devmac" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" field="id" width="280">
<ng-template let-value="item.id" let-item="item" let-index="index">
<div class="d-flex gap-1">
<button cButton [disabled]="backuploading" color="primary" size="sm"
(click)="ShowBackup(item)" variant="outline" title="View backup content">
<i *ngIf="backuploading && currentBackup?.id === item.id"
class="fa-solid fa-spinner fa-spin me-1"></i>
<i *ngIf="!backuploading || currentBackup?.id !== item.id"
class="fa-solid fa-eye me-1"></i>
View
</button>
<button *ngIf="ispro" cButton color="info" size="sm" variant="outline"
(click)="add_for_compare(item)" title="Add to comparison"
[disabled]="isInCompareList(item)">
<i class="fa-solid fa-code-compare me-1"></i>
{{isInCompareList(item) ? 'Added' : 'Compare'}}
</button>
<button *ngIf="ispro" cButton color="warning" size="sm" variant="outline"
(click)="restore_backup(false, false, item)" title="Restore this backup">
<i class="fa-solid fa-rotate-left me-1"></i>
Restore
</button>
</div>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search backups..."
(input)="applyFilterGlobal($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['devname', 'devip', 'createdC', 'filesize']" [loading]="loading">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="index" style="width: 70px" class="text-center" pResizableColumn>
<div class="justify-between">
<span>#No</span>
<p-sortIcon field="index"></p-sortIcon>
</div>
</th>
<th pSortableColumn="devname" pResizableColumn>
<div class="justify-between">
<span>Device Name</span>
<span>
<p-sortIcon field="devname"></p-sortIcon>
<p-columnFilter type="text" field="devname" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="devip" pResizableColumn>
<div class="justify-between">
<span>Device IP</span>
<span>
<p-sortIcon field="devip"></p-sortIcon>
<p-columnFilter type="text" field="devip" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="createdC" pResizableColumn>
<div class="justify-between">
<span>Backup Time</span>
<span>
<p-sortIcon field="createdC"></p-sortIcon>
<p-columnFilter type="text" field="createdC" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="filesize" pResizableColumn>
<div class="justify-between">
<span>File Size</span>
<span>
<p-sortIcon field="filesize"></p-sortIcon>
<p-columnFilter type="text" field="filesize" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 280px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td class="text-center">{{item.index}}</td>
<td>{{item.devname}}</td>
<td>{{item.devip}}</td>
<td>{{item.createdC}}</td>
<td>{{item.filesize}}</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
<button cButton [disabled]="backuploading" color="primary" size="sm" (click)="ShowBackup(item)"
variant="outline" title="View backup content">
<i *ngIf="backuploading && currentBackup?.id === item.id"
class="fa-solid fa-spinner fa-spin me-1"></i>
<i *ngIf="!backuploading || currentBackup?.id !== item.id" class="fa-solid fa-eye me-1"></i>
View
</button>
<button *ngIf="ispro" cButton color="info" size="sm" variant="outline" (click)="add_for_compare(item)"
title="Add to comparison" [disabled]="isInCompareList(item)">
<i class="fa-solid fa-code-compare me-1"></i>
{{isInCompareList(item) ? 'Added' : 'Compare'}}
</button>
<button *ngIf="ispro" cButton color="warning" size="sm" variant="outline"
(click)="restore_backup(false, false, item)" title="Restore this backup">
<i class="fa-solid fa-rotate-left me-1"></i>
Restore
</button>
</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="6" class="text-center p-4">No backups found.</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>
@ -166,11 +204,12 @@
<!-- <pre style="height: 100%;">
<code *ngIf="!loading" language="yaml" style="height:70vh" [highlight]="codeForHighlightAuto"
lineNumbers></code></pre> -->
<div highlight-js [lang]="hlang" [options]="{}" >{{codeForHighlightAuto}}</div>
<div highlight-js [lang]="hlang" [options]="{}">{{codeForHighlightAuto}}</div>
</c-modal-body>
<c-modal-footer style="justify-content: space-between;">
<button [cdkCopyToClipboard]="codeForHighlightAuto" [style.background-color]="copy_msg ? 'green' : null" (click)="copy_this()" cButton color="secondary">
<button [cdkCopyToClipboard]="codeForHighlightAuto" [style.background-color]="copy_msg ? 'green' : null"
(click)="copy_this()" cButton color="secondary">
<i class="fa-regular fa-copy"></i> To clipboard
</button>
<div>
@ -230,7 +269,7 @@
<i class="fa-solid fa-rotate-left fa-3x text-warning mb-3"></i>
<h6>Restore Configuration Backup</h6>
</div>
<div class="backup-info bg-light p-3 rounded mb-3">
<h6 class="mb-2"><i class="fa-solid fa-info-circle me-2 text-primary"></i>Backup Details</h6>
<div class="row">
@ -276,7 +315,7 @@
<div class="confirmation-box bg-light border border-danger p-3 rounded">
<p class="mb-3 fw-bold">To proceed with this critical action, type <code>CONFIRM</code> in the box below:</p>
<input cFormControl [(ngModel)]="confirmationText" placeholder="Type CONFIRM to proceed"
<input cFormControl [(ngModel)]="confirmationText" placeholder="Type CONFIRM to proceed"
class="form-control text-center fw-bold" style="letter-spacing: 2px;" />
</div>
@ -290,8 +329,7 @@
<button (click)="cancelCriticalRestore()" cButton color="secondary">
<i class="fa-solid fa-times me-1"></i>Cancel
</button>
<button (click)="restore_backup(true, true)" cButton color="danger"
[disabled]="confirmationText !== 'CONFIRM'">
<button (click)="restore_backup(true, true)" cButton color="danger" [disabled]="confirmationText !== 'CONFIRM'">
<i class="fa-solid fa-rotate-left me-1"></i>RESTORE BACKUP
</button>
</c-modal-footer>

View file

@ -1,18 +1,8 @@
import { Component, OnInit, QueryList, ViewChildren } from "@angular/core";
import { Component, OnInit, QueryList, ViewChildren, ViewChild } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router, ActivatedRoute } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiSearching,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { formatInTimeZone } from "date-fns-tz";
import { ToasterComponent } from "@coreui/angular";
import { AppToastComponent } from "../toast-simple/toast.component";
@ -22,10 +12,10 @@ import { AppToastComponent } from "../toast-simple/toast.component";
styleUrls: ["backups.component.scss"],
})
export class BackupsComponent implements OnInit {
public uid: number;
public uname: string;
public uid: number = 0;
public uname: string = '';
public tz: string = "UTC";
public filterText: string;
public filterText: string = '';
public filters: any = {};
public codeForHighlightAuto: string = "";
public ispro: boolean = false;
@ -69,34 +59,19 @@ export class BackupsComponent implements OnInit {
return value !== undefined && value !== null && value !== "";
}
}
@ViewChild("dt") table!: Table;
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public backuploading : boolean=false;
public backuploading: boolean = false;
public rows: any = [];
public Selectedrows: any;
public BakcupModalVisible: boolean = false;
public devid: number = 0;
public filters_visible: boolean = false;
public currentBackup:any=false;
public hlang:string='';
public sorting = {
enabled: true,
multiSorting: true,
};
searching: GuiSearching = {
enabled: true,
placeholder: "Search Devices",
};
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public currentBackup: any = false;
public hlang: string = '';
toasterForm = {
autohide: true,
@ -105,25 +80,10 @@ export class BackupsComponent implements OnInit {
fade: true,
closeButton: true,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
ngOnInit(): void {
this.devid = Number(this.route.snapshot.paramMap.get("devid"));

View file

@ -17,7 +17,9 @@ import {
import { BackupsRoutingModule } from "./backups-routing.module";
import { BackupsComponent } from "./backups.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule } from 'primeng/table';
import { InputTextModule } from 'primeng/inputtext';
import { TooltipModule } from 'primeng/tooltip';
import { MatDatepickerModule } from "@angular/material/datepicker";
import { MatInputModule } from "@angular/material/input";
import { MatFormFieldModule } from "@angular/material/form-field";
@ -35,7 +37,9 @@ import { ClipboardModule } from "@angular/cdk/clipboard";
FormModule,
FormsModule,
ButtonModule,
GuiGridModule,
TableModule,
InputTextModule,
TooltipModule,
CollapseModule,
BadgeModule,
AlertModule,

View file

@ -13,107 +13,170 @@
</c-row>
</c-card-header>
<c-card-body>
<gui-grid [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<strong>{{value}}</strong>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Description" field="description">
<ng-template let-value="item.description" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Direction" field="direction">
<ng-template let-value="item.direction" let-item="item" let-index="index">
<c-badge [color]="value == 'twoway' ? 'success' : 'warning'">{{value == 'twoway' ? 'Two Way' : 'Master Mode'}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Live Mode" field="live_mode">
<ng-template let-value="item.live_mode" let-item="item" let-index="index">
<c-badge [color]="value ? 'success' : 'secondary'">{{value ? 'Active' : 'Inactive'}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Schedule" field="desc_cron">
<ng-template let-value="item.desc_cron" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="warning" size="sm" (click)="editAddCloner(item,'edit');"><i
class="fa-regular fa-pen-to-square"></i></button>
<!-- <button cButton color="info" size="sm" (click)="confirm_run(item);" class="mx-1"><i
class="fa-solid fa-bolt"></i></button> -->
<button class=" mx-1" cButton color="danger" size="sm" (click)="confirm_delete(item);"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search cloner..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name', 'description', 'direction']" [loading]="loading">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="description" pResizableColumn>
<div class="justify-between">
<span>Description</span>
<span>
<p-sortIcon field="description"></p-sortIcon>
<p-columnFilter type="text" field="description" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="direction" pResizableColumn>
<div class="justify-between">
<span>Direction</span>
<span>
<p-sortIcon field="direction"></p-sortIcon>
<p-columnFilter type="text" field="direction" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="live_mode" class="text-center" pResizableColumn>
<div class="justify-between">
<span>Live Mode</span>
<span>
<p-sortIcon field="live_mode"></p-sortIcon>
<p-columnFilter type="boolean" field="live_mode" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="desc_cron" pResizableColumn>
<div class="justify-between">
<span>Schedule</span>
<span>
<p-sortIcon field="desc_cron"></p-sortIcon>
<p-columnFilter type="text" field="desc_cron" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 120px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><strong>{{item.name}}</strong></td>
<td>{{item.description}}</td>
<td>
<c-badge [color]="item.direction == 'twoway' ? 'success' : 'warning'">
{{item.direction == 'twoway' ? 'Two Way' : 'Master Mode'}}
</c-badge>
</td>
<td class="text-center">
<c-badge [color]="item.live_mode ? 'success' : 'secondary'">{{item.live_mode ? 'Active' :
'Inactive'}}</c-badge>
</td>
<td>{{item.desc_cron}}</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
<button cButton color="warning" size="sm" (click)="editAddCloner(item,'edit');"
pTooltip="Edit Cloner">
<i class="fa-regular fa-pen-to-square"></i>
</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item);" pTooltip="Delete Cloner">
<i class="fa-regular fa-trash-can"></i>
</button>
</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="6" class="text-center p-4">No cloners found.</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>
</c-row>
<c-modal #EditClonerModal backdrop="static" size="lg" [(visible)]="EditClonerModalVisible" id="EditClonerModal">
<c-modal-header class="bg-light">
<h5 *ngIf="SelectedCloner['action']=='edit'" cModalTitle><i class="fa-solid fa-edit me-2"></i>Edit Cloner: {{SelectedCloner['name']}}</h5>
<h5 *ngIf="SelectedCloner['action']=='add'" cModalTitle><i class="fa-solid fa-plus me-2"></i>Add New Cloner</h5>
<button [cModalToggle]="EditClonerModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body class="p-3">
<!-- Basic Information -->
<div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-info-circle me-2"></i>Basic Information</h6>
</div>
<c-row class="g-2">
<c-col xs="12" md="6">
<input cFormControl placeholder="Cloner Name" [(ngModel)]="SelectedCloner['name']" class="form-input-sm" />
</c-col>
<c-col xs="12" md="6">
<input cFormControl placeholder="Description" [(ngModel)]="SelectedCloner['description']" class="form-input-sm" />
</c-col>
</c-row>
<c-modal #EditClonerModal backdrop="static" size="lg" [(visible)]="EditClonerModalVisible" id="EditClonerModal">
<c-modal-header class="bg-light">
<h5 *ngIf="SelectedCloner['action']=='edit'" cModalTitle><i class="fa-solid fa-edit me-2"></i>Edit Cloner:
{{SelectedCloner['name']}}</h5>
<h5 *ngIf="SelectedCloner['action']=='add'" cModalTitle><i class="fa-solid fa-plus me-2"></i>Add New Cloner</h5>
<button [cModalToggle]="EditClonerModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body class="p-3">
<!-- Basic Information -->
<div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-info-circle me-2"></i>Basic Information</h6>
</div>
<c-row class="g-2">
<c-col xs="12" md="6">
<input cFormControl placeholder="Cloner Name" [(ngModel)]="SelectedCloner['name']" class="form-input-sm" />
</c-col>
<c-col xs="12" md="6">
<input cFormControl placeholder="Description" [(ngModel)]="SelectedCloner['description']"
class="form-input-sm" />
</c-col>
</c-row>
</div>
<!-- Sync Configuration -->
<div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-sync me-2"></i>Synchronization Settings</h6>
</div>
<c-row class="g-2">
<c-col xs="12" md="4">
<label class="form-label-xs">Direction</label>
<select cSelect [(ngModel)]="SelectedCloner['direction']" (change)="onDirectionChange()" class="form-select-xs">
<option value="twoway">Two Way Sync</option>
<option value="oneway">Master Mode</option>
</select>
</c-col>
<c-col xs="12" md="4" *ngIf="SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Live Mode</label>
<select cSelect [(ngModel)]="SelectedCloner['live_mode']" class="form-select-xs">
<option [ngValue]="false">Inactive</option>
<option [ngValue]="true">Active</option>
</select>
</c-col>
<c-col xs="12" md="4" *ngIf="SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Schedule</label>
<select cSelect [(ngModel)]="SelectedCloner['schedule']" class="form-select-xs">
<option [ngValue]="false">Inactive</option>
<option [ngValue]="true">Active</option>
</select>
</c-col>
<c-col xs="12" *ngIf="SelectedCloner['schedule'] && SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Cron Expression</label>
<input cFormControl placeholder="0 0 * * *" [(ngModel)]="SelectedCloner['cron']" class="form-input-sm" />
</c-col>
</c-row>
<!-- Sync Configuration -->
<div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-sync me-2"></i>Synchronization Settings</h6>
</div>
<c-row class="g-2">
<c-col xs="12" md="4">
<label class="form-label-xs">Direction</label>
<select cSelect [(ngModel)]="SelectedCloner['direction']" (change)="onDirectionChange()"
class="form-select-xs">
<option value="twoway">Two Way Sync</option>
<option value="oneway">Master Mode</option>
</select>
</c-col>
<c-col xs="12" md="4" *ngIf="SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Live Mode</label>
<select cSelect [(ngModel)]="SelectedCloner['live_mode']" class="form-select-xs">
<option [ngValue]="false">Inactive</option>
<option [ngValue]="true">Active</option>
</select>
</c-col>
<c-col xs="12" md="4" *ngIf="SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Schedule</label>
<select cSelect [(ngModel)]="SelectedCloner['schedule']" class="form-select-xs">
<option [ngValue]="false">Inactive</option>
<option [ngValue]="true">Active</option>
</select>
</c-col>
<c-col xs="12" *ngIf="SelectedCloner['schedule'] && SelectedCloner['direction']=='oneway'">
<label class="form-label-xs">Cron Expression</label>
<input cFormControl placeholder="0 0 * * *" [(ngModel)]="SelectedCloner['cron']" class="form-input-sm" />
</c-col>
</c-row>
</div>
<!-- Peers Configuration (Currently disabled - only devices supported) -->
<!-- <div class="cloner-form-section mb-3">
<!-- Peers Configuration (Currently disabled - only devices supported) -->
<!-- <div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-network-wired me-2"></i>Peers Configuration</h6>
<small class="text-muted">Select the type of peers to synchronize</small>
@ -129,233 +192,301 @@
</c-row>
</div> -->
<!-- Commands Configuration -->
<div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-terminal me-2"></i>Commands Configuration</h6>
</div>
<div class="commands-container-compact">
<div class="nav nav-underline commands-nav-compact">
<div class="nav-item" *ngFor="let tab of tabs; let i = index">
<a class="nav-link" [active]="i==0" [cTabContent]="tabContent" [tabPaneIdx]="i">{{ tab.name }}</a>
</div>
<!-- Commands Configuration -->
<div class="cloner-form-section mb-3">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-terminal me-2"></i>Commands Configuration</h6>
</div>
<div class="commands-container-compact">
<div class="nav nav-underline commands-nav-compact">
<div class="nav-item" *ngFor="let tab of tabs; let i = index">
<a class="nav-link" [active]="i==0" [cTabContent]="tabContent" [tabPaneIdx]="i">{{ tab.name }}</a>
</div>
<c-tab-content class="command-sections-compact" #tabContent="cTabContent">
<c-tab-pane *ngFor="let tab of tabs; let i = index">
<div class="command-category-compact" *ngFor="let section of tab.sections">
<h6 class="category-title-compact">{{ section.title }}</h6>
<div class="commands-grid-compact">
<div class="command-item-compact" *ngFor="let command of section.commands">
<div class="command-content-compact">
<span class="command-name-compact">{{ command }}</span>
<div class="custom-switch-compact">
<input type="checkbox" class="custom-control-input" [checked]="in_active_commands(command)" (click)="activate_command(command)" [id]="command.replace('/', '')">
<label class="custom-control-label" [for]="command.replace('/', '')"></label>
</div>
</div>
<c-tab-content class="command-sections-compact" #tabContent="cTabContent">
<c-tab-pane *ngFor="let tab of tabs; let i = index">
<div class="command-category-compact" *ngFor="let section of tab.sections">
<h6 class="category-title-compact">{{ section.title }}</h6>
<div class="commands-grid-compact">
<div class="command-item-compact" *ngFor="let command of section.commands">
<div class="command-content-compact">
<span class="command-name-compact">{{ command }}</span>
<div class="custom-switch-compact">
<input type="checkbox" class="custom-control-input" [checked]="in_active_commands(command)"
(click)="activate_command(command)" [id]="command.replace('/', '')">
<label class="custom-control-label" [for]="command.replace('/', '')"></label>
</div>
</div>
</div>
</div>
</c-tab-pane>
</c-tab-content>
</div>
</c-tab-pane>
</c-tab-content>
</div>
</div>
<!-- Master Device Selection (for Master Mode) -->
<div class="cloner-form-section mb-2" *ngIf="SelectedCloner['direction']=='oneway' && SelectedMembers.length > 0">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-crown me-2 text-warning"></i>Master Device</h6>
</div>
<div class="master-selection-compact">
<div class="master-device-compact" *ngIf="master > 0">
<i class="fa-solid fa-server me-2 text-primary"></i>
<strong>{{getMasterDeviceName()}}</strong>
<c-badge color="warning" class="ms-2">Master</c-badge>
</div>
<div class="no-master-compact" *ngIf="master == 0">
<i class="fa-solid fa-exclamation-triangle me-2 text-warning"></i>
<span class="text-muted">No master device selected</span>
</div>
</div>
</div>
<!-- Device Management -->
<div class="cloner-form-section">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-server me-2"></i>Device Management</h6>
<div class="d-flex justify-content-between align-items-center">
<c-badge color="info">{{SelectedMembers.length}} device(s)</c-badge>
</div>
</div>
<!-- Master Device Selection (for Master Mode) -->
<div class="cloner-form-section mb-2" *ngIf="SelectedCloner['direction']=='oneway' && SelectedMembers.length > 0">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-crown me-2 text-warning"></i>Master Device</h6>
</div>
<div class="master-selection-compact">
<div class="master-device-compact" *ngIf="master > 0">
<i class="fa-solid fa-server me-2 text-primary"></i>
<strong>{{getMasterDeviceName()}}</strong>
<c-badge color="warning" class="ms-2">Master</c-badge>
<div class="peers-container">
<div *ngIf="SelectedMembers.length == 0" class="empty-peers">
<div class="empty-icon">
<i class="fa-solid fa-users-slash fa-2x text-muted opacity-50"></i>
</div>
<div class="no-master-compact" *ngIf="master == 0">
<i class="fa-solid fa-exclamation-triangle me-2 text-warning"></i>
<span class="text-muted">No master device selected</span>
<div class="empty-text">
<strong>No peers added</strong>
<p class="mb-0 text-muted">Click "Add Members" to start adding peers</p>
</div>
</div>
</div>
<!-- Device Management -->
<div class="cloner-form-section">
<div class="section-header mb-2">
<h6 class="section-title mb-0"><i class="fa-solid fa-server me-2"></i>Device Management</h6>
<div class="d-flex justify-content-between align-items-center">
<c-badge color="info">{{SelectedMembers.length}} device(s)</c-badge>
</div>
</div>
<div class="peers-container">
<div *ngIf="SelectedMembers.length == 0" class="empty-peers">
<div class="empty-icon">
<i class="fa-solid fa-users-slash fa-2x text-muted opacity-50"></i>
</div>
<div class="empty-text">
<strong>No peers added</strong>
<p class="mb-0 text-muted">Click "Add Members" to start adding peers</p>
</div>
</div>
<div *ngIf="SelectedMembers.length > 0">
<gui-grid [autoResizeWidth]="true" [source]="SelectedMembers" [columnMenu]="columnMenu" [sorting]="sorting"
[rowSelection]="rowSelection" [autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid fa-crown me-2 text-warning" *ngIf="SelectedCloner['direction']=='oneway' && item.id==master" title="Master Device"></i>
<i class="fa-solid fa-server me-2 text-primary" *ngIf="!(SelectedCloner['direction']=='oneway' && item.id==master)"></i>
<strong>{{value}}</strong>
<div *ngIf="SelectedMembers.length > 0">
<p-table [value]="SelectedMembers" [paginator]="true" [rows]="10" [showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<p-sortIcon field="name"></p-sortIcon>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="MAC" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<div class="btn-group" role="group">
<button *ngIf="SelectedCloner['direction']=='oneway'" cButton color="warning" size="sm" variant="outline"
[cTooltip]="'Set as Master'" (click)="set_master(item.id)" [disabled]="item.id==master">
</th>
<th *ngIf="SelectedCloner['pair_type']=='devices'" pSortableColumn="mac" pResizableColumn>
<div class="justify-between">
<span>MAC Address</span>
<p-sortIcon field="mac"></p-sortIcon>
</div>
</th>
<th style="width: 100px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>
<div class="d-flex align-items-center">
<i class="fa-solid fa-crown me-2 text-warning"
*ngIf="SelectedCloner['direction']=='oneway' && item.id==master" pTooltip="Master Device"></i>
<i class="fa-solid fa-server me-2 text-primary"
*ngIf="!(SelectedCloner['direction']=='oneway' && item.id==master)"></i>
<strong>{{item.name}}</strong>
</div>
</td>
<td *ngIf="SelectedCloner['pair_type']=='devices'">
<c-badge color="secondary">{{item.mac}}</c-badge>
</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
<button *ngIf="SelectedCloner['direction']=='oneway'" cButton color="warning" size="sm"
variant="outline" pTooltip="Set as Master" (click)="set_master(item.id)"
[disabled]="item.id==master">
<i class="fa-solid fa-crown"></i>
</button>
<button cButton color="danger" size="sm" variant="outline"
[cTooltip]="'Remove Member'" (click)="remove_member(item)">
<i class="fa-solid fa-times"></i>
<button cButton color="danger" size="sm" variant="outline" pTooltip="Remove Member"
(click)="remove_member(item)">
<i class="fa-solid fa-trash-can"></i>
</button>
</div>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
<div class="add-members-section mt-3">
<button cButton color="success" (click)="show_new_member_form()">
<i class="fa-solid fa-plus me-1"></i>Add Members
</button>
</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td [attr.colspan]="SelectedCloner['pair_type']=='devices' ? 3 : 2" class="text-center p-2">No members
added.</td>
</tr>
</ng-template>
</p-table>
</div>
<div class="add-members-section mt-3">
<button cButton color="success" (click)="show_new_member_form()">
<i class="fa-solid fa-plus me-1"></i>Add Members
</button>
</div>
</div>
</c-modal-body>
<c-modal-footer>
<button *ngIf="SelectedCloner['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button>
<button *ngIf="SelectedCloner['action']=='edit'" (click)="submit('edit')" cButton color="primary">save</button>
<button [cModalToggle]="EditClonerModal.id" cButton color="secondary">
Close
</button>
</c-modal-footer>
</c-modal>
</div>
</c-modal-body>
<c-modal-footer>
<button *ngIf="SelectedCloner['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button>
<button *ngIf="SelectedCloner['action']=='edit'" (click)="submit('edit')" cButton color="primary">save</button>
<button [cModalToggle]="EditClonerModal.id" cButton color="secondary">
Close
</button>
</c-modal-footer>
</c-modal>
<c-modal #NewMemberModal backdrop="static" size="xl" [(visible)]="NewMemberModalVisible" id="NewMemberModal">
<c-modal-header class="bg-success text-white">
<h5 cModalTitle><i class="fa-solid fa-user-plus me-2"></i>Add Members to Cloner</h5>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
</c-modal-header>
<c-modal-body class="p-4">
<!-- Selection Summary -->
<div class="mb-3" style="min-height: 58px;">
<c-alert *ngIf="NewMemberRows.length > 0" color="info" class="d-flex align-items-center mb-0">
<i class="fa-solid fa-info-circle me-2"></i>
<span><strong>{{NewMemberRows.length}}</strong> {{SelectedCloner['pair_type']}} selected for addition</span>
</c-alert>
</div>
<c-modal #NewMemberModal backdrop="static" size="xl" [(visible)]="NewMemberModalVisible" id="NewMemberModal">
<c-modal-header class="bg-success text-white">
<h5 cModalTitle><i class="fa-solid fa-user-plus me-2"></i>Add Members to Cloner</h5>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
</c-modal-header>
<c-modal-body class="p-4">
<!-- Selection Summary -->
<div class="mb-3" style="min-height: 58px;">
<c-alert *ngIf="NewMemberRows.length > 0" color="info" class="d-flex align-items-center mb-0">
<i class="fa-solid fa-info-circle me-2"></i>
<span><strong>{{NewMemberRows.length}}</strong> {{SelectedCloner['pair_type']}} selected for addition</span>
</c-alert>
</div>
<!-- Available Members -->
<c-card>
<c-card-header class="bg-light">
<h6 class="mb-0">
<i class="fa-solid me-2" [class.fa-server]="SelectedCloner['pair_type'] == 'devices'" [class.fa-layer-group]="SelectedCloner['pair_type'] != 'devices'"></i>
Available {{SelectedCloner['pair_type'] | titlecase}} ({{availbleMembers.length}} total)
</h6>
</c-card-header>
<c-card-body class="p-0">
<div *ngIf="availbleMembers.length == 0" class="text-center p-4 text-muted">
<i class="fa-solid fa-check-circle fa-3x mb-3 text-success opacity-50"></i>
<h6>All {{SelectedCloner['pair_type']}} are already added</h6>
<p class="mb-0">No available {{SelectedCloner['pair_type']}} to add to this cloner</p>
<!-- Available Members -->
<c-card>
<c-card-header class="bg-light">
<h6 class="mb-0">
<i class="fa-solid me-2" [class.fa-server]="SelectedCloner['pair_type'] == 'devices'"
[class.fa-layer-group]="SelectedCloner['pair_type'] != 'devices'"></i>
Available {{SelectedCloner['pair_type'] | titlecase}} ({{availbleMembers.length}} total)
</h6>
</c-card-header>
<c-card-body class="p-0">
<div *ngIf="availbleMembers.length == 0" class="text-center p-4 text-muted">
<i class="fa-solid fa-check-circle fa-3x mb-3 text-success opacity-50"></i>
<h6>All {{SelectedCloner['pair_type']}} are already added</h6>
<p class="mb-0">No available {{SelectedCloner['pair_type']}} to add to this cloner</p>
</div>
<div *ngIf="availbleMembers.length > 0">
<div class="mb-3 d-flex justify-content-end p-2">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search available..."
(input)="applyFilterGlobalNewMembers($event, 'contains')" class="form-control-sm" />
</span>
</div>
<div *ngIf="availbleMembers.length > 0">
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true
[paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid me-2 text-primary" [class.fa-server]="SelectedCloner['pair_type'] == 'devices'" [class.fa-layer-group]="SelectedCloner['pair_type'] != 'devices'"></i>
<strong>{{value}}</strong>
<p-table #dtNewMembers [value]="availbleMembers" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [showGridlines]="true" [stripedRows]="true" styleClass="p-datatable-sm"
[(selection)]="SelectedNewMemberRows" (selectionChange)="onSelectedRowsNewMembers($event)"
[globalFilterFields]="['name', 'ip', 'mac']">
<ng-template pTemplate="header">
<tr>
<th style="width: 4rem" pResizableColumn><p-tableHeaderCheckbox></p-tableHeaderCheckbox></th>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<p-sortIcon field="name"></p-sortIcon>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedCloner['pair_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<small class="text-muted">{{value}}</small>
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
</c-card-body>
</c-card>
</c-modal-body>
</th>
<th *ngIf="SelectedCloner['pair_type']=='devices'" pSortableColumn="ip" pResizableColumn>
<div class="justify-between">
<span>IP Address</span>
<p-sortIcon field="ip"></p-sortIcon>
</div>
</th>
<th *ngIf="SelectedCloner['pair_type']=='devices'" pSortableColumn="mac" pResizableColumn>
<div class="justify-between">
<span>MAC Address</span>
<p-sortIcon field="mac"></p-sortIcon>
</div>
</th>
</tr>
</ng-template>
<c-modal-footer class="bg-light d-flex justify-content-between">
<div>
<small class="text-muted">Select {{SelectedCloner['pair_type']}} from the list above to add them to the cloner</small>
</div>
<div>
<button *ngIf="NewMemberRows.length > 0" (click)="add_new_members()" cButton color="success">
<i class="fa-solid fa-plus me-1"></i>Add {{NewMemberRows.length}} {{SelectedCloner['pair_type'] | titlecase}}
</button>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary" class="ms-2">
<i class="fa-solid fa-times me-1"></i>Cancel
</button>
</div>
</c-modal-footer>
</c-modal>
<ng-template pTemplate="body" let-item>
<tr>
<td><p-tableCheckbox [value]="item"></p-tableCheckbox></td>
<td>
<div class="d-flex align-items-center">
<i class="fa-solid me-2 text-primary" [class.fa-server]="SelectedCloner['pair_type'] == 'devices'"
[class.fa-layer-group]="SelectedCloner['pair_type'] != 'devices'"></i>
<strong>{{item.name}}</strong>
</div>
</td>
<td *ngIf="SelectedCloner['pair_type']=='devices'">
<c-badge color="secondary">{{item.ip}}</c-badge>
</td>
<td *ngIf="SelectedCloner['pair_type']=='devices'">
<small class="text-muted">{{item.mac}}</small>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td [attr.colspan]="SelectedCloner['pair_type']=='devices' ? 4 : 2" class="text-center p-4">No available
members.</td>
</tr>
</ng-template>
</p-table>
</div>
</c-card-body>
</c-card>
</c-modal-body>
<c-modal #DeleteConfirmModal backdrop="static" [(visible)]="DeleteConfirmModalVisible" id="DeleteConfirmModal">
<c-modal-header>
<h5 cModalTitle>Confirm delete {{ SelectedCloner['name'] }}</h5>
<button [cModalToggle]="DeleteConfirmModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
Are you sure that You want to delete following task ?
<br />
<br />
<table style="width: 100%;">
<tr>
<td><b>Taks name : </b></td>
<td>{{ SelectedCloner['name'] }}</td>
</tr>
<tr>
<td><b>Description : </b></td>
<td>{{ SelectedCloner['description'] }}</td>
</tr>
<tr>
<td><b>Cron exec : </b></td>
<td>{{ SelectedCloner['desc_cron'] }}</td>
</tr>
</table>
</c-modal-body>
<c-modal-footer>
<button (click)="confirm_delete('',true)" cButton color="danger">
Yes,Delete!
<c-modal-footer class="bg-light d-flex justify-content-between">
<div>
<small class="text-muted">Select {{SelectedCloner['pair_type']}} from the list above to add them to the
cloner</small>
</div>
<div>
<button *ngIf="NewMemberRows.length > 0" (click)="add_new_members()" cButton color="success">
<i class="fa-solid fa-plus me-1"></i>Add {{NewMemberRows.length}} {{SelectedCloner['pair_type'] | titlecase}}
</button>
<button [cModalToggle]="DeleteConfirmModal.id" cButton color="info">
Close
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary" class="ms-2">
<i class="fa-solid fa-times me-1"></i>Cancel
</button>
</c-modal-footer>
</c-modal>
</div>
</c-modal-footer>
</c-modal>
<c-toaster position="fixed" placement="top-end"></c-toaster>
<c-modal #DeleteConfirmModal backdrop="static" [(visible)]="DeleteConfirmModalVisible" id="DeleteConfirmModal">
<c-modal-header>
<h5 cModalTitle>Confirm delete {{ SelectedCloner['name'] }}</h5>
<button [cModalToggle]="DeleteConfirmModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
Are you sure that You want to delete following task ?
<br />
<br />
<table style="width: 100%;">
<tr>
<td><b>Taks name : </b></td>
<td>{{ SelectedCloner['name'] }}</td>
</tr>
<tr>
<td><b>Description : </b></td>
<td>{{ SelectedCloner['description'] }}</td>
</tr>
<tr>
<td><b>Cron exec : </b></td>
<td>{{ SelectedCloner['desc_cron'] }}</td>
</tr>
</table>
</c-modal-body>
<c-modal-footer>
<button (click)="confirm_delete('',true)" cButton color="danger">
Yes,Delete!
</button>
<button [cModalToggle]="DeleteConfirmModal.id" cButton color="info">
Close
</button>
</c-modal-footer>
</c-modal>
<c-toaster position="fixed" placement="top-end"></c-toaster>

View file

@ -1,19 +1,8 @@
import { Component, OnInit, OnDestroy, ViewChildren ,QueryList } from "@angular/core";
import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ViewChild } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiSelectedRow,
GuiSearching,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { NgxSuperSelectOptions } from "ngx-super-select";
import { _getFocusedElementPierceShadowDom } from "@angular/cdk/platform";
@ -27,10 +16,13 @@ import { AppToastComponent } from "../toast-simple/toast.component";
})
export class ClonerComponent implements OnInit {
public uid: number;
public uname: string;
public uid: number = 0;
public uname: string = '';
public ispro: boolean = false;
@ViewChild('dt') table!: Table;
@ViewChild('dtNewMembers') tableNewMembers!: Table;
constructor(
private data_provider: dataProvider,
private router: Router,
@ -62,7 +54,6 @@ export class ClonerComponent implements OnInit {
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public SelectedCloner: any = {};
@ -74,9 +65,17 @@ export class ClonerComponent implements OnInit {
public NewMemberModalVisible: boolean = false;
public availbleMembers: any = [];
public NewMemberRows: any = [];
public SelectedNewMemberRows: any;
public SelectedNewMemberRows: any[] = [];
public master: number = 0;
public active_commands:any=[];
public active_commands: any = [];
toasterForm = {
autohide: true,
delay: 3000,
position: "fixed",
fade: true,
closeButton: true,
};
public tabs:any=[
{
"name": "Network",
@ -186,59 +185,13 @@ export class ClonerComponent implements OnInit {
}
];
public sorting = {
enabled: true,
multiSorting: true,
};
searching: GuiSearching = {
enabled: true,
placeholder: "Search Devices",
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
options: Partial<NgxSuperSelectOptions> = {
selectionMode: "single",
actionsEnabled: false,
displayExpr: "name",
valueExpr: "id",
placeholder: "Snippet",
searchEnabled: true,
enableDarkMode: false,
};
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
toasterForm = {
autohide: true,
delay: 3000,
position: "fixed",
fade: true,
closeButton: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterGlobalNewMembers($event: any, stringVal: string) {
this.tableNewMembers.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
activate_command(command:string){
// add to active_commands if it not added before
if(!this.active_commands.includes(command)){
@ -340,9 +293,9 @@ export class ClonerComponent implements OnInit {
}
}
onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void {
onSelectedRowsNewMembers(rows: any[]): void {
this.NewMemberRows = rows;
this.SelectedNewMemberRows = rows.map((m: GuiSelectedRow) => m.source);
this.SelectedNewMemberRows = rows;
}
add_new_members() {

View file

@ -18,7 +18,9 @@ import {
} from "@coreui/angular";
import { ClonerRoutingModule } from "./cloner-routing.module";
import { ClonerComponent } from "./cloner.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PTableModule } from 'primeng/table';
import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
import { NgxSuperSelectModule} from "ngx-super-select";
@ -31,7 +33,9 @@ import { NgxSuperSelectModule} from "ngx-super-select";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
PTableModule,
PInputTextModule,
PTooltipModule,
ModalModule,
ReactiveFormsModule,
FormsModule,

View file

@ -173,7 +173,7 @@
<span *ngIf="copy_msg" style="color: #fff!important;" class="badge text-bg-success"><i
class="fa-solid fa-check"></i>Copy</span>
</div>
<c-badge *ngIf="stats['username']" color="danger">Not Registred</c-badge>
<c-badge *ngIf="stats['username']" color="danger">Not Registered</c-badge>
<c-badge *ngIf="!stats['username']" color="danger">License Validation failed</c-badge>
</div>
<div *ngIf="stats['license']=='connection_error'" class="my-1">
@ -194,7 +194,7 @@
<span *ngIf="copy_msg" style="color: #fff!important;" class="badge text-bg-success mx-1"><i
class="fa-solid fa-check"></i>Copy</span>
</div>
<c-badge color="success">Registred</c-badge>
<c-badge color="success">Registered</c-badge>
<c-badge class="mx-1" color="info">License Type : {{stats['license']}}</c-badge>
<c-badge *ngIf="stats['update_mode']!='auto'" color="info">Manual update</c-badge>
<c-badge *ngIf="stats['update_mode']=='auto'" color="info">Auto update</c-badge>
@ -223,8 +223,12 @@
class="fa-regular fa-hand-pointer fa-beat-fade"></i> Update availble </button>
</span>
</div>
<p *ngIf="!stats['license'] && !stats['username']" style="color: rgb(0, 119, 255);"><strong>License User name is not set in settings <a style="color: rgb(0, 119, 255);" target="_blank" href="https://mikrowizard.com/docs/register-serial-number/" >read more!</a></strong></p>
<p *ngIf="!stats['license'] && stats['username']" style="color: rgb(0, 119, 255);"><strong>Serial number not submited<a style="color: rgb(0, 119, 255);" target="_blank" href="https://mikrowizard.com/docs/register-serial-number/" >read more!</a></strong> </p>
<p *ngIf="!stats['license'] && !stats['username']" style="color: rgb(0, 119, 255);"><strong>License User name is
not set in settings <a style="color: rgb(0, 119, 255);" target="_blank"
href="https://mikrowizard.com/docs/register-serial-number/">read more!</a></strong></p>
<p *ngIf="!stats['license'] && stats['username']" style="color: rgb(0, 119, 255);"><strong>Serial number not
submited<a style="color: rgb(0, 119, 255);" target="_blank"
href="https://mikrowizard.com/docs/register-serial-number/">read more!</a></strong> </p>
<!-- <div *ngIf="stats['update_mode']!='auto'" class="my-1">
<button cButton color="warning" *ngIf="stats['update_available']" size="sm" style="font-size: 1em;"><i class="fa-regular fa-hand-pointer fa-beat-fade"></i> Update <strong>Mikroman</strong> and reload server</button>

View file

@ -12,7 +12,7 @@
<tr>
<th scope="col">#</th>
<th scope="col">Address</th>
<th scope="col">Att</th>
<th scope="col">At</th>
<th scope="col">Group</th>
<th scope="col">Name</th>
<th scope="col">Via</th>
@ -27,8 +27,9 @@
<td>{{item['group']}}</td>
<td>{{item['name']}}</td>
<td>{{item['via']}}</td>
<td><button cButton (click)="killsession(item)" style="padding: 0;" size="sm" color="danger" variant="ghost"><i class="fa-solid fa-skull-crossbones"></i>
kill</button></td>
<td><button cButton (click)="killsession(item)" style="padding: 0;" size="sm" color="danger"
variant="ghost"><i class="fa-solid fa-skull-crossbones"></i>
kill</button></td>
</tr>
</tbody>
</table>

View file

@ -104,69 +104,97 @@
<c-card class="mb-1">
<c-card-body>
<c-row style="flex-direction: row">
<gui-grid [source]="interfaces" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[autoResizeWidth]="true">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
{{ value }} - {{ item["default-name"] }}
</ng-template>
</gui-grid-column>
<div class="mb-3 d-flex justify-content-end w-100">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search interfaces..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<gui-grid-column header="MAC" field="mac-address">
<ng-template let-value="item['mac-address']" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="rx" field="rx-byte">
<ng-template let-value="item['rx-byte']" let-item="item" let-index="index">
<div>{{ convert_bw_human(value, "rx") }}</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="tx" field="tx-byte">
<ng-template let-value="item['tx-byte']" let-item="item" let-index="index">
{{ convert_bw_human(value, "tx") }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="l2mtu" field="l2mtu">
<ng-template let-value="item.l2mtu" let-item="item" let-index="index">
curr:{{ value }}<br />
max : {{ item["max-l2mtu"] }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="rx/s" field="rx-bits-per-second" [enabled]="false">
<ng-template let-value="item['rx-bits-per-second']" let-item="item" let-index="index">
{{ convert_bw_human(value, "rx") }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="tx/s" field="tx-bits-per-second" [enabled]="false">
<ng-template let-value="item['tx-bits-per-second']" let-item="item" let-index="index">
{{ convert_bw_human(value, "tx") }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created" [enabled]="false">
<ng-template let-value="item.created" let-item="item.id" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Last Up" field="last-link-up-time">
<ng-template let-value="item['last-link-up-time']" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created" [enabled]="false">
<ng-template let-value="item.created" let-item="item.id" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" field="action" width="60" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="info" size="sm" (click)="show_interface_rate(item['default-name'])"
class="mx-1">
<p-table #dtInterfaces [value]="interfaces" [paginator]="true" [rows]="10"
[showGridlines]="true" [stripedRows]="true"
[resizableColumns]="true" columnResizeMode="expand"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name', 'default-name', 'mac-address']">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="mac-address" pResizableColumn>
<div class="justify-between">
<span>MAC</span>
<span>
<p-sortIcon field="mac-address"></p-sortIcon>
<p-columnFilter type="text" field="mac-address" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="rx-byte" pResizableColumn>
<div class="justify-between">
<span>RX</span>
<span>
<p-sortIcon field="rx-byte"></p-sortIcon>
<p-columnFilter type="numeric" field="rx-byte" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="tx-byte" pResizableColumn>
<div class="justify-between">
<span>TX</span>
<span>
<p-sortIcon field="tx-byte"></p-sortIcon>
<p-columnFilter type="numeric" field="tx-byte" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="l2mtu" pResizableColumn>
<div class="justify-between">
<span>L2MTU</span>
<span>
<p-sortIcon field="l2mtu"></p-sortIcon>
<p-columnFilter type="numeric" field="l2mtu" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="last-link-up-time" pResizableColumn>
<div class="justify-between">
<span>Last Up</span>
<span>
<p-sortIcon field="last-link-up-time"></p-sortIcon>
<p-columnFilter type="text" field="last-link-up-time" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 60px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-iface>
<tr>
<td>{{iface.name}} <small class="text-muted">({{iface['default-name']}})</small></td>
<td><small class="font-monospace">{{iface['mac-address']}}</small></td>
<td>{{convert_bw_human(iface['rx-byte'], 'rx')}}</td>
<td>{{convert_bw_human(iface['tx-byte'], 'tx')}}</td>
<td>
<small>curr: {{iface.l2mtu}}</small><br/>
<small>max: {{iface['max-l2mtu']}}</small>
</td>
<td><small>{{iface['last-link-up-time']}}</small></td>
<td class="text-center">
<button cButton color="info" size="sm" (click)="show_interface_rate(iface['default-name'])" pTooltip="Interface Chart">
<i class="fa-solid fa-chart-line"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</td>
</tr>
</ng-template>
</p-table>
</c-row>
</c-card-body>
</c-card>
@ -182,16 +210,16 @@
</c-card-header>
<c-card-body>
<h6>{{ raddata.key }}</h6>
<app-widgets-dropdown [devicedata]="raddata.value"></app-widgets-dropdown>
<app-widgets-dropdown [devicedata]="$any(raddata.value)"></app-widgets-dropdown>
<c-row>
<c-col md="3">
<table style="word-break: break-word" small stripedColumns cTable>
<tbody>
<ng-container *ngFor="
let d of raddata.value['data'] | keyvalue;
let d of $any(raddata.value)['data'] | keyvalue;
let i = index
">
<tr *ngIf="i < objectlen(raddata.value['data']) / 4">
<tr *ngIf="i < objectlen($any(raddata.value)['data']) / 4">
<th style="width: 20%; text-wrap: nowrap">
{{ d.key }}
</th>
@ -205,12 +233,12 @@
<table style="word-break: break-word" small stripedColumns cTable>
<tbody>
<ng-container *ngFor="
let d of raddata.value['data'] | keyvalue;
let d of $any(raddata.value)['data'] | keyvalue;
let i = index
">
<tr *ngIf="
i >= objectlen(raddata.value['data']) / 4 &&
i < (objectlen(raddata.value['data']) / 4) * 2
i >= objectlen($any(raddata.value)['data']) / 4 &&
i < (objectlen($any(raddata.value)['data']) / 4) * 2
">
<th style="width: 20%; text-wrap: nowrap">
{{ d.key }}
@ -225,12 +253,12 @@
<table style="word-break: break-word" small stripedColumns cTable>
<tbody>
<ng-container *ngFor="
let d of raddata.value['data'] | keyvalue;
let d of $any(raddata.value)['data'] | keyvalue;
let i = index
">
<tr *ngIf="
i >= (objectlen(raddata.value['data']) / 4) * 2 &&
i < (objectlen(raddata.value['data']) / 4) * 3
i >= (objectlen($any(raddata.value)['data']) / 4) * 2 &&
i < (objectlen($any(raddata.value)['data']) / 4) * 3
">
<th style="width: 20%; text-wrap: nowrap">
{{ d.key }}
@ -245,11 +273,11 @@
<table small stripedColumns cTable>
<tbody>
<ng-container *ngFor="
let d of raddata.value['data'] | keyvalue;
let d of $any(raddata.value)['data'] | keyvalue;
let i = index
">
<tr *ngIf="
i >= (objectlen(raddata.value['data']) / 4) * 3
i >= (objectlen($any(raddata.value)['data']) / 4) * 3
">
<th>{{ d.key }}</th>
<td scope="row">{{ d.value }}</td>
@ -259,7 +287,7 @@
</table>
</c-col>
</c-row>
<c-row *ngIf="raddata.value['strength-at-rates']">
<c-row *ngIf="$any(raddata.value)['strength-at-rates']">
<c-col>
<table style="word-break: break-word" small borderless cTable>
<tbody>
@ -276,7 +304,7 @@
<td scope="row">
<c-badge color="info" style="font-size: 0.85em" class="mx-1" *ngFor="
let st of strangth_at_rate_extract(
raddata.value['strength-at-rates']
$any(raddata.value)['strength-at-rates']
)
">{{ st }}</c-badge>
</td>
@ -292,8 +320,7 @@
</c-tab-pane>
<c-tab-pane>
<ng-container *ngIf="dhcp_server_available">
<app-dhcp-info [tz]="tz" [dhcp_server_data]="dhcp_server_data" [small_screen]="small_screen" [columnMenu]="columnMenu"
[sorting]="sorting" [searching]="searching" [infoPanel]="infoPanel" [paging]="paging"></app-dhcp-info>
<app-dhcp-info [tz]="tz" [dhcp_server_data]="dhcp_server_data" [small_screen]="small_screen"></app-dhcp-info>
</ng-container>
</c-tab-pane>
<c-tab-pane style="width: 100%;padding-top: 10px;background-color: #fff;">

View file

@ -24,9 +24,6 @@
cursor: pointer;
}
.interfaces gui-grid gui-structure{
min-height: unset!important;
}
.nav-underline .nav-link:hover,.nav-underline .nav-link:focus {
border-color: var(--cui-nav-underline-link-active-border-color, #321fdb)
}

View file

@ -1,18 +1,8 @@
import { Component, OnDestroy, OnInit ,ViewEncapsulation} from "@angular/core";
import { Component, OnDestroy, OnInit, ViewEncapsulation, ViewChild } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router";
import { dataProvider } from "../../providers/mikrowizard/data";
import { loginChecker } from "../../providers/login_checker";
import {
GuiSearching,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { __setFunctionName } from "tslib";
interface IUser {
name: string;
@ -38,10 +28,10 @@ type radiodata = {
encapsulation: ViewEncapsulation.None,
})
export class DeviceComponent implements OnInit, OnDestroy {
public uid: number;
public uid!: number;
public sessionloaded: boolean = false;
public uname: string;
public tz: string;
public uname!: string;
public tz!: string;
public ispro: boolean = false;
public small_screen=false;
public show_dev_logs: boolean = false;
@ -81,8 +71,11 @@ export class DeviceComponent implements OnInit, OnDestroy {
}
public devdata: any;
public devsensors: any;
public radio_devsensors: radiodata;
public columns: Array<GuiColumn> = [];
public radiodata!: radiodata;
public radio_devsensors!: any;
@ViewChild('dtInterfaces') dtInterfaces!: Table;
public loading: boolean = true;
public radio_loading: boolean = true;
public InterfaceChartModalVisible: boolean = false;
@ -98,45 +91,11 @@ export class DeviceComponent implements OnInit, OnDestroy {
public dhcp_server_available: boolean = false;
public dhcp_server_data: any = {};
public sorting = {
enabled: true,
multiSorting: true,
};
public interfaces: Array<any> = [];
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 20,
pageSizes: [20],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
objectlen(object:any){
return Object.keys(object).length;
applyFilterGlobal($event: any, stringVal: string) {
this.dtInterfaces.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
strangth_at_rate_extract(data:string){
return data.split(',');
}
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
searching: GuiSearching = {
enabled: true,
placeholder: "Search In table",
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
reload_dhcp_server(){
this.get_DHCP_data();
}
@ -553,6 +512,12 @@ export class DeviceComponent implements OnInit, OnDestroy {
show_history(itme:any) {
return
}
objectlen(object:any){
return object ? Object.keys(object).length : 0;
}
strangth_at_rate_extract(data:string){
return data ? data.split(',') : [];
}
ngOnDestroy() {
clearInterval(this.data_interval);
}

View file

@ -22,7 +22,9 @@ import { ChartjsModule } from "@coreui/angular-chartjs";
import { DeviceRoutingModule } from "./device-routing.module";
import { DeviceComponent } from "./device.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PTableModule } from 'primeng/table';
import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
import { WidgetsModule } from "../widgets/widgets.module";
import { DeviceInfoModule } from "./device-info/device-info.module";
@ -53,7 +55,9 @@ import { AccModule } from "../acc_log/acc.module";
AuthModule,
AccModule,
SpinnerModule,
GuiGridModule,
PTableModule,
PInputTextModule,
PTooltipModule,
NavbarModule,
ModalModule,
TableModule,

View file

@ -1,5 +1,5 @@
<ng-container *ngFor="let item of dhcp_server_data ">
<c-row style="padding: 10px 0;" >
<c-row style="padding: 10px 0;">
<c-col xl="3" sm="12">
<c-row *ngIf="small_screen">
<c-col sm="6" style="padding-right: 0;">
@ -77,51 +77,101 @@
<c-col xl="9" sm="12">
<c-card class="mb-1 h-100">
<c-card-body>
<gui-grid [source]="item['lease_table']" [columnMenu]="columnMenu" [sorting]="sorting"
[searching]="searching" [infoPanel]="infoPanel" [rowHeight]="28" [autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="address" field="address">
<ng-template let-value="item.address" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Type" field="dynamic">
<ng-template let-value="item" let-item="item" let-index="index">
<span *ngIf="item['dynamic']">Dynamic</span>
<span *ngIf="item['static']">Static</span>
</ng-template>
</gui-grid-column>
<gui-grid-column header="expire" field="expires-after">
<ng-template let-value="item['expires-after']" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="host-name" field="host-name">
<ng-template let-value="item['host-name']" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="last-seen" field="last-seen">
<ng-template let-value="item['last-seen']" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="status" field="status">
<ng-template let-value="item['status']" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="MAC" field="mac-address">
<ng-template let-value="item['mac-address']" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="60" field="">
<ng-template let-value="item" let-item="item" let-index="index">
<button cButton color="info" size="sm" (click)="show_history(item)" class="mx-1"><i
class="fa-solid fa-clock-rotate-left"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search leases..."
(input)="applyFilterGlobalLeases($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dtLeases [value]="item['lease_table']" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" [resizableColumns]="true" columnResizeMode="expand"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['address', 'host-name', 'mac-address', 'status']">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="address" pResizableColumn>
<div class="justify-between">
<span>Address</span>
<span>
<p-sortIcon field="address"></p-sortIcon>
<p-columnFilter type="text" field="address" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="dynamic" pResizableColumn>
<div class="justify-between">
<span>Type</span>
<span>
<p-sortIcon field="dynamic"></p-sortIcon>
<p-columnFilter type="boolean" field="dynamic" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="expires-after" pResizableColumn>
<div class="justify-between">
<span>Expire</span>
<span>
<p-sortIcon field="expires-after"></p-sortIcon>
<p-columnFilter type="text" field="expires-after" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="host-name" pResizableColumn>
<div class="justify-between">
<span>Host Name</span>
<span>
<p-sortIcon field="host-name"></p-sortIcon>
<p-columnFilter type="text" field="host-name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="status" pResizableColumn>
<div class="justify-between">
<span>Status</span>
<span>
<p-sortIcon field="status"></p-sortIcon>
<p-columnFilter type="text" field="status" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="mac-address" pResizableColumn>
<div class="justify-between">
<span>MAC</span>
<span>
<p-sortIcon field="mac-address"></p-sortIcon>
<p-columnFilter type="text" field="mac-address" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 60px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-lease>
<tr>
<td>{{lease.address}}</td>
<td>
<c-badge [color]="lease.dynamic ? 'info' : 'secondary'">
{{lease.dynamic ? 'Dynamic' : 'Static'}}
</c-badge>
</td>
<td>{{lease['expires-after']}}</td>
<td>{{lease['host-name']}}</td>
<td>
<c-badge [color]="lease.status === 'bound' ? 'success' : 'warning'">
{{lease.status}}
</c-badge>
</td>
<td><small class="font-monospace">{{lease['mac-address']}}</small></td>
<td class="text-center">
<button cButton color="info" size="sm" (click)="show_history(lease)" pTooltip="View History">
<i class="fa-solid fa-clock-rotate-left"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>
@ -129,36 +179,60 @@
</ng-container>
<c-modal #DhcpHistoryModal backdrop="static" size="xl" [visible]="dhcp_history_modal"
id="DhcpHistoryModal" *ngIf="dhcp_history_modal">
<c-modal #DhcpHistoryModal backdrop="static" size="xl" [visible]="dhcp_history_modal" id="DhcpHistoryModal"
*ngIf="dhcp_history_modal">
<c-modal-header>
<h5 cModalTitle>History for {{current_dhcp['mac-address']}}</h5>
<button [cModalToggle]="DhcpHistoryModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body style="overflow-x: auto;">
<gui-grid style="min-width: 900px;" [source]="dhcp_history" [columnMenu]="columnMenu" [sorting]="sorting"
[searching]="searching" [infoPanel]="infoPanel" [rowHeight]="35" [autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="comment" field="comment" >
<ng-template let-value="item.comment" let-item="item" let-index="index">
<div style="text-wrap: auto;font-weight: bold;line-height: normal;">{{value}}</div>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search history..."
(input)="applyFilterGlobalHistory($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dtHistory [value]="dhcp_history" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" [resizableColumns]="true" columnResizeMode="expand"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['comment', 'detail', 'eventtime', 'ip', 'name']">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="comment" pResizableColumn>
Comment
<p-sortIcon field="comment"></p-sortIcon>
<p-columnFilter type="text" field="comment" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="detail" style="width: 150px" pResizableColumn>
Type
<p-sortIcon field="detail"></p-sortIcon>
<p-columnFilter type="text" field="detail" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="eventtime" style="width: 200px" pResizableColumn>
Event Time
<p-sortIcon field="eventtime"></p-sortIcon>
<p-columnFilter type="text" field="eventtime" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="ip" style="width: 250px" pResizableColumn>
Router
<p-sortIcon field="ip"></p-sortIcon>
<p-columnFilter type="text" field="ip" display="menu" class="ms-auto" />
</th>
</tr>
</ng-template>
</gui-grid-column>
<gui-grid-column header="type" field="detail" width="150">
<ng-template let-value="item['detail']" let-item="item" let-index="index">
{{value}}
<ng-template pTemplate="body" let-hist>
<tr>
<td>
<div style="text-wrap: auto; font-weight: bold;">{{hist.comment}}</div>
</td>
<td>{{hist.detail}}</td>
<td>{{hist.eventtime}}</td>
<td>{{hist.name}} ({{hist.ip}})</td>
</tr>
</ng-template>
</gui-grid-column>
<gui-grid-column header="eventtime" field="eventtime" width="200">
<ng-template let-value="item['eventtime']" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Router" field="ip" width="250">
<ng-template let-value="item['ip']" let-item="item" let-index="index">
{{item.name}} ({{value}})
</ng-template>
</gui-grid-column>
</gui-grid>
</p-table>
</c-modal-body>
<c-modal-footer>
<button [cModalToggle]="DhcpHistoryModal.id" cButton color="secondary">

View file

@ -1,6 +1,7 @@
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component,Input } from '@angular/core';
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, ViewChild } from '@angular/core';
import { dataProvider } from "../../../providers/mikrowizard/data";
import { formatInTimeZone } from "date-fns-tz";
import { Table } from 'primeng/table';
@Component({
selector: 'app-dhcp-info',
@ -11,13 +12,11 @@ import { formatInTimeZone } from "date-fns-tz";
export class DhcpInfoComponent implements AfterContentInit {
@Input() dhcp_server_data: any;
@Input() small_screen: boolean=false;
@Input() columnMenu: any;
@Input() sorting: any;
@Input() searching: any;
@Input() infoPanel: any;
@Input() paging: any;
@Input() rowSelectionMode: any;
@Input() tz: any;
@ViewChild('dtLeases') dtLeases!: Table;
@ViewChild('dtHistory') dtHistory!: Table;
dhcp_history:any;
dhcp_history_modal: boolean = false;
current_dhcp:any;
@ -26,7 +25,14 @@ export class DhcpInfoComponent implements AfterContentInit {
private data_provider: dataProvider,
) {}
show_history(item:any) {
applyFilterGlobalLeases($event: any, stringVal: string) {
this.dtLeases.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
applyFilterGlobalHistory($event: any, stringVal: string) {
this.dtHistory.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
show_history(item: any) {
var _self = this;
this.data_provider.getDhcpHistory(item).then((res) => {
_self.current_dhcp = item;

View file

@ -23,7 +23,9 @@ import { WidgetsModule } from "../../widgets/widgets.module";
// import { WidgetsRoutingModule } from './widgets-routing.module';
import { DhcpInfoComponent } from './dhcp-info.component';
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PTableModule } from 'primeng/table';
import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
@NgModule({
declarations: [
@ -42,7 +44,9 @@ import { GuiGridModule } from "@generic-ui/ngx-grid";
WidgetsModule,
NavbarModule,
ModalModule,
GuiGridModule,
PTableModule,
PInputTextModule,
PTooltipModule,
TableModule,
UtilitiesModule,
BadgeModule,

View file

@ -9,7 +9,8 @@
{{ping['successful_pings']}}</c-badge></h6>
<h6 style="display: inline-block;margin-left: 0.5rem;"> | Failed pings : <c-badge color="danger"
style="font-size: 90%;"> {{ping['failed_pings']}}</c-badge></h6>
<h6 style="display: inline-block;margin-left: 0.5rem;">| Avrage : <c-badge color="dark" style="font-size: 90%;">
<h6 style="display: inline-block;margin-left: 0.5rem;">| Average : <c-badge color="dark"
style="font-size: 90%;">
{{ping['average_ping_time']}} ms</c-badge></h6>
<table cTable small>
<thead>

View file

@ -4,7 +4,7 @@
<c-card-header>
<c-row>
<c-col xs [lg]="11" style="display: flex;flex-direction: column;align-items: flex-start;">
<h5>Device LOGS
<h5>Device Logs
<a style="cursor: pointer;" (click)="reinitgrid('none','none')"><i
*ngIf="devid!=0 && component_devid && !reloading" class="fa-solid fa-arrows-rotate"
style="color: #74C0FC;"></i>
@ -14,8 +14,10 @@
</h5>
<c-badge color="warning" *ngIf="devid!=0 && !component_devid">Filtered Result For Device ID
{{devid}}</c-badge>
<c-alert color="warning" style="padding-top: 5px!important;font-size: 0.8rem;display: inline-block;" *ngIf="!filters['start_time'] && !filters['end_time']">
<i class="fa-solid fa-triangle-exclamation mx-1"></i>Showing <strong>last 24 hours logs</strong> by default. Use filters to modify the date and time.
<c-alert color="warning" style="padding-top: 5px!important;font-size: 0.8rem;display: inline-block;"
*ngIf="!filters['start_time'] && !filters['end_time']">
<i class="fa-solid fa-triangle-exclamation mx-1"></i>Showing <strong>last 24 hours logs</strong> by
default. Use filters to modify the date and time.
</c-alert>
</c-col>
<c-col xs [lg]="1">
@ -84,74 +86,207 @@
</div>
</c-row>
<gui-grid wid [rowDetail]="rowDetail" [horizontalGrid]="true" [rowHeight]="52" [source]="source"
[columnMenu]="columnMenu" [paging]="paging" [sorting]="sorting" [infoPanel]="infoPanel"
[autoResizeWidth]="true">
<gui-grid-column header="#No" type="NUMBER" field="index" width="1" align="CENTER">
<ng-template let-value="item.index" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search logs..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<gui-grid-column header="level" width='90' wid field="level">
<ng-template let-value="item.level" let-item="item" let-index="index">
<c-badge style="cursor: pointer; font-weight: normal" color="danger" *ngIf="value == 'Critical'">{{ value
}}</c-badge>
<c-badge style="cursor: pointer; font-weight: normal" color="warning" *ngIf="value == 'Error'">{{ value
}}</c-badge>
<c-badge style="cursor: pointer; font-weight: normal" color="warning" *ngIf="value == 'Warning'">{{ value
}}</c-badge>
<c-badge style="cursor: pointer; font-weight: normal; min-width: 60px;" color="info"
*ngIf="value == 'info'">{{ value }}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Event" width='200' field="detail">
<ng-template let-value="item.detail" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Detail" field="comment">
<ng-template let-value="item.comment" let-item="item" let-index="index">
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['detail', 'comment', 'src', 'name', 'devip', 'level']" selectionMode="single"
(onRowSelect)="showLogDetails($event.data)">
<div class="gui-dev-info">
{{ value }}
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="index" style="width: 5rem" pResizableColumn>
<div class="justify-between">
<span>#No</span>
<p-sortIcon field="index"></p-sortIcon>
</div>
</th>
<th pSortableColumn="level" style="width: 100px" pResizableColumn>
<div class="justify-between">
<span>Level</span>
<span>
<p-sortIcon field="level"></p-sortIcon>
<p-columnFilter type="text" field="level" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="detail" pResizableColumn>
<div class="justify-between">
<span>Event</span>
<span>
<p-sortIcon field="detail"></p-sortIcon>
<p-columnFilter type="text" field="detail" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="comment" pResizableColumn>
<div class="justify-between">
<span>Detail</span>
<span>
<p-sortIcon field="comment"></p-sortIcon>
<p-columnFilter type="text" field="comment" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="src" style="width: 100px" pResizableColumn>
<div class="justify-between">
<span>Source</span>
<span>
<p-sortIcon field="src"></p-sortIcon>
<p-columnFilter type="text" field="src" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="status" style="width: 100px" pResizableColumn>
<div class="justify-between">
<span>Status</span>
<span>
<p-sortIcon field="status"></p-sortIcon>
<p-columnFilter type="text" field="status" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="eventtime" style="width: 200px" pResizableColumn>
<div class="justify-between">
<span>Time</span>
<span>
<p-sortIcon field="eventtime"></p-sortIcon>
<p-columnFilter type="text" field="eventtime" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Device</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr [pSelectableRow]="item" style="cursor: pointer;">
<td class="text-center">{{item.index}}</td>
<td>
<c-badge
[color]="item.level == 'Critical' ? 'danger' : (item.level == 'Error' || item.level == 'Warning' ? 'warning' : 'info')"
style="font-weight: normal; min-width: 65px;">
{{item.level}}
</c-badge>
</td>
<td>{{item.detail}}</td>
<td>
<div class="text-truncate" style="max-width: 250px;" [pTooltip]="item.comment" tooltipPosition="top">
{{item.comment}}
</div>
</td>
<td>{{item.src}}</td>
<td class="text-center">
<c-badge [color]="item.status == true ? 'success' : 'danger'" style="font-weight: normal">
{{item.status == true ? 'Fixed' : 'Not Fixed'}}
</c-badge>
</td>
<td class="small">
<div *ngIf="item.fixtime" class="d-flex flex-column gap-1">
<div class="d-flex align-items-center gap-1">
<c-badge color="danger" style="font-size: 0.6rem; padding: 2px 4px;">EVT</c-badge>
<span>{{item.eventtime}}</span>
</div>
<div class="d-flex align-items-center gap-1">
<c-badge color="success" style="font-size: 0.6rem; padding: 2px 4px;">FIX</c-badge>
<span>{{item.fixtime}}</span>
</div>
</div>
<div *ngIf="!item.fixtime">{{item.eventtime}}</div>
</td>
<td>
<div class="fw-bold">{{item.name}}</div>
<div class="small text-muted">{{item.devip}}</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="8" class="text-center p-4">No device logs found.</td>
</tr>
</ng-template>
</p-table>
<!-- Details Drawer -->
<p-drawer [(visible)]="detailsVisible" position="right" [style]="{width: '500px'}" header="Device Log Details">
<div *ngIf="selectedLog" class="p-0">
<div [style.background-color]="getSeverityColor(selectedLog.level)" class="p-4 text-white shadow-sm mb-4">
<div class="d-flex align-items-center justify-content-between">
<div>
<h3 class="m-0"><i class="pi pi-exclamation-triangle me-2"></i>{{selectedLog.level}} Alert</h3>
<div class="opacity-75">{{selectedLog.eventtype || 'System Event'}}</div>
</div>
<i class="pi pi-bell" style="font-size: 2.5rem; opacity: 0.3;"></i>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Source" width='90' field="src">
<ng-template let-value="item.src" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="status" width='100' field="status" align="CENTER">
<ng-template let-value="item.status" let-item="item" let-index="index">
<c-badge style=" cursor: pointer; font-weight: normal" color="success"
*ngIf="value == true">Fixed</c-badge>
<c-badge style="cursor: pointer; font-weight: normal" color="danger" *ngIf="value != true">Not
Fixed</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="eventtime" width='220' field="eventtime">
<ng-template let-value="item.eventtime" let-item="item" let-index="index">
<div *ngIf="item.fixtime" class="fixed_time"><span><c-badge *ngIf="item.status==true" color="danger"
style="font-size: 0.65em!important;padding: 3px 5px;" size="sm" shape="rounded-pill">Event</c-badge>
{{value}}</span><span>
<c-badge *ngIf="item.status==true" color="success"
style="font-size: 0.65em!important;padding: 3px 5px;" size="sm" shape="rounded-pill">Fixed</c-badge>
{{item.fixtime}}</span>
</div>
<div class="px-4 pb-4">
<h5 class="border-bottom pb-2 mb-3"><i class="pi pi-desktop me-2 text-primary"></i>Device Info</h5>
<div class="row mb-2">
<div class="col-5 fw-bold text-muted small text-uppercase">Device Name:</div>
<div class="col-7 fw-bold">{{selectedLog.name}}</div>
</div>
<div *ngIf="!item.fixtime"> {{ value }}</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Device" width='200' field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="gui-dev-info">
<span class="gui-dev-info-name">{{ value }}</span>
<span class="gui-dev-info-ip">{{ item.devip }}</span>
<div class="row mb-2">
<div class="col-5 fw-bold text-muted small text-uppercase">IP Address:</div>
<div class="col-7 font-monospace">{{selectedLog.devip}}</div>
</div>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="row mb-2">
<div class="col-5 fw-bold text-muted small text-uppercase">MAC Address:</div>
<div class="col-7 small font-monospace">{{selectedLog.mac}}</div>
</div>
<h5 class="border-bottom pb-2 mb-3 mt-4"><i class="pi pi-info-circle me-2 text-primary"></i>Event Details
</h5>
<div class="row mb-2">
<div class="col-5 fw-bold text-muted small text-uppercase">Event:</div>
<div class="col-7">{{selectedLog.detail}}</div>
</div>
<div class="row mb-2">
<div class="col-5 fw-bold text-muted small text-uppercase">Source:</div>
<div class="col-7">{{selectedLog.src}}</div>
</div>
<div class="row mb-2">
<div class="col-5 fw-bold text-muted small text-uppercase">Event Time:</div>
<div class="col-7 small">{{selectedLog.eventtime}}</div>
</div>
<div *ngIf="selectedLog.fixtime" class="row mb-2">
<div class="col-5 fw-bold text-muted small text-uppercase">Fixed Time:</div>
<div class="col-7 small text-success fw-bold">{{selectedLog.fixtime}}</div>
</div>
<div class="row mb-2">
<div class="col-5 fw-bold text-muted small text-uppercase">Status:</div>
<div class="col-7">
<c-badge [color]="selectedLog.status == true ? 'success' : 'danger'" style="padding: 5px 10px;">
{{selectedLog.status == true ? 'Fixed' : 'Not Fixed'}}
</c-badge>
</div>
</div>
<h5 class="border-bottom pb-2 mb-3 mt-4"><i class="pi pi-comment me-2 text-primary"></i>Detail Message
</h5>
<div class="bg-light p-3 rounded border italic text-muted text-break"
style="font-size: 0.9rem; line-height: 1.4;">
{{selectedLog.comment || 'No additional details available for this event.'}}
</div>
</div>
</div>
</p-drawer>
</c-card-body>
</c-card>
</c-col>

View file

@ -1,19 +1,9 @@
import { Component, OnInit, ViewEncapsulation,Input } from "@angular/core";
import { Component, OnInit, ViewChild, ViewEncapsulation, Input } from "@angular/core";
import { FormControl } from "@angular/forms";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router, ActivatedRoute } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiRowDetail,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { formatInTimeZone } from "date-fns-tz";
import { takeUntil } from "rxjs/operators";
import { Subject } from "rxjs";
@ -27,10 +17,14 @@ import { Subject } from "rxjs";
})
export class DevLogsComponent implements OnInit {
@Input() component_devid: any=false;
public uid: number;
public uname: string;
public uid!: number;
public uname!: string;
public tz: string = "UTC"
public filterText: string;
public filterText!: string;
public detailsVisible: boolean = false;
public selectedLog: any = null;
@ViewChild('dt') table!: Table;
public filters: any = {
start_time: false,
end_time: false,
@ -73,108 +67,28 @@ export class DevLogsComponent implements OnInit {
}
}
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public Selectedrows: any;
public selected_rows: any[] = [];
public Selectedrows: any[] = [];
public devid: number = 0;
public sorting = {
enabled: true,
multiSorting: true,
};
public bankMultiFilterCtrl: FormControl = new FormControl<string>("");
protected _onDestroy = new Subject<void>();
public campaignOnestart: any;
public campaignOneend: any;
rowDetail: GuiRowDetail = {
enabled: true,
template: (item) => {
return `
<div class='log-detail' style="width: 355px;color:#fff;background-color:${(() => {
if (item.level == "Critical") return "#e55353";
else if (item.level == "Warning") return "#f9b115";
else item.level == "Info";
return "#3399ff";
})()}">
<h1>Device :</h1>
<table>
<tr>
<td>Device Name</td>
<td>${item.name}</td>
</tr>
<tr>
<td>Device IP</td>
<td>${item.devip}</td>
</tr>
<tr>
<td>Device MAC</td>
<td>${item.mac}</td>
</tr>
</table>
<h1 style="margin-top: 10px;">Alert Detail :
</h1>
<table>
<tr>
<td>Event</td>
<td>${item.detail}</td>
</tr>
<tr>
<td>Event Status</td>
<td><span (click)="logger(${item})" style="display:inline-block;background-color:${
item.status ? "green" : "#db4848"
} ;padding: 4px 10px;border-radius: 5px;line-height: 10px;color: rgba(255, 255, 255, 0.87);">${
item.status ? "Fixed" : "Not Fixed"
}</span></td>
</tr>
<tr>
<td>Event Category</td>
<td>${item.eventtype}</td>
</tr>
<tr>
<td>Exec time</td>
<td>${item.eventtime}</td>
</tr>
<tr>
<td>Detail</td>
<td>${item.comment}</td>
</tr>
<tr>
<td>Source</td>
<td>${item.src}</td>
</tr>
</table>
</div>`;
},
};
getSeverityColor(level: string): string {
if (level === "Critical") return "#e55353";
if (level === "Warning") return "#f9b115";
return "#3399ff";
}
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
showLogDetails(log: any) {
this.selectedLog = log;
this.detailsVisible = true;
}
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
ngOnInit(): void {
var _self = this;
if (this.component_devid) {

View file

@ -15,7 +15,10 @@ import {
import { NgxMatSelectSearchModule } from "ngx-mat-select-search";
import { DevLogsRoutingModule } from "./devlogs-routing.module";
import { DevLogsComponent } from "./devlogs.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule } from 'primeng/table';
import { DrawerModule } from 'primeng/drawer';
import { InputTextModule } from 'primeng/inputtext';
import { TooltipModule } from 'primeng/tooltip';
import { MatDatepickerModule } from "@angular/material/datepicker";
import { MatInputModule } from "@angular/material/input";
import { MatFormFieldModule } from "@angular/material/form-field";
@ -34,7 +37,10 @@ import { MatSelectModule } from "@angular/material/select";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
TableModule,
DrawerModule,
InputTextModule,
TooltipModule,
CollapseModule,
BadgeModule,
MatInputModule,

View file

@ -8,22 +8,24 @@
</c-col>
<c-col xs [lg]="9">
<h6 style="text-align: right;">
<button cButton color="success" (click)="openAddDeviceModal()" class="mx-1"
size="sm" style="color: #fff;"><i class="fa-solid fa-plus"></i> Bulk Add </button>
<button cButton color="success" (click)="openAddDeviceModal()" class="mx-1" size="sm"
style="color: #fff;"><i class="fa-solid fa-plus"></i> Bulk Add </button>
<button cButton color="dark" (click)="scanwizard(1,'')" [cModalToggle]="ScannerModal.id" class="mx-1"
size="sm" style="color: #fff;"><i class="fa-solid fa-magnifying-glass"></i> Scan</button>
<button cButton color="primary" (click)="show_exec()" class="mx-1"
size="sm" style="color: #fff;"><i class="fa-solid fa-history"></i> History</button>
<button cButton color="primary" (click)="show_exec()" class="mx-1" size="sm" style="color: #fff;"><i
class="fa-solid fa-history"></i> History</button>
</h6>
</c-col>
</c-row>
</c-card-header>
<c-card-body>
<c-row>
<c-row>
<c-col [lg]="9">
<button cButton color="danger" class="mx-1" size="sm" style="color: #fff;" (click)="filterUpdatable()">{{updates.length}} Updatable
<button cButton color="danger" class="mx-1" size="sm" style="color: #fff;"
(click)="filterUpdatable()">{{updates.length}} Updatable
</button>
<button cButton color="warning" class="mx-1" size="sm" style="color: #fff;" (click)="filterUpgradable()">{{upgrades.length}}
<button cButton color="warning" class="mx-1" size="sm" style="color: #fff;"
(click)="filterUpgradable()">{{upgrades.length}}
Upgradable</button>
<button cButton color="secondary" class="mx-1" size="sm" (click)="clearFilter()">Clear Filter</button>
</c-col>
@ -38,116 +40,162 @@
</c-input-group>
</c-col>
</c-row>
<gui-grid #grid [rowClass]="rowClass" [source]="source" [searching]="searching" [paging]="paging"
[columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection"
(selectedRows)="onSelectedRows($event)" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<button cButton size="sm" variant="ghost" color="primary" (click)="webAccess(item)"
style="padding: 2px 4px; margin-right: 5px; border: none;" cTooltip="Web Access">
<i class="fas fa-globe" style="font-size: 12px;"></i>
</button>
<img *ngIf="item.status=='updating'" width="20px" src="assets/img/loading.svg" />
<i *ngIf="item.status=='updated'" cTooltip="Tooltip text"
style="color: green; margin-right: 3px;font-size: .7em;" class="fa-solid fa-check"></i>
<i *ngIf="item.status=='failed'" cTooltip="Update failed"
style="color: red; margin-right: 3px;font-size: .7em;" class="fa-solid fa-x"></i>
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="CPU Type" field="arch">
<ng-template let-value="item.arch" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Firmware" field="current_firmware">
<ng-template let-value="item.current_firmware" let-item="item" let-index="index">
<div>{{value}}</div>
<i *ngIf="item.update_availble" cTooltip="Firmware Update availble"
class="fa-solid fa-up-long text-primary mx-1"></i>
<i *ngIf="item.upgrade_availble" cTooltip="Device Firmware not Upgraded"
class="fa-solid fa-microchip text-danger mx-1"></i>
</ng-template>
</gui-grid-column>
<gui-grid-column header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="License" field="license" [enabled]="false">
<ng-template let-value="item.license" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Interface" field="interface" [enabled]="false">
<ng-template let-value="item.interface" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created" [enabled]="false">
<ng-template let-value="item.created" let-item="item.id" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Uptime" field="uptime">
<ng-template let-value="item.uptime" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created" [enabled]="false">
<ng-template let-value="item.created" let-item="item.id" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column align="center" [cellEditing]="false" [sorting]="false" header="Action">
<ng-template let-value="value" let-item="item">
<button size="sm" shape="rounded-0" variant="outline" cButton color="primary" (click)="show_detail(item)"
style="border: none;padding: 4px 7px;"><i class="fa-regular fa-eye"></i><small> Details</small>
</button>
<button color="primary" shape="rounded-0" variant="ghost" style="padding: 4px 7px;"
[matMenuTriggerFor]="menu" cButton>
<i class="fa-solid fa-bars"></i>
</button>
<mat-menu #menu="matMenu">
<div cListGroup>
<li cListGroupItem [active]="false" color="dark">Actions Menu</li>
<button size="sm" (click)="single_device_action(item,'edit')" style="padding: 4px 7px;"
cListGroupItem><i class="fa-solid fa-pencil"></i><small>
Edit Device</small></button>
<button size="sm" (click)="single_device_action(item,'firmware')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-magnifying-glass"></i><small>
Check Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'update')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-upload"></i><small>
Update Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'upgrade')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-microchip"></i><small>
Upgrade Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'devlogs')" style="padding: 4px 7px;"
cListGroupItem><i class="fa-regular fa-rectangle-list"></i><small>
Device Logs</small></button>
<button size="sm" (click)="single_device_action(item,'logauth')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-regular fa-clock"></i><small>
Show Auth Logs</small></button>
<button size="sm" (click)="single_device_action(item,'logacc')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-table-list"></i><small>
Show Acc Logs</small></button>
<button size="sm" (click)="single_device_action(item,'backup')" style="padding: 4px 7px;"
cListGroupItem><i class="text-success fa-solid fa-database"></i><small>
Show Backups</small></button>
<button size="sm" (click)="single_device_action(item,'delete')" style="padding: 4px 7px;"
cListGroupItem><i class="text-danger fa-solid fa-trash"></i><small>
Delete Device</small></button>
<div class="mb-1 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search devices..."
(input)="applyFilterGlobal($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[(selection)]="selected_rows" (selectionChange)="onSelectionChange($event)"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name','ip','mac','arch']">
<ng-template pTemplate="header">
<tr>
<th style="width: 3rem" pResizableColumn>
<p-tableHeaderCheckbox></p-tableHeaderCheckbox>
</th>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</div>
</mat-menu>
</ng-template>
</gui-grid-column>
</gui-grid>
</th>
<th pSortableColumn="arch" pResizableColumn>
<div class="justify-between">
<span>CPU Type</span>
<span>
<p-sortIcon field="arch"></p-sortIcon>
<p-columnFilter type="text" field="arch" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="current_firmware" pResizableColumn>
<div class="justify-between">
<span>Current Firmware</span>
<span>
<p-sortIcon field="current_firmware"></p-sortIcon>
<p-columnFilter type="text" field="current_firmware" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="ip" pResizableColumn>
<div class="justify-between">
<span>IP Address</span>
<span>
<p-sortIcon field="ip"></p-sortIcon>
<p-columnFilter type="text" field="ip" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="mac" pResizableColumn>
<div class="justify-between">
<span>MAC Address</span>
<span>
<p-sortIcon field="mac"></p-sortIcon>
<p-columnFilter type="text" field="mac" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="uptime" pResizableColumn>
<div class="justify-between">
<span>Uptime</span>
<span>
<p-sortIcon field="uptime"></p-sortIcon>
<p-columnFilter type="text" field="uptime" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 120px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr [ngClass]="item.status === 'updating' ? 'row-highlighted' : ''">
<td>
<p-tableCheckbox [value]="item"></p-tableCheckbox>
</td>
<td>
<button cButton size="sm" variant="ghost" color="primary" (click)="webAccess(item)"
style="padding: 2px 4px; margin-right: 5px; border: none;" cTooltip="Web Access">
<i class="fas fa-globe" style="font-size: 12px;"></i>
</button>
<img *ngIf="item.status=='updating'" width="20px" src="assets/img/loading.svg" />
<i *ngIf="item.status=='updated'" cTooltip="Updated"
style="color: green; margin-right: 3px;font-size: .7em;" class="fa-solid fa-check"></i>
<i *ngIf="item.status=='failed'" cTooltip="Update failed"
style="color: red; margin-right: 3px;font-size: .7em;" class="fa-solid fa-x"></i>
{{item.name}}
</td>
<td>{{item.arch}}</td>
<td>
<div style="display:inline-block">{{item.current_firmware}}</div>
<i *ngIf="item.update_availble" cTooltip="Firmware Update available"
class="fa-solid fa-up-long text-primary mx-1"></i>
<i *ngIf="item.upgrade_availble" cTooltip="Device Firmware not Upgraded"
class="fa-solid fa-microchip text-danger mx-1"></i>
</td>
<td>{{item.ip}}</td>
<td>{{item.mac}}</td>
<td>{{item.uptime}}</td>
<td class="text-center">
<button size="sm" shape="rounded-0" variant="outline" cButton color="primary"
(click)="show_detail(item)" style="border: none;padding: 4px 7px;"><i
class="fa-regular fa-eye"></i><small> Details</small>
</button>
<button color="primary" shape="rounded-0" variant="ghost" style="padding: 4px 7px;"
[matMenuTriggerFor]="menu" cButton>
<i class="fa-solid fa-bars"></i>
</button>
<mat-menu #menu="matMenu">
<div cListGroup>
<li cListGroupItem [active]="false" color="dark">Actions Menu</li>
<button size="sm" (click)="single_device_action(item,'edit')" style="padding: 4px 7px;"
cListGroupItem><i class="fa-solid fa-pencil"></i><small>
Edit Device</small></button>
<button size="sm" (click)="single_device_action(item,'firmware')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-magnifying-glass"></i><small>
Check Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'update')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-upload"></i><small>
Update Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'upgrade')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-microchip"></i><small>
Upgrade Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'devlogs')" style="padding: 4px 7px;"
cListGroupItem><i class="fa-regular fa-rectangle-list"></i><small>
Device Logs</small></button>
<button size="sm" (click)="single_device_action(item,'logauth')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-regular fa-clock"></i><small>
Show Auth Logs</small></button>
<button size="sm" (click)="single_device_action(item,'logacc')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-table-list"></i><small>
Show Acc Logs</small></button>
<button size="sm" (click)="single_device_action(item,'backup')" style="padding: 4px 7px;"
cListGroupItem><i class="text-success fa-solid fa-database"></i><small>
Show Backups</small></button>
<button size="sm" (click)="single_device_action(item,'delete')" style="padding: 4px 7px;"
cListGroupItem><i class="text-danger fa-solid fa-trash"></i><small>
Delete Device</small></button>
</div>
</mat-menu>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="8">No devices found.</td>
</tr>
</ng-template>
</p-table>
<c-navbar *ngIf="rows.length!= 0" class="bg-light" colorScheme="light" expand="lg">
<c-container [fluid]="true">
<a cNavbarBrand href="javascript:;">
@ -292,54 +340,105 @@
</select>
</c-input-group>
</div>
<gui-grid [autoResizeWidth]="true" *ngIf="ExecutedDataModalVisible" [searching]="searching"
[source]="filteredExecutedData" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [paging]="paging">
<gui-grid-column header="Type" field="task_type" [width]="100">
<ng-template let-value="item['task_type']" let-item="item" let-index="index">
<i *ngIf="item.task_type === 'ip-scan'" class="fas fa-search" style="margin-right: 3px; color: #0d6efd;"></i>
<i *ngIf="item.task_type === 'bulk-add'" class="fas fa-plus-circle" style="margin-right: 3px; color: #198754;"></i>
<span style="font-size: 11px;">{{getTaskTypeLabel(value)}}</span>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Time" field="time" [width]="220">
<ng-template let-value="value" let-item="item" let-index="index">
<div style="font-size: 10px; line-height: 1.3;">
<div><strong>Start:</strong> {{item.started}}</div>
<div><strong>End:</strong> {{item.ended}}</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Details" field="details">
<ng-template let-value="value" let-item="item" let-index="index">
<div *ngIf="item.task_type === 'ip-scan'" style="font-size: 12px; line-height: 1.3;">
<div>{{item.start_ip}} - {{item.end_ip}}</div>
<div>User: {{item.username}}</div>
</div>
<div *ngIf="item.task_type === 'bulk-add'" style="font-size: 12px; line-height: 1.3;">
<div>{{item.device_count}} devices</div>
<div style="word-break: break-all;">{{item.task_id}}</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Results" field="results" align="center" [width]="70">
<ng-template let-value="value" let-item="item" let-index="index">
<div style="font-size: 11px;">
<span style="color: green;">✓ {{item.success_count}}</span>
<span *ngIf="item.failed_count > 0" style="color: red; margin-left: 5px;">✗ {{item.failed_count}}</span>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" field="actions" align="center" [width]="160">
<ng-template let-value="value" let-item="item" let-index="index">
<button (click)="showTaskDetails(item)" color="info" size="sm" cButton class="me-1">
<i class="fas fa-eye"></i> Details
</button>
<button (click)="exportToCsv(item.result)" color="primary" size="sm" cButton>
<i class="fas fa-download"></i> CSV
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search history..."
(input)="dtTask.filterGlobal($any($event.target).value, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dtTask [value]="filteredExecutedData" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['task_id', 'username', 'start_ip', 'end_ip']">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="index" style="width: 5rem" pResizableColumn>
<div class="justify-between">
<span>#No</span>
<p-sortIcon field="index"></p-sortIcon>
</div>
</th>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="createdC" pResizableColumn>
<div class="justify-between">
<span>Execution Time</span>
<span>
<p-sortIcon field="createdC"></p-sortIcon>
<p-columnFilter type="text" field="createdC" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="username" pResizableColumn>
<div class="justify-between">
<span>User</span>
<span>
<p-sortIcon field="username"></p-sortIcon>
<p-columnFilter type="text" field="username" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 100px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>
<div class="d-flex align-items-center gap-1">
<i *ngIf="item.task_type === 'ip-scan'" class="fas fa-search text-primary"></i>
<i *ngIf="item.task_type === 'bulk-add'" class="fas fa-plus-circle text-success"></i>
<span class="small">{{getTaskTypeLabel(item.task_type)}}</span>
</div>
</td>
<td class="small">
<div><strong>S:</strong> {{item.started}}</div>
<div><strong>E:</strong> {{item.ended}}</div>
</td>
<td>
<div *ngIf="item.task_type === 'ip-scan'" class="small">
<div>{{item.start_ip}} - {{item.end_ip}}</div>
<div class="text-muted">User: {{item.username}}</div>
</div>
<div *ngIf="item.task_type === 'bulk-add'" class="small">
<div>{{item.device_count}} devices</div>
<div class="text-muted text-truncate" style="max-width: 150px;">{{item.task_id}}</div>
</div>
</td>
<td class="text-center">
<div class="d-flex flex-column align-items-center">
<span class="text-success small">✓ {{item.success_count}}</span>
<span *ngIf="item.failed_count > 0" class="text-danger small">✗ {{item.failed_count}}</span>
</div>
</td>
<td class="text-center">
<button (click)="showTaskDetails(item)" color="info" size="sm" cButton class="me-1"
style="padding: 2px 6px;">
<i class="fas fa-eye"></i> Details
</button>
<button (click)="exportToCsv(item.result)" color="primary" size="sm" cButton style="padding: 2px 6px;">
<i class="fas fa-download"></i> CSV
</button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="5" class="text-center p-4">No task history found.</td>
</tr>
</ng-template>
</p-table>
</c-modal-body>
<c-modal-footer>
<button (click)="ExecutedDataModalVisible=!ExecutedDataModalVisible" cButton color="secondary">
@ -453,7 +552,8 @@
<c-modal-body>
<div *ngIf="addDeviceStep === 1">
<h6>Upload CSV File</h6>
<p>Please upload a CSV file containing device information with columns: IP Address, Username, Password, API Port</p>
<p>Please upload a CSV file containing device information with columns: IP Address, Username, Password, API Port
</p>
<input type="file" accept=".csv" (change)="onFileSelected($event)" class="form-control mb-3">
<div *ngIf="csvPreview.length > 0">
<h6>Preview (First 3 rows):</h6>
@ -522,7 +622,8 @@
</div>
</c-modal-body>
<c-modal-footer>
<button *ngIf="addDeviceStep === 1" cButton color="primary" (click)="uploadDevices()" [disabled]="!isValidMapping()">
<button *ngIf="addDeviceStep === 1" cButton color="primary" (click)="uploadDevices()"
[disabled]="!isValidMapping()">
Add Devices
</button>
<button *ngIf="addDeviceStep === 3" cButton color="success" (click)="closeAddDeviceModal()">
@ -557,13 +658,14 @@
</c-modal>
<!-- Task Details Modal -->
<c-modal #TaskDetailsModal backdrop="static" size="xl" [(visible)]="detailsModalVisible" id="TaskDetailsModal"
style="z-index: 1060; backdrop-filter: blur(2px);">
<c-modal #TaskDetailsModal backdrop="static" size="xl" [(visible)]="detailsModalVisible" id="TaskDetailsModal"
style="z-index: 1060; backdrop-filter: blur(2px);">
<c-modal-header style="background: rgba(255,255,255,0.95); backdrop-filter: blur(10px);">
<h5 cModalTitle>{{getTaskTypeLabel(selectedTaskDetails?.task_type)}} Results</h5>
<button (click)="closeDetailsModal()" cButtonClose></button>
</c-modal-header>
<c-modal-body *ngIf="selectedTaskDetails" style="background: rgba(255,255,255,0.98); backdrop-filter: blur(10px); max-height: 70vh; overflow-y: auto;">
<c-modal-body *ngIf="selectedTaskDetails"
style="background: rgba(255,255,255,0.98); backdrop-filter: blur(10px); max-height: 70vh; overflow-y: auto;">
<div class="mb-3">
<c-row>
<c-col md="6">
@ -587,8 +689,8 @@
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Detailed Results:</h6>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" placeholder="Search IP or error..."
[(ngModel)]="detailsSearchTerm" (input)="onDetailsSearch()">
<input type="text" class="form-control form-control-sm" placeholder="Search IP or error..."
[(ngModel)]="detailsSearchTerm" (input)="onDetailsSearch()">
</div>
</div>
<table class="table table-striped table-hover table-responsive">
@ -620,19 +722,20 @@
<nav *ngIf="getTotalDetailsPages() > 1" class="mt-3">
<ul class="pagination pagination-sm justify-content-center">
<li class="page-item" [class.disabled]="detailsCurrentPage === 1">
<button class="page-link" (click)="onDetailsPageChange(detailsCurrentPage - 1)" [disabled]="detailsCurrentPage === 1">
<button class="page-link" (click)="onDetailsPageChange(detailsCurrentPage - 1)"
[disabled]="detailsCurrentPage === 1">
Previous
</button>
</li>
<li class="page-item" *ngFor="let page of [].constructor(getTotalDetailsPages()); let i = index"
[class.active]="detailsCurrentPage === i + 1">
<li class="page-item" *ngFor="let page of [].constructor(getTotalDetailsPages()); let i = index"
[class.active]="detailsCurrentPage === i + 1">
<button class="page-link" (click)="onDetailsPageChange(i + 1)">
{{i + 1}}
</button>
</li>
<li class="page-item" [class.disabled]="detailsCurrentPage === getTotalDetailsPages()">
<button class="page-link" (click)="onDetailsPageChange(detailsCurrentPage + 1)"
[disabled]="detailsCurrentPage === getTotalDetailsPages()">
<button class="page-link" (click)="onDetailsPageChange(detailsCurrentPage + 1)"
[disabled]="detailsCurrentPage === getTotalDetailsPages()">
Next
</button>
</li>

View file

@ -10,21 +10,9 @@ import { dataProvider } from "../../providers/mikrowizard/data";
import { Router, ActivatedRoute } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiGridComponent,
GuiGridApi,
GuiRowClass,
GuiSearching,
GuiSelectedRow,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { ToasterComponent } from "@coreui/angular";
ToasterComponent
} from "@coreui/angular";
import { Table } from 'primeng/table';
import { AppToastComponent } from "../toast-simple/toast.component";
import { formatInTimeZone } from "date-fns-tz";
@ -34,9 +22,9 @@ import { formatInTimeZone } from "date-fns-tz";
templateUrl: "devices.component.html",
})
export class DevicesComponent implements OnInit, OnDestroy {
public uid: number;
public uname: string;
public tz: string;
public uid!: number;
public uname!: string;
public tz!: string;
public ispro:boolean=false;
constructor(
@ -69,14 +57,11 @@ export class DevicesComponent implements OnInit, OnDestroy {
return value !== undefined && value !== null && value !== "";
}
}
@ViewChild("grid", { static: true }) gridComponent: GuiGridComponent;
@ViewChild("dt") table!: Table;
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
public source: Array<any> = [];
public originalSource: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public Selectedrows: any;
public upgrades: any = [];
public updates: any = [];
public scanwizard_step: number = 1;
@ -117,6 +102,10 @@ export class DevicesComponent implements OnInit, OnDestroy {
public uploadResult = { success: 0, failed: 0, resultFile: null };
public currentTaskId: string = '';
public statusCheckTimer: any;
public selected_rows: any[] = []; // Used by p-table selection
public Selectedrows: any[] = []; // Legacy ID array used by actions
public rows: any = []; // For legacy internal use
toasterForm = {
autohide: true,
@ -125,7 +114,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
fade: true,
closeButton: true,
};
rowClass: GuiRowClass = {
rowClass: any = {
class: "row-highlighted",
};
public sorting = {
@ -134,38 +123,36 @@ export class DevicesComponent implements OnInit, OnDestroy {
};
public ip_scanner: any;
searching: GuiSearching = {
enabled: true,
placeholder: "Search Devices",
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
public paging: GuiPaging = {
public paging: any = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
public columnMenu: any = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
public infoPanel: any = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
onSelectionChange(value: any[]) {
this.selected_rows = value;
this.Selectedrows = value.map(item => item.id);
this.rows = value; // For legacy compat if needed
}
ngOnInit(): void {
this.selected_group = Number(this.route.snapshot.paramMap.get("id"));
this.initGridTable();
@ -177,8 +164,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
}
single_device_action(dev: any, action: string) {
const api: GuiGridApi = this.gridComponent.api;
api.unselectAll();
this.selected_rows = [];
this.Selectedrows = [dev["id"]];
switch (action) {
case "edit":
@ -273,10 +259,7 @@ export class DevicesComponent implements OnInit, OnDestroy {
});
}
onSelectedRows(rows: Array<GuiSelectedRow>): void {
this.rows = rows;
this.Selectedrows = rows.map((m: GuiSelectedRow) => m.source.id);
}
// Removed legacy onSelectedRows
checkvalid(type: string): boolean {
var rx =

View file

@ -22,7 +22,11 @@ import {
import { MatMenuModule } from "@angular/material/menu";
import { DevicesRoutingModule } from "./devices-routing.module";
import { DevicesComponent } from "./devices.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PrimeNGTableModule } from 'primeng/table';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { MultiSelectModule } from 'primeng/multiselect';
import { TooltipModule as PrimeNGTooltipModule } from 'primeng/tooltip';
@NgModule({
imports: [
@ -34,7 +38,6 @@ import { GuiGridModule } from "@generic-ui/ngx-grid";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
NavbarModule,
CollapseModule,
DropdownModule,
@ -46,6 +49,11 @@ import { GuiGridModule } from "@generic-ui/ngx-grid";
MatMenuModule,
TooltipModule,
TableModule,
PrimeNGTableModule,
CheckboxModule,
InputTextModule,
MultiSelectModule,
PrimeNGTooltipModule
],
declarations: [DevicesComponent],
})

View file

@ -13,82 +13,119 @@
</c-row>
</c-card-header>
<c-card-body>
<gui-grid [source]="source" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Devices" field="array_agg" align="CENTER">
<ng-template let-value="item.array_agg" let-item="item" let-index="index">
<ng-container *ngIf="item.id==1 ; then Default;else NotDefault">
</ng-container>
<ng-template #Default>
<c-badge color="info"><i class="fa-solid fa-network-wired me-1"></i>All Devices</c-badge>
</ng-template>
<ng-template #NotDefault>
<c-badge color="info" *ngIf="value[0]==null && item.id!=1"
[cTooltip]="'No devices assigned'" style="cursor: help">
<i class="fa-solid fa-server me-1"></i>0
</c-badge>
<c-badge color="info" *ngIf="value[0]!=null"
[cTooltip]="getDevicesTooltip(item)"
[cTooltipPlacement]="'top'" style="cursor: help">
<i class="fa-solid fa-server me-1"></i>{{value.length}}
</c-badge>
</ng-template>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Create Time" field="created">
<ng-template let-value="item.created" let-item="item" let-index="index">
{{formatCreateTime(value)}}
</ng-template>
</gui-grid-column>
<div class="mb-1 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search groups..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<gui-grid-column header="Users" field="assigned_users" width="80" align="CENTER">
<ng-template let-value="item.assigned_users" let-item="item" let-index="index">
<c-badge color="info"
[cTooltip]="getUsersTooltip(value)"
[cTooltipPlacement]="'top'"
style="cursor: help">
<i class="fa-solid fa-users me-1"></i>{{value.length}}
</c-badge>
</ng-template>
</gui-grid-column>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name']" [loading]="loading">
<gui-grid-column align="center" [cellEditing]="false" [sorting]="false" header="Action">
<ng-template let-value="value" let-item="item">
<button size="sm" shape="rounded-0" variant="outline" cButton color="primary" (click)="show_members(item.id)"
style="border: none;padding: 4px 7px;"><i class="fa-regular fa-eye"></i><small> View devices</small>
</button>
<button color="primary" shape="rounded-0" variant="ghost" style="padding: 4px 7px;"
[matMenuTriggerFor]="menu" cButton>
<i class="fa-solid fa-bars"></i>
</button>
<mat-menu #menu="matMenu">
<div cListGroup>
<li cListGroupItem [active]="false" color="dark">Actions Menu</li>
<button size="sm" (click)="editAddGroup(item,'showedit')" style="padding: 4px 7px;"
[disabled]="item.id==1" cListGroupItem><i class="fa-solid fa-pencil"></i><small>
Edit Group</small></button>
<button size="sm" (click)="manageUsers(item)" style="padding: 4px 7px;"
cListGroupItem><i class="fa-solid fa-users-gear"></i><small>
Manage Users</small></button>
<button size="sm" (click)="groupFirmwareAction(item, 'update')" style="padding: 4px 7px;"
cListGroupItem><i class="text-success fa-solid fa-upload"></i><small>
Update Firmware</small></button>
<button size="sm" (click)="groupFirmwareAction(item, 'upgrade')" style="padding: 4px 7px;"
cListGroupItem><i class="text-secondary fa-solid fa-microchip"></i><small>
Upgrade Firmware</small></button>
<button size="sm" (click)="show_delete_group(item)" style="padding: 4px 7px;"
[disabled]="item.id==1" cListGroupItem><i class="text-danger fa-solid fa-trash"></i><small>
Delete Group</small></button>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</mat-menu>
</ng-template>
</gui-grid-column>
</gui-grid>
</th>
<th pSortableColumn="description" pResizableColumn>
<div class="justify-between">
<span>Member Devices</span>
<span>
<p-sortIcon field="description"></p-sortIcon>
<p-columnFilter type="text" field="description" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="pair_type" pResizableColumn>
<div class="justify-between">
<span>Created</span>
<span>
<p-sortIcon field="pair_type"></p-sortIcon>
<p-columnFilter type="text" field="pair_type" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="member_count" pResizableColumn>
<div class="justify-between">
<span>Member Users</span>
<span>
<p-sortIcon field="member_count"></p-sortIcon>
<p-columnFilter type="numeric" field="member_count" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 120px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><strong>{{item.name}}</strong></td>
<td class="text-center">
<ng-container *ngIf="item.id==1 ; then Default; else NotDefault"></ng-container>
<ng-template #Default>
<h6><c-badge color="info"><i class="fa-solid fa-network-wired me-1"></i>All Devices</c-badge></h6>
</ng-template>
<ng-template #NotDefault>
<c-badge color="info" *ngIf="(!item.array_agg || item.array_agg[0]==null) && item.id!=1"
pTooltip="No devices assigned" style="cursor: help">
<i class="fa-solid fa-server me-1"></i>0
</c-badge>
<h6><c-badge color="info" *ngIf="item.array_agg && item.array_agg[0]!=null"
[pTooltip]="getDevicesTooltip(item)" style="cursor: help">
<i class="fa-solid fa-server me-1"></i>{{item.array_agg.length}}
</c-badge></h6>
</ng-template>
</td>
<td>{{formatCreateTime(item.created)}}</td>
<td class="text-center">
<h6><c-badge color="info" [pTooltip]="getUsersTooltip(item.assigned_users)" style="cursor: help">
<i class="fa-solid fa-users me-1"></i>{{item.assigned_users?.length || 0}}
</c-badge></h6>
</td>
<td class="text-center">
<div class="d-flex justify-content-center">
<button size="sm" variant="outline" cButton color="primary" (click)="show_members(item.id)"
style="border: none;padding: 4px 7px;" pTooltip="View devices"><i class="fa-regular fa-eye"></i>
</button>
<button color="primary" variant="ghost" style="padding: 4px 7px;" [matMenuTriggerFor]="menu" cButton>
<i class="fa-solid fa-bars"></i>
</button>
<mat-menu #menu="matMenu">
<div cListGroup>
<li cListGroupItem [active]="false" color="dark">Actions Menu</li>
<button size="sm" (click)="editAddGroup(item,'showedit')" style="padding: 4px 7px;"
[disabled]="item.id==1" cListGroupItem><i class="fa-solid fa-pencil"></i><small>
Edit Group</small></button>
<button size="sm" (click)="manageUsers(item)" style="padding: 4px 7px;" cListGroupItem><i
class="fa-solid fa-users-gear"></i><small>
Manage Users</small></button>
<button size="sm" (click)="groupFirmwareAction(item, 'update')" style="padding: 4px 7px;"
cListGroupItem><i class="text-success fa-solid fa-upload"></i><small>
Update Firmware</small></button>
<button size="sm" (click)="groupFirmwareAction(item, 'upgrade')" style="padding: 4px 7px;"
cListGroupItem><i class="text-secondary fa-solid fa-microchip"></i><small>
Upgrade Firmware</small></button>
<button size="sm" (click)="show_delete_group(item)" style="padding: 4px 7px;"
[disabled]="item.id==1" cListGroupItem><i class="text-danger fa-solid fa-trash"></i><small>
Delete Group</small></button>
</div>
</mat-menu>
</div>
</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
@ -134,30 +171,54 @@
<p class="mb-0">Click "Add Devices" to start adding devices to this group</p>
</div>
<div *ngIf="groupMembers.length > 0">
<gui-grid [autoResizeWidth]="true" [searching]="searching" [source]="groupMembers" [columnMenu]="columnMenu"
[sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection"
(selectedRows)="onSelectedRowsMembers($event)" [paging]="paging">
<gui-grid-column header="Device Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid fa-server me-2 text-primary"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="100" field="action" align="CENTER">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_from_group(item.id)" title="Remove from group">
<i class="fa-solid fa-times"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search members..."
(input)="applyFilterMembers($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dtMembers [value]="groupMembers" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm" [(selection)]="MemberRows"
(selectionChange)="onSelectedRowsMembers($event)" [globalFilterFields]="['name', 'ip']">
<ng-template pTemplate="header">
<tr>
<th style="width: 4rem"><p-tableHeaderCheckbox></p-tableHeaderCheckbox></th>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<p-sortIcon field="name"></p-sortIcon>
</div>
</th>
<th pSortableColumn="mac" pResizableColumn>
<div class="justify-between">
<span>MAC Address</span>
<p-sortIcon field="mac"></p-sortIcon>
</div>
</th>
<th style="width: 100px" class="text-center">Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><p-tableCheckbox [value]="item"></p-tableCheckbox></td>
<td>
<div class="d-flex align-items-center">
<i class="fa-solid fa-server me-2 text-primary"></i>
<strong>{{item.name}}</strong>
</div>
</td>
<td><c-badge color="secondary">{{item.ip}}</c-badge></td>
<td class="text-center">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_from_group(item.id)"
pTooltip="Remove from group">
<i class="fa-solid fa-times"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
</c-card-body>
</c-card>
@ -172,7 +233,8 @@
</c-modal-footer>
</c-modal>
<c-modal #NewMemberModal [backdrop]="true" size="xl" [(visible)]="NewMemberModalVisible" id="NewMemberModal" style="z-index: 1060; backdrop-filter: blur(2px);">
<c-modal #NewMemberModal [backdrop]="true" size="xl" [(visible)]="NewMemberModalVisible" id="NewMemberModal"
style="z-index: 1060; backdrop-filter: blur(2px);">
<c-modal-header class="bg-success text-white">
<h5 cModalTitle><i class="fa-solid fa-plus-circle me-2"></i>Add Devices to Group</h5>
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
@ -189,7 +251,8 @@
<!-- Available Devices -->
<c-card>
<c-card-header>
<h6 class="mb-0"><i class="fa-solid fa-server me-2"></i>Available Devices ({{availbleMembers.length}} total)</h6>
<h6 class="mb-0"><i class="fa-solid fa-server me-2"></i>Available Devices ({{availbleMembers.length}} total)
</h6>
</c-card-header>
<c-card-body class="p-0">
<div *ngIf="availbleMembers.length === 0" class="text-center p-4 text-muted">
@ -198,28 +261,45 @@
<p class="mb-0">No available devices to add to this group</p>
</div>
<div *ngIf="availbleMembers.length > 0">
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [paging]="paging">
<gui-grid-column header="Device Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fa-solid fa-server me-2 text-primary"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<small class="text-muted">{{value}}</small>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end w-100">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search available..."
(input)="applyFilterNewMembers($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dtNewMembers [value]="availbleMembers" [paginator]="true" [rows]="10" [resizableColumns]="true"
columnResizeMode="expand" [showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped" [(selection)]="NewMemberRows"
(selectionChange)="onSelectedRowsNewMembers($event)" [globalFilterFields]="['name', 'ip', 'mac']">
<ng-template pTemplate="header">
<tr>
<th style="width: 4rem" pResizableColumn><p-tableHeaderCheckbox></p-tableHeaderCheckbox></th>
<th pSortableColumn="name" pResizableColumn>Device Name <p-sortIcon
field="name"></p-sortIcon><p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="ip" pResizableColumn>IP Address <p-sortIcon field="ip"></p-sortIcon><p-columnFilter
type="text" field="ip" display="menu" class="ms-auto" /></th>
<th pSortableColumn="mac" pResizableColumn>MAC Address <p-sortIcon
field="mac"></p-sortIcon><p-columnFilter type="text" field="mac" display="menu" class="ms-auto" />
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><p-tableCheckbox [value]="item"></p-tableCheckbox></td>
<td>
<div class="d-flex align-items-center">
<i class="fa-solid fa-server me-2 text-primary"></i>
<strong>{{item.name}}</strong>
</div>
</td>
<td><c-badge color="secondary">{{item.ip}}</c-badge></td>
<td><small class="text-muted">{{item.mac}}</small></td>
</tr>
</ng-template>
</p-table>
</div>
</c-card-body>
</c-card>
@ -268,7 +348,8 @@
</button>
</c-modal-footer>
</c-modal>
<c-modal #UserManagementModal backdrop="static" size="xl" [(visible)]="UserManagementModalVisible" id="UserManagementModal">
<c-modal #UserManagementModal backdrop="static" size="xl" [(visible)]="UserManagementModalVisible"
id="UserManagementModal">
<c-modal-header>
<h5 cModalTitle><i class="fa-solid fa-users-gear me-2"></i>User Permissions - {{selectedGroup?.name}}</h5>
<button [cModalToggle]="UserManagementModal.id" cButtonClose></button>
@ -283,18 +364,11 @@
<c-col md="4">
<div class="search-select-wrapper">
<label class="search-label">User</label>
<input cFormControl
[(ngModel)]="userSearch"
(input)="filterUsers($event)"
(focus)="showUserDropdown = true"
(blur)="hideUserDropdown()"
placeholder="Search and select user..."
class="search-input compact-select"
autocomplete="off" />
<input cFormControl [(ngModel)]="userSearch" (input)="filterUsers($event)"
(focus)="showUserDropdown = true" (blur)="hideUserDropdown()" placeholder="Search and select user..."
class="search-input compact-select" autocomplete="off" />
<div *ngIf="showUserDropdown && filteredUsers.length > 0" class="search-dropdown">
<div *ngFor="let user of filteredUsers"
class="search-option"
(mousedown)="selectUser(user)">
<div *ngFor="let user of filteredUsers" class="search-option" (mousedown)="selectUser(user)">
{{user.username}} ({{user.first_name}} {{user.last_name}})
</div>
</div>
@ -306,28 +380,24 @@
<c-col md="4">
<div class="search-select-wrapper">
<label class="search-label">Permission</label>
<input cFormControl
[(ngModel)]="permissionSearch"
(input)="filterPermissions($event)"
(focus)="showPermissionDropdown = true"
(blur)="hidePermissionDropdown()"
placeholder="Search and select permission..."
class="search-input compact-select"
autocomplete="off" />
<input cFormControl [(ngModel)]="permissionSearch" (input)="filterPermissions($event)"
(focus)="showPermissionDropdown = true" (blur)="hidePermissionDropdown()"
placeholder="Search and select permission..." class="search-input compact-select" autocomplete="off" />
<div *ngIf="showPermissionDropdown && filteredPermissions.length > 0" class="search-dropdown">
<div *ngFor="let perm of filteredPermissions"
class="search-option"
<div *ngFor="let perm of filteredPermissions" class="search-option"
(mousedown)="selectPermission(perm)">
{{perm.name}}
</div>
</div>
<div *ngIf="showPermissionDropdown && filteredPermissions.length === 0 && permissionSearch" class="search-no-results">
<div *ngIf="showPermissionDropdown && filteredPermissions.length === 0 && permissionSearch"
class="search-no-results">
No permissions found
</div>
</div>
</c-col>
<c-col md="4" class="d-flex align-items-end">
<button cButton color="success" (click)="addUserPermission()" [disabled]="!selectedUser || !selectedPermission">
<button cButton color="success" (click)="addUserPermission()"
[disabled]="!selectedUser || !selectedPermission">
<i class="fa-solid fa-plus me-1"></i>Add Permission
</button>
</c-col>
@ -336,7 +406,8 @@
</c-card>
<c-card>
<c-card-header class="bg-light">
<h6 class="mb-0"><i class="fa-solid fa-list-check me-2"></i>Current Permissions ({{selectedGroup?.assigned_users?.length || 0}} users)</h6>
<h6 class="mb-0"><i class="fa-solid fa-list-check me-2"></i>Current Permissions
({{selectedGroup?.assigned_users?.length || 0}} users)</h6>
</c-card-header>
<c-card-body class="p-0">
<div *ngIf="selectedGroup?.assigned_users?.length === 0" class="text-center p-4 text-muted">
@ -369,14 +440,12 @@
</td>
<td>
<div class="btn-group" role="group">
<button cButton color="warning" size="sm" variant="outline"
(click)="editUserPermission(user)" title="Change Permission"
[disabled]="selectedGroup.id === 1 && user.username === 'mikrowizard'">
<button cButton color="warning" size="sm" variant="outline" (click)="editUserPermission(user)"
title="Change Permission" [disabled]="selectedGroup.id === 1 && user.username === 'mikrowizard'">
<i class="fa-solid fa-edit"></i>
</button>
<button cButton color="danger" size="sm" variant="outline"
(click)="removeUserPermission(user)" title="Remove Permission"
[disabled]="selectedGroup.id === 1 && user.username === 'mikrowizard'">
<button cButton color="danger" size="sm" variant="outline" (click)="removeUserPermission(user)"
title="Remove Permission" [disabled]="selectedGroup.id === 1 && user.username === 'mikrowizard'">
<i class="fa-solid fa-trash"></i>
</button>
</div>
@ -402,7 +471,8 @@
</c-modal-header>
<c-modal-body>
<div class="mb-3">
<label class="form-label">Current Permission: <c-badge [color]="getPermissionColor(editingUser?.perm_name)">{{editingUser?.perm_name}}</c-badge></label>
<label class="form-label">Current Permission: <c-badge
[color]="getPermissionColor(editingUser?.perm_name)">{{editingUser?.perm_name}}</c-badge></label>
</div>
<div class="mb-3">
<label class="form-label">New Permission</label>
@ -430,7 +500,9 @@
<c-modal-body>
<div class="text-center">
<i class="fa-solid fa-exclamation-triangle fa-3x text-warning mb-3"></i>
<p>Are you sure you want to remove <strong>{{removingUser?.username}}</strong>'s permission from group <strong>{{selectedGroup?.name}}</strong>?</p>
<p>Are you sure you want to remove <strong>{{removingUser?.username}}</strong>'s permission from group
<strong>{{selectedGroup?.name}}</strong>?
</p>
<p class="text-muted small">This action cannot be undone.</p>
</div>
</c-modal-body>
@ -451,13 +523,17 @@
<c-modal-body>
<div class="text-center">
<i class="fa-solid fa-exclamation-triangle fa-3x text-warning mb-3"></i>
<p>Are you sure you want to <strong>{{firmwareAction}}</strong> firmware for all devices in group <strong>{{selectedGroupForFirmware?.name}}</strong>?</p>
<p class="text-muted small">This action will affect all devices in this group and may take some time to complete.</p>
<p>Are you sure you want to <strong>{{firmwareAction}}</strong> firmware for all devices in group
<strong>{{selectedGroupForFirmware?.name}}</strong>?
</p>
<p class="text-muted small">This action will affect all devices in this group and may take some time to complete.
</p>
</div>
</c-modal-body>
<c-modal-footer>
<button cButton color="primary" (click)="confirmGroupFirmwareAction()">
<i class="fa-solid fa-{{firmwareAction === 'update' ? 'upload' : 'microchip'}} me-1"></i>{{firmwareAction | titlecase}} Firmware
<i class="fa-solid fa-{{firmwareAction === 'update' ? 'upload' : 'microchip'}} me-1"></i>{{firmwareAction |
titlecase}} Firmware
</button>
<button [cModalToggle]="FirmwareConfirmModal.id" cButton color="secondary">
Cancel

View file

@ -1,20 +1,9 @@
import { Component, OnInit } from "@angular/core";
import { Component, OnInit, ViewChild } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import { formatInTimeZone } from "date-fns-tz";
import {
GuiSearching,
GuiSelectedRow,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
interface IUser {
name: string;
@ -35,9 +24,13 @@ interface IUser {
styleUrls: ["devgroup.component.scss"]
})
export class DevicesGroupComponent implements OnInit {
public uid: number;
public uname: string;
public tz: string;
public uid: number = 0;
public uname: string = '';
public tz: string = '';
@ViewChild('dt') table!: Table;
@ViewChild('dtMembers') tableMembers!: Table;
@ViewChild('dtNewMembers') tableNewMembers!: Table;
constructor(
private data_provider: dataProvider,
@ -68,7 +61,6 @@ export class DevicesGroupComponent implements OnInit {
}
}
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public MemberRows: any = [];
public NewMemberRows: any = [];
@ -117,42 +109,17 @@ export class DevicesGroupComponent implements OnInit {
id: 0,
name: "",
};
public sorting = {
enabled: true,
multiSorting: true,
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
searching: GuiSearching = {
enabled: true,
placeholder: "Search Devices",
};
applyFilterMembers($event: any, stringVal: string) {
this.tableMembers.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterNewMembers($event: any, stringVal: string) {
this.tableNewMembers.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
ngOnInit(): void {
this.initGridTable();
@ -175,13 +142,13 @@ export class DevicesGroupComponent implements OnInit {
});
}
onSelectedRowsMembers(rows: Array<GuiSelectedRow>): void {
onSelectedRowsMembers(rows: any[]): void {
this.MemberRows = rows;
this.SelectedMemberRows = rows.map((m: GuiSelectedRow) => m.source.id);
this.SelectedMemberRows = rows.map((m: any) => m.id);
}
onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void {
onSelectedRowsNewMembers(rows: any[]): void {
this.NewMemberRows = rows;
this.SelectedNewMemberRows = rows.map((m: GuiSelectedRow) => m.source.id);
this.SelectedNewMemberRows = rows.map((m: any) => m.id);
}
add_new_members() {
var _self = this;
@ -193,7 +160,7 @@ export class DevicesGroupComponent implements OnInit {
this.groupMembers = [
...new Set(
this.groupMembers.concat(
this.NewMemberRows.map((m: GuiSelectedRow) => m.source)
this.NewMemberRows
)
),
];

View file

@ -15,7 +15,9 @@ import {
} from "@coreui/angular";
import { DevicesGroupRoutingModule } from "./devgroup-routing.module";
import { DevicesGroupComponent } from "./devgroup.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PTableModule } from 'primeng/table';
import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
import { BadgeModule } from "@coreui/angular";
import { FormsModule } from "@angular/forms";
import { MatMenuModule } from "@angular/material/menu";
@ -30,7 +32,9 @@ import { MatMenuModule } from "@angular/material/menu";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
PTableModule,
PInputTextModule,
PTooltipModule,
CollapseModule,
ModalModule,
BadgeModule,

View file

@ -13,30 +13,66 @@
</c-row>
</c-card-header>
<c-card-body>
<gui-grid [rowHeight]="82" [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu"
[sorting]="sorting" [autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column width="auto" header="Perms" field="perms">
<ng-template let-value="item.role" let-item="item" let-index="index">
<div style="text-wrap: initial;">
<ng-container *ngFor="let perm of item['perms'] | keyvalue">
<c-badge *ngIf="perm.value" class="m-1" color="success">{{perm.key}}</c-badge>
</ng-container>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="warning" size="sm" class="mx-1" (click)="editAddTask(item,'edit');"><i
class="fa-regular fa-pen-to-square"></i></button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item);"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search permissions..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand"
[showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name']"
[loading]="loading">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Permission Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pResizableColumn>Perms</th>
<th style="width: 120px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>{{item.name}}</td>
<td>
<div class="d-flex flex-wrap gap-1">
<ng-container *ngFor="let perm of item['perms'] | keyvalue">
<c-badge *ngIf="perm.value" color="success">{{perm.key}}</c-badge>
</ng-container>
</div>
</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
<button cButton color="warning" size="sm" (click)="editAddTask(item,'edit');" pTooltip="Edit Permission">
<i class="fa-regular fa-pen-to-square"></i>
</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item);" pTooltip="Delete Permission">
<i class="fa-regular fa-trash-can"></i>
</button>
</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="3" class="text-center p-4">No permissions found.</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>

View file

@ -1,13 +1,8 @@
import { Component, OnInit, QueryList, ViewChildren } from "@angular/core";
import { Component, OnInit, QueryList, ViewChildren, ViewChild } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { ToasterComponent } from "@coreui/angular";
import { AppToastComponent } from "../toast-simple/toast.component";
@ -30,8 +25,10 @@ interface IUser {
templateUrl: "permissions.component.html",
})
export class PermissionsComponent implements OnInit {
public uid: number;
public uname: string;
public uid: number = 0;
public uname: string = '';
@ViewChild('dt') table!: Table;
constructor(
private data_provider: dataProvider,
@ -63,7 +60,6 @@ export class PermissionsComponent implements OnInit {
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public SelectedPerm: any = {};
@ -107,24 +103,9 @@ export class PermissionsComponent implements OnInit {
closeButton: true,
};
public sorting = {
enabled: true,
multiSorting: true,
};
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
show_toast(title: string, body: string, color: string) {
const { ...props } = { ...this.toasterForm, color, title, body };
const componentRef = this.viewChildren.first.addToast(

View file

@ -18,7 +18,9 @@ import { IconModule } from "@coreui/icons-angular";
import { PermissionsRoutingModule } from "./permissions-routing.module";
import { PermissionsComponent } from "./permissions.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule } from 'primeng/table';
import { InputTextModule } from 'primeng/inputtext';
import { TooltipModule } from 'primeng/tooltip';
@NgModule({
imports: [
@ -33,7 +35,9 @@ import { GuiGridModule } from "@generic-ui/ngx-grid";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
TableModule,
InputTextModule,
TooltipModule,
ModalModule,
FormsModule,
BadgeModule,

View file

@ -4,7 +4,7 @@
<c-card-header>
<c-row>
<c-col xs [lg]="3">
Alert Sequences (Pro)
Sequences
</c-col>
<c-col xs [lg]="9">
<h6 style="text-align: right;">
@ -18,48 +18,109 @@
</c-row>
</c-card-header>
<c-card-body>
<gui-grid [source]="source" [searching]="searching" [paging]="paging" [columnMenu]="columnMenu"
[sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Source Snippet" field="source_snippet_id">
<ng-template let-value="item.source_snippet_id" let-item="item" let-index="index">
Snippet #{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Active" field="is_active">
<ng-template let-value="item.is_active" let-item="item" let-index="index">
<i *ngIf="value" class="fa-solid fa-check" style="color: green;"></i>
<i *ngIf="!value" class="fa-solid fa-x" style="color: red;"></i>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created">
<ng-template let-value="item.created" let-item="item" let-index="index">
<div>{{value}}</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="success" size="sm" (click)="Run_Sequence(item)" class=""><i
class="fa-solid fa-play mx-1"></i>Execute</button>
<button cButton color="primary" size="sm" (click)="Edit_Sequence(item,'edit')" class="mx-1"><i
class="fa-regular fa-pen-to-square mx-1"></i>Edit</button>
<button cButton color="info" size="sm" (click)="show_history(item)" class=""><i
class="fa-solid fa-clock-rotate-left mx-1"></i>History</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item,false)" class="mx-1"><i
class="fa-regular fa-trash-can mx-1"></i>Delete</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search sequences..."
(input)="applyFilterGlobal($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand"
[showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name', 'created']" [loading]="false">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
<div class="d-flex justify-content-between align-items-center">
<span>Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="source_snippet_id" pResizableColumn>
<div class="d-flex justify-content-between align-items-center">
<span>Source Snippet</span>
<span>
<p-sortIcon field="source_snippet_id"></p-sortIcon>
<p-columnFilter type="numeric" field="source_snippet_id" display="menu"
class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="is_active" class="text-center" pResizableColumn>
<div class="d-flex justify-content-between align-items-center">
<span>Active</span>
<span>
<p-sortIcon field="is_active"></p-sortIcon>
<p-columnFilter type="boolean" field="is_active" display="menu"
class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="created" pResizableColumn>
<div class="d-flex justify-content-between align-items-center">
<span>Created</span>
<span>
<p-sortIcon field="created"></p-sortIcon>
<p-columnFilter type="text" field="created" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 280px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><strong>{{item.name}}</strong></td>
<td>Snippet #{{item.source_snippet_id}}</td>
<td class="text-center">
<i *ngIf="item.is_active" class="fa-solid fa-check" style="color: green;"></i>
<i *ngIf="!item.is_active" class="fa-solid fa-x" style="color: red;"></i>
</td>
<td>{{item.created}}</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
<button cButton color="success" size="sm" (click)="Run_Sequence(item)"
pTooltip="Execute Sequence">
<i class="fa-solid fa-play"></i>
</button>
<button cButton color="primary" size="sm" (click)="Edit_Sequence(item,'edit')"
pTooltip="Edit Sequence">
<i class="fa-regular fa-pen-to-square"></i>
</button>
<button cButton color="info" size="sm" (click)="show_history(item)"
pTooltip="Execution History">
<i class="fa-solid fa-clock-rotate-left"></i>
</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item,false)"
pTooltip="Delete Sequence">
<i class="fa-regular fa-trash-can"></i>
</button>
</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="5" class="text-center p-4">No sequences found.</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>
</c-row>
<c-modal #EditSequenceModal backdrop="static" size="xl" [(visible)]="EditSequenceModalVisible" id="EditSequenceModal" class="builder-modal">
<c-modal #EditSequenceModal backdrop="static" size="xl" [(visible)]="EditSequenceModalVisible" id="EditSequenceModal"
class="builder-modal">
<c-modal-header class="bg-light">
<h5 *ngIf="ModalAction=='edit'" cModalTitle>
<i class="fa-solid fa-edit me-2"></i>Edit Sequence: {{current_sequence['name']}}
@ -345,60 +406,72 @@
<c-card class="shadow-sm border-0">
<c-card-body class="p-0">
<div *ngIf="sequence_history.length > 0">
<div *ngIf="sequence_history.length > 0">
<div *ngFor="let run of sequence_history; let i = index" class="mb-3">
<c-card class="border-0 shadow-sm overflow-hidden">
<c-card-header (click)="run.visible = !run.visible" style="cursor: pointer;"
class="d-flex justify-content-between align-items-center bg-white border-0 py-3">
<span>
<i class="fa-solid" [ngClass]="run.visible ? 'fa-chevron-down' : 'fa-chevron-right'" class="me-3 text-muted"></i>
<i class="fa-solid fa-calendar-check me-2 text-primary"></i>
<strong>{{run.created}}</strong>
<small class="text-muted ms-3">Exec ID: {{run.exec_id | slice:0:8}}...</small>
</span>
<div class="d-flex align-items-center">
<c-badge [color]="run.successCount === run.totalCount ? 'success' : (run.successCount === 0 ? 'danger' : 'warning')" class="px-3 py-2 text-white">
{{run.successCount}}/{{run.totalCount}} Success
</c-badge>
<div *ngIf="sequence_history.length > 0">
<div *ngFor="let run of sequence_history; let i = index" class="mb-3">
<c-card class="border-0 shadow-sm overflow-hidden">
<c-card-header (click)="run.visible = !run.visible" style="cursor: pointer;"
class="d-flex justify-content-between align-items-center bg-white border-0 py-3">
<span>
<i class="fa-solid"
[ngClass]="run.visible ? 'fa-chevron-down' : 'fa-chevron-right'"
class="me-3 text-muted"></i>
<i class="fa-solid fa-calendar-check me-2 text-primary"></i>
<strong>{{run.created}}</strong>
<small class="text-muted ms-3">Exec ID: {{run.exec_id | slice:0:8}}...</small>
</span>
<div class="d-flex align-items-center">
<c-badge
[color]="run.successCount === run.totalCount ? 'success' : (run.successCount === 0 ? 'danger' : 'warning')"
class="px-3 py-2 text-white">
{{run.successCount}}/{{run.totalCount}} Success
</c-badge>
</div>
</c-card-header>
<div [visible]="run.visible" cCollapse class="border-top">
<c-card-body class="p-0 border-top">
<p-table [value]="run.devices" [paginator]="true" [rows]="5"
[showGridlines]="true" [stripedRows]="true" styleClass="p-datatable-sm"
class="border-0">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="device_id">Device <p-sortIcon
field="device_id"></p-sortIcon></th>
<th pSortableColumn="status" class="text-center"
style="width: 150px">Status <p-sortIcon
field="status"></p-sortIcon></th>
<th style="width: 150px" class="text-end">Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-dev>
<tr>
<td>
<div class="fw-semibold ms-2">
<i class="fa-solid fa-microchip me-2 opacity-50"></i>Device
#{{dev.device_id}}
</div>
</td>
<td class="text-center">
<c-badge
[color]="dev.status === 'success' ? 'success' : 'danger'"
class="text-white px-2">
{{dev.status | uppercase}}
</c-badge>
</td>
<td class="text-end">
<button cButton color="dark" size="sm" variant="outline"
pTooltip="Trace Log" (click)="showTrace(dev)">
<i class="fa-solid fa-terminal"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</div>
</c-card-header>
<div [visible]="run.visible" cCollapse class="border-top">
<c-card-body class="p-0 border-top">
<gui-grid [source]="run.devices"
[searching]="searchingHistory"
[paging]="pagingHistory"
[sorting]="sorting"
[infoPanel]="false"
[autoResizeWidth]="true"
class="border-0">
<gui-grid-column header="Device" field="device_id">
<ng-template let-item="item">
<div class="fw-semibold ms-2">
<i class="fa-solid fa-microchip me-2 opacity-50"></i>Device #{{item.device_id}}
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Status" field="status" align="center" width="150">
<ng-template let-item="item">
<c-badge [color]="item.status === 'success' ? 'success' : 'danger'" class="text-white px-2">
{{item.status | uppercase}}
</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" align="right" width="150">
<ng-template let-item="item">
<button cButton color="dark" size="sm" variant="outline" class="me-2" (click)="showTrace(item)">
<i class="fa-solid fa-terminal me-1"></i> Trace Log
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-card-body>
</div>
</c-card>
</c-card>
</div>
</div>
</div>
</div>
<div *ngIf="sequence_history.length === 0" class="text-center text-muted py-5">
<i class="fa-solid fa-inbox fa-3x mb-3 opacity-25"></i>
<h6>No History Found</h6>
@ -418,12 +491,14 @@
<c-modal-body class="p-0 bg-dark">
<div class="nav nav-tabs border-secondary px-3 pt-2">
<div class="nav-item">
<a [cTabContent]="tabContent" [active]="true" [tabPaneIdx]="0" class="nav-link text-light border-secondary cursor-pointer">
<a [cTabContent]="tabContent" [active]="true" [tabPaneIdx]="0"
class="nav-link text-light border-secondary cursor-pointer">
<i class="fa-solid fa-code me-2"></i>SSH Output
</a>
</div>
<div class="nav-item">
<a [cTabContent]="tabContent" [active]="false" [tabPaneIdx]="1" class="nav-link text-light border-secondary cursor-pointer">
<a [cTabContent]="tabContent" [active]="false" [tabPaneIdx]="1"
class="nav-link text-light border-secondary cursor-pointer">
<i class="fa-solid fa-list-check me-2"></i>Evaluation Details
</a>
</div>
@ -441,11 +516,13 @@
<div class="evaluation-tree" *ngIf="current_trace.parsedLog?.evaluation">
<!-- Structured JSON Evaluation -->
<div *ngIf="isObject(current_trace.parsedLog.evaluation); else flatEval">
<ng-container *ngTemplateOutlet="evalNodeTemplate; context: { node: current_trace.parsedLog.evaluation }"></ng-container>
<ng-container
*ngTemplateOutlet="evalNodeTemplate; context: { node: current_trace.parsedLog.evaluation }"></ng-container>
</div>
<!-- Legacy String Evaluation -->
<ng-template #flatEval>
<pre class="text-info bg-transparent border-0 p-0 m-0" style="white-space: pre-wrap;">{{current_trace.parsedLog.evaluation}}</pre>
<pre class="text-info bg-transparent border-0 p-0 m-0"
style="white-space: pre-wrap;">{{current_trace.parsedLog.evaluation}}</pre>
</ng-template>
</div>
<div *ngIf="!current_trace.parsedLog?.evaluation" class="text-center py-4 text-muted">
@ -467,7 +544,7 @@
<i class="fa-solid fa-check-circle text-success me-2" *ngIf="rule.matched"></i>
<i class="fa-regular fa-circle text-muted me-2" *ngIf="!rule.matched"></i>
<span [class.text-white]="rule.matched" [class.text-muted]="!rule.matched">
Rule <strong>[{{rule.type}}]</strong>: '{{rule.pattern}}' &rarr;
Rule <strong>[{{rule.type}}]</strong>: '{{rule.pattern}}' &rarr;
<span [class.text-success]="rule.matched">{{rule.matched ? 'MATCHED' : 'NO MATCH'}}</span>
</span>
</div>
@ -477,10 +554,11 @@
<strong>ACTION:</strong> {{act.action_type}}
<span *ngIf="act.alert_id" class="text-warning ms-1">(Alert ID: {{act.alert_id}})</span>
<span *ngIf="act.snippet_id" class="text-primary ms-1">(Snippet: {{act.snippet_id}})</span>
<!-- Nested Evaluation if another snippet was run -->
<div *ngIf="act.nested_eval" class="mt-2">
<ng-container *ngTemplateOutlet="evalNodeTemplate; context: { node: act.nested_eval }"></ng-container>
<ng-container
*ngTemplateOutlet="evalNodeTemplate; context: { node: act.nested_eval }"></ng-container>
</div>
</div>
</div>
@ -519,25 +597,34 @@
</c-input-group>
<h5>Members :</h5>
<gui-grid [autoResizeWidth]="true" [source]="SelectedMembers" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [rowSelection]="rowSelection" [paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="current_sequence['selection_type']=='devices'" header="MAC" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" (click)="remove_member(item)">
<i class="fa-regular fa-trash-can"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table #dtMembers [value]="SelectedMembers" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
<th *ngIf="current_sequence['selection_type']=='devices'" pSortableColumn="mac">MAC <p-sortIcon
field="mac"></p-sortIcon></th>
<th style="width: 100px" class="text-center">Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>{{item.name}}</td>
<td *ngIf="current_sequence['selection_type']=='devices'">{{item.mac}}</td>
<td class="text-center">
<button cButton color="danger" size="sm" (click)="remove_member(item)" pTooltip="Remove Member">
<i class="fa-regular fa-trash-can"></i>
</button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td [attr.colspan]="current_sequence['selection_type']=='devices' ? 3 : 2" class="text-center p-2">
No members added.</td>
</tr>
</ng-template>
</p-table>
<hr />
<button cButton color="primary" (click)="show_new_member_form()">+ Add new Members</button>
@ -559,24 +646,56 @@
<c-modal-body>
<c-input-group class="mb-3">
<h5>Group Members :</h5>
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [paging]="paging">
<gui-grid-column header="Member Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="current_sequence['selection_type']=='devices'" header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="current_sequence['selection_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end w-100">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search members..."
(input)="applyFilterNewMembers($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dtNewMembers [value]="availbleMembers" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm" [(selection)]="SelectedNewMemberRows"
(selectionChange)="onSelectedRowsNewMembers($event)" [globalFilterFields]="['name', 'ip', 'mac']">
<ng-template pTemplate="header">
<tr>
<th style="width: 4rem" pResizableColumn><p-tableHeaderCheckbox></p-tableHeaderCheckbox></th>
<th pSortableColumn="name" pResizableColumn>
<div class="d-flex justify-content-between align-items-center">
<span>Member Name</span>
<p-sortIcon field="name"></p-sortIcon>
</div>
</th>
<th *ngIf="current_sequence['selection_type']=='devices'" pSortableColumn="ip" pResizableColumn>
<div class="d-flex justify-content-between align-items-center">
<span>IP Address</span>
<p-sortIcon field="ip"></p-sortIcon>
</div>
</th>
<th *ngIf="current_sequence['selection_type']=='devices'" pSortableColumn="mac"
pResizableColumn>
<div class="d-flex justify-content-between align-items-center">
<span>MAC Address</span>
<p-sortIcon field="mac"></p-sortIcon>
</div>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><p-tableCheckbox [value]="item"></p-tableCheckbox></td>
<td>{{item.name}}</td>
<td *ngIf="current_sequence['selection_type']=='devices'">{{item.ip}}</td>
<td *ngIf="current_sequence['selection_type']=='devices'">{{item.mac}}</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td [attr.colspan]="current_sequence['selection_type']=='devices' ? 4 : 2"
class="text-center p-4">No available members found.</td>
</tr>
</ng-template>
</p-table>
<br />
</c-input-group>
<hr />

View file

@ -1,7 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewChild } from '@angular/core';
import { dataProvider } from "../../providers/mikrowizard/data";
import { ToastComponent } from '@coreui/angular';
import { NgxSuperSelectOptions } from "ngx-super-select";
import { Table } from 'primeng/table';
@Component({
selector: 'app-sequences',
@ -11,26 +12,23 @@ import { NgxSuperSelectOptions } from "ngx-super-select";
export class SequencesComponent implements OnInit {
public sequences: any[] = [];
public source: any[] = [];
// Grid config
source: Array<any> = [];
searching = {
enabled: true,
placeholder: 'Search sequences...'
};
paging = {
enabled: true,
pageSize: 10,
pageSizes: [10, 25, 50]
};
columnMenu = { enabled: false };
sorting = { enabled: true };
infoPanel = { enabled: true };
rowSelection: any = {
enabled: true,
type: 'checkbox',
mode: 'multiple',
};
@ViewChild('dt') table!: Table;
@ViewChild('dtMembers') tableMembers!: Table;
@ViewChild('dtNewMembers') tableNewMembers!: Table;
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
applyFilterMembers($event: any, stringVal: string) {
this.tableMembers.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
applyFilterNewMembers($event: any, stringVal: string) {
this.tableNewMembers.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
public EditSequenceModalVisible: boolean = false;
public current_sequence: any = {
@ -57,21 +55,7 @@ export class SequencesComponent implements OnInit {
public SelectedNewMemberRows: any[] = [];
public NewMemberRows: any[] = [];
public searchingMembers = {
enabled: true,
placeholder: 'Search members...'
};
// Device History Grid Config
searchingHistory = {
enabled: true,
placeholder: 'Search devices in this run...'
};
pagingHistory = {
enabled: true,
pageSize: 5,
pageSizes: [5, 10, 25]
};
public loading: boolean = true;
// super select options
snippetOptions: Partial<NgxSuperSelectOptions> = {

View file

@ -20,7 +20,9 @@ import {
import { HighlightJsModule } from 'ngx-highlight-js';
import { SequencesRoutingModule } from "./sequences-routing.module";
import { SequencesComponent } from "./sequences.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule } from 'primeng/table';
import { InputTextModule } from 'primeng/inputtext';
import { TooltipModule } from 'primeng/tooltip';
import { NgxSuperSelectModule } from "ngx-super-select";
@NgModule({
@ -32,7 +34,9 @@ import { NgxSuperSelectModule } from "ngx-super-select";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
TableModule,
InputTextModule,
TooltipModule,
ModalModule,
ToastModule,
FormsModule,

File diff suppressed because it is too large Load diff

View file

@ -7,12 +7,11 @@
}
.mdc-line-ripple.mdc-line-ripple--deactivating.ng-star-inserted {
display: none!important;
display: none !important;
}
/* Settings Container */
.settings-container {
max-width: 1200px;
margin: 0 auto;
}
@ -24,7 +23,7 @@
}
.settings-header {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
// background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid #dee2e6;
padding: 1.25rem 1.5rem;
}
@ -92,12 +91,12 @@
padding: 1.5rem;
transition: all 0.2s ease;
}
.section-card:hover {
border-color: #0a58ca;
background: linear-gradient(135deg, #cfe2ff 0%, #e7f3ff 100%);
}
.section-header {
display: flex;
align-items: center;
@ -205,7 +204,8 @@
/* Form Controls */
.form-control, .form-select {
.form-control,
.form-select {
border-radius: 6px;
border: 1px solid #ced4da;
padding: 0.5rem 0.75rem;
@ -213,7 +213,8 @@
transition: all 0.2s ease;
}
.form-control:focus, .form-select:focus {
.form-control:focus,
.form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
@ -250,29 +251,30 @@
/* Mobile Responsive for Informative Elements */
@media (max-width: 768px) {
.info-banner,
.warning-banner,
.success-banner {
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
}
.help-icon {
font-size: 0.75rem;
}
.setting-help {
font-size: 0.75rem;
}
.switch-description {
font-size: 0.8rem;
}
.switch-description small {
font-size: 0.7rem;
}
::ng-deep .tooltip-inner {
max-width: 250px;
font-size: 0.75rem;
@ -280,6 +282,7 @@
}
@media (max-width: 576px) {
.info-banner,
.warning-banner,
.success-banner {
@ -287,7 +290,7 @@
align-items: flex-start;
gap: 0.25rem;
}
.setting-label {
flex-direction: column;
align-items: flex-start;
@ -300,40 +303,40 @@
.settings-header {
padding: 1rem;
}
.settings-tabs {
padding: 0 1rem;
}
.tab-content {
padding: 1rem;
}
.tab-link {
padding: 0.75rem 1rem;
font-size: 0.9rem;
}
.status-section,
.config-section,
.firmware-settings {
padding: 1rem;
}
.download-section .section-card {
padding: 1rem;
}
.switch-content {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.switch-info {
margin-right: 0;
}
.section-title {
font-size: 1rem;
}
@ -343,15 +346,15 @@
.settings-container {
margin: 0 0.5rem;
}
.settings-card {
border-radius: 8px;
}
.tab-content {
padding: 0.75rem;
}
.status-section,
.config-section,
.firmware-settings {
@ -421,8 +424,15 @@
/* Animation for dropdown */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.search-dropdown {
@ -618,16 +628,4 @@
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Grid Enhancements */
::ng-deep .gui-grid {
.gui-grid-header {
background: #f8f9fa;
font-weight: 600;
}
.gui-grid-cell {
padding: 0.75rem 0.5rem;
}
}

View file

@ -4,21 +4,12 @@ import {
QueryList,
ViewChildren,
ViewEncapsulation,
ViewChild
} from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiSelectedRow,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { ToasterComponent } from "@coreui/angular";
import { AppToastComponent } from "../toast-simple/toast.component";
import { TimeZones } from "./timezones-data";
@ -30,14 +21,16 @@ import { TimeZones } from "./timezones-data";
})
export class SettingsComponent implements OnInit {
public uid: number;
public uname: string;
public uid: number = 0;
public uname: string = '';
public ispro:boolean=false;
public filterText: string;
public filterText: string = '';
public filters: any = {};
public firms: any = {};
public firmtodownload: any = {};
public activeTab: string = 'firmware';
@ViewChild('dt') dt!: Table;
// Search functionality properties
public firmwareSearch: string = '';
@ -81,7 +74,6 @@ export class SettingsComponent implements OnInit {
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public SysConfigloading: boolean = true;
@ -104,37 +96,9 @@ export class SettingsComponent implements OnInit {
closeButton: true,
};
public sorting = {
enabled: true,
multiSorting: true,
};
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 5,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterGlobal($event: any, stringVal: string) {
this.dt.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
public timezones = this.TimeZones.timezones;
ngOnInit(): void {
@ -208,9 +172,9 @@ export class SettingsComponent implements OnInit {
});
}
onSelectedRows(rows: Array<GuiSelectedRow>): void {
onSelectedRows(rows: any[]): void {
this.rows = rows;
this.Selectedrows = rows.map((m: GuiSelectedRow) => m.source.id);
this.Selectedrows = rows.map((m: any) => m.id);
}
show_toast(title: string, body: string, color: string) {

View file

@ -15,7 +15,9 @@ import {
} from "@coreui/angular";
import { SettingsRoutingModule } from "./settings-routing.module";
import { SettingsComponent } from "./settings.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PTableModule } from 'primeng/table';
import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
import { FormsModule } from "@angular/forms";
@ -29,7 +31,9 @@ import { FormsModule } from "@angular/forms";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
PTableModule,
PInputTextModule,
PTooltipModule,
SpinnerModule,
ToastModule,
ModalModule,

View file

@ -8,46 +8,71 @@
</c-col>
<c-col xs [lg]="9">
<h6 style="text-align: right;">
<button cButton color="dark" class="mx-1" size="sm" (click)="Edit_Snippet('','showadd')"
<button cButton color="dark" class="mx-1" size="sm" (click)="Edit_Snippet('','showadd')"
style="color: #fff;"><i class="fa-solid fa-plus"></i> </button>
</h6>
</c-col>
</c-row>
</c-card-header>
<c-card-body>
<gui-grid [source]="source" [searching]="searching" [paging]="paging" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRows($event)" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<img *ngIf="item.status=='updating'" width="20px" src="assets/img/loading.svg" />
<i *ngIf="item.status=='updated'" style="color: green;margin: 5px;" class="fa-solid fa-check"></i>
<i *ngIf="item.status=='failed'" style="color: red;margin: 5px;" class="fa-solid fa-x"></i>
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Description" field="description">
<ng-template let-value="item.description" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created">
<ng-template let-value="item.created" let-item="item" let-index="index">
<div>{{value}}</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="primary" size="sm" (click)="Edit_Snippet(item,'edit')" class=""><i
class="fa-regular fa-pen-to-square mx-1"></i>Edit</button>
<button cButton color="warning" size="sm" (click)="Run_Snippet(item,'exec')" class="mx-1"><i
class="fa-solid fa-bolt mx-1"></i>Execute</button>
<button cButton color="info" size="sm" (click)="show_exec(item)" class="mx-1"><i
class="fa-solid fa-bolt mx-1"></i>Data</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item,false)" class=""><i
class="fa-regular fa-trash-can mx-1"></i>Delete</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[(selection)]="Selectedrows" [rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true"
columnResizeMode="expand" [showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name','description']">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
Name
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</div>
</th>
<th pSortableColumn="description" pResizableColumn>
<div class="justify-between">
Description
<p-sortIcon field="description"></p-sortIcon>
<p-columnFilter type="text" field="description" display="menu" class="ms-auto" />
</div>
</th>
<th pSortableColumn="created" pResizableColumn>
<div class="justify-between">
Created
<p-sortIcon field="created"></p-sortIcon>
<p-columnFilter type="text" field="created" display="menu" class="ms-auto" />
</div>
</th>
<th style="width: 280px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>
<img *ngIf="item.status=='updating'" width="20px" src="assets/img/loading.svg" />
<i *ngIf="item.status=='updated'" style="color: green;margin-right: 5px;" class="fa-solid fa-check"></i>
<i *ngIf="item.status=='failed'" style="color: red;margin-right: 5px;" class="fa-solid fa-x"></i>
{{item.name}}
</td>
<td>{{item.description}}</td>
<td class="small">{{item.created}}</td>
<td class="text-center">
<button cButton color="primary" size="sm" (click)="Edit_Snippet(item,'edit')" class="me-1">
<i class="fa-regular fa-pen-to-square me-1"></i>Edit
</button>
<button cButton color="warning" size="sm" (click)="Run_Snippet(item,'exec')" class="me-1">
<i class="fa-solid fa-bolt me-1"></i>Execute
</button>
<button cButton color="info" size="sm" (click)="show_exec(item)" class="me-1">
<i class="fa-solid fa-history me-1"></i>History
</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item,false)">
<i class="fa-regular fa-trash-can me-1"></i>Delete
</button>
</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>
@ -63,7 +88,8 @@
</c-modal-header>
<c-modal-body>
<div [cFormFloating]="true" class="mb-3">
<input cFormControl id="floatingInput" placeholder="current_snippet['name']" [(ngModel)]="current_snippet['name']" disabled="true"/>
<input cFormControl id="floatingInput" placeholder="current_snippet['name']" [(ngModel)]="current_snippet['name']"
disabled="true" />
<label cLabel for="floatingInput">Snipet Name</label>
</div>
@ -84,30 +110,36 @@
</c-input-group>
<h5>Members :</h5>
<gui-grid [autoResizeWidth]="true" [source]="SelectedMembers" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [rowSelection]="rowSelection" [autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="current_snippet['selection_type']=='devices'" header="MAC" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" (click)="remove_member(item)"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table #dtSelected [value]="SelectedMembers" [paginator]="true" [rows]="5" [resizableColumns]="true"
columnResizeMode="expand" [showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>Name <p-sortIcon field="name"></p-sortIcon></th>
<th *ngIf="current_snippet['selection_type']=='devices'" pSortableColumn="mac" pResizableColumn>MAC
<p-sortIcon field="mac"></p-sortIcon>
</th>
<th style="width: 80px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-member>
<tr>
<td>{{member.name}}</td>
<td *ngIf="current_snippet['selection_type']=='devices'">{{member.mac}}</td>
<td class="text-center">
<button cButton color="danger" size="sm" (click)="remove_member(member)">
<i class="fa-regular fa-trash-can"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
<hr />
<button cButton color="primary" (click)="show_new_member_form()">+ Add new Members</button>
</c-modal-body>
<c-modal-footer>
<button (click)="submit('exec')" cButton color="primary">Execute</button>
<button (click)="submit('exec')" cButton color="primary">Execute</button>
<button [cModalToggle]="ExecSnipetModal.id" cButton color="secondary">
Close
</button>
@ -123,25 +155,48 @@
<c-modal-body>
<c-input-group class="mb-3">
<h5>Group Members :</h5>
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true
[paging]="paging">
<gui-grid-column header="Member Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="current_snippet['selection_type']=='devices'" header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="current_snippet['selection_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end w-100">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search..." (input)="applyFilterNewMember($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<p-table #dtNewMember [value]="availbleMembers" [paginator]="true" [rows]="10"
[(selection)]="SelectedNewMemberRows" (selectionChange)="NewMemberRows = $event" [resizableColumns]="true"
columnResizeMode="expand" [showGridlines]="true" [stripedRows]="true"
Class="p-datatable-sm p-datatable-gridlines p-datatable-striped" [globalFilterFields]="['name','ip','mac']">
<ng-template pTemplate="header">
<tr>
<th style="width: 3rem" pResizableColumn>
<p-tableHeaderCheckbox></p-tableHeaderCheckbox>
</th>
<th pSortableColumn="name" pResizableColumn>
Name
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</th>
<th *ngIf="current_snippet['selection_type']=='devices'" pSortableColumn="ip" pResizableColumn>
IP
<p-sortIcon field="ip"></p-sortIcon>
<p-columnFilter type="text" field="ip" display="menu" class="ms-auto" />
</th>
<th *ngIf="current_snippet['selection_type']=='devices'" pSortableColumn="mac" pResizableColumn>
MAC
<p-sortIcon field="mac"></p-sortIcon>
<p-columnFilter type="text" field="mac" display="menu" class="ms-auto" />
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-member>
<tr>
<td><p-tableCheckbox [value]="member"></p-tableCheckbox></td>
<td>{{member.name}}</td>
<td *ngIf="current_snippet['selection_type']=='devices'">{{member.ip}}</td>
<td *ngIf="current_snippet['selection_type']=='devices'">{{member.mac}}</td>
</tr>
</ng-template>
</p-table>
<br />
</c-input-group>
<hr />
@ -164,25 +219,44 @@
</c-modal-header>
<c-modal-body>
<c-input-group class="mb-3">
<gui-grid [autoResizeWidth]="true" *ngIf="ExecutedDataModalVisible" [searching]="searching"
[source]="ExecutedData" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[autoResizeWidth]=true
[paging]="paging">
<gui-grid-column header="Start time" field="start">
<ng-template let-value="item['started']" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="End time" field="end">
<ng-template let-value="item['ended']" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="info" field="mac" align="center">
<ng-template let-value="item['result']" let-item="item" let-index="index">
<button (click)="exportToCsv(value)" color="primary" cButton>download</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end w-100">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search history..." (input)="applyFilterHistory($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<p-table #dtHistory width="100%" [value]="ExecutedData" [paginator]="true" [rows]="10" [resizableColumns]="true"
columnResizeMode="expand" [showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['username','detail']">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="started" pResizableColumn>
Start time
<p-sortIcon field="started"></p-sortIcon>
<p-columnFilter type="text" field="started" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="ended" pResizableColumn>
End time
<p-sortIcon field="ended"></p-sortIcon>
<p-columnFilter type="text" field="ended" display="menu" class="ms-auto" />
</th>
<th style="width: 120px" class="text-center" pResizableColumn>Info</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td class="small">{{item.started}}</td>
<td class="small">{{item.ended}}</td>
<td class="text-center">
<button (click)="exportToCsv(item.result)" color="primary" pTooltip="Download CSV" size="sm" cButton>
<i class="fas fa-download"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
<br />
</c-input-group>
<hr />
@ -210,16 +284,19 @@
</c-input-group>
<c-input-group class="mb-3">
<div [cFormFloating]="true" class="mb-3">
<input cFormControl id="floatingInput" placeholder="Snippet Description" [(ngModel)]="current_snippet['description']" />
<input cFormControl id="floatingInput" placeholder="Snippet Description"
[(ngModel)]="current_snippet['description']" />
<label cLabel for="floatingInput">Description</label>
</div>
</c-input-group>
<c-input-group class="mb-3">
<div [cFormFloating]="true" class="mb-3">
<textarea [style.height.px]="50 + (23 * lineNum)"
cFormControl (ngModelChange)="calcline($event)" id="floatingInput" placeholder="Snippet code" [(ngModel)]="current_snippet['content']" ></textarea>
<textarea [style.height.px]="50 + (23 * lineNum)" cFormControl (ngModelChange)="calcline($event)"
id="floatingInput" placeholder="Snippet code" [(ngModel)]="current_snippet['content']"></textarea>
<label cLabel for="floatingInput">Code</label>
<div class="col-sm-12 c-d-block c-text-truncate">Note : In case of multiple IP addresses for the MikroWizard server, use<code style="padding: 0!important;">[mikrowizard]</code> instead of the MikroWizard server IP.</div>
<div class="col-sm-12 c-d-block c-text-truncate">Note : In case of multiple IP addresses for the MikroWizard
server, use<code style="padding: 0!important;">[mikrowizard]</code> instead of the MikroWizard server IP.
</div>
</div>
</c-input-group>
<br />
@ -230,27 +307,28 @@
</c-modal-footer>
</c-modal>
<c-modal #DeleteConfirmModal backdrop="static" [(visible)]="DeleteConfirmModalVisible"
id="DeleteConfirmModal">
<c-modal #DeleteConfirmModal backdrop="static" [(visible)]="DeleteConfirmModalVisible" id="DeleteConfirmModal">
<c-modal-header>
<h5 cModalTitle>Confirm delete {{ SelectedSnippet['name'] }}</h5>
<h5 cModalTitle>Confirm delete {{ SelectedSnippet['name'] }}</h5>
<button [cModalToggle]="DeleteConfirmModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
Are you sure that You want to delete following Snippet ?
<br/>
<br/>
<br />
<br />
<table style="width: 100%;">
<tr>
<td><b>Snippet name : </b>{{ SelectedSnippet['name'] }}
</tr>
<tr>
</tr>
<tr>
<td>
<p ><code style="padding: 0!important;"><b>Warning:</b> ALL <b>Tasks</b> related to this snippet Will be <b>modifed or deleted</b> and stop working!</code></p>
</td>
</tr>
<tr>
<td><b>Snippet name : </b>{{ SelectedSnippet['name'] }}
</tr>
<tr>
</tr>
<tr>
<td>
<p><code
style="padding: 0!important;"><b>Warning:</b> ALL <b>Tasks</b> related to this snippet Will be <b>modifed or deleted</b> and stop working!</code>
</p>
</td>
</tr>
</table>
</c-modal-body>
<c-modal-footer>
@ -263,6 +341,4 @@
</c-modal-footer>
</c-modal>
<c-toaster position="fixed" placement="top-end"></c-toaster>
<c-toaster position="fixed" placement="top-end"></c-toaster>

View file

@ -12,32 +12,18 @@ import { UntypedFormControl, UntypedFormGroup } from "@angular/forms";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import { HttpClient, HttpClientModule, HttpParams } from "@angular/common/http";
import {
GuiCellView,
GuiSearching,
GuiSelectedRow,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiDataType,
GuiPaging,
GuiPagingDisplay,
GuiRowColoring,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { HttpClient, HttpParams } from "@angular/common/http";
import { formatInTimeZone } from "date-fns-tz";
import { Table } from 'primeng/table';
@Component({
templateUrl: "snippets.component.html",
})
export class SnippetsComponent implements OnInit, OnDestroy {
public uid: number;
public uname: string;
public tz: string;
public uid!: number;
public uname!: string;
public tz!: string;
public ispro: boolean = false;
constructor(
@ -75,9 +61,9 @@ export class SnippetsComponent implements OnInit, OnDestroy {
}
}
@ViewChild("nameSummaryCell")
nameSummaryCell: TemplateRef<any>;
nameSummaryCell!: TemplateRef<any>;
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public columns: Array<any> = [];
public loading: boolean = true;
public rows: any = [];
public Selectedrows: any;
@ -96,6 +82,9 @@ export class SnippetsComponent implements OnInit, OnDestroy {
public NewMemberRows: any = [];
public SelectedNewMemberRows: any;
@ViewChild('dtNewMember') dtNewMember!: Table;
@ViewChild('dtHistory') dtHistory!: Table;
public current_snippet: any = {
content: "",
created: "",
@ -112,44 +101,15 @@ export class SnippetsComponent implements OnInit, OnDestroy {
name: "",
};
public sorting = {
enabled: true,
multiSorting: true,
};
public ip_scanner: any;
searching: GuiSearching = {
enabled: true,
placeholder: "Search Devices",
};
applyFilterNewMember($event: any, stringVal: string) {
this.dtNewMember.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterHistory($event: any, stringVal: string) {
this.dtHistory.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
ngOnInit(): void {
this.initGridTable();
@ -256,9 +216,9 @@ export class SnippetsComponent implements OnInit, OnDestroy {
});
}
onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void {
onSelectedRowsNewMembers(rows: Array<any>): void {
this.NewMemberRows = rows;
this.SelectedNewMemberRows = rows.map((m: GuiSelectedRow) => m.source);
this.SelectedNewMemberRows = rows.map((m: any) => m.source || m);
}
add_new_members() {
@ -305,9 +265,9 @@ export class SnippetsComponent implements OnInit, OnDestroy {
});
}
onSelectedRows(rows: Array<GuiSelectedRow>): void {
onSelectedRows(rows: Array<any>): void {
this.rows = rows;
this.Selectedrows = rows.map((m: GuiSelectedRow) => m.source.id);
this.Selectedrows = rows.map((m: any) => (m.source ? m.source.id : m.id));
}
remove(item: any) {

View file

@ -13,7 +13,9 @@ import {
} from "@coreui/angular";
import { SnippetsRoutingModule } from "./snippets-routing.module";
import { SnippetsComponent } from "./snippets.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PTableModule } from 'primeng/table';
import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
@NgModule({
imports: [
@ -24,7 +26,9 @@ import { GuiGridModule } from "@generic-ui/ngx-grid";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
PTableModule,
PInputTextModule,
PTooltipModule,
ModalModule,
ToastModule,
FormsModule,

View file

@ -17,62 +17,93 @@
</c-row>
</c-card-header>
<c-card-body>
<gui-grid [source]="source" [searching]="searching" [paging]="paging" [columnMenu]="columnMenu"
[sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Regex Pattern" field="regex_pattern">
<ng-template let-value="item.regex_pattern" let-item="item" let-index="index">
<code class="px-1 py-1">{{value}}</code>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Alert / Severity" field="alert_id">
<ng-template let-value="item.alert_id" let-item="item" let-index="index">
<div class="d-flex flex-row gap-1">
<span class="fw-bold">{{ getAlertName(value) }}</span>
<c-badge size="sm" [color]="getAlertLevel(value).toLowerCase() =='critical' ? 'danger' :
getAlertLevel(value).toLowerCase() =='warning' ? 'warning' :
getAlertLevel(value).toLowerCase() =='info' ? 'info' :
getAlertLevel(value).toLowerCase() =='error' ? 'danger' : 'secondary'">{{
getAlertLevel(value)}}</c-badge>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Alert Enabled/Type" field="alert_enabled" align="center" width="150">
<ng-template let-value="item.alert_enabled" let-item="item" let-index="index">
<i *ngIf="value" class="fa-solid fa-check mx-1" style="color: green;"></i>
<i *ngIf="!value" class="fa-solid fa-x mx-1" style="color: red;"></i>
<span *ngIf="item.alert_enabled && item.global_alert"><i
class="fa-solid fa-earth-americas text-primary"
title="Global Alert"></i>Global</span>
<span *ngIf="item.alert_enabled && !item.global_alert"><i
class="fa-solid fa-server text-secondary" title="Device Specific"></i>String
Match</span>
</ng-template>
</gui-grid-column>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search regexes..."
(input)="applyFilterGlobal($event, 'contains')" class="form-control-sm" />
</span>
</div>
<gui-grid-column header="Match String" field="match_string">
<ng-template let-value="item.match_string" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created">
<ng-template let-value="item.created" let-item="item" let-index="index">
<div>{{value}}</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" field="action" align="center" width="200">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="primary" size="sm" (click)="Edit_Regex(item,'edit')" class="mx-1"><i
class="fa-regular fa-pen-to-square mx-1"></i>Edit</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item,false)"
class="mx-1"><i class="fa-regular fa-trash-can mx-1"></i>Delete</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table #dt showGridlines [value]="source" [paginator]="true" [rows]="10"
[showCurrentPageReport]="true" [rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true"
columnResizeMode="expand" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name','regex_pattern','match_string']">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
Name
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="regex_pattern" pResizableColumn>
Regex Pattern
<p-sortIcon field="regex_pattern"></p-sortIcon>
<p-columnFilter type="text" field="regex_pattern" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="alert_id" pResizableColumn>Alert / Severity <p-sortIcon
field="alert_id"></p-sortIcon></th>
<th pSortableColumn="alert_enabled" pResizableColumn
style="width: 15rem; text-align: center;">Alert Enabled/Type <p-sortIcon
field="alert_enabled"></p-sortIcon></th>
<th pSortableColumn="match_string" pResizableColumn>
Match String
<p-sortIcon field="match_string"></p-sortIcon>
<p-columnFilter type="text" field="match_string" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="created" pResizableColumn>Created <p-sortIcon
field="created"></p-sortIcon></th>
<th style="width: 10rem; text-align: center;" class="p-resizable-column">Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>{{item.name}}</td>
<td><code class="px-1 py-1">{{item.regex_pattern}}</code></td>
<td>
<div class="d-flex flex-row gap-1 align-items-center">
<span class="fw-bold">{{ getAlertName(item.alert_id) }}</span>
<c-badge size="sm"
[color]="getAlertLevel(item.alert_id).toLowerCase() =='critical' ? 'danger' :
getAlertLevel(item.alert_id).toLowerCase() =='warning' ? 'warning' :
getAlertLevel(item.alert_id).toLowerCase() =='info' ? 'info' :
getAlertLevel(item.alert_id).toLowerCase() =='error' ? 'danger' : 'secondary'">{{
getAlertLevel(item.alert_id)}}</c-badge>
</div>
</td>
<td class="text-center">
<i *ngIf="item.alert_enabled" class="fa-solid fa-check mx-1" style="color: green;"></i>
<i *ngIf="!item.alert_enabled" class="fa-solid fa-x mx-1" style="color: red;"></i>
<span *ngIf="item.alert_enabled && item.global_alert"><i
class="fa-solid fa-earth-americas text-primary" title="Global Alert"></i>
Global</span>
<span *ngIf="item.alert_enabled && !item.global_alert"><i
class="fa-solid fa-server text-secondary" title="Device Specific"></i> String
Match</span>
</td>
<td>{{item.match_string}}</td>
<td>{{item.created}}</td>
<td class="text-center">
<button cButton color="primary" size="sm" (click)="Edit_Regex(item,'edit')" class="mx-1"
title="Edit">
<i class="fa-regular fa-pen-to-square"></i>
</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item,false)"
class="mx-1" title="Delete">
<i class="fa-regular fa-trash-can"></i>
</button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="7">No regexes found.</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>

View file

@ -37,7 +37,8 @@ textarea.form-control {
position: relative;
z-index: 2;
background: transparent !important;
color: transparent !important; /* Hide real text to only show highlights behind it, but keep caret visible */
color: transparent !important;
/* Hide real text to only show highlights behind it, but keep caret visible */
caret-color: #000;
}
@ -48,14 +49,16 @@ textarea.form-control {
right: 0;
bottom: 0;
z-index: 1;
padding: 0.375rem 0.75rem; /* Match textarea padding */
font-size: 0.8rem; /* Match small text if needed */
padding: 0.375rem 0.75rem;
/* Match textarea padding */
font-size: 0.8rem;
/* Match small text if needed */
line-height: 1.5;
color: #495057;
background-color: #fff;
white-space: pre-wrap;
word-wrap: break-word;
border: 1px solid transparent;
border: 1px solid transparent;
pointer-events: none;
border-radius: 0.375rem;
overflow-y: auto;
@ -64,19 +67,19 @@ textarea.form-control {
.segment-row {
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid #edf2f7 !important;
&:hover {
background-color: #ffffff !important;
border-color: #90cdf4 !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transform: translateY(-2px);
}
.btn-link {
opacity: 0.3;
transition: opacity 0.2s;
}
&:hover .btn-link {
opacity: 1;
}
@ -87,11 +90,12 @@ textarea.form-control {
height: 8px;
border-radius: 50%;
display: inline-block;
box-shadow: 0 0 5px rgba(0,0,0,0.1);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}
.step-card {
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important;
@ -105,10 +109,17 @@ textarea.form-control {
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-5px); }
to { opacity: 1; transform: translateY(0); }
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fadeIn 0.4s ease-out forwards;
}
}

View file

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewChild } from '@angular/core';
import { Table } from 'primeng/table';
import { dataProvider } from "../../providers/mikrowizard/data";
import { NgxSuperSelectOptions } from "ngx-super-select";
import { ToastComponent } from '@coreui/angular';
@ -16,6 +17,7 @@ interface RegexSegment {
styleUrls: ['./syslog-regex.component.scss']
})
export class SyslogRegexComponent implements OnInit {
@ViewChild('dt') dt: Table | undefined;
public syslogRegexes: any[] = [];
@ -93,6 +95,10 @@ export class SyslogRegexComponent implements OnInit {
this.loadAlerts();
}
applyFilterGlobal($event: any, stringVal: string) {
this.dt!.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
loadRegexes() {
this.MikroWizardRPC.get_syslog_regexes().then(
(res: any) => {

View file

@ -20,8 +20,12 @@ import {
import { HighlightJsModule } from 'ngx-highlight-js';
import { SyslogRegexRoutingModule } from "./syslog-regex-routing.module";
import { SyslogRegexComponent } from "./syslog-regex.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { NgxSuperSelectModule } from "ngx-super-select";
import { TableModule } from 'primeng/table';
import { InputTextModule } from 'primeng/inputtext';
import { MultiSelectModule } from 'primeng/multiselect';
import { TooltipModule } from 'primeng/tooltip';
import { DropdownModule } from 'primeng/dropdown';
@NgModule({
imports: [
@ -32,7 +36,11 @@ import { NgxSuperSelectModule } from "ngx-super-select";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
TableModule,
InputTextModule,
MultiSelectModule,
TooltipModule,
DropdownModule,
ModalModule,
ToastModule,
FormsModule,

View file

@ -8,7 +8,7 @@ const routes: Routes = [
path: '',
component: SyslogComponent,
data: {
title: $localize`Mikrowizard System Logs`
title: $localize`System Logs`
}
}
];

View file

@ -4,9 +4,11 @@
<c-card-header>
<c-row>
<c-col xs [lg]="11" style="display: flex;flex-direction: column;align-items: flex-start;">
<h5>Devices</h5>
<c-alert color="warning" style="padding-top: 5px!important;font-size: 0.8rem;display: inline-block;" *ngIf="!filters['start_time'] && !filters['end_time']">
<i class="fa-solid fa-triangle-exclamation mx-1"></i>Showing <strong>last 24 hours logs</strong> by default. Use filters to modify the date and time.
<h5>System Logs</h5>
<c-alert color="warning" style="padding-top: 5px!important;font-size: 0.8rem;display: inline-block;"
*ngIf="!filters['start_time'] && !filters['end_time']">
<i class="fa-solid fa-triangle-exclamation mx-1"></i>Showing <strong>last 24 hours logs</strong> by
default. Use filters to modify the date and time.
</c-alert>
</c-col>
<c-col xs [lg]="1">
@ -62,44 +64,144 @@
</div>
</c-row>
<gui-grid wid [rowDetail]="rowDetail" [horizontalGrid]="true" [rowHeight]="52" [source]="source"
[columnMenu]="columnMenu" [paging]="paging" [sorting]="sorting" [infoPanel]="infoPanel"
[autoResizeWidth]="true">
<gui-grid-column header="#No" type="NUMBER" field="index" width="1" align="CENTER">
<ng-template let-value="item.index" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search logs..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<gui-grid-column header="username" field="username">
<ng-template let-value="item.username" let-item="item" let-index="index">
<div class="gui-dev-info">
<span class="gui-dev-info-name">{{ value }}</span>
<span class="gui-dev-info-ip">{{ item.first_name }} {{ item.last_name }}</span>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['username','section','action','ip']" selectionMode="single"
(onRowSelect)="showLogDetails($event.data)">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="index" style="width: 5rem" pResizableColumn>
<div class="justify-between">
<span>#No</span>
<p-sortIcon field="index"></p-sortIcon>
</div>
</th>
<th pSortableColumn="username" pResizableColumn>
<div class="justify-between">
<span>Username</span>
<span>
<p-sortIcon field="username"></p-sortIcon>
<p-columnFilter type="text" field="username" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="section" pResizableColumn>
<div class="justify-between">
<span>Section</span>
<span>
<p-sortIcon field="section"></p-sortIcon>
<p-columnFilter type="text" field="section" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="action" pResizableColumn>
<div class="justify-between">
<span>Action</span>
<span>
<p-sortIcon field="action"></p-sortIcon>
<p-columnFilter type="text" field="action" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="ip" pResizableColumn>
<div class="justify-between">
<span>IP</span>
<span>
<p-sortIcon field="ip"></p-sortIcon>
<p-columnFilter type="text" field="ip" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="created" pResizableColumn>
<div class="justify-between">
<span>Time</span>
<span>
<p-sortIcon field="created"></p-sortIcon>
<p-columnFilter type="text" field="created" display="menu" class="ms-auto" />
</span>
</div>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr [pSelectableRow]="item" style="cursor: pointer;">
<td>{{item.index}}</td>
<td>
<div class="gui-dev-info">
<span class="gui-dev-info-name">{{ item.username }}</span>
<span class="gui-dev-info-ip" style="font-size: 0.75rem; color: #666;">{{ item.first_name }} {{
item.last_name }}</span>
</div>
</td>
<td>{{item.section}}</td>
<td>{{item.action}}</td>
<td>{{item.ip}}</td>
<td>{{item.created}}</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="6">No logs found.</td>
</tr>
</ng-template>
</p-table>
<!-- Details Drawer -->
<p-drawer [(visible)]="detailsVisible" position="right" [style]="{width: '450px'}" header="Log Details">
<div *ngIf="selectedLog" class="p-3">
<h5 class="border-bottom pb-2 mb-3"><i class="pi pi-info-circle me-2 text-primary"></i>System Log</h5>
<div class="row mb-2">
<div class="col-4 fw-bold">Section:</div>
<div class="col-8">{{selectedLog.section}}</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">Action:</div>
<div class="col-8">{{selectedLog.action}}</div>
</div>
<div class="row mb-4">
<div class="col-4 fw-bold">Time:</div>
<div class="col-8 text-muted small">{{selectedLog.created}}</div>
</div>
<h5 class="border-bottom pb-2 mb-3 mt-4"><i class="pi pi-user me-2 text-primary"></i>User Detail</h5>
<div class="row mb-2">
<div class="col-4 fw-bold">User:</div>
<div class="col-8">{{selectedLog.username}}</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">Name:</div>
<div class="col-8">{{selectedLog.first_name}} {{selectedLog.last_name}}</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">IP:</div>
<div class="col-8">{{selectedLog.ip}}</div>
</div>
<div class="row mb-2">
<div class="col-4 fw-bold">Agent:</div>
<div class="col-8 small text-wrap text-break" style="max-height: 80px; overflow-y: auto;">
{{selectedLog.agent}}
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Section" field="section">
<ng-template let-value="item.section" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="action" field="action">
<ng-template let-value="item.action" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="ip" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Time" field="created">
<ng-template let-value="item.created" let-item="item" let-index="index">
{{ value }}
</ng-template>
</gui-grid-column>
</gui-grid>
</div>
<h5 class="border-bottom pb-2 mb-3 mt-4"><i class="pi pi-code me-2 text-primary"></i>Raw Data</h5>
<div class="bg-light p-3 rounded"
style="max-height: 250px; overflow-y: auto; font-family: 'Courier New', Courier, monospace; font-size: 0.85rem; border: 1px solid #dee2e6;">
{{selectedLog.data}}
</div>
</div>
</p-drawer>
</c-card-body>
</c-card>

View file

@ -1,18 +1,8 @@
import { Component, OnInit, ViewEncapsulation } from "@angular/core";
import { Component, OnInit, ViewChild, ViewEncapsulation } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router, ActivatedRoute } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiRowDetail,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { formatInTimeZone } from "date-fns-tz";
@ -22,10 +12,11 @@ import { formatInTimeZone } from "date-fns-tz";
encapsulation: ViewEncapsulation.None,
})
export class SyslogComponent implements OnInit {
public uid: number;
public uname: string;
public uid!: number;
public uname!: string;
public tz: string= "UTC";
public filterText: string;
public filterText!: string;
public userid: number = 0;
public filters: any = {
start_time: false,
end_time: false,
@ -36,6 +27,10 @@ export class SyslogComponent implements OnInit {
public event_section: any = [];
public event_action: any = [];
public filters_visible: boolean = false;
public detailsVisible: boolean = false;
public selectedLog: any = null;
@ViewChild('dt') table!: Table;
constructor(
private data_provider: dataProvider,
private router: Router,
@ -66,95 +61,18 @@ export class SyslogComponent implements OnInit {
}
}
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public Selectedrows: any;
public userid: number = 0;
public sorting = {
enabled: true,
multiSorting: true,
};
public campaignOnestart: any;
public campaignOneend: any;
rowDetail: GuiRowDetail = {
enabled: true,
template: (item) => {
return `
<div class='log-detail' style="width: 355px;color:#fff;background-color:#3399ff">
<h2>System Log :</h2>
<table>
<tr>
<td>Section</td>
<td>${item.section}</td>
</tr>
<tr>
<td>Action</td>
<td>${item.action}</td>
</tr>
<tr>
<td>Time</td>
<td>${item.created}</td>
</tr>
</table>
<h2 style="margin-top: 5px;">User Detail :
</h2>
<table>
<tr>
<td>User</td>
<td>${item.username}</td>
</tr>
<tr>
<td>FirstName</td>
<td>${item.first_name}</td>
</tr>
<tr>
<td>LastName</td>
<td>${item.last_name}</td>
</tr>
<tr>
<td>IP</td>
<td>${item.ip}</td>
</tr>
<tr>
<td>Agent</td>
<td><div style="height: 40px;overflow-y: scroll;">${item.agent}</div></td>
</tr>
</table>
<div class="code-title">data</div>
<code>
${item.data}
</code>
</div>`;
},
};
showLogDetails(log: any) {
this.selectedLog = log;
this.detailsVisible = true;
}
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
// Removed legacy GuiGrid configs
ngOnInit(): void {
var _self = this;
this.userid = Number(this.route.snapshot.paramMap.get("userid"));

View file

@ -12,7 +12,9 @@ import {
import { NgxMatSelectSearchModule } from "ngx-mat-select-search";
import { SyslogRoutingModule } from "./syslog-routing.module";
import { SyslogComponent } from "./syslog.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule } from 'primeng/table';
import { DrawerModule } from 'primeng/drawer';
import { InputTextModule } from 'primeng/inputtext';
import { MatDatepickerModule } from "@angular/material/datepicker";
import { MatInputModule } from "@angular/material/input";
import { MatFormFieldModule } from "@angular/material/form-field";
@ -28,7 +30,9 @@ import { MatSelectModule } from "@angular/material/select";
GridModule,
FormsModule,
ButtonModule,
GuiGridModule,
TableModule,
DrawerModule,
InputTextModule,
CollapseModule,
DropdownModule,
MatInputModule,

View file

@ -4,7 +4,7 @@
<c-card-header>
<c-row>
<c-col xs [lg]="10">
Users
User Managment
</c-col>
<c-col xs [lg]="2" style="text-align: right;">
<button cButton color="primary" (click)="editAddUser({},'showadd')"><i
@ -12,41 +12,91 @@
</c-col>
</c-row>
</c-card-header>
<c-card-body >
<gui-grid [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu" [sorting]="sorting"
[autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="User Name" field="username">
<ng-template let-value="item.username" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="First Name" field="first_name">
<ng-template let-value="item.first_name" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Last Name" field="last_name">
<ng-template let-value="item.last_name" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Role" field="role">
<ng-template let-value="item.role" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="warning" size="sm" (click)="editAddUser(item,'edit');" ><i
class="fa-regular fa-pen-to-square"></i></button>
<button cButton color="danger" size="sm" class="mx-1" (click)="confirm_delete(item);"><i
class="fa-regular fa-trash-can"></i></button>
<c-card-body>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search users..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<button *ngIf="ispro" cButton color="secondary" size="sm" (click)="showrest(item);">
<i class="fa-solid fa-fingerprint"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['username', 'first_name', 'last_name', 'role']" [loading]="loading">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="username" pResizableColumn>
<div class="justify-between">
<span>User Name</span>
<span>
<p-sortIcon field="username"></p-sortIcon>
<p-columnFilter type="text" field="username" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="first_name" pResizableColumn>
<div class="justify-between">
<span>First Name</span>
<span>
<p-sortIcon field="first_name"></p-sortIcon>
<p-columnFilter type="text" field="first_name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="last_name" pResizableColumn>
<div class="justify-between">
<span>Last Name</span>
<span>
<p-sortIcon field="last_name"></p-sortIcon>
<p-columnFilter type="text" field="last_name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="role" pResizableColumn>
<div class="justify-between">
<span>Role</span>
<span>
<p-sortIcon field="role"></p-sortIcon>
<p-columnFilter type="text" field="role" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 120px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>{{item.username}}</td>
<td>{{item.first_name}}</td>
<td>{{item.last_name}}</td>
<td>{{item.role}}</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
<button cButton color="warning" size="sm" (click)="editAddUser(item,'edit');" pTooltip="Edit User">
<i class="fa-regular fa-pen-to-square"></i>
</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item);" pTooltip="Delete User">
<i class="fa-regular fa-trash-can"></i>
</button>
<button *ngIf="ispro" cButton color="secondary" size="sm" (click)="showrest(item);"
pTooltip="Security Restrictions">
<i class="fa-solid fa-fingerprint"></i>
</button>
</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="5" class="text-center p-4">No users found.</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>
@ -54,285 +104,304 @@
<c-modal-header>
<c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal">
<c-modal-header>
<h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle><i class="fa-solid fa-user-pen me-1"></i>Edit: {{SelectedUser['username']}}</h5>
<h5 *ngIf="SelectedUser['action']=='add'" cModalTitle><i class="fa-solid fa-user-plus me-1"></i>Add User</h5>
<button [cModalToggle]="EditTaskModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body class="p-3">
<!-- User Details -->
<div class="user-form-section mb-4">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-user me-2"></i>Basic Information</h6>
<small class="text-muted">Enter the user's personal details and login credentials</small>
</div>
<c-row class="g-3">
<c-col xs="12" md="6">
<input cFormControl placeholder="Username (required for login)" [(ngModel)]="SelectedUser['username']"
class="form-input" title="Unique username for system login" />
</c-col>
<c-col xs="12" md="6">
<input cFormControl placeholder="Email Address (for notifications)" [(ngModel)]="SelectedUser['email']"
class="form-input" type="email" title="Valid email address for system notifications" />
</c-col>
<c-col xs="12" md="4">
<input cFormControl placeholder="First Name (optional)" [(ngModel)]="SelectedUser['first_name']"
class="form-input" title="User's first name" />
</c-col>
<c-col xs="12" md="4">
<input cFormControl placeholder="Last Name (optional)" [(ngModel)]="SelectedUser['last_name']"
class="form-input" title="User's last name" />
</c-col>
<c-col xs="12" md="4">
<input type="password" cFormControl placeholder="Password (min 6 characters)" [(ngModel)]="SelectedUser['password']"
class="form-input" title="Secure password for user login" />
</c-col>
</c-row>
</div>
<!-- System Permissions -->
<div class="permissions-section mb-4">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-shield-halved me-2"></i>System Access Levels</h6>
<small class="text-muted">Control what system features this user can access and modify</small>
</div>
<div class="permission-legend mb-2">
<span class="legend-item"><span class="legend-color bg-info"></span>R = Read Only</span>
<span class="legend-item"><span class="legend-color bg-warning"></span>W = Read & Write</span>
<span class="legend-item"><span class="legend-color bg-success"></span>F = Full Control</span>
<span class="legend-item"><span class="legend-color bg-secondary"></span>× = No Access</span>
</div>
<div class="permissions-compact">
<div *ngFor='let perm of adminperms | keyvalue' class="perm-row">
<span class="perm-label">{{ perm.key.replace('_', ' ') }}</span>
<div class="perm-controls">
<button cButton size="sm" variant="outline" color="info" [active]="adminperms[perm.key]=='read'"
(click)="setRadioValue(perm.key,'read')" title="Read Only Access">R</button>
<button cButton size="sm" variant="outline" color="warning" [active]="adminperms[perm.key]=='write'"
(click)="setRadioValue(perm.key,'write')" title="Read & Write Access">W</button>
<button cButton size="sm" variant="outline" color="success" [active]="adminperms[perm.key]=='full'"
(click)="setRadioValue(perm.key,'full')" title="Full Control Access">F</button>
<button cButton size="sm" variant="outline" color="secondary" [active]="adminperms[perm.key]=='none'"
(click)="setRadioValue(perm.key,'none')" title="No Access">×</button>
</div>
<c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal">
<c-modal-header>
<h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle><i class="fa-solid fa-user-pen me-1"></i>Edit:
{{SelectedUser['username']}}</h5>
<h5 *ngIf="SelectedUser['action']=='add'" cModalTitle><i class="fa-solid fa-user-plus me-1"></i>Add User</h5>
<button [cModalToggle]="EditTaskModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body class="p-3">
<!-- User Details -->
<div class="user-form-section mb-4">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-user me-2"></i>Basic Information</h6>
<small class="text-muted">Enter the user's personal details and login credentials</small>
</div>
</div>
</div>
<!-- Device Permissions -->
<div class="device-permissions-section">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-network-wired me-2"></i>Device Access Control</h6>
<small class="text-muted">Grant access to specific device groups with defined permission levels</small>
</div>
<!-- Add New Permission -->
<div class="add-permission-card mb-3">
<div class="add-header">
<i class="fa-solid fa-plus-circle text-primary me-2"></i>
<span class="fw-semibold">Add Device Permission</span>
</div>
<c-row class="g-2 mt-2">
<c-col xs="12" md="5">
<div class="search-select-wrapper">
<label class="search-label">Device Group</label>
<input cFormControl
[(ngModel)]="devgroupSearch"
(input)="filterDevGroups($event)"
(focus)="showDevGroupDropdown = true"
(blur)="hideDevGroupDropdown()"
placeholder="Search and select device group..."
class="search-input compact-select"
autocomplete="off" />
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length > 0" class="search-dropdown">
<div *ngFor="let group of filteredDevGroups"
class="search-option"
(mousedown)="selectDevGroup(group)">
{{group.name}}
</div>
</div>
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length === 0 && devgroupSearch" class="search-no-results">
No groups found
</div>
</div>
<c-row class="g-3">
<c-col xs="12" md="6">
<input cFormControl placeholder="Username (required for login)" [(ngModel)]="SelectedUser['username']"
class="form-input" title="Unique username for system login" />
</c-col>
<c-col xs="12" md="5">
<div class="search-select-wrapper">
<label class="search-label">Permission Level</label>
<input cFormControl
[(ngModel)]="permissionSearch"
(input)="filterPermissions($event)"
(focus)="showPermissionDropdown = true"
(blur)="hidePermissionDropdown()"
placeholder="Search and select permission..."
class="search-input compact-select"
autocomplete="off" />
<div *ngIf="showPermissionDropdown && filteredPermissions.length > 0" class="search-dropdown">
<div *ngFor="let perm of filteredPermissions"
class="search-option"
(mousedown)="selectPermission(perm)">
{{perm.name}}
</div>
</div>
<div *ngIf="showPermissionDropdown && filteredPermissions.length === 0 && permissionSearch" class="search-no-results">
No permissions found
</div>
</div>
<c-col xs="12" md="6">
<input cFormControl placeholder="Email Address (for notifications)" [(ngModel)]="SelectedUser['email']"
class="form-input" type="email" title="Valid email address for system notifications" />
</c-col>
<c-col xs="12" md="2" class="d-flex align-items-end">
<button *ngIf="SelectedUser['action']=='edit'" cButton color="success" size="sm" class="w-100 add-btn"
(click)="add_user_perm()" [disabled]="!devgroup || !permission">
<i class="fa-solid fa-plus me-1"></i>Add Access
</button>
<button *ngIf="SelectedUser['action']=='add'" cButton color="success" size="sm" class="w-100 add-btn"
(click)="add_new_user_perm()" [disabled]="!devgroup || !permission">
<i class="fa-solid fa-plus me-1"></i>Add Access
</button>
<c-col xs="12" md="4">
<input cFormControl placeholder="First Name (optional)" [(ngModel)]="SelectedUser['first_name']"
class="form-input" title="User's first name" />
</c-col>
<c-col xs="12" md="4">
<input cFormControl placeholder="Last Name (optional)" [(ngModel)]="SelectedUser['last_name']"
class="form-input" title="User's last name" />
</c-col>
<c-col xs="12" md="4">
<input type="password" cFormControl placeholder="Password (min 6 characters)"
[(ngModel)]="SelectedUser['password']" class="form-input" title="Secure password for user login" />
</c-col>
</c-row>
</div>
<!-- Current Permissions -->
<div class="current-permissions">
<div class="permissions-header">
<h6 class="mb-1"><i class="fa-solid fa-list-check me-2 text-success"></i>Current Device Access</h6>
<span class="badge bg-info">{{userperms.length}} permission(s)</span>
<!-- System Permissions -->
<div class="permissions-section mb-4">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-shield-halved me-2"></i>System Access Levels</h6>
<small class="text-muted">Control what system features this user can access and modify</small>
</div>
<div *ngIf="userperms.length>0" class="permissions-list mt-2">
<div *ngFor="let perm of userperms; let i = index" class="permission-item">
<div class="perm-number">{{i + 1}}</div>
<div class="perm-info">
<div class="perm-main">
<i class="fa-solid fa-server me-1 text-primary"></i>
<span class="group-name">{{perm.group_name}}</span>
</div>
<div class="perm-detail">
<i class="fa-solid fa-key me-1 text-warning"></i>
<span class="perm-name">{{perm.perm_name}}</span>
</div>
<div class="permission-legend mb-2">
<span class="legend-item"><span class="legend-color bg-info"></span>R = Read Only</span>
<span class="legend-item"><span class="legend-color bg-warning"></span>W = Read & Write</span>
<span class="legend-item"><span class="legend-color bg-success"></span>F = Full Control</span>
<span class="legend-item"><span class="legend-color bg-secondary"></span>× = No Access</span>
</div>
<div class="permissions-compact">
<div *ngFor='let perm of adminperms | keyvalue' class="perm-row">
<span class="perm-label">{{ perm.key.replace('_', ' ') }}</span>
<div class="perm-controls">
<button cButton size="sm" variant="outline" color="info" [active]="adminperms[perm.key]=='read'"
(click)="setRadioValue(perm.key,'read')" title="Read Only Access">R</button>
<button cButton size="sm" variant="outline" color="warning" [active]="adminperms[perm.key]=='write'"
(click)="setRadioValue(perm.key,'write')" title="Read & Write Access">W</button>
<button cButton size="sm" variant="outline" color="success" [active]="adminperms[perm.key]=='full'"
(click)="setRadioValue(perm.key,'full')" title="Full Control Access">F</button>
<button cButton size="sm" variant="outline" color="secondary" [active]="adminperms[perm.key]=='none'"
(click)="setRadioValue(perm.key,'none')" title="No Access">×</button>
</div>
<button cButton color="danger" size="sm" variant="outline" (click)="confirm_delete_perm(perm)"
title="Remove this permission" class="remove-btn">
<i class="fa-solid fa-trash-can"></i>
</button>
</div>
</div>
<div *ngIf="userperms.length==0" class="empty-permissions">
<div class="empty-icon">
<i class="fa-solid fa-shield-xmark text-muted"></i>
</div>
<div class="empty-text">
<strong>No device access granted</strong>
<p class="mb-0 text-muted">Add permissions above to grant access to device groups</p>
</div>
</div>
</div>
</div>
</c-modal-body>
<c-modal-footer class="d-flex justify-content-between flex-wrap gap-2 p-2">
<div>
<button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton color="danger" size="sm">
<i class="fa-solid fa-user-slash me-1"></i><span class="d-none d-sm-inline">Deactivate</span>
</button>
<button *ngIf="SelectedUser['role']=='disabled'" (click)="SelectedUser['role']='admin'" cButton color="success" size="sm">
<i class="fa-solid fa-user-check me-1"></i><span class="d-none d-sm-inline">Activate</span>
</button>
</div>
<div class="d-flex gap-2">
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary" size="sm">
<i class="fa-solid fa-plus me-1"></i>Add
</button>
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary" size="sm">
<i class="fa-solid fa-save me-1"></i>Save
</button>
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary" size="sm">
<i class="fa-solid fa-times me-1"></i>Close
</button>
</div>
</c-modal-footer>
</c-modal>
<c-modal #RestrictionsTaskModal *ngIf="ispro && userresttrictions" backdrop="static" size="lg" [(visible)]="RestrictionsTaskModalVisible" id="RestrictionsTaskModal">
<c-modal-header>
<h5 cModalTitle>Security Restrictions of {{SelectedUser['username']}}</h5>
</c-modal-header>
<c-modal-body>
<table width="100%">
<tr>
<td><h6>TOTP status :</h6></td>
<td>
<c-form-check sizing="xl" switch>
<input cFormCheckInput [(ngModel)]="userresttrictions['totp']" [checked]="userresttrictions['totp']" type="checkbox" />
<label *ngIf="userresttrictions['totp']" cFormCheckLabel> TOTP is active</label>
<label *ngIf="!userresttrictions['totp']" cFormCheckLabel> TOTP is deactive</label>
</c-form-check>
</td>
</tr>
<tr>
<td><h6>Use OTP for device login:</h6></td>
<td>
<c-button-group aria-label="Basic example" role="group">
<button cButton color="info" variant="outline" size="sm" [active]="userresttrictions['device-totp']=='system'"
(click)="userresttrictions['device-totp']='system'">System Defined</button>
<button cButton color="danger" variant="outline" size="sm" [active]="userresttrictions['device-totp']=='yes'"
(click)="userresttrictions['device-totp']='yes'">TOTP</button>
<button cButton color="success" variant="outline" size="sm" [active]="userresttrictions['device-totp']=='no'"
(click)="userresttrictions['device-totp']='no'">Password</button>
</c-button-group>
</td>
</tr>
<tr>
<td><h6>Restrict IP access:</h6></td>
<td>
<c-form-check sizing="xl" switch>
<input cFormCheckInput [(ngModel)]="userresttrictions['ip']" [checked]="userresttrictions['ip']" type="checkbox" />
<label *ngIf="userresttrictions['ip']" cFormCheckLabel> Restricted</label>
<label *ngIf="!userresttrictions['ip']" cFormCheckLabel> Not Restricted</label>
</c-form-check>
</td>
</tr>
</table>
<c-input-group *ngIf="userresttrictions['ip'] && userresttrictions['allowed_ips'].length>0" class="mb-3">
<h5>Allowed ips :</h5>
<gui-grid [autoResizeWidth]="true" [source]="userresttrictions['allowed_ips']" [columnMenu]="columnMenu" [sorting]="sorting"
[autoResizeWidth]=true [paging]="paging" >
<gui-grid-column header="IP Address" >
<ng-template let-value="item" let-item="item" let-index="index">
&nbsp; {{item}} </ng-template>
</gui-grid-column>
<gui-grid-column header="Action" width="80" align="center">
<ng-template let-value="item" let-item="item" let-index="index">
<button cButton color="danger" (click)="delete_ip(item)"><i class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-input-group>
<hr />
<table *ngIf="userresttrictions['ip']" class="mb-3">
<td style="width: 30%;">
<span>Add new IP</span>
</td>
<td>
<div >
<input cFormControl id="floatingInput" placeholder="IP address/cidr" [(ngModel)]="ipaddress" />
<!-- Device Permissions -->
<div class="device-permissions-section">
<div class="section-header mb-3">
<h6 class="section-title mb-1"><i class="fa-solid fa-network-wired me-2"></i>Device Access Control</h6>
<small class="text-muted">Grant access to specific device groups with defined permission levels</small>
</div>
</td>
<td style="vertical-align: top;">
<button cButton color="primary" (click)="add_ip()">Add+</button>
</td>
</table>
</c-modal-body>
<c-modal-footer>
<button (click)="save_sec()" cButton color="primary">Save</button>
<button [cModalToggle]="RestrictionsTaskModal.id" cButton color="secondary">
Close
</button>
</c-modal-footer>
</c-modal>
<!-- Add New Permission -->
<div class="add-permission-card mb-3">
<div class="add-header">
<i class="fa-solid fa-plus-circle text-primary me-2"></i>
<span class="fw-semibold">Add Device Permission</span>
</div>
<c-row class="g-2 mt-2">
<c-col xs="12" md="5">
<div class="search-select-wrapper">
<label class="search-label">Device Group</label>
<input cFormControl [(ngModel)]="devgroupSearch" (input)="filterDevGroups($event)"
(focus)="showDevGroupDropdown = true" (blur)="hideDevGroupDropdown()"
placeholder="Search and select device group..." class="search-input compact-select"
autocomplete="off" />
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length > 0" class="search-dropdown">
<div *ngFor="let group of filteredDevGroups" class="search-option"
(mousedown)="selectDevGroup(group)">
{{group.name}}
</div>
</div>
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length === 0 && devgroupSearch"
class="search-no-results">
No groups found
</div>
</div>
</c-col>
<c-col xs="12" md="5">
<div class="search-select-wrapper">
<label class="search-label">Permission Level</label>
<input cFormControl [(ngModel)]="permissionSearch" (input)="filterPermissions($event)"
(focus)="showPermissionDropdown = true" (blur)="hidePermissionDropdown()"
placeholder="Search and select permission..." class="search-input compact-select"
autocomplete="off" />
<div *ngIf="showPermissionDropdown && filteredPermissions.length > 0" class="search-dropdown">
<div *ngFor="let perm of filteredPermissions" class="search-option"
(mousedown)="selectPermission(perm)">
{{perm.name}}
</div>
</div>
<div *ngIf="showPermissionDropdown && filteredPermissions.length === 0 && permissionSearch"
class="search-no-results">
No permissions found
</div>
</div>
</c-col>
<c-col xs="12" md="2" class="d-flex align-items-end">
<button *ngIf="SelectedUser['action']=='edit'" cButton color="success" size="sm" class="w-100 add-btn"
(click)="add_user_perm()" [disabled]="!devgroup || !permission">
<i class="fa-solid fa-plus me-1"></i>Add Access
</button>
<button *ngIf="SelectedUser['action']=='add'" cButton color="success" size="sm" class="w-100 add-btn"
(click)="add_new_user_perm()" [disabled]="!devgroup || !permission">
<i class="fa-solid fa-plus me-1"></i>Add Access
</button>
</c-col>
</c-row>
</div>
<!-- Current Permissions -->
<div class="current-permissions">
<div class="permissions-header">
<h6 class="mb-1"><i class="fa-solid fa-list-check me-2 text-success"></i>Current Device Access</h6>
<span class="badge bg-info">{{userperms.length}} permission(s)</span>
</div>
<div *ngIf="userperms.length>0" class="permissions-list mt-2">
<div *ngFor="let perm of userperms; let i = index" class="permission-item">
<div class="perm-number">{{i + 1}}</div>
<div class="perm-info">
<div class="perm-main">
<i class="fa-solid fa-server me-1 text-primary"></i>
<span class="group-name">{{perm.group_name}}</span>
</div>
<div class="perm-detail">
<i class="fa-solid fa-key me-1 text-warning"></i>
<span class="perm-name">{{perm.perm_name}}</span>
</div>
</div>
<button cButton color="danger" size="sm" variant="outline" (click)="confirm_delete_perm(perm)"
title="Remove this permission" class="remove-btn">
<i class="fa-solid fa-trash-can"></i>
</button>
</div>
</div>
<div *ngIf="userperms.length==0" class="empty-permissions">
<div class="empty-icon">
<i class="fa-solid fa-shield-xmark text-muted"></i>
</div>
<div class="empty-text">
<strong>No device access granted</strong>
<p class="mb-0 text-muted">Add permissions above to grant access to device groups</p>
</div>
</div>
</div>
</div>
</c-modal-body>
<c-modal-footer class="d-flex justify-content-between flex-wrap gap-2 p-2">
<div>
<button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton
color="danger" size="sm">
<i class="fa-solid fa-user-slash me-1"></i><span class="d-none d-sm-inline">Deactivate</span>
</button>
<button *ngIf="SelectedUser['role']=='disabled'" (click)="SelectedUser['role']='admin'" cButton color="success"
size="sm">
<i class="fa-solid fa-user-check me-1"></i><span class="d-none d-sm-inline">Activate</span>
</button>
</div>
<div class="d-flex gap-2">
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary" size="sm">
<i class="fa-solid fa-plus me-1"></i>Add
</button>
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary" size="sm">
<i class="fa-solid fa-save me-1"></i>Save
</button>
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary" size="sm">
<i class="fa-solid fa-times me-1"></i>Close
</button>
</div>
</c-modal-footer>
</c-modal>
<c-modal #RestrictionsTaskModal *ngIf="ispro && userresttrictions" backdrop="static" size="lg"
[(visible)]="RestrictionsTaskModalVisible" id="RestrictionsTaskModal">
<c-modal-header>
<h5 cModalTitle>Security Restrictions of {{SelectedUser['username']}}</h5>
</c-modal-header>
<c-modal-body>
<table width="100%">
<tr>
<td>
<h6>TOTP status :</h6>
</td>
<td>
<c-form-check sizing="xl" switch>
<input cFormCheckInput [(ngModel)]="userresttrictions['totp']" [checked]="userresttrictions['totp']"
type="checkbox" />
<label *ngIf="userresttrictions['totp']" cFormCheckLabel> TOTP is active</label>
<label *ngIf="!userresttrictions['totp']" cFormCheckLabel> TOTP is deactive</label>
</c-form-check>
</td>
</tr>
<tr>
<td>
<h6>Use OTP for device login:</h6>
</td>
<td>
<c-button-group aria-label="Basic example" role="group">
<button cButton color="info" variant="outline" size="sm"
[active]="userresttrictions['device-totp']=='system'"
(click)="userresttrictions['device-totp']='system'">System Defined</button>
<button cButton color="danger" variant="outline" size="sm"
[active]="userresttrictions['device-totp']=='yes'"
(click)="userresttrictions['device-totp']='yes'">TOTP</button>
<button cButton color="success" variant="outline" size="sm"
[active]="userresttrictions['device-totp']=='no'"
(click)="userresttrictions['device-totp']='no'">Password</button>
</c-button-group>
</td>
</tr>
<tr>
<td>
<h6>Restrict IP access:</h6>
</td>
<td>
<c-form-check sizing="xl" switch>
<input cFormCheckInput [(ngModel)]="userresttrictions['ip']" [checked]="userresttrictions['ip']"
type="checkbox" />
<label *ngIf="userresttrictions['ip']" cFormCheckLabel> Restricted</label>
<label *ngIf="!userresttrictions['ip']" cFormCheckLabel> Not Restricted</label>
</c-form-check>
</td>
</tr>
</table>
<c-input-group *ngIf="userresttrictions['ip'] && userresttrictions['allowed_ips'].length>0" class="mb-3">
<h5>Allowed ips :</h5>
<p-table [value]="userresttrictions['allowed_ips']" [paginator]="true" [rows]="5" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm" class="w-100">
<ng-template pTemplate="header">
<tr>
<th>IP Address</th>
<th style="width: 80px" class="text-center">Action</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>{{item}}</td>
<td class="text-center">
<button cButton color="danger" size="sm" (click)="delete_ip(item)" pTooltip="Remove IP">
<i class="fa-regular fa-trash-can"></i>
</button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="2" class="text-center p-2">No restricted IPs.</td>
</tr>
</ng-template>
</p-table>
</c-input-group>
<hr />
<table *ngIf="userresttrictions['ip']" class="mb-3">
<td style="width: 30%;">
<span>Add new IP</span>
</td>
<td>
<div>
<input cFormControl id="floatingInput" placeholder="IP address/cidr" [(ngModel)]="ipaddress" />
</div>
</td>
<td style="vertical-align: top;">
<button cButton color="primary" (click)="add_ip()">Add+</button>
</td>
</table>
</c-modal-body>
<c-modal-footer>
<button (click)="save_sec()" cButton color="primary">Save</button>
<button [cModalToggle]="RestrictionsTaskModal.id" cButton color="secondary">
Close
</button>
</c-modal-footer>
</c-modal>
@ -376,7 +445,8 @@
</c-modal-footer>
</c-modal>
<c-modal #DeletePermConfirmModal backdrop="static" [(visible)]="DeletePermConfirmModalVisible" id="DeletePermConfirmModal">
<c-modal #DeletePermConfirmModal backdrop="static" [(visible)]="DeletePermConfirmModalVisible"
id="DeletePermConfirmModal">
<c-modal-header>
<h5 cModalTitle>Confirm delete {{ SelectedUser['name'] }}</h5>
<button [cModalToggle]="DeletePermConfirmModal.id" cButtonClose></button>

View file

@ -1,17 +1,8 @@
import { Component, OnInit, QueryList, ViewChildren } from "@angular/core";
import { Component, OnInit, QueryList, ViewChildren, ViewChild } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiGridComponent,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { NgxSuperSelectOptions } from "ngx-super-select";
import { AppToastComponent } from "../toast-simple/toast.component";
import { ToasterComponent } from "@coreui/angular";
@ -21,11 +12,11 @@ import { ToasterComponent } from "@coreui/angular";
styleUrls: ["user_manager.scss"],
})
export class UserManagerComponent implements OnInit {
public uid: number;
public uname: string;
public ispro:boolean=false;
public uid: number = 0;
public uname: string = '';
public ispro: boolean = false;
gridComponent: GuiGridComponent;
@ViewChild('dt') table!: Table;
toasterForm = {
autohide: true,
delay: 10000,
@ -64,7 +55,6 @@ export class UserManagerComponent implements OnInit {
}
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = false;
public rows: any = [];
public SelectedUser: any = {};
@ -87,8 +77,8 @@ export class UserManagerComponent implements OnInit {
public DeletePermConfirmModalVisible: boolean = false;
public userperms: any = {};
public userresttrictions: any = false;
public ipaddress:string="";
public adminperms: { [index: string]: string };
public ipaddress: string = "";
public adminperms: { [index: string]: string } = {};
public defadminperms: { [index: string]: string } = {
device: "none",
device_group: "none",
@ -103,11 +93,6 @@ export class UserManagerComponent implements OnInit {
system_backup: "none",
};
public sorting = {
enabled: true,
multiSorting: true,
};
options: Partial<NgxSuperSelectOptions> = {
actionsEnabled: false,
displayExpr: "name",
@ -117,34 +102,20 @@ export class UserManagerComponent implements OnInit {
enableDarkMode: false,
};
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
setRadioValue(key: string, value: string): void {
this.adminperms[key] = value;
}
filterDevGroups(event: any): void {
const query = event.target.value.toLowerCase();
this.filteredDevGroups = this.allDevGroups.filter((group: any) =>
this.filteredDevGroups = this.allDevGroups.filter((group: any) =>
group.name.toLowerCase().includes(query)
);
}
filterPermissions(event: any): void {
const query = event.target.value.toLowerCase();
this.filteredPermissions = this.allPerms.filter((perm: any) =>
this.filteredPermissions = this.allPerms.filter((perm: any) =>
perm.name.toLowerCase().includes(query)
);
}
@ -169,11 +140,9 @@ export class UserManagerComponent implements OnInit {
setTimeout(() => this.showPermissionDropdown = false, 200);
}
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
ngOnInit(): void {
this.initGridTable();
@ -188,12 +157,12 @@ export class UserManagerComponent implements OnInit {
);
componentRef.instance["closeButton"] = props.closeButton;
}
totp(item:any){
totp(item: any) {
this.SelectedUser = item;
this.data_provider.totp('enable',this.SelectedUser.id).then((res) => {
if(res.status == "success"){
this.data_provider.totp('enable', this.SelectedUser.id).then((res) => {
if (res.status == "success") {
this.show_toast("Success", "Totp generated successfully", "success");
}else{
} else {
this.show_toast("Error", res.err, "danger");
}
});
@ -218,15 +187,15 @@ export class UserManagerComponent implements OnInit {
"danger"
);
}
else{
if ("id" in res && !("status" in res)) {
_self.initGridTable();
this.EditTaskModalVisible = false;
} else {
//show error
_self.show_toast("Error", res.err, "danger");
else {
if ("id" in res && !("status" in res)) {
_self.initGridTable();
this.EditTaskModalVisible = false;
} else {
//show error
_self.show_toast("Error", res.err, "danger");
}
}
}
});
} else {
if (_self.userperms.length > 0) {
@ -243,9 +212,9 @@ export class UserManagerComponent implements OnInit {
"danger"
);
}
else{
_self.initGridTable();
_self.EditTaskModalVisible = false;
else {
_self.initGridTable();
_self.EditTaskModalVisible = false;
}
});
}
@ -262,27 +231,30 @@ export class UserManagerComponent implements OnInit {
"danger"
);
}
else{
_self.allPerms = res.map((x: any) => {
return { id: x["id"], name: x.name };
});
_self.filteredPermissions = [..._self.allPerms];
_self.data_provider.get_devgroup_list().then((res) => {
if ("error" in res && res.error.indexOf("Unauthorized")) {
_self.show_toast(
"Error",
"You are not authorized to perform this action",
"danger"
);
}
else{
_self.allDevGroups = res.map((x: any) => {
else if ("err" in res) {
_self.show_toast("Error", res.err, "danger");
}
else {
_self.allPerms = res.map((x: any) => {
return { id: x["id"], name: x.name };
});
_self.filteredDevGroups = [..._self.allDevGroups];
_self.filteredPermissions = [..._self.allPerms];
_self.data_provider.get_devgroup_list().then((res) => {
if ("error" in res && res.error.indexOf("Unauthorized")) {
_self.show_toast(
"Error",
"You are not authorized to perform this action",
"danger"
);
}
else {
_self.allDevGroups = res.map((x: any) => {
return { id: x["id"], name: x.name };
});
_self.filteredDevGroups = [..._self.allDevGroups];
}
});
}
});
}
});
if (action == "showadd") {
this.userperms = [];
@ -303,6 +275,10 @@ export class UserManagerComponent implements OnInit {
this.EditTaskModalVisible = true;
return;
}
if (item.username == "system") {
this.show_toast("Error", "System user cannot be edited", "danger");
return;
}
this.SelectedUser = { ...item };
if (this.SelectedUser["adminperms"].length > 0) {
this.adminperms = JSON.parse(this.SelectedUser["adminperms"]);
@ -316,13 +292,13 @@ export class UserManagerComponent implements OnInit {
_self.EditTaskModalVisible = true;
}
checkIpAddress(ip:string) {
checkIpAddress(ip: string) {
const ipv4Pattern = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|\/|)){4}\b(0?[1-9]|1[0-9]|2[0-9]|3[0-2])\b$/;
return ipv4Pattern.test(ip)
}
showrest(item: any) {
var _self=this;
var _self = this;
this.SelectedUser = { ...item };
this.data_provider.get_user_restrictions(this.SelectedUser["id"]).then((res) => {
@ -330,33 +306,33 @@ export class UserManagerComponent implements OnInit {
_self.RestrictionsTaskModalVisible = true;
});
}
delete_ip(item:string){
this.userresttrictions['allowed_ips']=this.userresttrictions['allowed_ips'].filter((x:any)=>x!=item);
delete_ip(item: string) {
this.userresttrictions['allowed_ips'] = this.userresttrictions['allowed_ips'].filter((x: any) => x != item);
}
add_ip(){
add_ip() {
//check if ip address is valid cidr and not added before
let ip=this.ipaddress.trim();
if(ip=="")return;
if(this.userresttrictions['allowed_ips'].includes(ip)){
let ip = this.ipaddress.trim();
if (ip == "") return;
if (this.userresttrictions['allowed_ips'].includes(ip)) {
this.show_toast("Error", "IP already added", "danger");
return;
}
//check if ip is valid cidr ip
if(this.checkIpAddress(ip)){
if (this.checkIpAddress(ip)) {
this.userresttrictions['allowed_ips'].push(ip);
this.userresttrictions['allowed_ips']=this.userresttrictions['allowed_ips'].filter((x:any)=>x!="");
this.ipaddress="";
this.userresttrictions['allowed_ips'] = this.userresttrictions['allowed_ips'].filter((x: any) => x != "");
this.ipaddress = "";
}
else{
else {
this.show_toast("Error", "Invalid IP address", "danger");
}
}
save_sec(){
var _self=this;
this.data_provider.save_user_restrictions(this.SelectedUser.id,this.userresttrictions).then((res) => {
save_sec() {
var _self = this;
this.data_provider.save_user_restrictions(this.SelectedUser.id, this.userresttrictions).then((res) => {
if ("error" in res && res.error.indexOf("Unauthorized")) {
_self.show_toast(
"Error",
@ -364,17 +340,17 @@ export class UserManagerComponent implements OnInit {
"danger"
);
}
else{
if('status' in res && res['status']=='success')
else {
if ('status' in res && res['status'] == 'success')
this.RestrictionsTaskModalVisible = false;
else if('status' in res && res['status']=='failed')
else if ('status' in res && res['status'] == 'failed')
this.show_toast("Error", res.err, "danger");
else
else
this.show_toast("Error", "Somthing went wrong", "danger");
}
});
}
add_user_perm() {
var _self = this;
this.data_provider
@ -391,10 +367,10 @@ export class UserManagerComponent implements OnInit {
"danger"
);
}
else{
_self.get_user_perms(_self.SelectedUser["id"]);
_self.permission = 0;
_self.devgroup = 0;
else {
_self.get_user_perms(_self.SelectedUser["id"]);
_self.permission = 0;
_self.devgroup = 0;
}
});
}
@ -425,9 +401,12 @@ export class UserManagerComponent implements OnInit {
"danger"
);
}
else{
_self.initGridTable();
_self.DeleteConfirmModalVisible = false;
else if ("err" in res) {
_self.show_toast("Error", res.err, "danger");
}
else {
_self.initGridTable();
_self.DeleteConfirmModalVisible = false;
}
});
}
@ -451,12 +430,12 @@ export class UserManagerComponent implements OnInit {
"danger"
);
}
else{
this.get_user_perms(this.SelectedUser["id"]);
else {
this.get_user_perms(this.SelectedUser["id"]);
}
});
}
logger(item: any) {
console.dir(item);
}
@ -474,13 +453,13 @@ export class UserManagerComponent implements OnInit {
"danger"
);
}
else{
_self.source = res.map((x: any) => {
return x;
});
_self.SelectedUser = {};
_self.loading = false;
}
else {
_self.source = res.map((x: any) => {
return x;
});
_self.SelectedUser = {};
_self.loading = false;
}
});
}
}

View file

@ -16,7 +16,9 @@ import { NgxMatSelectSearchModule } from "ngx-mat-select-search";
import { UserManagerRoutingModule } from "./user_manager-routing.module";
import { UserManagerComponent } from "./user_manager.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule } from 'primeng/table';
import { InputTextModule } from 'primeng/inputtext';
import { TooltipModule } from 'primeng/tooltip';
@NgModule({
imports: [
@ -29,7 +31,9 @@ import { GuiGridModule } from "@generic-ui/ngx-grid";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
TableModule,
InputTextModule,
TooltipModule,
ModalModule,
FormsModule,
ToastModule,

View file

@ -14,44 +14,90 @@
</c-row>
</c-card-header>
<c-card-body>
<gui-grid [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<i *ngIf="item.task_type=='snippet'" class="fa-solid fa-code"></i>
<i *ngIf="item.task_type=='backup'" class="fa-solid fa-database"></i>
<i *ngIf="item.task_type=='firmware'" class="fa-solid fa-upload"></i>
<i *ngIf="item.task_type=='sequence'" class="fa-solid fa-code-branch"></i>
&nbsp; {{value}} </ng-template>
</gui-grid-column>
<gui-grid-column header="Description" field="description">
<ng-template let-value="item.description" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Members type" field="selection_type">
<ng-template let-value="item.selection_type" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left table-search-input">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search tasks..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<gui-grid-column header="Runtime" field="desc_cron">
<ng-template let-value="item.desc_cron" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name', 'description', 'desc_cron']" [loading]="loading">
<gui-grid-column header="Actions" width="120" field="action">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="warning" size="sm" (click)="editAddTask(item,'edit');"><i
class="fa-regular fa-pen-to-square"></i></button>
<!-- <button cButton color="info" size="sm" (click)="confirm_run(item);" class="mx-1"><i
class="fa-solid fa-bolt"></i></button> -->
<button class=" mx-1" cButton color="danger" size="sm" (click)="confirm_delete(item);"><i
class="fa-regular fa-trash-can"></i></button>
</ng-template>
</gui-grid-column>
</gui-grid>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
<div class="justify-between">
<span>Name</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="description" pResizableColumn>
<div class="justify-between">
<span>Description</span>
<span>
<p-sortIcon field="description"></p-sortIcon>
<p-columnFilter type="text" field="description" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="selection_type" pResizableColumn>
<div class="justify-between">
<span>Members type</span>
<span>
<p-sortIcon field="selection_type"></p-sortIcon>
<p-columnFilter type="text" field="selection_type" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="desc_cron" pResizableColumn>
<div class="justify-between">
<span>Runtime</span>
<p-sortIcon field="desc_cron"></p-sortIcon>
<p-columnFilter type="text" field="desc_cron" display="menu" class="ms-auto" />
</div>
</th>
<th style="width: 120px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>
<i *ngIf="item.task_type=='snippet'" class="fa-solid fa-code me-1"></i>
<i *ngIf="item.task_type=='backup'" class="fa-solid fa-database me-1"></i>
<i *ngIf="item.task_type=='firmware'" class="fa-solid fa-upload me-1"></i>
<i *ngIf="item.task_type=='sequence'" class="fa-solid fa-code-branch me-1"></i>
<strong>{{item.name}}</strong>
</td>
<td>{{item.description}}</td>
<td><c-badge color="info" variant="outline">{{item.selection_type}}</c-badge></td>
<td>{{item.desc_cron}}</td>
<td class="text-center">
<div class="d-flex gap-1 justify-content-center">
<button cButton color="warning" size="sm" (click)="editAddTask(item,'edit');" pTooltip="Edit Task">
<i class="fa-regular fa-pen-to-square"></i>
</button>
<button cButton color="danger" size="sm" (click)="confirm_delete(item);" pTooltip="Delete Task">
<i class="fa-regular fa-trash-can"></i>
</button>
</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="5" class="text-center p-4">No tasks found.</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>
@ -299,31 +345,42 @@
select targets for this task</p>
</div>
<div *ngIf="SelectedMembers.length > 0">
<gui-grid [autoResizeWidth]="true" [source]="SelectedMembers" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [autoResizeWidth]=true [paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i
class="fa-solid fa-{{SelectedTask['selection_type'] === 'devices' ? 'server' : 'layer-group'}} me-2 text-primary"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<small class="text-muted">{{value}}</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="100" field="action" align="CENTER">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_member(item)"
title="Remove from task">
<i class="fa-solid fa-times"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table [value]="SelectedMembers" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
<th *ngIf="SelectedTask['selection_type']=='devices'" pSortableColumn="mac">MAC Address <p-sortIcon
field="mac"></p-sortIcon></th>
<th style="width: 100px" class="text-center">Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>
<div class="d-flex align-items-center">
<i
class="fa-solid fa-{{SelectedTask['selection_type'] === 'devices' ? 'server' : 'layer-group'}} me-2 text-primary"></i>
<strong>{{item.name}}</strong>
</div>
</td>
<td *ngIf="SelectedTask['selection_type']=='devices'"><small class="text-muted">{{item.mac}}</small>
</td>
<td class="text-center">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_member(item)"
pTooltip="Remove from task">
<i class="fa-solid fa-times"></i>
</button>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td [attr.colspan]="SelectedTask['selection_type']=='devices' ? 3 : 2" class="text-center p-2">No
targets selected.</td>
</tr>
</ng-template>
</p-table>
</div>
</c-card-body>
</c-card>
@ -404,30 +461,50 @@
<p class="mb-0">No available {{SelectedTask['selection_type']}} to add to this task</p>
</div>
<div *ngIf="availbleMembers.length > 0">
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true
[paging]="paging">
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i
class="fa-solid fa-{{SelectedTask['selection_type'] === 'devices' ? 'server' : 'layer-group'}} me-2 text-primary"></i>
<strong>{{value}}</strong>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
<small class="text-muted">{{value}}</small>
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end w-100">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search available..."
(input)="applyFilterNewMembers($event, 'contains')" class="form-control-sm" />
</span>
</div>
<p-table #dtNewMembers [value]="availbleMembers" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm" [(selection)]="NewMemberRows"
(selectionChange)="onSelectedRowsNewMembers($event)" [globalFilterFields]="['name', 'ip', 'mac']">
<ng-template pTemplate="header">
<tr>
<th style="width: 4rem"><p-tableHeaderCheckbox></p-tableHeaderCheckbox></th>
<th pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
<th *ngIf="SelectedTask['selection_type']=='devices'" pSortableColumn="ip">IP Address <p-sortIcon
field="ip"></p-sortIcon></th>
<th *ngIf="SelectedTask['selection_type']=='devices'" pSortableColumn="mac">MAC Address <p-sortIcon
field="mac"></p-sortIcon></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><p-tableCheckbox [value]="item"></p-tableCheckbox></td>
<td>
<div class="d-flex align-items-center">
<i
class="fa-solid fa-{{SelectedTask['selection_type'] === 'devices' ? 'server' : 'layer-group'}} me-2 text-primary"></i>
<strong>{{item.name}}</strong>
</div>
</td>
<td *ngIf="SelectedTask['selection_type']=='devices'"><c-badge color="secondary">{{item.ip}}</c-badge>
</td>
<td *ngIf="SelectedTask['selection_type']=='devices'"><small class="text-muted">{{item.mac}}</small>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td [attr.colspan]="SelectedTask['selection_type']=='devices' ? 4 : 2" class="text-center p-4">No
available targets found.</td>
</tr>
</ng-template>
</p-table>
</div>
</c-card-body>
</c-card>
@ -511,7 +588,7 @@
</table>
</c-modal-body>
<c-modal-footer>
<button (click)="confirm_delete" cButton color="danger">
<button (click)="confirm_delete('',true)" cButton color="danger">
Yes,Run!
</button>
<button [cModalToggle]="runConfirmModal.id" cButton color="info">

View file

@ -1,19 +1,8 @@
import { Component, OnInit, OnDestroy, QueryList, ViewChildren } from "@angular/core";
import { Component, OnInit, OnDestroy, QueryList, ViewChildren, ViewChild } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiSelectedRow,
GuiSearching,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { NgxSuperSelectOptions } from "ngx-super-select";
import { _getFocusedElementPierceShadowDom } from "@angular/cdk/platform";
import { AppToastComponent } from "../toast-simple/toast.component";
@ -25,10 +14,13 @@ import { ToasterComponent } from "@coreui/angular";
styleUrls: ["user_tasks.component.scss"]
})
export class UserTasksComponent implements OnInit {
public uid: number;
public uname: string;
public uid: number = 0;
public uname: string = '';
public ispro: boolean = false;
@ViewChild('dt') table!: Table;
@ViewChild('dtNewMembers') tableNewMembers!: Table;
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
toasterForm = {
autohide: true,
@ -67,7 +59,6 @@ export class UserTasksComponent implements OnInit {
}
}
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public SelectedTask: any = {};
@ -138,14 +129,13 @@ export class UserTasksComponent implements OnInit {
public cronSearch: string = '';
public selectedCronPreset: any = null;
public filteredCrons: any[] = [];
public sorting = {
enabled: true,
multiSorting: true,
};
searching: GuiSearching = {
enabled: true,
placeholder: "Search Devices",
};
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
applyFilterNewMembers($event: any, stringVal: string) {
this.tableNewMembers.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
options: Partial<NgxSuperSelectOptions> = {
selectionMode: "single",
@ -167,32 +157,6 @@ export class UserTasksComponent implements OnInit {
enableDarkMode: false,
};
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
show_new_member_form() {
this.NewMemberModalVisible = true;
@ -267,9 +231,9 @@ export class UserTasksComponent implements OnInit {
}
}
onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void {
onSelectedRowsNewMembers(rows: any[]): void {
this.NewMemberRows = rows;
this.SelectedNewMemberRows = rows.map((m: GuiSelectedRow) => m.source);
this.SelectedNewMemberRows = rows;
}
add_new_members() {

View file

@ -15,7 +15,9 @@ import {
} from "@coreui/angular";
import { UserTasksRoutingModule } from "./user_tasks-routing.module";
import { UserTasksComponent } from "./user_tasks.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PTableModule } from 'primeng/table';
import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
import { NgxSuperSelectModule} from "ngx-super-select";
@ -31,7 +33,9 @@ import { NgxSuperSelectModule} from "ngx-super-select";
BadgeModule,
AlertModule,
ToastModule,
GuiGridModule,
PTableModule,
PInputTextModule,
PTooltipModule,
ModalModule,
ReactiveFormsModule,
FormsModule,

View file

@ -4,19 +4,22 @@
<c-col xs="12" md="6">
<div class="nav nav-underline bg-transparent">
<div class="nav-item">
<a [active]="activetab==0" class="nav-link" [cTabContent]="tabContent" (click)="activetab=0" [tabPaneIdx]="0">
<a [active]="activetab==0" class="nav-link" [cTabContent]="tabContent" (click)="activetab=0"
[tabPaneIdx]="0">
<i class="fas fa-cog me-1"></i>Settings
</a>
</div>
<div class="nav-item">
<a [active]="activetab==1" class="nav-link" [cTabContent]="tabContent" (click)="get_passwords();activetab=1" [tabPaneIdx]="1">
<a [active]="activetab==1" class="nav-link" [cTabContent]="tabContent" (click)="get_passwords();activetab=1"
[tabPaneIdx]="1">
<i class="fas fa-key me-1"></i>Passwords
</a>
</div>
</div>
</c-col>
<c-col xs="12" md="6" class="text-end mt-2 mt-md-0">
<button *ngIf="activetab==0" cButton size="sm" class="me-2" (click)="runConfirmModalVisible=!runConfirmModalVisible" color="danger">
<button *ngIf="activetab==0" cButton size="sm" class="me-2"
(click)="runConfirmModalVisible=!runConfirmModalVisible" color="danger">
<i class="fas fa-play me-1"></i>Execute Now
</button>
<button *ngIf="activetab==1" cButton size="sm" (click)="toggleCollapse()" color="info">
@ -36,11 +39,12 @@
<i class="fas fa-cog text-primary me-2 fs-4"></i>
<div>
<h5 class="mb-1">Password Vault Settings</h5>
<p class="text-muted mb-0 small">Configure automated password management for your device groups. Set schedules, define password policies, and manage affected devices.</p>
<p class="text-muted mb-0 small">Configure automated password management for your device groups. Set
schedules, define password policies, and manage affected devices.</p>
</div>
</div>
</div>
<!-- Password Vault Configuration -->
<div class="mb-4 pb-4 border-bottom">
<h6 class="mb-3"><i class="fas fa-shield-alt me-2"></i>Password Vault Configuration</h6>
@ -54,7 +58,8 @@
</select>
</c-col>
<c-col xs="12" md="6" lg="3">
<label class="form-label small">Strategy <i class="fas fa-info-circle text-info ms-1" [cTooltip]="strategyTooltipTemplate" cTooltipPlacement="top"></i></label>
<label class="form-label small">Strategy <i class="fas fa-info-circle text-info ms-1"
[cTooltip]="strategyTooltipTemplate" cTooltipPlacement="top"></i></label>
<select cSelect size="sm" [(ngModel)]="settings['strategy']" class="form-select-sm">
<option>Choose...</option>
<option value="all">All Local Users</option>
@ -62,7 +67,8 @@
</select>
</c-col>
<c-col xs="12" md="6" lg="3">
<label class="form-label small">Interval <i class="fas fa-info-circle text-info ms-1" [cTooltip]="intervalTooltipTemplate" cTooltipPlacement="top"></i></label>
<label class="form-label small">Interval <i class="fas fa-info-circle text-info ms-1"
[cTooltip]="intervalTooltipTemplate" cTooltipPlacement="top"></i></label>
<select cSelect size="sm" [(ngModel)]="settings['interval']" class="form-select-sm">
<option>Choose...</option>
<option value="daily">Daily</option>
@ -74,7 +80,8 @@
</select>
</c-col>
<c-col xs="12" md="6" lg="3">
<label class="form-label small">Password Type <i class="fas fa-info-circle text-info ms-1" [cTooltip]="passwordTypeTooltipTemplate" cTooltipPlacement="top"></i></label>
<label class="form-label small">Password Type <i class="fas fa-info-circle text-info ms-1"
[cTooltip]="passwordTypeTooltipTemplate" cTooltipPlacement="top"></i></label>
<select cSelect size="sm" [(ngModel)]="settings['password_type']" class="form-select-sm">
<option>Choose...</option>
<option value="random">Random</option>
@ -104,20 +111,26 @@
</c-col>
</c-row>
<div *ngIf="settings['exceptions']?.length > 0">
<gui-grid [autoResizeWidth]="true" [source]="settings['exceptions']" [columnMenu]="columnMenu" [paging]="paging" [sorting]="sorting">
<gui-grid-column header="Username" field="name">
<ng-template let-value="item" let-item="item" let-index="index">
<i class="fas fa-user me-2 text-muted"></i>{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="80" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button (click)="remove_exception(item)" cButton color="danger" size="sm" variant="outline">
<i class="fas fa-trash"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table [value]="settings['exceptions']" [showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">Username <p-sortIcon field="name"></p-sortIcon></th>
<th style="width: 80px" class="text-center">Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><i class="fas fa-user me-2 text-muted"></i>{{item}}</td>
<td class="text-center">
<button (click)="remove_exception(item)" cButton color="danger" size="sm" variant="outline"
pTooltip="Remove Exception">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
<!-- Pre-defined Passwords -->
@ -137,20 +150,26 @@
</c-col>
</c-row>
<div *ngIf="settings['passwords']?.length > 0">
<gui-grid [autoResizeWidth]="true" [source]="settings['passwords']" [columnMenu]="columnMenu" [sorting]="sorting" [paging]="paging">
<gui-grid-column header="Password" field="name">
<ng-template let-value="item" let-item="item" let-index="index">
<i class="fas fa-lock me-2 text-muted"></i>••••••••
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="80" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_password(item)">
<i class="fas fa-trash"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table [value]="settings['passwords']" [showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">Password <p-sortIcon field="name"></p-sortIcon></th>
<th style="width: 80px" class="text-center">Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><i class="fas fa-lock me-2 text-muted"></i>••••••••</td>
<td class="text-center">
<button cButton color="danger" size="sm" variant="outline" (click)="remove_password(item)"
pTooltip="Remove Password">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
<!-- Affected Groups -->
@ -167,20 +186,25 @@
</c-col>
</c-row>
<div *ngIf="Members?.length > 0" class="mb-3">
<gui-grid [autoResizeWidth]="true" [source]="Members" [columnMenu]="columnMenu" [sorting]="sorting" [paging]="paging">
<gui-grid-column header="Group Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<i class="fas fa-server me-2 text-primary"></i>{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="80" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button (click)="delete_group(item.id)" cButton color="danger" size="sm" variant="outline">
<i class="fas fa-trash"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table [value]="Members" [showGridlines]="true" [stripedRows]="true" styleClass="p-datatable-sm">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">Group Name <p-sortIcon field="name"></p-sortIcon></th>
<th style="width: 80px" class="text-center">Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><i class="fas fa-server me-2 text-primary"></i>{{item.name}}</td>
<td class="text-center">
<button (click)="delete_group(item.id)" cButton color="danger" size="sm" variant="outline"
pTooltip="Remove Group">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
<div class="text-end">
<button cButton color="success" (click)="save_settings()">
@ -192,25 +216,28 @@
<!-- Execution History -->
<div *ngIf="vault_history">
<h6 class="mb-3"><i class="fas fa-history me-2"></i>Execution Reports</h6>
<gui-grid [autoResizeWidth]="true" [source]="vault_history" [columnMenu]="columnMenu" [sorting]="sorting" [paging]="paging">
<gui-grid-column header="Start Time" field="started">
<ng-template let-value="item.started" let-item="item" let-index="index">
<small>{{value}}</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="End Time" field="ended">
<ng-template let-value="item.ended" let-item="item" let-index="index">
<small>{{value}}</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Results" field="result" align="center" width="100">
<ng-template let-value="item['result']" let-item="item" let-index="index">
<button (click)="exportToCsv(value)" color="primary" size="sm" cButton variant="outline">
<i class="fas fa-download"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
<p-table #dtHistory [value]="vault_history" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" styleClass="p-datatable-sm">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="started">Start Time <p-sortIcon field="started"></p-sortIcon></th>
<th pSortableColumn="ended">End Time <p-sortIcon field="ended"></p-sortIcon></th>
<th style="width: 100px" class="text-center">Results</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><small>{{item.started}}</small></td>
<td><small>{{item.ended}}</small></td>
<td class="text-center">
<button (click)="exportToCsv(item.result)" color="primary" size="sm" cButton variant="outline"
pTooltip="Download CSV">
<i class="fas fa-download"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
</c-card-body>
</c-card>
@ -224,11 +251,12 @@
<i class="fas fa-key text-warning me-2 fs-4"></i>
<div>
<h5 class="mb-1">Stored Passwords</h5>
<p class="text-muted mb-0 small">View and manage passwords stored by the vault system. Use filters to find specific devices or users.</p>
<p class="text-muted mb-0 small">View and manage passwords stored by the vault system. Use filters to find
specific devices or users.</p>
</div>
</div>
</div>
<!-- Filters -->
<div class="mb-4 pb-4 border-bottom" [visible]="filters_visible" cCollapse>
<h6 class="mb-3"><i class="fas fa-filter me-2"></i>Filters</h6>
@ -257,36 +285,52 @@
<!-- Stored Passwords -->
<div *ngIf="passwords">
<h6 class="mb-3"><i class="fas fa-database me-2"></i>Stored Passwords</h6>
<gui-grid [autoResizeWidth]="true" [source]="passwords" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel">
<gui-grid-column header="Device" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<div class="d-flex align-items-center">
<i class="fas fa-router me-2 text-primary"></i>
<div>
<div class="fw-semibold">{{value}}</div>
<small class="text-muted">{{item.devip}}</small>
<p-table #dtPasswords [value]="passwords" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" [resizableColumns]="true" columnResizeMode="expand"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name', 'devip', 'username', 'changed']" [loading]="loading">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name" pResizableColumn>
Device
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="username" pResizableColumn>
User
<p-sortIcon field="username"></p-sortIcon>
<p-columnFilter type="text" field="username" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="changed" pResizableColumn>
Last Changed
<p-sortIcon field="changed"></p-sortIcon>
<p-columnFilter type="text" field="changed" display="menu" class="ms-auto" />
</th>
<th style="width: 100px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td>
<div class="d-flex align-items-center">
<i class="fas fa-server me-2 text-primary"></i>
<div>
<div class="fw-semibold">{{item.name}}</div>
<small class="text-muted">{{item.devip}}</small>
</div>
</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Username" field="username">
<ng-template let-value="item.username" let-item="item" let-index="index">
<i class="fas fa-user me-2 text-muted"></i>{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Last Changed" field="changed">
<ng-template let-value="item.changed" let-item="item" let-index="index">
<small>{{value}}</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="100" field="action" align="center">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton *ngIf="ispro" (click)="reveal_password(item.devid,item.username)" color="info" size="sm" variant="outline">
<i class="fas fa-eye"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</td>
<td><i class="fas fa-user me-2 text-muted"></i>{{item.username}}</td>
<td><small>{{item.changed}}</small></td>
<td class="text-center">
<button cButton *ngIf="ispro" (click)="reveal_password(item.devid,item.username)" color="info"
size="sm" variant="outline" pTooltip="View Password">
<i class="fas fa-eye"></i>
</button>
</td>
</tr>
</ng-template>
</p-table>
</div>
</c-card-body>
</c-card>
@ -306,7 +350,7 @@
</div>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-key"></i></span>
<input [value]="password" cFormControl disabled="true" class="form-control"/>
<input [value]="password" cFormControl disabled="true" class="form-control" />
<button cButton color="secondary" variant="outline" (click)="copyToClipboard(password)">
<i class="fas fa-copy"></i>
</button>
@ -325,7 +369,8 @@
</c-modal-header>
<c-modal-body>
<div class="alert alert-warning">
<strong>Warning:</strong> This will execute the password vault job and change passwords on all configured device groups.
<strong>Warning:</strong> This will execute the password vault job and change passwords on all configured device
groups.
</div>
<p>Are you sure you want to proceed with the password vault execution?</p>
</c-modal-body>
@ -345,25 +390,50 @@
</c-modal-header>
<c-modal-body>
<p class="text-muted mb-3">Select device groups to include in the password vault task:</p>
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [paging]="paging">
<gui-grid-column header="Group Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<i class="fas fa-server me-2 text-primary"></i>{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column *ngIf="SelectedTask['selection_type']=='devices'" header="MAC Address" field="mac">
<ng-template let-value="item.mac" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
</gui-grid>
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search groups..." (input)="applyFilterNewMembers($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<p-table #dtNewMembers [value]="availbleMembers" [paginator]="true" [rows]="10" [showGridlines]="true"
[stripedRows]="true" [resizableColumns]="true" columnResizeMode="expand"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped" [(selection)]="NewMemberRows"
(selectionChange)="onSelectedRowsNewMembers($event)" [globalFilterFields]="['name', 'ip', 'mac']">
<ng-template pTemplate="header">
<tr>
<th style="width: 4rem" pResizableColumn><p-tableHeaderCheckbox></p-tableHeaderCheckbox></th>
<th pSortableColumn="name" pResizableColumn>
Name
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="ip" pResizableColumn>
IP
<p-sortIcon field="ip"></p-sortIcon>
<p-columnFilter type="text" field="ip" display="menu" class="ms-auto" />
</th>
<th pSortableColumn="mac" pResizableColumn>
MAC
<p-sortIcon field="mac"></p-sortIcon>
<p-columnFilter type="text" field="mac" display="menu" class="ms-auto" />
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-item>
<tr>
<td><p-tableCheckbox [value]="item"></p-tableCheckbox></td>
<td>
<div class="d-flex align-items-center">
<i class="fas fa-server me-2 text-primary"></i>
<strong>{{item.name}}</strong>
</div>
</td>
</tr>
</ng-template>
</p-table>
</c-modal-body>
<c-modal-footer>
<button *ngIf="NewMemberRows.length != 0" (click)="add_new_members()" cButton color="primary">

View file

@ -1,19 +1,8 @@
import { Component, OnInit, ViewChildren ,QueryList} from "@angular/core";
import { Component, OnInit, ViewChildren, QueryList, ViewChild } from "@angular/core";
import { dataProvider } from "../../providers/mikrowizard/data";
import { Router } from "@angular/router";
import { loginChecker } from "../../providers/login_checker";
import {
GuiSelectedRow,
GuiSearching,
GuiInfoPanel,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiRowSelectionMode,
GuiRowSelection,
GuiRowSelectionType,
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { NgxSuperSelectOptions } from "ngx-super-select";
import { _getFocusedElementPierceShadowDom } from "@angular/cdk/platform";
import { formatInTimeZone } from "date-fns-tz";
@ -27,10 +16,14 @@ import { AppToastComponent } from "../toast-simple/toast.component";
})
export class VaultComponent implements OnInit {
public uid: number;
public uname: string;
public uid: number = 0;
public uname: string = '';
public ispro: boolean = false;
public tz: string;
public tz: string = '';
@ViewChild('dtPasswords') dtPasswords!: Table;
@ViewChild('dtHistory') dtHistory!: Table;
@ViewChild('dtNewMembers') dtNewMembers!: Table;
constructor(
private data_provider: dataProvider,
@ -71,7 +64,6 @@ export class VaultComponent implements OnInit {
public password:string="";
public PasswordModalVisible:boolean=false;
public source: Array<any> = [];
public columns: Array<GuiColumn> = [];
public loading: boolean = true;
public rows: any = [];
public SelectedTask: any = {};
@ -87,16 +79,7 @@ export class VaultComponent implements OnInit {
public filters: any = {};
public activetab:number=0;
public sorting = {
enabled: true,
multiSorting: true,
};
searching: GuiSearching = {
enabled: true,
placeholder: "Search Devices",
};
toasterForm = {
public toasterForm = {
autohide: true,
delay: 3000,
position: "fixed",
@ -104,42 +87,17 @@ export class VaultComponent implements OnInit {
closeButton: true,
};
options: Partial<NgxSuperSelectOptions> = {
selectionMode: "single",
actionsEnabled: false,
displayExpr: "name",
valueExpr: "id",
placeholder: "Snippet",
searchEnabled: true,
enableDarkMode: false,
};
applyFilterPasswords($event: any, stringVal: string) {
this.dtPasswords.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
applyFilterHistory($event: any, stringVal: string) {
this.dtHistory.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
public columnMenu: GuiColumnMenu = {
enabled: true,
sort: true,
columnsManager: true,
};
public infoPanel: GuiInfoPanel = {
enabled: true,
infoDialog: false,
columnsManager: true,
schemaManager: true,
};
public rowSelection: boolean | GuiRowSelection = {
enabled: true,
type: GuiRowSelectionType.CHECKBOX,
mode: GuiRowSelectionMode.MULTIPLE,
};
applyFilterNewMembers($event: any, stringVal: string) {
this.dtNewMembers.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
reinitgrid(field: string, $event: any) {
if (field == "username") this.filters["username"] = $event;
@ -153,10 +111,9 @@ export class VaultComponent implements OnInit {
this.get_vault_history();
}
onSelectedRowsNewMembers(rows: Array<GuiSelectedRow>): void {
onSelectedRowsNewMembers(rows: any[]): void {
this.NewMemberRows = rows;
this.SelectedNewMemberRows = rows.map((m: GuiSelectedRow) => ({'id': m.source.id,'name':m.source.name}));
this.SelectedNewMemberRows = rows.map((m: any) => ({'id': m.id,'name':m.name}));
}
toggleCollapse(): void {

View file

@ -16,7 +16,9 @@ import {
} from "@coreui/angular";
import { VaultRoutingModule } from "./vault-routing.module";
import { VaultComponent } from "./vault.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { TableModule as PTableModule } from 'primeng/table';
import { InputTextModule as PInputTextModule } from 'primeng/inputtext';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
import { MatInputModule } from "@angular/material/input";
import { MatFormFieldModule } from "@angular/material/form-field";
@ -29,7 +31,9 @@ import { MatFormFieldModule } from "@angular/material/form-field";
FormModule,
ButtonModule,
ButtonGroupModule,
GuiGridModule,
PTableModule,
PInputTextModule,
PTooltipModule,
ModalModule,
ReactiveFormsModule,
FormsModule,

View file

@ -63,12 +63,6 @@
}
}
// Grid Improvements
gui-grid {
.gui-grid {
font-size: 0.85rem;
}
}
// Input Group Compact
.input-group-sm {
@ -204,19 +198,6 @@ gui-grid {
}
}
// Grid Cell Improvements
::ng-deep gui-grid {
.gui-grid-cell {
padding: 0.5rem;
font-size: 0.85rem;
}
.gui-grid-header-cell {
font-size: 0.8rem;
font-weight: 600;
padding: 0.5rem;
}
}
// Tab Info Headers
.tab-info-header {

View file

@ -116,217 +116,217 @@
</c-row>
</c-card-header>
<c-card-body>
<gui-grid #grid [rowClass]="rowClass" [source]="source" [searching]="searching" [paging]="paging"
[columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [autoResizeWidth]="true"
<div class="mb-3 d-flex justify-content-end">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input type="text" pInputText placeholder="Search peers..." (input)="applyFilterGlobal($event, 'contains')"
class="form-control-sm" />
</span>
</div>
<p-table #dt [value]="source" [paginator]="true" [rows]="10" [showCurrentPageReport]="true"
[rowsPerPageOptions]="[10, 25, 50]" [resizableColumns]="true" columnResizeMode="expand"
[showGridlines]="true" [stripedRows]="true"
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped"
[globalFilterFields]="['name', 'assigned_ip', 'public_key', 'description']"
[loading]="loading">
<gui-grid-column header="Status" field="status" [width]="50" align="center">
<ng-template let-item="item">
<i *ngIf="item.status === 'online'" class="fa-solid fa-circle text-success" cTooltip="Online"></i>
<i *ngIf="item.status === 'offline'" class="fa-solid fa-circle text-danger" cTooltip="Offline"></i>
<i *ngIf="item.status === 'unreachable'" class="fa-solid fa-triangle-exclamation text-warning"
cTooltip="Unreachable (Handshake OK, Ping Failed)"></i>
<i *ngIf="!item.status && item.is_enabled" class="fa-solid fa-circle text-info" cTooltip="Enabled"></i>
<i *ngIf="!item.status && !item.is_enabled" class="fa-solid fa-circle text-secondary"
cTooltip="Disabled"></i>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Identity & IP" field="name" [width]="220">
<ng-template let-item="item">
<div style="line-height: 1.2;">
<i *ngIf="item.mt_user" class="fas fa-server text-info me-1" cTooltip="Managed MikroTik Device"
style="vertical-align: middle; font-size: 0.9rem;"></i>
<strong class="text-primary fs-6 text-truncate d-inline-block"
style="max-width: 140px; vertical-align: middle;" [title]="item.name || item.assigned_ip">
{{ item.name || item.assigned_ip || (item.public_key | slice:0:8) + '...' }}
</strong>
<c-badge *ngIf="item.is_managed === false" color="secondary" class="ms-1"
style="vertical-align: middle; font-size: 0.65rem;">Unmanaged</c-badge>
</div>
<div style="font-size: 0.85rem; color: #495057;" class="fw-semibold ms-2">
<i class="fas fa-network-wired opacity-75"></i> {{ item.assigned_ip || 'No IP' }}
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Details & Description" field="description">
<ng-template let-item="item">
<div class="d-flex flex-column justify-content-center w-100">
<!-- Description (Bigger if exists, hidden if empty) -->
<div *ngIf="item.description" class="text-muted mb-1 text-wrap"
style="font-size: 0.88rem; line-height: 1.3; font-weight: 500;" [title]="item.description">
{{ item.description }}
<ng-template pTemplate="header">
<tr>
<th style="width: 50px" class="text-center" pResizableColumn>Status</th>
<th pSortableColumn="name" style="width: 200px" pResizableColumn>
<div class="d-flex justify-content-between">
<span>Identity & IP</span>
<span>
<p-sortIcon field="name"></p-sortIcon>
<p-columnFilter type="text" field="name" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="description" pResizableColumn>
<div class="d-flex justify-content-between">
<span>Details & Description</span>
<span>
<p-sortIcon field="description"></p-sortIcon>
<p-columnFilter type="text" field="description" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th pSortableColumn="nat_mode" style="width: 140px" pResizableColumn>
<div class="d-flex justify-content-between">
<span>Routing</span>
<span>
<p-sortIcon field="nat_mode"></p-sortIcon>
<p-columnFilter type="text" field="nat_mode" display="menu" class="ms-auto" />
</span>
</div>
</th>
<th style="width: 220px" class="text-center" pResizableColumn>Integration</th>
<th pSortableColumn="stats.last_handshake" style="width: 130px" class="text-center" pResizableColumn>
<div class="d-flex justify-content-between">
<span>Last Handshake</span>
<p-sortIcon field="stats.last_handshake"></p-sortIcon>
</div>
</th>
<th style="width: 130px" pResizableColumn>Transfer Speed</th>
<th style="width: 120px" pResizableColumn>Total Volume</th>
<th pSortableColumn="is_enabled" style="width: 90px" class="text-center" pResizableColumn>
<div class="d-flex justify-content-between">
<span>State</span>
<p-sortIcon field="is_enabled"></p-sortIcon>
</div>
</th>
<th style="width: 70px" class="text-center" pResizableColumn>Actions</th>
</tr>
</ng-template>
<!-- Peer Info (Horizontal flex, smaller if Description exists) -->
<div style="color: #adb5bd;" class="d-flex flex-row align-items-center gap-3"
[ngStyle]="{'font-size': item.description ? '0.7rem' : '0.8rem', 'margin-top': item.description ? '4px' : '0'}">
<div class="text-truncate" title="Public Key" style="max-width: 140px;">
<i class="fas fa-key me-1 opacity-75"></i> {{ item.public_key | slice:0:16 }}...
<ng-template pTemplate="body" let-item>
<tr [ngClass]="{'row-highlighted': item.status === 'online'}">
<td class="text-center">
<i *ngIf="item.status === 'online'" class="fa-solid fa-circle text-success" [pTooltip]="'Online'" tooltipPosition="top"></i>
<i *ngIf="item.status === 'offline'" class="fa-solid fa-circle text-danger" [pTooltip]="'Offline'" tooltipPosition="top"></i>
<i *ngIf="item.status === 'unreachable'" class="fa-solid fa-triangle-exclamation text-warning"
[pTooltip]="'Unreachable (Handshake OK, Ping Failed)'" tooltipPosition="top"></i>
<i *ngIf="!item.status && item.is_enabled" class="fa-solid fa-circle text-info" [pTooltip]="'Enabled'" tooltipPosition="top"></i>
<i *ngIf="!item.status && !item.is_enabled" class="fa-solid fa-circle text-secondary"
[pTooltip]="'Disabled'" tooltipPosition="top"></i>
</td>
<td>
<div style="line-height: 1.2;">
<i *ngIf="item.mt_user" class="fas fa-server text-info me-1" [pTooltip]="'Managed MikroTik Device'"
style="vertical-align: middle; font-size: 0.9rem;"></i>
<strong class="text-primary fs-6 text-truncate d-inline-block"
style="max-width: 140px; vertical-align: middle;" [title]="item.name || item.assigned_ip">
{{ item.name || item.assigned_ip || (item.public_key | slice:0:8) + '...' }}
</strong>
<c-badge *ngIf="item.is_managed === false" color="secondary" class="ms-1"
style="vertical-align: middle; font-size: 0.65rem;">Unmanaged</c-badge>
</div>
<div style="font-size: 0.85rem; color: #495057;" class="fw-semibold ms-2">
<i class="fas fa-network-wired opacity-75"></i> {{ item.assigned_ip || 'No IP' }}
</div>
</td>
<td>
<div class="d-flex flex-column justify-content-center w-100">
<div *ngIf="item.description" class="text-muted mb-1 text-wrap small fw-bold" [title]="item.description">
{{ item.description }}
</div>
<div class="text-truncate" title="Created At">
<i class="fas fa-clock me-1 opacity-75"></i> {{ item.created_at | date:'mediumDate' }}
<div style="color: #adb5bd; font-size: 0.75rem" class="d-flex flex-row align-items-center gap-3">
<div class="text-truncate" title="Public Key" style="max-width: 140px;">
<i class="fas fa-key me-1 opacity-75"></i> {{ item.public_key | slice:0:16 }}...
</div>
<div class="text-truncate" title="Created At">
<i class="fas fa-clock me-1 opacity-75"></i> {{ item.created_at | date:'mediumDate' }}
</div>
</div>
</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Routing & Identity" field="nat_mode" [width]="170">
<ng-template let-value="item.nat_mode" let-item="item">
<div class="mb-1 mx-1">
<c-badge size="sm"
[color]="value === 'full' ? 'success' : (value === 'split' ? 'info' : 'secondary')">
{{ value | uppercase }}
</c-badge>
</div>
<div style="font-size: 0.75rem;margin-top: 5px; " class="text-muted">
<i class="fas fa-network-wired"></i> Iface: {{ item.custom_interface || 'Auto' }}
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Integration" align="center" field="linked_device_id" [width]="250">
<ng-template let-item="item">
<div class="mb-1">
<a *ngIf="item.linked_device_id" [routerLink]="['/device-stats', {id: item.linked_device_id}]"
target="_blank" class="btn btn-sm btn-outline-primary py-0 px-2"
style="font-size: 0.75rem; transition: all 0.2s;">
<i class="fa-solid fa-link"></i> View Device
</a>
<div class="d-flex flex-row justify-content-center w-100 ng-star-inserted">
<small *ngIf="!item.linked_device_id && !item.scan_status && item.mt_user" class="text-muted">Device
Not Linked</small>
<!-- Scan Status Micro-animations -->
<div style="font-size: 0.75rem;"
*ngIf="item.scan_status &&item.scan_status === 'starting' || item.scan_status === 'running'"
class="text-info mx-1 mt-1">
<i class="fas fa-circle-notch fa-spin me-1"></i> Scanning...
</td>
<td>
<div class="mb-1">
<c-badge size="sm"
[color]="item.nat_mode === 'full' ? 'success' : (item.nat_mode === 'split' ? 'info' : 'secondary')">
{{ item.nat_mode | uppercase }}
</c-badge>
</div>
<div style="font-size: 0.75rem;" class="text-muted mt-1">
<i class="fas fa-network-wired small opacity-50"></i> Iface: {{ item.custom_interface || 'Auto' }}
</div>
</td>
<td class="text-center">
<div class="d-flex flex-column align-items-center gap-1">
<a *ngIf="item.linked_device_id" [routerLink]="['/device-stats', {id: item.linked_device_id}]"
target="_blank" class="btn btn-sm btn-outline-primary py-0 px-2" style="font-size: 0.7rem;">
<i class="fa-solid fa-link"></i> View Device
</a>
<div class="d-flex flex-row justify-content-center w-100 container-fluid p-0">
<small *ngIf="!item.linked_device_id && !item.scan_status && item.mt_user" class="text-muted" style="font-size: 0.65rem;">Not Linked</small>
<div style="font-size: 0.7rem;" *ngIf="item.scan_status === 'starting' || item.scan_status === 'running'" class="text-info">
<i class="fas fa-circle-notch fa-spin me-1"></i> Scanning...
</div>
<div style="font-size: 0.7rem;" *ngIf="item.scan_status === 'completed'" class="text-success">
<i class="fas fa-check-circle me-1"></i> Success
</div>
<div style="font-size: 0.7rem;" *ngIf="item.scan_status === 'failed'" class="text-danger" [pTooltip]="'MikroTik connection failed'" tooltipPosition="top">
<i class="fas fa-exclamation-triangle me-1"></i> Failed
</div>
</div>
<div style="font-size: 0.75rem;" *ngIf="item.scan_status && item.scan_status === 'completed'"
class="text-success mx-1 mt-1" style="animation: fadeIn 0.5s ease-in-out;">
<i class="fas fa-check-circle me-1"></i> Scan Completed
</div>
<div style="font-size: 0.75rem;" *ngIf="item.scan_status &&item.scan_status === 'failed'"
class="text-danger mx-1 mt-1" cTooltip="MikroTik connection or credential validation failed">
<i class="fas fa-exclamation-triangle me-1"></i> Scan Failed
</div>
<div style="font-size: 0.75rem;" class="text-secondary mx-1 mt-1">
<div style="font-size: 0.65rem;" class="text-secondary opacity-75">
<i class="fas fa-heartbeat"></i> Keepalive: {{ item.persistent_keepalive }}s
</div>
</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column align="center" header="Last Handshake" field="stats" [width]="150">
<ng-template let-item="item">
<div *ngIf="item.stats" style="font-size: 0.85rem;" class="mt-1">
<span *ngIf="item.stats.last_handshake > 0" class="text-secondary fw-semibold">
</td>
<td class="text-center small">
<div *ngIf="item.stats?.last_handshake > 0" class="text-secondary fw-semibold">
<i class="fas fa-handshake"></i> {{ item.stats.last_handshake * 1000 | date:'shortTime' }}<br>
<small class="text-muted">{{ item.stats.last_handshake * 1000 | date:'shortDate' }}</small>
</span>
<span *ngIf="!item.stats.last_handshake || item.stats.last_handshake === 0" class="text-muted">
</div>
<div *ngIf="!item.stats?.last_handshake || item.stats.last_handshake === 0" class="text-muted">
<i class="fas fa-handshake"></i> None
</span>
</div>
<span *ngIf="!item.stats" class="text-muted text-sm">-</span>
</ng-template>
</gui-grid-column>
<gui-grid-column align="center" header="Transfer Speed" field="stats" [width]="130">
<ng-template let-item="item">
<div *ngIf="item.stats" class="mt-1 flex-column d-flex align-items-start"
style="font-size: 0.75rem; font-family: monospace; font-weight: bold;">
<span [ngStyle]="{'color': item.stats.rx_speed !== 0 ? '#2eb85c' : '#8a93a2'}"
style="transition: color 0.3s ease;">
<i class="fas fa-arrow-down opacity-75"></i> {{ formatBytes(item.stats.rx_speed || 0) }}/s
</span>
<span [ngStyle]="{'color': item.stats.tx_speed !== 0 ? '#3399ff' : '#8a93a2'}" class="mt-1"
style="transition: color 0.3s ease;">
<i class="fas fa-arrow-up opacity-75"></i> {{ formatBytes(item.stats.tx_speed || 0) }}/s
</span>
</div>
<span *ngIf="!item.stats" class="text-muted text-sm">-</span>
</ng-template>
</gui-grid-column>
<gui-grid-column align="center" header="Total Volume" field="stats" [width]="120">
<ng-template let-item="item">
<div *ngIf="item.stats" class="mt-1 flex-column d-flex align-items-start" style="font-size: 0.75rem;">
<span class="text-success"><i class="fas fa-arrow-down opacity-50"></i> {{(item.stats.rx_bytes /
1048576) | number:'1.2-2'}} MB</span>
<span class="text-info mt-1"><i class="fas fa-arrow-up opacity-50"></i> {{(item.stats.tx_bytes /
1048576) | number:'1.2-2'}} MB</span>
</div>
<span *ngIf="!item.stats" class="text-muted text-sm">-</span>
</ng-template>
</gui-grid-column>
<gui-grid-column align="center" header="State" field="is_enabled" [width]="120">
<ng-template let-item="item">
<div class="mt-2 d-flex justify-content-center">
<c-form-check [switch]="true">
</div>
</td>
<td>
<div *ngIf="item.stats" class="d-flex flex-column" style="font-size: 0.72rem; font-family: monospace; font-weight: bold;">
<span [style.color]="item.stats.rx_speed !== 0 ? '#2eb85c' : '#8a93a2'">
<i class="fas fa-arrow-down opacity-75"></i> {{ formatBytes(item.stats.rx_speed || 0) }}/s
</span>
<span [style.color]="item.stats.tx_speed !== 0 ? '#3399ff' : '#8a93a2'">
<i class="fas fa-arrow-up opacity-75"></i> {{ formatBytes(item.stats.tx_speed || 0) }}/s
</span>
</div>
</td>
<td>
<div *ngIf="item.stats" class="d-flex flex-column" style="font-size: 0.7rem;">
<span class="text-success"><i class="fas fa-arrow-down opacity-50"></i> {{(item.stats.rx_bytes / 1048576) | number:'1.2-2'}} MB</span>
<span class="text-info"><i class="fas fa-arrow-up opacity-50"></i> {{(item.stats.tx_bytes / 1048576) | number:'1.2-2'}} MB</span>
</div>
</td>
<td class="text-center">
<c-form-check [switch]="true" class="d-inline-block">
<input cFormCheckInput type="checkbox" [checked]="item.is_enabled"
(click)="$event.preventDefault(); promptToggleEnabled(item)"
[disabled]="item.is_managed === false" [id]="'switch-peer-' + item.id" />
<label cFormCheckLabel [for]="'switch-peer-' + item.id"
[ngClass]="item.is_enabled ? 'text-success fw-bold' : 'text-secondary'"
style="cursor: pointer; font-size: 0.85rem;">
{{ item.is_enabled ? 'Enabled' : 'Disabled' }}
</label>
[disabled]="item.is_managed === false" [id]="'switch-peer-' + item.public_key" />
</c-form-check>
</div>
</ng-template>
</gui-grid-column>
</td>
<td class="text-center">
<button color="primary" shape="rounded-0" variant="ghost" style="padding: 4px 7px;"
[matMenuTriggerFor]="menu" cButton>
<i class="fa-solid fa-bars"></i>
</button>
<mat-menu #menu="matMenu">
<div cListGroup>
<button size="sm" cListGroupItem (click)="openEditModal(item)" *ngIf="item.is_managed !== false">
<i class="fa-solid fa-pencil text-primary"></i><small> Edit</small>
</button>
<button *ngIf="item.mt_user && !item.linked_device_id" size="sm" cListGroupItem
(click)="scanDevice(item)">
<i class="fa-solid fa-magnifying-glass text-info"></i><small> Manual Scan</small>
</button>
<button size="sm" cListGroupItem (click)="openScriptModal(item)">
<i class="fa-solid fa-terminal text-secondary"></i><small> MikroTik Script</small>
</button>
<button size="sm" cListGroupItem (click)="openQrModal(item)">
<i class="fa-solid fa-qrcode text-secondary"></i><small> QR Code</small>
</button>
<button size="sm" cListGroupItem (click)="downloadPeerConfigDirect(item)">
<i class="fa-solid fa-download text-secondary"></i><small> Download Config</small>
</button>
<button size="sm" cListGroupItem (click)="promptResetPeer(item)">
<i class="fa-solid fa-sync text-warning"></i><small> Reset Counters</small>
</button>
<button size="sm" cListGroupItem (click)="confirmDelete(item)">
<i class="fa-solid fa-trash text-danger"></i><small> Delete</small>
</button>
</div>
</mat-menu>
</td>
</tr>
</ng-template>
<gui-grid-column align="center" header="" field="_search_index" [enabled]="false" [width]="0">
<ng-template let-item="item"></ng-template>
</gui-grid-column>
<gui-grid-column align="center" [width]="80" [cellEditing]="false" [sorting]="false" header="Actions">
<ng-template let-item="item">
<button color="primary" shape="rounded-0" variant="ghost" style="padding: 4px 7px;"
[matMenuTriggerFor]="menu" cButton>
<i class="fa-solid fa-bars"></i>
</button>
<mat-menu #menu="matMenu">
<div cListGroup>
<button size="sm" cListGroupItem (click)="openEditModal(item)" *ngIf="item.is_managed !== false">
<i class="fa-solid fa-pencil text-primary"></i><small> Edit</small>
</button>
<button *ngIf="item.mt_user && !item.linked_device_id" size="sm" cListGroupItem
(click)="scanDevice(item)">
<i class="fa-solid fa-magnifying-glass text-info"></i><small> Manual Scan</small>
</button>
<button size="sm" cListGroupItem (click)="openScriptModal(item)">
<i class="fa-solid fa-terminal text-secondary"></i><small> MikroTik Script</small>
</button>
<button size="sm" cListGroupItem (click)="openQrModal(item)">
<i class="fa-solid fa-qrcode text-secondary"></i><small> QR Code</small>
</button>
<button size="sm" cListGroupItem (click)="downloadPeerConfigDirect(item)">
<i class="fa-solid fa-download text-secondary"></i><small> Download Config</small>
</button>
<button size="sm" cListGroupItem (click)="promptResetPeer(item)">
<i class="fa-solid fa-sync text-warning"></i><small> Reset Counters</small>
</button>
<button size="sm" cListGroupItem (click)="confirmDelete(item)">
<i class="fa-solid fa-trash text-danger"></i><small> Delete</small>
</button>
</div>
</mat-menu>
</ng-template>
</gui-grid-column>
</gui-grid>
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="10" class="text-center p-4">No VPN peers found.</td>
</tr>
</ng-template>
</p-table>
</c-card-body>
</c-card>
</c-col>

View file

@ -4,16 +4,7 @@ import { Router } from '@angular/router';
import { loginChecker } from '../../providers/login_checker';
import { VpnService, VpnStatusResponse, VpnPeer, VpnServerConfig } from '../../providers/mikrowizard/vpn.service';
import { Subscription, delay, of, repeat, switchMap, timer, catchError } from 'rxjs';
import {
GuiGridComponent,
GuiRowClass,
GuiSearching,
GuiColumn,
GuiColumnMenu,
GuiPaging,
GuiPagingDisplay,
GuiInfoPanel
} from "@generic-ui/ngx-grid";
import { Table } from 'primeng/table';
import { ToasterComponent, ToasterPlacement } from "@coreui/angular";
import { AppToastComponent } from "../toast-simple/toast.component";
@ -22,7 +13,7 @@ import { AppToastComponent } from "../toast-simple/toast.component";
styleUrls: ['vpn.component.scss']
})
export class VpnComponent implements OnInit, OnDestroy {
@ViewChild("grid", { static: true }) gridComponent!: GuiGridComponent;
@ViewChild("dt") table!: Table;
@ViewChildren(ToasterComponent) viewChildren!: QueryList<ToasterComponent>;
toasterForm = {
@ -155,18 +146,9 @@ export class VpnComponent implements OnInit, OnDestroy {
public resetServerModalVisible = false;
rowClass: GuiRowClass = { class: "row-highlighted" };
searching: GuiSearching = { enabled: true, placeholder: "Search Peers" };
public paging: GuiPaging = {
enabled: true,
page: 1,
pageSize: 10,
pageSizes: [5, 10, 25, 50],
display: GuiPagingDisplay.ADVANCED,
};
public columnMenu: GuiColumnMenu = { enabled: true, sort: true, columnsManager: true };
public infoPanel: GuiInfoPanel = { enabled: true, infoDialog: false, columnsManager: true, schemaManager: true };
public sorting = { enabled: true, multiSorting: true };
applyFilterGlobal($event: any, stringVal: string) {
this.table.filterGlobal(($event.target as HTMLInputElement).value, stringVal);
}
constructor(
private login_checker: loginChecker,

View file

@ -1,7 +1,6 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { GuiGridModule } from '@generic-ui/ngx-grid';
import { ChartjsModule } from '@coreui/angular-chartjs';
import { HighlightJsModule } from 'ngx-highlight-js';
import { ClipboardModule } from '@angular/cdk/clipboard';
@ -33,6 +32,9 @@ import { IconModule } from '@coreui/icons-angular';
import { VpnRoutingModule } from './vpn-routing.module';
import { VpnComponent } from './vpn.component';
import { TableModule as PTableModule } from 'primeng/table';
import { TooltipModule as PTooltipModule } from 'primeng/tooltip';
import { InputTextModule } from 'primeng/inputtext';
@NgModule({
imports: [
@ -42,7 +44,9 @@ import { VpnComponent } from './vpn.component';
IconModule,
TabsModule,
CommonModule,
GuiGridModule,
PTableModule,
PTooltipModule,
InputTextModule,
ProgressModule,
ReactiveFormsModule,
ButtonModule,

View file

@ -1,116 +1,165 @@
// Here you can add other styles
ngx-super-select.styled>div > span{
background:var(--cui-input-group-addon-bg, #d8dbe0);
height:100%;
padding:8px;
color:var(--cui-input-group-addon-color, rgba(44, 56, 74, 0.95))!important;
ngx-super-select.styled>div>span {
background: var(--cui-input-group-addon-bg, #d8dbe0);
height: 100%;
padding: 8px;
color: var(--cui-input-group-addon-color, rgba(44, 56, 74, 0.95)) !important;
}
ngx-super-select.styled>div {
padding:0!important;
width:100%;
padding: 0 !important;
width: 100%;
}
ngx-super-select.styled{
width:100%;
ngx-super-select.styled {
width: 100%;
}
ngx-super-select.styled.hiden .select-selected-items:before{
content:"click for select"
ngx-super-select.styled.hiden .select-selected-items:before {
content: "click for select"
}
ngx-super-select.styled.hiden .select-selected-items > *{
display:none!important;
ngx-super-select.styled.hiden .select-selected-items>* {
display: none !important;
}
ngx-super-select.styled .selection-counter:before{
content:"Selected Count : "
ngx-super-select.styled .selection-counter:before {
content: "Selected Count : "
}
ngx-super-select.styled .selection-counter{
color:#000!important;
background:#aaaaaa26!important;
margin:0!important;
ngx-super-select.styled .selection-counter {
color: #000 !important;
background: #aaaaaa26 !important;
margin: 0 !important;
}
.cdk-overlay-container {
z-index: 10000;
z-index: 10000;
}
.mdc-text-field--no-label:not(.mdc-text-field--outlined):not(.mdc-text-field--textarea) .mat-mdc-form-field-infix {
padding-top: 6px;
padding-bottom: 6px;
}
.mat-mdc-form-field-infix {
min-height: 34px;
padding-top: 6px;
padding-bottom: 6px;
}
.mdc-text-field--filled:not(.mdc-text-field--disabled){
background: transparent!important;
.mat-mdc-form-field-infix {
min-height: 34px;
}
.mdc-text-field--filled:not(.mdc-text-field--disabled) {
background: transparent !important;
}
/* Add application styles & imports to this file! */
pre {
display: flex!important;
margin-top: 0!important;
margin-bottom: 0!important;
word-wrap: break-word!important;
}
code {
flex: 1!important;
line-height: 1.8em!important;
font-size: 14px!important;
min-height: 100%!important;
padding: 1em 1.2em!important;
overflow-x: unset!important;
overflow-y: unset!important;
}
.hljs.hljs-line-numbers {
padding: 0 !important;
}
pre .hljs {
border: none!important;
transition: border ease 1s!important;
}
.hljs-ln {
tr {
&:first-child td {
padding-top: 10px !important;
}
&:last-child td {
padding-bottom: 10px !important;
}
display: flex !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
word-wrap: break-word !important;
}
code {
flex: 1 !important;
line-height: 1.8em !important;
font-size: 14px !important;
min-height: 100% !important;
padding: 1em 1.2em !important;
overflow-x: unset !important;
overflow-y: unset !important;
}
.hljs.hljs-line-numbers {
padding: 0 !important;
}
pre .hljs {
border: none !important;
transition: border ease 1s !important;
}
.hljs-ln {
tr {
&:first-child td {
padding-top: 10px !important;
}
&:last-child td {
padding-bottom: 10px !important;
}
}
.row-highlighted{
z-index: 0!important;
}
.mat-mdc-menu-panel,.mat-mdc-menu-content{
background: transparent!important;
padding: 0!important;
}
/* for block of numbers */
td.hljs-ln-numbers {
position: sticky!important;
left: 0!important;
user-select: none!important;
text-align: center!important;
color: #cccccc6b!important;
border-right: 1px solid #cccccc1c!important;
vertical-align: top!important;
padding-right: 10px !important;
padding-left: 10px !important;
}
/* for block of code */
td.hljs-ln-code {
padding-left: 10px !important;
}
}
.gui-structure, .gui-structure *{
font-size: inherit!important;
}
.row-highlighted {
z-index: 0 !important;
}
.mat-mdc-menu-panel,
.mat-mdc-menu-content {
background: transparent !important;
padding: 0 !important;
}
/* for block of numbers */
td.hljs-ln-numbers {
position: sticky !important;
left: 0 !important;
user-select: none !important;
text-align: center !important;
color: #cccccc6b !important;
border-right: 1px solid #cccccc1c !important;
vertical-align: top !important;
padding-right: 10px !important;
padding-left: 10px !important;
}
/* for block of code */
td.hljs-ln-code {
padding-left: 10px !important;
}
.gui-structure,
.gui-structure * {
font-size: inherit !important;
}
.p-datatable-table-container thead th:first-of-type {
border-top-left-radius: 8px;
}
.p-datatable-table-container thead th:last-of-type {
border-top-right-radius: 8px;
}
.p-datatable-table-container thead th {
padding: 0.30rem 1rem !important;
font-size: 0.7rem !important;
}
.justify-between {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
}
.table-search-input input {
border-top: none;
border-left: none !important;
border-right: none !important;
border-bottom: 1px !important solid;
border-radius: 0;
padding: 0 5px !important;
}
.table-search-input {
padding-top: 15px;
margin: 0 !important;
}
p-table {
width: 100%
}

View file

@ -25,4 +25,5 @@ $enable-rtl: true;
// animations
@import "animations";
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
@import "primeicons/primeicons.css";

View file

@ -4,7 +4,8 @@
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": [
"@angular/localize"
"@angular/localize",
"hammerjs"
],
"paths": {
"@docs-components/*": ["./src/components/*"]

View file

@ -14,7 +14,7 @@
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
@ -23,7 +23,11 @@
"lib": [
"ES2022",
"dom"
]
],
"skipLibCheck": true,
"paths": {
"@primeuix/themes/*": ["node_modules/@primeuix/themes/dist/*"]
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,