615 lines
16 KiB
JavaScript
615 lines
16 KiB
JavaScript
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: '<div class="check" :class="{ checked: value, disabled: disabled }" @keydown="handleKeyDown" @click="handleClick" tabindex="0"><div class="control"><div class="inner"></div></div><div class="label">{{ title }}</div></div>',
|
||
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: '<div class="radio" :class="{ checked: value == id, disabled: disabled }" @keydown="handleKeyDown" @click="handleClick" tabindex="0"><div class="control"><div class="inner"></div></div><div class="label">{{ title }}</div></div>',
|
||
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: '<div>' +
|
||
'<div class="start">' +
|
||
'<span class="value">{{ value.start }}</span>' +
|
||
'<div class="slidercontainer">' +
|
||
'<input type="range" min="0" max="4094" class="slider" v-model.number="value.start">' +
|
||
'</div>' +
|
||
'</div>' +
|
||
|
||
'<div class="end">' +
|
||
'<span class="value">{{ value.end }}</span>' +
|
||
'<div class="slidercontainer">' +
|
||
'<input type="range" min="1" max="4095" class="slider" v-model.number="value.end">' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'</div>',
|
||
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,
|
||
settingStatic: 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,
|
||
},
|
||
ledCount: null
|
||
},
|
||
|
||
static: {
|
||
r: 0,
|
||
g: 0,
|
||
b: 0,
|
||
w: 0
|
||
}
|
||
},
|
||
|
||
created: function()
|
||
{
|
||
var self = this;
|
||
|
||
self.notificationTimer = null;
|
||
self.disableSetStatic = false;
|
||
self.setStaticTimer = false;
|
||
|
||
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, headers: { 'Content-Type': 'application/json' } })
|
||
.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, headers: { 'Content-Type': 'application/json' } })
|
||
.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'));
|
||
},
|
||
|
||
staticOff: function()
|
||
{
|
||
this.static = {
|
||
r: 0,
|
||
g: 0,
|
||
b: 0,
|
||
w: 0
|
||
};
|
||
},
|
||
|
||
|
||
staticChanged: function()
|
||
{
|
||
var self = this;
|
||
if (self.loading || self.disableStaticChanged) return;
|
||
|
||
if (self.setStaticTimer === false)
|
||
self.setStaticTimer = setTimeout(function() { self.setStatic(); }, 200);
|
||
},
|
||
|
||
setStatic: function()
|
||
{
|
||
var self = this;
|
||
|
||
if (self.settingStatic)
|
||
self.setStaticTimer = setTimeout(function() { self.setStatic(); }, 200);
|
||
|
||
self.settingStatic = true;
|
||
self.setStaticTimer = false;
|
||
|
||
|
||
axios.get('/api/set/static', { params: this.static })
|
||
.then(function(response)
|
||
{
|
||
})
|
||
.catch(self.handleAPIError.bind(self, 'error.setColor'))
|
||
.then(function()
|
||
{
|
||
self.settingStatic = false;
|
||
});
|
||
},
|
||
},
|
||
|
||
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);
|
||
}
|
||
},
|
||
|
||
watch: {
|
||
static: {
|
||
handler: function(newValue) { this.staticChanged(); },
|
||
deep: true
|
||
}
|
||
}
|
||
});
|
||
} |