Skip to content

Commit a032259

Browse files
authored
Code duplication fixes
1 parent 6bbdac5 commit a032259

2 files changed

Lines changed: 113 additions & 116 deletions

File tree

config/var/www/admin/control-panel/external-services/external-services.js

Lines changed: 108 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -388,17 +388,12 @@ export class ExternalServicesManager {
388388
settingsContent.appendChild(saveButton);
389389
}
390390

391+
// ============ Card Creation Helpers ============
392+
391393
/**
392-
* Display static service card (no API/feed)
394+
* Create service card header (icon + info)
393395
*/
394-
displayStaticServiceCard(container, serviceKey, serviceDef) {
395-
const serviceLink = document.createElement("a");
396-
serviceLink.href = serviceDef.url;
397-
serviceLink.target = "_blank";
398-
serviceLink.rel = "noopener noreferrer";
399-
serviceLink.className = "external-service-card static";
400-
serviceLink.dataset.serviceKey = serviceKey;
401-
396+
createServiceCardHeader(serviceDef, statusClassName, statusContent) {
402397
const headerDiv = document.createElement("div");
403398
headerDiv.className = "service-header";
404399

@@ -413,79 +408,101 @@ export class ExternalServicesManager {
413408
h3.textContent = serviceDef.name;
414409

415410
const statusSpan = document.createElement("span");
416-
statusSpan.className = "service-status status-info";
417-
statusSpan.innerHTML = `<i class="fas fa-external-link-alt"></i> `;
418-
statusSpan.appendChild(document.createTextNode(serviceDef.statusText || 'Visit status page'));
411+
statusSpan.className = `service-status ${statusClassName}`;
412+
statusSpan.innerHTML = statusContent;
419413

420414
infoDiv.appendChild(h3);
421415
infoDiv.appendChild(statusSpan);
422416
headerDiv.appendChild(iconDiv);
423417
headerDiv.appendChild(infoDiv);
424-
serviceLink.appendChild(headerDiv);
425418

426-
container.appendChild(serviceLink);
419+
return headerDiv;
427420
}
428421

429422
/**
430-
* Display service card with loading state
423+
* Create base service card element
431424
*/
432-
displayServiceCardWithLoadingState(container, serviceKey, serviceDef) {
425+
createBaseServiceCard(serviceKey, serviceDef, cardClass, headerElement) {
433426
const serviceLink = document.createElement("a");
434427
serviceLink.href = serviceDef.url;
435428
serviceLink.target = "_blank";
436429
serviceLink.rel = "noopener noreferrer";
437-
serviceLink.className = "external-service-card loading";
430+
serviceLink.className = `external-service-card ${cardClass}`;
438431
serviceLink.dataset.serviceKey = serviceKey;
432+
serviceLink.appendChild(headerElement);
433+
return serviceLink;
434+
}
435+
436+
/**
437+
* Display static service card (no API/feed)
438+
*/
439+
displayStaticServiceCard(container, serviceKey, serviceDef) {
440+
const statusContent = `<i class="fas fa-external-link-alt"></i> `;
441+
const contentNode = document.createTextNode(serviceDef.statusText || 'Visit status page');
442+
const headerDiv = this.createServiceCardHeader(serviceDef, "status-info", statusContent);
443+
const statusSpan = headerDiv.querySelector(".service-status");
444+
statusSpan.appendChild(contentNode);
439445

440-
const headerDiv = document.createElement("div");
441-
headerDiv.className = "service-header";
442-
443-
const iconDiv = document.createElement("div");
444-
iconDiv.className = `service-icon ${serviceDef.color}`;
445-
iconDiv.innerHTML = `<i class="fas ${serviceDef.icon}"></i>`;
446-
447-
const infoDiv = document.createElement("div");
448-
infoDiv.className = "service-info";
449-
450-
const h3 = document.createElement("h3");
451-
h3.textContent = serviceDef.name;
452-
453-
const statusSpan = document.createElement("span");
454-
statusSpan.className = "service-status status-loading";
455-
statusSpan.innerHTML = `<i class="fas fa-spinner fa-spin"></i> `;
456-
statusSpan.appendChild(document.createTextNode("Loading..."));
457-
458-
infoDiv.appendChild(h3);
459-
infoDiv.appendChild(statusSpan);
460-
headerDiv.appendChild(iconDiv);
461-
headerDiv.appendChild(infoDiv);
462-
serviceLink.appendChild(headerDiv);
446+
const serviceLink = this.createBaseServiceCard(serviceKey, serviceDef, "static", headerDiv);
447+
container.appendChild(serviceLink);
448+
}
449+
450+
/**
451+
* Display service card with loading state
452+
*/
453+
displayServiceCardWithLoadingState(container, serviceKey, serviceDef) {
454+
const statusContent = `<i class="fas fa-spinner fa-spin"></i> `;
455+
const contentNode = document.createTextNode("Loading...");
456+
const headerDiv = this.createServiceCardHeader(serviceDef, "status-loading", statusContent);
457+
const statusSpan = headerDiv.querySelector(".service-status");
458+
statusSpan.appendChild(contentNode);
463459

460+
const serviceLink = this.createBaseServiceCard(serviceKey, serviceDef, "loading", headerDiv);
464461
container.appendChild(serviceLink);
465462
}
466463

464+
// ============ Status Update Helpers ============
465+
467466
/**
468-
* Update feed-based service status asynchronously
467+
* Extract and determine status display values
469468
*/
470-
async updateFeedServiceStatus(serviceKey, serviceDef) {
471-
try {
472-
// Check cache first
473-
let data = this.getCachedService(serviceKey);
469+
getStatusDisplayValues(statusIndicator) {
470+
const statusClass = statusIndicator === "none" ? "operational" : statusIndicator;
471+
const statusIcon = statusClass === "operational" ? "check-circle" : "exclamation-triangle";
472+
const statusColor = statusClass === "operational" ? "success" : statusClass === "minor" ? "warning" : "error";
473+
return { statusClass, statusIcon, statusColor };
474+
}
475+
476+
/**
477+
* Update service card with status data
478+
*/
479+
updateServiceCardStatus(serviceCard, statusDescription, statusClass, statusIcon, statusColor) {
480+
// Update card class
481+
serviceCard.classList.remove("loading", "error");
482+
483+
// Update status span
484+
const statusSpan = serviceCard.querySelector(".service-status");
485+
if (statusSpan) {
486+
statusSpan.className = `service-status status-${statusColor}`;
487+
statusSpan.innerHTML = `<i class="fas fa-${statusIcon}"></i> `;
488+
statusSpan.appendChild(document.createTextNode(this.utils.sanitizeInput(statusDescription)));
489+
}
490+
}
491+
492+
/**
493+
* Fetch data with timeout and caching support
494+
*/
495+
async fetchServiceData(fetchFn, serviceKey) {
496+
// Check cache first
497+
let data = this.getCachedService(serviceKey);
498+
499+
if (!data) {
500+
// Not in cache, fetch from API
501+
const controller = new AbortController();
502+
const timeoutId = setTimeout(() => controller.abort(), 60000);
474503

475-
if (!data) {
476-
// Not in cache, fetch from API
477-
const controller = new AbortController();
478-
const timeoutId = setTimeout(() => controller.abort(), 60000);
479-
480-
let apiUrl = `/api/external-services/feed?feed=${encodeURIComponent(serviceDef.feedType)}`;
481-
if (serviceDef.feedFilter) {
482-
apiUrl += `&filter=${encodeURIComponent(serviceDef.feedFilter)}`;
483-
}
484-
485-
const response = await fetch(apiUrl, {
486-
signal: controller.signal,
487-
credentials: 'include'
488-
});
504+
try {
505+
const response = await fetchFn(controller.signal);
489506
clearTimeout(timeoutId);
490507

491508
if (!response.ok) {
@@ -496,7 +513,31 @@ export class ExternalServicesManager {
496513

497514
// Cache the response
498515
this.setCachedService(serviceKey, data);
516+
} catch (error) {
517+
clearTimeout(timeoutId);
518+
throw error;
499519
}
520+
}
521+
522+
return data;
523+
}
524+
525+
/**
526+
* Update feed-based service status asynchronously
527+
*/
528+
async updateFeedServiceStatus(serviceKey, serviceDef) {
529+
try {
530+
const data = await this.fetchServiceData((signal) => {
531+
let apiUrl = `/api/external-services/feed?feed=${encodeURIComponent(serviceDef.feedType)}`;
532+
if (serviceDef.feedFilter) {
533+
apiUrl += `&filter=${encodeURIComponent(serviceDef.feedFilter)}`;
534+
}
535+
536+
return fetch(apiUrl, {
537+
signal: signal,
538+
credentials: 'include'
539+
});
540+
}, serviceKey);
500541

501542
if (!data || !data.status) {
502543
throw new Error('Invalid feed response format');
@@ -509,20 +550,8 @@ export class ExternalServicesManager {
509550
return;
510551
}
511552

512-
const statusClass = data.status.indicator === "none" ? "operational" : data.status.indicator;
513-
const statusIcon = statusClass === "operational" ? "check-circle" : "exclamation-triangle";
514-
const statusColor = statusClass === "operational" ? "success" : statusClass === "minor" ? "warning" : "error";
515-
516-
// Update card class
517-
serviceCard.classList.remove("loading", "error");
518-
519-
// Update status span
520-
const statusSpan = serviceCard.querySelector(".service-status");
521-
if (statusSpan) {
522-
statusSpan.className = `service-status status-${statusColor}`;
523-
statusSpan.innerHTML = `<i class="fas fa-${statusIcon}"></i> `;
524-
statusSpan.appendChild(document.createTextNode(this.utils.sanitizeInput(data.status.description)));
525-
}
553+
const { statusClass, statusIcon, statusColor } = this.getStatusDisplayValues(data.status.indicator);
554+
this.updateServiceCardStatus(serviceCard, data.status.description, statusClass, statusIcon, statusColor);
526555
} catch (error) {
527556
console.error(`Failed to load ${serviceDef.name} feed status:`, error);
528557
this.handleServiceError(serviceKey, serviceDef, error);
@@ -534,28 +563,11 @@ export class ExternalServicesManager {
534563
*/
535564
async updateStatusPageServiceStatus(serviceKey, serviceDef) {
536565
try {
537-
// Check cache first
538-
let data = this.getCachedService(serviceKey);
539-
540-
if (!data) {
541-
// Not in cache, fetch from API
542-
const controller = new AbortController();
543-
const timeoutId = setTimeout(() => controller.abort(), 60000);
544-
545-
const response = await fetch(serviceDef.api, {
546-
signal: controller.signal
566+
const data = await this.fetchServiceData((signal) => {
567+
return fetch(serviceDef.api, {
568+
signal: signal
547569
});
548-
clearTimeout(timeoutId);
549-
550-
if (!response.ok) {
551-
throw new Error(`HTTP error! status: ${response.status}`);
552-
}
553-
554-
data = await response.json();
555-
556-
// Cache the response
557-
this.setCachedService(serviceKey, data);
558-
}
570+
}, serviceKey);
559571

560572
if (!data || !data.status || !data.status.indicator) {
561573
throw new Error('Invalid API response format');
@@ -568,20 +580,8 @@ export class ExternalServicesManager {
568580
return;
569581
}
570582

571-
const statusClass = data.status.indicator === "none" ? "operational" : data.status.indicator;
572-
const statusIcon = statusClass === "operational" ? "check-circle" : "exclamation-triangle";
573-
const statusColor = statusClass === "operational" ? "success" : statusClass === "minor" ? "warning" : "error";
574-
575-
// Update card class
576-
serviceCard.classList.remove("loading", "error");
577-
578-
// Update status span
579-
const statusSpan = serviceCard.querySelector(".service-status");
580-
if (statusSpan) {
581-
statusSpan.className = `service-status status-${statusColor}`;
582-
statusSpan.innerHTML = `<i class="fas fa-${statusIcon}"></i> `;
583-
statusSpan.appendChild(document.createTextNode(this.utils.sanitizeInput(data.status.description)));
584-
}
583+
const { statusClass, statusIcon, statusColor } = this.getStatusDisplayValues(data.status.indicator);
584+
this.updateServiceCardStatus(serviceCard, data.status.description, statusClass, statusIcon, statusColor);
585585
} catch (error) {
586586
console.error(`Failed to load ${serviceDef.name} status:`, error);
587587
this.handleServiceError(serviceKey, serviceDef, error);
@@ -773,14 +773,12 @@ export class ExternalServicesManager {
773773
enableServiceDragDrop(container) {
774774
const serviceCards = container.querySelectorAll('.external-service-card');
775775
let draggedElement = null;
776-
let draggedServiceKey = null;
777776

778777
serviceCards.forEach(card => {
779778
card.draggable = true;
780779

781780
card.addEventListener('dragstart', (e) => {
782781
draggedElement = card;
783-
draggedServiceKey = card.dataset.serviceKey;
784782
card.classList.add('dragging');
785783
e.dataTransfer.effectAllowed = 'move';
786784
e.dataTransfer.setData('text/html', card.innerHTML);
@@ -818,7 +816,6 @@ export class ExternalServicesManager {
818816
targetCard.classList.remove('drag-over');
819817

820818
// Swap positions
821-
const targetServiceKey = targetCard.dataset.serviceKey;
822819
const allCards = Array.from(container.querySelectorAll('.external-service-card'));
823820
const draggedIndex = allCards.indexOf(draggedElement);
824821
const targetIndex = allCards.indexOf(targetCard);

config/var/www/admin/control-panel/index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/{FONTAWESOME_VER}/css/all.min.css" rel="stylesheet" crossorigin="anonymous">
1313

1414
<!-- Custom Styles -->
15-
<link rel="stylesheet" href="dashboard.css?v=2025.11.21.10">
16-
<link rel="stylesheet" href="external-services/external-services.css?v=2025.11.21.10">
15+
<link rel="stylesheet" href="dashboard.css?v=2025.11.21.12">
16+
<link rel="stylesheet" href="external-services/external-services.css?v=2025.11.21.12">
1717

1818
<!-- Preload Critical Resources -->
19-
<link rel="modulepreload" href="dashboard.js?v=2025.11.21.10" crossorigin="use-credentials">
20-
<link rel="modulepreload" href="modules/api.js?v=2025.11.21.10" crossorigin="use-credentials">
19+
<link rel="modulepreload" href="dashboard.js?v=2025.11.21.12" crossorigin="use-credentials">
20+
<link rel="modulepreload" href="modules/api.js?v=2025.11.21.12" crossorigin="use-credentials">
2121
</head>
2222
<body>
2323
<!-- Loading Screen -->
@@ -374,6 +374,6 @@ <h3>Uptime Robot</h3>
374374
</div>
375375

376376
<!-- Scripts -->
377-
<script type="module" src="dashboard.js?v=2025.11.21.10" data-cfasync="false"></script>
377+
<script type="module" src="dashboard.js?v=2025.11.21.12" data-cfasync="false"></script>
378378
</body>
379379
</html>

0 commit comments

Comments
 (0)