mirror of
https://github.com/MikroWizard/mikrofront.git
synced 2025-12-06 18:19:29 +00:00
feat: redesign device group management interface
- Complete UI/UX redesign of device groups table - Enhanced member addition interface - Add user management directly from device groups - Add firmware group actions (update and upgrade) - Improved bulk operations for device groups
This commit is contained in:
parent
5822fb5d1d
commit
996c189076
4 changed files with 742 additions and 86 deletions
|
|
@ -25,29 +25,67 @@
|
||||||
<ng-container *ngIf="item.id==1 ; then Default;else NotDefault">
|
<ng-container *ngIf="item.id==1 ; then Default;else NotDefault">
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #Default>
|
<ng-template #Default>
|
||||||
<c-badge color="info">All Devices</c-badge>
|
<c-badge color="info"><i class="fa-solid fa-network-wired me-1"></i>All Devices</c-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #NotDefault>
|
<ng-template #NotDefault>
|
||||||
<c-badge color="info" *ngIf="value[0]==null && item.id!=1">0 Members</c-badge>
|
<c-badge color="info" *ngIf="value[0]==null && item.id!=1"
|
||||||
<c-badge color="info" *ngIf="value[0]!=null">{{value.length}} Members</c-badge>
|
[cTooltip]="'No devices assigned'" style="cursor: help">
|
||||||
|
<i class="fa-solid fa-server me-1"></i>0
|
||||||
|
</c-badge>
|
||||||
|
<c-badge color="info" *ngIf="value[0]!=null"
|
||||||
|
[cTooltip]="getDevicesTooltip(item)"
|
||||||
|
[cTooltipPlacement]="'top'" style="cursor: help">
|
||||||
|
<i class="fa-solid fa-server me-1"></i>{{value.length}}
|
||||||
|
</c-badge>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</gui-grid-column>
|
</gui-grid-column>
|
||||||
<gui-grid-column header="Create Time" field="created">
|
<gui-grid-column header="Create Time" field="created">
|
||||||
<ng-template let-value="item.created" let-item="item" let-index="index">
|
<ng-template let-value="item.created" let-item="item" let-index="index">
|
||||||
|
{{formatCreateTime(value)}}
|
||||||
{{value}}
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</gui-grid-column>
|
</gui-grid-column>
|
||||||
|
|
||||||
<gui-grid-column header="Actions" field="action">
|
<gui-grid-column header="Users" field="assigned_users" width="80" align="CENTER">
|
||||||
<ng-template let-value="item.id" let-item="item" let-index="index">
|
<ng-template let-value="item.assigned_users" let-item="item" let-index="index">
|
||||||
<button [disabled]="value==1" cButton color="warning" size="sm" (click)="editAddGroup(item,'showedit');"
|
<c-badge color="info"
|
||||||
class="mx-1"><i class="fa-regular fa-pen-to-square"></i></button>
|
[cTooltip]="getUsersTooltip(value)"
|
||||||
<button [disabled]="value==1" cButton color="info" size="sm" (click)="show_members(item.id);"
|
[cTooltipPlacement]="'top'"
|
||||||
class="mx-1"><i class="fa-regular fa-eye"></i></button>
|
style="cursor: help">
|
||||||
<button [disabled]="value==1" cButton color="danger" size="sm" (click)="show_delete_group(item);"
|
<i class="fa-solid fa-users me-1"></i>{{value.length}}
|
||||||
class="mx-1"><i class="fa-regular fa-trash-can"></i></button>
|
</c-badge>
|
||||||
|
</ng-template>
|
||||||
|
</gui-grid-column>
|
||||||
|
|
||||||
|
<gui-grid-column align="center" [cellEditing]="false" [sorting]="false" header="Action">
|
||||||
|
<ng-template let-value="value" let-item="item">
|
||||||
|
<button size="sm" shape="rounded-0" variant="outline" cButton color="primary" (click)="show_members(item.id)"
|
||||||
|
style="border: none;padding: 4px 7px;"><i class="fa-regular fa-eye"></i><small> View devices</small>
|
||||||
|
</button>
|
||||||
|
<button color="primary" shape="rounded-0" variant="ghost" style="padding: 4px 7px;"
|
||||||
|
[matMenuTriggerFor]="menu" cButton>
|
||||||
|
<i class="fa-solid fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<mat-menu #menu="matMenu">
|
||||||
|
<div cListGroup>
|
||||||
|
<li cListGroupItem [active]="false" color="dark">Actions Menu</li>
|
||||||
|
<button size="sm" (click)="editAddGroup(item,'showedit')" style="padding: 4px 7px;"
|
||||||
|
[disabled]="item.id==1" cListGroupItem><i class="fa-solid fa-pencil"></i><small>
|
||||||
|
Edit Group</small></button>
|
||||||
|
<button size="sm" (click)="manageUsers(item)" style="padding: 4px 7px;"
|
||||||
|
cListGroupItem><i class="fa-solid fa-users-gear"></i><small>
|
||||||
|
Manage Users</small></button>
|
||||||
|
<button size="sm" (click)="groupFirmwareAction(item, 'update')" style="padding: 4px 7px;"
|
||||||
|
cListGroupItem><i class="text-success fa-solid fa-upload"></i><small>
|
||||||
|
Update Firmware</small></button>
|
||||||
|
<button size="sm" (click)="groupFirmwareAction(item, 'upgrade')" style="padding: 4px 7px;"
|
||||||
|
cListGroupItem><i class="text-secondary fa-solid fa-microchip"></i><small>
|
||||||
|
Upgrade Firmware</small></button>
|
||||||
|
<button size="sm" (click)="show_delete_group(item)" style="padding: 4px 7px;"
|
||||||
|
[disabled]="item.id==1" cListGroupItem><i class="text-danger fa-solid fa-trash"></i><small>
|
||||||
|
Delete Group</small></button>
|
||||||
|
</div>
|
||||||
|
</mat-menu>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</gui-grid-column>
|
</gui-grid-column>
|
||||||
</gui-grid>
|
</gui-grid>
|
||||||
|
|
@ -59,92 +97,146 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<c-modal #EditGroupModal backdrop="static" size="lg" [(visible)]="EditGroupModalVisible" id="EditGroupModal">
|
<c-modal #EditGroupModal backdrop="static" size="xl" [(visible)]="EditGroupModalVisible" id="EditGroupModal">
|
||||||
<c-modal-header>
|
<c-modal-header class="bg-light">
|
||||||
<h5 cModalTitle> Group Edit</h5>
|
<h5 cModalTitle><i class="fa-solid fa-edit me-2"></i>Edit Device Group</h5>
|
||||||
<button [cModalToggle]="EditGroupModal.id" cButtonClose></button>
|
<button [cModalToggle]="EditGroupModal.id" cButtonClose></button>
|
||||||
</c-modal-header>
|
</c-modal-header>
|
||||||
<c-modal-body>
|
<c-modal-body class="p-4">
|
||||||
<c-input-group class="mb-3">
|
<!-- Group Basic Info -->
|
||||||
<div [cFormFloating]="true" class="mb-3">
|
<div class="bg-light p-3 rounded mb-3 d-flex align-items-center justify-content-between">
|
||||||
<input cFormControl id="floatingInput" placeholder="Group Name" [(ngModel)]="currentGroup['name']" />
|
<div class="d-flex align-items-center flex-grow-1 me-3">
|
||||||
<label cLabel for="floatingInput">Group Name</label>
|
<label class="form-label me-2 mb-0 fw-semibold">Group Name:</label>
|
||||||
|
<input cFormControl [(ngModel)]="currentGroup['name']" class="form-control-sm" style="max-width: 300px;" />
|
||||||
</div>
|
</div>
|
||||||
</c-input-group>
|
<c-badge color="info" class="fs-6 p-2">
|
||||||
<c-input-group class="mb-3">
|
<i class="fa-solid fa-server me-1"></i>{{groupMembers.length}} Devices
|
||||||
<h5>Group Members :</h5>
|
</c-badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Current Members -->
|
||||||
|
<c-card class="mb-3">
|
||||||
|
<c-card-header class="d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="mb-0"><i class="fa-solid fa-list me-2"></i>Current Group Members</h6>
|
||||||
|
<div>
|
||||||
|
<button *ngIf="MemberRows.length > 0" cButton color="danger" size="sm" variant="outline" class="me-2">
|
||||||
|
<i class="fa-solid fa-trash me-1"></i>Remove {{MemberRows.length}} Selected
|
||||||
|
</button>
|
||||||
|
<button cButton color="success" size="sm" (click)="show_new_member_form()">
|
||||||
|
<i class="fa-solid fa-plus me-1"></i>Add Devices
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</c-card-header>
|
||||||
|
<c-card-body class="p-0">
|
||||||
|
<div *ngIf="groupMembers.length === 0" class="text-center p-4 text-muted">
|
||||||
|
<i class="fa-solid fa-server fa-3x mb-3 opacity-50"></i>
|
||||||
|
<h6>No devices in this group</h6>
|
||||||
|
<p class="mb-0">Click "Add Devices" to start adding devices to this group</p>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="groupMembers.length > 0">
|
||||||
<gui-grid [autoResizeWidth]="true" [searching]="searching" [source]="groupMembers" [columnMenu]="columnMenu"
|
<gui-grid [autoResizeWidth]="true" [searching]="searching" [source]="groupMembers" [columnMenu]="columnMenu"
|
||||||
[sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection"
|
[sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection"
|
||||||
(selectedRows)="onSelectedRowsMembers($event)" [autoResizeWidth]=true [paging]="paging">
|
(selectedRows)="onSelectedRowsMembers($event)" [paging]="paging">
|
||||||
<gui-grid-column header="Member Name" field="name">
|
<gui-grid-column header="Device Name" field="name">
|
||||||
<ng-template let-value="item.name" let-item="item" let-index="index">
|
<ng-template let-value="item.name" let-item="item" let-index="index">
|
||||||
{{value}} </ng-template>
|
<div class="d-flex align-items-center">
|
||||||
</gui-grid-column>
|
<i class="fa-solid fa-server me-2 text-primary"></i>
|
||||||
<gui-grid-column header="perm Name" field="ip">
|
<strong>{{value}}</strong>
|
||||||
<ng-template let-value="item.ip" let-item="item" let-index="index">
|
</div>
|
||||||
{{value}}
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</gui-grid-column>
|
</gui-grid-column>
|
||||||
<gui-grid-column header="Actions" width="120" field="action">
|
<gui-grid-column header="IP Address" field="ip">
|
||||||
|
<ng-template let-value="item.ip" let-item="item" let-index="index">
|
||||||
|
<c-badge color="secondary">{{value}}</c-badge>
|
||||||
|
</ng-template>
|
||||||
|
</gui-grid-column>
|
||||||
|
<gui-grid-column header="Actions" width="100" field="action" align="CENTER">
|
||||||
<ng-template let-value="item.id" let-item="item" let-index="index">
|
<ng-template let-value="item.id" let-item="item" let-index="index">
|
||||||
<button cButton color="danger" size="sm" (click)="remove_from_group(item.id)"><i
|
<button cButton color="danger" size="sm" variant="outline" (click)="remove_from_group(item.id)" title="Remove from group">
|
||||||
class="fa-regular fa-trash-can"></i></button>
|
<i class="fa-solid fa-times"></i>
|
||||||
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</gui-grid-column>
|
</gui-grid-column>
|
||||||
</gui-grid>
|
</gui-grid>
|
||||||
<br />
|
</div>
|
||||||
<button *ngIf="MemberRows.length!= 0" style="margin: 10px 0;" cButton color="danger" size="sm"><i
|
</c-card-body>
|
||||||
class="fa-regular fa-trash-can"></i>Delete {{MemberRows.length}} Selected</button>
|
</c-card>
|
||||||
</c-input-group>
|
|
||||||
<hr />
|
|
||||||
<button cButton color="primary" (click)="show_new_member_form()">+ Add new Members</button>
|
|
||||||
</c-modal-body>
|
</c-modal-body>
|
||||||
<c-modal-footer>
|
<c-modal-footer class="bg-light">
|
||||||
<button cButton color="primary" (click)="save_group()">save</button>
|
<button cButton color="primary" (click)="save_group()">
|
||||||
|
<i class="fa-solid fa-save me-1"></i>Save Changes
|
||||||
|
</button>
|
||||||
<button [cModalToggle]="EditGroupModal.id" cButton color="secondary">
|
<button [cModalToggle]="EditGroupModal.id" cButton color="secondary">
|
||||||
Close
|
<i class="fa-solid fa-times me-1"></i>Cancel
|
||||||
</button>
|
</button>
|
||||||
</c-modal-footer>
|
</c-modal-footer>
|
||||||
</c-modal>
|
</c-modal>
|
||||||
|
|
||||||
<c-modal #NewMemberModal backdrop="static" size="lg" [(visible)]="NewMemberModalVisible" id="NewMemberModal">
|
<c-modal #NewMemberModal [backdrop]="true" size="xl" [(visible)]="NewMemberModalVisible" id="NewMemberModal" style="z-index: 1060; backdrop-filter: blur(2px);">
|
||||||
<c-modal-header>
|
<c-modal-header class="bg-success text-white">
|
||||||
<h5 cModalTitle>Members not in Group</h5>
|
<h5 cModalTitle><i class="fa-solid fa-plus-circle me-2"></i>Add Devices to Group</h5>
|
||||||
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
|
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButtonClose></button>
|
||||||
</c-modal-header>
|
</c-modal-header>
|
||||||
<c-modal-body>
|
<c-modal-body class="p-4">
|
||||||
<c-input-group class="mb-3">
|
<!-- Selection Summary -->
|
||||||
<h5>Members Availble to add:</h5>
|
<div class="mb-3" style="min-height: 58px;">
|
||||||
|
<c-alert *ngIf="NewMemberRows.length > 0" color="info" class="d-flex align-items-center mb-0">
|
||||||
|
<i class="fa-solid fa-info-circle me-2"></i>
|
||||||
|
<span><strong>{{NewMemberRows.length}}</strong> device(s) selected for addition</span>
|
||||||
|
</c-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Available Devices -->
|
||||||
|
<c-card>
|
||||||
|
<c-card-header>
|
||||||
|
<h6 class="mb-0"><i class="fa-solid fa-server me-2"></i>Available Devices ({{availbleMembers.length}} total)</h6>
|
||||||
|
</c-card-header>
|
||||||
|
<c-card-body class="p-0">
|
||||||
|
<div *ngIf="availbleMembers.length === 0" class="text-center p-4 text-muted">
|
||||||
|
<i class="fa-solid fa-check-circle fa-3x mb-3 text-success opacity-50"></i>
|
||||||
|
<h6>All devices are already in groups</h6>
|
||||||
|
<p class="mb-0">No available devices to add to this group</p>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="availbleMembers.length > 0">
|
||||||
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
|
<gui-grid [autoResizeWidth]="true" *ngIf="NewMemberModalVisible" [searching]="searching"
|
||||||
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
|
[source]="availbleMembers" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel"
|
||||||
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [autoResizeWidth]=true
|
[rowSelection]="rowSelection" (selectedRows)="onSelectedRowsNewMembers($event)" [paging]="paging">
|
||||||
[paging]="paging">
|
<gui-grid-column header="Device Name" field="name">
|
||||||
<gui-grid-column header="Group Name" field="name">
|
|
||||||
<ng-template let-value="item.name" let-item="item" let-index="index">
|
<ng-template let-value="item.name" let-item="item" let-index="index">
|
||||||
{{value}} </ng-template>
|
<div class="d-flex align-items-center">
|
||||||
</gui-grid-column>
|
<i class="fa-solid fa-server me-2 text-primary"></i>
|
||||||
<gui-grid-column header="perm Name" field="ip">
|
<strong>{{value}}</strong>
|
||||||
<ng-template let-value="item.ip" let-item="item" let-index="index">
|
</div>
|
||||||
{{value}}
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</gui-grid-column>
|
</gui-grid-column>
|
||||||
<gui-grid-column header="perm Name" field="mac">
|
<gui-grid-column header="IP Address" field="ip">
|
||||||
|
<ng-template let-value="item.ip" let-item="item" let-index="index">
|
||||||
|
<c-badge color="secondary">{{value}}</c-badge>
|
||||||
|
</ng-template>
|
||||||
|
</gui-grid-column>
|
||||||
|
<gui-grid-column header="MAC Address" field="mac">
|
||||||
<ng-template let-value="item.mac" let-item="item" let-index="index">
|
<ng-template let-value="item.mac" let-item="item" let-index="index">
|
||||||
{{value}}
|
<small class="text-muted">{{value}}</small>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</gui-grid-column>
|
</gui-grid-column>
|
||||||
</gui-grid>
|
</gui-grid>
|
||||||
<br />
|
</div>
|
||||||
</c-input-group>
|
</c-card-body>
|
||||||
<hr />
|
</c-card>
|
||||||
</c-modal-body>
|
</c-modal-body>
|
||||||
|
|
||||||
<c-modal-footer>
|
<c-modal-footer class="bg-light d-flex justify-content-between">
|
||||||
<button *ngIf="NewMemberRows.length!= 0" (click)="add_new_members()" cButton color="primary">Add {{
|
<div>
|
||||||
NewMemberRows.length }}</button>
|
<small class="text-muted">Select devices from the list above to add them to the group</small>
|
||||||
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary">
|
</div>
|
||||||
Close
|
<div>
|
||||||
|
<button *ngIf="NewMemberRows.length > 0" (click)="add_new_members()" cButton color="success">
|
||||||
|
<i class="fa-solid fa-plus me-1"></i>Add {{NewMemberRows.length}} Device(s)
|
||||||
</button>
|
</button>
|
||||||
|
<button (click)="NewMemberModalVisible=!NewMemberModalVisible" cButton color="secondary" class="ms-2">
|
||||||
|
<i class="fa-solid fa-times me-1"></i>Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</c-modal-footer>
|
</c-modal-footer>
|
||||||
</c-modal>
|
</c-modal>
|
||||||
|
|
||||||
|
|
@ -176,3 +268,199 @@
|
||||||
</button>
|
</button>
|
||||||
</c-modal-footer>
|
</c-modal-footer>
|
||||||
</c-modal>
|
</c-modal>
|
||||||
|
<c-modal #UserManagementModal backdrop="static" size="xl" [(visible)]="UserManagementModalVisible" id="UserManagementModal">
|
||||||
|
<c-modal-header>
|
||||||
|
<h5 cModalTitle><i class="fa-solid fa-users-gear me-2"></i>User Permissions - {{selectedGroup?.name}}</h5>
|
||||||
|
<button [cModalToggle]="UserManagementModal.id" cButtonClose></button>
|
||||||
|
</c-modal-header>
|
||||||
|
<c-modal-body>
|
||||||
|
<c-card class="mb-3">
|
||||||
|
<c-card-header class="bg-light">
|
||||||
|
<h6 class="mb-0"><i class="fa-solid fa-user-plus me-2"></i>Add User Permission</h6>
|
||||||
|
</c-card-header>
|
||||||
|
<c-card-body>
|
||||||
|
<c-row class="g-3">
|
||||||
|
<c-col md="4">
|
||||||
|
<div class="search-select-wrapper">
|
||||||
|
<label class="search-label">User</label>
|
||||||
|
<input cFormControl
|
||||||
|
[(ngModel)]="userSearch"
|
||||||
|
(input)="filterUsers($event)"
|
||||||
|
(focus)="showUserDropdown = true"
|
||||||
|
(blur)="hideUserDropdown()"
|
||||||
|
placeholder="Search and select user..."
|
||||||
|
class="search-input compact-select"
|
||||||
|
autocomplete="off" />
|
||||||
|
<div *ngIf="showUserDropdown && filteredUsers.length > 0" class="search-dropdown">
|
||||||
|
<div *ngFor="let user of filteredUsers"
|
||||||
|
class="search-option"
|
||||||
|
(mousedown)="selectUser(user)">
|
||||||
|
{{user.username}} ({{user.first_name}} {{user.last_name}})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="showUserDropdown && filteredUsers.length === 0 && userSearch" class="search-no-results">
|
||||||
|
No users found
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</c-col>
|
||||||
|
<c-col md="4">
|
||||||
|
<div class="search-select-wrapper">
|
||||||
|
<label class="search-label">Permission</label>
|
||||||
|
<input cFormControl
|
||||||
|
[(ngModel)]="permissionSearch"
|
||||||
|
(input)="filterPermissions($event)"
|
||||||
|
(focus)="showPermissionDropdown = true"
|
||||||
|
(blur)="hidePermissionDropdown()"
|
||||||
|
placeholder="Search and select permission..."
|
||||||
|
class="search-input compact-select"
|
||||||
|
autocomplete="off" />
|
||||||
|
<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 md="4" class="d-flex align-items-end">
|
||||||
|
<button cButton color="success" (click)="addUserPermission()" [disabled]="!selectedUser || !selectedPermission">
|
||||||
|
<i class="fa-solid fa-plus me-1"></i>Add Permission
|
||||||
|
</button>
|
||||||
|
</c-col>
|
||||||
|
</c-row>
|
||||||
|
</c-card-body>
|
||||||
|
</c-card>
|
||||||
|
<c-card>
|
||||||
|
<c-card-header class="bg-light">
|
||||||
|
<h6 class="mb-0"><i class="fa-solid fa-list-check me-2"></i>Current Permissions ({{selectedGroup?.assigned_users?.length || 0}} users)</h6>
|
||||||
|
</c-card-header>
|
||||||
|
<c-card-body class="p-0">
|
||||||
|
<div *ngIf="selectedGroup?.assigned_users?.length === 0" class="text-center p-4 text-muted">
|
||||||
|
<i class="fa-solid fa-users-slash fa-2x mb-2"></i>
|
||||||
|
<p class="mb-0">No users have permissions for this group</p>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="selectedGroup?.assigned_users?.length > 0" class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th width="40">#</th>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Permission</th>
|
||||||
|
<th width="200">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let user of selectedGroup.assigned_users; let i = index">
|
||||||
|
<td class="text-muted">{{i + 1}}</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="fa-solid fa-user me-2 text-primary"></i>
|
||||||
|
<strong>{{user.username}}</strong>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{user.first_name}} {{user.last_name}}</td>
|
||||||
|
<td>
|
||||||
|
<c-badge [color]="getPermissionColor(user.perm_name)">{{user.perm_name}}</c-badge>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button cButton color="warning" size="sm" variant="outline"
|
||||||
|
(click)="editUserPermission(user)" title="Change Permission"
|
||||||
|
[disabled]="selectedGroup.id === 1 && user.username === 'mikrowizard'">
|
||||||
|
<i class="fa-solid fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button cButton color="danger" size="sm" variant="outline"
|
||||||
|
(click)="removeUserPermission(user)" title="Remove Permission"
|
||||||
|
[disabled]="selectedGroup.id === 1 && user.username === 'mikrowizard'">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</c-card-body>
|
||||||
|
</c-card>
|
||||||
|
</c-modal-body>
|
||||||
|
<c-modal-footer>
|
||||||
|
<button [cModalToggle]="UserManagementModal.id" cButton color="secondary">
|
||||||
|
<i class="fa-solid fa-times me-1"></i>Close
|
||||||
|
</button>
|
||||||
|
</c-modal-footer>
|
||||||
|
</c-modal>
|
||||||
|
|
||||||
|
<c-modal #EditPermissionModal backdrop="static" [(visible)]="EditPermissionModalVisible" id="EditPermissionModal">
|
||||||
|
<c-modal-header>
|
||||||
|
<h5 cModalTitle>Change Permission for {{editingUser?.username}}</h5>
|
||||||
|
<button [cModalToggle]="EditPermissionModal.id" cButtonClose></button>
|
||||||
|
</c-modal-header>
|
||||||
|
<c-modal-body>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Current Permission: <c-badge [color]="getPermissionColor(editingUser?.perm_name)">{{editingUser?.perm_name}}</c-badge></label>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">New Permission</label>
|
||||||
|
<select cSelect [(ngModel)]="newPermissionId" class="form-select">
|
||||||
|
<option value="">Select Permission...</option>
|
||||||
|
<option *ngFor="let perm of availablePermissions" [value]="perm.id">{{perm.name}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</c-modal-body>
|
||||||
|
<c-modal-footer>
|
||||||
|
<button cButton color="primary" (click)="updateUserPermission()" [disabled]="!newPermissionId">
|
||||||
|
<i class="fa-solid fa-save me-1"></i>Update
|
||||||
|
</button>
|
||||||
|
<button [cModalToggle]="EditPermissionModal.id" cButton color="secondary">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</c-modal-footer>
|
||||||
|
</c-modal>
|
||||||
|
|
||||||
|
<c-modal #RemovePermissionModal backdrop="static" [(visible)]="RemovePermissionModalVisible" id="RemovePermissionModal">
|
||||||
|
<c-modal-header>
|
||||||
|
<h5 cModalTitle>Remove Permission</h5>
|
||||||
|
<button [cModalToggle]="RemovePermissionModal.id" cButtonClose></button>
|
||||||
|
</c-modal-header>
|
||||||
|
<c-modal-body>
|
||||||
|
<div class="text-center">
|
||||||
|
<i class="fa-solid fa-exclamation-triangle fa-3x text-warning mb-3"></i>
|
||||||
|
<p>Are you sure you want to remove <strong>{{removingUser?.username}}</strong>'s permission from group <strong>{{selectedGroup?.name}}</strong>?</p>
|
||||||
|
<p class="text-muted small">This action cannot be undone.</p>
|
||||||
|
</div>
|
||||||
|
</c-modal-body>
|
||||||
|
<c-modal-footer>
|
||||||
|
<button cButton color="danger" (click)="confirmRemovePermission()">
|
||||||
|
<i class="fa-solid fa-trash me-1"></i>Remove
|
||||||
|
</button>
|
||||||
|
<button [cModalToggle]="RemovePermissionModal.id" cButton color="secondary">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</c-modal-footer>
|
||||||
|
</c-modal>
|
||||||
|
<c-modal #FirmwareConfirmModal backdrop="static" [(visible)]="FirmwareConfirmModalVisible" id="FirmwareConfirmModal">
|
||||||
|
<c-modal-header>
|
||||||
|
<h5 cModalTitle>Confirm Firmware {{firmwareAction | titlecase}}</h5>
|
||||||
|
<button [cModalToggle]="FirmwareConfirmModal.id" cButtonClose></button>
|
||||||
|
</c-modal-header>
|
||||||
|
<c-modal-body>
|
||||||
|
<div class="text-center">
|
||||||
|
<i class="fa-solid fa-exclamation-triangle fa-3x text-warning mb-3"></i>
|
||||||
|
<p>Are you sure you want to <strong>{{firmwareAction}}</strong> firmware for all devices in group <strong>{{selectedGroupForFirmware?.name}}</strong>?</p>
|
||||||
|
<p class="text-muted small">This action will affect all devices in this group and may take some time to complete.</p>
|
||||||
|
</div>
|
||||||
|
</c-modal-body>
|
||||||
|
<c-modal-footer>
|
||||||
|
<button cButton color="primary" (click)="confirmGroupFirmwareAction()">
|
||||||
|
<i class="fa-solid fa-{{firmwareAction === 'update' ? 'upload' : 'microchip'}} me-1"></i>{{firmwareAction | titlecase}} Firmware
|
||||||
|
</button>
|
||||||
|
<button [cModalToggle]="FirmwareConfirmModal.id" cButton color="secondary">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</c-modal-footer>
|
||||||
|
</c-modal>
|
||||||
108
src/app/views/devices_group/devgroup.component.scss
Normal file
108
src/app/views/devices_group/devgroup.component.scss
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
.users-summary {
|
||||||
|
min-height: 60px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badges c-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
max-width: 80px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.users-summary {
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badges c-badge {
|
||||||
|
max-width: 60px;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
|
||||||
import { dataProvider } from "../../providers/mikrowizard/data";
|
import { dataProvider } from "../../providers/mikrowizard/data";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { loginChecker } from "../../providers/login_checker";
|
import { loginChecker } from "../../providers/login_checker";
|
||||||
|
import { formatInTimeZone } from "date-fns-tz";
|
||||||
import {
|
import {
|
||||||
GuiSearching,
|
GuiSearching,
|
||||||
GuiSelectedRow,
|
GuiSelectedRow,
|
||||||
|
|
@ -31,10 +32,12 @@ interface IUser {
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "devgroup.component.html",
|
templateUrl: "devgroup.component.html",
|
||||||
|
styleUrls: ["devgroup.component.scss"]
|
||||||
})
|
})
|
||||||
export class DevicesGroupComponent implements OnInit {
|
export class DevicesGroupComponent implements OnInit {
|
||||||
public uid: number;
|
public uid: number;
|
||||||
public uname: string;
|
public uname: string;
|
||||||
|
public tz: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private data_provider: dataProvider,
|
private data_provider: dataProvider,
|
||||||
|
|
@ -50,6 +53,7 @@ export class DevicesGroupComponent implements OnInit {
|
||||||
this.data_provider.getSessionInfo().then((res) => {
|
this.data_provider.getSessionInfo().then((res) => {
|
||||||
_self.uid = res.uid;
|
_self.uid = res.uid;
|
||||||
_self.uname = res.name;
|
_self.uname = res.name;
|
||||||
|
_self.tz = res.tz;
|
||||||
const userId = _self.uid;
|
const userId = _self.uid;
|
||||||
|
|
||||||
if (res.role != "admin") {
|
if (res.role != "admin") {
|
||||||
|
|
@ -83,6 +87,30 @@ export class DevicesGroupComponent implements OnInit {
|
||||||
id: 0,
|
id: 0,
|
||||||
name: "",
|
name: "",
|
||||||
};
|
};
|
||||||
|
public selectedGroup: any = null;
|
||||||
|
public UserManagementModalVisible: boolean = false;
|
||||||
|
public EditPermissionModalVisible: boolean = false;
|
||||||
|
public RemovePermissionModalVisible: boolean = false;
|
||||||
|
public availableUsers: any[] = [];
|
||||||
|
public availablePermissions: any[] = [];
|
||||||
|
public selectedUserId: string = "";
|
||||||
|
public selectedPermId: string = "";
|
||||||
|
public selectedUser: any = null;
|
||||||
|
public selectedPermission: any = null;
|
||||||
|
public userSearch: string = '';
|
||||||
|
public permissionSearch: string = '';
|
||||||
|
public filteredUsers: any[] = [];
|
||||||
|
public filteredPermissions: any[] = [];
|
||||||
|
public showUserDropdown: boolean = false;
|
||||||
|
public showPermissionDropdown: boolean = false;
|
||||||
|
public editingUser: any = null;
|
||||||
|
public removingUser: any = null;
|
||||||
|
public newPermissionId: string = "";
|
||||||
|
private deviceCache: { [key: number]: any[] } = {};
|
||||||
|
private loadingDevices: { [key: number]: boolean } = {};
|
||||||
|
public FirmwareConfirmModalVisible: boolean = false;
|
||||||
|
public firmwareAction: string = "";
|
||||||
|
public selectedGroupForFirmware: any = null;
|
||||||
public DefaultCurrentGroup: any = {
|
public DefaultCurrentGroup: any = {
|
||||||
array_agg: [],
|
array_agg: [],
|
||||||
created: "",
|
created: "",
|
||||||
|
|
@ -182,6 +210,9 @@ export class DevicesGroupComponent implements OnInit {
|
||||||
save_group() {
|
save_group() {
|
||||||
var _self = this;
|
var _self = this;
|
||||||
this.data_provider.update_save_group(this.currentGroup).then((res) => {
|
this.data_provider.update_save_group(this.currentGroup).then((res) => {
|
||||||
|
// Clear device cache for this group
|
||||||
|
delete this.deviceCache[this.currentGroup.id];
|
||||||
|
delete this.loadingDevices[this.currentGroup.id];
|
||||||
_self.initGridTable();
|
_self.initGridTable();
|
||||||
_self.EditGroupModalVisible = false;
|
_self.EditGroupModalVisible = false;
|
||||||
});
|
});
|
||||||
|
|
@ -237,4 +268,225 @@ export class DevicesGroupComponent implements OnInit {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
manageUsers(group: any): void {
|
||||||
|
this.selectedGroup = { ...group };
|
||||||
|
this.loadAvailableUsers();
|
||||||
|
this.loadAvailablePermissions();
|
||||||
|
this.UserManagementModalVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAvailableUsers(): void {
|
||||||
|
this.data_provider.get_users(1, 1000, "").then((res) => {
|
||||||
|
this.availableUsers = res.filter((user: any) =>
|
||||||
|
!this.selectedGroup.assigned_users.some((assignedUser: any) => assignedUser.user_id === user.id)
|
||||||
|
);
|
||||||
|
this.filteredUsers = [...this.availableUsers];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAvailablePermissions(): void {
|
||||||
|
this.data_provider.get_perms(1, 1000, "").then((res) => {
|
||||||
|
this.availablePermissions = res;
|
||||||
|
this.filteredPermissions = [...this.availablePermissions];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filterUsers(event: any): void {
|
||||||
|
const query = event.target.value.toLowerCase();
|
||||||
|
this.filteredUsers = this.availableUsers.filter((user: any) =>
|
||||||
|
user.username.toLowerCase().includes(query) ||
|
||||||
|
(user.first_name + ' ' + user.last_name).toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterPermissions(event: any): void {
|
||||||
|
const query = event.target.value.toLowerCase();
|
||||||
|
this.filteredPermissions = this.availablePermissions.filter((perm: any) =>
|
||||||
|
perm.name.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectUser(user: any): void {
|
||||||
|
this.selectedUser = user;
|
||||||
|
this.selectedUserId = user.id;
|
||||||
|
this.userSearch = user.username + ' (' + user.first_name + ' ' + user.last_name + ')';
|
||||||
|
this.showUserDropdown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectPermission(perm: any): void {
|
||||||
|
this.selectedPermission = perm;
|
||||||
|
this.selectedPermId = perm.id;
|
||||||
|
this.permissionSearch = perm.name;
|
||||||
|
this.showPermissionDropdown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideUserDropdown(): void {
|
||||||
|
setTimeout(() => this.showUserDropdown = false, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
hidePermissionDropdown(): void {
|
||||||
|
setTimeout(() => this.showPermissionDropdown = false, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
addUserPermission(): void {
|
||||||
|
if (!this.selectedUser || !this.selectedPermission) return;
|
||||||
|
|
||||||
|
console.log('Adding user permission:', {
|
||||||
|
userId: this.selectedUser.id,
|
||||||
|
permissionId: this.selectedPermission.id,
|
||||||
|
groupId: this.selectedGroup.id,
|
||||||
|
selectedUser: this.selectedUser,
|
||||||
|
selectedPermission: this.selectedPermission
|
||||||
|
});
|
||||||
|
|
||||||
|
this.data_provider.Add_user_perm(this.selectedUser.id, +this.selectedPermission.id, this.selectedGroup.id)
|
||||||
|
.then((res) => {
|
||||||
|
console.log('Add user permission response:', res);
|
||||||
|
this.initGridTable();
|
||||||
|
this.selectedUserId = "";
|
||||||
|
this.selectedPermId = "";
|
||||||
|
this.selectedUser = null;
|
||||||
|
this.selectedPermission = null;
|
||||||
|
this.userSearch = "";
|
||||||
|
this.permissionSearch = "";
|
||||||
|
// Refresh the selected group data
|
||||||
|
this.data_provider.get_devgroup_list().then((groups) => {
|
||||||
|
this.selectedGroup = groups.find((g: any) => g.id === this.selectedGroup.id);
|
||||||
|
this.loadAvailableUsers();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editUserPermission(user: any): void {
|
||||||
|
this.editingUser = { ...user };
|
||||||
|
this.newPermissionId = user.perm_id.toString();
|
||||||
|
this.EditPermissionModalVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserPermission(): void {
|
||||||
|
if (!this.newPermissionId) return;
|
||||||
|
|
||||||
|
// Remove old permission and add new one
|
||||||
|
this.data_provider.Delete_user_perm(this.editingUser.id).then(() => {
|
||||||
|
this.data_provider.Add_user_perm(this.editingUser.user_id, +this.newPermissionId, this.selectedGroup.id)
|
||||||
|
.then(() => {
|
||||||
|
this.EditPermissionModalVisible = false;
|
||||||
|
this.initGridTable();
|
||||||
|
// Refresh the selected group data
|
||||||
|
this.data_provider.get_devgroup_list().then((groups) => {
|
||||||
|
this.selectedGroup = groups.find((g: any) => g.id === this.selectedGroup.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeUserPermission(user: any): void {
|
||||||
|
this.removingUser = { ...user };
|
||||||
|
this.RemovePermissionModalVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmRemovePermission(): void {
|
||||||
|
this.data_provider.Delete_user_perm(this.removingUser.id).then(() => {
|
||||||
|
this.RemovePermissionModalVisible = false;
|
||||||
|
this.initGridTable();
|
||||||
|
// Refresh the selected group data
|
||||||
|
this.data_provider.get_devgroup_list().then((groups) => {
|
||||||
|
this.selectedGroup = groups.find((g: any) => g.id === this.selectedGroup.id);
|
||||||
|
this.loadAvailableUsers();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPermissionColor(permName: string): string {
|
||||||
|
const colorMap: { [key: string]: string } = {
|
||||||
|
'full': 'success',
|
||||||
|
'read': 'info',
|
||||||
|
'write': 'warning',
|
||||||
|
'admin': 'danger',
|
||||||
|
'test': 'secondary'
|
||||||
|
};
|
||||||
|
return colorMap[permName] || 'primary';
|
||||||
|
}
|
||||||
|
|
||||||
|
getUsersTooltip(users: any[]): string {
|
||||||
|
if (users.length === 0) return 'No users assigned';
|
||||||
|
|
||||||
|
const maxShow = 10;
|
||||||
|
const userList = users.slice(0, maxShow).map((user, index) =>
|
||||||
|
`• ${user.username} (${user.perm_name})`
|
||||||
|
).join('\n');
|
||||||
|
|
||||||
|
return users.length > maxShow
|
||||||
|
? `${userList}\n━━━━━━━━━━━━━━━━\n+${users.length - maxShow} more users`
|
||||||
|
: userList;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDevicesTooltip(group: any): string {
|
||||||
|
if (group.id === 1) return 'All devices in the system';
|
||||||
|
if (!group.array_agg || group.array_agg[0] === null) return 'No devices assigned';
|
||||||
|
|
||||||
|
// Check if data is cached
|
||||||
|
if (this.deviceCache[group.id]) {
|
||||||
|
const devices = this.deviceCache[group.id];
|
||||||
|
const maxShow = 10;
|
||||||
|
const deviceList = devices.slice(0, maxShow).map(device =>
|
||||||
|
`• ${device.name} (${device.ip})`
|
||||||
|
).join('\n');
|
||||||
|
|
||||||
|
return devices.length > maxShow
|
||||||
|
? `${deviceList}\n━━━━━━━━━━━━━━━━\n+${devices.length - maxShow} more devices`
|
||||||
|
: deviceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already loading
|
||||||
|
if (this.loadingDevices[group.id]) {
|
||||||
|
return 'Loading devices...';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading
|
||||||
|
this.loadingDevices[group.id] = true;
|
||||||
|
this.data_provider.get_devgroup_members(group.id).then((devices) => {
|
||||||
|
this.deviceCache[group.id] = devices;
|
||||||
|
this.loadingDevices[group.id] = false;
|
||||||
|
}).catch(() => {
|
||||||
|
this.loadingDevices[group.id] = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return 'Loading devices...';
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCreateTime(dateString: string): string {
|
||||||
|
if (!dateString || !this.tz) return dateString;
|
||||||
|
return formatInTimeZone(
|
||||||
|
dateString.split(".")[0] + ".000Z",
|
||||||
|
this.tz,
|
||||||
|
"yyyy-MM-dd HH:mm:ss XXX"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupFirmwareAction(group: any, action: string): void {
|
||||||
|
this.selectedGroupForFirmware = group;
|
||||||
|
this.firmwareAction = action;
|
||||||
|
this.FirmwareConfirmModalVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmGroupFirmwareAction(): void {
|
||||||
|
if (!this.selectedGroupForFirmware) return;
|
||||||
|
|
||||||
|
this.data_provider.group_firmware_action(this.selectedGroupForFirmware.id, this.firmwareAction)
|
||||||
|
.then((res) => {
|
||||||
|
if ("error" in res) {
|
||||||
|
console.error('Firmware action failed:', res.error);
|
||||||
|
} else {
|
||||||
|
const actionText = this.firmwareAction === 'update' ? 'Update' : 'Upgrade';
|
||||||
|
console.log(`${actionText} firmware initiated for group: ${this.selectedGroupForFirmware.name}`);
|
||||||
|
}
|
||||||
|
this.FirmwareConfirmModalVisible = false;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Firmware action error:', error);
|
||||||
|
this.FirmwareConfirmModalVisible = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { NgModule } from "@angular/core";
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AlertModule,
|
||||||
ButtonGroupModule,
|
ButtonGroupModule,
|
||||||
ButtonModule,
|
ButtonModule,
|
||||||
CardModule,
|
CardModule,
|
||||||
|
|
@ -9,15 +10,19 @@ import {
|
||||||
GridModule,
|
GridModule,
|
||||||
CollapseModule,
|
CollapseModule,
|
||||||
ModalModule,
|
ModalModule,
|
||||||
|
TooltipModule,
|
||||||
|
ListGroupModule,
|
||||||
} from "@coreui/angular";
|
} from "@coreui/angular";
|
||||||
import { DevicesGroupRoutingModule } from "./devgroup-routing.module";
|
import { DevicesGroupRoutingModule } from "./devgroup-routing.module";
|
||||||
import { DevicesGroupComponent } from "./devgroup.component";
|
import { DevicesGroupComponent } from "./devgroup.component";
|
||||||
import { GuiGridModule } from "@generic-ui/ngx-grid";
|
import { GuiGridModule } from "@generic-ui/ngx-grid";
|
||||||
import { BadgeModule } from "@coreui/angular";
|
import { BadgeModule } from "@coreui/angular";
|
||||||
import { FormsModule } from "@angular/forms";
|
import { FormsModule } from "@angular/forms";
|
||||||
|
import { MatMenuModule } from "@angular/material/menu";
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
DevicesGroupRoutingModule,
|
DevicesGroupRoutingModule,
|
||||||
|
AlertModule,
|
||||||
CardModule,
|
CardModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GridModule,
|
GridModule,
|
||||||
|
|
@ -29,6 +34,9 @@ import { FormsModule } from "@angular/forms";
|
||||||
CollapseModule,
|
CollapseModule,
|
||||||
ModalModule,
|
ModalModule,
|
||||||
BadgeModule,
|
BadgeModule,
|
||||||
|
TooltipModule,
|
||||||
|
MatMenuModule,
|
||||||
|
ListGroupModule,
|
||||||
],
|
],
|
||||||
declarations: [DevicesGroupComponent],
|
declarations: [DevicesGroupComponent],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue