diff --git a/app/routes.js b/app/routes.js index 63d8160a7524582c049871c68649a6fa2a045d42..57b74fbabb89f003c7b78ef53ba656517ab73ae5 100644 --- a/app/routes.js +++ b/app/routes.js @@ -59,10 +59,10 @@ module.exports = function (app, redirect, config, proxyServer, proxyRouter, k8, }) }); - const commandsBase = components.templatize(cconf.commands.path) + const commandsBase = components.templatize(cconf.commands.path)(components.baseRepl) - app.get(commandsBase + "/viewContainers", ensureLoggedIn('/login'), bodyParser.urlencoded({extended: true}), function(req, res){ - let user = selfUserName(req) + app.get(commandsBase + "/view-containers", ensureLoggedIn('/login'), bodyParser.urlencoded({extended: true}), function(req, res){ + let user = components.selfUserName(req) var selectors if (req.body.all && req.body.all !== 'false') selectors = { user: user } @@ -72,10 +72,54 @@ module.exports = function (app, redirect, config, proxyServer, proxyRouter, k8, if (err) { res.send(getHtmlErrorTemplate(err, "Viewing running containers")) } else { - evalHtmlTemplate( - "html/viewContainers.html", + let podList = pods.items + if (podList) + podList = podList.map(function(pod){ + let secondsSinceCreation = (Date.now() - Date.parse(pod.metadata.creationTimestamp))/ 1000.0 + let time = secondsSinceCreation + let unit = "s" + if (time > 60) { + time = time / 60.0 + unit = "m" + if (time > 60) { + time = time / 60.0 + unit = "h" + if (time > 24) { + time = time / 24.0 + unit = "d" + } + } + } + let status = "danger" + if (pod.status && pod.status.phase === 'Pending') { + status = "warning" + } else if (pod.status && pod.status.phase === 'Running') { + const conds = pod.status.conditions + let ready = false + if (pod.status && conds) { + for (icond in conds) { + let cond = conds[icond] + if (cond.type === 'Ready' && cond.status === 'True') + ready = true + } + if (ready) + status = "success" + else + status = "warning" + } + } + + return { + name: pod.metadata.name, + time: `${time.toFixed(1)} ${unit}`, + status: status, + detail: pod + } + }) + components.evalHtmlTemplate( + "viewContainers.html", { - pods: stringify(pods) + pods: podList }, function (err, page) { res.send(page) }) @@ -83,14 +127,78 @@ module.exports = function (app, redirect, config, proxyServer, proxyRouter, k8, }) }) - app.post(commandsBase + "/stop", ensureLoggedIn('/login'), function(req, res){ - - }) - - app.post(commandsBase + "/stopAll", ensureLoggedIn('/login'), function(req, res){ + app.get(commandsBase + "/container/:podname", ensureLoggedIn('/login'), bodyParser.json(), function(req, res){ + var loggedUsername = components.selfUserName(req); + var podName = req.params.podname; + var podInfo = components.infoForPodName(podName) + if (podInfo.user && loggedUsername === podInfo.user) { + k8.ns(config.k8component.namespace).pods.get({ name: podName }, function (err, result) { + if (!err) { + res.type('application/vnd.api+json').json({data:{ id: podName, + type: 'pod', + attributes: { + data: result + } + } + }, null, 2); + } else res.type('application/vnd.api+json').json({errors:[{ + id: 'no pod', + detail: `error getting info onn pod ${podName}`, + data: err + }]}); + }); + } else if (podInfo.user) { + res.status(401).type('application/vnd.api+json').json({ errors: [{ + id:'not allowed', + detail:'You don\'t have the right to view that container.'}] }); + } else { + res.status(400).type('application/vnd.api+json').json({ errors: [{ + id: 'invalid request', + detail:'The pod name is incorrectly formatted.'}] }); + } }) - - app.post(commandsBase + "/refresh", ensureLoggedIn('/login'), function(req, res){ + + app.delete(commandsBase + "/container/:podname", ensureLoggedIn('/login'), bodyParser.json(), function(req, res){ + var loggedUsername = components.selfUserName(req); + var podName = req.params.podname; + var podInfo = components.infoForPodName(podName) + if (podInfo.user && loggedUsername === podInfo.user) { + logger.info(`Deleting pod: ${podName}`) + k8.ns(config.k8component.namespace).pods.delete({ name: podName }, function (err, result) { + if (!err) { + logger.info(`deleted pod ${podName}`) + res.type('application/vnd.api+json').json({ + data: { + id: podName, + type: 'pod', + attributes:{ + data: result + } + } + }); + } else { + logger.warn(`Error deleting pod ${podName}: ${stringify(err)}`) + res.type('application/vnd.api+json').json({ + errors:[ { + id: 'delete error', + detail: `failed to delete pod ${podName}`, + data: err + }]}); + } + }); + } else if (podInfo.user) { + let err = { + id:'not allowed', + detail:'You don\'t have the right to delete that container.'} + logger.warn(stringify(err)) + res.status(401).type('application/vnd.api+json').json({ errors: [err] }); + } else { + let err = { + id: 'invalid request', + detail:'The pod name is incorrectly formatted.'} + logger.warn(stringify(err)) + res.status(400).type('application/vnd.api+json').json({ errors: [err] }); + } }) /*app.get('/notebook-edit/*', ensureLoggedIn('/login'), function(req, res){ diff --git a/app/userapi.js b/app/userapi.js index 5ed8d6fa3b0d55e5e795e8b42dc49ca7d8726ca5..fc0013425a163a182ffa259e60b4fac589d90e7e 100644 --- a/app/userapi.js +++ b/app/userapi.js @@ -158,48 +158,6 @@ module.exports = function (app, config, passport, models, ensureLoggedIn, bodyPa }); }); - /** - * Returns a list of RCs for a certain type of notebooks (imagetype) associated with the user's account (username) - */ - app.get('/userapi/mycontainers/:imagetype', function (req, res) { - const k8 = require('../app/kubernetes')(config); - const k8component = require('../app/components')(config); - var username = selfUserName(req); - var imagetype = req.params.imagetype; - var searchPhrase = imagetype + '-rc-' + username; - logger.debug("Searching for replication controllers: " + searchPhrase); - k8.namespaces.replicationcontrollers.get(searchPhrase, function (err, result) { - if (!err) { - res.send(result); - } else res.send(err); - }); - }); - - /** - * Delete an RC using its name (rcname) - */ - app.get('/userapi/delete-container/:rcname', function (req, res) { - const k8 = require('../app/kubernetes')(config); - const k8component = require('../app/components')(config); - var rcName = req.params.rcname; - var loggedUsername = selfUserName(req); - var rcUsername = rcName.split('-')[2]; - if (rcUsername && loggedUsername === rcUsername) { - logger.info("Deleting replication controller: " + rcName); - k8.namespaces.replicationcontrollers.delete({ name: rcName, preservePods: false }, function (err, result) { - if (!err) { - res.send(result); - } else res.send(err); - }); - } else { - if (rcUsername) - res.send({ error: 'You don\'t have the right to delete that container.' }); - else { - res.send({ error: 'The RC name is incorrectly formatted.' }); - } - } - }); - /*app.get('/notebook-edit/*', function (req, res) { const target = 'https://labdev-nomad.esc.rzg.mpg.de/beaker/#/open?uri=' + req.url.slice(14, req.url.length).replace("/", "%2F") logger.debug(`notebook-edit redirecting to ${target}`) diff --git a/templates/html/containerDetails.html b/templates/html/containerDetails.html new file mode 100644 index 0000000000000000000000000000000000000000..0f03394a87fe02c04c0a4564d22178609df197ae --- /dev/null +++ b/templates/html/containerDetails.html @@ -0,0 +1,7 @@ +title: "Active Containers" +--- +<ol> +{{#each pods as |pod|}} + <li><pre>{{pod}}</pre></li> +{{/each}} +</ol> diff --git a/templates/html/viewContainers.html b/templates/html/viewContainers.html index 67efd4f93ad9bff6f005823afafe83970b812358..eb5c30825e5d0eae03cb682e26411c2d9e981981 100644 --- a/templates/html/viewContainers.html +++ b/templates/html/viewContainers.html @@ -1 +1,41 @@ -<pre>{{pods}}</pre> +title: "Active Containers" +--- +<h1>Active Containers</h1> + +<div class="alert alert-primary" role="alert" id="msgs"> +</div> + +<script type="text/javascript"> + function deleteContainer(cName) { + const xhttp = new XMLHttpRequest(); + xhttp.responseType = 'json' + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.response && this.response.data) + document.getElementById("msgs").innerHTML = 'successful delete'; + else + document.getElementById("msgs").innerHTML = 'delete error: '+JSON.stringify(this.response); + } + }; + + xhttp.open("DELETE", "container/"+cName, true); + xhttp.setRequestHeader('content-type','application/json') + xhttp.send(JSON.stringify({name:cName})); + } +</script> + +{{#each pods as |pod|}} +<div class="alery alert-{{pod.status}}" role="alert" > + <a class="" data-toggle="collapse" href="#detail{{pod.name}}" aria-expanded="false" aria-controls="detail{{pod.name}}"> + {{pod.name}} + </a> ({{pod.time}}) + <button type="button" class="btn btn-default btn-sm" onclick="deleteContainer('{{pod.name}}');"> + Delete + </button> + <div class="collapse" id="detail{{pod.name}}"> + <div class="card card-block"> + <pre>{{prettyJson pod.detail}}</pre> + </div> + </div> +</div> +{{/each}}