Skip to content

Commit 1dafad5

Browse files
committed
add first start mode
1 parent 99accc0 commit 1dafad5

File tree

3 files changed

+301
-9
lines changed

3 files changed

+301
-9
lines changed

FIRST_START_FEATURE.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# First-Start Mode Feature
2+
3+
## Overview
4+
5+
The KEA DHCP Lease Manager now includes a "first-start" mode that prevents the application from hanging during startup when the configuration is unconfigured or set to default values.
6+
7+
## Problem Solved
8+
9+
**Before:** When starting the application with a blank or default configuration pointing to `localhost`, the GUI would hang for a long time trying to connect to a non-existent KEA server, resulting in poor user experience.
10+
11+
**After:** The application now detects unconfigured state and immediately shows the configuration dialog without attempting to connect to KEA, providing a smooth first-time setup experience.
12+
13+
## How It Works
14+
15+
### Backend Detection (`app.py`)
16+
17+
The application now includes an `is_config_valid()` function that detects if the configuration is in an unconfigured state:
18+
19+
```python
20+
def is_config_valid():
21+
"""
22+
Check if the configuration is valid (not in first-start/unconfigured state).
23+
Returns True if config is properly set up, False if it's still using defaults.
24+
"""
25+
kea_url = config['kea']['control_agent_url']
26+
27+
# Check if using default localhost URL or empty URL
28+
if not kea_url or kea_url.strip() == '':
29+
return False
30+
31+
# Check if it's still pointing to localhost (default config)
32+
if 'localhost' in kea_url.lower() or '127.0.0.1' in kea_url:
33+
return False
34+
35+
return True
36+
```
37+
38+
### Unconfigured State Handling
39+
40+
When the configuration is detected as invalid:
41+
42+
1. **Health Check Endpoint** (`/api/health`):
43+
- Returns `status: 'unconfigured'` instead of attempting connection
44+
- No timeout or hanging
45+
- HTTP 200 response with clear message
46+
47+
2. **Leases Endpoint** (`/api/leases`):
48+
- Returns `unconfigured: true` flag
49+
- Friendly error message directing user to configure
50+
- No KEA connection attempt
51+
52+
3. **Subnets Endpoint** (`/api/subnets`):
53+
- Returns empty subnets array with unconfigured flag
54+
- No KEA connection attempt
55+
56+
### Frontend Experience (`index.html`)
57+
58+
On page load, the application:
59+
60+
1. **Checks health status first** before loading any data
61+
2. **Detects unconfigured state** from health response
62+
3. **Shows warning banner** with clear instructions
63+
4. **Automatically opens configuration modal** after 500ms delay
64+
5. **Skips data loading** until properly configured
65+
66+
#### Visual Indicators
67+
68+
- **Connection Status**: Shows "Not Configured" in red
69+
- **Warning Banner**: Yellow alert at top of page with first-time setup message
70+
- **Configuration Modal**: Opens automatically with empty/default values
71+
- **Error Messages**: Shows friendly "not configured" message instead of timeout errors
72+
73+
## User Flow
74+
75+
### First-Time Setup
76+
77+
1. User starts the application with default/blank config
78+
2. Health check detects unconfigured state (instant, no timeout)
79+
3. Yellow warning banner appears at top of page
80+
4. Configuration modal opens automatically after brief delay
81+
5. User enters KEA server details and saves
82+
6. Page reloads with new configuration
83+
7. Application connects to KEA and loads data normally
84+
85+
### Subsequent Starts
86+
87+
- If configuration is valid (not localhost), normal startup occurs
88+
- Health check attempts connection to KEA
89+
- Data loads as expected
90+
91+
## Configuration Validation Rules
92+
93+
The configuration is considered **invalid/unconfigured** if:
94+
95+
- KEA Control Agent URL is empty or blank
96+
- URL contains "localhost" (case-insensitive)
97+
- URL contains "127.0.0.1"
98+
99+
The configuration is considered **valid** if:
100+
101+
- URL points to a non-localhost address (e.g., `http://kea.tux42.au:8000`)
102+
- URL is properly formatted with http:// or https://
103+
104+
## Benefits
105+
106+
**No hanging or timeouts** on first start
107+
**Clear user guidance** with automatic modal opening
108+
**Instant feedback** - no waiting for connection attempts
109+
**Better UX** - users know exactly what to do
110+
**Prevents confusion** - clear unconfigured state vs connection errors
111+
**Faster startup** - skips unnecessary connection attempts
112+
113+
## API Changes
114+
115+
### Health Check Response
116+
117+
**Unconfigured State:**
118+
```json
119+
{
120+
"status": "unconfigured",
121+
"kea_connection": "not_configured",
122+
"message": "KEA server not configured. Please update configuration."
123+
}
124+
```
125+
126+
**Healthy State:**
127+
```json
128+
{
129+
"status": "healthy",
130+
"kea_connection": "ok"
131+
}
132+
```
133+
134+
**Unhealthy State:**
135+
```json
136+
{
137+
"status": "unhealthy",
138+
"kea_connection": "failed",
139+
"error": "Connection refused"
140+
}
141+
```
142+
143+
### Leases/Subnets Response (Unconfigured)
144+
145+
```json
146+
{
147+
"success": false,
148+
"unconfigured": true,
149+
"error": "KEA server not configured. Please update configuration to connect."
150+
}
151+
```
152+
153+
## Testing
154+
155+
To test the first-start mode:
156+
157+
1. **Reset to default config:**
158+
```bash
159+
cp config.yaml.template config.yaml
160+
```
161+
162+
2. **Start the application:**
163+
```bash
164+
python app.py
165+
# or in Docker
166+
docker-compose up
167+
```
168+
169+
3. **Expected behavior:**
170+
- Page loads instantly (no hanging)
171+
- Yellow warning banner appears
172+
- Configuration modal opens automatically
173+
- Status shows "Not Configured"
174+
175+
4. **Configure KEA URL:**
176+
- Enter valid KEA server URL (not localhost)
177+
- Save configuration
178+
- Page reloads and connects normally
179+
180+
## Docker Considerations
181+
182+
When running in Docker with volume-mounted config:
183+
184+
```bash
185+
docker run -p 5000:5000 \
186+
-v $(pwd)/config.yaml:/app/config/config.yaml:ro \
187+
awkto/kea-gui-reservations:latest
188+
```
189+
190+
- If `config.yaml` has default localhost URL, first-start mode activates
191+
- User can configure through web UI, but changes won't persist without writable volume
192+
- For persistent config, mount as read-write: `-v $(pwd)/config.yaml:/app/config/config.yaml:rw`
193+
194+
## Future Enhancements
195+
196+
Possible improvements:
197+
198+
- [ ] Add "skip localhost check" option for legitimate localhost setups
199+
- [ ] Persist "first-start complete" flag to only show banner once
200+
- [ ] Add config validation before save (test KEA connection)
201+
- [ ] Remember last successful config for rollback
202+
- [ ] Add guided setup wizard with multiple steps

app.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ def get_kea_client():
7474
)
7575

7676

77+
def is_config_valid():
78+
"""
79+
Check if the configuration is valid (not in first-start/unconfigured state).
80+
Returns True if config is properly set up, False if it's still using defaults.
81+
"""
82+
kea_url = config['kea']['control_agent_url']
83+
84+
# Check if using default localhost URL or empty URL
85+
if not kea_url or kea_url.strip() == '':
86+
return False
87+
88+
# Check if it's still pointing to localhost (default config)
89+
if 'localhost' in kea_url.lower() or '127.0.0.1' in kea_url:
90+
return False
91+
92+
return True
93+
94+
7795
@app.route('/')
7896
def index():
7997
"""Render the main page"""
@@ -83,6 +101,14 @@ def index():
83101
@app.route('/api/health', methods=['GET'])
84102
def health_check():
85103
"""Health check endpoint"""
104+
# Check if configuration is valid first
105+
if not is_config_valid():
106+
return jsonify({
107+
'status': 'unconfigured',
108+
'kea_connection': 'not_configured',
109+
'message': 'KEA server not configured. Please update configuration.'
110+
}), 200
111+
86112
try:
87113
# Test connection to KEA
88114
client = get_kea_client()
@@ -103,6 +129,14 @@ def health_check():
103129
@app.route('/api/leases', methods=['GET'])
104130
def get_leases():
105131
"""Fetch all DHCPv4 leases"""
132+
# Check if configuration is valid first
133+
if not is_config_valid():
134+
return jsonify({
135+
'success': False,
136+
'unconfigured': True,
137+
'error': 'KEA server not configured. Please update configuration to connect.'
138+
}), 200
139+
106140
try:
107141
client = get_kea_client()
108142
subnet_id = request.args.get('subnet_id', type=int)
@@ -190,6 +224,15 @@ def promote_lease():
190224
@app.route('/api/subnets', methods=['GET'])
191225
def get_subnets():
192226
"""Fetch configured subnets"""
227+
# Check if configuration is valid first
228+
if not is_config_valid():
229+
return jsonify({
230+
'success': False,
231+
'unconfigured': True,
232+
'error': 'KEA server not configured',
233+
'subnets': []
234+
}), 200
235+
193236
try:
194237
client = get_kea_client()
195238
subnets = client.get_subnets()

templates/index.html

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -467,10 +467,19 @@ <h6 class="border-bottom pb-2 mb-3 mt-4">Logging Settings</h6>
467467
deleteReservationModal = new bootstrap.Modal(document.getElementById('deleteReservationModal'));
468468
configModal = new bootstrap.Modal(document.getElementById('configModal'));
469469

470-
checkHealth();
471-
loadSubnets();
472-
loadReservations().then(() => {
473-
loadLeases();
470+
// Check health first to detect unconfigured state
471+
checkHealth().then(healthData => {
472+
if (healthData && healthData.status === 'unconfigured') {
473+
// Show config modal immediately for first-start
474+
showFirstStartBanner();
475+
setTimeout(() => showConfigModal(), 500);
476+
} else {
477+
// Normal startup - load data
478+
loadSubnets();
479+
loadReservations().then(() => {
480+
loadLeases();
481+
});
482+
}
474483
});
475484

476485
// Setup search filter
@@ -490,17 +499,24 @@ <h6 class="border-bottom pb-2 mb-3 mt-4">Logging Settings</h6>
490499
statusEl.classList.remove('offline');
491500
statusEl.classList.add('online');
492501
textEl.textContent = 'Connected to KEA';
502+
} else if (data.status === 'unconfigured') {
503+
statusEl.classList.remove('online');
504+
statusEl.classList.add('offline');
505+
textEl.textContent = 'Not Configured';
493506
} else {
494507
statusEl.classList.remove('online');
495508
statusEl.classList.add('offline');
496509
textEl.textContent = 'Connection failed';
497510
}
511+
512+
return data;
498513
} catch (error) {
499514
const statusEl = document.querySelector('.connection-status');
500515
const textEl = document.getElementById('status-text');
501516
statusEl.classList.remove('online');
502517
statusEl.classList.add('offline');
503518
textEl.textContent = 'Connection error';
519+
return null;
504520
}
505521
}
506522

@@ -566,6 +582,13 @@ <h6 class="border-bottom pb-2 mb-3 mt-4">Logging Settings</h6>
566582
displayLeases(allLeases);
567583
loading.style.display = 'none';
568584
tableContainer.style.display = 'block';
585+
} else if (data.unconfigured) {
586+
// Show unconfigured state message
587+
loading.style.display = 'none';
588+
const errorText = document.getElementById('error-text');
589+
errorText.textContent = data.error;
590+
document.getElementById('setup-help').style.display = 'none';
591+
errorMsg.style.display = 'block';
569592
} else {
570593
throw new Error(data.error || 'Failed to load leases');
571594
}
@@ -969,6 +992,24 @@ <h6 class="border-bottom pb-2 mb-3 mt-4">Logging Settings</h6>
969992
}, 5000);
970993
}
971994

995+
function showFirstStartBanner() {
996+
const banner = document.createElement('div');
997+
banner.id = 'first-start-banner';
998+
banner.className = 'alert alert-warning alert-dismissible fade show';
999+
banner.style.marginBottom = '0';
1000+
banner.innerHTML = `
1001+
<div class="container-fluid">
1002+
<h5><i class="bi bi-exclamation-triangle-fill"></i> First-Time Setup Required</h5>
1003+
<p class="mb-2">Welcome! Your KEA DHCP Lease Manager needs to be configured before use.</p>
1004+
<p class="mb-0">Please configure your KEA server connection in the configuration dialog.</p>
1005+
</div>
1006+
`;
1007+
1008+
// Insert after navbar
1009+
const navbar = document.querySelector('.navbar');
1010+
navbar.insertAdjacentElement('afterend', banner);
1011+
}
1012+
9721013
async function showConfigModal() {
9731014
configModal.show();
9741015

@@ -1052,13 +1093,19 @@ <h6 class="border-bottom pb-2 mb-3 mt-4">Logging Settings</h6>
10521093

10531094
if (data.success) {
10541095
configModal.hide();
1055-
showAlert('success', data.message);
10561096

1057-
// Refresh health check after a short delay
1097+
// Remove first-start banner if it exists
1098+
const banner = document.getElementById('first-start-banner');
1099+
if (banner) {
1100+
banner.remove();
1101+
}
1102+
1103+
showAlert('success', data.message + ' Reloading page...');
1104+
1105+
// Reload the page after a short delay to apply new configuration
10581106
setTimeout(() => {
1059-
checkHealth();
1060-
loadSubnets();
1061-
}, 1000);
1107+
window.location.reload();
1108+
}, 1500);
10621109
} else {
10631110
throw new Error(data.error || 'Failed to save configuration');
10641111
}

0 commit comments

Comments
 (0)