diff --git a/kernelsu_antibootloop_backup-v1.0.0.zip b/kernelsu_antibootloop_backup-v1.0.0.zip index 2118eee..87f39f5 100644 Binary files a/kernelsu_antibootloop_backup-v1.0.0.zip and b/kernelsu_antibootloop_backup-v1.0.0.zip differ diff --git a/kernelsu_antibootloop_backup/webroot/css/style.css b/kernelsu_antibootloop_backup/webroot/css/style.css index a497b82..0393155 100644 --- a/kernelsu_antibootloop_backup/webroot/css/style.css +++ b/kernelsu_antibootloop_backup/webroot/css/style.css @@ -1120,6 +1120,149 @@ body { animation: spin var(--md-sys-motion-duration-extra-long1) linear infinite; } +/* Bottom Navigation */ +.bottom-navigation { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background-color: var(--md-sys-color-surface-container); + border-top: 1px solid var(--md-sys-color-outline-variant); + display: flex; + z-index: var(--z-fab); + box-shadow: var(--md-sys-elevation-level2); +} + +.bottom-nav-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 8px 12px; + min-height: 64px; + cursor: pointer; + color: var(--md-sys-color-on-surface-variant); + transition: color var(--md-sys-motion-duration-short2) var(--md-sys-motion-easing-standard); + position: relative; + overflow: hidden; +} + +.bottom-nav-item.active { + color: var(--md-sys-color-primary); +} + +.bottom-nav-item span { + font-size: var(--md-sys-typescale-label-small-font-size); + margin-top: 4px; +} + +.bottom-nav-item i { + font-size: 24px; +} + +/* Chart Placeholder */ +.chart-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 300px; + background-color: var(--md-sys-color-surface-container); + border-radius: var(--md-sys-shape-corner-medium); + color: var(--md-sys-color-on-surface-variant); + text-align: center; + gap: 16px; +} + +/* Activity Styles */ +.activity-log { + max-height: 400px; + overflow-y: auto; +} + +.activity-item { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 12px 0; + border-bottom: 1px solid var(--md-sys-color-outline-variant); +} + +.activity-item:last-child { + border-bottom: none; +} + +.activity-icon { + width: 32px; + height: 32px; + border-radius: var(--md-sys-shape-corner-full); + display: flex; + align-items: center; + justify-content: center; + background-color: var(--md-sys-color-primary-container); + color: var(--md-sys-color-on-primary-container); + flex-shrink: 0; +} + +.activity-content { + flex: 1; + min-width: 0; +} + +.activity-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 4px; +} + +.activity-title { + font-weight: var(--md-sys-typescale-body-medium-font-weight); + color: var(--md-sys-color-on-surface); +} + +.activity-time { + font-size: var(--md-sys-typescale-body-small-font-size); + color: var(--md-sys-color-on-surface-variant); + flex-shrink: 0; + margin-left: 8px; +} + +.activity-description { + margin: 0; + color: var(--md-sys-color-on-surface-variant); + font-size: var(--md-sys-typescale-body-small-font-size); +} + +/* Ripple Effect */ +.ripple-container { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + pointer-events: none; + border-radius: inherit; +} + +.ripple { + position: absolute; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.3); + pointer-events: none; + transform: scale(0); + animation: ripple-animation 0.6s ease-out; +} + +@keyframes ripple-animation { + to { + transform: scale(2); + opacity: 0; + } +} + /* Utilities */ .hidden { display: none !important; } .visible { display: block !important; } diff --git a/kernelsu_antibootloop_backup/webroot/js/dashboard.js b/kernelsu_antibootloop_backup/webroot/js/dashboard.js index fef7636..c101599 100644 --- a/kernelsu_antibootloop_backup/webroot/js/dashboard.js +++ b/kernelsu_antibootloop_backup/webroot/js/dashboard.js @@ -455,7 +455,12 @@ const DashboardController = { // Check if Chart.js is loaded if (typeof Chart === 'undefined') { - console.error('Chart.js is not loaded, skipping boot history chart'); + console.error('Chart.js is not loaded, showing placeholder instead'); + bootHistoryCanvas.style.display = 'none'; + const placeholder = document.createElement('div'); + placeholder.className = 'chart-placeholder'; + placeholder.innerHTML = '

Charts require internet connection

'; + bootHistoryCanvas.parentNode.appendChild(placeholder); return; } @@ -549,7 +554,12 @@ const DashboardController = { // Check if Chart.js is loaded if (typeof Chart === 'undefined') { - console.error('Chart.js is not loaded, skipping backup size chart'); + console.error('Chart.js is not loaded, showing placeholder instead'); + backupSizeCanvas.style.display = 'none'; + const placeholder = document.createElement('div'); + placeholder.className = 'chart-placeholder'; + placeholder.innerHTML = '

Charts require internet connection

'; + backupSizeCanvas.parentNode.appendChild(placeholder); return; } @@ -1313,14 +1323,28 @@ const DashboardController = { // Initialize dashboard when document is loaded document.addEventListener('DOMContentLoaded', () => { - // Add Chart.js script dynamically - const chartScript = document.createElement('script'); - chartScript.src = 'https://cdn.jsdelivr.net/npm/chart.js'; - chartScript.onload = () => { - console.log('Chart.js loaded'); + // Check if Chart.js is already loaded + if (typeof Chart !== 'undefined') { + console.log('Chart.js already loaded'); DashboardController.init(); - }; - document.head.appendChild(chartScript); + } else { + // Add Chart.js script dynamically + const chartScript = document.createElement('script'); + chartScript.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js'; + chartScript.onload = () => { + console.log('Chart.js loaded successfully'); + // Wait a bit for Chart.js to initialize + setTimeout(() => { + DashboardController.init(); + }, 100); + }; + chartScript.onerror = (error) => { + console.error('Failed to load Chart.js:', error); + // Initialize without charts + DashboardController.init(); + }; + document.head.appendChild(chartScript); + } // Expose dashboard controller globally window.Dashboard = DashboardController; diff --git a/kernelsu_antibootloop_backup/webroot/js/main.js b/kernelsu_antibootloop_backup/webroot/js/main.js index ed47a22..07c912d 100644 --- a/kernelsu_antibootloop_backup/webroot/js/main.js +++ b/kernelsu_antibootloop_backup/webroot/js/main.js @@ -55,17 +55,14 @@ const AppState = { */ document.addEventListener('DOMContentLoaded', () => { // Initialize UI components - UI.initTheme(); - UI.initRipple(); - UI.initNavigation(); - UI.initBottomNav(); - UI.initModals(); - UI.initFab(); - UI.initSwitches(); - UI.initSliders(); + if (typeof UI !== 'undefined') { + UI.init(); // Use the main init function + } // Initialize Dashboard - Dashboard.init(); + if (typeof Dashboard !== 'undefined') { + Dashboard.init(); + } // Initialize WebUIX API connection initWebUIX(); @@ -83,7 +80,9 @@ document.addEventListener('DOMContentLoaded', () => { refreshData(); // Show toast to indicate app is ready - UI.showToast('Application initialized'); + if (typeof UI !== 'undefined' && UI.showNotification) { + UI.showNotification('Application initialized', 'info'); + } console.log('KernelSU Anti-Bootloop Backup WebUI initialized'); }); @@ -2038,4 +2037,320 @@ function formatSize(bytes) { const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} \ No newline at end of file +} + +/** + * Update system info in UI + */ +function updateSystemInfo() { + const { systemInfo } = AppState; + + // Update system overview + const deviceModel = document.getElementById('device-model'); + const androidVersion = document.getElementById('android-version'); + const kernelsuVersion = document.getElementById('kernelsu-version'); + const moduleVersion = document.getElementById('module-version'); + + if (deviceModel) deviceModel.textContent = systemInfo.deviceModel || 'Unknown'; + if (androidVersion) androidVersion.textContent = systemInfo.androidVersion || 'Unknown'; + if (kernelsuVersion) kernelsuVersion.textContent = systemInfo.kernelSUVersion || 'Unknown'; + if (moduleVersion) moduleVersion.textContent = systemInfo.moduleVersion || 'v1.0.0'; +} + +/** + * Parse size string to bytes for comparison + * @param {string} sizeStr - Size string (e.g., "1.2G") + * @returns {number} Size in bytes + */ +function parseSize(sizeStr) { + const units = { 'B': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3, 'TB': 1024**4 }; + const match = sizeStr.match(/([\d.]+)\s*(\w+)/); + if (!match) return 0; + const [, value, unit] = match; + return parseFloat(value) * (units[unit] || 1); +} + +/** + * Show offline details dialog + */ +function showOfflineDetails() { + const content = ` +
+

The application is currently running in offline mode. Some features may be limited:

+ +

To restore full functionality, ensure:

+ +
+ `; + + UI.showModal('Offline Mode Details', content); +} + +/** + * Show activity log in a modal + */ +function showActivityLog() { + let content = '
'; + + if (AppState.activityLog.length === 0) { + content += '

No activity to display

'; + } else { + content += '
'; + + AppState.activityLog.forEach(activity => { + const typeIcon = { + 'backup': 'backup', + 'restore': 'restore', + 'safety': 'security', + 'error': 'error', + 'warning': 'warning' + }[activity.type] || 'info'; + + content += ` +
+
+ ${typeIcon} +
+
+
${activity.message}
+
${activity.date.toLocaleString()}
+
+
+ `; + }); + + content += '
'; + } + + content += '
'; + + UI.showModal('Activity Log', content); +} + +/** + * Test bootloop protection + */ +function testProtection() { + UI.showConfirmDialog( + 'Test Bootloop Protection', + 'This will test the bootloop protection system by simulating a boot failure. Are you sure you want to continue?', + 'Test', + 'Cancel', + async () => { + UI.showLoader('Testing bootloop protection...'); + + try { + if (AppState.isWebUIXConnected) { + const result = await executeCommand('sh /data/adb/modules/kernelsu_antibootloop_backup/scripts/test-protection.sh'); + + if (result && result.includes('Test completed successfully')) { + UI.showToast('Bootloop protection test completed successfully'); + logActivity('safety', 'Bootloop protection test completed'); + } else { + UI.showToast('Bootloop protection test failed', 'error'); + } + } else { + setTimeout(() => { + UI.showToast('Bootloop protection test completed (mock mode)'); + logActivity('safety', 'Bootloop protection test completed (mock)'); + }, 2000); + } + } catch (error) { + UI.showToast('Failed to test bootloop protection', 'error'); + console.error('Test protection error:', error); + } finally { + UI.hideLoader(); + } + } + ); +} + +/** + * Create recovery point + * @param {string} description - Recovery point description + */ +function createRecoveryPoint(description = 'Manual recovery point') { + return new Promise(async (resolve, reject) => { + try { + UI.showLoader('Creating recovery point...'); + + if (AppState.isWebUIXConnected) { + const name = `recovery_point_${Date.now()}.point`; + const command = `sh /data/adb/modules/kernelsu_antibootloop_backup/scripts/recovery-point.sh create "${name}" "${description}"`; + + const result = await executeCommand(command); + + if (result && result.includes('Recovery point created')) { + UI.showToast('Recovery point created successfully'); + logActivity('safety', `Created recovery point: ${description}`); + + // Add to recovery points array + AppState.recoveryPoints.push({ + name, + path: `/data/adb/modules/kernelsu_antibootloop_backup/config/recovery_points/${name}`, + date: new Date(), + description + }); + + updateRecoveryPointList(); + resolve(true); + } else { + UI.showToast('Failed to create recovery point', 'error'); + reject(new Error('Recovery point creation failed')); + } + } else { + // Mock mode + setTimeout(() => { + UI.showToast('Recovery point created successfully (mock mode)'); + logActivity('safety', `Created recovery point: ${description} (mock)`); + + const name = `recovery_point_${Date.now()}.point`; + AppState.recoveryPoints.push({ + name, + path: `/mock/path/${name}`, + date: new Date(), + description + }); + + updateRecoveryPointList(); + resolve(true); + }, 1500); + } + } catch (error) { + UI.showToast('Failed to create recovery point', 'error'); + console.error('Create recovery point error:', error); + reject(error); + } finally { + UI.hideLoader(); + } + }); +} + +/** + * Show reboot dialog + * @param {string} message - Reboot message + */ +function showRebootDialog(message) { + UI.showConfirmDialog( + 'Reboot Required', + message, + 'Reboot Now', + 'Later', + async () => { + UI.showLoader('Rebooting device...'); + + if (AppState.isWebUIXConnected) { + try { + await executeCommand('reboot'); + } catch (error) { + console.error('Reboot command error:', error); + } + } else { + setTimeout(() => { + UI.hideLoader(); + UI.showToast('Reboot command sent (mock mode)'); + }, 2000); + } + } + ); +} + +/** + * Show logs dialog + */ +function showLogs() { + UI.showLoader('Loading logs...'); + + const loadLogs = async () => { + if (!AppState.isWebUIXConnected) { + return 'Mock log content\n[INFO] Module started\n[INFO] Bootloop protection enabled\n[INFO] Backup system ready'; + } + + try { + const logPath = '/data/adb/modules/kernelsu_antibootloop_backup/logs/module.log'; + return await executeCommand(`tail -100 ${logPath}`); + } catch (error) { + return 'Error loading logs: ' + error.message; + } + }; + + loadLogs().then(logContent => { + UI.hideLoader(); + + const content = ` +
+
+ + + +
+
${logContent}
+
+ `; + + UI.showModal('System Logs', content); + }); +} + +/** + * Show about dialog + */ +function showAboutDialog() { + const content = ` +
+
+

KernelSU Anti-Bootloop & Backup

+

Version: ${APP_CONFIG.version}

+

Author: Wiktor/overspend1

+

Built: ${new Date().getFullYear()}

+
+ +
+

Advanced KernelSU module that combines anti-bootloop protection with comprehensive backup and restoration capabilities.

+
+ +
+

Features:

+ +
+ +
+

System Information:

+

Device: ${AppState.systemInfo.deviceModel}

+

Android: ${AppState.systemInfo.androidVersion}

+

KernelSU: ${AppState.systemInfo.kernelSUVersion}

+

Kernel: ${AppState.systemInfo.kernelVersion}

+
+
+ `; + + UI.showModal('About', content); +} + +// Expose functions globally +window.updateSystemInfo = updateSystemInfo; +window.showOfflineDetails = showOfflineDetails; +window.showActivityLog = showActivityLog; +window.testProtection = testProtection; +window.createRecoveryPoint = createRecoveryPoint; +window.showRebootDialog = showRebootDialog; +window.showLogs = showLogs; +window.showAboutDialog = showAboutDialog; +window.parseSize = parseSize; +window.AppState = AppState; \ No newline at end of file diff --git a/kernelsu_antibootloop_backup/webroot/js/ui.js b/kernelsu_antibootloop_backup/webroot/js/ui.js index 3a4f24a..0e557bc 100644 --- a/kernelsu_antibootloop_backup/webroot/js/ui.js +++ b/kernelsu_antibootloop_backup/webroot/js/ui.js @@ -118,6 +118,10 @@ const UIController = { /** * Initialize Material Design ripple effect */ + initRipple: function() { + return this.initRippleEffect(); + }, + initRippleEffect: function() { // Add ripple effect to buttons document.querySelectorAll('.btn, .icon-button, .nav-item, .bottom-nav-item').forEach(button => {