2018-10-16 02:37:07 +00:00
|
|
|
;(function () {
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var Vue = window.Vue;
|
2018-10-18 07:52:30 +00:00
|
|
|
var Telebit = window.TELEBIT;
|
2018-10-16 02:37:07 +00:00
|
|
|
var api = {};
|
|
|
|
|
2018-10-21 09:32:04 +00:00
|
|
|
/*
|
2018-10-21 05:25:14 +00:00
|
|
|
function safeFetch(url, opts) {
|
|
|
|
var controller = new AbortController();
|
|
|
|
var tok = setTimeout(function () {
|
|
|
|
controller.abort();
|
|
|
|
}, 4000);
|
|
|
|
if (!opts) {
|
|
|
|
opts = {};
|
|
|
|
}
|
|
|
|
opts.signal = controller.signal;
|
|
|
|
return window.fetch(url, opts).finally(function () {
|
|
|
|
clearTimeout(tok);
|
|
|
|
});
|
|
|
|
}
|
2018-10-21 09:32:04 +00:00
|
|
|
*/
|
2018-10-21 05:25:14 +00:00
|
|
|
|
2018-10-16 02:37:07 +00:00
|
|
|
api.config = function apiConfig() {
|
2018-10-21 09:32:04 +00:00
|
|
|
return Telebit.reqLocalAsync({
|
|
|
|
url: "/api/config"
|
|
|
|
, method: "GET"
|
2018-10-21 05:25:14 +00:00
|
|
|
}).then(function (resp) {
|
2018-10-21 09:32:04 +00:00
|
|
|
var json = resp.body;
|
|
|
|
appData.config = json;
|
|
|
|
return json;
|
2018-10-16 02:37:07 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
api.status = function apiStatus() {
|
2018-10-21 09:32:04 +00:00
|
|
|
return Telebit.reqLocalAsync({ url: "/api/status", method: "GET" }).then(function (resp) {
|
|
|
|
var json = resp.body;
|
|
|
|
return json;
|
2018-10-16 02:37:07 +00:00
|
|
|
});
|
|
|
|
};
|
2018-10-23 06:44:59 +00:00
|
|
|
api.http = function apiHttp(name, handler) {
|
|
|
|
var opts = {
|
|
|
|
url: "/api/http"
|
|
|
|
, method: "POST"
|
|
|
|
, headers: { 'Content-Type': 'application/json' }
|
|
|
|
, json: { name: name, handler: handler }
|
|
|
|
};
|
|
|
|
return Telebit.reqLocalAsync(opts).then(function (resp) {
|
|
|
|
var json = resp.body;
|
|
|
|
appData.initResult = json;
|
|
|
|
return json;
|
|
|
|
}).catch(function (err) {
|
|
|
|
window.alert("Error: [init] " + (err.message || JSON.stringify(err, null, 2)));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
api.ssh = function apiSsh(port) {
|
|
|
|
var opts = {
|
|
|
|
url: "/api/ssh"
|
|
|
|
, method: "POST"
|
|
|
|
, headers: { 'Content-Type': 'application/json' }
|
|
|
|
, json: { port: port }
|
|
|
|
};
|
|
|
|
return Telebit.reqLocalAsync(opts).then(function (resp) {
|
|
|
|
var json = resp.body;
|
|
|
|
appData.initResult = json;
|
|
|
|
return json;
|
|
|
|
}).catch(function (err) {
|
|
|
|
window.alert("Error: [init] " + (err.message || JSON.stringify(err, null, 2)));
|
|
|
|
});
|
|
|
|
};
|
2018-10-23 04:36:46 +00:00
|
|
|
api.enable = function apiEnable() {
|
2018-10-21 05:25:14 +00:00
|
|
|
var opts = {
|
2018-10-23 04:36:46 +00:00
|
|
|
url: "/api/enable"
|
2018-10-21 09:32:04 +00:00
|
|
|
, method: "POST"
|
2018-10-23 04:36:46 +00:00
|
|
|
//, headers: { 'Content-Type': 'application/json' }
|
|
|
|
};
|
|
|
|
return Telebit.reqLocalAsync(opts).then(function (resp) {
|
|
|
|
var json = resp.body;
|
2018-10-23 06:44:59 +00:00
|
|
|
console.log('enable', json);
|
2018-10-23 04:36:46 +00:00
|
|
|
return json;
|
|
|
|
}).catch(function (err) {
|
2018-10-23 06:44:59 +00:00
|
|
|
window.alert("Error: [enable] " + (err.message || JSON.stringify(err, null, 2)));
|
2018-10-23 04:36:46 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
api.disable = function apiDisable() {
|
|
|
|
var opts = {
|
|
|
|
url: "/api/disable"
|
|
|
|
, method: "POST"
|
|
|
|
//, headers: { 'Content-Type': 'application/json' }
|
2018-10-21 05:25:14 +00:00
|
|
|
};
|
2018-10-21 09:32:04 +00:00
|
|
|
return Telebit.reqLocalAsync(opts).then(function (resp) {
|
|
|
|
var json = resp.body;
|
2018-10-23 06:44:59 +00:00
|
|
|
console.log('disable', json);
|
2018-10-21 09:32:04 +00:00
|
|
|
return json;
|
|
|
|
}).catch(function (err) {
|
2018-10-23 06:44:59 +00:00
|
|
|
window.alert("Error: [disable] " + (err.message || JSON.stringify(err, null, 2)));
|
2018-10-21 05:25:14 +00:00
|
|
|
});
|
|
|
|
};
|
2018-10-16 02:37:07 +00:00
|
|
|
|
2018-10-22 06:17:49 +00:00
|
|
|
function showOtp(otp, pollUrl) {
|
|
|
|
localStorage.setItem('poll_url', pollUrl);
|
|
|
|
telebitState.pollUrl = pollUrl;
|
|
|
|
appData.init.otp = otp;
|
|
|
|
changeState('otp');
|
|
|
|
}
|
|
|
|
function doConfigure() {
|
|
|
|
if (telebitState.dir.pair_request) {
|
|
|
|
telebitState._can_pair = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Read config from form
|
|
|
|
//
|
|
|
|
|
|
|
|
// Create Empty Config, If Necessary
|
|
|
|
if (!telebitState.config) { telebitState.config = {}; }
|
|
|
|
if (!telebitState.config.greenlock) { telebitState.config.greenlock = {}; }
|
|
|
|
|
|
|
|
// Populate Config
|
|
|
|
if (appData.init.teletos && appData.init.letos) { telebitState.config.agreeTos = true; }
|
|
|
|
if (appData.init.relay) { telebitState.config.relay = appData.init.relay; }
|
|
|
|
if (appData.init.email) { telebitState.config.email = appData.init.email; }
|
|
|
|
if ('undefined' !== typeof appData.init.letos) { telebitState.config.greenlock.agree = appData.init.letos; }
|
|
|
|
if ('newsletter' === appData.init.notifications) {
|
|
|
|
telebitState.config.newsletter = true; telebitState.config.communityMember = true;
|
|
|
|
}
|
|
|
|
if ('important' === appData.init.notifications) { telebitState.config.communityMember = true; }
|
|
|
|
if (appData.init.acmeVersion) { telebitState.config.greenlock.version = appData.init.acmeVersion; }
|
|
|
|
if (appData.init.acmeServer) { telebitState.config.greenlock.server = appData.init.acmeServer; }
|
|
|
|
|
|
|
|
// Temporary State
|
|
|
|
telebitState._otp = Telebit.otp();
|
|
|
|
appData.init.otp = telebitState._otp;
|
|
|
|
|
|
|
|
return Telebit.authorize(telebitState, showOtp).then(function () {
|
|
|
|
console.log('1 api.init...');
|
|
|
|
return api.initialize();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-18 07:11:37 +00:00
|
|
|
// TODO test for internet connectivity (and telebit connectivity)
|
|
|
|
var DEFAULT_RELAY = 'telebit.cloud';
|
|
|
|
var BETA_RELAY = 'telebit.ppl.family';
|
2018-10-21 05:25:14 +00:00
|
|
|
var TELEBIT_RELAYS = [
|
|
|
|
DEFAULT_RELAY
|
|
|
|
, BETA_RELAY
|
|
|
|
];
|
|
|
|
var PRODUCTION_ACME = 'https://acme-v02.api.letsencrypt.org/directory';
|
|
|
|
var STAGING_ACME = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
2018-10-16 02:37:07 +00:00
|
|
|
var appData = {
|
2018-10-23 04:36:46 +00:00
|
|
|
config: {}
|
|
|
|
, status: {}
|
2018-10-18 07:11:37 +00:00
|
|
|
, init: {
|
|
|
|
teletos: true
|
|
|
|
, letos: true
|
|
|
|
, notifications: "important"
|
|
|
|
, relay: DEFAULT_RELAY
|
2018-10-21 05:25:14 +00:00
|
|
|
, telemetry: true
|
|
|
|
, acmeServer: PRODUCTION_ACME
|
2018-10-18 07:11:37 +00:00
|
|
|
}
|
2018-10-23 06:44:59 +00:00
|
|
|
, state: {}
|
2018-10-18 07:11:37 +00:00
|
|
|
, views: {
|
2018-10-22 06:17:49 +00:00
|
|
|
flash: {
|
|
|
|
error: ""
|
|
|
|
}
|
|
|
|
, section: {
|
|
|
|
loading: true
|
|
|
|
, setup: false
|
2018-10-21 05:25:14 +00:00
|
|
|
, advanced: false
|
2018-10-22 06:17:49 +00:00
|
|
|
, otp: false
|
|
|
|
, status: false
|
2018-10-18 07:11:37 +00:00
|
|
|
}
|
|
|
|
}
|
2018-10-23 06:44:59 +00:00
|
|
|
, newHttp: {}
|
2018-10-16 02:37:07 +00:00
|
|
|
};
|
2018-10-21 05:25:14 +00:00
|
|
|
var telebitState = {};
|
2018-10-16 02:37:07 +00:00
|
|
|
var appMethods = {
|
|
|
|
initialize: function () {
|
|
|
|
console.log("call initialize");
|
2018-10-18 07:11:37 +00:00
|
|
|
if (!appData.init.relay) {
|
|
|
|
appData.init.relay = DEFAULT_RELAY;
|
|
|
|
}
|
2018-10-21 05:25:14 +00:00
|
|
|
appData.init.relay = appData.init.relay.toLowerCase();
|
|
|
|
telebitState = { relay: appData.init.relay };
|
2018-10-22 06:17:49 +00:00
|
|
|
|
2018-10-21 05:25:14 +00:00
|
|
|
return Telebit.api.directory(telebitState).then(function (dir) {
|
|
|
|
if (!dir.api_host) {
|
|
|
|
window.alert("Error: '" + telebitState.relay + "' does not appear to be a valid telebit service");
|
2018-10-18 07:52:30 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-10-22 06:17:49 +00:00
|
|
|
|
|
|
|
telebitState.dir = dir;
|
|
|
|
|
|
|
|
// If it's one of the well-known relays
|
2018-10-21 05:25:14 +00:00
|
|
|
if (-1 !== TELEBIT_RELAYS.indexOf(appData.init.relay)) {
|
2018-10-22 06:17:49 +00:00
|
|
|
return doConfigure();
|
2018-10-21 05:25:14 +00:00
|
|
|
} else {
|
|
|
|
changeState('advanced');
|
|
|
|
}
|
|
|
|
}).catch(function (err) {
|
2018-10-21 09:32:04 +00:00
|
|
|
console.error(err);
|
2018-10-22 06:17:49 +00:00
|
|
|
window.alert("Error: [initialize] " + (err.message || JSON.stringify(err, null, 2)));
|
2018-10-18 07:52:30 +00:00
|
|
|
});
|
2018-10-18 07:11:37 +00:00
|
|
|
}
|
2018-10-21 05:25:14 +00:00
|
|
|
, advance: function () {
|
2018-10-22 06:17:49 +00:00
|
|
|
return doConfigure();
|
2018-10-21 05:25:14 +00:00
|
|
|
}
|
|
|
|
, productionAcme: function () {
|
|
|
|
console.log("prod acme:");
|
|
|
|
appData.init.acmeServer = PRODUCTION_ACME;
|
|
|
|
console.log(appData.init.acmeServer);
|
|
|
|
}
|
|
|
|
, stagingAcme: function () {
|
|
|
|
console.log("staging acme:");
|
|
|
|
appData.init.acmeServer = STAGING_ACME;
|
|
|
|
console.log(appData.init.acmeServer);
|
|
|
|
}
|
2018-10-18 07:11:37 +00:00
|
|
|
, defaultRelay: function () {
|
|
|
|
appData.init.relay = DEFAULT_RELAY;
|
|
|
|
}
|
|
|
|
, betaRelay: function () {
|
|
|
|
appData.init.relay = BETA_RELAY;
|
2018-10-16 02:37:07 +00:00
|
|
|
}
|
2018-10-23 04:36:46 +00:00
|
|
|
, enable: function () {
|
|
|
|
api.enable();
|
2018-10-21 05:25:14 +00:00
|
|
|
}
|
2018-10-23 04:36:46 +00:00
|
|
|
, disable: function () {
|
|
|
|
api.disable();
|
2018-10-21 05:25:14 +00:00
|
|
|
}
|
2018-10-23 06:44:59 +00:00
|
|
|
, ssh: function (port) {
|
|
|
|
// -1 to disable
|
|
|
|
// 0 is auto (22)
|
|
|
|
// 1-65536
|
|
|
|
api.ssh(port || 22);
|
|
|
|
}
|
|
|
|
, createHttp: function (domain, handler) {
|
|
|
|
api.http(domain.name, handler);
|
|
|
|
appData.newHttp = {};
|
|
|
|
}
|
|
|
|
, changePortForward: function (domain, port) {
|
|
|
|
api.http(domain.name, port);
|
|
|
|
}
|
|
|
|
, deletePortForward: function (domain) {
|
|
|
|
api.http(domain.name, 'none');
|
|
|
|
}
|
|
|
|
, changePathHost: function (domain, path) {
|
|
|
|
api.http(domain.name, path);
|
|
|
|
}
|
|
|
|
, deletePathHost: function (domain) {
|
|
|
|
api.http(domain.name, 'none');
|
|
|
|
}
|
2018-10-21 05:25:14 +00:00
|
|
|
};
|
|
|
|
var appStates = {
|
|
|
|
setup: function () {
|
|
|
|
appData.views.section = { setup: true };
|
|
|
|
}
|
|
|
|
, advanced: function () {
|
|
|
|
appData.views.section = { advanced: true };
|
|
|
|
}
|
2018-10-22 06:17:49 +00:00
|
|
|
, otp: function () {
|
|
|
|
appData.views.section = { otp: true };
|
|
|
|
}
|
|
|
|
, status: function () {
|
2018-10-23 19:18:58 +00:00
|
|
|
function exitState() {
|
|
|
|
clearInterval(tok);
|
|
|
|
}
|
|
|
|
|
2018-10-23 19:03:36 +00:00
|
|
|
function updateStatus() {
|
|
|
|
return api.status().then(function (status) {
|
2018-10-23 19:18:58 +00:00
|
|
|
if (status.error) {
|
|
|
|
appData.views.flash.error = status.error.message || JSON.stringify(status.error, null, 2);
|
|
|
|
}
|
2018-10-23 06:44:59 +00:00
|
|
|
var wilddomains = [];
|
|
|
|
var rootdomains = [];
|
|
|
|
var subdomains = [];
|
|
|
|
var directories = [];
|
|
|
|
var portforwards = [];
|
|
|
|
var free = [];
|
2018-10-23 04:36:46 +00:00
|
|
|
appData.status = status;
|
2018-10-23 06:44:59 +00:00
|
|
|
Object.keys(appData.status.servernames).forEach(function (k) {
|
|
|
|
var s = appData.status.servernames[k];
|
|
|
|
s.name = k;
|
|
|
|
if (s.wildcard) { wilddomains.push(s); }
|
|
|
|
if (!s.sub && !s.wildcard) { rootdomains.push(s); }
|
|
|
|
if (s.sub) { subdomains.push(s); }
|
|
|
|
if (s.handler) {
|
|
|
|
if (s.handler.toString() === parseInt(s.handler, 10).toString()) {
|
|
|
|
s._port = s.handler;
|
|
|
|
portforwards.push(s);
|
|
|
|
} else {
|
|
|
|
s.path = s.handler;
|
|
|
|
directories.push(s);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
free.push(s);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
appData.status.portForwards = portforwards;
|
|
|
|
appData.status.pathHosting = directories;
|
|
|
|
appData.status.wildDomains = wilddomains;
|
|
|
|
appData.newHttp.name = (appData.status.wildDomains[0] || {}).name;
|
|
|
|
appData.state.ssh = (appData.status.ssh > 0) && appData.status.ssh || undefined;
|
2018-10-23 04:36:46 +00:00
|
|
|
});
|
2018-10-23 19:03:36 +00:00
|
|
|
}
|
|
|
|
var tok = setInterval(updateStatus, 2000);
|
2018-10-23 04:36:46 +00:00
|
|
|
|
2018-10-23 19:03:36 +00:00
|
|
|
return updateStatus().then(function () {
|
|
|
|
appData.views.section = { status: true };
|
2018-10-23 19:18:58 +00:00
|
|
|
return exitState;
|
2018-10-23 19:03:36 +00:00
|
|
|
});
|
2018-10-22 06:17:49 +00:00
|
|
|
}
|
2018-10-16 02:37:07 +00:00
|
|
|
};
|
|
|
|
|
2018-10-21 05:25:14 +00:00
|
|
|
function changeState(newstate) {
|
2018-10-22 06:17:49 +00:00
|
|
|
var newhash = '#/' + newstate + '/';
|
|
|
|
if (location.hash === newhash) {
|
|
|
|
if (!telebitState.firstState) {
|
|
|
|
telebitState.firstState = true;
|
|
|
|
setState();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
location.hash = newhash;
|
2018-10-21 05:25:14 +00:00
|
|
|
}
|
2018-10-23 04:36:46 +00:00
|
|
|
/*globals Promise*/
|
2018-10-21 05:25:14 +00:00
|
|
|
window.addEventListener('hashchange', setState, false);
|
|
|
|
function setState(/*ev*/) {
|
|
|
|
//ev.oldURL
|
|
|
|
//ev.newURL
|
2018-10-23 04:36:46 +00:00
|
|
|
if (appData.exit) {
|
|
|
|
appData.exit.then(function (exit) {
|
|
|
|
if ('function' === typeof appData.exit) {
|
|
|
|
exit();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2018-10-21 05:25:14 +00:00
|
|
|
var parts = location.hash.substr(1).replace(/^\//, '').replace(/\/$/, '').split('/');
|
|
|
|
var fn = appStates;
|
|
|
|
parts.forEach(function (s) {
|
|
|
|
console.log("state:", s);
|
|
|
|
fn = fn[s];
|
|
|
|
});
|
2018-10-23 04:36:46 +00:00
|
|
|
appData.exit = Promise.resolve(fn());
|
2018-10-21 05:25:14 +00:00
|
|
|
//appMethods.states[newstate]();
|
|
|
|
}
|
|
|
|
|
2018-10-23 04:36:46 +00:00
|
|
|
function msToHumanReadable(ms) {
|
|
|
|
var uptime = ms;
|
|
|
|
var uptimed = uptime / 1000;
|
|
|
|
var minute = 60;
|
|
|
|
var hour = 60 * minute;
|
|
|
|
var day = 24 * hour;
|
|
|
|
var days = 0;
|
|
|
|
var times = [];
|
|
|
|
while (uptimed > day) {
|
|
|
|
uptimed -= day;
|
|
|
|
days += 1;
|
|
|
|
}
|
|
|
|
times.push(days + " days ");
|
|
|
|
var hours = 0;
|
|
|
|
while (uptimed > hour) {
|
|
|
|
uptimed -= hour;
|
|
|
|
hours += 1;
|
|
|
|
}
|
|
|
|
times.push(hours.toString().padStart(2, "0") + " h ");
|
|
|
|
var minutes = 0;
|
|
|
|
while (uptimed > minute) {
|
|
|
|
uptimed -= minute;
|
|
|
|
minutes += 1;
|
|
|
|
}
|
|
|
|
times.push(minutes.toString().padStart(2, "0") + " m ");
|
|
|
|
var seconds = Math.round(uptimed);
|
|
|
|
times.push(seconds.toString().padStart(2, "0") + " s ");
|
|
|
|
return times.join('');
|
|
|
|
}
|
|
|
|
|
2018-10-16 02:37:07 +00:00
|
|
|
new Vue({
|
|
|
|
el: ".v-app"
|
|
|
|
, data: appData
|
2018-10-23 04:36:46 +00:00
|
|
|
, computed: {
|
|
|
|
statusProctime: function () {
|
|
|
|
return msToHumanReadable(this.status.proctime);
|
|
|
|
}
|
|
|
|
, statusRuntime: function () {
|
|
|
|
return msToHumanReadable(this.status.runtime);
|
|
|
|
}
|
|
|
|
, statusUptime: function () {
|
|
|
|
return msToHumanReadable(this.status.uptime);
|
|
|
|
}
|
|
|
|
}
|
2018-10-16 02:37:07 +00:00
|
|
|
, methods: appMethods
|
|
|
|
});
|
|
|
|
|
2018-10-21 05:25:14 +00:00
|
|
|
|
2018-10-22 06:17:49 +00:00
|
|
|
api.config().then(function (config) {
|
|
|
|
telebitState.config = config;
|
|
|
|
if (config.greenlock) {
|
|
|
|
appData.init.acmeServer = config.greenlock.server;
|
|
|
|
}
|
|
|
|
if (config.relay) {
|
|
|
|
appData.init.relay = config.relay;
|
|
|
|
}
|
|
|
|
if (config.email) {
|
|
|
|
appData.init.email = config.email;
|
|
|
|
}
|
|
|
|
if (config.agreeTos) {
|
|
|
|
appData.init.letos = config.agreeTos;
|
|
|
|
appData.init.teletos = config.agreeTos;
|
|
|
|
}
|
|
|
|
if (config._otp) {
|
|
|
|
appData.init.otp = config._otp;
|
|
|
|
}
|
|
|
|
|
|
|
|
telebitState.pollUrl = config._pollUrl || localStorage.getItem('poll_url');
|
|
|
|
|
|
|
|
if ((!config.token && !config._otp) || !config.relay || !config.email || !config.agreeTos) {
|
|
|
|
changeState('setup');
|
|
|
|
setState();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!config.token && config._otp) {
|
|
|
|
changeState('otp');
|
|
|
|
setState();
|
|
|
|
// this will skip ahead as necessary
|
|
|
|
return Telebit.authorize(telebitState, showOtp).then(function () {
|
|
|
|
console.log('2 api.init...');
|
|
|
|
return api.initialize();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO handle default state
|
|
|
|
changeState('status');
|
|
|
|
}).catch(function (err) {
|
|
|
|
appData.views.flash.error = err.message || JSON.stringify(err, null, 2);
|
2018-10-21 05:25:14 +00:00
|
|
|
});
|
2018-10-16 02:37:07 +00:00
|
|
|
|
|
|
|
window.api = api;
|
2018-10-22 06:17:49 +00:00
|
|
|
|
|
|
|
setTimeout(function () {
|
|
|
|
document.body.hidden = false;
|
|
|
|
}, 50);
|
|
|
|
|
2018-10-16 02:37:07 +00:00
|
|
|
}());
|