/** Define main view model **/ function ViewModel() { // Initialize models var self = this; self.queue = new QueueListModel(this); self.history = new HistoryListModel(this); self.filelist = new Fileslisting(this); // Set status varibales self.isRestarting = ko.observable(false); self.useGlobalOptions = ko.observable(true).extend({ persist: 'useGlobalOptions' }); self.refreshRate = ko.observable(1).extend({ persist: 'pageRefreshRate' }); self.dateFormat = ko.observable('DD/MM/YYYY HH:mm').extend({ persist: 'pageDateFormat' }); self.displayTabbed = ko.observable().extend({ persist: 'displayTabbed' }); self.displayCompact = ko.observable(false).extend({ persist: 'displayCompact' }); self.confirmDeleteQueue = ko.observable(true).extend({ persist: 'confirmDeleteQueue' }); self.confirmDeleteHistory = ko.observable(true).extend({ persist: 'confirmDeleteHistory' }); self.extraQueueColumn = ko.observable('').extend({ persist: 'extraColumn' }); self.extraHistoryColumn = ko.observable('').extend({ persist: 'extraHistoryColumn' }); self.showActiveConnections = ko.observable(false).extend({ persist: 'showActiveConnections' }); self.speedMetrics = { K: "KB/s", M: "MB/s", G: "GB/s" }; // Set information varibales self.title = ko.observable(); self.speed = ko.observable(0); self.speedMetric = ko.observable(); self.bandwithLimit = ko.observable(false); self.pauseCustom = ko.observable('').extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } }); self.speedLimit = ko.observable(100).extend({ rateLimit: { timeout: 200, method: "notifyWhenChangesStop" } }); self.speedLimitInt = ko.observable(false); // We need the 'internal' counter so we don't trigger the API all the time self.downloadsPaused = ko.observable(false); self.timeLeft = ko.observable("0:00"); self.diskSpaceLeft1 = ko.observable(); self.diskSpaceLeft2 = ko.observable(); self.queueDataLeft = ko.observable(); self.diskSpaceExceeded1 = ko.observable(false); self.diskSpaceExceeded2 = ko.observable(false); self.quotaLimit = ko.observable(); self.quotaLimitLeft = ko.observable(); self.systemLoad = ko.observable(); self.nrWarnings = ko.observable(0); self.allWarnings = ko.observableArray([]); self.allMessages = ko.observableArray([]); self.onQueueFinish = ko.observable(''); self.speedHistory = []; // Statusinfo container self.hasStatusInfo = ko.observable(false); self.hasDiskStatusInfo = ko.observable(false); self.statusInfo = {}; self.statusInfo.folders = ko.observableArray([]); self.statusInfo.servers = ko.observableArray([]); self.statusInfo.localipv4 = ko.observable(); self.statusInfo.publicipv4 = ko.observable(); self.statusInfo.ipv6 = ko.observable(); self.statusInfo.dnslookup = ko.observable(); self.statusInfo.pystone = ko.observable(); self.statusInfo.cpumodel = ko.observable(); self.statusInfo.loglevel = ko.observable(); self.statusInfo.cache_size = ko.observable(); self.statusInfo.cache_art = ko.observable(); self.statusInfo.downloaddir = ko.observable(); self.statusInfo.downloaddirspeed = ko.observable(); self.statusInfo.completedir = ko.observable(); self.statusInfo.completedirspeed = ko.observable(); /*** Dynamic functions ***/ // Make the speedlimit tekst self.speedLimitText = ko.pureComputed(function() { // Set? if(!self.bandwithLimit()) return; // The text var bandwithLimitText = self.bandwithLimit().replace(/[^a-zA-Z]+/g, ''); // Only the number var speedLimitNumberFull = (parseFloat(self.bandwithLimit()) * (self.speedLimit() / 100)); // Trick to only get decimal-point when needed var speedLimitNumber = Math.round(speedLimitNumberFull*10)/10; // Fix it for lower than 1MB/s if(bandwithLimitText == 'M' && speedLimitNumber < 1) { bandwithLimitText = 'K'; speedLimitNumber = Math.round(speedLimitNumberFull * 1024); } // Show text return self.speedLimit() + '% (' + speedLimitNumber + ' ' + self.speedMetrics[bandwithLimitText] + ')'; }); // Dynamic speed text function self.speedText = ko.pureComputed(function() { return self.speed() + ' ' + (self.speedMetrics[self.speedMetric()] ? self.speedMetrics[self.speedMetric()] : "KB/s"); }); // Dynamic icon self.SABIcon = ko.pureComputed(function() { if(self.downloadsPaused()) { return './staticcfg/ico/faviconpaused.ico?v=1.1.0'; } else { return './staticcfg/ico/favicon.ico?v=1.1.0'; } }) // Dynamic queue length check self.hasQueue = ko.pureComputed(function() { return(self.queue.queueItems().length > 0 || self.queue.searchTerm() || self.queue.isLoading()) }) // Dynamic history length check self.hasHistory = ko.pureComputed(function() { // We also 'have history' if we can't find any results of the search or there are no failed ones return (self.history.historyItems().length > 0 || self.history.searchTerm() || self.history.showFailed() || self.history.isLoading()) }) self.hasWarnings = ko.pureComputed(function() { return(self.allWarnings().length > 0) }) // Check for any warnings/messages self.hasMessages = ko.pureComputed(function() { return self.nrWarnings() > 0 || self.allMessages().length > 0; }) // Update main queue self.updateQueue = function(response) { if(!self.queue.shouldUpdate()) return; // Make sure we are displaying the interface if(self.isRestarting() >= 1) { // Decrease the counter by 1 // In case of restart (which takes time to fire) we count down // In case of re-connect after failure it counts from 1 so emmediate continuation self.isRestarting(self.isRestarting() - 1); return; } /*** Basic information ***/ // Queue left self.queueDataLeft(response.queue.mbleft > 0 ? response.queue.sizeleft : '') // Paused? self.downloadsPaused(response.queue.paused); // Finish action. Replace null with empty self.onQueueFinish(response.queue.finishaction ? response.queue.finishaction : ''); // Disk sizes self.diskSpaceLeft1(response.queue.diskspace1_norm) // Same sizes? Then it's all 1 disk! if(response.queue.diskspace1 != response.queue.diskspace2) { self.diskSpaceLeft2(response.queue.diskspace2_norm) } // Did we exceed the space? self.diskSpaceExceeded1(parseInt(response.queue.mbleft)/1024 > parseInt(response.queue.diskspace1)) self.diskSpaceExceeded2(parseInt(response.queue.mbleft)/1024 > parseInt(response.queue.diskspace2)) // Quota self.quotaLimit(response.queue.quota) self.quotaLimitLeft(response.queue.left_quota) // System load self.systemLoad(response.queue.loadavg) // Warnings (new warnings will trigger an update of allMessages) self.nrWarnings(response.queue.have_warnings) /*** Spark line ***/ // Break the speed if empty queue if(response.queue.sizeleft == '0 B') { response.queue.kbpersec = 0; response.queue.speed = '0'; } // Re-format the speed var speedSplit = response.queue.speed.split(/\s/); self.speed(parseFloat(speedSplit[0])); self.speedMetric(speedSplit[1]); // Update sparkline data if(self.speedHistory.length >= 275) { // Remove first one self.speedHistory.shift(); } // Add self.speedHistory.push(parseInt(response.queue.kbpersec)); // Is sparkline visible? Not on small mobile devices.. if($('.sparkline-container').css('display') != 'none') { // Make sparkline if(self.speedHistory.length == 1) { // We only use speedhistory from SAB if we use global settings // Otherwise SAB doesn't know the refresh rate if(!self.useGlobalOptions()) { sabSpeedHistory = []; } else { // Update internally self.speedHistory = sabSpeedHistory; } // Create $('.sparkline').peity("line", { width: 275, height: 32, fill: '#9DDB72', stroke: '#AAFFAA', values: sabSpeedHistory }) } else { // Update $('.sparkline').text(self.speedHistory.join(",")).change() } } /*** Speedlimit ***/ // Nothing = 100% response.queue.speedlimit = (response.queue.speedlimit == '') ? 100.0 : parseFloat(response.queue.speedlimit).toFixed(1); // Trick to only get decimal-point when needed response.queue.speedlimit = Math.round(response.queue.speedlimit*10)/10; self.speedLimitInt(response.queue.speedlimit) // Only update from external source when user isn't doing input if(!$('.speedlimit-dropdown .btn-group .btn-group').is('.open')) { self.speedLimit(response.queue.speedlimit) } /*** Download timing and pausing ***/ var timeString = response.queue.timeleft; if(timeString === '') { timeString = '0:00'; } else { timeString = rewriteTime(response.queue.timeleft) } // Paused main queue if(self.downloadsPaused()) { if(response.queue.pause_int == '0') { timeString = glitterTranslate.paused; } else { var pauseSplit = response.queue.pause_int.split(/:/); var seconds = parseInt(pauseSplit[0]) * 60 + parseInt(pauseSplit[1]); var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds -= hours * 3600) / 60); seconds -= minutes * 60; timeString = glitterTranslate.paused + ' (' + rewriteTime(hours + ":" + minutes + ":" + seconds) + ')'; } // Add info about amount of download (if actually downloading) if(response.queue.noofslots > 0 && parseInt(self.queueDataLeft()) > 0) { self.title(timeString + ' - ' + self.queueDataLeft() + ' ' + glitterTranslate.left + ' - SABnzbd') } else { // Set title with pause information self.title(timeString + ' - SABnzbd') } } else if(response.queue.noofslots > 0 && parseInt(self.queueDataLeft()) > 0) { // Set title only if we are actually downloading something.. self.title(self.speedText() + ' - ' + self.queueDataLeft() + ' ' + glitterTranslate.left + ' - SABnzbd') } else { // Empty title self.title('SABnzbd') } // Save for timing box self.timeLeft(timeString); // Update queue rows self.queue.updateFromData(response.queue); } // Update history items self.updateHistory = function(response) { if(!response) return; self.history.updateFromData(response.history); } // Refresh function self.refresh = function(forceFullHistory) { // Clear previous timeout and set a new one to prevent double-calls clearTimeout(self.interval); self.interval = setTimeout(self.refresh, parseInt(self.refreshRate()) * 1000); /** Limited refresh **/ // Only update the title when page not visible if(!pageIsVisible) { // Request new title callSpecialAPI('./queue/', { limit: 1, start: 0 }).done(function(data) { // Split title & speed var dataSplit = data.split('|||'); // Set title self.title(dataSplit[0]); // Update sparkline data if(self.speedHistory.length >= 50) { // Remove first one self.speedHistory.shift(); } // Add self.speedHistory.push(dataSplit[1]); // Does it contain 'Paused'? Update icon! self.downloadsPaused(data.indexOf(glitterTranslate.paused) > -1) }) // Do not continue! return; } /** Do first load with start-data Only works when the server knows the settings! **/ if(glitterPreLoadHistory && self.useGlobalOptions()) { self.updateQueue(glitterPreLoadQueue); self.updateHistory(glitterPreLoadHistory); glitterPreLoadQueue = undefined; glitterPreLoadHistory = undefined; return; } /** Full refresh **/ // Do requests for full information // Catch the fail to display message var queueApi = callAPI({ mode: "queue", search: self.queue.searchTerm(), start: self.queue.pagination.currentStart(), limit: parseInt(self.queue.paginationLimit()) }) .done(self.updateQueue) .fail(function(response) { // Catch the failure of authorization error if(response.status == 401) { // Stop refresh and reload clearInterval(self.interval) location.reload(); } // Show screen self.isRestarting(1) }); // Force full history update? if(forceFullHistory) { self.history.lastUpdate = 0 } // History callAPI({ mode: "history", search: self.history.searchTerm(), failed_only: self.history.showFailed()*1, start: self.history.pagination.currentStart(), limit: parseInt(self.history.paginationLimit()), last_history_call: self.history.lastUpdate }).done(self.updateHistory); // We are now done with any loading // But we wait a few ms so Knockout has time to update setTimeout(function() { self.queue.isLoading(false); self.history.isLoading(false); }, 100) // Return for .then() functionality return queueApi; }; // Set pause action on click of toggle self.pauseToggle = function() { callAPI({ mode: (self.downloadsPaused() ? "resume" : "pause") }).then(self.refresh); self.downloadsPaused(!self.downloadsPaused()); } // Set pause timer self.pauseTime = function(item, event) { callAPI({ mode: 'config', name: 'set_pause', value: $(event.currentTarget).data('time') }).then(self.refresh); self.downloadsPaused(true); }; // Open modal self.openCustomPauseTime = function() { // Was it loaded already? if(!Date.i18n) { jQuery.getScript('./static/javascripts/date.min.js').then(function() { // After loading we start again self.openCustomPauseTime() }) return; } // Show modal $('#modal_custom_pause').modal('show') // Focus on the input field $('#modal_custom_pause').on('shown.bs.modal', function () { $('#customPauseInput').focus() }) // Reset on modal close $('#modal_custom_pause').on('hide.bs.modal', function () { self.pauseCustom(''); }) } // Update on changes self.pauseCustom.subscribe(function(newValue) { // Is it plain numbers? if(newValue.match(/^\s*\d+\s*$/)) { // Treat it as a number of minutes newValue += "minutes"; } // At least 3 charaters if(newValue.length < 3) { $('#customPauseOutput').text('').data('time', 0) $('#modal_custom_pause .btn-default').addClass('disabled') return; } // Parse var pauseParsed = Date.parse(newValue); // Did we get it? if(pauseParsed) { // Is it just now? if(pauseParsed <= Date.parse('now')) { // Try again with the '+' in front, the parser doesn't get 100min pauseParsed = Date.parse('+' + newValue); } // Calculate difference in minutes and save var pauseDuration = Math.round((pauseParsed - Date.parse('now'))/1000/60); $('#customPauseOutput').html(' ' +glitterTranslate.pauseFor + ' ' + pauseDuration + ' ' + glitterTranslate.minutes) $('#customPauseOutput').data('time', pauseDuration) $('#modal_custom_pause .btn-default').removeClass('disabled') } else if(newValue) { // No.. $('#customPauseOutput').text(glitterTranslate.pausePromptFail) $('#modal_custom_pause .btn-default').addClass('disabled') } }) // Save custom pause self.saveCustomPause = function() { // Get duration var pauseDuration = $('#customPauseOutput').data('time'); // If in the future if(pauseDuration > 0) { callAPI({ mode: 'config', name: 'set_pause', value: pauseDuration }).then(function() { // Refresh and close the modal self.refresh() self.downloadsPaused(true); $('#modal_custom_pause').modal('hide') }); } } // Update the warnings self.nrWarnings.subscribe(function(newValue) { // Really any change? if(newValue == self.allWarnings().length) return; // Get all warnings callAPI({ mode: 'warnings' }).then(function(response) { // Reset it all self.allWarnings.removeAll(); if(response) { // Newest first response.warnings.reverse() // Go over all warnings and add $.each(response.warnings, function(index, warning) { // Split warning into parts var warningSplit = warning.split(/\n/); // Reformat CSS label and date var warningData = { index: index, type: glitterTranslate.status[warningSplit[1]].slice(0, -1), text: warningSplit.slice(2).join('
').replace(/ /g, '\u00A0'), // Recombine if multiple lines date: displayDateTime(warningSplit[0], self.dateFormat(), 'YYYY-MM-DD HH:mm'), timestamp: moment(warningSplit[0], 'YYYY-MM-DD HH:mm').unix(), css: (warningSplit[1] == "ERROR" ? "danger" : warningSplit[1] == "WARNING" ? "warning" : "info"), clear: self.clearWarnings }; self.allWarnings.push(warningData) }) } }); }) // Clear warnings through this special URL.. self.clearWarnings = function() { // Activate callSpecialAPI("./status/clearwarnings/").done(self.refresh) } // Clear messages self.clearMessages = function(whatToRemove) { // Remove specifc type of messages self.allMessages.remove(function(item) { return item.index == whatToRemove }); // Now so we don't show again today localStorageSetItem(whatToRemove, Date.now()) } // Update on speed-limit change self.speedLimit.subscribe(function(newValue) { // Only on new load if(!self.speedLimitInt()) return; // Update if(self.speedLimitInt() != newValue) { callAPI({ mode: "config", name: "speedlimit", value: newValue }) } }); // Clear speedlimit self.clearSpeedLimit = function() { // Send call to override speedlimit callAPI({ mode: "config", name: "speedlimit", value: 100 }) self.speedLimitInt(100.0) self.speedLimit(100.0) } // Shutdown options self.onQueueFinish.subscribe(function(newValue) { // Ignore updates before the page is done if(!self.hasStatusInfo()) return; // Something changes callAPI({ mode: 'queue', name: 'change_complete_action', value: newValue }) }) // Use global settings or device-specific? self.useGlobalOptions.subscribe(function(newValue) { // Reload in case of enabling global options if(newValue) document.location = document.location; }) // Update refreshrate self.refreshRate.subscribe(function(newValue) { // Refresh now self.refresh(); // Save in config if global-settings if(self.useGlobalOptions()) { callAPI({ mode: "set_config", section: "misc", keyword: "refresh_rate", value: newValue }) } }) /*** Add NZB's ***/ // Updating the label self.updateBrowseLabel = function(data, event) { // Get filename var fileName = $(event.target).val().replace(/\\/g, '/').replace(/.*\//, ''); // Set label if(fileName) $('.btn-file em').text(fileName) } // From the upload self.addNZBFromFileForm = function(form) { // Anything? if(!$(form.nzbFile)[0].files[0]) { $('.btn-file').attr('style', 'border-color: red !important') setTimeout(function() { $('.btn-file').css('border-color', '') }, 2000) return false; } // Upload showNotification('.main-notification-box-uploading', 0, 1) self.addNZBFromFile($(form.nzbFile)[0].files[0]); // After that, hide and reset $("#modal-add-nzb").modal("hide"); form.reset() $('#nzbname').val('') $('.btn-file em').html(glitterTranslate.chooseFile + '…') } // From URL self.addNZBFromURL = function(form) { // Anything? if(!$(form.nzbURL).val()) { $(form.nzbURL).attr('style', 'border-color: red !important') setTimeout(function() { $(form.nzbURL).css('border-color', '') }, 2000) return false; } // Add callAPI({ mode: "addurl", name: $(form.nzbURL).val(), nzbname: $('#nzbname').val(), cat: $('#modal-add-nzb select[name="Category"]').val() == '' ? 'Default' : $('#modal-add-nzb select[name="Category"]').val(), script: $('#modal-add-nzb select[name="Post-processing"]').val() == '' ? 'Default' : $('#modal-add-nzb select[name="Post-processing"]').val(), priority: $('#modal-add-nzb select[name="Priority"]').val() == '' ? -100 : $('#modal-add-nzb select[name="Priority"]').val(), pp: $('#modal-add-nzb select[name="Processing"]').val() == '' ? -1 : $('#modal-add-nzb select[name="Processing"]').val() }).then(function(r) { // Hide and reset/refresh self.refresh() $("#modal-add-nzb").modal("hide"); form.reset() $('#nzbname').val('') }); } // From the upload or filedrop self.addNZBFromFile = function(file) { // Adding a file happens through this special function var data = new FormData(); data.append("name", file); data.append("mode", "addfile"); data.append("nzbname", $('#nzbname').val()); data.append("cat", $('#modal-add-nzb select[name="Category"]').val() == '' ? 'Default' : $('#modal-add-nzb select[name="Category"]').val()); // Default category data.append("script", $('#modal-add-nzb select[name="Post-processing"]').val() == '' ? 'Default' : $('#modal-add-nzb select[name="Post-processing"]').val()); // Default script data.append("priority", $('#modal-add-nzb select[name="Priority"]').val() == '' ? -100 : $('#modal-add-nzb select[name="Priority"]').val()); // Default priority data.append("pp", $('#modal-add-nzb select[name="Processing"]').val() == '' ? -1 : $('#modal-add-nzb select[name="Processing"]').val()); // Default post-processing options data.append("apikey", apiKey); // Add $.ajax({ url: "./tapi", type: "POST", cache: false, processData: false, contentType: false, data: data }).then(function(r) { // Hide notification hideNotification('.main-notification-box-uploading') // Refresh self.refresh(); }); } // Load status info self.loadStatusInfo = function(item, event) { // Full refresh? Only on click and for the status-screen var statusFullRefresh = (event != undefined) && $('#options-status').hasClass('active'); // Make it spin self.hasStatusInfo(false) // Load the custom status info callAPI({ mode: 'fullstatus', skip_dashboard: (!statusFullRefresh)*1 }).then(function(data) { // Update basic self.statusInfo.loglevel(data.status.loglevel) self.statusInfo.cache_art(data.status.cache_art) self.statusInfo.cache_size(data.status.cache_size) self.statusInfo.folders(data.status.folders) // Update the full set if(statusFullRefresh) { self.statusInfo.pystone(data.status.pystone) self.statusInfo.cpumodel(data.status.cpumodel) self.statusInfo.downloaddir(data.status.downloaddir) self.statusInfo.downloaddirspeed(data.status.downloaddirspeed) self.statusInfo.completedir(data.status.completedir) self.statusInfo.completedirspeed(data.status.completedirspeed) self.statusInfo.dnslookup(data.status.dnslookup) self.statusInfo.localipv4(data.status.localipv4) self.statusInfo.publicipv4(data.status.publicipv4) self.statusInfo.ipv6(data.status.ipv6 || glitterTranslate.noneText) // Loaded disk info self.hasDiskStatusInfo(true) } // Update the servers if(self.statusInfo.servers().length == 0) { // Initial add $.each(data.status.servers, function() { self.statusInfo.servers.push({ 'servername': this.servername, 'serveroptional': this.serveroptional, 'serverpriority': this.serverpriority, 'servertotalconn': this.servertotalconn, 'serveractiveconn': ko.observable(this.serveractiveconn), 'servererror': ko.observable(this.servererror), 'serveractive': ko.observable(this.serveractive), 'serverconnections': ko.observableArray(this.serverconnections) }) }) // Only now we can subscribe to the log-level-changes! self.statusInfo.loglevel.subscribe(function(newValue) { // Update log-level callSpecialAPI('./status/change_loglevel/', { loglevel: newValue }); }) } else { // Update $.each(data.status.servers, function(index) { var activeServer = self.statusInfo.servers()[index]; activeServer.serveractiveconn(this.serveractiveconn) activeServer.servererror(this.servererror) activeServer.serveractive(this.serveractive) activeServer.serverconnections(this.serverconnections) }) } // Add tooltips to possible new items if(!isMobile) $('#modal-options [data-tooltip="true"]').tooltip({ trigger: 'hover', container: 'body' }) // Stop it spin self.hasStatusInfo(true) }); } // Do a disk-speedtest self.testDiskSpeed = function(item, event) { self.hasDiskStatusInfo(false) // Run it and then display it callSpecialAPI('./status/dashrefresh/').then(function() { self.loadStatusInfo(true, true) }) } // Unblock server self.unblockServer = function(servername) { callSpecialAPI("./status/unblock_server/", { server: servername }).then(function() { $("#modal-options").modal("hide"); }) } // Refresh connections page var connectionRefresh $('.nav-tabs a[href="#options_connections"]').on('shown.bs.tab', function() { // Check size on open checkSize() // Set the interval connectionRefresh = setInterval(function() { // Start small checkSize() // Check if still visible if(!$('#options_connections').is(':visible') && connectionRefresh) { // Stop refreshing clearInterval(connectionRefresh) return } // Only when we show them if(self.showActiveConnections()) { self.loadStatusInfo() } }, self.refreshRate() * 1000) }) // On close of the tab $('.nav-tabs a[href="#options_connections"]').on('hidden.bs.tab', function() { checkSize() }) // Function that handles the actual sizing of connections tab function checkSize() { // Any connections? if(self.showActiveConnections() && $('#options_connections').is(':visible') && $('.table-server-connections').height() > 1) { var mainWidth = $('.main-content').width() $('#modal-options .modal-dialog').width(mainWidth*0.85 > 650 ? mainWidth*0.85 : '') } else { // Small again $('#modal-options .modal-dialog').width('') } } // Make sure Connections get refreshed also after open->close->open $('#modal-options').on('show.bs.modal', function () { // Trigger $('.nav-tabs a[href="#options_connections"]').trigger('shown.bs.tab') }) // Orphaned folder processing self.folderProcess = function(folder, htmlElement) { // Hide tooltips (otherwise they stay forever..) $('#options-orphans [data-tooltip="true"]').tooltip('hide') // Show notification on delete if($(htmlElement.currentTarget).data('action') == 'delete') { showNotification('.main-notification-box-removing', 1000) } else { // Adding back to queue showNotification('.main-notification-box-sendback', 2000) } // Activate callSpecialAPI("./status/" + $(htmlElement.currentTarget).data('action'), { name: $("
").html(folder).text() }).then(function() { // Remove item and load status data $(htmlElement.currentTarget).parent().parent().fadeOut(fadeOnDeleteDuration) // Refresh self.loadStatusInfo(true, true) // Hide notification hideNotification(true) }) } // Orphaned folder deletion of all self.removeAllOrphaned = function() { if(!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) { // Show notification showNotification('.main-notification-box-removing-multiple', 0, self.statusInfo.folders().length) // Delete them all callSpecialAPI("./status/delete_all/").then(function() { // Remove notifcation and update screen hideNotification(true) self.loadStatusInfo(true, true) }) } } // Orphaned folder adding of all self.addAllOrphaned = function() { if(!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) { // Show notification showNotification('.main-notification-box-sendback') // Delete them all callSpecialAPI("./status/add_all/").then(function() { // Remove notifcation and update screen hideNotification(true) self.loadStatusInfo(true, true) }) } } // Toggle Glitter's compact layout dynamically self.displayCompact.subscribe(function() { $('body').toggleClass('container-compact') }) // Toggle Glitter's tabbed modus self.displayTabbed.subscribe(function() { $('body').toggleClass('container-tabbed') }) /** SABnzb options **/ // Shutdown self.shutdownSAB = function() { if(confirm(glitterTranslate.shutdown)) { // Show notification and return true to follow the URL showNotification('.main-notification-box-shutdown') return true } } // Restart self.restartSAB = function() { if(!confirm(glitterTranslate.restart)) return; // Call restart function callSpecialAPI("./config/restart/") // Set counter, we need at least 15 seconds self.isRestarting(Math.max(1, Math.floor(15 / self.refreshRate()))); // Force refresh in case of very long refresh-times if(self.refreshRate() > 30) { setTimeout(self.refresh, 30 * 1000) } } // Queue actions self.doQueueAction = function(data, event) { // Event var theAction = $(event.target).data('mode'); // Show notification if available if(['rss_now', 'watched_now'].indexOf(theAction) > -1) { showNotification('.main-notification-box-' + theAction, 2000) } // Send to the API callAPI({ mode: theAction }) } // Repair queue self.repairQueue = function() { if(!confirm(glitterTranslate.repair)) return; // Hide the modal and show the notifucation $("#modal-options").modal("hide"); showNotification('.main-notification-box-queue-repair') // Call the API callSpecialAPI("./config/repair/").then(function() { hideNotification(true) }) } // Force disconnect self.forceDisconnect = function() { // Show notification showNotification('.main-notification-box-disconnect', 3000) // Call API callSpecialAPI("./status/disconnect/").then(function() { $("#modal-options").modal("hide"); }) } /*** Retrieve config information and do startup functions ***/ // Force compact mode as fast as possible if(localStorageGetItem('displayCompact') === 'true') { // Add extra class $('body').addClass('container-compact') } // Tabbed layout? if(localStorageGetItem('displayTabbed') === 'true') { $('body').addClass('container-tabbed') } // Get the speed-limit, refresh rate and server names callAPI({ mode: 'get_config' }).then(function(response) { // Do we use global, or local settings? if(self.useGlobalOptions()) { // Set refreshrate (defaults to 1/s) if(!response.config.misc.refresh_rate) response.config.misc.refresh_rate = 1; self.refreshRate(response.config.misc.refresh_rate.toString()); // Set history limit self.history.paginationLimit(response.config.misc.history_limit.toString()) // Set queue limit self.queue.paginationLimit(response.config.misc.queue_limit.toString()) } // Set bandwidth limit if(!response.config.misc.bandwidth_max) response.config.misc.bandwidth_max = false; self.bandwithLimit(response.config.misc.bandwidth_max); // Save servers (for reporting functionality of OZnzb) self.servers = response.config.servers; // Update message if(newRelease) { self.allMessages.push({ index: 'UpdateMsg', type: glitterTranslate.status['INFO'], text: (''+glitterTranslate.updateAvailable+' '+newRelease+' '), css: 'info' }); } // Message about cache - Not for 5 days if user ignored it if(!response.config.misc.cache_limit && localStorageGetItem('CacheMsg')*1+(1000*3600*24*5) < Date.now()) { self.allMessages.push({ index: 'CacheMsg', type: glitterTranslate.status['INFO'], text: (''+glitterTranslate.useCache.replace(/
/g, " ")+'
'), css: 'info', clear: function() { self.clearMessages('CacheMsg')} }); } }) // Orphaned folder check - Not for 5 days if user ignored it var orphanMsg = localStorageGetItem('OrphanedMsg')*1+(1000*3600*24*5) < Date.now(); // Delay the check if(orphanMsg) { setTimeout(self.loadStatusInfo, 200); } // On any status load we check Orphaned folders self.hasStatusInfo.subscribe(function(finishedLoading) { // Loaded or just starting? if(!finishedLoading) return; // Orphaned folders? If user clicked away we check again in 5 days if(self.statusInfo.folders().length >= 3 && orphanMsg) { // Check if not already there if(!ko.utils.arrayFirst(self.allMessages(), function(item) { return item.index == 'OrphanedMsg' })) { self.allMessages.push({ index: 'OrphanedMsg', type: glitterTranslate.status['INFO'], text: glitterTranslate.orphanedJobsMsg + ' ', css: 'info', clear: function() { self.clearMessages('OrphanedMsg')} }); } } else { // Remove any message, if it was there self.allMessages.remove(function(item) { return item.index == 'OrphanedMsg'; }) } }) // Message about localStorage not being enabled every 20 days if(!hasLocalStorage && localStorageGetItem('LocalStorageMsg')*1+(1000*3600*24*20) < Date.now()) { self.allMessages.push({ index: 'LocalStorageMsg', type: glitterTranslate.status['WARNING'].replace(':', ''), text: glitterTranslate.noLocalStorage, css: 'warning', clear: function() { self.clearMessages('LocalStorageMsg')} }); } /*** Date-stuff ***/ moment.locale(displayLang); // Fill the basic info for date-formats with current date-time $('[name="general-date-format"] option').each(function() { $(this).text(displayDateTime('', $(this).val()), '') }) // Update the date every minute setInterval(function() { $('[data-timestamp]').each(function() { $(this).text(displayDateTime($(this).data('timestamp'), self.dateFormat(), 'X')) }) }, 60*1000) /*** End of main functions, start of the fun! ***/ // Trigger first refresh self.interval = setTimeout(self.refresh, parseInt(self.refreshRate()) * 1000); // And refresh now! self.refresh() // Activate tooltips if(!isMobile) $('[data-tooltip="true"]').tooltip({ trigger: 'hover', container: 'body' }) }