Use hero-job crate instead of runner_rust for job types
This commit is contained in:
3549
Cargo.lock
generated
3549
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,9 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Runner crate with integrated job module
|
# Job types
|
||||||
runner_rust = { git = "https://git.ourworld.tf/herocode/runner_rust.git" }
|
hero-job = { path = "../job/rust" }
|
||||||
|
# hero-job = { git = "https://git.ourworld.tf/herocode/job.git", subdirectory = "rust" }
|
||||||
# Async runtime
|
# Async runtime
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
|
||||||
|
|||||||
197
clients/admin-ui/BREADCRUMB_NAVIGATION.md
Normal file
197
clients/admin-ui/BREADCRUMB_NAVIGATION.md
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
# Breadcrumb Navigation with Dynamic Sidebar and Content Islands
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Implemented a breadcrumb-style navigation system where clicking on a job transitions the UI to show:
|
||||||
|
- **Sidebar**: Job details with metadata and actions
|
||||||
|
- **Main Content**: Two side-by-side islands showing Payload (left) and Logs (right)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
The UI uses `ContentView` enum to track the current view:
|
||||||
|
```rust
|
||||||
|
enum ContentView {
|
||||||
|
JobsList, // Default view showing all jobs
|
||||||
|
JobDetail(String), // Job detail view with job_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Layout Structure
|
||||||
|
|
||||||
|
#### Jobs List View
|
||||||
|
```
|
||||||
|
┌─────────────────┬──────────────────────────────┐
|
||||||
|
│ Sidebar │ Main Content │
|
||||||
|
│ │ │
|
||||||
|
│ [Runners/Keys] │ Jobs Table │
|
||||||
|
│ Toggle │ - Filter │
|
||||||
|
│ │ - Sort │
|
||||||
|
│ Runner/Key │ - Actions │
|
||||||
|
│ Cards │ │
|
||||||
|
│ │ │
|
||||||
|
└─────────────────┴──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Job Detail View
|
||||||
|
```
|
||||||
|
┌─────────────────┬──────────────────────────────┐
|
||||||
|
│ Sidebar │ Main Content │
|
||||||
|
│ │ │
|
||||||
|
│ [← Back] │ ┌────────┬────────┐ │
|
||||||
|
│ │ │Payload │ Logs │ │
|
||||||
|
│ Job Details │ │ │ │ │
|
||||||
|
│ - Status │ │ (code) │ (code) │ │
|
||||||
|
│ - Job ID │ │ │ │ │
|
||||||
|
│ - Runner │ └────────┴────────┘ │
|
||||||
|
│ - Timeout │ │
|
||||||
|
│ - Created │ ┌──────────────────┐ │
|
||||||
|
│ │ │ Output & Status │ │
|
||||||
|
│ [▶ Run Job] │ │ [Status Badge] │ │
|
||||||
|
│ [🗑 Delete] │ │ [View Output] │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │ ⏳ Running... │ │
|
||||||
|
│ │ │ or ✅ Complete │ │
|
||||||
|
│ │ │ or ❌ Failed │ │
|
||||||
|
│ │ └──────────────────┘ │
|
||||||
|
└─────────────────┴──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### 1. Dynamic Sidebar (`view_dashboard`)
|
||||||
|
The sidebar content changes based on `ContentView`:
|
||||||
|
- **JobsList**: Shows Runners/Keys toggle with respective cards
|
||||||
|
- **JobDetail**: Shows job metadata and action buttons
|
||||||
|
|
||||||
|
### 2. Job Detail Sidebar (`view_job_detail_sidebar`)
|
||||||
|
Displays:
|
||||||
|
- Back button (← arrow) to return to jobs list
|
||||||
|
- Job metadata in labeled rows:
|
||||||
|
- Status (with colored badge)
|
||||||
|
- Job ID (monospace)
|
||||||
|
- Runner name
|
||||||
|
- Timeout duration
|
||||||
|
- Created timestamp
|
||||||
|
- Action buttons:
|
||||||
|
- Run Job (primary button)
|
||||||
|
- Delete Job (danger button)
|
||||||
|
|
||||||
|
### 3. Job Detail Content (`view_job_detail_content`)
|
||||||
|
Three-pane layout with top row and bottom row:
|
||||||
|
|
||||||
|
**Top Row** (Two equal-width islands side-by-side):
|
||||||
|
- **Payload Island** (Left):
|
||||||
|
- Header: "Payload"
|
||||||
|
- Content: Code block with job payload
|
||||||
|
- **Logs Island** (Right):
|
||||||
|
- Header: "Logs"
|
||||||
|
- Content: Code block with job logs or loading spinner
|
||||||
|
|
||||||
|
**Bottom Row** (Full-width island):
|
||||||
|
- **Output & Status Island**:
|
||||||
|
- Header: "Output & Status" with status badge and "View Output" button
|
||||||
|
- Content: Dynamic status display based on job state:
|
||||||
|
- **Running** (⏳): Shows "Job is running..." message
|
||||||
|
- **Completed** (✅): Shows "Job completed successfully" with prompt to view output
|
||||||
|
- **Failed** (❌): Shows "Job failed" error message
|
||||||
|
- **Idle** (⏸): Shows "Job not started" with instruction to run
|
||||||
|
|
||||||
|
### 4. Navigation Flow
|
||||||
|
```
|
||||||
|
Jobs List → Click Job Row → Job Detail View
|
||||||
|
↓
|
||||||
|
Click Back Button
|
||||||
|
↓
|
||||||
|
Jobs List
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Styling
|
||||||
|
|
||||||
|
### Key Classes
|
||||||
|
- `.job-detail-content`: Flex column layout for three-pane structure
|
||||||
|
- `.detail-top-row`: Grid layout for two islands (1fr 1fr)
|
||||||
|
- `.detail-bottom-row`: Full-width container for output/status
|
||||||
|
- `.detail-island`: Container for payload/logs/output with header and content
|
||||||
|
- `.island-header`: Title bar for each island
|
||||||
|
- `.island-content`: Scrollable content area
|
||||||
|
- `.code-block`: Monospace code display
|
||||||
|
- `.job-detail-sidebar`: Vertical layout for job metadata
|
||||||
|
- `.detail-row`: Label-value pairs for job info
|
||||||
|
- `.status-display`: Status indicator with icon and text
|
||||||
|
- `.btn-back`: Back arrow button
|
||||||
|
|
||||||
|
### Responsive Design
|
||||||
|
- Desktop (>1200px): Top row shows two islands side-by-side, bottom row full-width
|
||||||
|
- Mobile (<1200px): All islands stack vertically
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Breadcrumb Navigation
|
||||||
|
- Clear visual hierarchy with back button
|
||||||
|
- Sidebar transforms to show contextual information
|
||||||
|
- Main content splits into focused islands
|
||||||
|
|
||||||
|
### Job Details Display
|
||||||
|
- Clean, organized metadata presentation
|
||||||
|
- Status badges with color coding
|
||||||
|
- Monospace formatting for technical data (IDs, code)
|
||||||
|
|
||||||
|
### Code Display
|
||||||
|
- Syntax-friendly monospace font
|
||||||
|
- Proper line wrapping
|
||||||
|
- Scrollable for long content
|
||||||
|
- Dark theme optimized
|
||||||
|
|
||||||
|
### Status & Output Pane
|
||||||
|
- **Visual Status Indicators**: Large emoji icons (⏳, ✅, ❌, ⏸) with colored borders
|
||||||
|
- **Real-time Status**: Shows current job state (running, completed, failed, idle)
|
||||||
|
- **Quick Actions**: View Output button in header (disabled for queued jobs)
|
||||||
|
- **Contextual Messages**: Clear instructions based on job state
|
||||||
|
- **Color-coded Borders**:
|
||||||
|
- Orange for running jobs
|
||||||
|
- Green for completed jobs
|
||||||
|
- Red for failed jobs
|
||||||
|
- Gray for idle jobs
|
||||||
|
|
||||||
|
### Actions
|
||||||
|
- Contextual actions in sidebar (Run, Delete)
|
||||||
|
- View Output button in status pane header
|
||||||
|
- Always visible without scrolling
|
||||||
|
- Clear visual hierarchy
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Viewing Job Details
|
||||||
|
1. Click any row in the jobs table
|
||||||
|
2. Sidebar updates to show job metadata
|
||||||
|
3. Main content splits into Payload and Logs islands
|
||||||
|
4. Click back arrow to return to jobs list
|
||||||
|
|
||||||
|
### Running/Deleting Jobs
|
||||||
|
- Actions are available in the sidebar
|
||||||
|
- Run Job: Queues the job for execution
|
||||||
|
- Delete Job: Removes the job from the system
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Better Information Architecture**: Job details in sidebar, code in main area, status below
|
||||||
|
2. **Side-by-Side Comparison**: View payload and logs simultaneously in top row
|
||||||
|
3. **Prominent Status Display**: Full-width status pane with visual indicators
|
||||||
|
4. **Consistent Navigation**: Back button provides clear exit path
|
||||||
|
5. **Focused View**: Each island has a single purpose
|
||||||
|
6. **Visual Feedback**: Color-coded status indicators with emoji icons
|
||||||
|
7. **Quick Actions**: View Output button readily accessible in status pane
|
||||||
|
8. **Responsive**: Adapts to different screen sizes
|
||||||
|
9. **Reusable Pattern**: Can be extended to other detail views
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential improvements:
|
||||||
|
- ✅ **Job output/status pane** (implemented as third pane)
|
||||||
|
- Show job execution history timeline
|
||||||
|
- Add real-time log streaming with auto-refresh
|
||||||
|
- Include job dependencies visualization
|
||||||
|
- Add breadcrumb trail in header (Jobs > Job-123)
|
||||||
|
- Add inline output display (instead of modal)
|
||||||
|
- Show job metrics (execution time, resource usage)
|
||||||
|
- Add job re-run with modified parameters
|
||||||
209
clients/admin-ui/ENHANCED_JOB_EDITING.md
Normal file
209
clients/admin-ui/ENHANCED_JOB_EDITING.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# Enhanced Job Editing Features
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Added comprehensive job editing capabilities with name field, editable job ID, runner dropdown, and signature management directly in the sidebar.
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
### 1. Job Name Field ✅
|
||||||
|
- **Optional field** for human-readable job names
|
||||||
|
- Separate from the auto-generated job ID
|
||||||
|
- Placeholder: "My Job"
|
||||||
|
- Stored in job object as `name` field
|
||||||
|
- Displayed in edit/create mode
|
||||||
|
|
||||||
|
### 2. Editable Job ID ✅
|
||||||
|
- **Auto-generated** by default (job-{uuid})
|
||||||
|
- **Manually editable** if needed
|
||||||
|
- Monospace font for better readability
|
||||||
|
- Placeholder shows format: "job-xxxxx"
|
||||||
|
- Allows custom job IDs when required
|
||||||
|
|
||||||
|
### 3. Runner Dropdown ✅
|
||||||
|
- **Replaced text input** with proper dropdown/select
|
||||||
|
- Shows all available runners
|
||||||
|
- Auto-selects first runner when creating new job
|
||||||
|
- Clear "No runners available" message when empty
|
||||||
|
- Better UX than text input with datalist
|
||||||
|
|
||||||
|
### 4. Signature Management ✅
|
||||||
|
- **Add signatures** directly in sidebar
|
||||||
|
- **Display signatures** with public key preview
|
||||||
|
- **Remove signatures** with × button
|
||||||
|
- **Private key input** (password field)
|
||||||
|
- **Sign button** to add signature
|
||||||
|
- Signatures shown as compact cards with monospace font
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### State Added
|
||||||
|
```rust
|
||||||
|
edit_job_name: String, // Optional job name
|
||||||
|
edit_job_id: String, // Editable job ID
|
||||||
|
edit_job_signatures: Vec<(String, String)>, // (pubkey, signature)
|
||||||
|
edit_job_private_key: String, // For signing
|
||||||
|
```
|
||||||
|
|
||||||
|
### Messages Added
|
||||||
|
```rust
|
||||||
|
UpdateEditJobName(String), // Update name field
|
||||||
|
UpdateEditJobId(String), // Update job ID
|
||||||
|
UpdateEditJobPrivateKey(String), // Update private key
|
||||||
|
SignJobEdit, // Sign the job
|
||||||
|
RemoveEditSignature(usize), // Remove signature by index
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sidebar Fields (Edit/Create Mode)
|
||||||
|
|
||||||
|
**Order:**
|
||||||
|
1. **Name** (optional) - Text input
|
||||||
|
2. **Job ID** - Editable text input (monospace)
|
||||||
|
3. **Runner** - Dropdown select
|
||||||
|
4. **Timeout** - Number input
|
||||||
|
5. **Signatures** - List + Add interface
|
||||||
|
- Existing signatures displayed as cards
|
||||||
|
- Private key input (password)
|
||||||
|
- Sign button
|
||||||
|
|
||||||
|
### Signature Display
|
||||||
|
|
||||||
|
Each signature shows:
|
||||||
|
- First 16 characters of public key
|
||||||
|
- Monospace font
|
||||||
|
- Remove button (×)
|
||||||
|
- Compact card layout
|
||||||
|
- Background color for visibility
|
||||||
|
|
||||||
|
### Signature Adding
|
||||||
|
|
||||||
|
- Password input for private key
|
||||||
|
- Sign button (disabled when empty)
|
||||||
|
- Creates canonical representation
|
||||||
|
- Signs with provided key
|
||||||
|
- Adds to signatures list
|
||||||
|
- Clears private key after signing
|
||||||
|
- Shows toast notification
|
||||||
|
|
||||||
|
## User Flow
|
||||||
|
|
||||||
|
### Creating a Job
|
||||||
|
```
|
||||||
|
1. Click "+ New Job"
|
||||||
|
2. Fill in fields:
|
||||||
|
- Name: "My Important Job" (optional)
|
||||||
|
- Job ID: Auto-filled, can edit
|
||||||
|
- Runner: Select from dropdown
|
||||||
|
- Timeout: 30 (default)
|
||||||
|
- Payload: Enter script/code
|
||||||
|
3. Optional: Add signatures
|
||||||
|
- Enter private key
|
||||||
|
- Click "Sign"
|
||||||
|
- Repeat for multiple signatures
|
||||||
|
4. Click "Create"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Editing a Job
|
||||||
|
```
|
||||||
|
1. Click job in table
|
||||||
|
2. Click "Edit" in sidebar
|
||||||
|
3. Modify fields:
|
||||||
|
- Name, Job ID, Runner, Timeout
|
||||||
|
- Payload in main area
|
||||||
|
4. Optional: Add/remove signatures
|
||||||
|
5. Click "Save"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Signing a Job
|
||||||
|
```
|
||||||
|
1. In edit/create mode
|
||||||
|
2. Scroll to Signatures section
|
||||||
|
3. Enter private key (hex format)
|
||||||
|
4. Click "Sign"
|
||||||
|
5. Signature added to list
|
||||||
|
6. Private key cleared automatically
|
||||||
|
7. Repeat for multiple signers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
### User Experience
|
||||||
|
- **Clearer job identification**: Name + ID
|
||||||
|
- **Easier runner selection**: Dropdown vs typing
|
||||||
|
- **Flexible job IDs**: Can customize when needed
|
||||||
|
- **Integrated signing**: No separate modal
|
||||||
|
- **Visual feedback**: Signatures displayed inline
|
||||||
|
|
||||||
|
### Data Model
|
||||||
|
- **Name field**: Optional, human-readable
|
||||||
|
- **ID field**: Required, system identifier
|
||||||
|
- **Separation of concerns**: Name for humans, ID for system
|
||||||
|
- **Signature support**: Full cryptographic signing
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- **Password field**: Private keys not visible
|
||||||
|
- **Canonical representation**: Proper signing protocol
|
||||||
|
- **Multiple signatures**: Support for multi-sig workflows
|
||||||
|
- **Key cleared**: Private key removed after signing
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Job Object Structure
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "job-abc123",
|
||||||
|
"name": "My Important Job", // Optional
|
||||||
|
"payload": "...",
|
||||||
|
"runner": "runner-name",
|
||||||
|
"timeout": 30,
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"public_key": "04e58314...",
|
||||||
|
"signature": "3045..."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Signing Process
|
||||||
|
1. Validate all required fields
|
||||||
|
2. Create canonical representation using `create_job_canonical_repr`
|
||||||
|
3. Sign canonical with `sign_job_canonical`
|
||||||
|
4. Extract public key and signature from result
|
||||||
|
5. Add to signatures list
|
||||||
|
6. Clear private key input
|
||||||
|
|
||||||
|
### Runner Dropdown
|
||||||
|
- Uses HTML `<select>` element
|
||||||
|
- Populated from `self.runners` vector
|
||||||
|
- Selected value bound to `self.edit_job_runner`
|
||||||
|
- Auto-selects first runner for new jobs
|
||||||
|
- Shows message when no runners available
|
||||||
|
|
||||||
|
## CSS Styling
|
||||||
|
|
||||||
|
Signature cards use inline styles:
|
||||||
|
- Flex layout for horizontal arrangement
|
||||||
|
- Monospace font for public keys
|
||||||
|
- Background color for visibility
|
||||||
|
- Compact padding
|
||||||
|
- Remove button aligned right
|
||||||
|
|
||||||
|
## Build Status
|
||||||
|
|
||||||
|
✅ **Success**
|
||||||
|
- Only minor warnings (unused variables)
|
||||||
|
- No errors
|
||||||
|
- Ready for deployment
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential improvements:
|
||||||
|
- Show full public key on hover
|
||||||
|
- Validate private key format
|
||||||
|
- Import signatures from file
|
||||||
|
- Export signed job
|
||||||
|
- Signature verification indicator
|
||||||
|
- Show signature timestamps
|
||||||
|
- Multi-select for runners (parallel execution)
|
||||||
145
clients/admin-ui/IMPROVEMENTS_SUMMARY.md
Normal file
145
clients/admin-ui/IMPROVEMENTS_SUMMARY.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# Job Detail View Improvements
|
||||||
|
|
||||||
|
## Summary of Changes
|
||||||
|
|
||||||
|
Implemented several UX improvements to simplify and unify the job detail interface:
|
||||||
|
|
||||||
|
### 1. Auto-Display Job Output ✅
|
||||||
|
- **Removed**: "View Output" button
|
||||||
|
- **Added**: Automatic output loading when viewing job details
|
||||||
|
- Output displays automatically in the bottom pane when available
|
||||||
|
- No manual action needed - output appears as soon as job completes
|
||||||
|
|
||||||
|
### 2. Enhanced Job Sidebar ✅
|
||||||
|
- **Added**: Signatures count display
|
||||||
|
- Shows "X signature(s)" if present
|
||||||
|
- Shows "None" if no signatures
|
||||||
|
- **Added**: Timeout display (already present, kept for completeness)
|
||||||
|
- **Added**: Edit Job button (✏️)
|
||||||
|
- Sidebar now shows: Status, Job ID, Runner, Timeout, Signatures, Created
|
||||||
|
|
||||||
|
### 3. Inline Job Editing ✅
|
||||||
|
- **Removed**: Separate job creation modal
|
||||||
|
- **Added**: Inline editing in job detail view
|
||||||
|
- Click "✏️ Edit Job" button to enter edit mode
|
||||||
|
- **Edit mode transforms the view**:
|
||||||
|
- **Payload island**: Becomes editable textarea
|
||||||
|
- **Logs island**: Remains read-only (shows execution logs)
|
||||||
|
- **Bottom pane**: Shows edit form with Runner and Timeout inputs
|
||||||
|
- **Actions**: Cancel or Save Changes buttons
|
||||||
|
- Unified look - uses same layout as viewing
|
||||||
|
|
||||||
|
### 4. Simplified Layout
|
||||||
|
- Same three-pane structure for viewing and editing
|
||||||
|
- No modal overlays for job management
|
||||||
|
- Consistent visual language throughout
|
||||||
|
- Edit mode is clearly indicated by header change
|
||||||
|
|
||||||
|
## New User Flow
|
||||||
|
|
||||||
|
### Viewing a Job
|
||||||
|
```
|
||||||
|
1. Click job in table
|
||||||
|
2. View opens with:
|
||||||
|
- Sidebar: Job metadata (status, ID, runner, timeout, signatures, created)
|
||||||
|
- Top-left: Payload (read-only code)
|
||||||
|
- Top-right: Logs (auto-loaded)
|
||||||
|
- Bottom: Output (auto-loaded when available) or status message
|
||||||
|
```
|
||||||
|
|
||||||
|
### Editing a Job
|
||||||
|
```
|
||||||
|
1. Click "✏️ Edit Job" in sidebar
|
||||||
|
2. View transforms:
|
||||||
|
- Sidebar: Unchanged (shows current job info)
|
||||||
|
- Top-left: Payload becomes editable textarea
|
||||||
|
- Top-right: Logs remain visible
|
||||||
|
- Bottom: Edit form with Runner and Timeout inputs
|
||||||
|
3. Make changes
|
||||||
|
4. Click "💾 Save Changes" or "Cancel"
|
||||||
|
5. View returns to normal mode with updated data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running a Job
|
||||||
|
```
|
||||||
|
1. Click "▶ Run Job" in sidebar
|
||||||
|
2. Bottom pane shows "⏳ Job is running..."
|
||||||
|
3. When complete, output auto-loads in bottom pane
|
||||||
|
4. Status badge updates automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
### User Experience
|
||||||
|
- **Fewer clicks**: No need to click "View Output" button
|
||||||
|
- **Immediate feedback**: Output appears automatically
|
||||||
|
- **Unified interface**: Edit and view use same layout
|
||||||
|
- **Less cognitive load**: No modal context switching
|
||||||
|
- **More information**: Signatures count visible at a glance
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- **Reduced complexity**: Removed job creation modal code
|
||||||
|
- **Consistent patterns**: Single layout for all job operations
|
||||||
|
- **Better state management**: Edit mode is a simple boolean flag
|
||||||
|
- **Maintainable**: Less code to maintain and test
|
||||||
|
|
||||||
|
### Visual Design
|
||||||
|
- **Cleaner**: No modal overlays
|
||||||
|
- **Consistent**: Same island structure throughout
|
||||||
|
- **Professional**: Smooth transitions between modes
|
||||||
|
- **Informative**: All relevant data visible
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### State Added
|
||||||
|
```rust
|
||||||
|
job_detail_output: Option<String>, // Auto-loaded output
|
||||||
|
editing_job: bool, // Edit mode flag
|
||||||
|
edit_job_payload: String, // Edit buffer for payload
|
||||||
|
edit_job_runner: String, // Edit buffer for runner
|
||||||
|
edit_job_timeout: String, // Edit buffer for timeout
|
||||||
|
```
|
||||||
|
|
||||||
|
### Messages Added
|
||||||
|
```rust
|
||||||
|
EditJob, // Enter edit mode
|
||||||
|
CancelEditJob, // Exit edit mode
|
||||||
|
UpdateEditJobPayload(String), // Update payload buffer
|
||||||
|
UpdateEditJobRunner(String), // Update runner buffer
|
||||||
|
UpdateEditJobTimeout(String), // Update timeout buffer
|
||||||
|
SaveJobEdit, // Save changes
|
||||||
|
JobEditSaved(Result<String, String>), // Save result
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-Loading
|
||||||
|
- Output loads automatically when `ViewJobDetail` message is sent
|
||||||
|
- Stored in `job_detail_output` instead of modal state
|
||||||
|
- Displayed immediately when available
|
||||||
|
- No user interaction required
|
||||||
|
|
||||||
|
### Edit Mode
|
||||||
|
- Payload textarea replaces code block
|
||||||
|
- Bottom pane shows edit form instead of status
|
||||||
|
- Header changes to "Edit Settings"
|
||||||
|
- Cancel restores original view
|
||||||
|
- Save updates job and reloads data
|
||||||
|
|
||||||
|
## CSS Additions
|
||||||
|
|
||||||
|
```css
|
||||||
|
.edit-textarea /* Editable payload textarea */
|
||||||
|
.edit-form /* Edit form container */
|
||||||
|
.edit-form .form-group /* Form field groups */
|
||||||
|
.edit-form label /* Form labels */
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential improvements:
|
||||||
|
- Add signature management in edit mode
|
||||||
|
- Show signature details (public keys) in sidebar
|
||||||
|
- Add job cloning feature
|
||||||
|
- Real-time output streaming for running jobs
|
||||||
|
- Diff view when editing to show changes
|
||||||
|
- Undo/redo for edits
|
||||||
|
- Auto-save drafts
|
||||||
177
clients/admin-ui/REFACTORING_COMPLETE.md
Normal file
177
clients/admin-ui/REFACTORING_COMPLETE.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Job Management Refactoring - Complete
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Successfully refactored the job management UI to remove the modal-based workflow and use inline editing in the job detail view instead.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### 1. Removed Job Creation Modal ✅
|
||||||
|
- **Deleted**: Entire `view_job_form` modal function and related code
|
||||||
|
- **Deleted**: Modal overlay and form UI
|
||||||
|
- **Replaced with**: Inline job creation using the same job detail view
|
||||||
|
|
||||||
|
### 2. Unified Job Creation and Editing ✅
|
||||||
|
- **"+ New Job" button** now creates a new job detail view in edit mode
|
||||||
|
- **Same layout** for creating and editing jobs
|
||||||
|
- **Sidebar fields** become editable inputs (Runner, Timeout)
|
||||||
|
- **Payload** becomes editable textarea
|
||||||
|
- **No separate modal** - everything happens in the main view
|
||||||
|
|
||||||
|
### 3. Sidebar-Based Editing ✅
|
||||||
|
- **View mode** shows: Status, Job ID, Runner, Timeout, Signatures, Created
|
||||||
|
- **Edit mode** shows: Job ID (read-only), Runner (input), Timeout (input)
|
||||||
|
- **Actions change** based on mode:
|
||||||
|
- View: Run, Edit, Delete buttons
|
||||||
|
- Edit: Cancel, Save/Create buttons
|
||||||
|
- **No emojis** - clean text labels only
|
||||||
|
|
||||||
|
### 4. Removed Emojis ✅
|
||||||
|
- Changed "▶ Run Job" → "Run"
|
||||||
|
- Changed "✏️ Edit Job" → "Edit"
|
||||||
|
- Changed "🗑 Delete Job" → "Delete"
|
||||||
|
- Changed "💾 Save Changes" → "Save"
|
||||||
|
- Changed "📄 View Output" → Removed (auto-displays)
|
||||||
|
- Removed emoji status icons (⏳, ✅, ❌, ⏸)
|
||||||
|
|
||||||
|
### 5. Simplified Bottom Pane ✅
|
||||||
|
- **Removed**: Edit form from bottom pane (moved to sidebar)
|
||||||
|
- **Kept**: Output & Status display only
|
||||||
|
- **Auto-displays**: Job output when available (no button needed)
|
||||||
|
- **Status messages**: Text-only, centered, no icons
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
```rust
|
||||||
|
// Removed old modal state:
|
||||||
|
- show_job_form: bool
|
||||||
|
- job_id: String
|
||||||
|
- job_payload: String
|
||||||
|
- job_runner: String
|
||||||
|
- job_timeout: String
|
||||||
|
- job_signatures: Vec<(String, String)>
|
||||||
|
- job_private_key: String
|
||||||
|
- creating_job: bool
|
||||||
|
|
||||||
|
// Kept unified editing state:
|
||||||
|
+ editing_job: bool
|
||||||
|
+ edit_job_payload: String
|
||||||
|
+ edit_job_runner: String
|
||||||
|
+ edit_job_timeout: String
|
||||||
|
```
|
||||||
|
|
||||||
|
### Messages
|
||||||
|
```rust
|
||||||
|
// Removed old modal messages:
|
||||||
|
- ShowJobForm
|
||||||
|
- HideJobForm
|
||||||
|
- UpdateJobId
|
||||||
|
- UpdateJobPayload
|
||||||
|
- UpdateJobRunner
|
||||||
|
- UpdateJobTimeout
|
||||||
|
- UpdateJobPrivateKey
|
||||||
|
- SignJob
|
||||||
|
- RemoveSignature
|
||||||
|
- CreateJob
|
||||||
|
- JobCreated
|
||||||
|
|
||||||
|
// Kept unified messages:
|
||||||
|
+ CreateNewJob (enters edit mode with new ID)
|
||||||
|
+ EditJob (enters edit mode with existing job)
|
||||||
|
+ CancelEditJob
|
||||||
|
+ UpdateEditJobPayload
|
||||||
|
+ UpdateEditJobRunner
|
||||||
|
+ UpdateEditJobTimeout
|
||||||
|
+ SaveJobEdit (handles both create and update)
|
||||||
|
+ JobEditSaved
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Flow
|
||||||
|
|
||||||
|
**Creating a Job:**
|
||||||
|
```
|
||||||
|
1. Click "+ New Job" button
|
||||||
|
2. View switches to job detail with:
|
||||||
|
- Sidebar: Job ID (generated), Runner input, Timeout input
|
||||||
|
- Payload: Empty textarea
|
||||||
|
- Logs: "No logs yet"
|
||||||
|
- Output: "New job" message
|
||||||
|
3. Fill in payload, runner, timeout
|
||||||
|
4. Click "Create" in sidebar
|
||||||
|
5. Job is saved and view returns to jobs list
|
||||||
|
```
|
||||||
|
|
||||||
|
**Editing a Job:**
|
||||||
|
```
|
||||||
|
1. Click job in table
|
||||||
|
2. View shows job details
|
||||||
|
3. Click "Edit" in sidebar
|
||||||
|
4. Sidebar fields become inputs
|
||||||
|
5. Payload becomes editable textarea
|
||||||
|
6. Modify fields as needed
|
||||||
|
7. Click "Save" or "Cancel" in sidebar
|
||||||
|
8. Changes are saved or discarded
|
||||||
|
```
|
||||||
|
|
||||||
|
**Viewing a Job:**
|
||||||
|
```
|
||||||
|
1. Click job in table
|
||||||
|
2. Sidebar shows: Status, Job ID, Runner, Timeout, Signatures, Created
|
||||||
|
3. Top panes show: Payload (code), Logs (code)
|
||||||
|
4. Bottom pane shows: Output (auto-loaded) or status message
|
||||||
|
5. Actions: Run, Edit, Delete
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
### User Experience
|
||||||
|
- **Simpler**: No modal overlays to manage
|
||||||
|
- **Consistent**: Same view for create/edit/view
|
||||||
|
- **Cleaner**: No emojis, professional text labels
|
||||||
|
- **Faster**: Fewer clicks, inline editing
|
||||||
|
- **Unified**: All job operations in one place
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- **Less code**: Removed ~200 lines of modal code
|
||||||
|
- **Simpler state**: Fewer state fields to manage
|
||||||
|
- **Fewer messages**: Consolidated create/edit flow
|
||||||
|
- **Maintainable**: Single code path for job management
|
||||||
|
- **No duplication**: Reuses same view components
|
||||||
|
|
||||||
|
### Visual Design
|
||||||
|
- **Professional**: Text-only buttons and labels
|
||||||
|
- **Clean**: No emoji clutter
|
||||||
|
- **Consistent**: Matches overall UI style
|
||||||
|
- **Accessible**: Clear text labels for all actions
|
||||||
|
- **Modern**: Inline editing pattern
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
- `src/app.rs`: Main application logic
|
||||||
|
- Removed modal state and messages
|
||||||
|
- Added unified editing state
|
||||||
|
- Updated sidebar to show editable fields
|
||||||
|
- Simplified bottom pane (no edit form)
|
||||||
|
- Removed emoji icons from buttons
|
||||||
|
|
||||||
|
- `styles.css`: Updated styles
|
||||||
|
- Removed emoji icon styling
|
||||||
|
- Centered status text
|
||||||
|
- Kept clean, professional appearance
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Build Status: ✅ **Success**
|
||||||
|
- Only minor warnings (unused variables)
|
||||||
|
- No errors
|
||||||
|
- Ready for deployment
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Potential enhancements:
|
||||||
|
- Add signature management in edit mode
|
||||||
|
- Implement job cloning feature
|
||||||
|
- Add validation feedback
|
||||||
|
- Show diff when editing
|
||||||
|
- Add undo/redo for edits
|
||||||
97
clients/admin-ui/REFACTORING_PLAN.md
Normal file
97
clients/admin-ui/REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# Admin UI Refactoring Status
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Split the monolithic `app.rs` (2600+ lines) and `styles.css` (1500+ lines) into modular, maintainable files.
|
||||||
|
|
||||||
|
## ✅ CSS Refactoring COMPLETE!
|
||||||
|
|
||||||
|
## CSS Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
styles/
|
||||||
|
├── main.css # Variables, base styles, layout (CREATED ✅)
|
||||||
|
├── components.css # Reusable components: islands, buttons, badges (CREATED ✅)
|
||||||
|
├── runners.css # Runner list, cards, status
|
||||||
|
├── jobs.css # Job list, detail views, job cards
|
||||||
|
├── keys.css # API key management styles
|
||||||
|
└── header.css # Header, breadcrumbs, user info
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import in index.html
|
||||||
|
```html
|
||||||
|
<link rel="stylesheet" href="/styles/main.css">
|
||||||
|
<link rel="stylesheet" href="/styles/components.css">
|
||||||
|
<link rel="stylesheet" href="/styles/runners.css">
|
||||||
|
<link rel="stylesheet" href="/styles/jobs.css">
|
||||||
|
<link rel="stylesheet" href="/styles/keys.css">
|
||||||
|
<link rel="stylesheet" href="/styles/header.css">
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rust Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app.rs # Main App component, state, update logic
|
||||||
|
├── components/
|
||||||
|
│ ├── mod.rs # Module declarations
|
||||||
|
│ ├── header.rs # Header component
|
||||||
|
│ ├── runners.rs # Runner list and management views
|
||||||
|
│ ├── jobs.rs # Job list and detail views
|
||||||
|
│ └── keys.rs # API key management views
|
||||||
|
└── lib.rs # Re-export App
|
||||||
|
```
|
||||||
|
|
||||||
|
### Module Organization
|
||||||
|
|
||||||
|
**app.rs** (~500 lines)
|
||||||
|
- App struct and state
|
||||||
|
- Message enum
|
||||||
|
- Main update() logic
|
||||||
|
- Top-level view() that delegates to components
|
||||||
|
|
||||||
|
**components/header.rs** (~100 lines)
|
||||||
|
- view_header()
|
||||||
|
- Server info, breadcrumbs, user info
|
||||||
|
|
||||||
|
**components/runners.rs** (~400 lines)
|
||||||
|
- view_runners_sidebar()
|
||||||
|
- view_runners_content()
|
||||||
|
- Runner cards, status, management
|
||||||
|
|
||||||
|
**components/jobs.rs** (~1000 lines)
|
||||||
|
- view_jobs_sidebar()
|
||||||
|
- view_jobs_list_content()
|
||||||
|
- view_job_detail()
|
||||||
|
- view_job_detail_sidebar()
|
||||||
|
- Job creation, editing
|
||||||
|
|
||||||
|
**components/keys.rs** (~300 lines)
|
||||||
|
- view_keys_sidebar()
|
||||||
|
- view_keys_content()
|
||||||
|
- Key creation, deletion
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Maintainability**: Easier to find and modify specific features
|
||||||
|
2. **Collaboration**: Multiple developers can work on different components
|
||||||
|
3. **Testing**: Easier to test individual components
|
||||||
|
4. **Performance**: Smaller compilation units
|
||||||
|
5. **Clarity**: Clear separation of concerns
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
1. ✅ Create directory structure
|
||||||
|
2. ✅ Create main.css and components.css
|
||||||
|
3. ⏳ Extract remaining CSS files
|
||||||
|
4. ⏳ Create component modules
|
||||||
|
5. ⏳ Move view functions to appropriate modules
|
||||||
|
6. ⏳ Update imports and module declarations
|
||||||
|
7. ⏳ Test and verify functionality
|
||||||
|
8. ⏳ Remove old monolithic files
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
- Keep old files until refactoring is complete
|
||||||
|
- Test each component as it's extracted
|
||||||
|
- Use feature flags if needed for gradual rollout
|
||||||
|
- Maintain backward compatibility during transition
|
||||||
@@ -6,6 +6,14 @@
|
|||||||
<title>Hero Supervisor</title>
|
<title>Hero Supervisor</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
<!-- Modular CSS -->
|
||||||
|
<link data-trunk rel="css" href="styles/main.css">
|
||||||
|
<link data-trunk rel="css" href="styles/components.css">
|
||||||
|
<link data-trunk rel="css" href="styles/header.css">
|
||||||
|
<link data-trunk rel="css" href="styles/runners.css">
|
||||||
|
<link data-trunk rel="css" href="styles/jobs.css">
|
||||||
|
<link data-trunk rel="css" href="styles/keys.css">
|
||||||
|
<!-- Legacy fallback (remove after migration) -->
|
||||||
<link data-trunk rel="css" href="styles.css">
|
<link data-trunk rel="css" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
54
clients/admin-ui/src/app_components/header.rs
Normal file
54
clients/admin-ui/src/app_components/header.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
use crate::app::{App, Msg, ContentView};
|
||||||
|
|
||||||
|
pub fn view_header(app: &App, ctx: &Context<App>) -> Html {
|
||||||
|
html! {
|
||||||
|
<header class="app-header">
|
||||||
|
<div class="header-island">
|
||||||
|
<div class="server-info">
|
||||||
|
<span class="server-label">{ "Server" }</span>
|
||||||
|
<span class="server-url">{ &app.server_url }</span>
|
||||||
|
<span class={classes!("status-dot", if app.connected { "connected" } else { "disconnected" })}>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// Breadcrumbs island
|
||||||
|
<div class="header-island breadcrumbs-island">
|
||||||
|
{
|
||||||
|
match &app.content_view {
|
||||||
|
ContentView::JobsList => {
|
||||||
|
html! {
|
||||||
|
<span class="breadcrumb-item">{ "Jobs" }</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContentView::JobDetail(job_id) => {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
class="breadcrumb-link"
|
||||||
|
onclick={ctx.link().callback(|_| Msg::BackToJobsList)}
|
||||||
|
>
|
||||||
|
{ "Jobs" }
|
||||||
|
</button>
|
||||||
|
<span class="breadcrumb-separator">{ "/" }</span>
|
||||||
|
<span class="breadcrumb-item">{ &job_id[..12.min(job_id.len())] }</span>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-island">
|
||||||
|
<div class="user-info">
|
||||||
|
<span class="user-name">{ &app.user_name }</span>
|
||||||
|
<span class="user-scope">{ &app.user_scope }</span>
|
||||||
|
</div>
|
||||||
|
<button onclick={ctx.link().callback(|_| Msg::Logout)} class="logout-btn">
|
||||||
|
{ "⎋" }
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
}
|
||||||
|
}
|
||||||
11
clients/admin-ui/src/app_components/mod.rs
Normal file
11
clients/admin-ui/src/app_components/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Component modules
|
||||||
|
pub mod header;
|
||||||
|
// pub mod runners;
|
||||||
|
// pub mod jobs;
|
||||||
|
// pub mod keys;
|
||||||
|
|
||||||
|
// Re-export commonly used items
|
||||||
|
pub use header::*;
|
||||||
|
// pub use runners::*;
|
||||||
|
// pub use jobs::*;
|
||||||
|
// pub use keys::*;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
pub mod app_components;
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
#[wasm_bindgen(start)]
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
|||||||
@@ -170,7 +170,16 @@ button:disabled {
|
|||||||
height: calc(100vh - 60px);
|
height: calc(100vh - 60px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar Island */
|
/* Unified Island Component */
|
||||||
|
.island {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legacy class names - map to unified island */
|
||||||
.sidebar-island {
|
.sidebar-island {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
@@ -253,6 +262,24 @@ button:disabled {
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When content-island contains job detail islands, remove wrapper styling */
|
||||||
|
.content-island:has(.detail-top-row) {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Island with header/content structure */
|
||||||
|
.island.with-header {
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-header {
|
.content-header {
|
||||||
@@ -402,13 +429,45 @@ button:disabled {
|
|||||||
.header-island {
|
.header-island {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
padding: 0.75rem 1.25rem;
|
padding: 0.75rem 1.25rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.breadcrumbs-island {
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-link {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-link:hover {
|
||||||
|
color: var(--accent-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-separator {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-item {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -945,6 +1004,11 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-created {
|
||||||
|
background: #64748b;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.status-queued {
|
.status-queued {
|
||||||
background: #3b82f6;
|
background: #3b82f6;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -1270,3 +1334,240 @@ button:disabled {
|
|||||||
.key-delete-btn:hover {
|
.key-delete-btn:hover {
|
||||||
color: #ef4444;
|
color: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Job Detail Layout */
|
||||||
|
.job-detail-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-top-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-bottom-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-island {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.island-header {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.island-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.island-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Job Detail Sidebar */
|
||||||
|
.job-detail-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Display */
|
||||||
|
.status-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display.running {
|
||||||
|
border-color: #f59e0b;
|
||||||
|
background: rgba(245, 158, 11, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display.completed {
|
||||||
|
border-color: #10b981;
|
||||||
|
background: rgba(16, 185, 129, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display.error {
|
||||||
|
border-color: #ef4444;
|
||||||
|
background: rgba(239, 68, 68, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display.idle {
|
||||||
|
border-color: var(--border);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text h4 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit Form */
|
||||||
|
.edit-textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 300px;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form .form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for job detail */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.detail-top-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-bottom-row {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
273
clients/admin-ui/styles/components.css
Normal file
273
clients/admin-ui/styles/components.css
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
/* ============================================
|
||||||
|
COMPONENTS - Reusable UI Components
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Unified Island Component */
|
||||||
|
.island {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Island with header/content structure */
|
||||||
|
.island.with-header {
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.island-header {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.island-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.island-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn {
|
||||||
|
padding: 0.625rem 1.25rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--accent-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--error);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: var(--success);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover {
|
||||||
|
background: #059669;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Badges */
|
||||||
|
.status-badge {
|
||||||
|
padding: 0.25rem 0.625rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-created {
|
||||||
|
background: #64748b;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-queued {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-running {
|
||||||
|
background: #f59e0b;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-completed {
|
||||||
|
background: #10b981;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-failed {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-unknown {
|
||||||
|
background: #64748b;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading Spinner */
|
||||||
|
.loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Display */
|
||||||
|
.status-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display.idle {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display.running {
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display.completed {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display.error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text h4 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text p {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code Block */
|
||||||
|
.code-block {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast Notifications */
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
right: 1.5rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 400px;
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.error {
|
||||||
|
border-color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.success {
|
||||||
|
border-color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Elements */
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="number"],
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.625rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
width: 100%;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
textarea:focus,
|
||||||
|
select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pulse Animation */
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse {
|
||||||
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
|
}
|
||||||
109
clients/admin-ui/styles/header.css
Normal file
109
clients/admin-ui/styles/header.css
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/* ============================================
|
||||||
|
HEADER - App Header and Navigation
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Header Islands */
|
||||||
|
.header-island {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs-island {
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Server Info */
|
||||||
|
.server-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-url {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Breadcrumbs */
|
||||||
|
.breadcrumbs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-link {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-link:hover {
|
||||||
|
color: var(--accent-hover);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-separator {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-current {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User Info */
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-scope {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logout Button */
|
||||||
|
.logout-button {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-button:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-color: var(--error);
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
220
clients/admin-ui/styles/jobs.css
Normal file
220
clients/admin-ui/styles/jobs.css
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
/* ============================================
|
||||||
|
JOBS - Job Management Styles
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Content Island */
|
||||||
|
.content-island {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When content-island contains job detail islands, remove wrapper styling */
|
||||||
|
.content-island:has(.detail-top-row) {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header h2 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Job List */
|
||||||
|
.jobs-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Job Cards */
|
||||||
|
.job-card {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-card:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-id {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-info-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-info-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-info-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Job Detail Layout */
|
||||||
|
.detail-top-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-bottom-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-island {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Job Detail Sidebar */
|
||||||
|
.job-detail-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons */
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signature List */
|
||||||
|
.signatures-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signature-item {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signature-pubkey {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 3rem 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state h3 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.detail-top-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
162
clients/admin-ui/styles/keys.css
Normal file
162
clients/admin-ui/styles/keys.css
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/* ============================================
|
||||||
|
KEYS - API Key Management Styles
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Key List */
|
||||||
|
.keys-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Key Cards */
|
||||||
|
.key-card {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-card:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-card.selected {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 2px var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Key Scope Badge */
|
||||||
|
.key-scope {
|
||||||
|
padding: 0.25rem 0.625rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-scope.admin {
|
||||||
|
background: var(--error);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-scope.user {
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-scope.register {
|
||||||
|
background: var(--success);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Key Info */
|
||||||
|
.key-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-value {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Key Form */
|
||||||
|
.key-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Key Detail */
|
||||||
|
.key-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-detail-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-detail-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-detail-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-detail-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-detail-value {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-align: right;
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy Button */
|
||||||
|
.copy-button {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
94
clients/admin-ui/styles/main.css
Normal file
94
clients/admin-ui/styles/main.css
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/* ============================================
|
||||||
|
MAIN STYLES - Variables, Base, Layout
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* CSS Variables */
|
||||||
|
:root {
|
||||||
|
--bg-primary: #0f172a;
|
||||||
|
--bg-secondary: #1e293b;
|
||||||
|
--bg-tertiary: #334155;
|
||||||
|
--text-primary: #f1f5f9;
|
||||||
|
--text-secondary: #cbd5e1;
|
||||||
|
--text-muted: #64748b;
|
||||||
|
--border: #334155;
|
||||||
|
--accent: #3b82f6;
|
||||||
|
--accent-hover: #2563eb;
|
||||||
|
--success: #10b981;
|
||||||
|
--warning: #f59e0b;
|
||||||
|
--error: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base Styles */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* App Container */
|
||||||
|
.app-container {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.app-header {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dashboard Layout */
|
||||||
|
.dashboard {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 320px 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Layout */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.dashboard {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar Styling */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--text-muted);
|
||||||
|
}
|
||||||
105
clients/admin-ui/styles/runners.css
Normal file
105
clients/admin-ui/styles/runners.css
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/* ============================================
|
||||||
|
RUNNERS - Runner Management Styles
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Sidebar Island */
|
||||||
|
.sidebar-island {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header h3 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Runner List */
|
||||||
|
.runners-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Runner Cards */
|
||||||
|
.runner-card {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runner-card:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.runner-card.selected {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 2px var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.runner-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runner-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Runner Status */
|
||||||
|
.runner-status {
|
||||||
|
padding: 0.25rem 0.625rem;
|
||||||
|
background: var(--success);
|
||||||
|
color: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runner-status.stopped {
|
||||||
|
background: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.runner-status.error {
|
||||||
|
background: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Runner Info */
|
||||||
|
.runner-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.sidebar-island {
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,8 @@ indexmap = "2.0"
|
|||||||
jsonrpsee = { version = "0.24", features = ["http-client", "macros"] }
|
jsonrpsee = { version = "0.24", features = ["http-client", "macros"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
hero-supervisor = { path = "../.." }
|
hero-supervisor = { path = "../.." }
|
||||||
runner_rust = { git = "https://git.ourworld.tf/herocode/runner_rust.git", branch = "main" }
|
hero-job = { path = "../../../job/rust" }
|
||||||
|
# hero-job = { git = "https://git.ourworld.tf/herocode/job.git", subdirectory = "rust" }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
|
||||||
# WASM-specific dependencies
|
# WASM-specific dependencies
|
||||||
|
|||||||
@@ -158,9 +158,9 @@ pub struct JobStartResponse {
|
|||||||
pub status: String,
|
pub status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-export Job types from runner_rust crate (native only)
|
// Re-export Job types from hero-job crate (native only)
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub use runner_rust::{Job, JobStatus, JobError, JobBuilder, Client, ClientBuilder};
|
pub use hero_job::{Job, JobStatus, JobError, JobBuilder, JobSignature, Client, ClientBuilder};
|
||||||
|
|
||||||
// WASM-compatible Job types (simplified versions)
|
// WASM-compatible Job types (simplified versions)
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Re-export job types from the runner_rust crate
|
// Re-export job types from the hero-job crate
|
||||||
pub use runner_rust::{Job, JobBuilder, JobStatus, JobError, Client, ClientBuilder};
|
pub use hero_job::{Job, JobBuilder, JobStatus, JobError, Client, ClientBuilder};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub mod mycelium;
|
|||||||
pub use runner::{Runner, RunnerConfig, RunnerResult, RunnerStatus};
|
pub use runner::{Runner, RunnerConfig, RunnerResult, RunnerStatus};
|
||||||
// pub use sal_service_manager::{ProcessManager, SimpleProcessManager, TmuxProcessManager};
|
// pub use sal_service_manager::{ProcessManager, SimpleProcessManager, TmuxProcessManager};
|
||||||
pub use supervisor::{Supervisor, SupervisorBuilder, ProcessManagerType};
|
pub use supervisor::{Supervisor, SupervisorBuilder, ProcessManagerType};
|
||||||
pub use runner_rust::{Job, JobBuilder, JobStatus, JobError, Client, ClientBuilder};
|
pub use hero_job::{Job, JobBuilder, JobStatus, JobError, Client, ClientBuilder};
|
||||||
pub use app::SupervisorApp;
|
pub use app::SupervisorApp;
|
||||||
|
|
||||||
#[cfg(feature = "mycelium")]
|
#[cfg(feature = "mycelium")]
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ impl MyceliumIntegration {
|
|||||||
.unwrap_or(60);
|
.unwrap_or(60);
|
||||||
|
|
||||||
// Deserialize the job
|
// Deserialize the job
|
||||||
let job: runner_rust::job::Job = serde_json::from_value(job_value.clone())
|
let job: hero_job::Job = serde_json::from_value(job_value.clone())
|
||||||
.map_err(|e| format!("invalid job format: {}", e))?;
|
.map_err(|e| format!("invalid job format: {}", e))?;
|
||||||
|
|
||||||
let job_id = job.id.clone();
|
let job_id = job.id.clone();
|
||||||
@@ -357,7 +357,7 @@ impl MyceliumIntegration {
|
|||||||
.ok_or("missing job")?;
|
.ok_or("missing job")?;
|
||||||
|
|
||||||
// Deserialize the job
|
// Deserialize the job
|
||||||
let job: runner_rust::job::Job = serde_json::from_value(job_value.clone())
|
let job: hero_job::Job = serde_json::from_value(job_value.clone())
|
||||||
.map_err(|e| format!("invalid job format: {}", e))?;
|
.map_err(|e| format!("invalid job format: {}", e))?;
|
||||||
|
|
||||||
let job_id = job.id.clone();
|
let job_id = job.id.clone();
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ pub enum RunnerError {
|
|||||||
#[error("Job error: {source}")]
|
#[error("Job error: {source}")]
|
||||||
JobError {
|
JobError {
|
||||||
#[from]
|
#[from]
|
||||||
source: runner_rust::JobError,
|
source: hero_job::JobError,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("Job '{job_id}' not found")]
|
#[error("Job '{job_id}' not found")]
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ use tokio::sync::Mutex;
|
|||||||
// use sal_service_manager::{ProcessManager, SimpleProcessManager, TmuxProcessManager};
|
// use sal_service_manager::{ProcessManager, SimpleProcessManager, TmuxProcessManager};
|
||||||
|
|
||||||
use crate::{job::JobStatus, runner::{LogInfo, Runner, RunnerConfig, RunnerError, RunnerResult, RunnerStatus}};
|
use crate::{job::JobStatus, runner::{LogInfo, Runner, RunnerConfig, RunnerError, RunnerResult, RunnerStatus}};
|
||||||
use runner_rust::{Client, ClientBuilder};
|
use hero_job::{Client, ClientBuilder};
|
||||||
|
|
||||||
|
|
||||||
/// Process manager type for a runner
|
/// Process manager type for a runner
|
||||||
@@ -314,21 +314,28 @@ impl Supervisor {
|
|||||||
let job_id = job.id.clone(); // Store job ID before moving job
|
let job_id = job.id.clone(); // Store job ID before moving job
|
||||||
|
|
||||||
if let Some(_runner) = self.runners.get(&runner) {
|
if let Some(_runner) = self.runners.get(&runner) {
|
||||||
// Store job metadata in the database
|
// Store job in Redis with "created" status so it can be retrieved later
|
||||||
|
self.client.store_job_in_redis_with_status(&job, hero_job::JobStatus::Created).await
|
||||||
|
.map_err(|e| RunnerError::QueueError {
|
||||||
|
actor_id: runner.clone(),
|
||||||
|
reason: format!("Failed to store job in Redis: {}", e),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Store job metadata in the database with "created" status
|
||||||
|
// Job will only be queued when explicitly started via start_job
|
||||||
let job_metadata = crate::services::JobMetadata {
|
let job_metadata = crate::services::JobMetadata {
|
||||||
job_id: job_id.clone(),
|
job_id: job_id.clone(),
|
||||||
runner: runner.clone(),
|
runner: runner.clone(),
|
||||||
created_at: chrono::Utc::now().to_rfc3339(),
|
created_at: chrono::Utc::now().to_rfc3339(),
|
||||||
created_by: api_key.name.clone(),
|
created_by: api_key.name.clone(),
|
||||||
status: "queued".to_string(),
|
status: "created".to_string(),
|
||||||
job: job.clone(),
|
job: job.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.services.jobs.store(job_metadata).await
|
self.services.jobs.store(job_metadata).await
|
||||||
.map_err(|e| RunnerError::ConfigError { reason: format!("Failed to store job: {}", e) })?;
|
.map_err(|e| RunnerError::ConfigError { reason: format!("Failed to store job: {}", e) })?;
|
||||||
|
|
||||||
// Use the supervisor's queue_job_to_runner method (fire-and-forget)
|
// Do NOT queue the job to the runner - it will be queued when start_job is called
|
||||||
self.queue_job_to_runner(&runner, job).await?;
|
|
||||||
Ok(job_id) // Return the job ID immediately
|
Ok(job_id) // Return the job ID immediately
|
||||||
} else {
|
} else {
|
||||||
Err(RunnerError::ActorNotFound {
|
Err(RunnerError::ActorNotFound {
|
||||||
@@ -555,11 +562,11 @@ impl Supervisor {
|
|||||||
reason: format!("Failed to connect to Redis: {}", e),
|
reason: format!("Failed to connect to Redis: {}", e),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Store the job in Redis first
|
// Store job in Redis first (will be set to "dispatched" by default)
|
||||||
self.client.store_job_in_redis(&job).await
|
self.client.store_job_in_redis(&job).await
|
||||||
.map_err(|e| RunnerError::QueueError {
|
.map_err(|e| RunnerError::QueueError {
|
||||||
actor_id: runner.id.clone(),
|
actor_id: runner.id.clone(),
|
||||||
reason: format!("Failed to store job: {}", e),
|
reason: format!("Failed to store job in Redis: {}", e),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Use the runner's get_queue method with our namespace
|
// Use the runner's get_queue method with our namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user