function startApp() { // Source: https://github.com/axios/axios/issues/164 axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) { var config = err.config; // If config does not exist or the retry option is not set, reject if(!config || !config.retry) return Promise.reject(err); // Set the variable for keeping track of the retry count config.__retryCount = config.__retryCount || 0; // Check if we've maxed out the total number of retries if(config.__retryCount >= config.retry) { // Reject with the error return Promise.reject(err); } // Increase the retry count config.__retryCount += 1; // Create new promise to handle exponential backoff var backoff = new Promise(function(resolve) { setTimeout(function() { resolve(); }, config.retryDelay || 1); }); // Return the promise in which recalls axios to retry the request return backoff.then(function() { return axios(config); }); }); Vue.component('check', { template: '
{{ title }}
', props: { title: String, value: { type: Boolean, default: false }, disabled: { type: Boolean, default: false } }, methods: { handleClick: function() { if (this.disabled) return; this.value = !this.value; this.$emit('input', this.value); }, handleKeyDown: function(event) { if (event.keyCode == 32) { this.handleClick(); event.preventDefault(); } } } }); Vue.component('radio', { template: '
{{ title }}
', props: { title: String, value: null, id: null, disabled: { type: Boolean, default: false } }, methods: { handleClick: function() { if (this.disabled) return; this.value = this.id; this.$emit('input', this.value); }, handleKeyDown: function(event) { if (event.keyCode == 32) { this.handleClick(); event.preventDefault(); } } } }); Vue.component('range', { template: '
' + '
' + '{{ value.start }}' + '
' + '' + '
' + '
' + '
' + '{{ value.end }}' + '
' + '' + '
' + '
' + '
', props: ['value'], mounted: function() { this.oldValue = { start: this.value.start, end: this.value.end }; }, watch: { value: { handler: function(newValue) { if (newValue.start != this.oldValue.start) { if (newValue.start > newValue.end) { newValue.end = newValue.start + 1; this.$emit('input', newValue); } } else if (newValue.end != this.oldValue.end) { if (newValue.end < newValue.start) { newValue.start = newValue.end - 1; this.$emit('input', newValue); } } this.oldValue.start = newValue.start; this.oldValue.end = newValue.end; }, deep: true } } }); var i18n = new VueI18n({ locale: navigator.language.split('-')[0], fallbackLocale: 'en', messages: messages }); var app = new Vue({ el: '#app', i18n: i18n, data: { notification: null, loading: true, saving: false, loadingIndicator: '|', uploadProgress: false, activeTab: 'status', status: { systemID: 'loading...', version: 'loading...', resetReason: null, stackTrace: false }, wifiStatus: { ap: { enabled: false, ip: '0.0.0.0' }, station: { enabled: false, status: 0, ip: '0.0.0.0' } }, connection: { hostname: null, accesspoint: true, station: false, ssid: null, password: null, dhcp: true, ip: null, subnetmask: null, gateway: null }, system: { pins: { ledAP: null, ledSTA: null, apButton: null, } } }, created: function() { var self = this; self.notificationTimer = null; document.title = i18n.t('title'); var hash = window.location.hash.substr(1); if (hash) self.activeTab = hash; self.startLoadingIndicator(); self.updateWiFiStatus(); // Sequential loading of all the settings makes sure // we don't overload the ESP8266 with requests, as that // can cause it to run out of memory easily. // This is a horrible way to implement it, but I don't feel like // including a big library or working out a clean short solution // at the moment, and it works :) self.loadStatus().then(function() { self.loadConnection().then(function() { self.loadSystem().then(function() { self.stopLoadingIndicator(); self.loading = false; }); }); }); }, methods: { showNotification: function(message, error) { var self = this; self.notification = { message: message, error: error }; if (self.notificationTimer != null) clearTimeout(self.notificationTimer); self.notificationTimer = setTimeout(function() { self.notification = null; self.notificationTimer = null; }, 5000); }, hideNotification: function() { var self = this; self.notification = null; if (self.notificationTimer != null) { clearTimeout(self.notificationTimer); self.notificationTimer = null; } }, handleAPIError: function(messageId, error) { var self = this; console.log(error); var errorMessage = ''; if (error.response) { errorMessage = 'HTTP response code ' + error.response.status; } else if (error.request) { errorMessage = 'No response'; } else { errorMessage = error.message; } self.showNotification(i18n.t(messageId) + '\n\n' + errorMessage, true); }, loadStatus: function() { var self = this; return axios.get('/api/status', { retry: 10, retryDelay: 1000 }) .then(function(response) { if (typeof response.data == 'object') self.status = response.data; }) .catch(self.handleAPIError.bind(self, 'error.loadStatus')); }, loadConnection: function() { var self = this; return axios.get('/api/connection', { retry: 10, retryDelay: 1000 }) .then(function(response) { if (typeof response.data == 'object') self.connection = response.data; }) .catch(self.handleAPIError.bind(self, 'error.loadConnection')); }, loadSystem: function() { var self = this; return axios.get('/api/system', { retry: 10, retryDelay: 1000 }) .then(function(response) { if (typeof response.data == 'object') self.system = response.data; }) .catch(self.handleAPIError.bind(self, 'error.loadSystem')); }, applyConnection: function() { var self = this; if (self.saving) return; self.saving = true; axios.post('/api/connection', { hostname: self.connection.hostname, accesspoint: self.connection.accesspoint, station: self.connection.station, ssid: self.connection.ssid, password: self.connection.password, dhcp: self.connection.dhcp, ip: self.connection.ip, subnetmask: self.connection.subnetmask, gateway: self.connection.gateway, }, { retry: 10, retryDelay: 1000 }) .then(function(response) { }) .catch(self.handleAPIError.bind(self, 'error.applyConnection')) .then(function() { self.saving = false; }); }, applySystem: function() { var self = this; if (self.saving) return; self.saving = true; axios.post('/api/system', self.system, { retry: 10, retryDelay: 1000 }) .then(function(response) { self.showNotification(i18n.t('rebootPending')); }) .catch(self.handleAPIError.bind(self, 'error.applySystem')) .then(function() { self.saving = false; }); }, startLoadingIndicator: function() { var self = this; self.loadingStage = 0; self.loadingTimer = setInterval(function() { self.loadingStage++; switch (self.loadingStage) { case 1: self.loadingIndicator = '/'; break; case 2: self.loadingIndicator = '-'; break; case 3: self.loadingIndicator = '\\'; break; case 4: self.loadingIndicator = '|'; self.loadingStage = 0; break; } }, 250); }, stopLoadingIndicator: function() { clearInterval(this.loadingTimer); }, getWiFiStationStatus: function() { if (!this.wifiStatus.station.enabled) return 'disconnected'; switch (this.wifiStatus.station.status) { case 0: // WL_IDLE_STATUS case 2: // WL_SCAN_COMPLETED return 'connecting'; case 1: // WL_NO_SSID_AVAIL case 4: // WL_CONNECT_FAILED case 5: // WL_CONNECTION_LOST return 'error'; case 3: // WL_CONNECTED return 'connected'; case 6: // WL_DISCONNECTED default: return 'disconnected'; } }, getWiFiStationStatusText: function() { if (!this.wifiStatus.station.enabled) return i18n.t('wifiStatus.stationmode.disabled'); switch (this.wifiStatus.station.status) { case 0: // WL_IDLE_STATUS return i18n.t('wifiStatus.stationmode.idle'); case 1: // WL_NO_SSID_AVAIL return i18n.t('wifiStatus.stationmode.noSSID'); case 2: // WL_SCAN_COMPLETED return i18n.t('wifiStatus.stationmode.scanCompleted'); case 3: // WL_CONNECTED return this.wifiStatus.station.ip; case 4: // WL_CONNECT_FAILED return i18n.t('wifiStatus.stationmode.connectFailed'); case 5: // WL_CONNECTION_LOST return i18n.t('wifiStatus.stationmode.connectionLost'); case 6: // WL_DISCONNECTED default: return i18n.t('wifiStatus.stationmode.disconnected'); } }, updateWiFiStatus: function() { var self = this; if (!self.saving) { axios.get('/api/connection/status', { retry: 10, retryDelay: 1000 }) .then(function(response) { if (typeof response.data == 'object') self.wifiStatus = response.data; }) .catch(self.handleAPIError.bind(self, 'error.updateWiFiStatus')) .then(function() { setTimeout(self.updateWiFiStatus, 5000); }); } else setTimeout(self.updateWiFiStatus, 5000); }, uploadFirmware: function() { var self = this; if (self.saving) return; self.saving = true; self.uploadProgress = 0; var data = new FormData(); data.append('file', document.getElementById('firmwareFile').files[0]); var config = { timeout: 360000, onUploadProgress: function(progressEvent) { self.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total); } }; axios.post('/api/firmware', data, config) .then(function(response) { self.showNotification(i18n.t('rebootPending')); }) .catch(self.handleAPIError.bind(self, 'error.uploadFirmware')) .then(function() { self.uploadProgress = false; self.saving = false; document.getElementById('firmware').reset(); }); }, deleteStackTrace: function() { var self = this; return axios.get('/api/stacktrace/delete', { retry: 10, retryDelay: 1000 }) .then(function(response) { self.status.resetReason = 0; self.status.stackTrace = false; }) .catch(self.handleAPIError.bind(self, 'error.stackTraceDeleteError')); } }, computed: { hasResetError: function() { var self = this; /* REASON_DEFAULT_RST = 0 normal startup by power on REASON_WDT_RST = 1 hardware watch dog reset REASON_EXCEPTION_RST = 2 exception reset, GPIO status won’t change REASON_SOFT_WDT_RST = 3 software watch dog reset, GPIO status won’t change REASON_SOFT_RESTART = 4 software restart ,system_restart , GPIO status won’t change REASON_DEEP_SLEEP_AWAKE = 5 wake up from deep-sleep REASON_EXT_SYS_RST = 6 system reset */ return (self.status.resetReason === 1 || self.status.resetReason === 2 || self.status.resetReason === 3 || self.status.stackTrace); } } }); }