mikrofront/src/app/views/devices/devices.component.html
sepehr 5822fb5d1d feat: major device management enhancements
- Add direct web access button for device WebFig
- Add web proxy-based access with auto-login via MikroWizard
- Fix updatable/upgradable filters and add more filter options
- Redesign devices table UI with improved UX
- Add bulk scan feature with CSV import capability
- Add parallel scanning functionality
- Add upgrade firmware feature
- Add scan history button with enhanced report viewing
2025-10-16 17:33:07 +03:00

656 lines
No EOL
32 KiB
HTML

<c-row>
<c-col xs>
<c-card class="mb-4">
<c-card-header>
<c-row>
<c-col xs [lg]="3">
Devices
</c-col>
<c-col xs [lg]="9">
<h6 style="text-align: right;">
<button cButton color="success" (click)="openAddDeviceModal()" class="mx-1"
size="sm" style="color: #fff;"><i class="fa-solid fa-plus"></i> Bulk Add </button>
<button cButton color="dark" (click)="scanwizard(1,'')" [cModalToggle]="ScannerModal.id" class="mx-1"
size="sm" style="color: #fff;"><i class="fa-solid fa-magnifying-glass"></i> Scan</button>
<button cButton color="primary" (click)="show_exec()" class="mx-1"
size="sm" style="color: #fff;"><i class="fa-solid fa-history"></i> History</button>
</h6>
</c-col>
</c-row>
</c-card-header>
<c-card-body>
<c-row>
<c-col [lg]="9">
<button cButton color="danger" class="mx-1" size="sm" style="color: #fff;" (click)="filterUpdatable()">{{updates.length}} Updatable
</button>
<button cButton color="warning" class="mx-1" size="sm" style="color: #fff;" (click)="filterUpgradable()">{{upgrades.length}}
Upgradable</button>
<button cButton color="secondary" class="mx-1" size="sm" (click)="clearFilter()">Clear Filter</button>
</c-col>
<c-col [lg]="3">
<c-input-group *ngIf="groups.length>0">
<span cInputGroupText>Group</span>
<select [(ngModel)]="selected_group" (change)="groupselected($event)" cSelect>
<option value="0" [selected]="selected_group == 0">Select a group</option>
<option *ngFor="let g of groups" [value]="g.id" [selected]=" selected_group == g.id ">{{g.name}}
</option>
</select>
</c-input-group>
</c-col>
</c-row>
<gui-grid #grid [rowClass]="rowClass" [source]="source" [searching]="searching" [paging]="paging"
[columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [rowSelection]="rowSelection"
(selectedRows)="onSelectedRows($event)" [autoResizeWidth]=true>
<gui-grid-column header="Name" field="name">
<ng-template let-value="item.name" let-item="item" let-index="index">
<button cButton size="sm" variant="ghost" color="primary" (click)="webAccess(item)"
style="padding: 2px 4px; margin-right: 5px; border: none;" cTooltip="Web Access">
<i class="fas fa-globe" style="font-size: 12px;"></i>
</button>
<img *ngIf="item.status=='updating'" width="20px" src="assets/img/loading.svg" />
<i *ngIf="item.status=='updated'" cTooltip="Tooltip text"
style="color: green; margin-right: 3px;font-size: .7em;" class="fa-solid fa-check"></i>
<i *ngIf="item.status=='failed'" cTooltip="Update failed"
style="color: red; margin-right: 3px;font-size: .7em;" class="fa-solid fa-x"></i>
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="CPU Type" field="arch">
<ng-template let-value="item.arch" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Firmware" field="current_firmware">
<ng-template let-value="item.current_firmware" let-item="item" let-index="index">
<div>{{value}}</div>
<i *ngIf="item.update_availble" cTooltip="Firmware Update availble"
class="fa-solid fa-up-long text-primary mx-1"></i>
<i *ngIf="item.upgrade_availble" cTooltip="Device Firmware not Upgraded"
class="fa-solid fa-microchip text-danger mx-1"></i>
</ng-template>
</gui-grid-column>
<gui-grid-column header="IP Address" field="ip">
<ng-template let-value="item.ip" let-item="item" let-index="index">
{{value}}
</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">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="License" field="license" [enabled]="false">
<ng-template let-value="item.license" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Interface" field="interface" [enabled]="false">
<ng-template let-value="item.interface" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created" [enabled]="false">
<ng-template let-value="item.created" let-item="item.id" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Uptime" field="uptime">
<ng-template let-value="item.uptime" let-item="item" let-index="index">
{{value}}
</ng-template>
</gui-grid-column>
<gui-grid-column header="Created" field="created" [enabled]="false">
<ng-template let-value="item.created" let-item="item.id" let-index="index">
{{value}}
</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_detail(item)"
style="border: none;padding: 4px 7px;"><i class="fa-regular fa-eye"></i><small> Details</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)="single_device_action(item,'edit')" style="padding: 4px 7px;"
cListGroupItem><i class="fa-solid fa-pencil"></i><small>
Edit Device</small></button>
<button size="sm" (click)="single_device_action(item,'firmware')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-magnifying-glass"></i><small>
Check Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'update')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-upload"></i><small>
Update Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'upgrade')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-microchip"></i><small>
Upgrade Firmware</small></button>
<button size="sm" (click)="single_device_action(item,'devlogs')" style="padding: 4px 7px;"
cListGroupItem><i class="fa-regular fa-rectangle-list"></i><small>
Device Logs</small></button>
<button size="sm" (click)="single_device_action(item,'logauth')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-regular fa-clock"></i><small>
Show Auth Logs</small></button>
<button size="sm" (click)="single_device_action(item,'logacc')" style="padding: 4px 7px;"
cListGroupItem><i class="text-primary fa-solid fa-table-list"></i><small>
Show Acc Logs</small></button>
<button size="sm" (click)="single_device_action(item,'backup')" style="padding: 4px 7px;"
cListGroupItem><i class="text-success fa-solid fa-database"></i><small>
Show Backups</small></button>
<button size="sm" (click)="single_device_action(item,'delete')" style="padding: 4px 7px;"
cListGroupItem><i class="text-danger fa-solid fa-trash"></i><small>
Delete Device</small></button>
</div>
</mat-menu>
</ng-template>
</gui-grid-column>
</gui-grid>
<c-navbar *ngIf="rows.length!= 0" class="bg-light" colorScheme="light" expand="lg">
<c-container [fluid]="true">
<a cNavbarBrand href="javascript:;">
Batch Action :
</a>
<button [cNavbarToggler]="collapseRef"></button>
<div #collapseRef="cCollapse" [navbar]="true" cCollapse>
<c-navbar-nav class="me-auto mb-2 mb-lg-0">
<c-nav-item>
<c-dropdown variant="nav-item" [popper]="false">
<a cDropdownToggle cNavLink>Select</a>
<ul cDropdownMenu dark>
<li><button cDropdownItem (click)="ConfirmAction='checkfirm';ConfirmModalVisible=true">Check
Firmware</button></li>
<li><button cDropdownItem
(click)="ConfirmAction='update';ConfirmModalVisible=true">Update</button></li>
<li><button cDropdownItem
(click)="ConfirmAction='upgrade';ConfirmModalVisible=true">Upgrade</button></li>
<!-- <li><button cDropdownItem>Update and Upgrade</button></li> -->
</ul>
</c-dropdown>
</c-nav-item>
</c-navbar-nav>
</div>
</c-container>
</c-navbar>
</c-card-body>
</c-card>
</c-col>
</c-row>
<c-modal #ScannerModal [visible]="scanwizard_modal" (visibleChange)="handleScanwizard_modal($event)" backdrop="static"
id="ScannerModal">
<c-modal-header>
<h5 cModalTitle>Scanner Wizard</h5>
<button [cModalToggle]="ScannerModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
<div *ngIf="scanwizard_step==1" class="mb-5" style="text-align: center;">
<h5 class="mb-5">Please select searching method</h5>
<button cButton color="info" (click)="scanwizard(2,'chip')" [disabled]="true" class="mx-1" size="lg"><img
width="100px" src="assets/img/chip.png" /><br />Layer2 Scan</button>
<button cButton color="info" (click)="scanwizard(2,'ip')" class="mx-1" size="lg"><img width="100px"
src="assets/img/tcpip.png" /><br />TCP/IP Scan</button>
</div>
<div *ngIf="scanwizard_step==2 && scan_type=='ip'" class="mb-2" style="text-align: center;">
<h5 class="mb-5">Please Provide needed information</h5>
<c-input-group class="mb-3">
<span cInputGroupText>Start IP</span>
<input aria-label="start" cFormControl [(ngModel)]="ip_scanner['start']" [valid]="checkvalid('start')"
placeholder="192.168.1.1" />
<span cInputGroupText>End IP</span>
<input aria-label="end" cFormControl [(ngModel)]="ip_scanner['end']" [valid]="checkvalid('end')"
placeholder="192.168.1.255" />
</c-input-group>
<c-input-group class="mb-3">
<span cInputGroupText>Username</span>
<input aria-label="start" cFormControl [(ngModel)]="ip_scanner['user']" placeholder="Default username" />
</c-input-group>
<c-input-group class="mb-3">
<span cInputGroupText>Password</span>
<input aria-label="end" cFormControl [(ngModel)]="ip_scanner['password']" placeholder="********" />
</c-input-group>
<c-input-group class="mb-3">
<span cInputGroupText>Port</span>
<input aria-label="end" cFormControl [valid]="checkvalid('port')" [(ngModel)]="ip_scanner['port']"
placeholder="8728" />
</c-input-group>
<button cButton color="info" (click)="scanwizard(3,'ip')" class="mx-1" size="lg">Start Scanning</button>
</div>
<div class="mwand" *ngIf="scanwizard_step==3">
<svg viewBox="0 0 203 148.27">
<g id="wand">
<g class="cls-1">
<path d="M194.63,152.18v-7.76C194.6,147,194.6,149.59,194.63,152.18Z" transform="translate(-10 -31.06)" />
</g>
<rect class="cls-2" x="5.07" y="129.83" width="117.08" height="17.1"
transform="translate(-77.95 30.6) rotate(-35.06)" />
<rect class="cls-3" x="106.38" y="88.26" width="32.89" height="17.1"
transform="translate(-43.33 57.07) rotate(-35.06)" />
<ellipse class="cls-4" cx="136.21" cy="87.42" rx="3.29" ry="8.55"
transform="translate(-35.5 63.06) rotate(-35.06)" />
<ellipse class="cls-2" cx="15.6" cy="172.07" rx="3.29" ry="8.55"
transform="translate(-106.02 9.13) rotate(-35.06)" />
<ellipse class="cls-3" cx="109.5" cy="106.16" rx="3.29" ry="8.55"
transform="translate(-51.12 51.12) rotate(-35.06)" />
<path class="cls-5" d="M138.71,85.25s4.26,6.06,2.68,9L20.15,179.32s-3.27.49-7.53-5.57Z"
transform="translate(-10 -31.06)" />
</g>
<g id="stars">
<g id="star1">
<polygon class="cls-6"
points="142.22 4.88 138.59 13.13 147.13 17.7 137.94 19.78 139.9 28.82 132.07 23.15 125.96 29.86 125.38 20.71 115.81 20.03 122.93 14.3 117.1 6.74 126.55 8.74 128.85 0 133.51 8.22 142.22 4.88" />
<polygon class="cls-7"
points="142.29 4.89 136.56 13.87 144.96 17.35 136.17 18.98 138.3 26.2 131.33 20.74 125.88 29.85 132.06 23.11 139.91 28.82 137.95 19.81 147.12 17.74 138.59 13.11 142.29 4.89" />
</g>
<g id="star2">
<polygon class="cls-6"
points="166.3 14.45 165.13 17.09 167.87 18.55 164.93 19.22 165.55 22.12 163.04 20.3 161.09 22.45 160.9 19.52 157.83 19.3 160.11 17.46 158.25 15.04 161.27 15.68 162.01 12.88 163.51 15.52 166.3 14.45" />
<polygon class="cls-7"
points="166.32 14.45 164.48 17.33 167.18 18.44 164.36 18.96 165.04 21.28 162.81 19.53 161.06 22.45 163.04 20.29 165.56 22.12 164.93 19.23 167.87 18.57 165.13 17.08 166.32 14.45" />
</g>
<g id="star3">
<polygon class="cls-6"
points="202.01 38.12 194.78 46.34 203 54.75 191.61 53.79 190.56 64.97 183.57 55.54 174.05 61.06 176.73 50.27 165.91 45.98 176.24 41.95 172.26 31.08 182.46 36.84 188.33 27.58 190.71 38.8 202.01 38.12" />
<polygon class="cls-7"
points="202.08 38.15 192.17 46.45 200.62 53.55 189.85 52.21 189.67 61.34 183.6 52.48 173.96 61.03 183.58 55.49 190.57 64.97 191.6 53.83 202.97 54.79 194.8 46.31 202.08 38.15" />
</g>
<g id="star4">
<polygon class="cls-6"
points="155.07 63.05 153.01 67.75 157.87 70.35 152.64 71.52 153.75 76.67 149.3 73.44 145.83 77.25 145.5 72.05 140.06 71.67 144.1 68.41 140.79 64.11 146.16 65.25 147.47 60.28 150.13 64.95 155.07 63.05" />
<polygon class="cls-7"
points="155.11 63.06 151.86 68.17 156.63 70.14 151.63 71.07 152.84 75.17 148.88 72.07 145.78 77.25 149.29 73.42 153.76 76.67 152.65 71.54 157.86 70.36 153.01 67.73 155.11 63.06" />
</g>
</g>
</svg>
<h5>{{scanwizard_prompt}}</h5>
</div>
</c-modal-body>
<c-modal-footer>
<small *ngIf="scan_type=='ip'">Empty username and password means system default
configuration</small>
</c-modal-footer>
</c-modal>
<c-modal #ExecutedDataModal backdrop="static" size="xl" [(visible)]="ExecutedDataModalVisible" id="ExecutedDataModal">
<c-modal-header>
<h5 cModalTitle>Task History</h5>
<button (click)="ExecutedDataModalVisible=!ExecutedDataModalVisible" cButtonClose></button>
</c-modal-header>
<c-modal-body style="min-height: 400px; max-height: 70vh; overflow-y: auto;">
<div class="mb-3">
<c-input-group>
<span cInputGroupText>Filter by Type</span>
<select [(ngModel)]="selectedTaskType" (change)="filterByTaskType()" cSelect>
<option value="all">All Tasks</option>
<option value="ip-scan">IP Scan</option>
<option value="bulk-add">Bulk Add</option>
</select>
</c-input-group>
</div>
<gui-grid [autoResizeWidth]="true" *ngIf="ExecutedDataModalVisible" [searching]="searching"
[source]="filteredExecutedData" [columnMenu]="columnMenu" [sorting]="sorting" [infoPanel]="infoPanel" [paging]="paging">
<gui-grid-column header="Type" field="task_type" [width]="100">
<ng-template let-value="item['task_type']" let-item="item" let-index="index">
<i *ngIf="item.task_type === 'ip-scan'" class="fas fa-search" style="margin-right: 3px; color: #0d6efd;"></i>
<i *ngIf="item.task_type === 'bulk-add'" class="fas fa-plus-circle" style="margin-right: 3px; color: #198754;"></i>
<span style="font-size: 11px;">{{getTaskTypeLabel(value)}}</span>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Time" field="time" [width]="220">
<ng-template let-value="value" let-item="item" let-index="index">
<div style="font-size: 10px; line-height: 1.3;">
<div><strong>Start:</strong> {{item.started}}</div>
<div><strong>End:</strong> {{item.ended}}</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Details" field="details">
<ng-template let-value="value" let-item="item" let-index="index">
<div *ngIf="item.task_type === 'ip-scan'" style="font-size: 12px; line-height: 1.3;">
<div>{{item.start_ip}} - {{item.end_ip}}</div>
<div>User: {{item.username}}</div>
</div>
<div *ngIf="item.task_type === 'bulk-add'" style="font-size: 12px; line-height: 1.3;">
<div>{{item.device_count}} devices</div>
<div style="word-break: break-all;">{{item.task_id}}</div>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Results" field="results" align="center" [width]="70">
<ng-template let-value="value" let-item="item" let-index="index">
<div style="font-size: 11px;">
<span style="color: green;">✓ {{item.success_count}}</span>
<span *ngIf="item.failed_count > 0" style="color: red; margin-left: 5px;">✗ {{item.failed_count}}</span>
</div>
</ng-template>
</gui-grid-column>
<gui-grid-column header="Actions" field="actions" align="center" [width]="160">
<ng-template let-value="value" let-item="item" let-index="index">
<button (click)="showTaskDetails(item)" color="info" size="sm" cButton class="me-1">
<i class="fas fa-eye"></i> Details
</button>
<button (click)="exportToCsv(item.result)" color="primary" size="sm" cButton>
<i class="fas fa-download"></i> CSV
</button>
</ng-template>
</gui-grid-column>
</gui-grid>
</c-modal-body>
<c-modal-footer>
<button (click)="ExecutedDataModalVisible=!ExecutedDataModalVisible" cButton color="secondary">
Close
</button>
</c-modal-footer>
</c-modal>
<c-modal #ConfirmModal backdrop="static" [(visible)]="ConfirmModalVisible" id="runConfirmModal">
<c-modal-header>
<h6 cModalTitle>Please Confirm Action </h6>
<button [cModalToggle]="ConfirmModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body>
<span *ngIf="ConfirmAction=='checkfirm'">Are you sure that You want to Check firmware of selected devices for
update?</span>
<span *ngIf="ConfirmAction=='update'">Are you sure that You want to <code>update firmware</code> of selected
devices?</span>
<span *ngIf="ConfirmAction=='upgrade'">Are you sure that You want to <code>upgrade firmware</code> of selected
devices?</span>
<span *ngIf="ConfirmAction=='reboot'">Are you sure that You want to <code>reboot</code> the selected
devices?</span>
<ng-container *ngIf="ConfirmAction=='delete'">
Are you sure that You want to<code>Delete Device {{selected_device.name}} ?</code><br />
<hr>
<p class="text-danger">
All Related Configuration will be deleted/Modified :<br /><br />
* User Permision Related to this Device<br />
* Device Groups including this Device<br />
* All Logs related to this device<br />
</p>
</ng-container>
</c-modal-body>
<c-modal-footer>
<button *ngIf="ConfirmAction=='checkfirm'" (click)="check_firmware()" cButton color="danger">
Yes
</button>
<button *ngIf="ConfirmAction=='update'" (click)="update_firmware()" cButton color="danger">
Yes
</button>
<button *ngIf="ConfirmAction=='upgrade'" (click)="upgrade_firmware()" cButton color="danger">
Yes
</button>
<button *ngIf="ConfirmAction=='reboot'" (click)="upgrade_firmware()" cButton color="danger">
Yes
</button>
<button *ngIf="ConfirmAction=='delete'" (click)="delete_device()" cButton color="danger">
Yes,Delete Device
</button>
<button cButton [cModalToggle]="ConfirmModal.id" color="info">
Cancel
</button>
</c-modal-footer>
</c-modal>
<c-modal #EditDevModal backdrop="static" [(visible)]="EditDevModalVisible" id="EditDevModal">
<c-modal-header>
<h6 cModalTitle>Editing Device</h6>
<button [cModalToggle]="EditDevModal.id" cButtonClose></button>
</c-modal-header>
<c-modal-body *ngIf="EditDevModalVisible">
<c-input-group class="mb-3">
<span cInputGroupText>Username</span>
<input aria-label="start" [(ngModel)]="selected_device['editform']['user_name']" cFormControl
placeholder=" username" />
</c-input-group>
<c-input-group class="mb-3">
<span cInputGroupText>Password</span>
<input aria-label="start" [type]="show_pass ? 'text' : 'password'"
[(ngModel)]="selected_device['editform']['password']" cFormControl placeholder=" username" />
<button cButton *ngIf="!ispro" (click)="show_pass=!show_pass" color="secondary" variant="outline">
<i *ngIf="show_pass" class="fa-solid fa-eye"></i>
<i *ngIf="!show_pass" class="fa-solid fa-eye-slash"></i>
</button>
<button cButton *ngIf="ispro" (click)="get_device_pass()" color="secondary" variant="outline">
<i *ngIf="show_pass" class="fa-solid fa-eye"></i>
<i *ngIf="!show_pass" class="fa-solid fa-eye-slash"></i>
</button>
</c-input-group>
<c-input-group class="mb-3">
<span cInputGroupText>ip</span>
<input aria-label="start" [(ngModel)]="selected_device['editform']['ip']" cFormControl
placeholder="Default username" />
</c-input-group>
<c-input-group class="mb-3">
<span cInputGroupText>peer ip</span>
<select aria-label="Default select example" cFormControl [(ngModel)]="selected_device['editform']['peer_ip']"
cSelect>
<option *ngFor="let o of selected_device['editform']['ips']" [value]="o">{{o}}</option>
</select>
</c-input-group>
</c-modal-body>
<c-modal-footer>
<button cButton (click)="save_device()" color="danger">
Save
</button>
<button cButton [cModalToggle]="EditDevModal.id" color="info">
Cancel
</button>
</c-modal-footer>
</c-modal>
<!-- Add Device Modal -->
<c-modal #AddDeviceModal backdrop="static" size="lg" [(visible)]="addDeviceModalVisible" id="AddDeviceModal">
<c-modal-header>
<h5 cModalTitle>Add Devices from CSV</h5>
<button cButtonClose (click)="closeAddDeviceModal()"></button>
</c-modal-header>
<c-modal-body>
<div *ngIf="addDeviceStep === 1">
<h6>Upload CSV File</h6>
<p>Please upload a CSV file containing device information with columns: IP Address, Username, Password, API Port</p>
<input type="file" accept=".csv" (change)="onFileSelected($event)" class="form-control mb-3">
<div *ngIf="csvPreview.length > 0">
<h6>Preview (First 3 rows):</h6>
<table class="table table-sm table-bordered">
<thead>
<tr>
<th *ngFor="let header of csvHeaders">{{header}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of csvPreview">
<td *ngFor="let cell of row">{{cell}}</td>
</tr>
</tbody>
</table>
<h6>Column Mapping:</h6>
<div class="row">
<div class="col-md-6">
<label>IP Address Column:</label>
<select [(ngModel)]="columnMapping.ip" class="form-select">
<option value="">Select Column</option>
<option *ngFor="let header of csvHeaders; let i = index" [value]="i">{{header}}</option>
</select>
</div>
<div class="col-md-6">
<label>Username Column:</label>
<select [(ngModel)]="columnMapping.username" class="form-select">
<option value="">Select Column</option>
<option *ngFor="let header of csvHeaders; let i = index" [value]="i">{{header}}</option>
</select>
</div>
</div>
<div class="row mt-2">
<div class="col-md-6">
<label>Password Column:</label>
<select [(ngModel)]="columnMapping.password" class="form-select">
<option value="">Select Column</option>
<option *ngFor="let header of csvHeaders; let i = index" [value]="i">{{header}}</option>
</select>
</div>
<div class="col-md-6">
<label>API Port Column:</label>
<select [(ngModel)]="columnMapping.port" class="form-select">
<option value="">Select Column</option>
<option *ngFor="let header of csvHeaders; let i = index" [value]="i">{{header}}</option>
</select>
</div>
</div>
</div>
</div>
<div *ngIf="addDeviceStep === 2" class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<h5 class="mt-3">{{uploadStatus}}</h5>
</div>
<div *ngIf="addDeviceStep === 3">
<h5>Upload Complete</h5>
<div class="alert alert-success">
<strong>Success:</strong> {{uploadResult.success}} devices added successfully<br>
<strong>Failed:</strong> {{uploadResult.failed}} devices failed to add
</div>
<button cButton color="primary" (click)="downloadResults()" *ngIf="uploadResult.resultFile">
<i class="fas fa-download"></i> Download Results
</button>
</div>
</c-modal-body>
<c-modal-footer>
<button *ngIf="addDeviceStep === 1" cButton color="primary" (click)="uploadDevices()" [disabled]="!isValidMapping()">
Add Devices
</button>
<button *ngIf="addDeviceStep === 3" cButton color="success" (click)="closeAddDeviceModal()">
Close
</button>
<button *ngIf="addDeviceStep !== 2" cButton color="secondary" (click)="closeAddDeviceModal()">
Cancel
</button>
</c-modal-footer>
</c-modal>
<!-- Web Access Modal -->
<c-modal #WebAccessModal backdrop="static" [(visible)]="showWebAccessModal" id="WebAccessModal">
<c-modal-header>
<h6 cModalTitle>Web Access Options</h6>
<button cButtonClose (click)="closeWebAccessModal()"></button>
</c-modal-header>
<c-modal-body>
<p>Choose how to access the device:</p>
<div class="d-grid gap-2">
<button *ngIf="ispro" cButton color="primary" (click)="openProxyAccess()">
<i class="fas fa-shield-alt"></i> Proxy Access, through Mikrowizard server
</button>
<button cButton color="secondary" (click)="openDirectAccess()">
<i class="fas fa-external-link-alt"></i> Direct Access
</button>
</div>
</c-modal-body>
<c-modal-footer>
<button cButton color="secondary" (click)="closeWebAccessModal()">Cancel</button>
</c-modal-footer>
</c-modal>
<!-- Task Details Modal -->
<c-modal #TaskDetailsModal backdrop="static" size="xl" [(visible)]="detailsModalVisible" id="TaskDetailsModal"
style="z-index: 1060; backdrop-filter: blur(2px);">
<c-modal-header style="background: rgba(255,255,255,0.95); backdrop-filter: blur(10px);">
<h5 cModalTitle>{{getTaskTypeLabel(selectedTaskDetails?.task_type)}} Results</h5>
<button (click)="closeDetailsModal()" cButtonClose></button>
</c-modal-header>
<c-modal-body *ngIf="selectedTaskDetails" style="background: rgba(255,255,255,0.98); backdrop-filter: blur(10px); max-height: 70vh; overflow-y: auto;">
<div class="mb-3">
<c-row>
<c-col md="6">
<strong>Task Type:</strong> {{getTaskTypeLabel(selectedTaskDetails.task_type)}}<br>
<strong>Started:</strong> {{selectedTaskDetails.started}}<br>
<strong>Completed:</strong> {{selectedTaskDetails.ended}}
</c-col>
<c-col md="6">
<div *ngIf="selectedTaskDetails.task_type === 'ip-scan'">
<strong>IP Range:</strong> {{selectedTaskDetails.start_ip}} - {{selectedTaskDetails.end_ip}}<br>
<strong>Username:</strong> {{selectedTaskDetails.username}}
</div>
<div *ngIf="selectedTaskDetails.task_type === 'bulk-add'">
<strong>Task ID:</strong> {{selectedTaskDetails.task_id}}<br>
<strong>Total Devices:</strong> {{selectedTaskDetails.device_count}}
</div>
</c-col>
</c-row>
</div>
<hr>
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Detailed Results:</h6>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" placeholder="Search IP or error..."
[(ngModel)]="detailsSearchTerm" (input)="onDetailsSearch()">
</div>
</div>
<table class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th>IP Address</th>
<th>Status</th>
<th *ngIf="selectedTaskDetails.task_type === 'bulk-add'">Error Details</th>
<th *ngIf="selectedTaskDetails.task_type === 'ip-scan'">Error Details</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let result of detailsPaginatedResults">
<td>{{result.ip}}</td>
<td>
<c-badge [color]="result.added ? 'success' : 'danger'">
{{result.added ? 'Success' : 'Failed'}}
</c-badge>
</td>
<td *ngIf="selectedTaskDetails.task_type === 'bulk-add'">
{{result.failures || 'N/A'}}
</td>
<td *ngIf="selectedTaskDetails.task_type === 'ip-scan'">
{{result.faileres || 'N/A'}}
</td>
</tr>
</tbody>
</table>
<nav *ngIf="getTotalDetailsPages() > 1" class="mt-3">
<ul class="pagination pagination-sm justify-content-center">
<li class="page-item" [class.disabled]="detailsCurrentPage === 1">
<button class="page-link" (click)="onDetailsPageChange(detailsCurrentPage - 1)" [disabled]="detailsCurrentPage === 1">
Previous
</button>
</li>
<li class="page-item" *ngFor="let page of [].constructor(getTotalDetailsPages()); let i = index"
[class.active]="detailsCurrentPage === i + 1">
<button class="page-link" (click)="onDetailsPageChange(i + 1)">
{{i + 1}}
</button>
</li>
<li class="page-item" [class.disabled]="detailsCurrentPage === getTotalDetailsPages()">
<button class="page-link" (click)="onDetailsPageChange(detailsCurrentPage + 1)"
[disabled]="detailsCurrentPage === getTotalDetailsPages()">
Next
</button>
</li>
</ul>
</nav>
<div class="mt-3">
<c-badge color="success" class="me-2">Success: {{selectedTaskDetails.success_count}}</c-badge>
<c-badge color="danger">Failed: {{selectedTaskDetails.failed_count}}</c-badge>
</div>
</c-modal-body>
<c-modal-footer>
<button (click)="exportToCsv(selectedTaskDetails.result)" color="primary" cButton class="me-2">
<i class="fas fa-download"></i> Download CSV
</button>
<button (click)="closeDetailsModal()" cButton color="secondary">
Close
</button>
</c-modal-footer>
</c-modal>
<c-toaster position="fixed" placement="top-end"></c-toaster>