From 10d4cff4a46eec2a8cfff1406c9870bf431ef723 Mon Sep 17 00:00:00 2001 From: sepehr Date: Mon, 3 Feb 2025 12:35:12 +0300 Subject: [PATCH] New menu/page for configuration sync and cloning (pro feature). Now displays DHCP server details along with historical lease information(pro feature)). Added a ping status component for monitoring device connectivity. Device pages now include logs, accounting, and authorization data for improved tracking. New component in the device page to display active sessions with the ability to terminate them. Redesigned Device Detail Page Reload Buttons for device details page Tabs Device detail pages now refresh every 30 seconds. Restored the missing device logs action menu on the devices list page. Improved handling of duplicate serial numbers when the license serial is not set, with better registration status details on the dashboard. Various minor stability and performance improvements. --- src/app/app-routing.module.ts | 5 + src/app/containers/default-layout/_nav.ts | 9 +- src/app/providers/mikrowizard/data.ts | 48 ++ src/app/views/acc_log/acc.component.html | 18 +- src/app/views/acc_log/acc.component.scss | 4 + src/app/views/acc_log/acc.component.ts | 14 +- src/app/views/acc_log/acc.module.ts | 5 + src/app/views/auth_log/auth.component.html | 24 +- src/app/views/auth_log/auth.component.scss | 4 + src/app/views/auth_log/auth.component.ts | 14 +- src/app/views/auth_log/auth.module.ts | 7 +- src/app/views/cloner/cloner-routing.module.ts | 21 + src/app/views/cloner/cloner.component.html | 239 ++++++++ src/app/views/cloner/cloner.component.ts | 458 ++++++++++++++++ src/app/views/cloner/cloner.module.ts | 44 ++ src/app/views/cloner/cloner.scss | 133 +++++ .../views/dashboard/dashboard.component.html | 10 +- .../active-users-routing.module.ts | 21 + .../active-users/active-users.component.html | 38 ++ .../active-users/active-users.component.scss | 0 .../active-users/active-users.component.ts | 27 + .../active-users/active-users.module.ts | 46 ++ .../device-info/device-info-routing.module.ts | 21 + .../device-info/device-info.component.html | 40 ++ .../device-info/device-info.component.scss | 0 .../device-info/device-info.component.ts | 25 + .../device-info/device-info.module.ts | 56 ++ .../views/device_detail/device.component.html | 517 ++++++++++-------- .../views/device_detail/device.component.scss | 32 ++ .../views/device_detail/device.component.ts | 83 ++- src/app/views/device_detail/device.module.ts | 25 +- .../dhcp-info/dhcp-info.component.html | 168 ++++++ .../dhcp-info/dhcp-info.component.scss | 0 .../dhcp-info/dhcp-info.component.ts | 47 ++ .../dhcp-info/dhcp-info.module.ts | 57 ++ .../dhcp-info/dhcp-inforouting.module.ts | 21 + .../ping-status/ping-status-routing.module.ts | 21 + .../ping-status/ping-status.component.html | 42 ++ .../ping-status/ping-status.component.scss | 0 .../ping-status/ping-status.component.ts | 19 + .../ping-status/ping-status.module.ts | 46 ++ .../radio-data/interface-bw.component.html | 77 +++ .../radio-data/radio-data-routing.module.ts | 21 + .../radio-data/radio-data.component.scss | 0 .../radio-data/radio-data.component.ts | 24 + .../radio-data/radio-data.module.ts | 56 ++ .../views/device_logs/devlogs.component.html | 30 +- .../views/device_logs/devlogs.component.scss | 4 + .../views/device_logs/devlogs.component.ts | 15 +- src/app/views/device_logs/devlogs.module.ts | 3 + src/app/views/devices/devices.component.html | 25 +- src/app/views/devices/devices.component.ts | 6 + src/app/views/syslog/syslog.component.html | 7 +- src/app/views/syslog/syslog.component.scss | 4 + src/app/views/syslog/syslog.module.ts | 2 + 55 files changed, 2412 insertions(+), 271 deletions(-) create mode 100644 src/app/views/cloner/cloner-routing.module.ts create mode 100644 src/app/views/cloner/cloner.component.html create mode 100644 src/app/views/cloner/cloner.component.ts create mode 100644 src/app/views/cloner/cloner.module.ts create mode 100644 src/app/views/cloner/cloner.scss create mode 100644 src/app/views/device_detail/active-users/active-users-routing.module.ts create mode 100644 src/app/views/device_detail/active-users/active-users.component.html create mode 100644 src/app/views/device_detail/active-users/active-users.component.scss create mode 100644 src/app/views/device_detail/active-users/active-users.component.ts create mode 100644 src/app/views/device_detail/active-users/active-users.module.ts create mode 100644 src/app/views/device_detail/device-info/device-info-routing.module.ts create mode 100644 src/app/views/device_detail/device-info/device-info.component.html create mode 100644 src/app/views/device_detail/device-info/device-info.component.scss create mode 100644 src/app/views/device_detail/device-info/device-info.component.ts create mode 100644 src/app/views/device_detail/device-info/device-info.module.ts create mode 100644 src/app/views/device_detail/dhcp-info/dhcp-info.component.html create mode 100644 src/app/views/device_detail/dhcp-info/dhcp-info.component.scss create mode 100644 src/app/views/device_detail/dhcp-info/dhcp-info.component.ts create mode 100644 src/app/views/device_detail/dhcp-info/dhcp-info.module.ts create mode 100644 src/app/views/device_detail/dhcp-info/dhcp-inforouting.module.ts create mode 100644 src/app/views/device_detail/ping-status/ping-status-routing.module.ts create mode 100644 src/app/views/device_detail/ping-status/ping-status.component.html create mode 100644 src/app/views/device_detail/ping-status/ping-status.component.scss create mode 100644 src/app/views/device_detail/ping-status/ping-status.component.ts create mode 100644 src/app/views/device_detail/ping-status/ping-status.module.ts create mode 100644 src/app/views/device_detail/radio-data/interface-bw.component.html create mode 100644 src/app/views/device_detail/radio-data/radio-data-routing.module.ts create mode 100644 src/app/views/device_detail/radio-data/radio-data.component.scss create mode 100644 src/app/views/device_detail/radio-data/radio-data.component.ts create mode 100644 src/app/views/device_detail/radio-data/radio-data.module.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 806ed24..3c4186a 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -84,6 +84,11 @@ const routes: Routes = [ loadChildren: () => import('./views/user_tasks/user_tasks.module').then((m) => m.UserTasksModule) }, + { + path: 'cloner', + loadChildren: () => + import('./views/cloner/cloner.module').then((m) => m.ClonerModule) + }, { path: 'snippets', loadChildren: () => diff --git a/src/app/containers/default-layout/_nav.ts b/src/app/containers/default-layout/_nav.ts index 6339791..d313617 100644 --- a/src/app/containers/default-layout/_nav.ts +++ b/src/app/containers/default-layout/_nav.ts @@ -60,10 +60,17 @@ export const navItems: INavData[] = [ icon: 'fa-solid fa-database' }, { - name: 'snippets', + name: 'Snippets', url: '/snippets', icon: 'fa-solid fa-code' }, + { + name: 'Sync and Cloner', + url: '/cloner', + icon: 'fa-solid fa-rotate', + attributes: { 'pro':true } + + }, { name: 'Password Vault', url: '/vault', diff --git a/src/app/providers/mikrowizard/data.ts b/src/app/providers/mikrowizard/data.ts index b80fbb0..ba6c8a4 100644 --- a/src/app/providers/mikrowizard/data.ts +++ b/src/app/providers/mikrowizard/data.ts @@ -172,6 +172,12 @@ export class dataProvider { } return this.MikroWizardRPC.sendJsonRequest("/api/dev/radio/sensors", data); } + get_dev_dhcp_info(id: number){ + var data={ + 'devid':id, + } + return this.MikroWizardRPC.sendJsonRequest("/api/dev/dhcp-server/get", data); + } get_dev_ifstat(id: number,delta:string="5m",iface:string="ether1",type:string="bps") { var data={ 'devid':id, @@ -553,6 +559,48 @@ export class dataProvider { } return this.MikroWizardRPC.sendJsonRequest("/api/sysconfig/apply_update", data); } + + + get_cloner_list() { + return this.MikroWizardRPC.sendJsonRequest("/api/cloner/list", {}); + } + + Add_cloner(data:any,members:any) { + data['members']=members; + return this.MikroWizardRPC.sendJsonRequest("/api/cloner/create", data); + } + + Delete_cloner(clonerid:number) { + var data={ + 'clonerid':clonerid, + } + return this.MikroWizardRPC.sendJsonRequest("/api/cloner/delete", data); + } + + Edit_cloner(data:any,members:any) { + data['members']=members; + return this.MikroWizardRPC.sendJsonRequest("/api/cloner/edit", data); + } + + get_cloner_members(clonerid:number) { + var data={ + 'clonerid':clonerid, + } + return this.MikroWizardRPC.sendJsonRequest("/api/cloner/memberdetails", data); + } + killSession(devid:number,item:any){ + var data={ + 'devid':devid, + 'item':item + } + return this.MikroWizardRPC.sendJsonRequest("/api/dev/kill_session", data); + } + getDhcpHistory(item:any){ + var data={ + 'item':item + } + return this.MikroWizardRPC.sendJsonRequest("/api/dhcp-history/get", data); + } //// //// End api funcs //// diff --git a/src/app/views/acc_log/acc.component.html b/src/app/views/acc_log/acc.component.html index 159b290..3b1b9b8 100644 --- a/src/app/views/acc_log/acc.component.html +++ b/src/app/views/acc_log/acc.component.html @@ -1,10 +1,22 @@ - + - - Accunting Logs + +
Accunting Logs + + + +
+ Filtered Result For Device ID + {{devid}} + + Showing last 24 hours logs by default. Use filters to modify the date and time. +
+ +
+
+ + + + + {{value}} + + + + + {{value}} + + + + + {{value}} + + + + + + + + + + + +
+
+
+ + + +
Editing Cloner {{SelectedCloner['name']}}
+
Adding new task
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
{{ section.title }}
+
+
+ + +
{{ command }}
+
+ + +
+
+
+
+
+
+
+
+
+ +
Peers :
+ + + + +   {{value}} + + + + {{value}} + + + + + + + + + +
+ +
+ + + + + +
+ + + + +
Editing Group
+ +
+ + +
Group Members :
+ + + +   {{value}} + + + + {{value}} + + + + + {{value}} + + + +
+
+
+
+ + + + + +
+ + + + +
Confirm delete {{ SelectedCloner['name'] }}
+ +
+ + Are you sure that You want to delete following task ? +
+
+ + + + + + + + + + + + + +
Taks name : {{ SelectedCloner['name'] }}
Description : {{ SelectedCloner['description'] }}
Cron exec : {{ SelectedCloner['desc_cron'] }}
+
+ + + + +
+ + \ No newline at end of file diff --git a/src/app/views/cloner/cloner.component.ts b/src/app/views/cloner/cloner.component.ts new file mode 100644 index 0000000..70a8a90 --- /dev/null +++ b/src/app/views/cloner/cloner.component.ts @@ -0,0 +1,458 @@ +import { Component, OnInit, OnDestroy, ViewChildren ,QueryList } 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 { NgxSuperSelectOptions } from "ngx-super-select"; +import { _getFocusedElementPierceShadowDom } from "@angular/cdk/platform"; + + +import { ToasterComponent } from "@coreui/angular"; +import { AppToastComponent } from "../toast-simple/toast.component"; + +@Component({ + templateUrl: "cloner.component.html", + styleUrls: ["cloner.scss"], + +}) +export class ClonerComponent implements OnInit { + public uid: number; + public uname: string; + public ispro: boolean = false; + + constructor( + private data_provider: dataProvider, + private router: Router, + private login_checker: loginChecker + ) { + var _self = this; + if (!this.login_checker.isLoggedIn()) { + setTimeout(function () { + _self.router.navigate(["login"]); + }, 100); + } + this.data_provider.getSessionInfo().then((res) => { + _self.uid = res.uid; + _self.uname = res.name; + _self.ispro = res['ISPRO'] + const userId = _self.uid; + + if (res.role != "admin") { + setTimeout(function () { + _self.router.navigate(["/user/dashboard"]); + }, 100); + } + }); + //get datagrid data + function isNotEmpty(value: any): boolean { + return value !== undefined && value !== null && value !== ""; + } + } + @ViewChildren(ToasterComponent) viewChildren!: QueryList; + + public source: Array = []; + public columns: Array = []; + public loading: boolean = true; + public rows: any = []; + public SelectedCloner: any = {}; + public SelectedClonerItems: any = ""; + public EditClonerModalVisible: boolean = false; + public DeleteConfirmModalVisible: boolean = false; + public Members: any = ""; + public SelectedMembers: any = []; + public NewMemberModalVisible: boolean = false; + public availbleMembers: any = []; + public NewMemberRows: any = []; + public SelectedNewMemberRows: any; + public master: number = 0; + public active_commands:any=[]; + public tabs:any=[ + { + "name": "Network", + "sections": [ + { + "title": "IP Management", + "commands": [ + "/ip address", + "/ip cloud", + "/ip dhcp-server", + "/ip dns", + "/ip pool", + "/ip route", + "/ip vrf" + ] + }, + { + "title": "IP Services", + "commands": [ + "/ip service" + ] + } + ] + }, + { + "name": "Security", + "sections": [ + { + "title": "Firewall", + "commands": [ + "/ip firewall address-list", + "/ip firewall connection", + "/ip firewall layer7-protocol", + "/ip firewall nat", + "/ip firewall service-port", + "/ip firewall calea", + "/ip firewall filter", + "/ip firewall mangle", + "/ip firewall raw" + ] + }, + { + "title": "IPSec", + "commands": [ + "/ip ipsec identity", + "/ip ipsec key", + "/ip ipsec peer", + "/ip ipsec profile", + "/ip ipsec settings", + "/ip ipsec statistics", + "/ip ipsec proposal", + "/ip ipsec policy", + "/ip ipsec mode-config", + "/ip ipsec active-peers", + "/ip ipsec installed-sa" + ] + } + ] + }, + { + "name": "System", + "sections": [ + { + "title": "Scripts & Scheduling", + "commands": [ + "/system script", + "/system scheduler" + ] + }, + { + "title": "Time Management", + "commands": [ + "/system ntp client servers", + "/system ntp client", + "/system ntp server", + "/system clock" + ] + }, + { + "title": "RADIUS", + "commands": [ + "/radius" + ] + } + ] + }, + { + "name": "MPLS", + "sections": [ + { + "title": "MPLS Configuration", + "commands": [ + "/mpls forwarding-table", + "/mpls interface", + "/mpls ldp", + "/mpls settings", + "/mpls traffic-eng" + ] + }, + { + "title": "VPLS", + "commands": [ + "/interface vpls" + ] + } + ] + } + ]; + + public sorting = { + enabled: true, + multiSorting: true, + }; + searching: GuiSearching = { + enabled: true, + placeholder: "Search Devices", + }; + + options: Partial = { + 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, + }; + activate_command(command:string){ + // add to active_commands if it not added before + if(!this.active_commands.includes(command)){ + this.active_commands.push(command); + } + else{ + this.active_commands=this.active_commands.filter((x:any)=>x!=command); + } + } + show_toast(title: string, body: string, color: string) { + const { ...props } = { ...this.toasterForm, color, title, body }; + const componentRef = this.viewChildren.first.addToast( + AppToastComponent, + props, + {} + ); + componentRef.instance["closeButton"] = props.closeButton; + } + show_new_member_form() { + this.NewMemberModalVisible = true; + var _self = this; + _self.availbleMembers = []; + this.SelectedNewMemberRows = []; + this.NewMemberRows = []; + + var data = { + group_id: false, + search: false, + page: false, + size: 10000, + }; + + if (this.SelectedCloner["pair_type"] == "devices") + _self.data_provider.get_dev_list(data).then((res) => { + _self.availbleMembers = res.filter( + (x: any) => !_self.SelectedClonerItems.includes(x.id) + ); + _self.NewMemberModalVisible = true; + }); + else + _self.data_provider.get_devgroup_list().then((res) => { + _self.availbleMembers = res.filter( + (x: any) => !_self.SelectedClonerItems.includes(x.id) + ); + _self.NewMemberModalVisible = true; + }); + } + + ngOnInit(): void { + this.initGridTable(); + } + + submit(action: string) { + var _self = this; + if(_self.master==0 && _self.SelectedCloner['direction']=='oneway'){ + _self.show_toast( + "Error", + "Master device is not selected", + "danger" + ); + return; + } + if(_self.SelectedCloner['direction']=='twoway' && _self.SelectedCloner['pair_type']=='groups'){ + _self.show_toast( + "Error", + "Using Groups is only allowed with Master Mode", + "danger" + ); + return; + } + if (_self.master!=0 && _self.SelectedCloner['direction']=='oneway'){ + _self.SelectedCloner["masterid"]=_self.master; + } + _self.SelectedCloner["active_commands"]=_self.active_commands; + if (action == "add") { + this.data_provider + .Add_cloner(_self.SelectedCloner, _self.SelectedClonerItems) + .then((res) => { + _self.initGridTable(); + }); + } else { + this.data_provider + .Edit_cloner(_self.SelectedCloner, _self.SelectedClonerItems) + .then((res) => { + _self.initGridTable(); + }); + } + this.EditClonerModalVisible = false; + } + + onSelectedRowsNewMembers(rows: Array): void { + this.NewMemberRows = rows; + this.SelectedNewMemberRows = rows.map((m: GuiSelectedRow) => m.source); + } + + add_new_members() { + var _self = this; + _self.SelectedMembers = [ + ...new Set(_self.SelectedMembers.concat(_self.SelectedNewMemberRows)), + ]; + + _self.SelectedClonerItems = _self.SelectedMembers.map((x: any) => { + return x.id; + }); + + this.NewMemberModalVisible = false; + } + + set_master(id:number){ + var _self=this; + this.master=id; + // sort SelectedMembers and put master on top + this.SelectedMembers= + [ + ...new Set( + this.SelectedMembers.sort((a:any,b:any)=>{ + if(a.id==_self.master) return -1; + if(b.id==_self.master) return 1; + return 0; + }) + + ), + ]; + } + + + editAddCloner(item: any, action: string) { + if (action == "showadd") { + this.SelectedCloner = { + id: 0, + name: "", + description: "", + pair_type: "devices", + live_mode: false, + schedule: false, + cron: "", + desc_cron: "", + direction: "oneway", + members: "", + action: "add", + }; + this.SelectedMembers = []; + this.SelectedClonerItems = []; + this.EditClonerModalVisible = true; + return; + } + + var _self = this; + this.SelectedCloner = { ...item }; + this.active_commands=item['active_commands']?JSON.parse(item['active_commands']):[]; + if (action != "select_change" ) { + this.SelectedCloner["action"] = "edit"; + this.data_provider.get_cloner_members(_self.SelectedCloner.id).then((res) => { + _self.SelectedMembers = res; + if(_self.SelectedCloner["master"] && _self.SelectedCloner['direction']=='oneway'){ + _self.set_master(_self.SelectedCloner["master"]); + } + _self.EditClonerModalVisible = true; + _self.SelectedClonerItems = res.map((x: any) => { + return x.id; + }); + }); + } else { + _self.SelectedMembers = []; + this.SelectedClonerItems = []; + } + } + + + in_active_commands(command:string){ + return this.active_commands.includes(command); + } + remove_member(item: any) { + var _self = this; + _self.SelectedMembers = _self.SelectedMembers.filter( + (x: any) => x.id != item.id + ); + _self.SelectedClonerItems = _self.SelectedMembers.map((x: any) => { + return x.id; + }); + } + + get_member_by_id(id: string) { + return this.Members.find((x: any) => x.id == id); + } + + confirm_delete(item: any = "", del: boolean = false) { + if (!del) { + this.SelectedCloner = { ...item }; + this.DeleteConfirmModalVisible = true; + } else { + var _self = this; + this.data_provider.Delete_cloner(_self.SelectedCloner["id"]).then((res) => { + _self.initGridTable(); + _self.DeleteConfirmModalVisible = false; + }); + } + } + + form_changed() { + this.editAddCloner(this.SelectedCloner, "select_change"); + } + + logger(item: any) { + console.dir(item); + } + + initGridTable(): void { + var _self = this; + this.data_provider.get_cloner_list().then((res) => { + _self.source = res.map((x: any) => { + return x; + }); + _self.loading = false; + }); + } +} diff --git a/src/app/views/cloner/cloner.module.ts b/src/app/views/cloner/cloner.module.ts new file mode 100644 index 0000000..d42f20e --- /dev/null +++ b/src/app/views/cloner/cloner.module.ts @@ -0,0 +1,44 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { FormsModule,ReactiveFormsModule } from "@angular/forms"; + +import { + ButtonModule, + CardModule, + FormModule, + GridModule, + ModalModule, + ButtonGroupModule, + ToastModule, + TooltipModule, + NavModule, + TabsModule, +} from "@coreui/angular"; +import { ClonerRoutingModule } from "./cloner-routing.module"; +import { ClonerComponent } from "./cloner.component"; +import { GuiGridModule } from "@generic-ui/ngx-grid"; + +import { NgxSuperSelectModule} from "ngx-super-select"; + +@NgModule({ + imports: [ + ClonerRoutingModule, + CardModule, + CommonModule, + GridModule, + FormModule, + ButtonModule, + ButtonGroupModule, + GuiGridModule, + ModalModule, + ReactiveFormsModule, + FormsModule, + NgxSuperSelectModule, + ToastModule, + TooltipModule, + NavModule, + TabsModule, + ], + declarations: [ClonerComponent], +}) +export class ClonerModule {} diff --git a/src/app/views/cloner/cloner.scss b/src/app/views/cloner/cloner.scss new file mode 100644 index 0000000..a502ea6 --- /dev/null +++ b/src/app/views/cloner/cloner.scss @@ -0,0 +1,133 @@ + +.nav-underline { + border-bottom: 2px solid var(--cui-nav-underline-border-color, #c4c9d0) +} + +.nav-underline .nav-item { + margin-bottom: -2px; + cursor: pointer; +} + +.nav-underline .nav-link { + color: var(--cui-nav-underline-link-color, #768192); + border-style: none none solid!important; + border-width:2px; + position:relative; + bottom:-1px; + cursor: pointer; + +} + +.nav-underline .nav-link:hover,.nav-underline .nav-link:focus { + border-color: var(--cui-nav-underline-link-active-border-color, #321fdb) +} + +.nav-underline .nav-link.active,.nav-underline .show>.nav-link { + color: var(--cui-nav-underline-link-active-color, #321fdb); + background: transparent; + border-color: var(--cui-nav-underline-link-active-border-color, #321fdb) +} + + + + + + +.custom-control-label::before, .custom-control-label::after { + top: 0.1rem; + width: 2rem; + height: 1rem; + } + + .custom-control-input:checked ~ .custom-control-label::before { + color: #fff; + border-color: #3498db; + background-color: #3498db; + } + + .custom-control-input:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(52, 152, 219, 0.25); + } + + h5.cloner-sections { + color: #3498db; + margin-bottom: 5px; + font-size: 15px; + font-weight: 600; } + + .nav-link { + color: #333; + } + + .nav-link.active { + color: #3498db; + border-bottom: 2px solid #3498db; + } + + .command-sections c-card-body{ + display: flex!important; + justify-content: space-between!important; + padding:5px!important; + align-items: center!important; + justify-content: space-between!important; + border-radius: 4px; +} + +.command-sections c-card-body h6{ + margin: 0!important; + font-size: 12px!important; + color: var(--primary-color); + white-space: nowrap!important; + overflow: hidden!important; + text-overflow: ellipsis!important; + width: 80%!important; + font-weight: bold; + +} +.command-sections c-card{ + border: 1px solid #e0e0e0!important;; + border-radius: 4px!important;; +} + + /* Checkbox Styling */ + .custom-switch { + position: relative; + width: 40px; + height: 20px; + } + + .custom-switch input { + display: none; + } + + .custom-control-label { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 20px; + background: #ccc; + cursor: pointer; + transition: all 0.3s ease; + } + + .custom-control-label::after { + content: ''; + position: absolute; + left: 2px; + top: 2px; + width: 16px; + height: 16px; + background: #fff; + border-radius: 50%; + transition: all 0.3s ease; + } + + .custom-switch input:checked + .custom-control-label { + background: #3498db; + } + + .custom-switch input:checked + .custom-control-label::after { + left: calc(100% - 18px); + } \ No newline at end of file diff --git a/src/app/views/dashboard/dashboard.component.html b/src/app/views/dashboard/dashboard.component.html index e454cf9..af6b2e9 100644 --- a/src/app/views/dashboard/dashboard.component.html +++ b/src/app/views/dashboard/dashboard.component.html @@ -139,9 +139,8 @@ Copy - Not Registred - Learn how to register and get automatic - updates! + Not Registred + License Validation failed
@@ -153,7 +152,7 @@
Unable connect to server/Check server internet connection
-
+
Serial: Update availble
+

License User name is not set in settings read more!

+

Serial number not submitedread more!

+ - - - - - - - - + + + + +

+ {{ devdata["name"] }} ( {{ devdata["ip"] }} ) +

+
+ +
+
+
- {{value}} - {{item['default-name']}} -
-
+ + - - - - {{value}} - - - - - -
{{convert_bw_human(value,'rx')}}
-
-
- - - - {{convert_bw_human(value,'tx')}} - - - - - - curr:{{value}}
- max : {{item['max-l2mtu']}} -
-
- - - - {{convert_bw_human(value,'rx')}} - - - - - - - {{convert_bw_human(value,'tx')}} - - - - - - {{value}} - - - - - - {{value}} - - - - - {{value}} - - - - - - - -
-
-
-
-
-
- - - - -
Radio data
- -
{{raddata.key}}
- + +
Loading...
+ + + + + + + + + + + + + + + + + + + + - - - - - - - - - - -
{{d.key}}{{d.value}}
+ + + + + + - + + + + + + + +
+ + + + + + + + + + {{ value }} - {{ item["default-name"] }} + + - - - - - - - - - -
{{d.key}}{{d.value}}
-
- - - - - - - - - - -
{{d.key}}{{d.value}}
-
- - - - - - - - - - -
{{d.key}}{{d.value}}
-
+ + + {{ value }} + + + + +
{{ convert_bw_human(value, "rx") }}
+
+
+ + + {{ convert_bw_human(value, "tx") }} + + + + + curr:{{ value }}
+ max : {{ item["max-l2mtu"] }} +
+
+ + + {{ convert_bw_human(value, "rx") }} + + + + + {{ convert_bw_human(value, "tx") }} + + + + + {{ value }} + + + + + {{ value }} + + + + + {{ value }} + + + + + + + + +
+
+
+
- - - - - - - - - -
Strength at rates - {{st}} -
-
+ + + + + + +
Radio data
+
+ +
{{ raddata.key }}
+ + + + + + + + + + + + +
+ {{ d.key }} + {{ d.value }}
+
+ + + + + + + + + + +
+ {{ d.key }} + {{ d.value }}
+
+ + + + + + + + + + +
+ {{ d.key }} + {{ d.value }}
+
+ + + + + + + + + + +
{{ d.key }}{{ d.value }}
+
+
+ + + + + + + + + +
+ Strength at rates + + {{ st }} +
+
+
+
+
+
- - +
+ + + + + + + + + + + + + + +
- -
{{interface_rate['name']}}
+
{{ interface_rate["name"] }}
- - + + + + + + + + + + + + + +
History for {{current_dhcp['mac-address']}}
+ +
+ + + + +
{{value}}
+
+
+ + + {{value}} + + + + + {{value}} + + + + + {{item.name}} ({{value}}) + + +
+
+ + + +
\ No newline at end of file diff --git a/src/app/views/device_detail/dhcp-info/dhcp-info.component.scss b/src/app/views/device_detail/dhcp-info/dhcp-info.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/views/device_detail/dhcp-info/dhcp-info.component.ts b/src/app/views/device_detail/dhcp-info/dhcp-info.component.ts new file mode 100644 index 0000000..31d21c9 --- /dev/null +++ b/src/app/views/device_detail/dhcp-info/dhcp-info.component.ts @@ -0,0 +1,47 @@ +import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component,Input } from '@angular/core'; +import { dataProvider } from "../../../providers/mikrowizard/data"; +import { formatInTimeZone } from "date-fns-tz"; + +@Component({ + selector: 'app-dhcp-info', + templateUrl: './dhcp-info.component.html', + styleUrls: ['./dhcp-info.component.scss'], + changeDetection: ChangeDetectionStrategy.Default +}) +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; + dhcp_history:any; + dhcp_history_modal: boolean = false; + current_dhcp:any; + constructor( + private changeDetectorRef: ChangeDetectorRef, + private data_provider: dataProvider, + ) {} + + show_history(item:any) { + var _self = this; + this.data_provider.getDhcpHistory(item).then((res) => { + _self.current_dhcp = item; + this.dhcp_history = res.map((d: any) => { + d.eventtime = formatInTimeZone( + d.eventtime.split(".")[0] + ".000Z", + _self.tz, + "yyyy-MM-dd HH:mm:ss XXX" + ); + return d; + }); + this.dhcp_history_modal=!this.dhcp_history_modal; + }); + } + ngAfterContentInit(): void { + this.changeDetectorRef.detectChanges(); + } +} diff --git a/src/app/views/device_detail/dhcp-info/dhcp-info.module.ts b/src/app/views/device_detail/dhcp-info/dhcp-info.module.ts new file mode 100644 index 0000000..d493f2a --- /dev/null +++ b/src/app/views/device_detail/dhcp-info/dhcp-info.module.ts @@ -0,0 +1,57 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { + ButtonGroupModule, + ButtonModule, + CardModule, + FormModule, + GridModule, + ProgressModule, + NavbarModule, + AlertModule, + ModalModule, + TableModule, + UtilitiesModule, + BadgeModule, + NavModule, + TabsModule, +} from '@coreui/angular'; +import { ChartjsModule } from '@coreui/angular-chartjs'; + +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"; + +@NgModule({ + declarations: [ + DhcpInfoComponent, + ], + imports: [ + CardModule, + AlertModule, + CommonModule, + GridModule, + ProgressModule, + FormModule, + ButtonModule, + ButtonGroupModule, + ChartjsModule, + WidgetsModule, + NavbarModule, + ModalModule, + GuiGridModule, + TableModule, + UtilitiesModule, + BadgeModule, + NavModule, + TabsModule, + ], + exports: [ + DhcpInfoComponent, + ] +}) +export class DhcpInfoModule { +} diff --git a/src/app/views/device_detail/dhcp-info/dhcp-inforouting.module.ts b/src/app/views/device_detail/dhcp-info/dhcp-inforouting.module.ts new file mode 100644 index 0000000..7216364 --- /dev/null +++ b/src/app/views/device_detail/dhcp-info/dhcp-inforouting.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { DhcpInfoComponent } from './dhcp-info.component'; + +const routes: Routes = [ + { + path: '', + component: DhcpInfoComponent, + data: { + title: 'Widgets' + } + } +]; + + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class DhcpInfoRoutingModule { +} diff --git a/src/app/views/device_detail/ping-status/ping-status-routing.module.ts b/src/app/views/device_detail/ping-status/ping-status-routing.module.ts new file mode 100644 index 0000000..2aa3361 --- /dev/null +++ b/src/app/views/device_detail/ping-status/ping-status-routing.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { PingStatsComponent } from './ping-status.component'; + +const routes: Routes = [ + { + path: '', + component: PingStatsComponent, + data: { + title: 'Widgets' + } + } +]; + + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class PingStatsRoutingModule { +} diff --git a/src/app/views/device_detail/ping-status/ping-status.component.html b/src/app/views/device_detail/ping-status/ping-status.component.html new file mode 100644 index 0000000..10a201c --- /dev/null +++ b/src/app/views/device_detail/ping-status/ping-status.component.html @@ -0,0 +1,42 @@ + + + + +
Ping status
+
+ +
Successful pings : + {{ping['successful_pings']}}
+
| Failed pings : {{ping['failed_pings']}}
+
| Avrage : + {{ping['average_ping_time']}} ms
+ + + + + + + + + + + + + + + + + + + + + +
#HostTimeStatusQualitydetails
{{i+1}}{{item['host']}}{{item['time']}} ms{{item['status']}}{{item['ping_quality']}}
+
+
+
+
\ No newline at end of file diff --git a/src/app/views/device_detail/ping-status/ping-status.component.scss b/src/app/views/device_detail/ping-status/ping-status.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/views/device_detail/ping-status/ping-status.component.ts b/src/app/views/device_detail/ping-status/ping-status.component.ts new file mode 100644 index 0000000..ec2a2b8 --- /dev/null +++ b/src/app/views/device_detail/ping-status/ping-status.component.ts @@ -0,0 +1,19 @@ +import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component,Input } from '@angular/core'; + +@Component({ + selector: 'app-ping-stats', + templateUrl: './ping-status.component.html', + styleUrls: ['./ping-status.component.scss'], + changeDetection: ChangeDetectionStrategy.Default +}) +export class PingStatsComponent implements AfterContentInit { + @Input() ping: any; + constructor( + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngAfterContentInit(): void { + + this.changeDetectorRef.detectChanges(); + } +} diff --git a/src/app/views/device_detail/ping-status/ping-status.module.ts b/src/app/views/device_detail/ping-status/ping-status.module.ts new file mode 100644 index 0000000..cc3b3a0 --- /dev/null +++ b/src/app/views/device_detail/ping-status/ping-status.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { + ButtonGroupModule, + ButtonModule, + CardModule, + FormModule, + GridModule, + ProgressModule, + NavbarModule, + ModalModule, + TableModule, + UtilitiesModule, + BadgeModule, + TooltipModule, +} from '@coreui/angular'; + +// import { WidgetsRoutingModule } from './widgets-routing.module'; +import { PingStatsComponent } from './ping-status.component'; + +@NgModule({ + declarations: [ + PingStatsComponent, + ], + imports: [ + CardModule, + CommonModule, + GridModule, + ProgressModule, + FormModule, + ButtonModule, + ButtonGroupModule, + NavbarModule, + ModalModule, + TableModule, + UtilitiesModule, + BadgeModule, + TooltipModule, + ], + exports: [ + PingStatsComponent, + ] +}) +export class PingStatsModule { +} diff --git a/src/app/views/device_detail/radio-data/interface-bw.component.html b/src/app/views/device_detail/radio-data/interface-bw.component.html new file mode 100644 index 0000000..250b2e4 --- /dev/null +++ b/src/app/views/device_detail/radio-data/interface-bw.component.html @@ -0,0 +1,77 @@ + + +
Radio data
+
+ +
{{raddata.key}}
+ + + + + + + + + + + + +
{{d.key}}{{d.value}}
+
+ + + + + + + + + + + +
{{d.key}}{{d.value}}
+
+ + + + + + + + + + +
{{d.key}}{{d.value}}
+
+ + + + + + + + + + +
{{d.key}}{{d.value}}
+
+
+ + + + + + + + + +
+ Strength at rates + {{st}} +
+
+
+
+
\ No newline at end of file diff --git a/src/app/views/device_detail/radio-data/radio-data-routing.module.ts b/src/app/views/device_detail/radio-data/radio-data-routing.module.ts new file mode 100644 index 0000000..53b7f99 --- /dev/null +++ b/src/app/views/device_detail/radio-data/radio-data-routing.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { RadioDataComponent } from './radio-data.component'; + +const routes: Routes = [ + { + path: '', + component: RadioDataComponent, + data: { + title: 'Widgets' + } + } +]; + + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class RadioDataRoutingModule { +} diff --git a/src/app/views/device_detail/radio-data/radio-data.component.scss b/src/app/views/device_detail/radio-data/radio-data.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/views/device_detail/radio-data/radio-data.component.ts b/src/app/views/device_detail/radio-data/radio-data.component.ts new file mode 100644 index 0000000..ace8e53 --- /dev/null +++ b/src/app/views/device_detail/radio-data/radio-data.component.ts @@ -0,0 +1,24 @@ +import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component,Input } from '@angular/core'; + +@Component({ + selector: 'app-iradio-data', + templateUrl: './radio-data.component.html', + styleUrls: ['./radio-data.component.scss'], + changeDetection: ChangeDetectionStrategy.Default +}) +export class RadioDataComponent implements AfterContentInit { + @Input() raddata: any; + constructor( + private changeDetectorRef: ChangeDetectorRef + ) {} + + objectlen(object:any){ + return Object.keys(object).length; + } + strangth_at_rate_extract(data:string){ + return data.split(','); + } + ngAfterContentInit(): void { + this.changeDetectorRef.detectChanges(); + } +} diff --git a/src/app/views/device_detail/radio-data/radio-data.module.ts b/src/app/views/device_detail/radio-data/radio-data.module.ts new file mode 100644 index 0000000..0b286bb --- /dev/null +++ b/src/app/views/device_detail/radio-data/radio-data.module.ts @@ -0,0 +1,56 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { + ButtonGroupModule, + ButtonModule, + CardModule, + FormModule, + GridModule, + ProgressModule, + NavbarModule, + AlertModule, + ModalModule, + TableModule, + UtilitiesModule, + BadgeModule, + NavModule, + TabsModule, +} from '@coreui/angular'; +import { IconModule } from '@coreui/icons-angular'; +import { ChartjsModule } from '@coreui/angular-chartjs'; + +import { WidgetsModule } from "../../widgets/widgets.module"; + +// import { WidgetsRoutingModule } from './widgets-routing.module'; +import { RadioDataComponent } from './radio-data.component'; + +@NgModule({ + declarations: [ + RadioDataComponent, + ], + imports: [ + CardModule, + AlertModule, + CommonModule, + GridModule, + ProgressModule, + FormModule, + ButtonModule, + ButtonGroupModule, + ChartjsModule, + WidgetsModule, + NavbarModule, + ModalModule, + TableModule, + UtilitiesModule, + BadgeModule, + NavModule, + TabsModule, + ], + exports: [ + RadioDataComponent, + ] +}) +export class RadioDataModule { +} diff --git a/src/app/views/device_logs/devlogs.component.html b/src/app/views/device_logs/devlogs.component.html index 89ffbf0..5d06c01 100644 --- a/src/app/views/device_logs/devlogs.component.html +++ b/src/app/views/device_logs/devlogs.component.html @@ -1,10 +1,22 @@ - + - - Device LOGS + +
Device LOGS + + + +
+ Filtered Result For Device ID + {{devid}} + + Showing last 24 hours logs by default. Use filters to modify the date and time. +
@@ -176,8 +179,8 @@
Please select searching method
- +
@@ -256,8 +259,9 @@
-
- Empty username and password means system default +
+ Empty username and password means system default configuration
@@ -271,9 +275,7 @@ + [source]="ExecutedData" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [paging]="paging">   {{value}} @@ -286,18 +288,18 @@   {{value}} - + {{value}} - + -
+

@@ -377,7 +379,8 @@ peer ip - diff --git a/src/app/views/devices/devices.component.ts b/src/app/views/devices/devices.component.ts index edb012b..ef56bf8 100644 --- a/src/app/views/devices/devices.component.ts +++ b/src/app/views/devices/devices.component.ts @@ -173,6 +173,9 @@ export class DevicesComponent implements OnInit, OnDestroy { case "logauth": this.router.navigate(["/authlog", { devid: dev.id }]); break; + case "devlogs": + this.router.navigate(["/devlogs", { devid: dev.id }]); + break; case "logacc": this.router.navigate(["/accountlog", { devid: dev.id }]); break; @@ -403,6 +406,9 @@ export class DevicesComponent implements OnInit, OnDestroy { "danger" ); } + else if ("error" in res) { + _self.show_toast("Error", res.error, "danger"); + } else{ _self.show_toast("info", "Updating Firmwares Sent", "light"); _self.initGridTable();} diff --git a/src/app/views/syslog/syslog.component.html b/src/app/views/syslog/syslog.component.html index 6be0bf3..0f804b1 100644 --- a/src/app/views/syslog/syslog.component.html +++ b/src/app/views/syslog/syslog.component.html @@ -3,8 +3,11 @@ - - Devices + +
Devices
+ + Showing last 24 hours logs by default. Use filters to modify the date and time. +