diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index f2ade54..806ed24 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -29,6 +29,11 @@ const routes: Routes = [ loadChildren: () => import('./views/monitoring/monitoring.module').then((m) => m.MonitoringModule) }, + { + path: 'vault', + loadChildren: () => + import('./views/vault/vault.module').then((m) => m.VaultModule) + }, { path: 'devices', loadChildren: () => diff --git a/src/app/containers/default-layout/_nav.ts b/src/app/containers/default-layout/_nav.ts index dbdc8c6..6339791 100644 --- a/src/app/containers/default-layout/_nav.ts +++ b/src/app/containers/default-layout/_nav.ts @@ -64,6 +64,12 @@ export const navItems: INavData[] = [ url: '/snippets', icon: 'fa-solid fa-code' }, + { + name: 'Password Vault', + url: '/vault', + icon:'fa-solid fa-vault', + attributes: { 'pro':true } + }, // { // name: 'Tools', // url: '/login', diff --git a/src/app/containers/default-layout/default-header/default-header.component.html b/src/app/containers/default-layout/default-header/default-header.component.html index 0aa2561..adefb87 100644 --- a/src/app/containers/default-layout/default-header/default-header.component.html +++ b/src/app/containers/default-layout/default-header/default-header.component.html @@ -77,11 +77,17 @@
User Menu
  • -
  • +
  • + +
  • diff --git a/src/app/containers/default-layout/default-header/default-header.component.ts b/src/app/containers/default-layout/default-header/default-header.component.ts index 65443f3..e957c15 100644 --- a/src/app/containers/default-layout/default-header/default-header.component.ts +++ b/src/app/containers/default-layout/default-header/default-header.component.ts @@ -50,8 +50,8 @@ export class DefaultHeaderComponent extends HeaderComponent { this.lname = this.current_user.lastname; } - callParent(): void { - this.UserModalEvent.next('test'); + callParent(action:string): void { + this.UserModalEvent.next(action); } logout() { diff --git a/src/app/containers/default-layout/default-layout.component.html b/src/app/containers/default-layout/default-layout.component.html index 6ba43e7..ea53bfd 100644 --- a/src/app/containers/default-layout/default-layout.component.html +++ b/src/app/containers/default-layout/default-layout.component.html @@ -1,43 +1,28 @@ - - + + }" routerLink="./" /> - + - +
    - +
    @@ -50,30 +35,73 @@ -
    Change Password Form of{{ uname }}({{ fname }} {{lname}})
    +
    Change Password Form + of{{ uname }}({{ fname }} {{lname}})
    +
    totp setup{{ uname }}({{ fname }} {{lname}})
    +
    - +
    - +
    - +
    - +
    - +
    {{error}}
    + +
    + +
    +

    Step 1: Enable TOTP

    +

    Please click the button below to enable Two-Factor Authentication.

    + + +
    + + +
    +

    Step 2: Scan QR Code

    +

    Open your Google Authenticator app and scan the QR code below:

    +
    + QR Code +
    + +
    + + +
    +

    Step 3: Verify TOTP

    +

    Step 3: Verify TOTP To Disable TOTP

    + +

    Please enter the code generated by your authenticator app:

    + +
    +
    + + + +
    - - + + diff --git a/src/app/containers/default-layout/default-layout.component.ts b/src/app/containers/default-layout/default-layout.component.ts index a4cc830..67c1d09 100644 --- a/src/app/containers/default-layout/default-layout.component.ts +++ b/src/app/containers/default-layout/default-layout.component.ts @@ -4,6 +4,8 @@ import { loginChecker } from '../../providers/login_checker'; import { User } from '../../providers/mikrowizard/user'; import { navItems } from './_nav'; import { dataProvider } from '../../providers/mikrowizard/data'; +import { arch } from 'os'; +import { DomSanitizer } from '@angular/platform-browser'; @Component({ selector: 'app-dashboard', @@ -19,8 +21,13 @@ export class DefaultLayoutComponent implements OnInit { public fname: string; public lname: string; public ispro: boolean=false; + public action: string="password"; public UserProfileModalVisible:boolean; public error:any=false; + public currentStep:number=1; + public qrCode:any=false; + public totpCode:string=''; + public errorMessage:any=false; public password:any={ 'cupass':'', 'pass1':'', @@ -38,6 +45,7 @@ export class DefaultLayoutComponent implements OnInit { private router: Router, private login_checker: loginChecker, private data_provider: dataProvider, + private _sanitizer: DomSanitizer ) { var _self = this; @@ -52,7 +60,39 @@ export class DefaultLayoutComponent implements OnInit { } } }); + } + otpwizard(step:number){ + var _self=this; + if(step==1){ + if(this.qrCode) + this.currentStep=2; + else + this.currentStep=3; + } + if(step==2){ + this.currentStep=3; + } + if(step==3){ + if(this.qrCode!=false) + this.data_provider.mytotp('enable',this.totpCode).then(res => { + if(res['status']=='success'){ + _self.UserProfileModalVisible = false; + } + else{ + this.errorMessage=res['err']; + } + }); + else + this.data_provider.mytotp('disable',this.totpCode).then(res => { + if(res['status']=='success'){ + _self.UserProfileModalVisible = false; + } + else{ + this.errorMessage=res['err']; + } + }); + } } password_changed(variable:string,value:any){ @@ -66,8 +106,29 @@ export class DefaultLayoutComponent implements OnInit { } } - show_user_modal(){ - this.UserProfileModalVisible = true; + show_user_modal(action:string){ + this.currentStep=1; + this.errorMessage=false; + this.totpCode=''; + this.qrCode=false; + this.action=action; + if(action=='otp') + this.data_provider.mytotp('enable').then(res => { + if(res['status']=='success'){ + this.currentStep=1; + this.qrCode=this._sanitizer.bypassSecurityTrustResourceUrl('data:image/jpg;base64,'+ res.otp); + this.UserProfileModalVisible = true; + } + else{ + this.qrCode=false; + this.currentStep=1; + this.UserProfileModalVisible = true; + + this.errorMessage=res['err']; + } + }); + else + this.UserProfileModalVisible = true; } submit(){ diff --git a/src/app/providers/mikrowizard/data.ts b/src/app/providers/mikrowizard/data.ts index f6852e4..2667693 100644 --- a/src/app/providers/mikrowizard/data.ts +++ b/src/app/providers/mikrowizard/data.ts @@ -43,7 +43,6 @@ export class dataProvider { res.perms, res.tz, ); - // console.dir(JSON.stringify(usr)) localStorage.setItem('current_user', JSON.stringify(usr)); } return res; @@ -181,6 +180,35 @@ export class dataProvider { } return this.MikroWizardRPC.sendJsonRequest("/api/dev/ifstat", data); } + totp(action:string,userid:string){ + var data={ + 'userid':userid, + 'action':action + } + return this.MikroWizardRPC.sendJsonRequest("/api/user/totp", data); + } + + get_user_restrictions(uid:string){ + var data={ + 'uid':uid + } + return this.MikroWizardRPC.sendJsonRequest("/api/user/restrictions", data); + } + save_user_restrictions(uid:string,restrictions:any){ + var data={ + 'uid':uid, + 'restrictions':restrictions + } + return this.MikroWizardRPC.sendJsonRequest("/api/user/save_restrictions", data); + } + + mytotp(action:string,otp:any=false){ + var data={ + 'action':action, + 'otp':otp + } + return this.MikroWizardRPC.sendJsonRequest("/api/user/mytotp", data); + } get_auth_logs(filters:any) { var data=filters; @@ -269,22 +297,22 @@ export class dataProvider { } return this.MikroWizardRPC.sendJsonRequest("/api/snippet/delete", data); } + get_executed_snipet(id:number){ var data={ 'id':id } return this.MikroWizardRPC.sendJsonRequest("/api/snippet/executed", data); } + get_user_task_list() { return this.MikroWizardRPC.sendJsonRequest("/api/user_tasks/list", {}); } - Add_task(data:any,members:any) { data['members']=members; return this.MikroWizardRPC.sendJsonRequest("/api/user_tasks/create", data); } - Delete_task(taskid:Number) { var data={ @@ -297,7 +325,7 @@ export class dataProvider { data['members']=members; return this.MikroWizardRPC.sendJsonRequest("/api/user_tasks/edit", data); } - + get_task_members(taskid:Number) { var data={ 'taskid':taskid, @@ -305,7 +333,6 @@ export class dataProvider { return this.MikroWizardRPC.sendJsonRequest("/api/taskmember/details", data); } - get_users(page:Number,size:Number,search:string) { var data={ 'page':page, @@ -350,6 +377,37 @@ export class dataProvider { return this.MikroWizardRPC.sendJsonRequest("/api/perms/delete", data); } + get_vault_setting(){ + return this.MikroWizardRPC.sendJsonRequest("/api/pssvault/get", {}); + } + + vault_task(data:any){ + + return this.MikroWizardRPC.sendJsonRequest("/api/pssvault/task", data); + } + vault_history(){ + return this.MikroWizardRPC.sendJsonRequest("/api/pssvault/history", {}); + } + exec_vault(){ + return this.MikroWizardRPC.sendJsonRequest("/api/pssvault/execute", {}); + } + reveal_password(devid:number,username:string){ + var data={ + 'devid':devid, + 'username':username + } + return this.MikroWizardRPC.sendJsonRequest("/api/pssvault/reveal", data); + } + + get_passwords(data:any){ + return this.MikroWizardRPC.sendJsonRequest("/api/pssvault/get_passwords", data); + } + get_device_pass(devid:number){ + var data={ + 'devid':devid + } + return this.MikroWizardRPC.sendJsonRequest("/api/pssvault/get_device_pass", data); + } user_perms(uid:string) { var data = { diff --git a/src/app/providers/mikrowizard/provider.ts b/src/app/providers/mikrowizard/provider.ts index a4c03ae..ac7deb4 100644 --- a/src/app/providers/mikrowizard/provider.ts +++ b/src/app/providers/mikrowizard/provider.ts @@ -110,7 +110,6 @@ export class MikroWizardProvider { } public sendRequestauth(url: string, params: Object){ let body = this.buildRequest(url, params); - console.dir(body); return this.http.post(this.MikroWizard_server + url, body, {observe: "response",headers: this.headers,withCredentials:true}); } public sendRequest(url: string, params: Object): Promise { @@ -164,7 +163,7 @@ export class MikroWizardProvider { username : login, password : password, // token: token, - ga: ga + otp: ga }; let $this = this; return this.sendRequest("/api/login", params); @@ -172,9 +171,6 @@ export class MikroWizardProvider { public isLoggedIn() { return this.getSessionInfo().then(function(result: any) { - // console.dir("result"); - console.dir(result); - // return true; if ( "uid" in result === false ) return false; else return true; }); diff --git a/src/app/views/devices/devices.component.html b/src/app/views/devices/devices.component.html index c4304f0..5a8cf61 100644 --- a/src/app/views/devices/devices.component.html +++ b/src/app/views/devices/devices.component.html @@ -362,7 +362,11 @@ Password - + diff --git a/src/app/views/devices/devices.component.ts b/src/app/views/devices/devices.component.ts index c846fc3..e246517 100644 --- a/src/app/views/devices/devices.component.ts +++ b/src/app/views/devices/devices.component.ts @@ -37,6 +37,8 @@ export class DevicesComponent implements OnInit, OnDestroy { public uid: number; public uname: string; public tz: string; + public ispro:boolean=false; + constructor( private data_provider: dataProvider, @@ -54,6 +56,7 @@ export class DevicesComponent implements OnInit, OnDestroy { _self.uid = res.uid; _self.uname = res.name; _self.tz = res.tz; + _self.ispro = res.ISPRO; const userId = _self.uid; if (res.role != "admin") { @@ -495,7 +498,7 @@ export class DevicesComponent implements OnInit, OnDestroy { downloadFile(data: string, filename: string, type: string) { const blob = new Blob([data], { type: type }); - const nav = (window.navigator as any); + const nav = (window.navigator as any); if (nav.msSaveOrOpenBlob) { nav.msSaveBlob(blob, filename); @@ -509,9 +512,19 @@ export class DevicesComponent implements OnInit, OnDestroy { document.body.removeChild(link); } } - - - + get_device_pass(){ + var _self=this; + _self.selected_device['editform']['password']="Loading ..."; + if (_self.ispro && !this.show_pass){ + _self.data_provider.get_device_pass(this.selected_device['id']).then((res) => { + _self.selected_device['editform']['password']=res['password']; + this.show_pass=!this.show_pass; + }); + } + else{ + this.show_pass=!this.show_pass; + } + } show_exec(){ var _self=this; this.ExecutedDataModalVisible = true; diff --git a/src/app/views/pages/login/login.component.html b/src/app/views/pages/login/login.component.html index 64a0039..77a0218 100644 --- a/src/app/views/pages/login/login.component.html +++ b/src/app/views/pages/login/login.component.html @@ -32,6 +32,17 @@ required #password /> + + + + + + {{error_msg}} diff --git a/src/app/views/pages/login/login.component.ts b/src/app/views/pages/login/login.component.ts index 73e2f4c..b3910e7 100644 --- a/src/app/views/pages/login/login.component.ts +++ b/src/app/views/pages/login/login.component.ts @@ -18,7 +18,7 @@ export class LoginComponent { public submitted = false; public forgot_page: boolean = false; public forgot_btn_disable: boolean = false; - + public show_otp: boolean = false; constructor( private router: Router, @@ -43,24 +43,25 @@ export class LoginComponent { var _self = this; let uname = _self.loginForm.get('username')!.value; let passwd = _self.loginForm.get('password')!.value; - let ga_code = ''; - console.dir(uname); - _self.data_provider.login(uname, passwd, '').then(res => { - if ('uid' in res && res['uid']){ + let ga_code = _self.loginForm.get('ga_code')!.value; + _self.data_provider.login(uname, passwd, ga_code).then(res => { + if('uid' in res && res['uid']){ _self.error_msg = ""; _self.login_checker.setStatus(true); _self.router.navigate(['/'], {replaceUrl: true}); } + else if('status' in res) { + _self.error_msg = res['err']; + } + else if('otp' in res && res['otp']){ + this.show_otp=true; + } else { - if ('reason' in res) { - } - else - _self.error_msg = res.error; + _self.error_msg = 'Error: Problem in backend'; } }).catch(err => { - _self.error_msg = "Wrong username or password!"; + _self.error_msg = "Connection with backend broken!"; }); - // }); } } diff --git a/src/app/views/settings/settings.component.html b/src/app/views/settings/settings.component.html index a0941a9..64dd84f 100644 --- a/src/app/views/settings/settings.component.html +++ b/src/app/views/settings/settings.component.html @@ -192,6 +192,13 @@ * Download and install reqired firmware before installing the target firmware . for example it will install latest 7.12 then upgrade to newer version >7.13 or install Required packages before update + + + + + PRO + * Force login to devices using otp for all users.(you can make exceptions for each user) + diff --git a/src/app/views/settings/settings.component.ts b/src/app/views/settings/settings.component.ts index 6195afa..7a8f41b 100644 --- a/src/app/views/settings/settings.component.ts +++ b/src/app/views/settings/settings.component.ts @@ -237,6 +237,9 @@ export class SettingsComponent implements OnInit { _self.sysconfigs["safe_install"]["value"] = /true/i.test( _self.sysconfigs["safe_install"]["value"] ); + _self.sysconfigs["otp_force"]["value"] = /true/i.test( + _self.sysconfigs["otp_force"]["value"] + ); _self.SysConfigloading = false; }); } diff --git a/src/app/views/user_manager/user_manager.component.html b/src/app/views/user_manager/user_manager.component.html index 0894f5d..0637be2 100644 --- a/src/app/views/user_manager/user_manager.component.html +++ b/src/app/views/user_manager/user_manager.component.html @@ -36,10 +36,14 @@ - - + + @@ -79,17 +83,6 @@
    - -
    MikroWizard permisssions :
    @@ -165,24 +158,108 @@ - - + + - + +
    + + +
    +
    + + + +
    +
    + + + +
    Security Restrictions of {{SelectedUser['username']}}
    +
    + + + + + + + + + + + + + + +
    TOTP status :
    + + + + + +
    Use OTP for device login:
    + + + + + + +
    Restrict IP access:
    + + + + + +
    + +
    Allowed ips :
    + + + +   {{item}} + + + + + + + +
    +
    + + + + +
    + Add new IP + +
    + +
    +
    + +
    +
    - - - +
    +
    Confirm delete {{ SelectedUser['name'] }}
    diff --git a/src/app/views/user_manager/user_manager.component.ts b/src/app/views/user_manager/user_manager.component.ts index 8da80eb..6bdc57f 100644 --- a/src/app/views/user_manager/user_manager.component.ts +++ b/src/app/views/user_manager/user_manager.component.ts @@ -16,26 +16,15 @@ import { NgxSuperSelectOptions } from "ngx-super-select"; import { AppToastComponent } from "../toast-simple/toast.component"; import { ToasterComponent } from "@coreui/angular"; -interface IUser { - name: string; - state: string; - registered: string; - country: string; - usage: number; - period: string; - payment: string; - activity: string; - avatar: string; - status: string; - color: string; -} - @Component({ templateUrl: "user_manager.component.html", + styleUrls: ["user_manager.scss"], }) export class UserManagerComponent implements OnInit { public uid: number; public uname: string; + public ispro:boolean=false; + gridComponent: GuiGridComponent; toasterForm = { autohide: true, @@ -59,6 +48,7 @@ export class UserManagerComponent implements OnInit { 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") { @@ -81,6 +71,7 @@ export class UserManagerComponent implements OnInit { public SelectedUserItems: string = ""; public EditTaskModalVisible: boolean = false; public DeleteConfirmModalVisible: boolean = false; + public RestrictionsTaskModalVisible: boolean = false; public Members: any = ""; public devgroup: any = {}; @@ -89,6 +80,8 @@ export class UserManagerComponent implements OnInit { public allPerms: any = []; public DeletePermConfirmModalVisible: boolean = false; public userperms: any = {}; + public userresttrictions: any = false; + public ipaddress:string=""; public adminperms: { [index: string]: string }; public defadminperms: { [index: string]: string } = { device: "none", @@ -155,7 +148,16 @@ export class UserManagerComponent implements OnInit { ); componentRef.instance["closeButton"] = props.closeButton; } - + totp(item:any){ + this.SelectedUser = item; + this.data_provider.totp('enable',this.SelectedUser.id).then((res) => { + if(res.status == "success"){ + this.show_toast("Success", "Totp generated successfully", "success"); + }else{ + this.show_toast("Error", res.err, "danger"); + } + }); + } submit(action: string) { var _self = this; if (action == "add") { @@ -178,7 +180,7 @@ export class UserManagerComponent implements OnInit { } }); } else { - console.dir(_self.userperms); if (_self.userperms.length > 0) { + if (_self.userperms.length > 0) { _self.SelectedUser["userperms"] = _self.userperms; } else { _self.SelectedUser["userperms"] = []; @@ -228,6 +230,56 @@ export class UserManagerComponent implements OnInit { _self.EditTaskModalVisible = true; } + 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; + this.SelectedUser = { ...item }; + + this.data_provider.get_user_restrictions(this.SelectedUser["id"]).then((res) => { + _self.userresttrictions = res; + console.log(_self.userresttrictions); + _self.RestrictionsTaskModalVisible = true; + }); + } + delete_ip(item:string){ + + this.userresttrictions['allowed_ips']=this.userresttrictions['allowed_ips'].filter((x:any)=>x!=item); + } + + 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)){ + this.show_toast("Error", "IP already added", "danger"); + return; + } + //check if ip is valid cidr 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=""; + } + else{ + this.show_toast("Error", "Invalid IP address", "danger"); + } + } + + save_sec(){ + this.data_provider.save_user_restrictions(this.SelectedUser.id,this.userresttrictions).then((res) => { + if('status' in res && res['status']=='success') + this.RestrictionsTaskModalVisible = false; + else if('status' in res && res['status']=='failed') + this.show_toast("Error", res.err, "danger"); + else + this.show_toast("Error", "Somthing went wrong", "danger"); + }); + } + add_user_perm() { var _self = this; this.data_provider @@ -281,6 +333,7 @@ export class UserManagerComponent implements OnInit { this.get_user_perms(this.SelectedUser["id"]); }); } + logger(item: any) { console.dir(item); } diff --git a/src/app/views/user_manager/user_manager.scss b/src/app/views/user_manager/user_manager.scss new file mode 100644 index 0000000..37a3dcd --- /dev/null +++ b/src/app/views/user_manager/user_manager.scss @@ -0,0 +1,4 @@ +table tr td{ + padding-bottom:20px; + vertical-align:top +} \ No newline at end of file diff --git a/src/app/views/user_tasks/user_tasks.component.html b/src/app/views/user_tasks/user_tasks.component.html index 4592d59..f7da0bc 100644 --- a/src/app/views/user_tasks/user_tasks.component.html +++ b/src/app/views/user_tasks/user_tasks.component.html @@ -55,7 +55,6 @@ - diff --git a/src/app/views/vault/vault-routing.module.ts b/src/app/views/vault/vault-routing.module.ts new file mode 100644 index 0000000..c166a0b --- /dev/null +++ b/src/app/views/vault/vault-routing.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { VaultComponent } from './vault.component'; + +const routes: Routes = [ + { + path: '', + component: VaultComponent, + data: { + title: $localize`Password Vault` + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class VaultRoutingModule { +} diff --git a/src/app/views/vault/vault.component.html b/src/app/views/vault/vault.component.html new file mode 100644 index 0000000..762126c --- /dev/null +++ b/src/app/views/vault/vault.component.html @@ -0,0 +1,360 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    User Exceptions
    +
    + + + + +
    +
    + +
    +
    + +
    +
    +
    + + + + +   {{value}} + + + + + + + + +
    + +
    + + +
    Password list
    +
    + + + + +
    +
    + +
    +
    + +
    +
    +
    + + + +   {{value}} + + + + + + + +
    +
    +
    + + + +
    +
    +
    + + + + +
    Efected Groups
    + + + +   {{value}} + + + + + + + +
    + + + + +
    +
    +
    + + + + +
    Reports
    + + + +   {{value}} + + + +   {{value}} + + + + + + + +
    +
    +
    +
    +
    + + +
    + + + Username + + + + Device IP + + + + Device Name + + + +
    +
    + + + + + + + +   {{value}} + + + + {{value}} + + + + + {{value}} + + + + + {{value}} + + + + + + + + + + + + +
    +
    + + + +
    Password
    + +
    + +

    + + + + +

    + + Your attempt to reveal password is logged in system! + +
    + + + +
    + + + + + +
    Confirm RUN {{ SelectedTask['name'] }}
    + +
    + + Are you sure that You want to run Vault Password Job ? +
    +
    + + + + +
    + + + + +
    Editing Group
    + +
    + + +
    Group Members :
    + + + +   {{value}} + + + + {{value}} + + + + + {{value}} + + + +
    +
    +
    +
    + + + + + +
    + + \ No newline at end of file diff --git a/src/app/views/vault/vault.component.ts b/src/app/views/vault/vault.component.ts new file mode 100644 index 0000000..acf81ee --- /dev/null +++ b/src/app/views/vault/vault.component.ts @@ -0,0 +1,428 @@ +import { Component, OnInit, 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 { formatInTimeZone } from "date-fns-tz"; + +import { ToasterComponent } from "@coreui/angular"; +import { AppToastComponent } from "../toast-simple/toast.component"; + +@Component({ + templateUrl: "vault.component.html", + styleUrls: ["vault.scss"], + +}) +export class VaultComponent implements OnInit { + public uid: number; + public uname: string; + public ispro: boolean = false; + public tz: string; + + 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.tz = res.tz; + _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 settings:any=false; + public new_password:any=""; + public new_exception:any=""; + public Members:any=false; + public vault_history:any=false; + public passwords:any=false; + public password:string=""; + public PasswordModalVisible:boolean=false; + public source: Array = []; + public columns: Array = []; + public loading: boolean = true; + public rows: any = []; + public SelectedTask: any = {}; + public SelectedTaskItems: any = ""; + public runConfirmModalVisible: boolean = false; + public DeleteConfirmModalVisible: boolean = false; + public SelectedMembers: any = []; + public NewMemberModalVisible: boolean = false; + public availbleMembers: any = []; + public NewMemberRows: any = []; + public SelectedNewMemberRows: any; + public filters_visible: boolean = false; + public filters: any = {}; + public activetab:number=0; + + public sorting = { + enabled: true, + multiSorting: true, + }; + searching: GuiSearching = { + enabled: true, + placeholder: "Search Devices", + }; + + toasterForm = { + autohide: true, + delay: 3000, + position: "fixed", + fade: true, + closeButton: true, + }; + + 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, + }; + + public infoPanel: GuiInfoPanel = { + enabled: true, + infoDialog: false, + columnsManager: true, + schemaManager: true, + }; + + public rowSelection: boolean | GuiRowSelection = { + enabled: true, + type: GuiRowSelectionType.CHECKBOX, + mode: GuiRowSelectionMode.MULTIPLE, + }; + + reinitgrid(field: string, $event: any) { + if (field == "username") this.filters["username"] = $event; + else if (field == "dev_name") this.filters["dev_name"] = $event; + else if (field == "dev_ip") this.filters["dev_ip"] = $event; + this.get_passwords(); + } + + ngOnInit(): void { + this.initGridTable(); + this.get_vault_history(); + } + + onSelectedRowsNewMembers(rows: Array): void { + this.NewMemberRows = rows; + this.SelectedNewMemberRows = rows.map((m: GuiSelectedRow) => ({'id': m.source.id,'name':m.source.name})); + + } + + toggleCollapse(): void { + this.filters_visible = !this.filters_visible; + } + + 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; + } + + add_new_members() { + var _self = this; + //check if members not added already + + for (var i = 0; i < _self.SelectedNewMemberRows.length; i++) { + if (!_self.Members.find((e:any) => e.id ===_self.SelectedNewMemberRows[i]['id'])) { + _self.Members.push(_self.SelectedNewMemberRows[i]); + } + } + _self.Members = _self.Members.filter((x: any) => x != ""); + // _self.Members = [ + // ...new Set(_self.Members.concat(_self.SelectedNewMemberRows)), + // ]; + + this.NewMemberModalVisible = false; + } + + delete_group(id:number){ + this.Members=this.Members.filter((x:any)=>x.id!=id); + } + + get_member_by_id(id: string) { + return this.Members.find((x: any) => x.id == id); + } + + get_passwords(){ + var _self=this; + this.data_provider.get_passwords(this.filters).then((res) => { + _self.passwords=res.data.map((d: any) => { + d.changed = formatInTimeZone( + d.changed.split(".")[0] + ".000Z", + _self.tz, + "yyyy-MM-dd HH:mm:ss XXX" + ); + return d; + }); + }); + } + + reveal_password(devid:number,username:string){ + var _self=this; + _self.password=""; + this.data_provider.reveal_password(devid,username).then((res) => { + _self.password=res.password; + _self.PasswordModalVisible=true; + }); + } + + exec_vault(){ + var _self=this; + this.data_provider.exec_vault().then((res) => { + if('err' in res){ + _self.show_toast( + "Error", + res['err'], + "danger" + ); + } + else{ + _self.show_toast( + "Success", + "Vault job executing", + "success" + ); + } + }); + } + + add_password(){ + var _self=this; + if(this.settings['passwords'].includes(this.new_password)){ + return; + } + else{ + this.settings.passwords.push(this.new_password); + this.settings.passwords=this.settings.passwords.filter((x:any)=>x!=""); + this.new_password=''; + } + } + get_vault_history(){ + var _self=this; + this.data_provider.vault_history().then((res) => { + let index = 1; + _self.vault_history=res.data.map((d: any) => { + d.index = index; + d.ended = formatInTimeZone( + d.created.split(".")[0] + ".000Z", + _self.tz, + "yyyy-MM-dd HH:mm:ss XXX" + ); + d.info=JSON.parse(d.info); + d.started = formatInTimeZone( + d.info.created.split(".")[0] + ".000Z", + _self.tz, + "yyyy-MM-dd HH:mm:ss XXX" + ); + d.start_ip=d.info.start_ip; + d.end_ip=d.info.end_ip; + d.result=JSON.parse(d.result); + index += 1; + return d; + }); + }); + } + + sanitizeString(desc:string) { + var itemDesc:string=''; + if (desc) { + itemDesc = desc.toString().replace(/"/g, '\"'); + itemDesc = itemDesc.replace(/'/g, '\''); + } else { + itemDesc = ''; + } + return itemDesc; + } + + exportToCsv(jsonResponse:any) { + const data = jsonResponse; + const columns = this.getColumns(data); + const csvData = this.convertToCsv(data, columns); + this.downloadFile(csvData, 'data.csv', 'text/csv'); + } + + getColumns(data: any[]): string[] { + const columns : any = []; + data.forEach(row => { + Object.keys(row).forEach((col) => { + if (!columns.includes(col)) { + columns.push(col); + } + }); + }); + return columns; + } + + convertToCsv(data: any[], columns: string[]): string { + var _self=this; + let csv = ''; + csv += columns.join(',') + '\n'; + data.forEach(row => { + const values : any = []; + columns.forEach((col:any) => { + values.push('"'+_self.sanitizeString(row[col])+'"'); + }); + csv += values.join(',') + '\n'; + }); + return csv; + } + + downloadFile(data: string, filename: string, type: string) { + const blob = new Blob([data], { type: type }); + const nav = (window.navigator as any); + + if (nav.msSaveOrOpenBlob) { + nav.msSaveBlob(blob, filename); + } else { + const link = document.createElement('a'); + link.setAttribute('href', URL.createObjectURL(blob)); + link.setAttribute('download', filename); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } + + show_new_member_form() { + this.NewMemberModalVisible = false; + var _self = this; + _self.availbleMembers = []; + this.SelectedNewMemberRows = []; + this.NewMemberRows = []; + + var data = { + group_id: false, + search: false, + page: false, + size: 10000, + }; + + _self.data_provider.get_devgroup_list().then((res) => { + _self.availbleMembers = res.filter( + (x: any) => !_self.SelectedTaskItems.includes(x.id) + ); + _self.NewMemberModalVisible = true; + }); + + } + + remove_password(item:string){ + var _self=this; + this.settings.passwords=this.settings.passwords.filter((x:any)=>x!=item); + } + add_exception(){ + var _self=this; + if(this.settings['exceptions'].includes(this.new_exception)){ + return; + } + else{ + this.settings.exceptions.push(this.new_exception); + this.settings.exceptions=this.settings.exceptions.filter((x:any)=>x!=""); + this.new_exception=''; + } + } + remove_exception(item:string){ + var _self=this; + this.settings.exceptions=this.settings.exceptions.filter((x:any)=>x!=item); + } + save_settings(){ + var _self=this; + this.settings['action']='update' + this.settings['members']=this.Members.map((x:any) => x.id); + if(this.settings['enable']=='disable') + this.settings['action']='disable'; + this.data_provider.vault_task(this.settings).then((res) => { + if('err' in res){ + _self.show_toast( + "Error", + res['err'], + "danger" + ); + } + else{ + _self.show_toast( + "Success", + "Settings saved", + "success" + ); + _self.initGridTable(); + } + }); + } + + + + logger(item: any) { + console.dir(item); + } + + initGridTable(): void { + var _self = this; + this.data_provider.get_vault_setting().then((res) => { + _self.settings=res.data; + _self.Members=res.members; + }) + this.data_provider.get_user_task_list().then((res) => { + _self.source = res.map((x: any) => { + return x; + }); + _self.loading = false; + }); + } +} diff --git a/src/app/views/vault/vault.module.ts b/src/app/views/vault/vault.module.ts new file mode 100644 index 0000000..e98d364 --- /dev/null +++ b/src/app/views/vault/vault.module.ts @@ -0,0 +1,43 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { FormsModule,ReactiveFormsModule } from "@angular/forms"; + +import { + ButtonModule, + CardModule, + FormModule, + GridModule, + ModalModule, + ButtonGroupModule, + TabsModule, + ToastModule, + CollapseModule, +} from "@coreui/angular"; +import { VaultRoutingModule } from "./vault-routing.module"; +import { VaultComponent } from "./vault.component"; +import { GuiGridModule } from "@generic-ui/ngx-grid"; + +import { MatInputModule } from "@angular/material/input"; +import { MatFormFieldModule } from "@angular/material/form-field"; +@NgModule({ + imports: [ + VaultRoutingModule, + CardModule, + CommonModule, + GridModule, + FormModule, + ButtonModule, + ButtonGroupModule, + GuiGridModule, + ModalModule, + ReactiveFormsModule, + FormsModule, + TabsModule, + ToastModule, + MatInputModule, + MatFormFieldModule, + CollapseModule + ], + declarations: [VaultComponent], +}) +export class VaultModule {} diff --git a/src/app/views/vault/vault.scss b/src/app/views/vault/vault.scss new file mode 100644 index 0000000..6acb3bb --- /dev/null +++ b/src/app/views/vault/vault.scss @@ -0,0 +1,29 @@ + +.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) +} diff --git a/src/assets/res/atom-one-dark.css b/src/assets/res/atom-one-dark.css new file mode 100644 index 0000000..3c51ce6 --- /dev/null +++ b/src/assets/res/atom-one-dark.css @@ -0,0 +1,90 @@ +pre code.hljs { + display: block; + overflow-x: auto; + padding: 1em +} +code.hljs { + padding: 3px 5px +} +/* + +Atom One Dark by Daniel Gamage +Original One Dark Syntax theme from https://github.com/atom/one-dark-syntax + +base: #282c34 +mono-1: #abb2bf +mono-2: #818896 +mono-3: #5c6370 +hue-1: #56b6c2 +hue-2: #61aeee +hue-3: #c678dd +hue-4: #98c379 +hue-5: #e06c75 +hue-5-2: #be5046 +hue-6: #d19a66 +hue-6-2: #e6c07b + +*/ +.hljs { + color: #abb2bf; + background: #282c34 +} +.hljs-comment, +.hljs-quote { + color: #5c6370; + font-style: italic +} +.hljs-doctag, +.hljs-keyword, +.hljs-formula { + color: #c678dd +} +.hljs-section, +.hljs-name, +.hljs-selector-tag, +.hljs-deletion, +.hljs-subst { + color: #e06c75 +} +.hljs-literal { + color: #56b6c2 +} +.hljs-string, +.hljs-regexp, +.hljs-addition, +.hljs-attribute, +.hljs-meta .hljs-string { + color: #98c379 +} +.hljs-attr, +.hljs-variable, +.hljs-template-variable, +.hljs-type, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-number { + color: #d19a66 +} +.hljs-symbol, +.hljs-bullet, +.hljs-link, +.hljs-meta, +.hljs-selector-id, +.hljs-title { + color: #61aeee +} +.hljs-built_in, +.hljs-title.class_, +.hljs-class .hljs-title { + color: #e6c07b +} +.hljs-emphasis { + font-style: italic +} +.hljs-strong { + font-weight: bold +} +.hljs-link { + text-decoration: underline +} \ No newline at end of file diff --git a/src/assets/res/highlight.min.js b/src/assets/res/highlight.min.js new file mode 100644 index 0000000..cdc9e00 --- /dev/null +++ b/src/assets/res/highlight.min.js @@ -0,0 +1,358 @@ +/*! + Highlight.js v11.10.0 (git: 366a8bd012) + (c) 2006-2024 Josh Goebel and other contributors + License: BSD-3-Clause + */ +var hljs=function(){"use strict";function e(t){ +return t instanceof Map?t.clear=t.delete=t.set=()=>{ +throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{ +const i=t[n],s=typeof i;"object"!==s&&"function"!==s||Object.isFrozen(i)||e(i) +})),t}class t{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function n(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function i(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] +;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const s=e=>!!e.scope +;class o{constructor(e,t){ +this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ +this.buffer+=n(e)}openNode(e){if(!s(e))return;const t=((e,{prefix:t})=>{ +if(e.startsWith("language:"))return e.replace("language:","language-") +;if(e.includes(".")){const n=e.split(".") +;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") +}return`${t}${e}`})(e.scope,{prefix:this.classPrefix});this.span(t)} +closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const r=(e={})=>{const t={children:[]} +;return Object.assign(t,e),t};class a{constructor(){ +this.rootNode=r(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const t=r({scope:e}) +;this.add(t),this.stack.push(t)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ +return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), +t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +a._collapse(e)})))}}class c extends a{constructor(e){super(),this.options=e} +addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){ +this.closeNode()}__addSublanguage(e,t){const n=e.root +;t&&(n.scope="language:"+t),this.add(n)}toHTML(){ +return new o(this,this.options).value()}finalize(){ +return this.closeAllNodes(),!0}}function l(e){ +return e?"string"==typeof e?e:e.source:null}function g(e){return h("(?=",e,")")} +function u(e){return h("(?:",e,")*")}function d(e){return h("(?:",e,")?")} +function h(...e){return e.map((e=>l(e))).join("")}function f(...e){const t=(e=>{ +const t=e[e.length-1] +;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} +})(e);return"("+(t.capture?"":"?:")+e.map((e=>l(e))).join("|")+")"} +function p(e){return RegExp(e.toString()+"|").exec("").length-1} +const b=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function m(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n +;let i=l(e),s="";for(;i.length>0;){const e=b.exec(i);if(!e){s+=i;break} +s+=i.substring(0,e.index), +i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?s+="\\"+(Number(e[1])+t):(s+=e[0], +"("===e[0]&&n++)}return s})).map((e=>`(${e})`)).join(t)} +const E="[a-zA-Z]\\w*",x="[a-zA-Z_]\\w*",w="\\b\\d+(\\.\\d+)?",y="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",_="\\b(0b[01]+)",O={ +begin:"\\\\[\\s\\S]",relevance:0},v={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[O]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[O]},N=(e,t,n={})=>{const s=i({scope:"comment",begin:e,end:t, +contains:[]},n);s.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const o=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return s.contains.push({begin:h(/[ ]+/,"(",o,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s +},S=N("//","$"),M=N("/\\*","\\*/"),R=N("#","$");var j=Object.freeze({ +__proto__:null,APOS_STRING_MODE:v,BACKSLASH_ESCAPE:O,BINARY_NUMBER_MODE:{ +scope:"number",begin:_,relevance:0},BINARY_NUMBER_RE:_,COMMENT:N, +C_BLOCK_COMMENT_MODE:M,C_LINE_COMMENT_MODE:S,C_NUMBER_MODE:{scope:"number", +begin:y,relevance:0},C_NUMBER_RE:y,END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ +t.data._beginMatch!==e[1]&&t.ignoreMatch()}}),HASH_COMMENT_MODE:R,IDENT_RE:E, +MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+x,relevance:0}, +NUMBER_MODE:{scope:"number",begin:w,relevance:0},NUMBER_RE:w, +PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/, +end:/\/[gimuy]*/,contains:[O,{begin:/\[/,end:/\]/,relevance:0,contains:[O]}]}, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const t=/^#![ ]*\// +;return e.binary&&(e.begin=h(t,/.*\b/,e.binary,/\b.*/)),i({scope:"meta",begin:t, +end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, +TITLE_MODE:{scope:"title",begin:E,relevance:0},UNDERSCORE_IDENT_RE:x, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:x,relevance:0}});function A(e,t){ +"."===e.input[e.index-1]&&t.ignoreMatch()}function I(e,t){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function T(e,t){ +t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=A,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function L(e,t){ +Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function B(e,t){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function P(e,t){ +void 0===e.relevance&&(e.relevance=1)}const D=(e,t)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] +})),e.keywords=n.keywords,e.begin=h(n.beforeMatch,g(n.begin)),e.starts={ +relevance:0,contains:[Object.assign(n,{endsParent:!0})] +},e.relevance=0,delete n.beforeMatch +},H=["of","and","for","in","not","or","if","then","parent","list","value"],C="keyword" +;function $(e,t,n=C){const i=Object.create(null) +;return"string"==typeof e?s(n,e.split(" ")):Array.isArray(e)?s(n,e):Object.keys(e).forEach((n=>{ +Object.assign(i,$(e[n],t,n))})),i;function s(e,n){ +t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") +;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ +return t?Number(t):(e=>H.includes(e.toLowerCase()))(e)?0:1}const z={},W=e=>{ +console.error(e)},X=(e,...t)=>{console.log("WARN: "+e,...t)},G=(e,t)=>{ +z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) +},K=Error();function F(e,t,{key:n}){let i=0;const s=e[n],o={},r={} +;for(let e=1;e<=t.length;e++)r[e+i]=s[e],o[e+i]=!0,i+=p(t[e-1]) +;e[n]=r,e[n]._emit=o,e[n]._multi=!0}function Z(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw W("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +K +;if("object"!=typeof e.beginScope||null===e.beginScope)throw W("beginScope must be object"), +K;F(e,e.begin,{key:"beginScope"}),e.begin=m(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw W("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +K +;if("object"!=typeof e.endScope||null===e.endScope)throw W("endScope must be object"), +K;F(e,e.end,{key:"endScope"}),e.end=m(e.end,{joinWith:""})}})(e)}function V(e){ +function t(t,n){ +return RegExp(l(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) +}class n{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,t){ +t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), +this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(m(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const t=this.matcherRe.exec(e);if(!t)return null +;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] +;return t.splice(0,n),Object.assign(t,i)}}class s{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n +;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), +t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ +this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ +const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex +;let n=t.exec(e) +;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ +const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} +return n&&(this.regexIndex+=n.position+1, +this.regexIndex===this.count&&this.considerAll()),n}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=i(e.classNameAliases||{}),function n(o,r){const a=o +;if(o.isCompiled)return a +;[I,B,Z,D].forEach((e=>e(o,r))),e.compilerExtensions.forEach((e=>e(o,r))), +o.__beforeBegin=null,[T,L,P].forEach((e=>e(o,r))),o.isCompiled=!0;let c=null +;return"object"==typeof o.keywords&&o.keywords.$pattern&&(o.keywords=Object.assign({},o.keywords), +c=o.keywords.$pattern, +delete o.keywords.$pattern),c=c||/\w+/,o.keywords&&(o.keywords=$(o.keywords,e.case_insensitive)), +a.keywordPatternRe=t(c,!0), +r&&(o.begin||(o.begin=/\B|\b/),a.beginRe=t(a.begin),o.end||o.endsWithParent||(o.end=/\B|\b/), +o.end&&(a.endRe=t(a.end)), +a.terminatorEnd=l(a.end)||"",o.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(o.end?"|":"")+r.terminatorEnd)), +o.illegal&&(a.illegalRe=t(o.illegal)), +o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>i(e,{ +variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?i(e,{ +starts:e.starts?i(e.starts):null +}):Object.isFrozen(e)?i(e):e))("self"===e?o:e)))),o.contains.forEach((e=>{n(e,a) +})),o.starts&&n(o.starts,r),a.matcher=(e=>{const t=new s +;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ +return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ +constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} +const Y=n,Q=i,ee=Symbol("nomatch"),te=n=>{ +const i=Object.create(null),s=Object.create(null),o=[];let r=!0 +;const a="Could not find the language '{}', did you forget to load/include a language module?",l={ +disableAutodetect:!0,name:"Plain text",contains:[]};let p={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:c};function b(e){ +return p.noHighlightRe.test(e)}function m(e,t,n){let i="",s="" +;"object"==typeof t?(i=e, +n=t.ignoreIllegals,s=t.language):(G("10.7.0","highlight(lang, code, ...args) has been deprecated."), +G("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +s=e,i=t),void 0===n&&(n=!0);const o={code:i,language:s};N("before:highlight",o) +;const r=o.result?o.result:E(o.language,o.code,n) +;return r.code=o.code,N("after:highlight",r),r}function E(e,n,s,o){ +const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(R) +;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(R),n="" +;for(;t;){n+=R.substring(e,t.index) +;const s=_.case_insensitive?t[0].toLowerCase():t[0],o=(i=s,N.keywords[i]);if(o){ +const[e,i]=o +;if(M.addText(n),n="",c[s]=(c[s]||0)+1,c[s]<=7&&(j+=i),e.startsWith("_"))n+=t[0];else{ +const n=_.classNameAliases[e]||e;u(t[0],n)}}else n+=t[0] +;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(R)}var i +;n+=R.substring(e),M.addText(n)}function g(){null!=N.subLanguage?(()=>{ +if(""===R)return;let e=null;if("string"==typeof N.subLanguage){ +if(!i[N.subLanguage])return void M.addText(R) +;e=E(N.subLanguage,R,!0,S[N.subLanguage]),S[N.subLanguage]=e._top +}else e=x(R,N.subLanguage.length?N.subLanguage:null) +;N.relevance>0&&(j+=e.relevance),M.__addSublanguage(e._emitter,e.language) +})():l(),R=""}function u(e,t){ +""!==e&&(M.startScope(t),M.addText(e),M.endScope())}function d(e,t){let n=1 +;const i=t.length-1;for(;n<=i;){if(!e._emit[n]){n++;continue} +const i=_.classNameAliases[e[n]]||e[n],s=t[n];i?u(s,i):(R=s,l(),R=""),n++}} +function h(e,t){ +return e.scope&&"string"==typeof e.scope&&M.openNode(_.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(u(R,_.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +R=""):e.beginScope._multi&&(d(e.beginScope,t),R="")),N=Object.create(e,{parent:{ +value:N}}),N}function f(e,n,i){let s=((e,t)=>{const n=e&&e.exec(t) +;return n&&0===n.index})(e.endRe,i);if(s){if(e["on:end"]){const i=new t(e) +;e["on:end"](n,i),i.isMatchIgnored&&(s=!1)}if(s){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return f(e.parent,n,i)}function b(e){ +return 0===N.matcher.regexIndex?(R+=e[0],1):(T=!0,0)}function m(e){ +const t=e[0],i=n.substring(e.index),s=f(N,e,i);if(!s)return ee;const o=N +;N.endScope&&N.endScope._wrap?(g(), +u(t,N.endScope._wrap)):N.endScope&&N.endScope._multi?(g(), +d(N.endScope,e)):o.skip?R+=t:(o.returnEnd||o.excludeEnd||(R+=t), +g(),o.excludeEnd&&(R=t));do{ +N.scope&&M.closeNode(),N.skip||N.subLanguage||(j+=N.relevance),N=N.parent +}while(N!==s.parent);return s.starts&&h(s.starts,e),o.returnEnd?0:t.length} +let w={};function y(i,o){const a=o&&o[0];if(R+=i,null==a)return g(),0 +;if("begin"===w.type&&"end"===o.type&&w.index===o.index&&""===a){ +if(R+=n.slice(o.index,o.index+1),!r){const t=Error(`0 width match regex (${e})`) +;throw t.languageName=e,t.badRule=w.rule,t}return 1} +if(w=o,"begin"===o.type)return(e=>{ +const n=e[0],i=e.rule,s=new t(i),o=[i.__beforeBegin,i["on:begin"]] +;for(const t of o)if(t&&(t(e,s),s.isMatchIgnored))return b(n) +;return i.skip?R+=n:(i.excludeBegin&&(R+=n), +g(),i.returnBegin||i.excludeBegin||(R=n)),h(i,e),i.returnBegin?0:n.length})(o) +;if("illegal"===o.type&&!s){ +const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"') +;throw e.mode=N,e}if("end"===o.type){const e=m(o);if(e!==ee)return e} +if("illegal"===o.type&&""===a)return 1 +;if(I>1e5&&I>3*o.index)throw Error("potential infinite loop, way more iterations than matches") +;return R+=a,a.length}const _=O(e) +;if(!_)throw W(a.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const v=V(_);let k="",N=o||v;const S={},M=new p.__emitter(p);(()=>{const e=[] +;for(let t=N;t!==_;t=t.parent)t.scope&&e.unshift(t.scope) +;e.forEach((e=>M.openNode(e)))})();let R="",j=0,A=0,I=0,T=!1;try{ +if(_.__emitTokens)_.__emitTokens(n,M);else{for(N.matcher.considerAll();;){ +I++,T?T=!1:N.matcher.considerAll(),N.matcher.lastIndex=A +;const e=N.matcher.exec(n);if(!e)break;const t=y(n.substring(A,e.index),e) +;A=e.index+t}y(n.substring(A))}return M.finalize(),k=M.toHTML(),{language:e, +value:k,relevance:j,illegal:!1,_emitter:M,_top:N}}catch(t){ +if(t.message&&t.message.includes("Illegal"))return{language:e,value:Y(n), +illegal:!0,relevance:0,_illegalBy:{message:t.message,index:A, +context:n.slice(A-100,A+100),mode:t.mode,resultSoFar:k},_emitter:M};if(r)return{ +language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:M,_top:N} +;throw t}}function x(e,t){t=t||p.languages||Object.keys(i);const n=(e=>{ +const t={value:Y(e),illegal:!1,relevance:0,_top:l,_emitter:new p.__emitter(p)} +;return t._emitter.addText(e),t})(e),s=t.filter(O).filter(k).map((t=>E(t,e,!1))) +;s.unshift(n);const o=s.sort(((e,t)=>{ +if(e.relevance!==t.relevance)return t.relevance-e.relevance +;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1 +;if(O(t.language).supersetOf===e.language)return-1}return 0})),[r,a]=o,c=r +;return c.secondBest=a,c}function w(e){let t=null;const n=(e=>{ +let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" +;const n=p.languageDetectRe.exec(t);if(n){const t=O(n[1]) +;return t||(X(a.replace("{}",n[1])), +X("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} +return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return +;if(N("before:highlightElement",{el:e,language:n +}),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e) +;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),p.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) +;t=e;const i=t.textContent,o=n?m(i,{language:n,ignoreIllegals:!0}):x(i) +;e.innerHTML=o.value,e.dataset.highlighted="yes",((e,t,n)=>{const i=t&&s[t]||n +;e.classList.add("hljs"),e.classList.add("language-"+i) +})(e,n,o.language),e.result={language:o.language,re:o.relevance, +relevance:o.relevance},o.secondBest&&(e.secondBest={ +language:o.secondBest.language,relevance:o.secondBest.relevance +}),N("after:highlightElement",{el:e,result:o,text:i})}let y=!1;function _(){ +"loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(w):y=!0 +}function O(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]} +function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +s[e.toLowerCase()]=t}))}function k(e){const t=O(e) +;return t&&!t.disableAutodetect}function N(e,t){const n=e;o.forEach((e=>{ +e[n]&&e[n](t)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +y&&_()}),!1),Object.assign(n,{highlight:m,highlightAuto:x,highlightAll:_, +highlightElement:w, +highlightBlock:e=>(G("10.7.0","highlightBlock will be removed entirely in v12.0"), +G("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{p=Q(p,e)}, +initHighlighting:()=>{ +_(),G("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +_(),G("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,t)=>{let s=null;try{s=t(n)}catch(t){ +if(W("Language definition for '{}' could not be registered.".replace("{}",e)), +!r)throw t;W(t),s=l} +s.name||(s.name=e),i[e]=s,s.rawDefinition=t.bind(null,n),s.aliases&&v(s.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete i[e] +;for(const t of Object.keys(s))s[t]===e&&delete s[t]}, +listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v, +autoDetection:k,inherit:Q,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ +e["before:highlightBlock"](Object.assign({block:t.el},t)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ +e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),o.push(e)}, +removePlugin:e=>{const t=o.indexOf(e);-1!==t&&o.splice(t,1)}}),n.debugMode=()=>{ +r=!1},n.safeMode=()=>{r=!0},n.versionString="11.10.0",n.regex={concat:h, +lookahead:g,either:f,optional:d,anyNumberOfTimes:u} +;for(const t in j)"object"==typeof j[t]&&e(j[t]);return Object.assign(n,j),n +},ne=te({});return ne.newInstance=()=>te({}),ne}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `routeros` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const r="foreach do while for if from to step else on-error and or not in",n="true false yes no nothing nil null",i={ +className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)\}/ +}]},s={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,i,{ +className:"variable",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]}]},t={ +className:"string",begin:/'/,end:/'/};return{name:"MikroTik RouterOS script", +aliases:["mikrotik"],case_insensitive:!0,keywords:{$pattern:/:?[\w-]+/, +literal:n, +keyword:r+" :"+r.split(" ").join(" :")+" :"+"global local beep delay put len typeof pick log time set find environment terminal error execute parse resolve toarray tobool toid toip toip6 tonum tostr totime".split(" ").join(" :") +},contains:[{variants:[{begin:/\/\*/,end:/\*\//},{begin:/\/\//,end:/$/},{ +begin:/<\//,end:/>/}],illegal:/./},e.COMMENT("^#","$"),s,t,i,{ +begin:/[\w-]+=([^\s{}[\]()>]+)/,relevance:0,returnBegin:!0,contains:[{ +className:"attribute",begin:/[^=]+/},{begin:/=/,endsWithParent:!0,relevance:0, +contains:[s,t,i,{className:"literal",begin:"\\b("+n.split(" ").join("|")+")\\b" +},{begin:/("[^"]*"|[^\s{}[\]]+)/}]}]},{className:"number",begin:/\*[0-9a-fA-F]+/ +},{ +begin:"\\b(add|remove|enable|disable|set|get|print|export|edit|find|run|debug|error|info|warning)([\\s[(\\]|])", +returnBegin:!0,contains:[{className:"built_in",begin:/\w+/}]},{ +className:"built_in",variants:[{ +begin:"(\\.\\./|/|\\s)((traffic-flow|traffic-generator|firewall|scheduler|aaa|accounting|address-list|address|align|area|bandwidth-server|bfd|bgp|bridge|client|clock|community|config|connection|console|customer|default|dhcp-client|dhcp-server|discovery|dns|e-mail|ethernet|filter|firmware|gps|graphing|group|hardware|health|hotspot|identity|igmp-proxy|incoming|instance|interface|ip|ipsec|ipv6|irq|l2tp-server|lcd|ldp|logging|mac-server|mac-winbox|mangle|manual|mirror|mme|mpls|nat|nd|neighbor|network|note|ntp|ospf|ospf-v3|ovpn-server|page|peer|pim|ping|policy|pool|port|ppp|pppoe-client|pptp-server|prefix|profile|proposal|proxy|queue|radius|resource|rip|ripng|route|routing|screen|script|security-profiles|server|service|service-port|settings|shares|smb|sms|sniffer|snmp|snooper|socks|sstp-server|system|tool|tracking|type|upgrade|upnp|user-manager|users|user|vlan|secret|vrrp|watchdog|web-access|wireless|pptp|pppoe|lan|wan|layer7-protocol|lease|simple|raw);?\\s)+" +},{begin:/\.\./,relevance:0}]}]}}})();hljs.registerLanguage("routeros",e)})();/*! `xml` grammar compiled for Highlight.js 11.10.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const a=e.regex,n=a.concat(/[\p{L}_]/u,a.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),s={ +className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},t={begin:/\s/, +contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] +},i=e.inherit(t,{begin:/\(/,end:/\)/}),c=e.inherit(e.APOS_STRING_MODE,{ +className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),r={ +endsWithParent:!0,illegal:/`]+/}]}]}]};return{ +name:"HTML, XML", +aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], +case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[t,l,c,i,{begin:/\[/,end:/\]/,contains:[{ +className:"meta",begin://,contains:[t,i,l,c]}]}] +},e.COMMENT(//,{relevance:10}),{begin://, +relevance:10},s,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/, +relevance:10,contains:[l]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"style"},contains:[r],starts:{ +end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"script"},contains:[r],starts:{ +end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ +className:"tag",begin:/<>|<\/>/},{className:"tag", +begin:a.concat(//,/>/,/\s/)))), +end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:r}]},{ +className:"tag",begin:a.concat(/<\//,a.lookahead(a.concat(n,/>/))),contains:[{ +className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}} +})();hljs.registerLanguage("xml",e)})(); \ No newline at end of file diff --git a/src/index.html b/src/index.html index c955373..c472667 100644 --- a/src/index.html +++ b/src/index.html @@ -17,6 +17,8 @@ content="mikrowizard,mikrotik,router" name="keyword" /> + + MikroWizard | Router Managment