citrascope 0.1.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- citrascope/__main__.py +13 -13
- citrascope/api/abstract_api_client.py +7 -0
- citrascope/api/citra_api_client.py +43 -2
- citrascope/citra_scope_daemon.py +205 -61
- citrascope/constants.py +23 -0
- citrascope/hardware/abstract_astro_hardware_adapter.py +70 -2
- citrascope/hardware/adapter_registry.py +94 -0
- citrascope/hardware/indi_adapter.py +456 -16
- citrascope/hardware/kstars_dbus_adapter.py +179 -0
- citrascope/hardware/nina_adv_http_adapter.py +593 -0
- citrascope/hardware/nina_adv_http_survey_template.json +328 -0
- citrascope/logging/__init__.py +2 -1
- citrascope/logging/_citrascope_logger.py +80 -1
- citrascope/logging/web_log_handler.py +75 -0
- citrascope/settings/citrascope_settings.py +140 -0
- citrascope/settings/settings_file_manager.py +126 -0
- citrascope/tasks/runner.py +129 -29
- citrascope/tasks/scope/base_telescope_task.py +25 -10
- citrascope/tasks/scope/static_telescope_task.py +11 -3
- citrascope/web/__init__.py +1 -0
- citrascope/web/app.py +479 -0
- citrascope/web/server.py +132 -0
- citrascope/web/static/api.js +82 -0
- citrascope/web/static/app.js +502 -0
- citrascope/web/static/config.js +438 -0
- citrascope/web/static/img/citra.png +0 -0
- citrascope/web/static/img/favicon.png +0 -0
- citrascope/web/static/style.css +152 -0
- citrascope/web/static/websocket.js +127 -0
- citrascope/web/templates/dashboard.html +407 -0
- {citrascope-0.1.0.dist-info → citrascope-0.4.0.dist-info}/METADATA +87 -47
- citrascope-0.4.0.dist-info/RECORD +38 -0
- {citrascope-0.1.0.dist-info → citrascope-0.4.0.dist-info}/WHEEL +1 -1
- citrascope/settings/_citrascope_settings.py +0 -42
- citrascope-0.1.0.dist-info/RECORD +0 -21
- docs/index.md +0 -47
- {citrascope-0.1.0.dist-info → citrascope-0.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
// Configuration management for CitraScope
|
|
2
|
+
|
|
3
|
+
import { getConfig, saveConfig, getConfigStatus, getHardwareAdapters, getAdapterSchema } from './api.js';
|
|
4
|
+
|
|
5
|
+
// API Host constants - must match backend constants in app.py
|
|
6
|
+
const PROD_API_HOST = 'api.citra.space';
|
|
7
|
+
const DEV_API_HOST = 'dev.api.citra.space';
|
|
8
|
+
const DEFAULT_API_PORT = 443;
|
|
9
|
+
|
|
10
|
+
let currentAdapterSchema = [];
|
|
11
|
+
export let currentConfig = {};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize configuration management
|
|
15
|
+
*/
|
|
16
|
+
async function fetchVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch('/api/version');
|
|
19
|
+
const data = await response.json();
|
|
20
|
+
const versionEl = document.getElementById('citraScopeVersion');
|
|
21
|
+
if (versionEl && data.version) {
|
|
22
|
+
versionEl.textContent = data.version;
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('Error fetching version:', error);
|
|
26
|
+
const versionEl = document.getElementById('citraScopeVersion');
|
|
27
|
+
if (versionEl) {
|
|
28
|
+
versionEl.textContent = 'unknown';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function initConfig() {
|
|
34
|
+
// Populate hardware adapter dropdown
|
|
35
|
+
await loadAdapterOptions();
|
|
36
|
+
|
|
37
|
+
// Hardware adapter selection change
|
|
38
|
+
const adapterSelect = document.getElementById('hardwareAdapterSelect');
|
|
39
|
+
if (adapterSelect) {
|
|
40
|
+
adapterSelect.addEventListener('change', async function(e) {
|
|
41
|
+
const adapter = e.target.value;
|
|
42
|
+
if (adapter) {
|
|
43
|
+
await loadAdapterSchema(adapter);
|
|
44
|
+
} else {
|
|
45
|
+
document.getElementById('adapter-settings-container').innerHTML = '';
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// API endpoint selection change
|
|
51
|
+
const apiEndpointSelect = document.getElementById('apiEndpoint');
|
|
52
|
+
if (apiEndpointSelect) {
|
|
53
|
+
apiEndpointSelect.addEventListener('change', function(e) {
|
|
54
|
+
const customContainer = document.getElementById('customHostContainer');
|
|
55
|
+
if (e.target.value === 'custom') {
|
|
56
|
+
customContainer.style.display = 'block';
|
|
57
|
+
} else {
|
|
58
|
+
customContainer.style.display = 'none';
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Config form submission
|
|
64
|
+
const configForm = document.getElementById('configForm');
|
|
65
|
+
if (configForm) {
|
|
66
|
+
configForm.addEventListener('submit', saveConfiguration);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Load initial config
|
|
70
|
+
await loadConfiguration();
|
|
71
|
+
checkConfigStatus();
|
|
72
|
+
fetchVersion();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if configuration is needed and show setup wizard if not configured
|
|
77
|
+
*/
|
|
78
|
+
async function checkConfigStatus() {
|
|
79
|
+
try {
|
|
80
|
+
const status = await getConfigStatus();
|
|
81
|
+
|
|
82
|
+
if (!status.configured) {
|
|
83
|
+
// Show setup wizard if not configured
|
|
84
|
+
const wizardModal = new bootstrap.Modal(document.getElementById('setupWizard'));
|
|
85
|
+
wizardModal.show();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (status.error) {
|
|
89
|
+
showConfigError(status.error);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('Failed to check config status:', error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Load available hardware adapters and populate dropdown
|
|
98
|
+
*/
|
|
99
|
+
async function loadAdapterOptions() {
|
|
100
|
+
try {
|
|
101
|
+
const data = await getHardwareAdapters();
|
|
102
|
+
const adapterSelect = document.getElementById('hardwareAdapterSelect');
|
|
103
|
+
|
|
104
|
+
if (adapterSelect && data.adapters) {
|
|
105
|
+
// Clear existing options except the first placeholder
|
|
106
|
+
while (adapterSelect.options.length > 1) {
|
|
107
|
+
adapterSelect.remove(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add options from API
|
|
111
|
+
data.adapters.forEach(adapterName => {
|
|
112
|
+
const option = document.createElement('option');
|
|
113
|
+
option.value = adapterName;
|
|
114
|
+
option.textContent = data.descriptions[adapterName] || adapterName;
|
|
115
|
+
adapterSelect.appendChild(option);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error('Failed to load hardware adapters:', error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Load configuration from API and populate form
|
|
125
|
+
*/
|
|
126
|
+
async function loadConfiguration() {
|
|
127
|
+
try {
|
|
128
|
+
const config = await getConfig();
|
|
129
|
+
currentConfig = config; // Save for reuse when saving
|
|
130
|
+
|
|
131
|
+
// Display config file path
|
|
132
|
+
const configPathElement = document.getElementById('configFilePath');
|
|
133
|
+
if (configPathElement && config.config_file_path) {
|
|
134
|
+
configPathElement.textContent = config.config_file_path;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Display log file path
|
|
138
|
+
const logPathElement = document.getElementById('logFilePath');
|
|
139
|
+
if (logPathElement) {
|
|
140
|
+
if (config.log_file_path) {
|
|
141
|
+
logPathElement.textContent = config.log_file_path;
|
|
142
|
+
} else {
|
|
143
|
+
logPathElement.textContent = 'Disabled';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Display images directory path
|
|
148
|
+
const imagesDirElement = document.getElementById('imagesDirPath');
|
|
149
|
+
if (imagesDirElement && config.images_dir_path) {
|
|
150
|
+
imagesDirElement.textContent = config.images_dir_path;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// API endpoint selector
|
|
154
|
+
const apiEndpointSelect = document.getElementById('apiEndpoint');
|
|
155
|
+
const customHostContainer = document.getElementById('customHostContainer');
|
|
156
|
+
const customHost = document.getElementById('customHost');
|
|
157
|
+
const customPort = document.getElementById('customPort');
|
|
158
|
+
const customUseSsl = document.getElementById('customUseSsl');
|
|
159
|
+
|
|
160
|
+
if (config.host === PROD_API_HOST) {
|
|
161
|
+
apiEndpointSelect.value = 'production';
|
|
162
|
+
customHostContainer.style.display = 'none';
|
|
163
|
+
} else if (config.host === DEV_API_HOST) {
|
|
164
|
+
apiEndpointSelect.value = 'development';
|
|
165
|
+
customHostContainer.style.display = 'none';
|
|
166
|
+
} else {
|
|
167
|
+
apiEndpointSelect.value = 'custom';
|
|
168
|
+
customHostContainer.style.display = 'block';
|
|
169
|
+
customHost.value = config.host || '';
|
|
170
|
+
customPort.value = config.port || DEFAULT_API_PORT;
|
|
171
|
+
customUseSsl.checked = config.use_ssl !== undefined ? config.use_ssl : true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Core fields
|
|
175
|
+
document.getElementById('personal_access_token').value = config.personal_access_token || '';
|
|
176
|
+
document.getElementById('telescopeId').value = config.telescope_id || '';
|
|
177
|
+
document.getElementById('hardwareAdapterSelect').value = config.hardware_adapter || '';
|
|
178
|
+
document.getElementById('logLevel').value = config.log_level || 'INFO';
|
|
179
|
+
document.getElementById('keep_images').checked = config.keep_images || false;
|
|
180
|
+
document.getElementById('file_logging_enabled').checked = config.file_logging_enabled !== undefined ? config.file_logging_enabled : true;
|
|
181
|
+
|
|
182
|
+
// Load adapter-specific settings if adapter is selected
|
|
183
|
+
if (config.hardware_adapter) {
|
|
184
|
+
await loadAdapterSchema(config.hardware_adapter);
|
|
185
|
+
populateAdapterSettings(config.adapter_settings || {});
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Failed to load config:', error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Load adapter schema and render settings form
|
|
194
|
+
*/
|
|
195
|
+
async function loadAdapterSchema(adapterName) {
|
|
196
|
+
try {
|
|
197
|
+
const data = await getAdapterSchema(adapterName);
|
|
198
|
+
currentAdapterSchema = data.schema || [];
|
|
199
|
+
renderAdapterSettings(currentAdapterSchema);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error('Failed to load adapter schema:', error);
|
|
202
|
+
showConfigError(`Failed to load settings for ${adapterName}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Render adapter-specific settings form
|
|
208
|
+
*/
|
|
209
|
+
function renderAdapterSettings(schema) {
|
|
210
|
+
const container = document.getElementById('adapter-settings-container');
|
|
211
|
+
|
|
212
|
+
if (!schema || schema.length === 0) {
|
|
213
|
+
container.innerHTML = '';
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let html = '<h5 class="mb-3">Adapter Settings</h5><div class="row g-3 mb-4">';
|
|
218
|
+
|
|
219
|
+
schema.forEach(field => {
|
|
220
|
+
const isRequired = field.required ? '<span class="text-danger">*</span>' : '';
|
|
221
|
+
const placeholder = field.placeholder || '';
|
|
222
|
+
const description = field.description || '';
|
|
223
|
+
const displayName = field.friendly_name || field.name;
|
|
224
|
+
|
|
225
|
+
html += '<div class="col-12 col-md-6">';
|
|
226
|
+
html += `<label for="adapter_${field.name}" class="form-label">${displayName} ${isRequired}</label>`;
|
|
227
|
+
|
|
228
|
+
if (field.type === 'bool') {
|
|
229
|
+
html += `<div class="form-check mt-2">`;
|
|
230
|
+
html += `<input class="form-check-input adapter-setting" type="checkbox" id="adapter_${field.name}" data-field="${field.name}" data-type="${field.type}">`;
|
|
231
|
+
html += `<label class="form-check-label" for="adapter_${field.name}">${description}</label>`;
|
|
232
|
+
html += `</div>`;
|
|
233
|
+
} else if (field.options && field.options.length > 0) {
|
|
234
|
+
const displayName = field.friendly_name || field.name;
|
|
235
|
+
html += `<select id="adapter_${field.name}" class="form-select adapter-setting" data-field="${field.name}" data-type="${field.type}" ${field.required ? 'required' : ''}>`;
|
|
236
|
+
html += `<option value="">-- Select ${displayName} --</option>`;
|
|
237
|
+
field.options.forEach(opt => {
|
|
238
|
+
html += `<option value="${opt}">${opt}</option>`;
|
|
239
|
+
});
|
|
240
|
+
html += `</select>`;
|
|
241
|
+
} else if (field.type === 'int' || field.type === 'float') {
|
|
242
|
+
const min = field.min !== undefined ? `min="${field.min}"` : '';
|
|
243
|
+
const max = field.max !== undefined ? `max="${field.max}"` : '';
|
|
244
|
+
html += `<input type="number" id="adapter_${field.name}" class="form-control adapter-setting" `;
|
|
245
|
+
html += `data-field="${field.name}" data-type="${field.type}" `;
|
|
246
|
+
html += `placeholder="${placeholder}" ${min} ${max} ${field.required ? 'required' : ''}>`;
|
|
247
|
+
} else {
|
|
248
|
+
// Default to text input
|
|
249
|
+
const pattern = field.pattern ? `pattern="${field.pattern}"` : '';
|
|
250
|
+
html += `<input type="text" id="adapter_${field.name}" class="form-control adapter-setting" `;
|
|
251
|
+
html += `data-field="${field.name}" data-type="${field.type}" `;
|
|
252
|
+
html += `placeholder="${placeholder}" ${pattern} ${field.required ? 'required' : ''}>`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (description && field.type !== 'bool') {
|
|
256
|
+
html += `<small class="text-muted">${description}</small>`;
|
|
257
|
+
}
|
|
258
|
+
html += '</div>';
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
html += '</div>';
|
|
262
|
+
container.innerHTML = html;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Populate adapter settings with values
|
|
267
|
+
*/
|
|
268
|
+
function populateAdapterSettings(adapterSettings) {
|
|
269
|
+
Object.entries(adapterSettings).forEach(([key, value]) => {
|
|
270
|
+
const input = document.getElementById(`adapter_${key}`);
|
|
271
|
+
if (input) {
|
|
272
|
+
if (input.type === 'checkbox') {
|
|
273
|
+
input.checked = value;
|
|
274
|
+
} else {
|
|
275
|
+
input.value = value;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Collect adapter settings from form
|
|
283
|
+
*/
|
|
284
|
+
function collectAdapterSettings() {
|
|
285
|
+
const settings = {};
|
|
286
|
+
const inputs = document.querySelectorAll('.adapter-setting');
|
|
287
|
+
|
|
288
|
+
inputs.forEach(input => {
|
|
289
|
+
const fieldName = input.dataset.field;
|
|
290
|
+
const fieldType = input.dataset.type;
|
|
291
|
+
let value;
|
|
292
|
+
|
|
293
|
+
if (input.type === 'checkbox') {
|
|
294
|
+
value = input.checked;
|
|
295
|
+
} else {
|
|
296
|
+
value = input.value;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Type conversion
|
|
300
|
+
if (value !== '' && value !== null) {
|
|
301
|
+
if (fieldType === 'int') {
|
|
302
|
+
value = parseInt(value, 10);
|
|
303
|
+
} else if (fieldType === 'float') {
|
|
304
|
+
value = parseFloat(value);
|
|
305
|
+
} else if (fieldType === 'bool') {
|
|
306
|
+
// Already handled above
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
settings[fieldName] = value;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return settings;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Save configuration form handler
|
|
318
|
+
*/
|
|
319
|
+
async function saveConfiguration(event) {
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
|
|
322
|
+
const saveButton = document.getElementById('saveConfigButton');
|
|
323
|
+
const buttonText = document.getElementById('saveButtonText');
|
|
324
|
+
const spinner = document.getElementById('saveButtonSpinner');
|
|
325
|
+
|
|
326
|
+
// Show loading state
|
|
327
|
+
saveButton.disabled = true;
|
|
328
|
+
spinner.style.display = 'inline-block';
|
|
329
|
+
buttonText.textContent = 'Saving...';
|
|
330
|
+
|
|
331
|
+
// Hide previous messages
|
|
332
|
+
hideConfigMessages();
|
|
333
|
+
|
|
334
|
+
// Determine API host settings based on endpoint selection
|
|
335
|
+
const apiEndpoint = document.getElementById('apiEndpoint').value;
|
|
336
|
+
let host, port, use_ssl;
|
|
337
|
+
|
|
338
|
+
if (apiEndpoint === 'production') {
|
|
339
|
+
host = PROD_API_HOST;
|
|
340
|
+
port = DEFAULT_API_PORT;
|
|
341
|
+
use_ssl = true;
|
|
342
|
+
} else if (apiEndpoint === 'development') {
|
|
343
|
+
host = DEV_API_HOST;
|
|
344
|
+
port = DEFAULT_API_PORT;
|
|
345
|
+
use_ssl = true;
|
|
346
|
+
} else { // custom
|
|
347
|
+
host = document.getElementById('customHost').value;
|
|
348
|
+
port = parseInt(document.getElementById('customPort').value, 10);
|
|
349
|
+
use_ssl = document.getElementById('customUseSsl').checked;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const config = {
|
|
353
|
+
personal_access_token: document.getElementById('personal_access_token').value,
|
|
354
|
+
telescope_id: document.getElementById('telescopeId').value,
|
|
355
|
+
hardware_adapter: document.getElementById('hardwareAdapterSelect').value,
|
|
356
|
+
adapter_settings: collectAdapterSettings(),
|
|
357
|
+
log_level: document.getElementById('logLevel').value,
|
|
358
|
+
keep_images: document.getElementById('keep_images').checked,
|
|
359
|
+
file_logging_enabled: document.getElementById('file_logging_enabled').checked,
|
|
360
|
+
// API settings from endpoint selector
|
|
361
|
+
host: host,
|
|
362
|
+
port: port,
|
|
363
|
+
use_ssl: use_ssl,
|
|
364
|
+
// Preserve other settings from loaded config
|
|
365
|
+
max_task_retries: currentConfig.max_task_retries || 3,
|
|
366
|
+
initial_retry_delay_seconds: currentConfig.initial_retry_delay_seconds || 30,
|
|
367
|
+
max_retry_delay_seconds: currentConfig.max_retry_delay_seconds || 300,
|
|
368
|
+
log_retention_days: currentConfig.log_retention_days || 30,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
const result = await saveConfig(config);
|
|
373
|
+
|
|
374
|
+
if (result.ok) {
|
|
375
|
+
showConfigSuccess(result.data.message || 'Configuration saved and applied successfully!');
|
|
376
|
+
} else {
|
|
377
|
+
showConfigError(result.data.error || result.data.message || 'Failed to save configuration');
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
showConfigError('Failed to save configuration: ' + error.message);
|
|
381
|
+
} finally {
|
|
382
|
+
// Reset button state
|
|
383
|
+
saveButton.disabled = false;
|
|
384
|
+
spinner.style.display = 'none';
|
|
385
|
+
buttonText.textContent = 'Save Configuration';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Show configuration error message
|
|
391
|
+
*/
|
|
392
|
+
function showConfigError(message) {
|
|
393
|
+
const errorDiv = document.getElementById('configError');
|
|
394
|
+
errorDiv.textContent = message;
|
|
395
|
+
errorDiv.style.display = 'block';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Show configuration success message
|
|
400
|
+
*/
|
|
401
|
+
function showConfigSuccess(message) {
|
|
402
|
+
const successDiv = document.getElementById('configSuccess');
|
|
403
|
+
successDiv.textContent = message;
|
|
404
|
+
successDiv.style.display = 'block';
|
|
405
|
+
|
|
406
|
+
// Auto-hide after 5 seconds
|
|
407
|
+
setTimeout(() => {
|
|
408
|
+
successDiv.style.display = 'none';
|
|
409
|
+
}, 5000);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Hide all configuration messages
|
|
414
|
+
*/
|
|
415
|
+
function hideConfigMessages() {
|
|
416
|
+
document.getElementById('configError').style.display = 'none';
|
|
417
|
+
document.getElementById('configSuccess').style.display = 'none';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Show configuration section (called from setup wizard)
|
|
422
|
+
*/
|
|
423
|
+
export function showConfigSection() {
|
|
424
|
+
// Close setup wizard modal
|
|
425
|
+
const wizardModal = bootstrap.Modal.getInstance(document.getElementById('setupWizard'));
|
|
426
|
+
if (wizardModal) {
|
|
427
|
+
wizardModal.hide();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Show config section
|
|
431
|
+
const configLink = document.querySelector('a[data-section="config"]');
|
|
432
|
+
if (configLink) {
|
|
433
|
+
configLink.click();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Make showConfigSection available globally for onclick handlers in HTML
|
|
438
|
+
window.showConfigSection = showConfigSection;
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* CitraScope Web Interface - Custom Styles
|
|
2
|
+
* Bootstrap 5 handles most styling; these are overrides and additions only
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/* Log terminal container - custom scrollbar and background watermark */
|
|
6
|
+
.log-container {
|
|
7
|
+
background: #0d1117 url('/static/img/citra.png') no-repeat 85% center;
|
|
8
|
+
background-size: auto 70%;
|
|
9
|
+
background-blend-mode: soft-light;
|
|
10
|
+
opacity: 1;
|
|
11
|
+
font-family: monospace;
|
|
12
|
+
line-height: 1.5;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Dark scrollbar for log container */
|
|
16
|
+
.log-container::-webkit-scrollbar {
|
|
17
|
+
width: 12px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.log-container::-webkit-scrollbar-track {
|
|
21
|
+
background: #0d1117;
|
|
22
|
+
border-radius: 5px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.log-container::-webkit-scrollbar-thumb {
|
|
26
|
+
background: #30363d;
|
|
27
|
+
border-radius: 5px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.log-container::-webkit-scrollbar-thumb:hover {
|
|
31
|
+
background: #484f58;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Body padding for fixed bottom log terminal */
|
|
35
|
+
body {
|
|
36
|
+
padding-bottom: 100px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Logo styling */
|
|
40
|
+
.logo-img {
|
|
41
|
+
height: 1.5em;
|
|
42
|
+
width: auto;
|
|
43
|
+
vertical-align: middle;
|
|
44
|
+
display: inline-block;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Status badge container */
|
|
48
|
+
.status-badge-container {
|
|
49
|
+
gap: 0.5em;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Muted text color for placeholders */
|
|
53
|
+
.text-muted-dark {
|
|
54
|
+
color: #a0aec0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Log accordion customization */
|
|
58
|
+
.log-accordion-button {
|
|
59
|
+
border-bottom: 1px solid #444 !important;
|
|
60
|
+
position: relative;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.accordion-social-links {
|
|
64
|
+
position: absolute;
|
|
65
|
+
right: 50px; /* Position to the left of the accordion arrow */
|
|
66
|
+
top: 50%;
|
|
67
|
+
transform: translateY(-50%);
|
|
68
|
+
display: flex;
|
|
69
|
+
gap: 12px;
|
|
70
|
+
align-items: center;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.social-link {
|
|
74
|
+
color: #a0aec0;
|
|
75
|
+
transition: color 0.2s ease;
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
text-decoration: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.social-link:hover {
|
|
82
|
+
color: #e2e8f0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.log-accordion-body {
|
|
86
|
+
max-height: 40vh;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.log-container {
|
|
91
|
+
max-height: 40vh;
|
|
92
|
+
overflow-y: auto;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.log-latest-line {
|
|
96
|
+
font-family: monospace;
|
|
97
|
+
color: #e2e8f0;
|
|
98
|
+
display: block;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
text-overflow: ellipsis;
|
|
101
|
+
display: -webkit-box;
|
|
102
|
+
-webkit-line-clamp: 2;
|
|
103
|
+
-webkit-box-orient: vertical;
|
|
104
|
+
line-height: 1.4em;
|
|
105
|
+
max-height: 2.8em;
|
|
106
|
+
padding-right: 130px; /* Make room for 3 social icons and accordion arrow */
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Task display */
|
|
110
|
+
.task-title {
|
|
111
|
+
font-size: 1.1em;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.no-task-message {
|
|
115
|
+
color: #a0aec0;
|
|
116
|
+
margin: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Ground station link */
|
|
120
|
+
.ground-station-link {
|
|
121
|
+
color: #4299e1;
|
|
122
|
+
text-decoration: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.ground-station-link:hover {
|
|
126
|
+
color: #63b3ed;
|
|
127
|
+
text-decoration: underline;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Log entry components */
|
|
131
|
+
.log-entry {
|
|
132
|
+
margin-bottom: 4px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.log-timestamp {
|
|
136
|
+
color: #a0aec0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.log-level {
|
|
140
|
+
font-weight: bold;
|
|
141
|
+
margin: 0 8px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.log-level-DEBUG { color: #a0aec0; }
|
|
145
|
+
.log-level-INFO { color: #48bb78; }
|
|
146
|
+
.log-level-WARNING { color: #f6ad55; }
|
|
147
|
+
.log-level-ERROR { color: #f56565; }
|
|
148
|
+
.log-level-CRITICAL { color: #c53030; }
|
|
149
|
+
|
|
150
|
+
.log-message {
|
|
151
|
+
color: #e2e8f0;
|
|
152
|
+
}
|