462 lines
14 KiB
JavaScript
462 lines
14 KiB
JavaScript
/* eslint-disable no-console */
|
|
/* eslint-disable no-alert */
|
|
/* eslint-disable no-undef */
|
|
/* eslint-disable no-new */
|
|
|
|
'use strict';
|
|
|
|
function bytes(bytes, decimals, kib, maxunit) {
|
|
kib = kib || false;
|
|
if (bytes === 0) return '0 B';
|
|
if (Number.isNaN(parseFloat(bytes)) && !Number.isFinite(bytes)) return 'NaN';
|
|
const k = kib ? 1024 : 1000;
|
|
const dm = decimals != null && !Number.isNaN(decimals) && decimals >= 0 ? decimals : 2;
|
|
const sizes = kib
|
|
? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB', 'BiB']
|
|
: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
|
|
let i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
if (maxunit !== undefined) {
|
|
const index = sizes.indexOf(maxunit);
|
|
if (index !== -1) i = index;
|
|
}
|
|
// eslint-disable-next-line no-restricted-properties
|
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
}
|
|
|
|
const i18n = new VueI18n({
|
|
locale: localStorage.getItem('lang') || 'en',
|
|
fallbackLocale: 'en',
|
|
messages,
|
|
});
|
|
|
|
const UI_CHART_TYPES = [
|
|
{ type: false, strokeWidth: 0 },
|
|
{ type: 'line', strokeWidth: 3 },
|
|
{ type: 'area', strokeWidth: 0 },
|
|
{ type: 'bar', strokeWidth: 0 },
|
|
];
|
|
|
|
const CHART_COLORS = {
|
|
rx: { light: 'rgba(128,128,128,0.3)', dark: 'rgba(255,255,255,0.3)' },
|
|
tx: { light: 'rgba(128,128,128,0.4)', dark: 'rgba(255,255,255,0.3)' },
|
|
gradient: { light: ['rgba(0,0,0,1.0)', 'rgba(0,0,0,1.0)'], dark: ['rgba(128,128,128,0)', 'rgba(128,128,128,0)'] },
|
|
};
|
|
|
|
new Vue({
|
|
el: '#app',
|
|
components: {
|
|
apexchart: VueApexCharts,
|
|
},
|
|
i18n,
|
|
data: {
|
|
authenticated: null,
|
|
authenticating: false,
|
|
password: null,
|
|
requiresPassword: null,
|
|
remember: false,
|
|
rememberMeEnabled: false,
|
|
|
|
clients: null,
|
|
clientsPersist: {},
|
|
clientDelete: null,
|
|
clientCreate: null,
|
|
clientCreateName: '',
|
|
clientEditName: null,
|
|
clientEditNameId: null,
|
|
clientEditAddress: null,
|
|
clientEditAddressId: null,
|
|
qrcode: null,
|
|
|
|
currentRelease: null,
|
|
latestRelease: null,
|
|
|
|
uiTrafficStats: false,
|
|
|
|
uiChartType: 0,
|
|
uiShowLinks: false,
|
|
uiShowCharts: localStorage.getItem('uiShowCharts') === '1',
|
|
uiTheme: localStorage.theme || 'auto',
|
|
prefersDarkScheme: window.matchMedia('(prefers-color-scheme: dark)'),
|
|
|
|
chartOptions: {
|
|
chart: {
|
|
background: 'transparent',
|
|
stacked: false,
|
|
toolbar: {
|
|
show: false,
|
|
},
|
|
animations: {
|
|
enabled: false,
|
|
},
|
|
parentHeightOffset: 0,
|
|
sparkline: {
|
|
enabled: true,
|
|
},
|
|
},
|
|
colors: [],
|
|
stroke: {
|
|
curve: 'smooth',
|
|
},
|
|
fill: {
|
|
type: 'gradient',
|
|
gradient: {
|
|
shade: 'dark',
|
|
type: 'vertical',
|
|
shadeIntensity: 0,
|
|
gradientToColors: CHART_COLORS.gradient[this.theme],
|
|
inverseColors: false,
|
|
opacityTo: 0,
|
|
stops: [0, 100],
|
|
},
|
|
},
|
|
dataLabels: {
|
|
enabled: false,
|
|
},
|
|
plotOptions: {
|
|
bar: {
|
|
horizontal: false,
|
|
},
|
|
},
|
|
xaxis: {
|
|
labels: {
|
|
show: false,
|
|
},
|
|
axisTicks: {
|
|
show: false,
|
|
},
|
|
axisBorder: {
|
|
show: false,
|
|
},
|
|
},
|
|
yaxis: {
|
|
labels: {
|
|
show: false,
|
|
},
|
|
min: 0,
|
|
},
|
|
tooltip: {
|
|
enabled: false,
|
|
},
|
|
legend: {
|
|
show: false,
|
|
},
|
|
grid: {
|
|
show: false,
|
|
padding: {
|
|
left: -10,
|
|
right: 0,
|
|
bottom: -15,
|
|
top: -15,
|
|
},
|
|
column: {
|
|
opacity: 0,
|
|
},
|
|
xaxis: {
|
|
lines: {
|
|
show: false,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
methods: {
|
|
dateTime: (value) => {
|
|
return new Intl.DateTimeFormat(undefined, {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: 'numeric',
|
|
minute: 'numeric',
|
|
}).format(value);
|
|
},
|
|
async refresh({
|
|
updateCharts = false,
|
|
} = {}) {
|
|
if (!this.authenticated) return;
|
|
|
|
const clients = await this.api.getClients();
|
|
this.clients = clients.map((client) => {
|
|
if (client.name.includes('@') && client.name.includes('.')) {
|
|
client.avatar = `https://gravatar.com/avatar/${sha256(client.name.toLowerCase().trim())}.jpg`;
|
|
}
|
|
|
|
if (!this.clientsPersist[client.id]) {
|
|
this.clientsPersist[client.id] = {};
|
|
this.clientsPersist[client.id].transferRxHistory = Array(50).fill(0);
|
|
this.clientsPersist[client.id].transferRxPrevious = client.transferRx;
|
|
this.clientsPersist[client.id].transferTxHistory = Array(50).fill(0);
|
|
this.clientsPersist[client.id].transferTxPrevious = client.transferTx;
|
|
}
|
|
|
|
// Debug
|
|
// client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000;
|
|
// client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000;
|
|
// client.latestHandshakeAt = new Date();
|
|
// this.requiresPassword = true;
|
|
|
|
this.clientsPersist[client.id].transferRxCurrent = client.transferRx - this.clientsPersist[client.id].transferRxPrevious;
|
|
this.clientsPersist[client.id].transferRxPrevious = client.transferRx;
|
|
this.clientsPersist[client.id].transferTxCurrent = client.transferTx - this.clientsPersist[client.id].transferTxPrevious;
|
|
this.clientsPersist[client.id].transferTxPrevious = client.transferTx;
|
|
|
|
if (updateCharts) {
|
|
this.clientsPersist[client.id].transferRxHistory.push(this.clientsPersist[client.id].transferRxCurrent);
|
|
this.clientsPersist[client.id].transferRxHistory.shift();
|
|
|
|
this.clientsPersist[client.id].transferTxHistory.push(this.clientsPersist[client.id].transferTxCurrent);
|
|
this.clientsPersist[client.id].transferTxHistory.shift();
|
|
|
|
this.clientsPersist[client.id].transferTxSeries = [{
|
|
name: 'Tx',
|
|
data: this.clientsPersist[client.id].transferTxHistory,
|
|
}];
|
|
|
|
this.clientsPersist[client.id].transferRxSeries = [{
|
|
name: 'Rx',
|
|
data: this.clientsPersist[client.id].transferRxHistory,
|
|
}];
|
|
|
|
client.transferTxHistory = this.clientsPersist[client.id].transferTxHistory;
|
|
client.transferRxHistory = this.clientsPersist[client.id].transferRxHistory;
|
|
client.transferMax = Math.max(...client.transferTxHistory, ...client.transferRxHistory);
|
|
|
|
client.transferTxSeries = this.clientsPersist[client.id].transferTxSeries;
|
|
client.transferRxSeries = this.clientsPersist[client.id].transferRxSeries;
|
|
}
|
|
|
|
client.transferTxCurrent = this.clientsPersist[client.id].transferTxCurrent;
|
|
client.transferRxCurrent = this.clientsPersist[client.id].transferRxCurrent;
|
|
|
|
client.hoverTx = this.clientsPersist[client.id].hoverTx;
|
|
client.hoverRx = this.clientsPersist[client.id].hoverRx;
|
|
|
|
return client;
|
|
});
|
|
},
|
|
login(e) {
|
|
e.preventDefault();
|
|
|
|
if (!this.password) return;
|
|
if (this.authenticating) return;
|
|
|
|
this.authenticating = true;
|
|
this.api.createSession({
|
|
password: this.password,
|
|
remember: this.remember,
|
|
})
|
|
.then(async () => {
|
|
const session = await this.api.getSession();
|
|
this.authenticated = session.authenticated;
|
|
this.requiresPassword = session.requiresPassword;
|
|
return this.refresh();
|
|
})
|
|
.catch((err) => {
|
|
alert(err.message || err.toString());
|
|
})
|
|
.finally(() => {
|
|
this.authenticating = false;
|
|
this.password = null;
|
|
});
|
|
},
|
|
logout(e) {
|
|
e.preventDefault();
|
|
|
|
this.api.deleteSession()
|
|
.then(() => {
|
|
this.authenticated = false;
|
|
this.clients = null;
|
|
})
|
|
.catch((err) => {
|
|
alert(err.message || err.toString());
|
|
});
|
|
},
|
|
createClient() {
|
|
const name = this.clientCreateName;
|
|
if (!name) return;
|
|
|
|
this.api.createClient({ name })
|
|
.catch((err) => alert(err.message || err.toString()))
|
|
.finally(() => this.refresh().catch(console.error));
|
|
},
|
|
deleteClient(client) {
|
|
this.api.deleteClient({ clientId: client.id })
|
|
.catch((err) => alert(err.message || err.toString()))
|
|
.finally(() => this.refresh().catch(console.error));
|
|
},
|
|
enableClient(client) {
|
|
this.api.enableClient({ clientId: client.id })
|
|
.catch((err) => alert(err.message || err.toString()))
|
|
.finally(() => this.refresh().catch(console.error));
|
|
},
|
|
disableClient(client) {
|
|
this.api.disableClient({ clientId: client.id })
|
|
.catch((err) => alert(err.message || err.toString()))
|
|
.finally(() => this.refresh().catch(console.error));
|
|
},
|
|
updateClientName(client, name) {
|
|
this.api.updateClientName({ clientId: client.id, name })
|
|
.catch((err) => alert(err.message || err.toString()))
|
|
.finally(() => this.refresh().catch(console.error));
|
|
},
|
|
updateClientAddress(client, address) {
|
|
this.api.updateClientAddress({ clientId: client.id, address })
|
|
.catch((err) => alert(err.message || err.toString()))
|
|
.finally(() => this.refresh().catch(console.error));
|
|
},
|
|
restoreConfig(e) {
|
|
e.preventDefault();
|
|
const file = e.currentTarget.files.item(0);
|
|
if (file) {
|
|
file.text()
|
|
.then((content) => {
|
|
this.api.restoreConfiguration(content)
|
|
.then((_result) => alert('The configuration was updated.'))
|
|
.catch((err) => alert(err.message || err.toString()))
|
|
.finally(() => this.refresh().catch(console.error));
|
|
})
|
|
.catch((err) => alert(err.message || err.toString()));
|
|
} else {
|
|
alert('Failed to load your file!');
|
|
}
|
|
},
|
|
toggleTheme() {
|
|
const themes = ['light', 'dark', 'auto'];
|
|
const currentIndex = themes.indexOf(this.uiTheme);
|
|
const newIndex = (currentIndex + 1) % themes.length;
|
|
this.uiTheme = themes[newIndex];
|
|
localStorage.theme = this.uiTheme;
|
|
this.setTheme(this.uiTheme);
|
|
},
|
|
setTheme(theme) {
|
|
const { classList } = document.documentElement;
|
|
const shouldAddDarkClass = theme === 'dark' || (theme === 'auto' && this.prefersDarkScheme.matches);
|
|
classList.toggle('dark', shouldAddDarkClass);
|
|
},
|
|
handlePrefersChange(e) {
|
|
if (localStorage.theme === 'auto') {
|
|
this.setTheme(e.matches ? 'dark' : 'light');
|
|
}
|
|
},
|
|
toggleCharts() {
|
|
localStorage.setItem('uiShowCharts', this.uiShowCharts ? 1 : 0);
|
|
},
|
|
},
|
|
filters: {
|
|
bytes,
|
|
timeago: (value) => {
|
|
return timeago.format(value, i18n.locale);
|
|
},
|
|
},
|
|
mounted() {
|
|
this.prefersDarkScheme.addListener(this.handlePrefersChange);
|
|
this.setTheme(this.uiTheme);
|
|
|
|
this.api = new API();
|
|
this.api.getSession()
|
|
.then((session) => {
|
|
this.authenticated = session.authenticated;
|
|
this.requiresPassword = session.requiresPassword;
|
|
this.refresh({
|
|
updateCharts: this.updateCharts,
|
|
}).catch((err) => {
|
|
alert(err.message || err.toString());
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
alert(err.message || err.toString());
|
|
});
|
|
|
|
this.api.getRememberMeEnabled()
|
|
.then((rememberMeEnabled) => {
|
|
this.rememberMeEnabled = rememberMeEnabled;
|
|
});
|
|
|
|
setInterval(() => {
|
|
this.refresh({
|
|
updateCharts: this.updateCharts,
|
|
}).catch(console.error);
|
|
}, 1000);
|
|
|
|
this.api.getuiTrafficStats()
|
|
.then((res) => {
|
|
this.uiTrafficStats = res;
|
|
})
|
|
.catch(() => {
|
|
this.uiTrafficStats = false;
|
|
});
|
|
|
|
this.api.getChartType()
|
|
.then((res) => {
|
|
this.uiChartType = parseInt(res, 10);
|
|
})
|
|
.catch(() => {
|
|
this.uiChartType = 0;
|
|
});
|
|
|
|
this.api.getUIShowLinks()
|
|
.then((res) => {
|
|
this.uiShowLinks = res;
|
|
})
|
|
.catch(() => {
|
|
this.uiShowLinks = false;
|
|
});
|
|
|
|
Promise.resolve().then(async () => {
|
|
const lang = await this.api.getLang();
|
|
if (lang !== localStorage.getItem('lang') && i18n.availableLocales.includes(lang)) {
|
|
localStorage.setItem('lang', lang);
|
|
i18n.locale = lang;
|
|
}
|
|
|
|
const currentRelease = await this.api.getRelease();
|
|
const latestRelease = await fetch('https://wg-easy.github.io/wg-easy/changelog.json')
|
|
.then((res) => res.json())
|
|
.then((releases) => {
|
|
const releasesArray = Object.entries(releases).map(([version, changelog]) => ({
|
|
version: parseInt(version, 10),
|
|
changelog,
|
|
}));
|
|
releasesArray.sort((a, b) => {
|
|
return b.version - a.version;
|
|
});
|
|
|
|
return releasesArray[0];
|
|
});
|
|
|
|
if (currentRelease >= latestRelease.version) return;
|
|
|
|
this.currentRelease = currentRelease;
|
|
this.latestRelease = latestRelease;
|
|
}).catch((err) => console.error(err));
|
|
},
|
|
computed: {
|
|
chartOptionsTX() {
|
|
const opts = {
|
|
...this.chartOptions,
|
|
colors: [CHART_COLORS.tx[this.theme]],
|
|
};
|
|
opts.chart.type = UI_CHART_TYPES[this.uiChartType].type || false;
|
|
opts.stroke.width = UI_CHART_TYPES[this.uiChartType].strokeWidth;
|
|
return opts;
|
|
},
|
|
chartOptionsRX() {
|
|
const opts = {
|
|
...this.chartOptions,
|
|
colors: [CHART_COLORS.rx[this.theme]],
|
|
};
|
|
opts.chart.type = UI_CHART_TYPES[this.uiChartType].type || false;
|
|
opts.stroke.width = UI_CHART_TYPES[this.uiChartType].strokeWidth;
|
|
return opts;
|
|
},
|
|
updateCharts() {
|
|
return this.uiChartType > 0 && this.uiShowCharts;
|
|
},
|
|
theme() {
|
|
if (this.uiTheme === 'auto') {
|
|
return this.prefersDarkScheme.matches ? 'dark' : 'light';
|
|
}
|
|
return this.uiTheme;
|
|
},
|
|
},
|
|
});
|