Commit 4e15a53b authored by Alain Takoudjou's avatar Alain Takoudjou

cleanup monitor2, allow to edit cors domain configurations

parent e17666e2
...@@ -54,41 +54,6 @@ eggs += ...@@ -54,41 +54,6 @@ eggs +=
Jinja2 Jinja2
# Monitor html files
# XXX Can be removed from software release
[monitor-web-default-promise-interface]
<= monitor-web-base
filename = default-promise-interface.html
md5sum = eaedae330cd155f8b693b418286d0d98
[monitor-web-index-html]
<= monitor-web-base
filename = index.html
md5sum = 58fe6ada21d1b0b25a2a38374ec676d3
[monitor-web-monitor-css]
<= monitor-web-base
filename = monitor.css
md5sum = 3cb91de30c729143af78461431fc6f8c
[monitor-web-monitor-js]
<= monitor-web-base
filename = monitor.js
md5sum = 29ee4b9a99a7164581ccf5cbf75d33ba
[monitor-web-monitor-logout-page]
<= monitor-web-base
filename = logout.html
md5sum = b210c6842df541305d299081bc1bf81e
[monitor-password-promise-interface]
<= monitor-download-base
filename = monitor-password-interface.html
url = ${:_profile_base_location_}/web/${:filename}
md5sum = 04b664dfb47bfd3d01502768311aa239
# End Monitor web files
# Monitor templates files # Monitor templates files
[monitor-httpd-conf] [monitor-httpd-conf]
<= monitor-template-base <= monitor-template-base
...@@ -127,7 +92,7 @@ recipe = slapos.recipe.template:jinja2 ...@@ -127,7 +92,7 @@ recipe = slapos.recipe.template:jinja2
filename = template-monitor.cfg filename = template-monitor.cfg
template = ${:_profile_base_location_}/instance-monitor.cfg.jinja2.in template = ${:_profile_base_location_}/instance-monitor.cfg.jinja2.in
rendered = ${buildout:directory}/template-monitor.cfg rendered = ${buildout:directory}/template-monitor.cfg
md5sum = c26a0f81d67dd28b7150b97eb21b4f37 md5sum = e439e22e754a50e1a3500cd4a995f6d8
context = context =
key apache_location apache:location key apache_location apache:location
key gzip_location gzip:location key gzip_location gzip:location
...@@ -139,16 +104,6 @@ context = ...@@ -139,16 +104,6 @@ context =
raw monitor_instance_info ${monitor-instance-info:location}/${monitor-instance-info:filename} raw monitor_instance_info ${monitor-instance-info:location}/${monitor-instance-info:filename}
raw monitor_globalstate ${monitor-globalstate:location}/${monitor-globalstate:filename} raw monitor_globalstate ${monitor-globalstate:location}/${monitor-globalstate:filename}
raw monitor_password_promise_template ${monitor-password-promise:location}/${monitor-password-promise:filename} raw monitor_password_promise_template ${monitor-password-promise:location}/${monitor-password-promise:filename}
raw monitor_password_cgi_template ${monitor-password-py-cgi:location}/${monitor-password-py-cgi:filename}
raw monitor_password_promise_interface_template ${monitor-password-promise-interface:location}/${monitor-password-promise-interface:filename}
raw monitor_web_default_promise_interface ${monitor-web-default-promise-interface:location}/${monitor-web-default-promise-interface:filename}
raw monitor_web_index_html ${monitor-web-index-html:location}/${monitor-web-index-html:filename}
raw monitor_web_monitor_css ${monitor-web-monitor-css:location}/${monitor-web-monitor-css:filename}
raw monitor_web_directory ${monitor-web-index-html:location}
key monitor_web_monitor_logout_cgi monitor-web-monitor-logout-cgi:rendered
raw monitor_web_monitor_logout_page ${monitor-web-monitor-logout-page:location}/${monitor-web-monitor-logout-page:filename}
raw monitor_web_monitor_promise_runner_cgi ${monitor-web-monitor-promise-runner-cgi:location}/${monitor-web-monitor-promise-runner-cgi:filename}
raw monitor_web_monitor_js ${monitor-web-monitor-js:location}/${monitor-web-monitor-js:filename}
raw curl_executable_location ${curl:location}/bin/curl raw curl_executable_location ${curl:location}/bin/curl
raw dash_executable_location ${dash:location}/bin/dash raw dash_executable_location ${dash:location}/bin/dash
raw dcron_executable_location ${dcron:location}/sbin/crond raw dcron_executable_location ${dcron:location}/sbin/crond
...@@ -165,7 +120,7 @@ context = ...@@ -165,7 +120,7 @@ context =
[monitor2-bin] [monitor2-bin]
<= monitor-template-script <= monitor-template-script
filename = monitor.py filename = monitor.py
md5sum = af9eba3b4e7f265f9779874df6b94afb md5sum = 927501a8c4ce8dcc39d55af1efdc66ed
[run-promise-py] [run-promise-py]
recipe = slapos.recipe.template:jinja2 recipe = slapos.recipe.template:jinja2
...@@ -199,7 +154,7 @@ md5sum = cc65aebd4c35b3172a7ca83abde761bc ...@@ -199,7 +154,7 @@ md5sum = cc65aebd4c35b3172a7ca83abde761bc
[monitor-document-edit] [monitor-document-edit]
<= monitor-template-script <= monitor-template-script
filename = monitor-document.py filename = monitor-document.py
md5sum = e9a41ff86b3d598e8f72d22d2ff3f838 md5sum = 79c53518e53ae8c8487b09bb49bc7b80
[make-rss-script] [make-rss-script]
......
...@@ -227,10 +227,10 @@ password = ${monitor-instance-parameter:password} ...@@ -227,10 +227,10 @@ password = ${monitor-instance-parameter:password}
[monitor-httpd-conf-parameter] [monitor-httpd-conf-parameter]
listening-ip = ${monitor-instance-parameter:monitor-httpd-ipv6} listening-ip = ${monitor-instance-parameter:monitor-httpd-ipv6}
port = ${monitor-instance-parameter:monitor-httpd-port} port = ${monitor-instance-parameter:monitor-httpd-port}
pid-file = ${directory:run}/httpd.pid pid-file = ${directory:run}/monitor-httpd.pid
cgid-pid-file = ${directory:run}/cgid.pid cgid-pid-file = ${directory:run}/cgid.pid
access-log = ${monitor-directory:log}/httpd-access.log access-log = ${monitor-directory:log}/monitor-httpd-access.log
error-log = ${monitor-directory:log}/httpd-error.log error-log = ${monitor-directory:log}/monitor-httpd-error.log
cert-file = ${ca-directory:certs}/httpd.crt cert-file = ${ca-directory:certs}/httpd.crt
key-file = ${ca-directory:certs}/httpd.key key-file = ${ca-directory:certs}/httpd.key
htpasswd-file = ${httpd-monitor-htpasswd:htpasswd-path} htpasswd-file = ${httpd-monitor-htpasswd:htpasswd-path}
...@@ -312,28 +312,6 @@ name = monitor-configurator ...@@ -312,28 +312,6 @@ name = monitor-configurator
frequency = * * * * * frequency = * * * * *
command = ${monitor-configurator-wrapper:wrapper-path} command = ${monitor-configurator-wrapper:wrapper-path}
[monitor-web-directory]
recipe = plone.recipe.command
command = cp -f {{ monitor_web_directory }}/* ${monitor-directory:web-dir}
update-command = ${:command}
[monitor-web-monitor-logout-cgi]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_web_monitor_logout_cgi }}
rendered = ${monitor-directory:cgi-bin}/monitor-logout.cgi
mode = 0755
context =
[monitor-web-monitor-promise-runner-cgi]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_web_monitor_promise_runner_cgi }}
rendered = ${monitor-directory:cgi-bin}/monitor-run-promise.cgi
mode = 0755
context =
raw python_executable {{ python_executable }}
key promise_wrapper_folder monitor-directory:promise-wrapper
[monitor-httpd-promise] [monitor-httpd-promise]
recipe = slapos.cookbook:check_url_available recipe = slapos.cookbook:check_url_available
path = ${directory:promises}/${:filename} path = ${directory:promises}/${:filename}
...@@ -382,22 +360,6 @@ rendered = ${directory:monitor-promise}/${monitor-password-promise:filename}.cfg ...@@ -382,22 +360,6 @@ rendered = ${directory:monitor-promise}/${monitor-password-promise:filename}.cfg
mode = 0644 mode = 0644
context = section parameter_dict monitor-password-promise-conf-parameter context = section parameter_dict monitor-password-promise-conf-parameter
[monitor-password-cgi]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_password_cgi_template }}
rendered = ${monitor-directory:cgi-bin}/monitor-password.cgi
context =
raw python_executable {{ python_executable }}
key password_changed_once_path monitor-password-parameter:password-changed-once-path
raw htpasswd_executable {{ apache_location }}/bin/htpasswd
key htpasswd_path httpd-monitor-htpasswd:htpasswd-path
[monitor-password-promise-interface]
recipe = slapos.recipe.template:jinja2
template = {{ monitor_password_promise_interface_template }}
rendered = ${monitor-directory:monitor-password-interface}/index.html
context =
[publish] [publish]
<= monitor-base <= monitor-base
monitor-base-url = ${monitor-conf-parameters:base-url} monitor-base-url = ${monitor-conf-parameters:base-url}
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import sys import sys
import os import os
import re
import json import json
import argparse import argparse
import subprocess import subprocess
...@@ -50,6 +51,46 @@ def htpasswdWrite(htpasswd_bin, parameter_dict, value): ...@@ -50,6 +51,46 @@ def htpasswdWrite(htpasswd_bin, parameter_dict, value):
pfile.write(value) pfile.write(value)
return True return True
def httpdCorsDomainWrite(httpd_cors_file, httpd_gracefull_bin, cors_domain):
cors_string = ""
cors_domain_list = cors_domain.split()
old_httpd_cors_file = os.path.join(
os.path.dirname(httpd_cors_file),
'prev_%s' % os.path.basename(httpd_cors_file)
)
if os.path.exists(old_httpd_cors_file) and os.path.isfile(old_httpd_cors_file):
try:
with open(old_httpd_cors_file, 'r') as cors_file:
if cors_file.read() == cors_domain:
return True
except OSError, e:
print "Failed to open file at %s. \n%s" % (old_httpd_cors_file, str(e))
for domain in cors_domain_list:
if cors_string:
cors_string += '|'
cors_string += re.escape(domain)
try:
with open(httpd_cors_file, 'w') as file:
file.write('SetEnvIf Origin "^http(s)?://(.+\.)?(%s)$" origin_is=$0\n' % cors_string)
file.write('Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is')
except OSError, e:
print "ERROR while writing CORS changes to %s.\n %s" % (httpd_cors_file, str(e))
return False
# Save current cors domain list
try:
with open(old_httpd_cors_file, 'w') as cors_file:
cors_file.write(cors_domain)
except OSError, e:
print "Failed to open file at %s. \n%s" % (old_httpd_cors_file, str(e))
return False
# Restart httpd process
try:
subprocess.call(httpd_gracefull_bin)
except OSError, e:
print "Failed to execute command %s.\n %s" % (httpd_gracefull_bin, str(e))
return False
def applyEditChage(parser): def applyEditChage(parser):
parameter_tmp_file = os.path.join(parser.config_folder, 'config.tmp.json') parameter_tmp_file = os.path.join(parser.config_folder, 'config.tmp.json')
...@@ -83,6 +124,8 @@ def applyEditChage(parser): ...@@ -83,6 +124,8 @@ def applyEditChage(parser):
result_dict[key] = fileWrite(description_entry['file'], new_parameter_list[i]['value']) result_dict[key] = fileWrite(description_entry['file'], new_parameter_list[i]['value'])
elif description_entry['type'] == 'htpasswd': elif description_entry['type'] == 'htpasswd':
result_dict[key] = htpasswdWrite(parser.htpasswd_bin, description_entry, new_parameter_list[i]['value']) result_dict[key] = htpasswdWrite(parser.htpasswd_bin, description_entry, new_parameter_list[i]['value'])
elif description_entry['type'] == 'httpdcors':
result_dict[key] = httpdCorsDomainWrite(description_entry['cors_file'], description_entry['gracefull_bin'], new_parameter_list[i]['value'])
if (parser.output_cfg_file): if (parser.output_cfg_file):
try: try:
......
...@@ -120,7 +120,7 @@ class Monitoring(object): ...@@ -120,7 +120,7 @@ class Monitoring(object):
return config return config
def readInstanceConfiguration(self): def readInstanceConfiguration(self):
type_list = ['raw', 'file', 'htpasswd'] type_list = ['raw', 'file', 'htpasswd', 'httpdcors']
configuration_list = [] configuration_list = []
if not self.parameter_list: if not self.parameter_list:
...@@ -152,14 +152,42 @@ class Monitoring(object): ...@@ -152,14 +152,42 @@ class Monitoring(object):
if config_list[0] == 'htpasswd': if config_list[0] == 'htpasswd':
if len(config_list) != 5 or not os.path.exists(config_list[4]): if len(config_list) != 5 or not os.path.exists(config_list[4]):
print 'htpasswd file is not specified: %s' % str(config_list) print 'htpasswd file is not specified: %s' % str(config_list)
return continue
parameter['description']['user'] = config_list[3] parameter['description']['user'] = config_list[3]
parameter['description']['htpasswd'] = config_list[4] parameter['description']['htpasswd'] = config_list[4]
configuration_list.append(parameter) configuration_list.append(parameter)
except OSError, e: except OSError, e:
print 'Cannot read file %s, Error is: %s' % (config_list[2], str(e)) print 'Cannot read file %s, Error is: %s' % (config_list[2], str(e))
pass pass
elif config_list[0] == 'httpdcors' and os.path.exists(config_list[2]) and \
os.path.exists(config_list[3]):
old_cors_file = os.path.join(
os.path.dirname(config_list[2]),
'prev_%s' % os.path.basename(config_list[2])
)
try:
cors_content = ""
if os.path.exists(old_cors_file):
with open(old_cors_file) as cfile:
cors_content = cfile.read()
else:
# Create empty file
with open(old_cors_file, 'w') as cfile:
cfile.write("")
parameter = dict(
key=config_list[1],
title=config_list[1],
value=cors_content,
description={
"type": config_list[0],
"cors_file": config_list[2],
"gracefull_bin": config_list[3]
}
)
configuration_list.append(parameter)
except OSError, e:
print 'Cannot read file at %s, Error is: %s' % (old_cors_file, str(e))
pass
return configuration_list return configuration_list
def setupPromiseDictFromFolder(self, folder): def setupPromiseDictFromFolder(self, folder):
......
<!DOCTYPE html>
<html>
<head>
<title>Promise status</title>
<style>
input, button {
min-height: 10mm;
min-width: 10mm;
}
</style>
<script>
function getServiceName() {
var match = /(?:&|\?)service_name=([^&]*)/.exec(location.search);
if (match) {
return match[1];
}
throw new Error("no service name found");
}
var service_name = getServiceName(),
monitor_json_url = "/monitor.haljson",
status_json_url = "/public/" + service_name + ".status.json",
rerun_cgi_url = "/cgi-bin/monitor-run-promise.cgi?service=" + service_name;
function newDeferred() {
var d = {
"promise": undefined,
"resolve": undefined,
"reject": undefined
};
d.promise = new Promise(function (resolve, reject) {
d.resolve = resolve;
d.reject = reject;
});
return d;
}
function xhr(param) {
/*global XMLHttpRequest */
var d = newDeferred(), xhr = new XMLHttpRequest(), k, i, l, a;
d.promise.cancel = function () { xhr.abort(); };
xhr.open((param.method || "GET").toUpperCase(), param.url, true);
xhr.responseType = param.responseType || "";
if (param.withCredentials !== undefined) {
xhr.withCredentials = param.withCredentials;
}
if (param.headers) {
a = Object.keys(param.headers);
l = a.length;
for (i = 0; i < l; i += 1) {
k = a[i];
xhr.setRequestHeader(k, param.headers[k]);
}
}
xhr.addEventListener("load", function (e) {
var r, t = e.target, callback;
if (param.noStatusCheck) {
d.resolve(t);
} else if (t.status < 400) {
d.resolve(t);
} else {
d.reject(new Error("HTTP: " + (t.status ? t.status + " " : "") + (t.statusText || "Unknown")));
}
}, false);
xhr.addEventListener("error", function (e) {
return d.reject(new Error("HTTP: Error"));
}, false);
xhr.addEventListener("abort", function (e) {
return d.reject(new Error("HTTP: Aborted"));
}, false);
xhr.send(param.data);
return d.promise;
}
function unexpectedError(reason) {
console.error(reason);
alert(reason);
}
function PromiseStatusInterface(config) {
var it = this,
statusP = document.createElement("p"),
descriptionH2 = document.createElement("h2"),
descriptionP = document.createElement("p"),
errorH2 = document.createElement("h2"),
errorPre = document.createElement("pre"),
header = document.createElement("header"),
h1 = document.createElement("h1"),
h2 = document.createElement("h2"),
a = document.createElement("a"),
button = document.createElement("button");
this.element = config.rootElement || document.createElement("div");
this.statusP = statusP;
this.descriptionP = descriptionP;
this.errorH2 = errorH2;
this.errorPre = errorPre;
this.element.appendChild(header);
header.appendChild(a);
a.setAttribute("tabindex", "-1");
a.setAttribute("href", "/");
a.appendChild(button);
button.textContent = "Home";
a = document.createElement("a");
button = document.createElement("button");
header.appendChild(a);
a.setAttribute("tabindex", "-1");
a.setAttribute("href", "");
a.appendChild(button);
button.textContent = "Refresh";
button = document.createElement("button");
header.appendChild(button);
button.textContent = "Run promise now";
button.onclick = function () {
this.runPromiseNow();
}.bind(this);
this.runPromiseNowButton = button;
this.element.appendChild(h1);
h1.textContent = "Promise status";
this.element.appendChild(statusP);
this.element.appendChild(descriptionH2);
descriptionH2.textContent = "Description";
this.element.appendChild(descriptionP);
this.element.appendChild(errorH2);
errorH2.textContent = "Error output";
errorH2.style.display = "none";
this.element.appendChild(errorPre);
errorPre.style.display = "none";
this.loadStatusUi();
this.loadDescriptionUi();
this.loadErrorUi();
}
PromiseStatusInterface.prototype.loadStatusJson = function () {
if (this.status_json_promise) { return; }
this.status_json_promise = Promise.resolve().then(function () {
return xhr({url: status_json_url, withCredentials: true, responseType: "json"});
}).then(function (xhr) {
return xhr.response;
});
this.status_json_promise.catch(function () { return; }).then(function () {
setTimeout(function () {
delete this.status_json_promise;
}.bind(this), 1000);
}.bind(this));
return this.status_json_promise;
};
PromiseStatusInterface.prototype.loadStatusUi = function () {
this.loadStatusJson();
this.statusP.textContent = "Loading status...";
return this.status_json_promise.then(function (status_json) {
if (status_json.status === "OK") {
this.statusP.textContent = "Status: OK.";
} else {
this.statusP.textContent = "Status: BAD (" + status_json.status + ").";
}
if (status_json.message) {
this.statusP.appendChild(document.createTextNode(" " + status_json.message));
}
}.bind(this), function (reason) {
var message = reason && (reason.target && (reason.target.statusText || "Unknown") || reason.message);
this.statusP.textContent = "Status Json Error: " + (message || "Unknown error");
}.bind(this)).catch(unexpectedError);
};
PromiseStatusInterface.prototype.loadDescriptionUi = function () {
this.loadStatusJson();
this.descriptionP.textContent = "Loading description...";
return this.status_json_promise.then(function (status_json) {
if (status_json.description) {
this.descriptionP.textContent = status_json.description;
} else {
this.descriptionP.textContent = "No description";
}
}.bind(this), function (reason) {
var message = reason && (reason.target && (reason.target.statusText || "Unknown") || reason.message);
this.descriptionP.textContent = "Status Json Error: " + (message || "Unknown error");
}.bind(this)).catch(unexpectedError);
};
PromiseStatusInterface.prototype.loadErrorUi = function () {
this.loadStatusJson();
this.errorPre.textContent = "Loading error output...";
return this.status_json_promise.then(function (status_json) {
if (status_json.error) {
this.errorH2.style.display = "";
this.errorPre.style.display = "";
this.errorPre.textContent = status_json.error;
} else {
this.errorH2.style.display = "none";
this.errorPre.style.display = "none";
this.errorPre.textContent = "";
}
}.bind(this), function (reason) {
var message = reason && (reason.target && (reason.target.statusText || "Unknown") || reason.message);
this.errorPre.textContent = "Status Json Error: " + (message || "Unknown error");
}.bind(this)).catch(unexpectedError);
};
PromiseStatusInterface.prototype.runPromiseNow = function () {
this.runPromiseNowButton.disabled = true;
var original_text = this.runPromiseNowButton.textContent;
this.runPromiseNowButton.textContent = "Sending message...";
return Promise.resolve().then(function () {
return xhr({url: rerun_cgi_url, method: "POST", withCredentials: true});
}).catch(unexpectedError).then(function () {
this.runPromiseNowButton.textContent = original_text;
}.bind(this));
};
/*global setTimeout */
setTimeout(function () {
/*global document */
document.body.innerHTML = "";
return new PromiseStatusInterface({rootElement: document.body});
});
</script>
</head>
<body>
<h1>Promise status</h1>
<noscript>Javascript should be enabled</noscript>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<link rel="stylesheet" href="monitor.css" />
<script src="monitor.js"></script>
</head>
<body>
<noscript>Please enable javascript on your browser to make this application to work.</noscript>
</body>
</html>
<!DOCTYPE html>
<html>
<head><title>Monitor logout</title></head>
<body>
<noscript>Cannot logout without javascript</noscript>
<script>
var logoutURL = "/cgi-bin/monitor-logout.cgi",
xhr = new XMLHttpRequest();
xhr.onload = function () {
if (xhr.status === 401) {
document.body.innerHTML = "<p>You are now logged out. You can go back to the monitor interface <a href=\"/\">here</a>.</p>";
} else {
console.error("Cannot logout (" + xhr.status + ")");
document.body.innerHTML = "<p>Cannot logout, retrying in 5 seconds.</p>";
setTimeout(location.reload.bind(location), 5000);
}
};
xhr.onerror = function () {
document.body.innerHTML = "<p>Cannot logout, please try again later.</p>";
};
xhr.open("POST", logoutURL, true, " logout", " password");
xhr.send();
document.body.innerHTML = "<p>Logging out...</p>";
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Monitor password</title>
<style>
input, button {
min-height: 10mm;
min-width: 10mm;
}
</style>
<script>
var service_name = "monitor-password", // XXX hardcoded
monitor_json_url = "/monitor.haljson",
status_json_url = "/public/" + service_name + ".status.json",
rerun_cgi_url = "/cgi-bin/monitor-run-promise.cgi?service=" + service_name,
password_cgi_url_part = "/cgi-bin/monitor-password.cgi?password=";
function newDeferred() {
var d = {
"promise": undefined,
"resolve": undefined,
"reject": undefined
};
d.promise = new Promise(function (resolve, reject) {
d.resolve = resolve;
d.reject = reject;
});
return d;
}
function xhr(param) {
/*global XMLHttpRequest */
var d = newDeferred(), xhr = new XMLHttpRequest(), k, i, l, a;
d.promise.cancel = function () { xhr.abort(); };
if (param.username) {
xhr.open((param.method || "GET").toUpperCase(), param.url, true, param.username, param.password);
} else {
xhr.open((param.method || "GET").toUpperCase(), param.url, true);
}
xhr.responseType = param.responseType || "";
if (param.withCredentials !== undefined) {
xhr.withCredentials = param.withCredentials;
}
if (param.headers) {
a = Object.keys(param.headers);
l = a.length;
for (i = 0; i < l; i += 1) {
k = a[i];
xhr.setRequestHeader(k, param.headers[k]);
}
}
xhr.addEventListener("load", function (e) {
var r, t = e.target, callback;
if (param.noStatusCheck) {
d.resolve(t);
} else if (t.status < 400) {
d.resolve(t);
} else {
r = new Error("HTTP: " + (t.status ? t.status + " " : "") + (t.statusText || "Unknown"));
r.target = t;
d.reject(r);
}
}, false);
xhr.addEventListener("error", function (e) {
return d.reject(new Error("HTTP: Error"));
}, false);
xhr.addEventListener("abort", function (e) {
return d.reject(new Error("HTTP: Aborted"));
}, false);
xhr.send(param.data);
return d.promise;
}
function unexpectedError(reason) {
console.error(reason);
alert(reason);
}
function MonitorPasswordInterface(config) {
var it = this,
statusP = document.createElement("p"),
descriptionP = document.createElement("p"),
form = document.createElement("form"),
formPassword1Input = document.createElement("input"),
formPassword2Input = document.createElement("input"),
formChangePasswordButton = document.createElement("button"),
errorH2 = document.createElement("h2"),
errorPre = document.createElement("pre"),
header = document.createElement("header"),
h1 = document.createElement("h1"),
h2 = document.createElement("h2"),
a = document.createElement("a"),
button = document.createElement("button");
this.element = config.rootElement || document.createElement("div");
this.statusP = statusP;
this.descriptionP = descriptionP;
this.formPassword1Input = formPassword1Input;
this.formPassword2Input = formPassword2Input;
this.formChangePasswordButton = formChangePasswordButton;
this.errorH2 = errorH2;
this.errorPre = errorPre;
this.element.appendChild(header);
header.appendChild(a);
a.setAttribute("tabindex", "-1");
a.setAttribute("href", "/");
a.appendChild(button);
button.textContent = "Home";
a = document.createElement("a");
button = document.createElement("button");
header.appendChild(a);
a.setAttribute("tabindex", "-1");
a.setAttribute("href", "");
a.appendChild(button);
button.textContent = "Refresh";
this.element.appendChild(h1);
h1.textContent = "Monitor password";
this.element.appendChild(statusP);
this.element.appendChild(descriptionP);
this.element.appendChild(form);
form.appendChild(formPassword1Input);
formPassword1Input.setAttribute("type", "password");
form.onsubmit = this.onFormSubmit.bind(this);
form.appendChild(document.createElement("br"));
form.appendChild(formPassword2Input);
formPassword2Input.setAttribute("type", "password");
form.appendChild(document.createElement("br"));
form.appendChild(formChangePasswordButton);
formChangePasswordButton.setAttribute("type", "submit");
formChangePasswordButton.textContent = "Change password";
this.element.appendChild(errorH2);
errorH2.textContent = "Operational error";
errorH2.style.display = "none";
this.element.appendChild(errorPre);
errorPre.style.display = "none";
this.loadStatusUi();
this.loadDescriptionUi();
this.loadErrorUi();
}
MonitorPasswordInterface.prototype.loadStatusJson = function () {
if (this.status_json_promise) { return; }
this.status_json_promise = Promise.resolve().then(function () {
return xhr({url: status_json_url, withCredentials: true, responseType: "json"});
}).then(function (xhr) {
return xhr.response;
});
this.status_json_promise.catch(function () { return; }).then(function () {
setTimeout(function () {
delete this.status_json_promise;
}.bind(this), 1000);
}.bind(this));
return this.status_json_promise;
};
MonitorPasswordInterface.prototype.loadStatusUi = function () {
this.loadStatusJson();
this.statusP.textContent = "Loading status...";
return this.status_json_promise.then(function (status_json) {
if (status_json.status === "OK") {
this.statusP.innerHTML = "&nbsp;";
} else {
this.statusP.textContent = "/!\\ The password needs to be changed at least once! /!\\";
}
}.bind(this), function (reason) {
if (reason && reason.target && reason.target.status === 404) {
this.statusP.textContent = "/!\\ The password needs to be changed at least once! /!\\";
return;
}
var message = reason && (reason.target && (reason.target.statusText || "Unknown") || reason.message);
this.statusP.textContent = "Status Json Error: " + (message || "Unknown error");
}.bind(this)).catch(unexpectedError);
};
MonitorPasswordInterface.prototype.loadDescriptionUi = function () {
this.descriptionP.textContent = [
"The monitor password is the password used to connect to this interface.",
"Here you can change the monitor password by filling the formular just below."
].join("\n");
};
MonitorPasswordInterface.prototype.loadErrorUi = function () {
this.loadStatusJson();
this.errorPre.textContent = "Loading error output...";
return this.status_json_promise.then(function (status_json) {
if (status_json.error) {
this.errorH2.style.display = "";
this.errorPre.style.display = "";
this.errorPre.textContent = status_json.error;
} else {
this.errorH2.style.display = "none";
this.errorPre.style.display = "none";
this.errorPre.textContent = "";
}
}.bind(this), function (reason) {
var message = reason && (reason.target && (reason.target.statusText || "Unknown") || reason.message);
this.errorPre.textContent = "Status Json Error: " + (message || "Unknown error");
}.bind(this)).catch(unexpectedError);
};
MonitorPasswordInterface.prototype.onFormSubmit = function (event) {
event.preventDefault();
event.stopPropagation();
this.execForm();
};
MonitorPasswordInterface.prototype.execForm = function () {
if (this.formPassword1Input.value !== this.formPassword2Input.value) {
this.statusP.textContent = "The two typed passwords should match!";
return;
}
this.statusP.textContent = "Changing password...";
var password = this.formPassword1Input.value;
return Promise.resolve().then(function () {
return xhr({url: password_cgi_url_part + password, method: "POST", withCredentials: true});
}).then(function () {
this.statusP.textContent = "Password changed succesfully!";
this.formPassword1Input.value = this.formPassword2Input.value = "";
// rerun promise with new login (also does the relogin)
xhr({url: rerun_cgi_url, method: "POST", withCredentials: true, username: "admin", password: password});
}.bind(this), function (reason) {
var message = reason && (reason.target && (reason.target.statusText || "Unknown") || reason.message);
this.statusP.textContent = "Status Json Error: " + (message || "Unknown error");
}.bind(this));
};
MonitorPasswordInterface.prototype.runPromiseNow = function () {
this.runPromiseNowButton.disabled = true;
var original_text = this.runPromiseNowButton.textContent;
this.runPromiseNowButton.textContent = "Sending message...";
return Promise.resolve().then(function () {
return xhr({url: rerun_cgi_url, method: "POST", withCredentials: true});
}).catch(unexpectedError).then(function () {
this.runPromiseNowButton.textContent = original_text;
}.bind(this));
};
/*global setTimeout */
setTimeout(function () {
/*global document */
document.body.innerHTML = "";
return new MonitorPasswordInterface({rootElement: document.body});
});
</script>
</head>
<body>
<h1>Monitor password</h1>
<noscript>Javascript should be enabled</noscript>
</body>
</html>
body { width: 80vw; margin: auto; padding-top: 1%; }
/* h1 { align-text: center; margin: auto; } */
/*td { padding: 0 2%; }/**/
td { padding: 0 1em; }/**/
table { border: 1px solid black; }
table > table { margin-top: 1em; }
input {
box-sizing: border-box;
min-height: 10mm;
min-width: 10mm;
}
button {
box-sizing: border-box;
min-height: 10mm;
min-width: 10mm;
background-color: lightgray;
background: linear-gradient(180deg, #F6F6F6 0%, #DDDDDD 100%);
border-radius: 2px;
border-style: solid;
border-width: 1px;
border-color: #A4A4A4;
}
a.as-button {
display: inline-block;
box-sizing: border-box;
min-height: 10mm;
min-width: 10mm;
padding: 0.5em 0.5em;
text-align: center;
text-decoration: initial;
}
a.as-button {
color: black;
background-color: lightgray;
background: linear-gradient(180deg, #F6F6F6 0%, #DDDDDD 100%);
border-radius: 2px;
border-style: solid;
border-width: 1px;
border-color: #A4A4A4;
}
a.as-button:active, button:active {
background-color: white;
background: linear-gradient(0deg, #F6F6F6 0%, #DDDDDD 100%);
}
a.as-button:hover, button:hover {
border-color: #777777;
}
.monitor-section {
float: left;
table-layout: fixed;
margin-top: 30px;
margin-right: 30px;
}
\ No newline at end of file
/*jslint indent:2 */
(function () {
"use strict";
var monitor_title = 'Monitoring interface',
RSS_ICON_DATA_URI = [
"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIj8+CjwhRE9DVFlQR",
"SBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cu",
"dzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM",
"9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPS",
"IxMjhweCIgaGVpZ2h0PSIxMjhweCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPgo8cmVjd",
"CB3aWR0aD0iMjU2IiBoZWlnaHQ9IjI1NiIgeD0iMCIgIHk9IjAiICBmaWxsPSIjRjQ5",
"QzUyIi8+CjxjaXJjbGUgY3g9IjY4IiBjeT0iMTg5IiByPSIyNCIgZmlsbD0iI0ZGRiI",
"vPgo8cGF0aCBkPSJNMTYwIDIxM2gtMzRhODIgODIgMCAwIDAgLTgyIC04MnYtMzRhMT",
"E2IDExNiAwIDAgMSAxMTYgMTE2eiIgZmlsbD0iI0ZGRiIvPgo8cGF0aCBkPSJNMTg0I",
"DIxM0ExNDAgMTQwIDAgMCAwIDQ0IDczIFYgMzhhMTc1IDE3NSAwIDAgMSAxNzUgMTc1",
"eiIgZmlsbD0iI0ZGRiIvPgo8L3N2Zz4K"
].join("");
function loadJson(url) {
/*global XMLHttpRequest */
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function (event) {
var response = event.target;
if (response.status < 400) {
try {
var data = ( response.responseType === 'text' || response.responseType === '') ? JSON.parse(response.responseText) : response.response;
resolve(data);
} catch (e) {
reject(e);
}
} else {
reject(new Error("XHR: " + response.status + ": " + response.statusText));
}
};
xhr.onerror = function () {
reject(new Error("XHR: Error"));
};
xhr.open("GET", url, true);
xhr.send();
});
}
///////////////////
// tools for HAL //
function getProperty(object, path) {
if (Array.isArray(path)) {
while (path.length) {
object = object[path.shift()];
}
} else {
return object[path];
}
return object;
}
function softGetProperty(object, path) {
try {
return getProperty(object, path);
} catch (ignored) {
return undefined;
}
}
function forceList(value) {
if (Array.isArray(value)) {
return value;
}
return [value];
}
function softGetPropertyAsList(object, path) {
try {
return forceList(getProperty(object, path));
} catch (ignored) {
return [];
}
}
///////////////////
function htmlToElementList(html) {
/*global document */
var div = document.createElement("div");
div.innerHTML = html;
return div.querySelectorAll("*");
}
function resolveUrl(firstUrl) {
/*jslint plusplus: true */
/*global URL, location */
var l = arguments.length, i = 1, url = new URL(firstUrl, location.href);
while (i < l) { url = new URL(arguments[i++], url); }
return url.href;
}
function joinUrl(url, path) {
if (path && path[0] === '/') {
path = path.slice(1);
}
if (url.indexOf('/', url.length - 1) === -1) {
return url + '/' + path;
}
return url + escapeHtml(path);
}
function escapeHtml(html) {
return html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
}
function loadAndRenderMonitorSection(root, monitor_dict, monitor_url) {
var table, service_list = softGetPropertyAsList(monitor_dict, ["_embedded", "service"]);
if (!service_list) {
root.textContent = "";
return;
}
table = document.createElement("table");
table.className = "monitor-section";
root.appendChild(table);
return Promise.all(service_list.map(function (service_dict) {
var interface_url = softGetProperty(service_dict, ["_links", "interface", "href"]),
status_url = softGetProperty(service_dict, ["_links", "status", "href"]),
href_html_part = (interface_url ? " href=\"" + escapeHtml(interface_url) + "\"" : ""),
title_html_part = (service_dict.title ? escapeHtml(service_dict.title) : (service_dict.id ||"Untitled")),
row = htmlToElementList("<table><tbody><tr><td><a" + href_html_part + ">" + title_html_part + "</a></td><td>Loading status...</td><td><a" + href_html_part + "><div style=\"height: 10mm; width: 10mm; background-color: gray;\"></div></a></td></tr></tbody></table>");
table.appendChild(row[2]);
if (!status_url) {
row[5].textContent = "No status";
return;
}
var full_status_url = (monitor_url === undefined) ? resolveUrl(monitor_url, status_url): joinUrl(monitor_url, status_url);
return loadJson(full_status_url).then(function (status_dict) {
if (status_dict.description) {
row[2].title = status_dict.description;
}
row[5].textContent = status_dict.message || "";
row[8].style.backgroundColor = status_dict.status === "OK" ? "green" : "red";
}).catch(function (reason) {
row[5].textContent = (reason && (reason.name + ": " + reason.message));
row[8].style.backgroundColor = "red";
});
}));
}
function loadAndRenderMonitorJson(root) {
root.textContent = "Loading monitor section...";
return loadJson("monitor.haljson").then(function (monitor_dict) {
//monitor_json_list.push(monitor_dict);
root.innerHTML = "";
var loading = loadAndRenderMonitorSection(root, monitor_dict), related_monitor_list = softGetPropertyAsList(monitor_dict, ["_links", "related_monitor"]);
if (!related_monitor_list.length) { return loading; }
return Promise.all([loading, Promise.all(related_monitor_list.map(function (link) {
var div = htmlToElementList("<div>Loading monitor section...</div>")[0];
root.appendChild(div);
if (link.href[link.href.length - 1] !== "/") {
link.href += "/";
}
var haljson_link = resolveUrl(link.href, "monitor.haljson");
return loadJson(haljson_link).catch(function (reason) {
div.textContent = (reason && (reason.name + ": " + reason.message));
}).then(function (monitor_dict) {
//monitor_json_list.push(monitor_dict);
div.remove();
return loadAndRenderMonitorSection(root, monitor_dict, link.href);
});
}))]);
});
}
function bootstrap(root) {
var element_list = htmlToElementList([
"<header>",
" <a href=\"\" class=\"as-button\">Refresh</a>",
" <a href=\"/logout.html\" class=\"as-button\">Logout</a>",
" <a href=\"/feed\"><img src=\"" + RSS_ICON_DATA_URI + "\" style=\"width: 10mm; height: 10mm; vertical-align: middle;\" alt=\"[RSS Feed]\" /></a>",
"</header>",
"<h1>" + monitor_title + "</h1>",
"<h2>System health status</h2>",
"<p>This interface allow to see the status of several features, it may show problems and sometimes provides a way to fix them.</p>",
"<p>Red square means the feature has a problem, green square means it is ok.</p>",
"<p>You can click on a feature below to get more precise information.</p>"
].join("\n")), div = document.createElement("div"), tmp;
[].reduce.call(element_list, function (array, element) {
if (element.parentNode.parentNode) { return array; }
array.push(element);
return array;
}, []).forEach(function (element) {
root.appendChild(element);
});
document.title = monitor_title;
root.appendChild(div);
/*global alert */
tmp = loadAndRenderMonitorJson(div);
tmp.catch(alert);
/*global console */
tmp.catch(console.error.bind(console));
}
/*global setTimeout */
setTimeout(function () {
/*global document */
bootstrap(document.body);
});
}());
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment