feat: settings redesign and core improvements

- Redesign settings interface with improved UX
- Enhanced dashboard functionality
- Improved device detail views
- Updated core data providers
- Minor snippet management improvements
This commit is contained in:
sepehr 2025-10-16 17:34:28 +03:00
parent 07808822f7
commit 433dcff5db
9 changed files with 1290 additions and 246 deletions

View file

@ -278,13 +278,14 @@ export class dataProvider {
return this.MikroWizardRPC.sendJsonRequest("/api/devgroup/update_save_group", data);
}
get_snippets(name:string,desc:string,content:string,page:number=0,size:number=1000){
get_snippets(name:string,desc:string,content:string,page:number=0,size:number=1000,limit:any=false){
var data={
'name':name,
'description':desc,
'content':content,
'page':page,
'size':size
'size':size,
'limit':limit
}
return this.MikroWizardRPC.sendJsonRequest("/api/snippet/list", data);
}
@ -601,6 +602,34 @@ export class dataProvider {
}
return this.MikroWizardRPC.sendJsonRequest("/api/dhcp-history/get", data);
}
getNetworkMap(){
return this.MikroWizardRPC.sendJsonRequest("/api/networkmap/get", {});
}
bulk_add_devices(devices: any[]){
var data = {
'devices': devices
}
return this.MikroWizardRPC.sendJsonRequest("/api/dev/bulk_add", data);
}
bulk_add_status(taskId: string){
var data = {
'taskId': taskId
}
return this.MikroWizardRPC.sendJsonRequest("/api/dev/bulk_add_status", data);
}
group_firmware_action(groupId: number, action: string){
var data = {
'groupId': groupId,
'action': action
}
return this.MikroWizardRPC.sendJsonRequest("/api/devgroup/firmware_action", data);
}
////
//// End api funcs
////

View file

@ -1,11 +1,23 @@
<c-row *ngIf="stats">
<c-row>
<c-col xs>
<c-card *ngIf="stats" class="mb-1">
<c-card class="mb-1">
<c-card-header>Past 24 Hour Statics</c-card-header>
<c-card-body>
<c-row>
<c-col md="12" xl="12" xs="12">
<c-row>
<c-row *ngIf="!stats">
<c-col class="mb-sm-1 mb-0" *ngFor="let item of [1,2,3,4,5]">
<c-card class="mb-1">
<c-card-body>
<div class="placeholder-glow">
<div class="placeholder col-3 mb-2" style="height: 40px;"></div>
<div class="placeholder col-6"></div>
</div>
</c-card-body>
</c-card>
</c-col>
</c-row>
<c-row *ngIf="stats">
<c-col class="mb-sm-1 mb-0">
<c-widget-stat-f [title]="'Failed Logins'" class="mb-1" color="danger" padding
value="{{stats['FailedLogins']}}">
@ -53,7 +65,17 @@
<c-col xs>
<c-row>
<c-col md="12" xl="12" xs="12">
<c-row>
<c-row *ngIf="!stats">
<c-col class="mb-0 pb-0" *ngFor="let item of [1,2,3,4,5]">
<div class="border-start border-start-4 border-start-info pt-1 px-3 mb-1">
<div class="placeholder-glow">
<div class="placeholder col-8"></div>
<div class="placeholder col-6"></div>
</div>
</div>
</c-col>
</c-row>
<c-row *ngIf="stats">
<c-col class="mb-0 pb-0">
<div class="border-start border-start-4 border-start-info pt-1 px-3 mb-1">
<div class="text-medium-emphasis small">Total users</div>
@ -120,16 +142,28 @@
</form>
</c-col>
</c-row>
<c-chart [data]="chart_data" [options]="options" [height]="250" type="line">
<div *ngIf="!chart_data.datasets" style="height: 250px;">
<div class="placeholder-glow" style="height: 100%;">
<div class="placeholder col-12" style="height: 250px;"></div>
</div>
</div>
<c-chart *ngIf="chart_data.datasets" [data]="chart_data" [options]="options" [height]="250" type="line">
</c-chart>
</c-card-body>
</c-card>
<c-row>
<c-col xl="6" *ngIf="stats" lg="12" class="h-100" style="min-height: 160px!important;display: grid">
<c-col xl="6" lg="12" class="h-100" style="min-height: 160px!important;display: grid">
<c-card class="mb-1 p-1 h-100" style="padding-left: 5px!important;">
<div class="my-1">
<h4 style="display: inline-block;">Version and Serial information</h4>
</div>
<div *ngIf="!stats">
<div class="placeholder-glow">
<div class="placeholder col-8"></div>
<div class="placeholder col-6"></div>
<div class="placeholder col-10"></div>
<div class="placeholder col-7"></div>
</div>
</div>
<div *ngIf="!stats['license']" class="my-1">
<div style="display: inline-block;margin-right: 5px;">
@ -199,8 +233,18 @@
</c-card>
</c-col>
<c-col xl="6" lg="12" class="h-100" style="min-height: 160px!important;display: grid;">
<c-card class="h-100" *ngIf="stats" style="padding: 0!important;margin: 0!important;">
<c-carousel [dark]="true" [animate]="false" [wrap]="false" [interval]="1000000">
<c-card class="h-100" style="padding: 0!important;margin: 0!important;">
<div *ngIf="!stats" style="padding: 20px;">
<div class="placeholder-glow">
<div class="placeholder col-4" style="height: 150px; float: left; margin-right: 20px;"></div>
<div class="placeholder col-7">
<div class="placeholder col-12"></div>
<div class="placeholder col-10"></div>
<div class="placeholder col-8"></div>
</div>
</div>
</div>
<c-carousel *ngIf="stats" [dark]="true" [animate]="false" [wrap]="false" [interval]="1000000">
<c-carousel-indicators></c-carousel-indicators>
<c-carousel-inner>
<c-carousel-item style="display: flex;" *ngFor="let slide of stats['blog']; index as i;">

View file

@ -32,7 +32,6 @@ import { ClipboardModule } from "@angular/cdk/clipboard";
ReactiveFormsModule,
ButtonModule,
TemplateIdDirective,
ButtonModule,
ButtonGroupModule,
ChartjsModule,
CarouselModule,

View file

@ -509,6 +509,7 @@ export class DeviceComponent implements OnInit, OnDestroy {
// loop in dhcp_server_data and create a new object with the data for chart for each dhcp server
_self.reloading = false;
_self.dhcp_server_data.forEach((element:any) => {
if(element.pools.length>0){
var pooldata=element.pools[0];
element.chartpools = {
labels: ['Used', 'Free'],
@ -517,6 +518,7 @@ export class DeviceComponent implements OnInit, OnDestroy {
data: [pooldata.used_ips, pooldata.available_ips]
}]
};
}
});
});
}

View file

@ -1,230 +1,510 @@
<c-row>
<c-col xs>
<c-card class="mb-4">
<c-card-header>Firmware Manager</c-card-header>
<c-card-body>
<c-input-group class="mb-3">
<h5>Firmware in repository:</h5>
<div class="settings-container">
<c-card class="settings-card">
<c-card-header class="settings-header">
<div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center">
<i class="fa-solid fa-cog me-2 text-primary"></i>
<h4 class="mb-0">System Settings</h4>
</div>
<c-badge color="info" class="fs-6">Admin Panel</c-badge>
</div>
</c-card-header>
<div class="settings-tabs">
<button class="tab-link" [class.active]="activeTab === 'firmware'" (click)="activeTab = 'firmware'">
<i class="fa-solid fa-microchip me-2"></i>Firmware Manager
</button>
<button class="tab-link" [class.active]="activeTab === 'system'" (click)="activeTab = 'system'">
<i class="fa-solid fa-server me-2"></i>System Configuration
</button>
</div>
<!-- Firmware Tab -->
<c-card-body *ngIf="activeTab === 'firmware'" class="tab-content">
<!-- Repository Status -->
<div class="status-section mb-4">
<div class="d-flex align-items-center justify-content-between mb-3">
<h5 class="section-title mb-0">
<i class="fa-solid fa-database me-2 text-success"></i>Firmware Repository
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'Local firmware repository stores RouterOS versions for offline installation. Devices can be updated without internet access.'"
cTooltipPlacement="top"></i>
</h5>
<c-badge color="success" class="fs-6">
<i class="fa-solid fa-check-circle me-1"></i>{{source.length}} Versions Available
</c-badge>
</div>
<div class="info-banner mb-3">
<i class="fa-solid fa-lightbulb me-2"></i>
<span><strong>Tip:</strong> Keep multiple firmware versions for compatibility. Always test updates on non-critical devices first.</span>
</div>
<gui-grid [autoResizeWidth]="true" [source]="source" [columnMenu]="columnMenu" [sorting]="sorting"
[infoPanel]="infoPanel" [autoResizeWidth]=true [paging]="paging">
[infoPanel]="infoPanel" [paging]="paging" class="compact-grid">
<gui-grid-column header="Version" field="version">
<ng-template let-value="item.version" let-item="item" let-index="index">
&nbsp; {{value}} </ng-template>
<c-badge color="primary" class="version-badge">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="arch" field="architecture">
<gui-grid-column header="Architecture" field="architecture">
<ng-template let-value="item.architecture" let-item="item" let-index="index">
{{value}}
<c-badge color="secondary">{{value}}</c-badge>
</ng-template>
</gui-grid-column>
<gui-grid-column header="sha256" field="sha256">
<gui-grid-column header="SHA256" field="sha256">
<ng-template let-value="item.sha256" let-item="item" let-index="index">
{{value}}
<small class="text-muted font-monospace">{{value.substring(0, 16)}}...</small>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" width="120" field="action">
<gui-grid-column header="Actions" width="100" field="action" align="CENTER">
<ng-template let-value="item.id" let-item="item" let-index="index">
<button cButton color="danger" size="sm" (click)="delete_fimrware(item);"><i
class="fa-regular fa-trash-can"></i></button>
<button cButton color="danger" size="sm" variant="outline" (click)="delete_fimrware(item)" title="Delete firmware">
<i class="fa-solid fa-trash"></i>
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-input-group>
</div>
<hr />
<table>
<td>
<span>Add new Permission</span>
</td>
<td *ngIf="loading">
<button cButton class="m-1" disabled>
<c-spinner aria-hidden="true" size="sm"></c-spinner>
Fetching Information from mikrotik website...
</button>
</td>
<td *ngIf="!loading">
<mat-form-field>
<mat-select cFormControl [(ngModel)]="firmtodownload" placeholder="Select Version For Download Group"
#singleSelect>
<mat-option>
<ngx-mat-select-search placeholderLabel="Search"
[hideClearSearchButton]="true"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let firm of firms" [value]="firm">
<!-- Download Section -->
<div class="download-section mb-4">
<div class="section-card">
<div class="section-header">
<i class="fa-solid fa-download me-2 text-primary"></i>
<span class="fw-semibold">Download New Firmware</span>
<i class="fa-solid fa-question-circle ms-2 text-info help-icon"
[cTooltip]="'Downloads firmware from MikroTik servers. Requires internet access. Downloaded files are verified for integrity.'"
cTooltipPlacement="top"></i>
</div>
<div class="warning-banner mb-2" *ngIf="!loading">
<i class="fa-solid fa-wifi me-2"></i>
<span><strong>Internet Required:</strong> Downloads from mikrotik.com. Ensure stable connection and access to mikrotik.com domain.</span>
</div>
<c-row class="g-3 mt-2" *ngIf="!loading">
<c-col md="8">
<div class="search-select-wrapper">
<label class="form-label">Select RouterOS Version</label>
<input cFormControl
[(ngModel)]="firmwareSearch"
(input)="filterFirmwares($event)"
(focus)="showFirmwareDropdown = true"
(blur)="hideFirmwareDropdown()"
placeholder="Search and select version..."
class="search-input"
autocomplete="off" />
<div *ngIf="showFirmwareDropdown && filteredFirmwares.length > 0" class="search-dropdown">
<div *ngFor="let firm of filteredFirmwares"
class="search-option"
(mousedown)="selectFirmware(firm)">
{{firm}}
</mat-option>
</mat-select>
</mat-form-field>
</td>
<td>
<button *ngIf="!loading" cButton color="primary" (click)="ConfirmModalVisible=true">Download to
repository</button>
<!-- <button *ngIf="SelectedUser['action']=='add'" cButton color="primary" (click)="loading=!loading">++</button> -->
</td>
</table>
<hr />
<c-input-group class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
V6 Firmware update Behavior
</label>
<select cSelect [(ngModel)]="updateBehavior" id="inputGroupSelect01">
<option>Choose...</option>
<option value="keep">Keep v6 and don't update to v7</option>
<option value="update">install latest</option>
</select>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Choose how Mikrowizard should update old v6 firmwares</c-form-feedback>
</c-input-group>
<c-input-group class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
Firmware version to install
</label>
<select cSelect [(ngModel)]="firmwaretoinstall" id="inputGroupSelect01">
<option>Choose...</option>
<option *ngFor="let f of available_firmwares" [value]="f">{{f}}</option>
</select>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* The version of firmware to install routers</c-form-feedback>
</c-input-group>
<c-input-group *ngIf="updateBehavior=='keep'" class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
Firmware version v6 to install
</label>
<select cSelect [(ngModel)]="firmwaretoinstallv6" id="inputGroupSelect01">
<option>Choose...</option>
<option *ngFor="let f of available_firmwaresv6" [value]="f">{{f}}</option>
</select>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* The version of firmware to install on V6 routers</c-form-feedback>
</c-input-group>
<button cButton color="primary" (click)="saveFirmwareSetting()">Save</button>
</c-card-body>
</c-card>
<c-card class="mb-4">
<c-card-header>System Settings</c-card-header>
<c-card-body *ngIf="!SysConfigloading">
<c-input-group class="mt-3">
<span cInputGroupText>Rad Secret</span>
<input cFormControl id="floatingInput" placeholder="rad_secret"
[(ngModel)]="sysconfigs['rad_secret']['value']" />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Radius Secret of Mikrowizard Radius Server</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3">
<span cInputGroupText>System URL</span>
<input cFormControl id="floatingInput" placeholder="System URL"
[(ngModel)]="sysconfigs['system_url']['value']" />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Default system access URl</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3">
<span cInputGroupText>Default IP</span>
<input cFormControl id="floatingInput" placeholder="System URL"
[(ngModel)]="sysconfigs['default_ip']['value']" />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Default Mikrowizard Access IP</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3">
<span cInputGroupText>System Time Zone</span>
<mat-form-field class="form-control" subscriptSizing="dynamic">
<mat-label>Select event type</mat-label>
<mat-select cFormControl [(ngModel)]="sysconfigs['timezone']['value']"
placeholder="Select Version For Download Group" #singleSelect>
<mat-option>
<ngx-mat-select-search placeholderLabel="Search" [hideClearSearchButton]="true"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let tz of timezones" [value]="tz.utc[0]">
{{tz.text}}
</mat-option>
</mat-select>
</mat-form-field>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Default TimeZone for the system</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3">
<span cInputGroupText>Default User</span>
<input aria-label="Username" type="password" [(ngModel)]="sysconfigs['default_user']['value']" cFormControl />
<span cInputGroupText>Default password</span>
<input aria-label="Password" type="password" [(ngModel)]="sysconfigs['default_password']['value']"
cFormControl />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Default username and Password for searching new devices</c-form-feedback>
</c-input-group>
<c-input-group class="mb-3">
<label cInputGroupText for="inputGroupSelect01">
Mikrowizard Update Mode
</label>
<select cSelect [(ngModel)]="sysconfigs['update_mode']['value']['mode']" id="inputGroupSelect01">
<option>Choose...</option>
<option value="auto">Automatic Update</option>
<option value="manual">Show update only/Update manually</option>
</select>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Choose if Mikrowizard should download updates automaticaly when availble or wait for user to download/apply updates</c-form-feedback>
</c-input-group>
<c-input-group class="mt-3 mb-3">
<span cInputGroupText>License Username</span>
<input aria-label="Username" type="text" [(ngModel)]="sysconfigs['username']['value']" cFormControl />
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* The username that you registred in Mikrowizard.com,Required for License Activation</c-form-feedback>
</c-input-group>
<c-form-check [switch]="true" sizing="xl">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_perms']['value']" type="checkbox" />
<label cFormCheckLabel>Force Perms</label>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Force User Groups under user>groups configuration of each router to match Mikrowizard Permissions and
monitor for any change to prevent/fix the configuration.</c-form-feedback>
</c-form-check>
<c-form-check [switch]="true" sizing="xl">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_radius']['value']" type="checkbox" />
<label cFormCheckLabel>Force Radius</label>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Force Radius config under radius>client and user>aaa setting of each router that added to Mikrowizard and
monitor for any change to prevent/fix the configuration.</c-form-feedback>
</c-form-check>
<c-form-check [switch]="true" sizing="xl">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_syslog']['value']" type="checkbox" />
<label cFormCheckLabel>Force Syslog</label>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true">
* Force Syslog config under system>logs setting of each router that added to Mikrowizard and monitor syslog
setting for any change to prevent/fix the configuration.</c-form-feedback>
</c-form-check>
<c-form-check *ngIf="ispro" [switch]="true" sizing="xl">
<input cFormCheckInput [(ngModel)]="sysconfigs['safe_install']['value']" type="checkbox" />
<label cFormCheckLabel>Safe Update</label>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true"><code style="padding: 0!important;">PRO</code>
* 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</c-form-feedback>
</c-form-check>
<c-form-check *ngIf="ispro" [switch]="true" sizing="xl">
<input cFormCheckInput [(ngModel)]="sysconfigs['otp_force']['value']" type="checkbox" />
<label cFormCheckLabel>Force device otp</label>
<c-form-feedback style="display: block;color: #5c5c5c;margin-top: 0;" [valid]="true"><code style="padding: 0!important;">PRO</code>
* Force login to devices using otp for all users.(you can make exceptions for each user)</c-form-feedback>
</c-form-check>
<button cButton color="primary" (click)="saveSysSetting()">Save</button>
</c-card-body>
</c-card>
</div>
</div>
<div *ngIf="showFirmwareDropdown && filteredFirmwares.length === 0 && firmwareSearch" class="search-no-results">
No versions found
</div>
</div>
</c-col>
<c-col md="4" class="d-flex align-items-end">
<button cButton color="primary" class="w-100" (click)="ConfirmModalVisible=true" [disabled]="!firmtodownload">
<i class="fa-solid fa-download me-1"></i>Download
</button>
</c-col>
</c-row>
<div *ngIf="loading" class="text-center py-3">
<c-spinner size="sm" class="me-2"></c-spinner>
<span class="text-muted">Fetching available versions from MikroTik...</span>
</div>
</div>
</div>
<!-- Firmware Settings -->
<div class="firmware-settings">
<h5 class="section-title mb-3">
<i class="fa-solid fa-cogs me-2 text-warning"></i>Update Configuration
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'These settings control how MikroWizard handles firmware updates across your network. Changes affect all future update operations.'"
cTooltipPlacement="top"></i>
</h5>
<div class="info-banner mb-3">
<i class="fa-solid fa-shield-alt me-2"></i>
<span><strong>Best Practice:</strong> Use "Keep v6" for legacy devices. Test new firmware versions in lab environment first.</span>
</div>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">V6 Firmware Behavior
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Choose how to handle RouterOS v6 devices: keep them on v6 with selected v6 firmware, or upgrade them to v7 with selected v7 firmware.'"
cTooltipPlacement="top"></i>
</label>
<select cSelect [(ngModel)]="updateBehavior" class="form-select">
<option value="">Choose behavior...</option>
<option value="keep">🛡️ Keep v6 (v6 devices get V6 Firmware Version, v7 devices get Default Firmware Version)</option>
<option value="update">⬆️ Upgrade to v7 (all v6 routers will be upgraded to v7)</option>
</select>
<small class="setting-help">📋 <strong>Keep v6:</strong> v6 devices use V6 Firmware Version, v7 devices use Default Firmware Version</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Default Firmware Version
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'This version will be installed on new devices added to MikroWizard. Choose stable LTS versions for production.'"
cTooltipPlacement="top"></i>
</label>
<select cSelect [(ngModel)]="firmwaretoinstall" class="form-select">
<option value="">Choose version...</option>
<option *ngFor="let f of available_firmwares" [value]="f">{{f}}</option>
</select>
<small class="setting-help">💡 <strong>Tip:</strong> Choose stable releases for production. LTS versions are only available for v6 firmware</small>
</div>
</c-col>
<c-col md="6" *ngIf="updateBehavior === 'keep'">
<div class="setting-group">
<label class="setting-label">V6 Firmware Version</label>
<select cSelect [(ngModel)]="firmwaretoinstallv6" class="form-select">
<option value="">Choose v6 version...</option>
<option *ngFor="let f of available_firmwaresv6" [value]="f">{{f}}</option>
</select>
<small class="setting-help">Firmware version for RouterOS v6 devices</small>
</div>
</c-col>
</c-row>
<div class="mt-3">
<button cButton color="primary" (click)="saveFirmwareSetting()">
<i class="fa-solid fa-save me-1"></i>Save Firmware Settings
</button>
</div>
</div>
</c-card-body>
<!-- System Tab -->
<c-card-body *ngIf="activeTab === 'system' && !SysConfigloading" class="tab-content">
<!-- Network Configuration -->
<div class="config-section mb-4">
<h5 class="section-title mb-3">
<i class="fa-solid fa-network-wired me-2 text-primary"></i>Network Configuration
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'Basic network settings for MikroWizard system access and device communication. Ensure these match your network setup.'"
cTooltipPlacement="top"></i>
</h5>
<div class="info-banner mb-3">
<i class="fa-solid fa-network-wired me-2"></i>
<span><strong>Network Setup:</strong> Configure these settings to match your network infrastructure for proper device communication.</span>
</div>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">System URL
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'URL or IP address where MikroWizard is accessible. Used for email notifications and API callbacks. Must be reachable by managed devices.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['system_url']['value']"
placeholder="https://mikrowizard.yourdomain.com or 192.168.1.100" class="form-control" />
<small class="setting-help">🌐 <strong>Examples:</strong> https://mikrowizard.company.com or 192.168.1.100</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Default IP Address
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'IP address where MikroWizard web interface is accessible. Used as fallback when domain is not available.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['default_ip']['value']"
placeholder="192.168.1.100" class="form-control" />
<small class="setting-help">🔧 <strong>Tip:</strong> Use static IP outside DHCP range (e.g., 192.168.1.100-200)</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">RADIUS Secret
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Secret used by MikroWizard internal RADIUS server for authenticating MikroTik system users. This is for device admin authentication, not end users.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['rad_secret']['value']"
placeholder="Enter MikroWizard RADIUS secret" class="form-control" type="password" />
<small class="setting-help">🔐 <strong>Internal RADIUS:</strong> Secret for MikroWizard's built-in RADIUS server (for MikroTik admin authentication)</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">System Timezone</label>
<div class="search-select-wrapper">
<input cFormControl
[(ngModel)]="timezoneSearch"
(input)="filterTimezones($event)"
(focus)="showTimezoneDropdown = true"
(blur)="hideTimezoneDropdown()"
placeholder="Search and select timezone..."
class="search-input"
autocomplete="off" />
<div *ngIf="showTimezoneDropdown && filteredTimezones.length > 0" class="search-dropdown">
<div *ngFor="let tz of filteredTimezones"
class="search-option"
(mousedown)="selectTimezone(tz)">
{{tz.text}}
</div>
</div>
<div *ngIf="showTimezoneDropdown && filteredTimezones.length === 0 && timezoneSearch" class="search-no-results">
No timezones found
</div>
</div>
<small class="setting-help">Default system timezone</small>
</div>
</c-col>
</c-row>
</div>
<!-- Authentication -->
<div class="config-section mb-4">
<h5 class="section-title mb-3">
<i class="fa-solid fa-key me-2 text-warning"></i>Device Discovery Credentials
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'Default credentials for discovering and connecting to new MikroTik devices. Keep these secure and change default passwords immediately.'"
cTooltipPlacement="top"></i>
</h5>
<div class="warning-banner mb-3">
<i class="fa-solid fa-shield-exclamation me-2"></i>
<span><strong>Security Warning:</strong> Change default passwords immediately after device setup. Use strong, unique credentials.</span>
</div>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Default Username
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Username for discovering and connecting to MikroTik devices in your network. This is used to find devices with default/common credentials.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['default_user']['value']"
placeholder="admin" class="form-control" type="password" />
<small class="setting-help">🔍 <strong>Device Discovery:</strong> Username for finding MikroTik devices with default credentials in your network</small>
</div>
</c-col>
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Default Password
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Password for discovering and connecting to MikroTik devices. Many new devices have empty password. Used to find devices with default/common credentials.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['default_password']['value']"
placeholder="(often empty on new devices)" class="form-control" type="password" />
<small class="setting-help">🔍 <strong>Device Discovery:</strong> Password for finding MikroTik devices with default credentials (often empty on new devices)</small>
</div>
</c-col>
</c-row>
</div>
<!-- License Configuration -->
<div class="config-section mb-4">
<h5 class="section-title mb-3">
<i class="fa-solid fa-certificate me-2 text-primary"></i>License Configuration
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'MikroWizard license activation and registration settings. Required for system activation and PRO features.'"
cTooltipPlacement="top"></i>
</h5>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">License Username
<i class="fa-solid fa-exclamation-triangle ms-1 text-warning help-icon"
[cTooltip]="'Your username from MikroWizard.com account. Same username you use to login to mikrowizard.com website. Required for license activation.'"
cTooltipPlacement="top"></i>
</label>
<input cFormControl [(ngModel)]="sysconfigs['username']['value']"
placeholder="Same as your mikrowizard.com login" class="form-control" />
<small class="setting-help text-warning">🔑 <strong>Same username</strong> you use to login to mikrowizard.com website</small>
</div>
</c-col>
</c-row>
</div>
<!-- System Behavior -->
<div class="config-section mb-4">
<h5 class="section-title mb-3">
<i class="fa-solid fa-cogs me-2 text-success"></i>System Behavior
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'Controls how MikroWizard handles automatic updates and system maintenance. Choose based on your change management policy.'"
cTooltipPlacement="top"></i>
</h5>
<c-row class="g-3">
<c-col md="6">
<div class="setting-group">
<label class="setting-label">Update Mode
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Automatic updates install immediately when available. Manual mode shows notifications in dashboard and waits for user confirmation.'"
cTooltipPlacement="top"></i>
</label>
<select cSelect [(ngModel)]="sysconfigs['update_mode']['value']['mode']" class="form-select">
<option value="">Choose mode...</option>
<option value="auto">🔄 Automatic Updates (Install immediately)</option>
<option value="manual">✋ Manual Updates (Confirm actions in dashboard view)</option>
</select>
<small class="setting-help">📊 <strong>Manual Mode:</strong> Updates require confirmation and actions in dashboard view</small>
</div>
</c-col>
</c-row>
</div>
<!-- Security Features -->
<div class="config-section mb-4">
<h5 class="section-title mb-3">
<i class="fa-solid fa-shield-alt me-2 text-danger"></i>Security & Enforcement
<i class="fa-solid fa-info-circle ms-2 text-info help-icon"
[cTooltip]="'Security policies automatically enforced on all managed devices. These settings override local device configurations for consistency.'"
cTooltipPlacement="top"></i>
</h5>
<div class="warning-banner mb-3">
<i class="fa-solid fa-exclamation-triangle me-2"></i>
<span><strong>Important:</strong> These settings will override device configurations. Test in lab environment before enabling in production.</span>
</div>
<div class="security-switches">
<div class="switch-item">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Force Permissions
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Synchronizes MikroTik user groups with MikroWizard permissions. Ensures consistent access control across all managed devices.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🔄 Sync MikroTik user groups with MikroWizard permissions
<br><small class="text-info">💡 Ensures consistent user access control across all devices</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_perms']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Force RADIUS
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Configures MikroTik devices to use MikroWizard RADIUS for admin user authentication. This is for MikroTik system users, not end users.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🔐 Configure RADIUS on MikroTik devices for admin user authentication
<br><small class="text-info">👥 For MikroTik system users authentication (not end users)</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_radius']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Force Syslog
<i class="fa-solid fa-exclamation-triangle ms-1 text-warning help-icon"
[cTooltip]="'CRITICAL: Will overwrite syslog configuration on all devices. If MikroWizard server is unreachable, devices may stop logging or experience performance issues.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">📊 Automatically configure syslog settings on managed devices
<br><small class="text-warning">⚠️ Will OVERWRITE existing syslog configurations on devices</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['force_syslog']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item" *ngIf="ispro">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Safe Updates <c-badge color="warning" class="ms-2">PRO</c-badge>
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Prevents firmware update failures by installing intermediate versions first. Reduces risk of device brick but takes longer.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🛡️ Install required intermediate firmware versions before target version
<br><small class="text-success">✅ Highly recommended for production environments</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['safe_install']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item" *ngIf="ispro">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">Force Device OTP <c-badge color="warning" class="ms-2">PRO</c-badge>
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Requires two-factor authentication for device access. Users must have OTP configured. Significantly improves security.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🔐 Require OTP authentication for all device access
<br><small class="text-warning">⚠️ Ensure users have OTP configured before enabling</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['otp_force']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
<div class="switch-item" *ngIf="ispro">
<div class="switch-content">
<div class="switch-info">
<h6 class="switch-title">WebFig Auto Login <c-badge color="warning" class="ms-2">PRO</c-badge>
<i class="fa-solid fa-question-circle ms-1 text-info help-icon"
[cTooltip]="'Creates temporary OTP password for automatic WebFig login. Users don\'t need to enter credentials when accessing device WebFig through MikroWizard.'"
cTooltipPlacement="top"></i>
</h6>
<p class="switch-description">🌐 Create temporary OTP for automatic WebFig login
<br><small class="text-success">🔑 Users don't need to input passwords to access WebFig admin interface</small>
</p>
</div>
<c-form-check [switch]="true" sizing="lg">
<input cFormCheckInput [(ngModel)]="sysconfigs['proxy_auto_login']['value']" type="checkbox" />
<label cFormCheckLabel></label>
</c-form-check>
</div>
</div>
</div>
</div>
<div class="save-section">
<button cButton color="primary" size="lg" (click)="saveSysSetting()">
<i class="fa-solid fa-save me-2"></i>Save System Settings
</button>
</div>
</c-card-body>
<!-- Loading State -->
<c-card-body *ngIf="activeTab === 'system' && SysConfigloading" class="text-center py-5">
<c-spinner size="sm" class="mb-3"></c-spinner>
<h5 class="text-muted">Loading system configuration...</h5>
</c-card-body>
</c-card>
</div>
<c-modal #ConfirmModal backdrop="static" [(visible)]="ConfirmModalVisible" id="runConfirmModal">
<c-modal-header>

View file

@ -5,9 +5,629 @@
}
}
}
.mdc-line-ripple.mdc-line-ripple--deactivating.ng-star-inserted {
display: none!important;
}
.form-check-label{
font-weight: bold;
/* Settings Container */
.settings-container {
max-width: 1200px;
margin: 0 auto;
}
.settings-card {
border: none;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
border-radius: 12px;
overflow: hidden;
}
.settings-header {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-bottom: 1px solid #dee2e6;
padding: 1.25rem 1.5rem;
}
/* Tabs */
.settings-tabs {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 0 1.5rem;
display: flex;
}
.tab-link {
border: none;
background: transparent;
border-radius: 0;
padding: 1rem 1.5rem;
font-weight: 500;
color: #6c757d;
transition: all 0.2s ease;
border-bottom: 3px solid transparent;
cursor: pointer;
}
.tab-link:hover {
color: #495057;
background: rgba(13, 110, 253, 0.05);
}
.tab-link.active {
color: #0d6efd;
background: white;
border-bottom-color: #0d6efd;
}
/* Tab Content */
.tab-content {
padding: 2rem;
}
/* Section Titles */
.section-title {
color: #495057;
font-weight: 600;
font-size: 1.1rem;
margin-bottom: 1rem;
display: flex;
align-items: center;
}
/* Status Section */
.status-section {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1.5rem;
}
/* Download Section */
.download-section {
.section-card {
background: linear-gradient(135deg, #e7f3ff 0%, #f8f9fa 100%);
border: 2px dashed #0d6efd;
border-radius: 8px;
padding: 1.5rem;
transition: all 0.2s ease;
}
.section-card:hover {
border-color: #0a58ca;
background: linear-gradient(135deg, #cfe2ff 0%, #e7f3ff 100%);
}
.section-header {
display: flex;
align-items: center;
font-size: 1rem;
font-weight: 600;
color: #495057;
}
}
/* Firmware Settings */
.firmware-settings {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
border: 1px solid #e9ecef;
}
/* Config Sections */
.config-section {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
border: 1px solid #e9ecef;
}
/* Setting Groups */
.setting-group {
margin-bottom: 1rem;
}
.setting-label {
display: block;
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.setting-help {
color: #6c757d;
font-size: 0.8rem;
margin-top: 0.25rem;
display: block;
}
/* Security Switches */
.security-switches {
display: grid;
gap: 1rem;
}
.switch-item {
background: white;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1.25rem;
transition: all 0.2s ease;
}
.switch-item:hover {
border-color: #0d6efd;
box-shadow: 0 2px 4px rgba(13, 110, 253, 0.1);
}
.switch-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.switch-info {
flex: 1;
margin-right: 1rem;
}
.switch-title {
font-size: 1rem;
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
display: flex;
align-items: center;
}
.switch-description {
color: #6c757d;
font-size: 0.85rem;
margin: 0;
line-height: 1.4;
}
/* Compact Grid */
.compact-grid {
border-radius: 6px;
overflow: hidden;
border: 1px solid #e9ecef;
}
/* Version Badge */
.version-badge {
font-family: 'Courier New', monospace;
font-weight: 600;
}
/* Form Controls */
.form-control, .form-select {
border-radius: 6px;
border: 1px solid #ced4da;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
transition: all 0.2s ease;
}
.form-control:focus, .form-select:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
/* Save Section */
.save-section {
text-align: center;
padding-top: 1rem;
border-top: 1px solid #e9ecef;
}
/* Form Check Labels */
.form-check-label {
font-weight: 500;
}
/* Button Enhancements */
.btn {
border-radius: 6px;
font-weight: 500;
transition: all 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Badge Enhancements */
.badge {
font-weight: 500;
padding: 0.4em 0.6em;
}
/* Mobile Responsive for Informative Elements */
@media (max-width: 768px) {
.info-banner,
.warning-banner,
.success-banner {
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
}
.help-icon {
font-size: 0.75rem;
}
.setting-help {
font-size: 0.75rem;
}
.switch-description {
font-size: 0.8rem;
}
.switch-description small {
font-size: 0.7rem;
}
::ng-deep .tooltip-inner {
max-width: 250px;
font-size: 0.75rem;
}
}
@media (max-width: 576px) {
.info-banner,
.warning-banner,
.success-banner {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
.setting-label {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
}
/* Original Responsive Design */
@media (max-width: 768px) {
.settings-header {
padding: 1rem;
}
.settings-tabs {
padding: 0 1rem;
}
.tab-content {
padding: 1rem;
}
.tab-link {
padding: 0.75rem 1rem;
font-size: 0.9rem;
}
.status-section,
.config-section,
.firmware-settings {
padding: 1rem;
}
.download-section .section-card {
padding: 1rem;
}
.switch-content {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.switch-info {
margin-right: 0;
}
.section-title {
font-size: 1rem;
}
}
@media (max-width: 576px) {
.settings-container {
margin: 0 0.5rem;
}
.settings-card {
border-radius: 8px;
}
.tab-content {
padding: 0.75rem;
}
.status-section,
.config-section,
.firmware-settings {
padding: 0.75rem;
}
}
/* Search Select Components */
.search-select-wrapper {
position: relative;
width: 100%;
}
.search-input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #ced4da;
border-radius: 6px;
font-size: 0.9rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.search-input:focus {
border-color: #0d6efd;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
outline: 0;
}
.search-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ced4da;
border-top: none;
border-radius: 0 0 6px 6px;
max-height: 300px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.search-option {
padding: 0.5rem 0.75rem;
cursor: pointer;
border-bottom: 1px solid #f8f9fa;
transition: background-color 0.15s ease-in-out;
font-size: 0.85rem;
}
.search-option:hover {
background-color: #f8f9fa;
}
.search-option:last-child {
border-bottom: none;
}
.search-no-results {
padding: 0.75rem;
text-align: center;
color: #6c757d;
font-style: italic;
font-size: 0.8rem;
}
/* Animation for dropdown */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.search-dropdown {
animation: fadeIn 0.2s ease-out;
}
/* Informative Design Elements */
.help-icon {
font-size: 0.8rem;
cursor: help;
opacity: 0.7;
transition: opacity 0.2s ease;
}
.help-icon:hover {
opacity: 1;
}
.help-icon.text-danger {
color: #dc3545 !important;
opacity: 1;
}
.help-icon.text-warning {
color: #fd7e14 !important;
opacity: 0.9;
}
.info-banner {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border: 1px solid #2196f3;
border-radius: 6px;
padding: 0.75rem 1rem;
color: #0d47a1;
font-size: 0.85rem;
display: flex;
align-items: center;
}
.warning-banner {
background: linear-gradient(135deg, #fff3e0 0%, #ffcc80 100%);
border: 1px solid #ff9800;
border-radius: 6px;
padding: 0.75rem 1rem;
color: #e65100;
font-size: 0.85rem;
display: flex;
align-items: center;
}
.success-banner {
background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%);
border: 1px solid #4caf50;
border-radius: 6px;
padding: 0.75rem 1rem;
color: #2e7d32;
font-size: 0.85rem;
display: flex;
align-items: center;
}
/* Enhanced Setting Help */
.setting-help {
color: #6c757d;
font-size: 0.8rem;
margin-top: 0.25rem;
display: block;
line-height: 1.4;
}
.setting-help strong {
color: #495057;
}
/* Enhanced Form Labels */
.setting-label {
display: flex;
align-items: center;
font-weight: 600;
color: #495057;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
/* Enhanced Switch Descriptions */
.switch-description {
color: #6c757d;
font-size: 0.85rem;
margin: 0;
line-height: 1.4;
}
.switch-description small {
display: block;
margin-top: 0.25rem;
font-size: 0.75rem;
}
.switch-description .text-warning {
color: #856404 !important;
}
.switch-description .text-info {
color: #0c5460 !important;
}
.switch-description .text-success {
color: #155724 !important;
}
.switch-description .text-danger {
color: #721c24 !important;
}
.setting-help.text-danger {
color: #721c24 !important;
font-weight: 600;
}
.setting-help.text-warning {
color: #856404 !important;
font-weight: 600;
}
/* Enhanced Section Titles */
.section-title {
color: #495057;
font-weight: 600;
font-size: 1.1rem;
margin-bottom: 1rem;
display: flex;
align-items: center;
}
/* Tooltip Enhancements */
::ng-deep .tooltip {
font-size: 0.8rem;
}
::ng-deep .tooltip-inner {
max-width: 300px;
text-align: left;
background-color: #2c3e50;
border-radius: 6px;
padding: 0.5rem 0.75rem;
}
/* Enhanced Form Controls with Status */
.form-control.has-warning {
border-color: #ffc107;
box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.25);
}
.form-control.has-success {
border-color: #28a745;
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
}
.form-control.has-error {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
/* Enhanced Select Options */
.form-select option {
padding: 0.5rem;
}
/* Status Indicators */
.status-indicator {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
margin-left: 0.5rem;
}
.status-indicator.recommended {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-indicator.warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.status-indicator.critical {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Grid Enhancements */
::ng-deep .gui-grid {
.gui-grid-header {
background: #f8f9fa;
font-weight: 600;
}
.gui-grid-cell {
padding: 0.75rem 0.5rem;
}
}

View file

@ -37,6 +37,17 @@ export class SettingsComponent implements OnInit {
public filters: any = {};
public firms: any = {};
public firmtodownload: any = {};
public activeTab: string = 'firmware';
// Search functionality properties
public firmwareSearch: string = '';
public showFirmwareDropdown: boolean = false;
public filteredFirmwares: any[] = [];
public timezoneSearch: string = '';
public showTimezoneDropdown: boolean = false;
public filteredTimezones: any[] = [];
constructor(
private data_provider: dataProvider,
private router: Router,
@ -293,6 +304,12 @@ export class SettingsComponent implements OnInit {
_self.sysconfigs["default_user"]["value"] = "";
_self.sysconfigs["default_password"]["value"] = "";
_self.timezones = _self.TimeZones.timezones;
_self.filteredTimezones = _self.TimeZones.timezones;
// Set initial timezone search display
const currentTz = _self.timezones.find((tz: any) => tz.utc[0] === _self.sysconfigs['timezone']['value']);
if (currentTz) {
_self.timezoneSearch = currentTz.text;
}
_self.sysconfigs["force_syslog"]["value"] = /true/i.test(
_self.sysconfigs["force_syslog"]["value"]
);
@ -310,6 +327,16 @@ export class SettingsComponent implements OnInit {
_self.sysconfigs["otp_force"]["value"]
);
}
if(_self.ispro && "proxy_auto_login" in _self.sysconfigs){
_self.sysconfigs["proxy_auto_login"]["value"] = /true/i.test(
_self.sysconfigs["proxy_auto_login"]["value"]
);
}
else if(_self.ispro){
_self.sysconfigs["proxy_auto_login"] = {
"value": true
}
}
//check if update_mode is in the sysconfigs
if ("update_mode" in _self.sysconfigs){
//convert string to json
@ -335,7 +362,50 @@ export class SettingsComponent implements OnInit {
this.data_provider.get_downloadable_firms().then((res) => {
let index = 1;
_self.firms = res.versions;
_self.filteredFirmwares = res.versions;
_self.loading = false;
});
}
// Firmware search methods
filterFirmwares(event: any): void {
const searchTerm = event.target.value.toLowerCase();
this.firmwareSearch = searchTerm;
this.filteredFirmwares = this.firms.filter((firm: string) =>
firm.toLowerCase().includes(searchTerm)
);
}
selectFirmware(firmware: string): void {
this.firmtodownload = firmware;
this.firmwareSearch = firmware;
this.showFirmwareDropdown = false;
}
hideFirmwareDropdown(): void {
setTimeout(() => {
this.showFirmwareDropdown = false;
}, 200);
}
// Timezone search methods
filterTimezones(event: any): void {
const searchTerm = event.target.value.toLowerCase();
this.timezoneSearch = searchTerm;
this.filteredTimezones = this.timezones.filter((tz: any) =>
tz.text.toLowerCase().includes(searchTerm)
);
}
selectTimezone(timezone: any): void {
this.sysconfigs['timezone']['value'] = timezone.utc[0];
this.timezoneSearch = timezone.text;
this.showTimezoneDropdown = false;
}
hideTimezoneDropdown(): void {
setTimeout(() => {
this.showTimezoneDropdown = false;
}, 200);
}
}

View file

@ -10,14 +10,14 @@ import {
SpinnerModule,
ToastModule,
ModalModule,
BadgeModule,
TooltipModule,
} from "@coreui/angular";
import { SettingsRoutingModule } from "./settings-routing.module";
import { SettingsComponent } from "./settings.component";
import { GuiGridModule } from "@generic-ui/ngx-grid";
import { FormsModule } from "@angular/forms";
import { MatSelectModule } from "@angular/material/select";
import { NgxMatSelectSearchModule } from "ngx-mat-select-search";
@NgModule({
imports: [
@ -30,11 +30,11 @@ import { NgxMatSelectSearchModule } from "ngx-mat-select-search";
ButtonModule,
ButtonGroupModule,
GuiGridModule,
MatSelectModule,
NgxMatSelectSearchModule,
SpinnerModule,
ToastModule,
ModalModule,
BadgeModule,
TooltipModule,
],
declarations: [SettingsComponent],
})

View file

@ -318,7 +318,7 @@ export class SnippetsComponent implements OnInit, OnDestroy {
initGridTable(): void {
var _self = this;
_self.data_provider.get_snippets("", "", "", 0, 1000).then((res) => {
_self.data_provider.get_snippets("", "", "", 0, 1000,false).then((res) => {
_self.source = res.map((x: any) => {
x.created = [
x.created.split("T")[0],