Mining/ui/src/app/profile-list.component.html
snider 5f3fe0deee feat(ui): Add loading states with spinners to action buttons
Add visual feedback for async operations across UI components:
- Profile list: Start, Delete, and Save buttons show spinners during actions
- Profile create: Create Profile button shows spinner during submission
- Nodes page: Initialize Node, Add Peer, Ping, and Remove buttons show spinners

Buttons are disabled while their action is in progress to prevent
duplicate submissions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 22:08:01 +00:00

117 lines
4.8 KiB
HTML

<div class="flex flex-col gap-4">
<h5 class="text-lg font-semibold">Existing Profiles</h5>
@for(profile of state().profiles; track profile.id) {
<div class="card p-4">
@if(editingProfile && editingProfile.id === profile.id) {
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-1">
<label class="text-sm font-medium text-slate-300">Profile Name</label>
<input
type="text"
class="input"
[value]="editingProfile.name"
(input)="onNameInput($event)">
</div>
<div class="flex flex-col gap-1">
<label class="text-sm font-medium text-slate-300">Miner Type</label>
<select
class="select"
[value]="editingProfile.minerType"
(change)="onMinerTypeChange($event)">
@for(miner of state().manageableMiners; track miner.name) {
<option [value]="miner.name" [selected]="miner.name === editingProfile.minerType">
{{ miner.name }}
</option>
}
</select>
</div>
<div class="flex flex-col gap-1">
<label class="text-sm font-medium text-slate-300">Pool Address</label>
<input
type="text"
class="input"
[value]="editingProfile.config.pool"
(input)="onPoolInput($event)">
</div>
<div class="flex flex-col gap-1">
<label class="text-sm font-medium text-slate-300">Wallet Address</label>
<input
type="text"
class="input"
[value]="editingProfile.config.wallet"
(input)="onWalletInput($event)">
</div>
<div class="flex gap-6 items-center">
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
class="w-4 h-4 rounded border-surface-50 text-accent-500 focus:ring-accent-500"
[checked]="editingProfile.config.tls"
(change)="onTlsChange($event)">
<span class="text-sm">TLS</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
class="w-4 h-4 rounded border-surface-50 text-accent-500 focus:ring-accent-500"
[checked]="editingProfile.config.hugePages"
(change)="onHugePagesChange($event)">
<span class="text-sm">Huge Pages</span>
</label>
</div>
<div class="flex gap-2">
<button
class="btn btn-primary btn-sm"
[disabled]="actionInProgress() === 'save-' + editingProfile.id"
(click)="updateProfile()">
@if (actionInProgress() === 'save-' + editingProfile.id) {
<div class="animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full mr-1"></div>
Saving...
} @else {
Save
}
</button>
<button
class="btn btn-secondary btn-sm"
[disabled]="actionInProgress() === 'save-' + editingProfile.id"
(click)="cancelEdit()">Cancel</button>
</div>
</div>
} @else {
<div class="flex justify-between items-center">
<span class="font-medium">{{ profile.name }} <span class="text-slate-400">({{ profile.minerType }})</span></span>
<div class="flex gap-2">
<button
class="btn btn-primary btn-xs"
[disabled]="actionInProgress() === 'start-' + profile.id"
(click)="startMiner(profile.id)">
@if (actionInProgress() === 'start-' + profile.id) {
<div class="animate-spin w-3 h-3 border-2 border-white border-t-transparent rounded-full mr-1"></div>
Starting...
} @else {
Start
}
</button>
<button
class="btn btn-secondary btn-xs"
[disabled]="actionInProgress() !== null"
(click)="editProfile(profile)">Edit</button>
<button
class="btn btn-danger btn-xs"
[disabled]="actionInProgress() === 'delete-' + profile.id"
(click)="deleteProfile(profile.id)">
@if (actionInProgress() === 'delete-' + profile.id) {
<div class="animate-spin w-3 h-3 border-2 border-white border-t-transparent rounded-full mr-1"></div>
Deleting...
} @else {
Delete
}
</button>
</div>
</div>
}
</div>
} @empty {
<p class="text-slate-400 italic">No profiles created yet.</p>
}
</div>