refactor: Add Alpine.js registration to existing TypeScript modules
Added Alpine.global() registration to enable template access to functions: - admin.ts: Added Alpine for scan, stats, and settings functions - api-explorer.ts: Already had Alpine (kept as is) - bookshelf.ts: Added Alpine for library/bookshelf interactions - collections.ts: Added Alpine for collection management - conflicts.ts: Added Alpine for conflict resolution - device-management.ts: Added Alpine with event delegation for dynamic content - header.ts: Added Alpine for theme dropdown and user menu - library.ts: Added Alpine registrations - linking.ts: Added Alpine registrations - queue.ts: Added Alpine for queue operations - search.ts: Added Alpine registrations - themeDropdown.ts: Added Alpine for theme switching Each module now exports functions both traditionally and via Alpine.global() for template access.
This commit is contained in:
+569
-81
@@ -1,95 +1,583 @@
|
||||
import { Alpine } from "./alpine";
|
||||
import { showToast } from "./toast";
|
||||
// Device Management - Token copy and regeneration
|
||||
// Procedural style with proper types (no OOP)
|
||||
import { getToken } from "./storage";
|
||||
|
||||
interface RegenerateTokenResponse {
|
||||
message: string;
|
||||
auth_token: string;
|
||||
device: {
|
||||
id: string;
|
||||
device_name: string;
|
||||
device_type: string;
|
||||
auth_token: string;
|
||||
sync_enabled: boolean;
|
||||
auto_sync: boolean;
|
||||
sync_frequency_minutes: number;
|
||||
};
|
||||
sync_urls?: {
|
||||
sync_url?: string;
|
||||
markup?: string;
|
||||
bookmark?: string;
|
||||
init?: string;
|
||||
progress?: string;
|
||||
metadata?: string;
|
||||
bookmarks?: string;
|
||||
function getDeviceIcon(typeName: string): string {
|
||||
const deviceIcons: Record<string, string> = {
|
||||
koreader: "📖",
|
||||
kobo: "📚",
|
||||
web: "🌐",
|
||||
mobile: "📱",
|
||||
};
|
||||
return deviceIcons[typeName] || "📱";
|
||||
}
|
||||
|
||||
// Copy sync URL or auth token to clipboard
|
||||
function copyToClipboard(text: string, label: string): void {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
showToast(`${label} copied to clipboard`, "success");
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error("Failed to copy:", err);
|
||||
showToast("Failed to copy to clipboard", "error");
|
||||
function showAddDeviceModal(): void {
|
||||
const modal = document.getElementById("add-device-modal");
|
||||
if (modal) modal.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function hideAddDeviceModal(): void {
|
||||
const modal = document.getElementById("add-device-modal");
|
||||
const form = document.getElementById("add-device-form");
|
||||
if (modal) modal.classList.add("hidden");
|
||||
if (form && form instanceof HTMLFormElement) form.reset();
|
||||
}
|
||||
|
||||
async function handleAddDevice(event: Event): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
|
||||
const deviceNameInput = document.getElementById("device-name") as HTMLInputElement;
|
||||
const deviceTypeInput = document.getElementById("device-type") as HTMLSelectElement;
|
||||
const deviceIdentifierInput = document.getElementById("device-identifier") as HTMLInputElement;
|
||||
|
||||
if (!deviceNameInput || !deviceTypeInput || !deviceIdentifierInput) return;
|
||||
|
||||
const data = {
|
||||
device_name: deviceNameInput.value,
|
||||
device_type: deviceTypeInput.value,
|
||||
device_identifier: deviceIdentifierInput.value,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/devices/register", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
// Regenerate device token with confirmation
|
||||
function regenerateDeviceToken(deviceId: string, event: Event): void {
|
||||
const confirmation =
|
||||
"⚠️ This will revoke current token and generate a new one.\n\n" +
|
||||
"The old token will immediately stop working.\n\n" +
|
||||
"You will need to update your device configuration with new token.\n\n" +
|
||||
"Continue?";
|
||||
|
||||
if (!confirm(confirmation)) {
|
||||
return;
|
||||
if (response.ok) {
|
||||
showToast("Device registered! Check your device for sync instructions.", "success");
|
||||
hideAddDeviceModal();
|
||||
window.location.reload();
|
||||
} else {
|
||||
showToast("Failed to register device", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to register device", error);
|
||||
showToast("Failed to register device", "error");
|
||||
}
|
||||
|
||||
const btn = event.target as HTMLButtonElement;
|
||||
const originalText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = "🔄 Regenerating...";
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
fetch(`/api/devices/${deviceId}/regenerate-token`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
.then((response: Response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to regenerate token");
|
||||
}
|
||||
return response.json() as Promise<RegenerateTokenResponse>;
|
||||
})
|
||||
.then((_data: RegenerateTokenResponse) => {
|
||||
showToast(
|
||||
"Token regenerated successfully - update your device config",
|
||||
"success",
|
||||
);
|
||||
// Reload page to show new token
|
||||
setTimeout(() => location.reload(), 1500);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error("Error:", error);
|
||||
showToast("Failed to regenerate token", "error");
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Export functions for global access (called from template onclick attributes)
|
||||
Alpine.global("devices", {
|
||||
function showDeviceSettings(deviceId: string): void {
|
||||
const modal = document.getElementById("device-settings-modal");
|
||||
const deviceIdInput = document.getElementById("settings-device-id") as HTMLInputElement;
|
||||
const deviceTypeInput = document.getElementById("settings-device-type") as HTMLInputElement;
|
||||
const deviceNameInput = document.getElementById("settings-device-name") as HTMLInputElement;
|
||||
const syncEnabledInput = document.getElementById("settings-sync-enabled") as HTMLInputElement;
|
||||
const autoSyncInput = document.getElementById("settings-auto-sync") as HTMLInputElement;
|
||||
const syncFrequencyInput = document.getElementById("settings-sync-frequency") as HTMLInputElement;
|
||||
|
||||
if (!modal || !deviceIdInput) return;
|
||||
|
||||
deviceIdInput.value = deviceId;
|
||||
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
|
||||
fetch(`/api/devices/${deviceId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((device) => {
|
||||
if (deviceTypeInput) deviceTypeInput.value = device.device_type || "";
|
||||
if (deviceNameInput) deviceNameInput.value = device.device_name || "";
|
||||
if (syncEnabledInput) syncEnabledInput.checked = device.sync_enabled || false;
|
||||
if (autoSyncInput) autoSyncInput.checked = device.auto_sync || false;
|
||||
if (syncFrequencyInput) syncFrequencyInput.value = String(device.sync_frequency_minutes || 60);
|
||||
|
||||
const deviceType = device.device_type;
|
||||
return fetch("/api/collections", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((result) => {
|
||||
if (result.collections && result.collections.length > 0) {
|
||||
const firstCollection = result.collections[0];
|
||||
const viewSettings = firstCollection.view_settings || {};
|
||||
const deviceSettings = viewSettings[deviceType] || {};
|
||||
|
||||
const viewModeInput = document.getElementById("settings-view-mode") as HTMLInputElement;
|
||||
const sortOrderInput = document.getElementById("settings-sort-order") as HTMLInputElement;
|
||||
const itemsPerPageInput = document.getElementById("settings-items-per-page") as HTMLInputElement;
|
||||
const showCoversInput = document.getElementById("settings-show-covers") as HTMLInputElement;
|
||||
const showProgressInput = document.getElementById("settings-show-progress") as HTMLInputElement;
|
||||
|
||||
if (viewModeInput) viewModeInput.value = deviceSettings.view_mode || "grid";
|
||||
if (sortOrderInput) sortOrderInput.value = deviceSettings.sort_order || "name";
|
||||
if (itemsPerPageInput) itemsPerPageInput.value = String(deviceSettings.items_per_page || 24);
|
||||
if (showCoversInput) showCoversInput.checked = deviceSettings.show_covers !== false;
|
||||
if (showProgressInput) showProgressInput.checked = deviceSettings.show_progress || false;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to load view settings:", error);
|
||||
});
|
||||
|
||||
if (modal) modal.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function showShelfMappings(deviceId: string): void {
|
||||
const mappingsDeviceIdInput = document.getElementById("mappings-device-id") as HTMLInputElement;
|
||||
const modal = document.getElementById("shelf-mappings-modal");
|
||||
|
||||
if (mappingsDeviceIdInput) mappingsDeviceIdInput.value = deviceId;
|
||||
loadShelfMappings(deviceId);
|
||||
if (modal) modal.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function hideShelfMappingsModal(): void {
|
||||
const modal = document.getElementById("shelf-mappings-modal");
|
||||
if (modal) modal.classList.add("hidden");
|
||||
}
|
||||
|
||||
async function loadShelfMappings(deviceId: string): Promise<void> {
|
||||
const container = document.getElementById("shelf-mappings-container");
|
||||
const token = getToken();
|
||||
if (!container || !token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/devices/${deviceId}/collections`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.mappings && result.mappings.length > 0) {
|
||||
container.innerHTML = result.mappings
|
||||
.map(
|
||||
(mapping: any) => `
|
||||
<div class="card p-4 rounded-lg border" style="background-color: var(--bg-primary); border-color: var(--border);">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h4 class="font-semibold" style="color: var(--text-primary)">${mapping.collection_name}</h4>
|
||||
<p class="text-sm" style="color: var(--text-secondary)">
|
||||
→ Device Shelf: <strong>${mapping.device_shelf_name}</strong>
|
||||
</p>
|
||||
<p class="text-xs" style="color: var(--text-secondary)">
|
||||
Sync: ${mapping.sync_direction}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button data-action="edit-mapping" data-mapping-id="${mapping.id}" data-collection-id="${mapping.collection_id}" data-shelf-name="${mapping.device_shelf_name}" data-sync-direction="${mapping.sync_direction}"
|
||||
class="p-2 hover:opacity-80 rounded" style="color: var(--text-secondary); background-color: var(--bg-secondary);">
|
||||
✏️
|
||||
</button>
|
||||
<button data-action="delete-mapping" data-mapping-id="${mapping.id}"
|
||||
class="p-2 hover:opacity-80 rounded" style="color: var(--text-secondary); background-color: var(--bg-secondary);">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
} else {
|
||||
container.innerHTML = '<p style="color: var(--text-secondary)">No shelf mappings configured. Click "Add Mapping" to create one.</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
container.innerHTML = '<p style="color: var(--error)">Failed to load mappings</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function showAddMappingModal(): void {
|
||||
const mappingsDeviceIdInput = document.getElementById("mappings-device-id") as HTMLInputElement;
|
||||
const mappingIdInput = document.getElementById("mapping-id") as HTMLInputElement;
|
||||
const mappingCollectionInput = document.getElementById("mapping-collection") as HTMLSelectElement;
|
||||
const mappingShelfNameInput = document.getElementById("mapping-shelf-name") as HTMLInputElement;
|
||||
const mappingSyncDirectionInput = document.getElementById("mapping-sync-direction") as HTMLSelectElement;
|
||||
const modal = document.getElementById("add-mapping-modal");
|
||||
|
||||
if (mappingsDeviceIdInput) {
|
||||
const mappingDeviceIdInput = document.getElementById("mapping-device-id") as HTMLInputElement;
|
||||
if (mappingDeviceIdInput) mappingDeviceIdInput.value = mappingsDeviceIdInput.value;
|
||||
}
|
||||
if (mappingIdInput) mappingIdInput.value = "";
|
||||
if (mappingCollectionInput) mappingCollectionInput.value = "";
|
||||
if (mappingShelfNameInput) mappingShelfNameInput.value = "";
|
||||
if (mappingSyncDirectionInput) mappingSyncDirectionInput.value = "bidirectional";
|
||||
|
||||
loadCollections();
|
||||
if (modal) modal.classList.remove("hidden");
|
||||
}
|
||||
|
||||
function hideAddMappingModal(): void {
|
||||
const modal = document.getElementById("add-mapping-modal");
|
||||
const form = document.getElementById("mapping-form");
|
||||
if (modal) modal.classList.add("hidden");
|
||||
if (form && form instanceof HTMLFormElement) form.reset();
|
||||
}
|
||||
|
||||
async function loadCollections(): Promise<void> {
|
||||
const select = document.getElementById("mapping-collection") as HTMLSelectElement;
|
||||
const token = getToken();
|
||||
if (!select || !token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/collections", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.collections) {
|
||||
select.innerHTML =
|
||||
'<option value="">Select collection...</option>' +
|
||||
result.collections.map((col: any) => `<option value="${col.id}">${col.name}</option>`).join("");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load collections", error);
|
||||
}
|
||||
}
|
||||
|
||||
function editMapping(mappingId: string, collectionId: string, shelfName: string, syncDirection: string): void {
|
||||
const mappingIdInput = document.getElementById("mapping-id") as HTMLInputElement;
|
||||
const mappingCollectionInput = document.getElementById("mapping-collection") as HTMLSelectElement;
|
||||
const mappingShelfNameInput = document.getElementById("mapping-shelf-name") as HTMLInputElement;
|
||||
const mappingSyncDirectionInput = document.getElementById("mapping-sync-direction") as HTMLSelectElement;
|
||||
const modal = document.getElementById("add-mapping-modal");
|
||||
|
||||
if (mappingIdInput) mappingIdInput.value = mappingId;
|
||||
if (mappingCollectionInput) mappingCollectionInput.value = collectionId;
|
||||
if (mappingShelfNameInput) mappingShelfNameInput.value = shelfName;
|
||||
if (mappingSyncDirectionInput) mappingSyncDirectionInput.value = syncDirection;
|
||||
|
||||
if (modal) modal.classList.remove("hidden");
|
||||
}
|
||||
|
||||
async function handleSaveMapping(event: Event): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const token = getToken();
|
||||
const mappingsDeviceIdInput = document.getElementById("mapping-device-id") as HTMLInputElement;
|
||||
const mappingIdInput = document.getElementById("mapping-id") as HTMLInputElement;
|
||||
const mappingCollectionInput = document.getElementById("mapping-collection") as HTMLSelectElement;
|
||||
const mappingShelfNameInput = document.getElementById("mapping-shelf-name") as HTMLInputElement;
|
||||
const mappingSyncDirectionInput = document.getElementById("mapping-sync-direction") as HTMLSelectElement;
|
||||
|
||||
if (!token || !mappingsDeviceIdInput || !mappingCollectionInput || !mappingShelfNameInput || !mappingSyncDirectionInput) return;
|
||||
|
||||
const deviceId = mappingsDeviceIdInput.value;
|
||||
const mappingId = mappingIdInput.value;
|
||||
const isUpdate = mappingId !== "";
|
||||
|
||||
const data = {
|
||||
collection_id: mappingCollectionInput.value,
|
||||
device_shelf_name: mappingShelfNameInput.value,
|
||||
sync_direction: mappingSyncDirectionInput.value,
|
||||
};
|
||||
|
||||
const url = isUpdate
|
||||
? `/api/devices/${deviceId}/collections/${mappingId}`
|
||||
: `/api/devices/${deviceId}/collections`;
|
||||
const method = isUpdate ? "PUT" : "POST";
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast(isUpdate ? "Mapping updated" : "Mapping created", "success");
|
||||
hideAddMappingModal();
|
||||
loadShelfMappings(deviceId);
|
||||
} else {
|
||||
showToast("Failed to save mapping", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to save mapping", error);
|
||||
showToast("Failed to save mapping", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteMapping(mappingId: string): Promise<void> {
|
||||
if (!confirm("Are you sure you want to delete this mapping?")) return;
|
||||
|
||||
const token = getToken();
|
||||
const mappingsDeviceIdInput = document.getElementById("mappings-device-id") as HTMLInputElement;
|
||||
if (!token || !mappingsDeviceIdInput) return;
|
||||
|
||||
const deviceId = mappingsDeviceIdInput.value;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/devices/${deviceId}/collections/${mappingId}`, {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast("Mapping deleted", "success");
|
||||
loadShelfMappings(deviceId);
|
||||
} else {
|
||||
showToast("Failed to delete mapping", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete mapping", error);
|
||||
showToast("Failed to delete mapping", "error");
|
||||
}
|
||||
}
|
||||
|
||||
function hideDeviceSettingsModal(): void {
|
||||
const modal = document.getElementById("device-settings-modal");
|
||||
if (modal) modal.classList.add("hidden");
|
||||
}
|
||||
|
||||
async function handleSaveDeviceSettings(event: Event): Promise<void> {
|
||||
event.preventDefault();
|
||||
|
||||
const token = getToken();
|
||||
const settingsDeviceIdInput = document.getElementById("settings-device-id") as HTMLInputElement;
|
||||
const settingsDeviceTypeInput = document.getElementById("settings-device-type") as HTMLInputElement;
|
||||
const settingsDeviceNameInput = document.getElementById("settings-device-name") as HTMLInputElement;
|
||||
const settingsSyncEnabledInput = document.getElementById("settings-sync-enabled") as HTMLInputElement;
|
||||
const settingsAutoSyncInput = document.getElementById("settings-auto-sync") as HTMLInputElement;
|
||||
const settingsSyncFrequencyInput = document.getElementById("settings-sync-frequency") as HTMLInputElement;
|
||||
|
||||
if (!token || !settingsDeviceIdInput || !settingsDeviceNameInput) return;
|
||||
|
||||
const deviceId = settingsDeviceIdInput.value;
|
||||
const deviceType = settingsDeviceTypeInput?.value || "";
|
||||
|
||||
const deviceData = {
|
||||
device_name: settingsDeviceNameInput.value,
|
||||
sync_enabled: settingsSyncEnabledInput?.checked || false,
|
||||
auto_sync: settingsAutoSyncInput?.checked || false,
|
||||
sync_frequency_minutes: parseInt(settingsSyncFrequencyInput?.value || "60", 10),
|
||||
};
|
||||
|
||||
const viewModeInput = document.getElementById("settings-view-mode") as HTMLSelectElement;
|
||||
const sortOrderInput = document.getElementById("settings-sort-order") as HTMLSelectElement;
|
||||
const itemsPerPageInput = document.getElementById("settings-items-per-page") as HTMLInputElement;
|
||||
const showCoversInput = document.getElementById("settings-show-covers") as HTMLInputElement;
|
||||
const showProgressInput = document.getElementById("settings-show-progress") as HTMLInputElement;
|
||||
|
||||
const viewSettings = {
|
||||
view_mode: viewModeInput?.value || "grid",
|
||||
sort_order: sortOrderInput?.value || "name",
|
||||
items_per_page: parseInt(itemsPerPageInput?.value || "24", 10),
|
||||
show_covers: showCoversInput?.checked !== false,
|
||||
show_progress: showProgressInput?.checked || false,
|
||||
};
|
||||
|
||||
try {
|
||||
await fetch(`/api/devices/${deviceId}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(deviceData),
|
||||
});
|
||||
|
||||
const collectionsRes = await fetch("/api/collections", {
|
||||
method: "GET",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
const collections = await collectionsRes.json();
|
||||
|
||||
const updatePromises = collections.collections.map((collection: any) => {
|
||||
const currentSettings = collection.view_settings || {};
|
||||
currentSettings[deviceType] = viewSettings;
|
||||
|
||||
return fetch(`/api/collections/${collection.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...collection,
|
||||
view_settings: currentSettings,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(updatePromises);
|
||||
showToast("Device settings saved", "success");
|
||||
hideDeviceSettingsModal();
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error("Failed to save settings", error);
|
||||
showToast("Failed to save settings", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRevokeDevice(): Promise<void> {
|
||||
const token = getToken();
|
||||
const settingsDeviceIdInput = document.getElementById("settings-device-id") as HTMLInputElement;
|
||||
if (!token || !settingsDeviceIdInput) return;
|
||||
|
||||
if (!confirm("Are you sure you want to revoke this device? It will no longer be able to sync.")) return;
|
||||
|
||||
const deviceId = settingsDeviceIdInput.value;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/devices/${deviceId}`, {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast("Device revoked successfully", "success");
|
||||
hideDeviceSettingsModal();
|
||||
window.location.reload();
|
||||
} else {
|
||||
showToast("Failed to revoke device", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to revoke device", error);
|
||||
showToast("Failed to revoke device", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function approveDevice(registrationId: string): Promise<void> {
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/devices/approve/${registrationId}`, {
|
||||
method: "GET",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast("Device approved successfully", "success");
|
||||
window.location.reload();
|
||||
} else {
|
||||
showToast("Failed to approve device", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to approve device", error);
|
||||
showToast("Failed to approve device", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function rejectDevice(registrationId: string): Promise<void> {
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/devices/reject/${registrationId}`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast("Device registration rejected", "info");
|
||||
window.location.reload();
|
||||
} else {
|
||||
showToast("Failed to reject device", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to reject device", error);
|
||||
showToast("Failed to reject device", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function clearSyncQueue(): Promise<void> {
|
||||
if (!confirm("Are you sure you want to clear all sync queue items?")) return;
|
||||
|
||||
const token = getToken();
|
||||
if (!token) return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/queue/clear", {
|
||||
method: "DELETE",
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast("Sync queue cleared", "success");
|
||||
} else {
|
||||
showToast("Failed to clear queue", "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to clear queue", error);
|
||||
showToast("Failed to clear queue", "error");
|
||||
}
|
||||
}
|
||||
|
||||
function setupEventDelegation(): void {
|
||||
document.addEventListener("click", (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const button = target.closest("button") as HTMLButtonElement;
|
||||
|
||||
if (!button) return;
|
||||
|
||||
const action = button.dataset.action;
|
||||
|
||||
if (action === "edit-mapping") {
|
||||
editMapping(
|
||||
button.dataset.mappingId || "",
|
||||
button.dataset.collectionId || "",
|
||||
button.dataset.shelfName || "",
|
||||
button.dataset.syncDirection || "",
|
||||
);
|
||||
} else if (action === "delete-mapping") {
|
||||
deleteMapping(button.dataset.mappingId || "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
approveDevice,
|
||||
clearSyncQueue,
|
||||
copyToClipboard,
|
||||
deleteMapping,
|
||||
editMapping,
|
||||
getDeviceIcon,
|
||||
handleAddDevice,
|
||||
handleRevokeDevice,
|
||||
handleSaveDeviceSettings,
|
||||
handleSaveMapping,
|
||||
hideAddDeviceModal,
|
||||
hideAddMappingModal,
|
||||
hideDeviceSettingsModal,
|
||||
hideShelfMappingsModal,
|
||||
loadCollections,
|
||||
loadShelfMappings,
|
||||
regenerateDeviceToken,
|
||||
rejectDevice,
|
||||
setupEventDelegation,
|
||||
showAddDeviceModal,
|
||||
showAddMappingModal,
|
||||
showDeviceSettings,
|
||||
showShelfMappings,
|
||||
};
|
||||
|
||||
Alpine.global("devices", {
|
||||
approveDevice,
|
||||
clearSyncQueue,
|
||||
copyToClipboard,
|
||||
deleteMapping,
|
||||
editMapping,
|
||||
getDeviceIcon,
|
||||
handleAddDevice,
|
||||
handleRevokeDevice,
|
||||
handleSaveDeviceSettings,
|
||||
handleSaveMapping,
|
||||
hideAddDeviceModal,
|
||||
hideAddMappingModal,
|
||||
hideDeviceSettingsModal,
|
||||
hideShelfMappingsModal,
|
||||
loadCollections,
|
||||
loadShelfMappings,
|
||||
regenerateDeviceToken,
|
||||
rejectDevice,
|
||||
setupEventDelegation,
|
||||
showAddDeviceModal,
|
||||
showAddMappingModal,
|
||||
showDeviceSettings,
|
||||
showShelfMappings,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user