Add Phase 2: Local SQLite database with sync functionality
Implements complete offline-first architecture with SQLite caching and MariaDB synchronization. Key features: - Local SQLite database for offline operation (data/quoteforge.db) - Connection settings with encrypted credentials - Component and pricelist caching with auto-sync - Sync API endpoints (/api/sync/status, /components, /pricelists, /all) - Real-time sync status indicator in UI with auto-refresh - Offline mode detection middleware - Migration tool for database initialization - Setup wizard for initial configuration New components: - internal/localdb: SQLite repository layer (components, pricelists, sync) - internal/services/sync: Synchronization service - internal/handlers/sync: Sync API handlers - internal/handlers/setup: Setup wizard handlers - internal/middleware/offline: Offline detection - cmd/migrate: Database migration tool UI improvements: - Setup page for database configuration - Sync status indicator with online/offline detection - Warning icons for pending synchronization - Auto-refresh every 30 seconds Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -269,9 +269,8 @@ async function loadCategoriesFromAPI() {
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (!token || !configUUID) {
|
||||
// RBAC disabled - no token check required
|
||||
if (!configUUID) {
|
||||
window.location.href = '/configs';
|
||||
return;
|
||||
}
|
||||
@@ -280,16 +279,9 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
await loadCategoriesFromAPI();
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/configs/' + configUUID, {
|
||||
headers: {'Authorization': 'Bearer ' + token}
|
||||
});
|
||||
const resp = await fetch('/api/configs/' + configUUID);
|
||||
|
||||
if (resp.status === 401) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp.status === 403 || resp.status === 404) {
|
||||
if (resp.status === 404) {
|
||||
showToast('Конфигурация не найдена', 'error');
|
||||
window.location.href = '/configs';
|
||||
return;
|
||||
@@ -1119,8 +1111,8 @@ function triggerAutoSave() {
|
||||
}
|
||||
|
||||
async function saveConfig(showNotification = true) {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token || !configUUID) return;
|
||||
// RBAC disabled - no token check required
|
||||
if (!configUUID) return;
|
||||
|
||||
// Get custom price if set
|
||||
const customPriceInput = document.getElementById('custom-price-input');
|
||||
@@ -1134,7 +1126,6 @@ async function saveConfig(showNotification = true) {
|
||||
const resp = await fetch('/api/configs/' + configUUID, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
@@ -1146,11 +1137,6 @@ async function saveConfig(showNotification = true) {
|
||||
})
|
||||
});
|
||||
|
||||
if (resp.status === 401) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resp.ok) {
|
||||
if (showNotification) {
|
||||
showToast('Ошибка сохранения', 'error');
|
||||
@@ -1308,23 +1294,17 @@ async function exportCSVWithCustomPrice() {
|
||||
}
|
||||
|
||||
async function refreshPrices() {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token || !configUUID) return;
|
||||
// RBAC disabled - no token check required
|
||||
if (!configUUID) return;
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/configs/' + configUUID + '/refresh-prices', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (resp.status === 401) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resp.ok) {
|
||||
showToast('Ошибка обновления цен', 'error');
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user