mirror of
https://github.com/MikroWizard/mikrofront.git
synced 2025-12-06 01:59:29 +00:00
feat: redesign user management interface
- Complete UI/UX overhaul of user manager - Improved user permissions handling - Enhanced user role management - Better user filtering and search capabilities
This commit is contained in:
parent
746711fa24
commit
069e53cec6
3 changed files with 696 additions and 112 deletions
|
|
@ -56,124 +56,200 @@
|
||||||
|
|
||||||
<c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal">
|
<c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal">
|
||||||
<c-modal-header>
|
<c-modal-header>
|
||||||
<h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle>Editing User {{SelectedUser['name']}}</h5>
|
<h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle><i class="fa-solid fa-user-pen me-1"></i>Edit: {{SelectedUser['username']}}</h5>
|
||||||
<h5 *ngIf="SelectedUser['action']=='add'" cModalTitle>Adding new User</h5>
|
<h5 *ngIf="SelectedUser['action']=='add'" cModalTitle><i class="fa-solid fa-user-plus me-1"></i>Add User</h5>
|
||||||
<button [cModalToggle]="EditTaskModal.id" cButtonClose></button>
|
<button [cModalToggle]="EditTaskModal.id" cButtonClose></button>
|
||||||
</c-modal-header>
|
</c-modal-header>
|
||||||
<c-modal-body>
|
<c-modal-body class="p-3">
|
||||||
<div [cFormFloating]="true" class="mb-3">
|
<!-- User Details -->
|
||||||
<input cFormControl id="floatingInput" placeholder="User Name" [(ngModel)]="SelectedUser['username']" />
|
<div class="user-form-section mb-4">
|
||||||
<label cLabel for="floatingInput">User Name</label>
|
<div class="section-header mb-3">
|
||||||
</div>
|
<h6 class="section-title mb-1"><i class="fa-solid fa-user me-2"></i>Basic Information</h6>
|
||||||
|
<small class="text-muted">Enter the user's personal details and login credentials</small>
|
||||||
<c-input-group class="mb-3">
|
</div>
|
||||||
<span cInputGroupText>First Name</span>
|
<c-row class="g-3">
|
||||||
<input cFormControl id="floatingInput" placeholder="First Name" [(ngModel)]="SelectedUser['first_name']" />
|
<c-col xs="12" md="6">
|
||||||
|
<input cFormControl placeholder="Username (required for login)" [(ngModel)]="SelectedUser['username']"
|
||||||
<span cInputGroupText>Last Name</span>
|
class="form-input" title="Unique username for system login" />
|
||||||
<input cFormControl id="floatingInput" placeholder="Last Name" [(ngModel)]="SelectedUser['last_name']" />
|
</c-col>
|
||||||
</c-input-group>
|
<c-col xs="12" md="6">
|
||||||
|
<input cFormControl placeholder="Email Address (for notifications)" [(ngModel)]="SelectedUser['email']"
|
||||||
<div [cFormFloating]="true" class="mb-3">
|
class="form-input" type="email" title="Valid email address for system notifications" />
|
||||||
<input cFormControl id="floatingInput" placeholder="Email Address" [(ngModel)]="SelectedUser['email']" />
|
</c-col>
|
||||||
<label cLabel for="floatingInput">Email Address</label>
|
<c-col xs="12" md="4">
|
||||||
</div>
|
<input cFormControl placeholder="First Name (optional)" [(ngModel)]="SelectedUser['first_name']"
|
||||||
|
class="form-input" title="User's first name" />
|
||||||
<div [cFormFloating]="true" class="mb-3">
|
</c-col>
|
||||||
<input type="password" cFormControl id="floatingInput" placeholder="Password" [(ngModel)]="SelectedUser['password']" />
|
<c-col xs="12" md="4">
|
||||||
<label cLabel for="floatingInput">Password</label>
|
<input cFormControl placeholder="Last Name (optional)" [(ngModel)]="SelectedUser['last_name']"
|
||||||
</div>
|
class="form-input" title="User's last name" />
|
||||||
<c-input-group>
|
</c-col>
|
||||||
<h5>MikroWizard permisssions :</h5>
|
<c-col xs="12" md="4">
|
||||||
<c-container>
|
<input type="password" cFormControl placeholder="Password (min 6 characters)" [(ngModel)]="SelectedUser['password']"
|
||||||
<c-row>
|
class="form-input" title="Secure password for user login" />
|
||||||
<c-col *ngFor='let perm of adminperms | keyvalue' [md]="6" class="mb-1">
|
|
||||||
<label cFormCheckLabel style="text-transform: capitalize">{{ perm.key}} :</label>
|
|
||||||
<c-form-check class="md-6" [switch]="true" style="float: right;">
|
|
||||||
<c-button-group>
|
|
||||||
<c-button-group aria-label="Basic example" role="group">
|
|
||||||
<button cButton color="info" variant="outline" size="sm" [active]="adminperms[perm.key]=='read'"
|
|
||||||
(click)="setRadioValue(perm.key,'read')">Read</button>
|
|
||||||
<button cButton color="danger" variant="outline" size="sm" [active]="adminperms[perm.key]=='write'"
|
|
||||||
(click)="setRadioValue(perm.key,'write')">Write</button>
|
|
||||||
<button cButton color="success" variant="outline" size="sm" [active]="adminperms[perm.key]=='full'"
|
|
||||||
(click)="setRadioValue(perm.key,'full')">Full</button>
|
|
||||||
<button cButton color="dark" variant="outline" size="sm" [active]="adminperms[perm.key]=='none'"
|
|
||||||
(click)="setRadioValue(perm.key,'none')">None</button>
|
|
||||||
</c-button-group>
|
|
||||||
</c-button-group>
|
|
||||||
</c-form-check>
|
|
||||||
</c-col>
|
</c-col>
|
||||||
</c-row>
|
</c-row>
|
||||||
</c-container>
|
|
||||||
</c-input-group>
|
|
||||||
<c-input-group *ngIf="userperms.length>0" class="mb-3">
|
|
||||||
<h5>Mikrotik permisssions :</h5>
|
|
||||||
<gui-grid [autoResizeWidth]="true" [source]="userperms" [columnMenu]="columnMenu" [sorting]="sorting"
|
|
||||||
[autoResizeWidth]=true [paging]="paging" >
|
|
||||||
<gui-grid-column header="Group Name" field="group_name">
|
|
||||||
<ng-template let-value="item.group_name" let-item="item" let-index="index">
|
|
||||||
{{value}} </ng-template>
|
|
||||||
</gui-grid-column>
|
|
||||||
<gui-grid-column header="perm Name" field="perm_name">
|
|
||||||
<ng-template let-value="item.perm_name" let-item="item" let-index="index">
|
|
||||||
{{value}}
|
|
||||||
</ng-template>
|
|
||||||
</gui-grid-column>
|
|
||||||
<gui-grid-column header="Actions" width="120" field="action">
|
|
||||||
<ng-template let-value="item.id" let-item="item" let-index="index">
|
|
||||||
<button cButton color="danger" size="sm" (click)="confirm_delete_perm(item);"><i
|
|
||||||
class="fa-regular fa-trash-can"></i></button>
|
|
||||||
</ng-template>
|
|
||||||
</gui-grid-column>
|
|
||||||
</gui-grid>
|
|
||||||
</c-input-group>
|
|
||||||
<hr />
|
|
||||||
<table >
|
|
||||||
<td style="width: 30%;">
|
|
||||||
<span>Add new Permission</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-select cFormControl [(ngModel)]="devgroup" placeholder="Device Group" #singleSelect>
|
|
||||||
<mat-option>
|
|
||||||
<ngx-mat-select-search></ngx-mat-select-search>
|
|
||||||
</mat-option>
|
|
||||||
<mat-option *ngFor="let group of allDevGroups" [value]="group">
|
|
||||||
{{group.name}}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-select cFormControl placeholder="Permission" [(ngModel)]="permission" #singleSelect>
|
|
||||||
<mat-option>
|
|
||||||
<ngx-mat-select-search></ngx-mat-select-search>
|
|
||||||
</mat-option>
|
|
||||||
<mat-option *ngFor="let perm of allPerms" [value]="perm">
|
|
||||||
{{perm.name}}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button *ngIf="SelectedUser['action']=='edit'" cButton color="primary" (click)="add_user_perm()">Add+</button>
|
|
||||||
<button *ngIf="SelectedUser['action']=='add'" cButton color="primary" (click)="add_new_user_perm()">Add+</button>
|
|
||||||
<!-- <button *ngIf="SelectedUser['action']=='add'" cButton color="primary" (click)="loading=!loading">++</button> -->
|
|
||||||
</td>
|
|
||||||
</table>
|
|
||||||
</c-modal-body>
|
|
||||||
<c-modal-footer style="justify-content: space-between;">
|
|
||||||
<div>
|
|
||||||
<button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton color="danger">Deactive</button>
|
|
||||||
<button *ngIf="SelectedUser['role']=='disabled'" (click)="SelectedUser['role']='admin'" cButton color="success">Activate</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- System Permissions -->
|
||||||
|
<div class="permissions-section mb-4">
|
||||||
|
<div class="section-header mb-3">
|
||||||
|
<h6 class="section-title mb-1"><i class="fa-solid fa-shield-halved me-2"></i>System Access Levels</h6>
|
||||||
|
<small class="text-muted">Control what system features this user can access and modify</small>
|
||||||
|
</div>
|
||||||
|
<div class="permission-legend mb-2">
|
||||||
|
<span class="legend-item"><span class="legend-color bg-info"></span>R = Read Only</span>
|
||||||
|
<span class="legend-item"><span class="legend-color bg-warning"></span>W = Read & Write</span>
|
||||||
|
<span class="legend-item"><span class="legend-color bg-success"></span>F = Full Control</span>
|
||||||
|
<span class="legend-item"><span class="legend-color bg-secondary"></span>× = No Access</span>
|
||||||
|
</div>
|
||||||
|
<div class="permissions-compact">
|
||||||
|
<div *ngFor='let perm of adminperms | keyvalue' class="perm-row">
|
||||||
|
<span class="perm-label">{{ perm.key.replace('_', ' ') }}</span>
|
||||||
|
<div class="perm-controls">
|
||||||
|
<button cButton size="sm" variant="outline" color="info" [active]="adminperms[perm.key]=='read'"
|
||||||
|
(click)="setRadioValue(perm.key,'read')" title="Read Only Access">R</button>
|
||||||
|
<button cButton size="sm" variant="outline" color="warning" [active]="adminperms[perm.key]=='write'"
|
||||||
|
(click)="setRadioValue(perm.key,'write')" title="Read & Write Access">W</button>
|
||||||
|
<button cButton size="sm" variant="outline" color="success" [active]="adminperms[perm.key]=='full'"
|
||||||
|
(click)="setRadioValue(perm.key,'full')" title="Full Control Access">F</button>
|
||||||
|
<button cButton size="sm" variant="outline" color="secondary" [active]="adminperms[perm.key]=='none'"
|
||||||
|
(click)="setRadioValue(perm.key,'none')" title="No Access">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Device Permissions -->
|
||||||
|
<div class="device-permissions-section">
|
||||||
|
<div class="section-header mb-3">
|
||||||
|
<h6 class="section-title mb-1"><i class="fa-solid fa-network-wired me-2"></i>Device Access Control</h6>
|
||||||
|
<small class="text-muted">Grant access to specific device groups with defined permission levels</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add New Permission -->
|
||||||
|
<div class="add-permission-card mb-3">
|
||||||
|
<div class="add-header">
|
||||||
|
<i class="fa-solid fa-plus-circle text-primary me-2"></i>
|
||||||
|
<span class="fw-semibold">Add Device Permission</span>
|
||||||
|
</div>
|
||||||
|
<c-row class="g-2 mt-2">
|
||||||
|
<c-col xs="12" md="5">
|
||||||
|
<div class="search-select-wrapper">
|
||||||
|
<label class="search-label">Device Group</label>
|
||||||
|
<input cFormControl
|
||||||
|
[(ngModel)]="devgroupSearch"
|
||||||
|
(input)="filterDevGroups($event)"
|
||||||
|
(focus)="showDevGroupDropdown = true"
|
||||||
|
(blur)="hideDevGroupDropdown()"
|
||||||
|
placeholder="Search and select device group..."
|
||||||
|
class="search-input compact-select"
|
||||||
|
autocomplete="off" />
|
||||||
|
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length > 0" class="search-dropdown">
|
||||||
|
<div *ngFor="let group of filteredDevGroups"
|
||||||
|
class="search-option"
|
||||||
|
(mousedown)="selectDevGroup(group)">
|
||||||
|
{{group.name}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length === 0 && devgroupSearch" class="search-no-results">
|
||||||
|
No groups found
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</c-col>
|
||||||
|
<c-col xs="12" md="5">
|
||||||
|
<div class="search-select-wrapper">
|
||||||
|
<label class="search-label">Permission Level</label>
|
||||||
|
<input cFormControl
|
||||||
|
[(ngModel)]="permissionSearch"
|
||||||
|
(input)="filterPermissions($event)"
|
||||||
|
(focus)="showPermissionDropdown = true"
|
||||||
|
(blur)="hidePermissionDropdown()"
|
||||||
|
placeholder="Search and select permission..."
|
||||||
|
class="search-input compact-select"
|
||||||
|
autocomplete="off" />
|
||||||
|
<div *ngIf="showPermissionDropdown && filteredPermissions.length > 0" class="search-dropdown">
|
||||||
|
<div *ngFor="let perm of filteredPermissions"
|
||||||
|
class="search-option"
|
||||||
|
(mousedown)="selectPermission(perm)">
|
||||||
|
{{perm.name}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="showPermissionDropdown && filteredPermissions.length === 0 && permissionSearch" class="search-no-results">
|
||||||
|
No permissions found
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</c-col>
|
||||||
|
<c-col xs="12" md="2" class="d-flex align-items-end">
|
||||||
|
<button *ngIf="SelectedUser['action']=='edit'" cButton color="success" size="sm" class="w-100 add-btn"
|
||||||
|
(click)="add_user_perm()" [disabled]="!devgroup || !permission">
|
||||||
|
<i class="fa-solid fa-plus me-1"></i>Add Access
|
||||||
|
</button>
|
||||||
|
<button *ngIf="SelectedUser['action']=='add'" cButton color="success" size="sm" class="w-100 add-btn"
|
||||||
|
(click)="add_new_user_perm()" [disabled]="!devgroup || !permission">
|
||||||
|
<i class="fa-solid fa-plus me-1"></i>Add Access
|
||||||
|
</button>
|
||||||
|
</c-col>
|
||||||
|
</c-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Current Permissions -->
|
||||||
|
<div class="current-permissions">
|
||||||
|
<div class="permissions-header">
|
||||||
|
<h6 class="mb-1"><i class="fa-solid fa-list-check me-2 text-success"></i>Current Device Access</h6>
|
||||||
|
<span class="badge bg-info">{{userperms.length}} permission(s)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="userperms.length>0" class="permissions-list mt-2">
|
||||||
|
<div *ngFor="let perm of userperms; let i = index" class="permission-item">
|
||||||
|
<div class="perm-number">{{i + 1}}</div>
|
||||||
|
<div class="perm-info">
|
||||||
|
<div class="perm-main">
|
||||||
|
<i class="fa-solid fa-server me-1 text-primary"></i>
|
||||||
|
<span class="group-name">{{perm.group_name}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="perm-detail">
|
||||||
|
<i class="fa-solid fa-key me-1 text-warning"></i>
|
||||||
|
<span class="perm-name">{{perm.perm_name}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button cButton color="danger" size="sm" variant="outline" (click)="confirm_delete_perm(perm)"
|
||||||
|
title="Remove this permission" class="remove-btn">
|
||||||
|
<i class="fa-solid fa-trash-can"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="userperms.length==0" class="empty-permissions">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<i class="fa-solid fa-shield-xmark text-muted"></i>
|
||||||
|
</div>
|
||||||
|
<div class="empty-text">
|
||||||
|
<strong>No device access granted</strong>
|
||||||
|
<p class="mb-0 text-muted">Add permissions above to grant access to device groups</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</c-modal-body>
|
||||||
|
<c-modal-footer class="d-flex justify-content-between flex-wrap gap-2 p-2">
|
||||||
<div>
|
<div>
|
||||||
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button>
|
<button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton color="danger" size="sm">
|
||||||
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary">save</button>
|
<i class="fa-solid fa-user-slash me-1"></i><span class="d-none d-sm-inline">Deactivate</span>
|
||||||
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary">
|
</button>
|
||||||
Close
|
<button *ngIf="SelectedUser['role']=='disabled'" (click)="SelectedUser['role']='admin'" cButton color="success" size="sm">
|
||||||
|
<i class="fa-solid fa-user-check me-1"></i><span class="d-none d-sm-inline">Activate</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary" size="sm">
|
||||||
|
<i class="fa-solid fa-plus me-1"></i>Add
|
||||||
|
</button>
|
||||||
|
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary" size="sm">
|
||||||
|
<i class="fa-solid fa-save me-1"></i>Save
|
||||||
|
</button>
|
||||||
|
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary" size="sm">
|
||||||
|
<i class="fa-solid fa-times me-1"></i>Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</c-modal-footer>
|
</c-modal-footer>
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,12 @@ export class UserManagerComponent implements OnInit {
|
||||||
public permission: any = {};
|
public permission: any = {};
|
||||||
public allDevGroups: any = [];
|
public allDevGroups: any = [];
|
||||||
public allPerms: any = [];
|
public allPerms: any = [];
|
||||||
|
public devgroupSearch: string = '';
|
||||||
|
public permissionSearch: string = '';
|
||||||
|
public filteredDevGroups: any = [];
|
||||||
|
public filteredPermissions: any = [];
|
||||||
|
public showDevGroupDropdown: boolean = false;
|
||||||
|
public showPermissionDropdown: boolean = false;
|
||||||
public DeletePermConfirmModalVisible: boolean = false;
|
public DeletePermConfirmModalVisible: boolean = false;
|
||||||
public userperms: any = {};
|
public userperms: any = {};
|
||||||
public userresttrictions: any = false;
|
public userresttrictions: any = false;
|
||||||
|
|
@ -129,6 +135,40 @@ export class UserManagerComponent implements OnInit {
|
||||||
this.adminperms[key] = value;
|
this.adminperms[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterDevGroups(event: any): void {
|
||||||
|
const query = event.target.value.toLowerCase();
|
||||||
|
this.filteredDevGroups = this.allDevGroups.filter((group: any) =>
|
||||||
|
group.name.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterPermissions(event: any): void {
|
||||||
|
const query = event.target.value.toLowerCase();
|
||||||
|
this.filteredPermissions = this.allPerms.filter((perm: any) =>
|
||||||
|
perm.name.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectDevGroup(group: any): void {
|
||||||
|
this.devgroup = group;
|
||||||
|
this.devgroupSearch = group.name;
|
||||||
|
this.showDevGroupDropdown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectPermission(perm: any): void {
|
||||||
|
this.permission = perm;
|
||||||
|
this.permissionSearch = perm.name;
|
||||||
|
this.showPermissionDropdown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDevGroupDropdown(): void {
|
||||||
|
setTimeout(() => this.showDevGroupDropdown = false, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
hidePermissionDropdown(): void {
|
||||||
|
setTimeout(() => this.showPermissionDropdown = false, 200);
|
||||||
|
}
|
||||||
|
|
||||||
public rowSelection: boolean | GuiRowSelection = {
|
public rowSelection: boolean | GuiRowSelection = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
type: GuiRowSelectionType.CHECKBOX,
|
type: GuiRowSelectionType.CHECKBOX,
|
||||||
|
|
@ -226,6 +266,7 @@ export class UserManagerComponent implements OnInit {
|
||||||
_self.allPerms = res.map((x: any) => {
|
_self.allPerms = res.map((x: any) => {
|
||||||
return { id: x["id"], name: x.name };
|
return { id: x["id"], name: x.name };
|
||||||
});
|
});
|
||||||
|
_self.filteredPermissions = [..._self.allPerms];
|
||||||
_self.data_provider.get_devgroup_list().then((res) => {
|
_self.data_provider.get_devgroup_list().then((res) => {
|
||||||
if ("error" in res && res.error.indexOf("Unauthorized")) {
|
if ("error" in res && res.error.indexOf("Unauthorized")) {
|
||||||
_self.show_toast(
|
_self.show_toast(
|
||||||
|
|
@ -238,6 +279,7 @@ export class UserManagerComponent implements OnInit {
|
||||||
_self.allDevGroups = res.map((x: any) => {
|
_self.allDevGroups = res.map((x: any) => {
|
||||||
return { id: x["id"], name: x.name };
|
return { id: x["id"], name: x.name };
|
||||||
});
|
});
|
||||||
|
_self.filteredDevGroups = [..._self.allDevGroups];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -254,6 +296,10 @@ export class UserManagerComponent implements OnInit {
|
||||||
action: "add",
|
action: "add",
|
||||||
};
|
};
|
||||||
this.adminperms = { ...this.defadminperms };
|
this.adminperms = { ...this.defadminperms };
|
||||||
|
this.devgroup = {};
|
||||||
|
this.permission = {};
|
||||||
|
this.devgroupSearch = '';
|
||||||
|
this.permissionSearch = '';
|
||||||
this.EditTaskModalVisible = true;
|
this.EditTaskModalVisible = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -263,6 +309,10 @@ export class UserManagerComponent implements OnInit {
|
||||||
} else this.adminperms = { ...this.defadminperms };
|
} else this.adminperms = { ...this.defadminperms };
|
||||||
_self.SelectedUser["action"] = "edit";
|
_self.SelectedUser["action"] = "edit";
|
||||||
_self.get_user_perms(_self.SelectedUser["id"]);
|
_self.get_user_perms(_self.SelectedUser["id"]);
|
||||||
|
_self.devgroup = {};
|
||||||
|
_self.permission = {};
|
||||||
|
_self.devgroupSearch = '';
|
||||||
|
_self.permissionSearch = '';
|
||||||
_self.EditTaskModalVisible = true;
|
_self.EditTaskModalVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,462 @@
|
||||||
table tr td{
|
table tr td{
|
||||||
padding-bottom:20px;
|
padding-bottom:20px;
|
||||||
vertical-align:top
|
vertical-align:top
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced Modal Styles */
|
||||||
|
.permission-item {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: #fafafa;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modern Form Sections */
|
||||||
|
.user-form-section, .permissions-section, .device-permissions-section {
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
color: #495057;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact Permissions */
|
||||||
|
.permissions-compact {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-row:hover {
|
||||||
|
background: #f1f3f4;
|
||||||
|
border-color: #adb5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #495057;
|
||||||
|
text-transform: capitalize;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-controls .btn {
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Headers */
|
||||||
|
.section-header {
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Permission Legend */
|
||||||
|
.permission-legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-color {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add Permission Card */
|
||||||
|
.add-permission-card {
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border: 2px dashed #0d6efd;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-permission-card:hover {
|
||||||
|
border-color: #0a58ca;
|
||||||
|
background: linear-gradient(135deg, #e7f3ff 0%, #cfe2ff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #495057;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Current Permissions */
|
||||||
|
.current-permissions {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-header {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-list {
|
||||||
|
max-height: 250px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid #f1f3f4;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-number {
|
||||||
|
background: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-detail {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-name {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-name {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty-permissions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text strong {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-select {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
height: calc(1.8em + 0.75rem + 2px);
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-select:focus {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search Select Components */
|
||||||
|
.search-select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 0.375rem 0.375rem;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-option {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid #f1f3f4;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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-size: 0.8rem;
|
||||||
|
font-style: italic;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 0.375rem 0.375rem;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-permission-form {
|
||||||
|
border: 1px dashed #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-modal-body {
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-card-header {
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item small {
|
||||||
|
color: #6c757d;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-button-group .btn {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-field {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-field .mat-form-field-wrapper {
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.c-modal-dialog {
|
||||||
|
margin: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form-section, .permissions-section, .device-permissions-section {
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-compact {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-label {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perm-controls {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-permission-card {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-legend {
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-permissions {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-button-group .btn {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 0.25rem 0.3rem;
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-modal-body {
|
||||||
|
max-height: 90vh;
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-card-body {
|
||||||
|
padding: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-permission-form {
|
||||||
|
padding: 0.75rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.c-modal-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-modal-footer > div {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue