13 Commits

Author SHA1 Message Date
Manuel Romero
e37d881eaa large modal 2020-03-28 15:19:49 +01:00
Manuel Romero
6df0af5fde modals 2020-03-28 12:03:42 +01:00
Manuel Romero
eb633f877a more changes 2020-03-27 18:04:37 +01:00
Manuel Romero
df267a8903 more fixed due to db changes 2020-03-27 15:17:31 +01:00
Manuel Romero
79786c4d97 More fixes 2020-03-27 14:32:41 +01:00
Manuel Romero
61eb106481 database mongo changes 2020-03-27 14:10:45 +01:00
Manuel Romero
96c18c6033 mongo 2020-03-26 18:57:48 +01:00
Manuel Romero
b06ab3b6db fix 2020-03-26 12:11:37 +01:00
Manuel Romero
056964306b alert and azureps 2020-03-26 11:29:05 +01:00
Manuel Romero
921ef46b06 Adding AG assign 2020-03-25 18:40:39 +01:00
Manuel Romero
3eed9c9b9a alerts 2020-03-24 18:04:01 +01:00
Manuel Romero
3e410e4eca server certs 2020-03-24 12:18:52 +01:00
Manuel Romero
720c2586bd Methods to admin table 2020-03-24 11:42:01 +01:00
46 changed files with 887 additions and 1691 deletions

1
.gitignore vendored
View File

@@ -47,4 +47,5 @@ Thumbs.db
secrets.json secrets.json
qmi-cloud-tf-modules/ qmi-cloud-tf-modules/
*.pfx

View File

@@ -27,14 +27,14 @@
"src/assets" "src/assets"
], ],
"styles": [ "styles": [
"src/styles.scss",
"node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss", "node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss",
"node_modules/@fortawesome/fontawesome-free/scss/solid.scss", "node_modules/@fortawesome/fontawesome-free/scss/solid.scss",
"node_modules/@fortawesome/fontawesome-free/scss/regular.scss", "node_modules/@fortawesome/fontawesome-free/scss/regular.scss",
"node_modules/@fortawesome/fontawesome-free/scss/brands.scss", "node_modules/@fortawesome/fontawesome-free/scss/brands.scss",
"node_modules/angular-bootstrap-md/assets/scss/bootstrap/bootstrap.scss", "node_modules/angular-bootstrap-md/assets/scss/bootstrap/bootstrap.scss",
"node_modules/angular-bootstrap-md/assets/scss/mdb.scss", "node_modules/angular-bootstrap-md/assets/scss/mdb.scss",
"node_modules/animate.css/animate.css", "node_modules/animate.css/animate.css"
"src/styles.scss"
], ],
"scripts": [ "scripts": [
"node_modules/chart.js/dist/Chart.js", "node_modules/chart.js/dist/Chart.js",

View File

@@ -6,8 +6,8 @@
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/favicon.ico"> <link rel="icon" href="assets/favicon.ico">
<link rel="stylesheet" href="styles.a1477262c132edf53006.css"></head> <link rel="stylesheet" href="styles.a176d817fea8bea6cd9e.css"></head>
<body> <body>
<app-root></app-root> <app-root></app-root>
<script src="runtime.689ba4fd6cadb82c1ac2.js" defer></script><script src="polyfills-es5.f752a17531a45fe93c1f.js" nomodule defer></script><script src="polyfills.06ba8d1a3d9dd3a8e8b9.js" defer></script><script src="scripts.cc5d7fb76aa54d397727.js" defer></script><script src="main.2460133151dbe7cec73e.js" defer></script></body> <script src="runtime.689ba4fd6cadb82c1ac2.js" defer></script><script src="polyfills-es5.f752a17531a45fe93c1f.js" nomodule defer></script><script src="polyfills.06ba8d1a3d9dd3a8e8b9.js" defer></script><script src="scripts.cc5d7fb76aa54d397727.js" defer></script><script src="main.09d3646b3fdc1d594bb9.js" defer></script></body>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1123
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "qmi-cloud", "name": "qmi-cloud",
"version": "1.0.5", "version": "1.0.7",
"scripts": { "scripts": {
"start": "node -r esm server/server.js", "start": "node -r esm server/server.js",
"dev": "nodemon -r esm server/server.js", "dev": "nodemon -r esm server/server.js",
@@ -25,7 +25,7 @@
"adal-angular4": "^4.0.12", "adal-angular4": "^4.0.12",
"angular-bootstrap-md": "^9.0.0", "angular-bootstrap-md": "^9.0.0",
"animate.css": "^3.7.2", "animate.css": "^3.7.2",
"azure": "^2.3.1-preview", "azure-arm-compute": "^10.0.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"boom": "^7.3.0", "boom": "^7.3.0",
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
@@ -45,6 +45,7 @@
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"leonardo-ui": "^1.7.1", "leonardo-ui": "^1.7.1",
"mongoose": "^5.7.4", "mongoose": "^5.7.4",
"ms-rest-azure": "^3.0.0",
"nodemailer": "^6.4.2", "nodemailer": "^6.4.2",
"nodemon": "^1.19.1", "nodemon": "^1.19.1",
"passport": "^0.4.0", "passport": "^0.4.0",

View File

@@ -1,5 +1,5 @@
const Azure = require('azure');
const MsRest = require('ms-rest-azure'); const MsRest = require('ms-rest-azure');
var computeManagementClient = require('azure-arm-compute');
const SUBSCRIPTION_ID = "62ebff8f-c40b-41be-9239-252d6c0c8ad9"; const SUBSCRIPTION_ID = "62ebff8f-c40b-41be-9239-252d6c0c8ad9";
var computeClient; var computeClient;
@@ -12,7 +12,7 @@ function auth(cb) {
console.log("Could not authenticate with Azure", err); console.log("Could not authenticate with Azure", err);
return; return;
} }
computeClient = new Azure.createComputeManagementClient(credentials, SUBSCRIPTION_ID); computeClient = new computeManagementClient(credentials, SUBSCRIPTION_ID);
cb(computeClient); cb(computeClient);
}); });
} }

0
server/certs/.keep Normal file
View File

View File

@@ -1,13 +0,0 @@
-----BEGIN CERTIFICATE-----
MIICAjCCAWsCFErekshtMPI0fqmz3A7nylyqZjP8MA0GCSqGSIb3DQEBCwUAMEAx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMQ0wCwYDVQQKDARHRUFS
MQ0wCwYDVQQLDARHRUFSMB4XDTIwMDExNTExMTIzNFoXDTIxMDExNDExMTIzNFow
QDELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxDTALBgNVBAoMBEdF
QVIxDTALBgNVBAsMBEdFQVIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALtP
gdhWsY9kETiO1y69v3gu6AXqTz6AZm/AfLlboe03zO4XHKSmvDKI+jx6/+GuuR/u
ZSNkJTdOszUDMKl+V2wvyAuvBotirVHXmj/5tb7zDDTMgaQwP/0w+GIxm09ED40Z
qgXF6nCeXHFSRzej1dDxKRtp1t4QAezoB73mtm+vAgMBAAEwDQYJKoZIhvcNAQEL
BQADgYEABgqzxUGd6PrwobXqaQKE8iHPjgR94clktM2+morAbXeV2trKhwaGDZiG
d/ODKEk+gehnXbGWjxxIbqWyac7HQQMGZ46jbJyIo32ltbxouylquV+ZR4HvxJmi
8h6Jd8dyGZDZv0lSTOL9525Du4eaYQA6GVCrZsoaQDwYJTo4GWM=
-----END CERTIFICATE-----

View File

@@ -1,15 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQC7T4HYVrGPZBE4jtcuvb94LugF6k8+gGZvwHy5W6HtN8zuFxyk
prwyiPo8ev/hrrkf7mUjZCU3TrM1AzCpfldsL8gLrwaLYq1R15o/+bW+8ww0zIGk
MD/9MPhiMZtPRA+NGaoFxepwnlxxUkc3o9XQ8SkbadbeEAHs6Ae95rZvrwIDAQAB
AoGAY06h7sr31KgfITdKCrP7IYLs7MXvQZndtX3+Il/cl+IvukNyojDsMnbBBPPJ
WiPCbpV79amQuaP2CzMnx5T+T6sxdhMzp1eKpYF0WrldAE9gxjfGT5xmXQb8FxLe
yKDqf5HRU1LpMuktOoBx4i8P2RqkWE8txWg2dU+2occfNmECQQDhhD42bJnGWEko
LRHG+jsH93/hvwn2y0xVpO57Hvaf8rWPBYaFx/4LP0/WlrR7hxWeb1miYbEXkLqg
a+IeUndXAkEA1KEv2u7y7ZV274Miku2Ldcc6+gWynwci9WZMXKi7uY8r04wZ9PcC
MutbSwAjbbnubBquHFkwSTzrF1m27ltLaQJAV9YgPSZzhpOgeuuC/xM7ptC5mH3G
Lb/lTX5d/MqEmKv6F2i5iqXoxwyz1gsA5RQqUXlhWiPezCTs68rZWaIZJQJAbLN6
o5JE3vDqPMfthO+rvCp+HBONnX8ogAwsPbKFRffLj/qvymv808s+gLkxY4cKPHnn
SNbPuKFMDkPvISvLAQJAAsBhe8m9kuEoSPeECPOFZjG6+WlFAagODvGDVY5uZhAF
ARkTxCGgp5hY2cXpFmU9usMPvvUJuCBAhgbWo7mszg==
-----END RSA PRIVATE KEY-----

View File

@@ -1,10 +1,15 @@
const mongoose = require('mongoose') const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false); mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true) //mongoose.set('debug', true)
const destroySchema = new mongoose.Schema({ const destroySchema = new mongoose.Schema({
userId: String, userId: {
type: mongoose.Types.ObjectId, ref: 'User'
},
user: {
type: mongoose.Types.ObjectId, ref: 'User'
},
created: { created: {
type: Date, type: Date,
default: Date.now default: Date.now

View File

@@ -1,11 +1,13 @@
const mongoose = require('mongoose') const mongoose = require('mongoose');
mongoose.set('useFindAndModify', false); mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true) //mongoose.set('debug', true)
const provisionSchema = new mongoose.Schema({ const provisionSchema = new mongoose.Schema({
userId: { userId: {
type: String, type: mongoose.Types.ObjectId, ref: 'User'
index: true },
user: {
type: mongoose.Types.ObjectId, ref: 'User'
}, },
created: { created: {
type: Date, type: Date,
@@ -37,6 +39,9 @@ const provisionSchema = new mongoose.Schema({
statusVms: { statusVms: {
type: String, type: String,
default: "Running" default: "Running"
},
destroy: {
type: mongoose.Types.ObjectId, ref: 'Destroy'
} }
}); });

View File

@@ -57,7 +57,7 @@ const getScenarioByName = async (name, reply) => {
const get = async (model, filter, reply) => { const get = async (model, filter, reply) => {
try { try {
const entity = await model.find(filter).sort({created: -1}); const entity = await model.find(filter).sort({created: -1}).populate('user').populate('destroy');
return entity; return entity;
} catch (err) { } catch (err) {
throw boom.boomify(err) throw boom.boomify(err)
@@ -66,7 +66,7 @@ const get = async (model, filter, reply) => {
const getSingle = async (model, id, reply) => { const getSingle = async (model, id, reply) => {
try { try {
const entity = await model.findById(id); const entity = await model.findById(id).populate('user').populate('destroy');
return entity; return entity;
} catch (err) { } catch (err) {
throw boom.boomify(err); throw boom.boomify(err);
@@ -86,7 +86,7 @@ const update = async (model, id, body, reply) => {
try { try {
const { ...updateData } = body; const { ...updateData } = body;
updateData.updated = new Date(); updateData.updated = new Date();
const update = await model.findByIdAndUpdate(id, updateData, { new: true }); const update = await model.findByIdAndUpdate(id, updateData, { new: true }).populate('user').populate('destroy');
return update; return update;
} catch (err) { } catch (err) {
throw boom.boomify(err) throw boom.boomify(err)
@@ -149,3 +149,54 @@ module.exports = {
} }
} }
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
async function toObjectId(model, fieldName, fieldNameTemp){
var filter = {};
filter[fieldName] = {$type : 2};
var results = await model.find(filter);
await asyncForEach(results, async function(d) {
console.log("doc", d);
var newId = mongoose.Types.ObjectId(d[fieldName]); // to ObjectId
var oldId = d[fieldName];
d[fieldName] = undefined;
d[fieldNameTemp] = newId;
await model.create(d);
var remFilter = {};
remFilter[fieldName] = oldId;
await model.remove(remFilter);
});
}
async function destroyStuff(){
var results = await Destroy.find({provId:{$type : 2}});
await asyncForEach(results, async function(d) {
console.log("doc", d);
var destroy = d._id;
await Provision.updateOne({_id: d.provId}, {destroy: destroy});
d.provId = undefined;
d.save();
});
}
toObjectId(Provision, "userId", "user");
toObjectId(Destroy, "userId", "user");
destroyStuff();

View File

@@ -137,7 +137,7 @@ router.put('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, ne
router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try { try {
req.body.userId = req.params.userId; req.body.user = req.params.userId;
const mongoJob = await db.provision.add(req.body); const mongoJob = await db.provision.add(req.body);
const scenarioSource = await db.scenario.getScenarioByName(req.body.scenario); const scenarioSource = await db.scenario.getScenarioByName(req.body.scenario);
@@ -199,13 +199,13 @@ router.delete('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, as
if (!mongoJob){ if (!mongoJob){
return res.status(404).json({"msg": "Not found privision with id "+req.params.id}); return res.status(404).json({"msg": "Not found privision with id "+req.params.id});
} }
const toDestroy = await db.destroy.get({"provId": req.params.id}); const delDest = await db.destroy.del(mongoJob.destroy._id);
const delDest = await db.destroy.del(toDestroy[0]._id.toString());
const delProv = await db.provision.update(req.params.id, {"isDeleted": true}); const delProv = await db.provision.update(req.params.id, {"isDeleted": true});
//Move folder //Move folder
if (fs.existsSync(`/provisions/${mongoJob.scenario}_${req.params.id}`)) {
fs.moveSync(`/provisions/${mongoJob.scenario}_${req.params.id}`, `/provisions/deleted/${mongoJob.scenario}_${req.params.id}`, { overwrite: true }) fs.moveSync(`/provisions/${mongoJob.scenario}_${req.params.id}`, `/provisions/deleted/${mongoJob.scenario}_${req.params.id}`, { overwrite: true })
}
return res.json({"provision": delProv, "destroy": delDest}); return res.json({"provision": delProv, "destroy": delDest});
} catch (error) { } catch (error) {
next(error); next(error);
@@ -343,12 +343,8 @@ router.post('/:userId/destroyprovisions', passport.ensureAuthenticatedAndIsMe, a
try { try {
req.body.userId = req.params.userId; req.body.userId = req.params.userId;
const destroyJob = await db.destroy.add({ "user": req.body.userId });
const mongoJob = await db.provision.getSingle(req.body.id); const mongoJob = await db.provision.update(req.body.id, {"destroy": destroyJob._id});
const destroyJob = await db.destroy.add({
"userId": req.body.userId,
"provId": mongoJob._id
});
const scenarioSource = await db.scenario.getScenarioByName(mongoJob.scenario); const scenarioSource = await db.scenario.getScenarioByName(mongoJob.scenario);
queues[TF_DESTROY_QUEUE].add("tf_destroy_job", { queues[TF_DESTROY_QUEUE].add("tf_destroy_job", {
@@ -359,7 +355,7 @@ router.post('/:userId/destroyprovisions', passport.ensureAuthenticatedAndIsMe, a
_scenario: scenarioSource _scenario: scenarioSource
}); });
return res.status(200).json(destroyJob); return res.status(200).json(mongoJob);
} catch (error) { } catch (error) {
next(error); next(error);
@@ -386,7 +382,7 @@ router.post('/:userId/destroyprovisions', passport.ensureAuthenticatedAndIsMe, a
router.get('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { router.get('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try { try {
const result = await db.provision.get({"userId": req.params.userId, "isDeleted": false}); const result = await db.provision.get({"user": req.params.userId, "isDeleted": false});
return res.json(result); return res.json(result);
} catch (error) { } catch (error) {
next(error); next(error);
@@ -414,7 +410,7 @@ router.get('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (re
router.get('/:userId/destroyprovisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { router.get('/:userId/destroyprovisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try { try {
const result = await db.destroy.get({"userId": req.params.userId}); const result = await db.destroy.get({"user": req.params.userId});
return res.json(result); return res.json(result);
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -120,9 +120,13 @@ app.listen(3000, () => {
console.log(`Server listening on port 3000`) console.log(`Server listening on port 3000`)
}); });
https.createServer({ var optionsHttps = {
key: fs.readFileSync(path.resolve(__dirname, 'certs', 'fake_server.key')), pfx: fs.readFileSync(path.resolve(__dirname, 'certs', process.env.CERT_PFX_FILENAME)),
cert: fs.readFileSync(path.resolve(__dirname, 'certs', 'fake_server.cert')) passphrase: process.env.CERT_PFX_PASSWORD
}, app).listen(3100, function(){ };
https.createServer(optionsHttps, app).listen(3100, function(){
console.log(`Secure server listening on port 3100`); console.log(`Secure server listening on port 3100`);
}); });

19
server/shell/appgw.ps1 Normal file
View File

@@ -0,0 +1,19 @@
Params (
[string] $ApplicationGatewayName,
[string] $ApplicationGatewayResourceGroup = "AppGW_RG",
[string] $PolicyName = "QlikSenseDefault",
[string] $PolicyResourceGroup = "QMI-infra-vnet"
)
Connect-AzAccount -Identity
$gw = Get-AzApplicationGateway -Name $ApplicationGatewayName -ResourceGroupName $ApplicationGatewayResourceGroup
$policy = Get-AzApplicationGatewayFirewallPolicy -Name $PolicyName -ResourceGroupName $PolicyResourceGroup
#Save the policy itself
Set-AzApplicationGatewayFirewallPolicy -InputObject $policy
#Attach the policy to an Application Gateway
$gw.FirewallPolicy = $policy
#Save the Application Gateway
Set-AzApplicationGateway -ApplicationGateway $gw

View File

@@ -2,8 +2,8 @@ const db = require('../mongo.js');
const path = require('path'); const path = require('path');
const PROJECT_PATH = process.env.PROJECT_PATH; const PROJECT_PATH = process.env.PROJECT_PATH;
const tf = require("./docker/tf"); const tf = require("./docker/tf");
const azure = require("./docker/azure");
const sendEmail = require("../send-email.js"); const sendEmail = require("../send-email.js");
module.exports = async function(job) { module.exports = async function(job) {
const provJob = await db.provision.update(job.data.id, { const provJob = await db.provision.update(job.data.id, {
@@ -26,6 +26,9 @@ module.exports = async function(job) {
}).then(async function(output) { }).then(async function(output) {
var status = ( output.indexOf("Error:") !== -1 )? "error" : "provisioned"; var status = ( output.indexOf("Error:") !== -1 )? "error" : "provisioned";
return await db.provision.update(provJob._id, {"status": status}); return await db.provision.update(provJob._id, {"status": status});
}).then(async function(mongoUpdated) {
// Application Gateway assign policy
return azure.appgateway(mongoUpdated);
}).then(async function(mongoUpdated) { }).then(async function(mongoUpdated) {
return tf.outputs(mongoUpdated).then( async function(outputs){ return tf.outputs(mongoUpdated).then( async function(outputs){
return await db.provision.update(mongoUpdated._id, {"outputs": outputs}); return await db.provision.update(mongoUpdated._id, {"outputs": outputs});

View File

@@ -0,0 +1,43 @@
const Docker = require('dockerode');
const docker = new Docker({
'socketPath': '/home/docker.sock'
//'socketPath': '/var/run/docker.sock'
});
const fs = require("fs");
const DOCKERIMAGE = "mcr.microsoft.com/azure-powershell";
//const cmd = `docker run --net=host -w /myapp -v ${FOLDER}:/myapp mcr.microsoft.com/azure-powershell pwsh appgw.ps1 -ApplicationGatewayName ${appGwName}`;
const appgateway = function( mongoJob ) {
if ( mongoJob.scenario === 'azqmi-qs-sn' || mongoJob.scenario === 'azqmi-qdc-qs') {
var provision_id = mongoJob._id.toString();
var processStream = fs.createWriteStream(mongoJob.logFile, {flags:'a'});
var name = 'qmi-azureps-appgw-'+provision_id;
console.log(`AzurePS: will spin up container: ${name}`);
return docker.run(DOCKERIMAGE, ['pwsh', 'appgw.ps1', "-ProvisionId", provision_id ], processStream, {
"name": name,
"WorkingDir": "/myapp",
"HostConfig": {
"Binds": [
`${mongoJob.path}/shell:/myapp`
],
"NetworkMode": "host"
}
}).then(function(data) {
var output = data[0];
var container = data[1];
console.log(`AzurePS: ${name} (${container.id}) has finished with code: ${output.StatusCode}`);
return container.remove();
}).then(function() {
console.log(`AzurePS: ${name} removed!`);
return Promise.resolve(mongoJob);
});
} else {
return Promise.resolve(mongoJob);
}
};
module.exports.appgateway = appgateway;

View File

@@ -18,10 +18,10 @@ module.exports = async function(job){
let update, update2; let update, update2;
if ( output.indexOf("Error:") !== -1 ) { if ( output.indexOf("Error:") !== -1 ) {
update = await db.destroy.update(destroyMongo._id,{"status": "error"}); update = await db.destroy.update(destroyMongo._id,{"status": "error"});
update2 = await db.provision.update(destroyMongo.provId, {"isDestroyed": false}); update2 = await db.provision.update(provMongo._id, {"isDestroyed": false});
} else { } else {
update = await db.destroy.update(destroyMongo._id, {"status": "destroyed"}); update = await db.destroy.update(destroyMongo._id, {"status": "destroyed"});
update2 = await db.provision.update(destroyMongo.provId, {"isDestroyed": true}); update2 = await db.provision.update(provMongo._id, {"isDestroyed": true});
sendEmail.sendDestroyed(update2, job.data.user, job.data._scenario); sendEmail.sendDestroyed(update2, job.data.user, job.data._scenario);
} }
return { destroy: update, provision: update2 }; return { destroy: update, provision: update2 };

View File

@@ -1,103 +1,8 @@
<app-logs [show]="logShow" (onClose)="onLogsClose()" [type]="logstype" [selectedprov]="selectedprov"></app-logs>
<h1 style="margin-top: 80px;">Users</h1> <h1 style="margin-top: 80px;">Users</h1>
<div *ngIf="showInfo" class="modal-popover"> <table-users *ngIf="users && users.length" [elements]="users"></table-users>
<div class="popover-content" #popovercontent>
<div class="lui-popover">
<div class="lui-popover__header">
<div class="lui-popover__title"><h4><i class="fa fa-info-circle"></i> Provision information</h4></div>
</div>
<div class="lui-popover__body">
<div>
<b>Scenario: </b> {{selectedprov._scenario[0].title}}
</div>
<div>
<b>ProvisionID: </b> {{selectedprov._id}}
</div>
<div>
<b>Instance Type: </b> {{selectedprov.vmType}}
</div>
<div *ngIf="selectedprov.nodeCount">
<b>Number of nodes: </b> {{selectedprov.nodeCount}}
</div>
<h5 style="padding-top: 10px;">Connection resources</h5>
<div *ngFor="let item of selectedprov.outputs | keyvalue">
<b>{{item.key}}</b>
<pre class="mypre">{{item.value}}</pre>
</div>
</div>
<div class="lui-popover__footer">
<button class="lui-button lui-popover__button" (click)="showInfo = false">Close</button>
</div>
</div>
</div>
</div>
<table-users *ngIf="users.length" [elements]="users"></table-users>
<h1>Provisions</h1> <h1>Provisions</h1>
<table-admin *ngIf="provisions && provisions.length" [elements]="provisions"></table-admin>
<qmi-alert></qmi-alert>
<table-admin *ngIf="provisions.length" [elements]="provisions" [headElements]="headElements"></table-admin>
<!--
<table class="table table-sm table-hover" style="margin-bottom: 100px; font-size: 12px;">
<thead class="thead-light">
<tr>
<td></td>
<th colspan="6">Scenario Provision</th>
<th colspan="3" style="border-left:2px solid #ccc;">Scenario Destroy</th>
<td></td>
</tr>
</thead>
<tr>
<th></th>
<th>ProvisionID</th>
<th>Prov. Date</th>
<th>User</th>
<th>Scenario</th>
<th>Status VMs</th>
<th style="width: 150px;">Status</th>
<th style="border-left:2px solid #ccc;">DestroyID</th>
<th>Dest. Date</th>
<th>Status</th>
<th></th>
</tr>
<tr *ngFor="let provision of provisions; let i = index">
<td>
<button style="margin-right: 5px;" class="lui-button" (click)="showResources($event,provision)">
<span class="lui-icon lui-icon--info" aria-hidden="true"></span>
</button>
</td>
<td>
<a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">{{ provision._id }}</a>
</td>
<td>{{provision.created | date: 'MMM dd, yyyy - H:mm'}}</td>
<td class="ell" title="{{provision.path}}" >{{provision.user}}</td>
<td>{{provision.scenario}}</td>
<td>{{provision.statusVms}}</td>
<td>
<span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-warning': provision.status === 'provisioning' || provision.status === 'initializing', 'badge-success': provision.status === 'provisioned', 'badge-danger': provision.status === 'error' }">{{provision.status}}</span>
<span *ngIf="provision.status === 'provisioning' || provision.status === 'initializing'" class="spinner-border spinner-border-sm text-warning" role="status">
<span class="sr-only"></span>
</span>
</td>
<td style="border-left:2px solid #ccc;"><a *ngIf="provision.destroyId" href (click)="showLogs($event, provision, 'destroy')" class="lui-text-info">{{provision.destroyId}}</a></td>
<td>{{provision.dateDestroy | date: 'MMM dd, yyyy - H:mm'}}</td>
<td>
<span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-warning': provision.statusDestroy === 'destroying', 'badge-success': provision.statusDestroy === 'destroyed', 'badge-danger': provision.statusDestroy === 'error'}">{{provision.statusDestroy}}</span>
<span *ngIf="provision.statusDestroy === 'destroying'" class="spinner-border spinner-border-sm text-warning" role="status">
<span class="sr-only"></span>
</span>
</td>
<td>
<button title="Stop Vms" *ngIf="!provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="stopVms(provision)" class="lui-button"><span class="lui-icon lui-icon--stop" aria-hidden="true"></span></button>
<button title="Start Vms" *ngIf="!provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="startVms(provision)" class="lui-button"><span class="lui-icon lui-icon--run" aria-hidden="true"></span></button>
<button title="Destroy provision" *ngIf="!provision.isDestroyed && provision.statusDestroy !== 'destroying' && provision.status !== 'provisioning'" (click)="destroy(provision)" class="lui-button lui-text-danger"><span class="lui-icon lui-icon--remove" aria-hidden="true"></span></button>
<button title="Remove entry" *ngIf="provision.isDestroyed && provision.statusDestroy === 'destroyed'" (click)="del(provision)" class="lui-button lui-text-danger"><span class="lui-icon lui-icon--bin" aria-hidden="true"></span></button>
</td>
</tr>
</table>-->

View File

@@ -1,26 +1,3 @@
td { td {
vertical-align: middle; vertical-align: middle;
} }
.modal-popover {
position: fixed;
z-index: 10000;
width: 100%;
height: 100%;
top: 0px;
bottom: 0px;
left: 0px;
background: rgba(0,0,0,0.5);
.lui-popover {
max-width: 700px;
}
.popover-content {
position: relative;
top: 10%;
}
.lui-popover__body {
font-size: 14px;
max-height: 600px;
overflow: auto;
}
}

View File

@@ -3,7 +3,6 @@ import { UsersService } from '../services/users.service';
import { ProvisionsService } from '../services/provisions.service'; import { ProvisionsService } from '../services/provisions.service';
import { Subscription, timer } from 'rxjs'; import { Subscription, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { AuthGuard } from '../services/auth.guard';
import { ScenariosService } from '../services/scenarios.service'; import { ScenariosService } from '../services/scenarios.service';
@@ -14,72 +13,37 @@ import { ScenariosService } from '../services/scenarios.service';
}) })
export class AdminComponent implements OnInit { export class AdminComponent implements OnInit {
headElements = ['', 'ProvisionID', 'Prov. Date', 'User', 'Scenario', 'Status VMs', 'Status', 'DestroyID', 'Dest. Date', 'Status', ''];
currentUser;
users; users;
provisions; provisions;
destroys; destroys;
subscription: Subscription; subscription: Subscription;
logShow: boolean = false;
logstype: String = 'provision';
selectedprov: Object = null;
showInfo: boolean = false;
usersSub: Subscription;
scenariosSub: Subscription;
scenarios; scenarios;
constructor( private _usersService: UsersService, private _provisionsService: ProvisionsService, private _auth: AuthGuard, private _scenariosService: ScenariosService ) { constructor( private _usersService: UsersService, private _provisionsService: ProvisionsService, private _scenariosService: ScenariosService ) { }
this._auth.getUserInfo().subscribe( value => {
this.currentUser = value;
});
}
private _fillUser(pair) {
pair['0'].forEach(prov => {
var foundDes = this.users.filter(u=>{
return u._id.toString() === prov.userId.toString();
});
if (foundDes.length){
prov.user = foundDes[0].displayName;
}
});
}
private _getUsers() { private _process(provisions) : void {
provisions.forEach(p=>{
}
private _process(pair) : void {
this._provisionsService.composePair(pair);
pair[0].forEach(p=>{
p._scenario = this.scenarios.filter(s => s.name === p.scenario); p._scenario = this.scenarios.filter(s => s.name === p.scenario);
}); });
this._fillUser(pair);
this.destroys = pair[1];
if ( !this.provisions ) { if ( !this.provisions ) {
this.provisions = pair[0]; this.provisions = provisions;
} else { } else {
this.provisions.forEach( function(p, index, object) { this.provisions.forEach( function(p, index, object) {
let found = pair[0].filter(a=>a._id.toString() === p._id.toString()); let found = provisions.filter(a=>a._id.toString() === p._id.toString());
if ( found.length ) { if ( found.length ) {
p.status = found[0].status; p.status = found[0].status;
p.statusVms = found[0].statusVms; p.statusVms = found[0].statusVms;
p.isDestroyed = found[0].isDestroyed; p.isDestroyed = found[0].isDestroyed;
if (found[0].destroyId) { p.outputs = found[0].outputs;
p.destroyId = found[0].destroyId; p.destroy = found[0].destroy;
p.dateDestroy = found[0].dateDestroy;
p.statusDestroy = found[0].statusDestroy;
}
} else { } else {
object.splice(index, 1); object.splice(index, 1);
} }
}); });
pair[0].forEach(function(p) { provisions.forEach(function(p) {
let found = this.provisions.filter(a=>a._id.toString() === p._id.toString()); let found = this.provisions.filter(a=>a._id.toString() === p._id.toString());
if (found.length === 0){ if (found.length === 0){
this.provisions.unshift(p); this.provisions.unshift(p);
@@ -90,106 +54,32 @@ export class AdminComponent implements OnInit {
ngOnInit() { ngOnInit() {
var usersSub = this._usersService.getUsers().subscribe( res => {
this.scenariosSub = this._scenariosService.getScenarios().subscribe( res => { usersSub.unsubscribe();
this.scenarios = res;
this.scenariosSub.unsubscribe();
this.usersSub = this._usersService.getUsers().subscribe( res => {
this.users = res; this.users = res;
this.usersSub.unsubscribe();
this.subscription = timer(0, 5000).pipe( switchMap(() => this._provisionsService.getCombinedProvisionsAdmin() ) ).subscribe(pair => {
this._process(pair);
}); });
var scenariosSub = this._scenariosService.getScenarios().subscribe( res => {
scenariosSub.unsubscribe();
this.scenarios = res;
this.subscription = timer(0, 5000).pipe( switchMap(() => this._provisionsService.getProvisionsAdmin() ) ).subscribe(provisions => {
this._process(provisions);
}); });
}); });
} }
private _refresh(): void { private _refresh(): void {
var instantSubs = this._provisionsService.getCombinedProvisionsAdmin().subscribe( pair=>{ var instantSubs = this._provisionsService.getProvisionsAdmin().subscribe( provisions=>{
instantSubs.unsubscribe(); instantSubs.unsubscribe();
this._process(pair); this._process(provisions);
}); });
} }
ngOnDestroy() { ngOnDestroy() {
if ( this.subscription ) {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
del(provision): void {
this._provisionsService.delProvision(provision._id.toString(), provision.userId).subscribe( res => {
console.log("Done!", res);
this._refresh();
})
}
destroy(provision) : void{
this._provisionsService.newDestroy({"id": provision._id.toString()}, provision.userId).subscribe( res => {
console.log("Done!", res);
this._refresh();
})
}
startVms(provision): void {
this._provisionsService.startVms(provision._id.toString(), provision.userId).subscribe( res => {
console.log("Done!", res);
provision.startVms = res.startVms;
})
}
stopVms(provision): void {
this._provisionsService.stopVms(provision._id.toString(), provision.userId).subscribe( res => {
console.log("Done!", res);
provision.startVms = res.startVms;
})
}
showLogs($event, provision, type): void {
$event.preventDefault();
$event.stopPropagation();
this.logstype = type;
this.logShow = false;
this.selectedprov = provision;
this.logShow = true;
}
onLogsClose(): void {
this.selectedprov = null;
this.logShow = false;
}
onStartProvision(): void {
console.log("onStartProvision");
this._refresh();
}
setAdmin(user) : void {
this._usersService.updateUser(user._id, {"role": "admin"}).subscribe( res1 => {
console.log("Updated", res1);
this._usersService.getUsers().subscribe( res => {
this.users = res;
});
})
}
removeAdmin(user) : void {
this._usersService.updateUser(user._id, {"role": null}).subscribe( res1 => {
console.log("Updated", res1);
this._usersService.getUsers().subscribe( res => {
this.users = res;
});
})
}
showResources($event, provision): void {
$event.preventDefault();
$event.stopPropagation();
this.selectedprov = provision;
console.log("this.selectedprov", this.selectedprov);
this.showInfo = true;
} }
} }

View File

@@ -0,0 +1,13 @@
<div class="qmialert" *ngIf="alert">
<div #qmialert class="alert alert-info alert-dismissible fade show" [ngClass]="alert.type" role="alert">
<button type="button" class="close" aria-label="Close" (click)="closeAlert()">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="alert-heading">
<span *ngIf="alert.type === 'alert-warning'" class="fa fa-warning"></span>
<span *ngIf="alert.type === 'alert-primary'" class="fa fa-info-circle"></span>
<span>&nbsp;&nbsp;{{alert.text}}</span>
</h5>
</div>
</div>

View File

@@ -0,0 +1,9 @@
.qmialert {
position: fixed;
bottom: 0px;
left: 0px;
padding: 15px;
width: 100%;
z-index: 1;
text-align: center;
}

View File

@@ -0,0 +1,32 @@
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { AlertService } from '../services/alert.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'qmi-alert',
templateUrl: './alert.component.html',
styleUrls: ['./alert.component.scss']
})
export class AlertComponent implements OnInit, OnDestroy {
@ViewChild('qmialert', { static: true }) alertEl: ElementRef;
subscription: Subscription;
alert : any = null;
constructor(private _alertService: AlertService) {}
ngOnInit() {
this.subscription = this._alertService.getAlertEmitter().subscribe(function(data){
this.alert = data;
}.bind(this));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
closeAlert() {
this.alert = null;
}
}

View File

@@ -0,0 +1,30 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title w-100" id="myModalLabel">Provision information</h4>
</div>
<div class="modal-body">
<div>
<b>Scenario: </b> {{info._scenario[0].title}}
</div>
<div>
<b>ProvisionID: </b> {{info._id}}
</div>
<div>
<b>Instance Type: </b> {{info.vmType}}
</div>
<div *ngIf="info.nodeCount">
<b>Number of nodes: </b> {{info.nodeCount}}
</div>
<h5 style="padding-top: 10px;">Connection resources</h5>
<div *ngFor="let item of info.outputs | keyvalue">
<b>{{item.key}}</b>
<pre class="mypre">{{item.value}}</pre>
</div>
</div>
<div class="modal-footer">
<button type="button" mdbBtn color="elegant" class="waves-light" aria-label="Close" (click)="modalRef.hide()" mdbWavesEffect>Close</button>
</div>
</div>

View File

@@ -0,0 +1,4 @@
.modal-body {
max-height: 600px;
overflow: auto;
}

View File

@@ -0,0 +1,23 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { MDBModalRef } from 'angular-bootstrap-md';
@Component({
selector: 'qmi-modalinfo',
templateUrl: './modalinfo.component.html',
styleUrls: ['./modalinfo.component.scss']
})
export class ModalInfoComponent implements OnInit, OnDestroy {
info;
constructor( public modalRef: MDBModalRef ) {}
ngOnInit() {
}
ngOnDestroy() {
}
}

View File

@@ -22,6 +22,9 @@ import { MyHttpInterceptor } from './interceptors/http.interceptor';
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { TableAdminComponent } from './tables/table-admin.component'; import { TableAdminComponent } from './tables/table-admin.component';
import { TableUsersComponent } from './tables/table-users.component'; import { TableUsersComponent } from './tables/table-users.component';
import { AlertComponent } from './alert/alert.component';
import { AlertService } from './services/alert.service';
import { ModalInfoComponent } from './alert/modalinfo.component';
@NgModule({ @NgModule({
@@ -34,7 +37,9 @@ import { TableUsersComponent } from './tables/table-users.component';
AdminComponent, AdminComponent,
PopoverconfirmComponent, PopoverconfirmComponent,
TableAdminComponent, TableAdminComponent,
TableUsersComponent TableUsersComponent,
AlertComponent,
ModalInfoComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@@ -49,6 +54,7 @@ import { TableUsersComponent } from './tables/table-users.component';
ProvisionsService, ProvisionsService,
ScenariosService, ScenariosService,
UsersService, UsersService,
AlertService,
AuthGuard AuthGuard
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@@ -4,10 +4,9 @@
<h1>QMI Cloud</h1> <h1>QMI Cloud</h1>
<p class="lead text-muted">Introducing a new way to launch scenarios based on Qlik software in the Cloud.</p> <p class="lead text-muted">Introducing a new way to launch scenarios based on Qlik software in the Cloud.</p>
<p *ngIf="!user"> <p *ngIf="!user">
<a class="btn btn-success" href="/login">Login <i class="fa fa-sign-in"></i></a> <a mdbBtn href="/login" color="dark-green" mdbWavesEffect>Login <mdb-icon fas icon="sign-in-alt"></mdb-icon></a> </p>
</p>
<p *ngIf="user"> <p *ngIf="user">
<a class="btn btn-secondary" routerLink="/provisions">Your Provisions <i class="fa fa-arrow-right"></i></a> <a mdbBtn color="elegant" routerLink="/provisions">Your Provisions <mdb-icon fas icon="angle-right"></mdb-icon></a>
</p> </p>
</div> </div>
</section> </section>

View File

@@ -6,7 +6,7 @@
<br> <br>
<h2 style="text-transform: capitalize;">{{type}} Logs</h2> <h2 style="text-transform: capitalize;">{{type}} Logs</h2>
<h3 *ngIf="type === 'provision'">{{selectedprov._id}} - {{selectedprov.scenario}}</h3> <h3 *ngIf="type === 'provision'">{{selectedprov._id}} - {{selectedprov.scenario}}</h3>
<h3 *ngIf="type === 'destroy'">{{selectedprov.destroyId}} - {{selectedprov.scenario}}</h3> <h3 *ngIf="selectedprov.destroy && type === 'destroy'">{{selectedprov.destroy._id}} - {{selectedprov.scenario}}</h3>
<pre *ngIf="content" class="mypre">{{content}}</pre> <pre *ngIf="content" class="mypre">{{content}}</pre>
<br> <br>
<br> <br>

View File

@@ -38,7 +38,7 @@ export class LogsComponent implements OnInit, OnChanges{
this.content = content; this.content = content;
}); });
} else if ( this.type === "destroy" ) { } else if ( this.type === "destroy" ) {
this.sub = timer(0, 5000).pipe( switchMap(() => this._provisionsService.getDestroyLogs(this.selectedprov.destroyId) ) ).subscribe(content => { this.sub = timer(0, 5000).pipe( switchMap(() => this._provisionsService.getDestroyLogs(this.selectedprov.destroy._id) ) ).subscribe(content => {
this.content = content; this.content = content;
}) })
} }
@@ -58,7 +58,7 @@ export class LogsComponent implements OnInit, OnChanges{
this.content = content; this.content = content;
}); });
} else if ( this.type === "destroy" ) { } else if ( this.type === "destroy" ) {
this.sub = timer(0, 5000).pipe( switchMap(() => this._provisionsService.getDestroyLogs(this.selectedprov.destroyId) ) ).subscribe(content => { this.sub = timer(0, 5000).pipe( switchMap(() => this._provisionsService.getDestroyLogs(this.selectedprov.destroy._id) ) ).subscribe(content => {
this.content = content; this.content = content;
}) })
} }

View File

@@ -1,47 +1,10 @@
<app-logs [show]="logShow" (onClose)="onLogsClose()" [type]="logstype" [selectedprov]="selectedprov"></app-logs> <app-logs [show]="logShow" (onClose)="onLogsClose()" [type]="logstype" [selectedprov]="selectedprov"></app-logs>
<div style="margin-top: 80px;"> <div style="margin-top: 80px;">
<h1>Scenarios</h1>
<app-scenarios (onStartProvision)="onStartProvision()"></app-scenarios>
</div>
<div *ngIf="showInfo" class="modal-popover">
<div class="popover-content" #popovercontent>
<div class="lui-popover">
<div class="lui-popover__header">
<div class="lui-popover__title"><h4><i class="fa fa-info-circle"></i> Provision information</h4></div>
</div>
<div class="lui-popover__body">
<div>
<b>Scenario: </b> {{selectedprov._scenario[0].title}}
</div>
<div>
<b>ProvisionID: </b> {{selectedprov._id}}
</div>
<div>
<b>Instance Type: </b> {{selectedprov.vmType}}
</div>
<div *ngIf="selectedprov.nodeCount">
<b>Number of nodes: </b> {{selectedprov.nodeCount}}
</div>
<h5 style="padding-top: 10px;">Connection resources</h5>
<div *ngFor="let item of selectedprov.outputs | keyvalue">
<b>{{item.key}}</b>
<pre class="mypre">{{item.value}}</pre>
</div>
</div>
<div class="lui-popover__footer">
<button class="lui-button lui-popover__button" (click)="showInfo = false">Close</button>
</div>
</div>
</div>
</div>
<div style="margin-bottom: 100px;">
<h1>Provisions</h1> <h1>Provisions</h1>
<h3 style="border-bottom: 1px solid #ccc;">Running</h3> <h3 style="border-bottom: 1px solid #ccc;">Running</h3>
<div *ngIf="provisions && provisions.length" class="flexcontainer"> <div *ngIf="provisions && provisions.length" class="flexcontainer">
<div *ngFor="let provision of provisions; let i = index" class="box"> <div *ngFor="let provision of provisions; let i = index" class="box">
<div class="title {{provision.status}} {{provision.statusVms}} {{provision.statusDestroy}}"> <div class="title {{provision.status}} {{provision.statusVms}} {{provision.destroy? provision.destroy.status : ''}}">
<div class="maintitle">{{provision._scenario[0].title}}</div> <div class="maintitle">{{provision._scenario[0].title}}</div>
<div class="subtitle">{{provision._scenario[0].name}} (v{{provision._scenario[0].version}})</div> <div class="subtitle">{{provision._scenario[0].name}} (v{{provision._scenario[0].version}})</div>
</div> </div>
@@ -61,22 +24,22 @@
- -
<a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">Logs</a> <a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">Logs</a>
</div> </div>
<div *ngIf="provision.dateDestroy"> <div *ngIf="provision.destroy">
Destroy ({{provision.dateDestroy | date: 'MMM dd, yyyy - H:mm'}}h) Destroy ({{provision.destroy.created | date: 'MMM dd, yyyy - H:mm'}}h)
- -
<span> <span>
<span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-warning': provision.statusDestroy === 'destroying', 'badge-success': provision.statusDestroy === 'destroyed', 'badge-danger': provision.statusDestroy === 'error'}">{{provision.statusDestroy}}</span> <span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-warning': provision.destroy.status === 'destroying', 'badge-success': provision.destroy.status === 'destroyed', 'badge-danger': provision.destroy.status === 'error'}">{{provision.destroy.status}}</span>
<span *ngIf="provision.statusDestroy === 'destroying'" class="spinner-border spinner-border-sm text-warning" role="status"> <span *ngIf="provision.destroy.status === 'destroying'" class="spinner-border spinner-border-sm text-warning" role="status">
<span class="sr-only"></span> <span class="sr-only"></span>
</span> </span>
</span> </span>
- -
<a *ngIf="provision.destroyId" href (click)="showLogs($event, provision, 'destroy')" class="lui-text-info">Logs</a> <a *ngIf="provision.destroy._id" href (click)="showLogs($event, provision, 'destroy')" class="lui-text-info">Logs</a>
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">
<button style="margin-right: 5px;" class="lui-button" (click)="showResources($event,provision)"> <button style="margin-right: 5px;" class="lui-button" (click)="openModal(provision)">
<span class="lui-icon lui-icon--info" aria-hidden="true"></span> <span class="lui-icon lui-icon--info" aria-hidden="true"></span>
</button> </button>
<button style="margin-right: 5px;" title="Stop VMs" *ngIf="provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="stopVms(provision)" class="lui-button"> <button style="margin-right: 5px;" title="Stop VMs" *ngIf="provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="stopVms(provision)" class="lui-button">
@@ -85,10 +48,10 @@
<button style="margin-right: 5px;" title="Start VMs" *ngIf="provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="startVms(provision)" class="lui-button"> <button style="margin-right: 5px;" title="Start VMs" *ngIf="provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="startVms(provision)" class="lui-button">
<span class="lui-icon lui-icon--run" aria-hidden="true"></span> <span class="lui-icon lui-icon--run" aria-hidden="true"></span>
</button> </button>
<button style="float: right;" title="Destroy provision" *ngIf="!provision.isDestroyed && provision.statusDestroy !== 'destroying' && provision.status !== 'provisioning'" (click)="destroy(provision)" class="lui-button"> <button style="float: right;" title="Destroy provision" *ngIf="!provision.isDestroyed && (!provision.destroy || provision.destroy.status !== 'destroying') && provision.status !== 'provisioning'" (click)="setModal(provision, frame2)" class="lui-button">
<span class="lui-icon lui-icon--remove" aria-hidden="true"></span> <span class="lui-icon lui-icon--remove" aria-hidden="true"></span>
</button> </button>
<button style="float: right;" title="Remove entry" *ngIf="provision.isDestroyed && provision.statusDestroy === 'destroyed'" (click)="del(provision)" class="lui-button"> <button style="float: right;" title="Remove entry" *ngIf="provision.isDestroyed && provision.destroy.status === 'destroyed'" (click)="del(provision)" class="lui-button">
<span class="lui-icon lui-icon--bin" aria-hidden="true"></span> <span class="lui-icon lui-icon--bin" aria-hidden="true"></span>
</button> </button>
@@ -103,7 +66,7 @@
<h3 style="padding-top: 40px; border-bottom: 1px solid #ccc;" *ngIf="destroys && destroys.length">History</h3> <h3 style="padding-top: 40px; border-bottom: 1px solid #ccc;" *ngIf="destroys && destroys.length">History</h3>
<div *ngIf="destroys && destroys.length" class="flexcontainer"> <div *ngIf="destroys && destroys.length" class="flexcontainer">
<div *ngFor="let provision of destroys; let i = index" class="box"> <div *ngFor="let provision of destroys; let i = index" class="box">
<div class="title {{provision.status}} {{provision.statusDestroy}}"> <div class="title {{provision.status}} {{provision.destroy.status}}">
<div class="maintitle">{{provision._scenario[0].title}}</div> <div class="maintitle">{{provision._scenario[0].title}}</div>
<div class="subtitle">{{provision._scenario[0].name}} (v{{provision._scenario[0].version}})</div> <div class="subtitle">{{provision._scenario[0].name}} (v{{provision._scenario[0].version}})</div>
</div> </div>
@@ -120,26 +83,57 @@
- -
<a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">Logs</a> <a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">Logs</a>
</div> </div>
<div *ngIf="provision.dateDestroy"> <div *ngIf="provision.destroy">
Destroy ({{provision.dateDestroy | date: 'MMM dd, yyyy - H:mm'}}h) Destroy ({{provision.destroy.created | date: 'MMM dd, yyyy - H:mm'}}h)
- -
<span> <span>
<span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-warning': provision.statusDestroy === 'destroying', 'badge-success': provision.statusDestroy === 'destroyed', 'badge-danger': provision.statusDestroy === 'error'}">{{provision.statusDestroy}}</span> <span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-warning': provision.destroy.status === 'destroying', 'badge-success': provision.destroy.status === 'destroyed', 'badge-danger': provision.destroy.status === 'error'}">{{provision.destroy.status}}</span>
<span *ngIf="provision.statusDestroy === 'destroying'" class="spinner-border spinner-border-sm text-warning" role="status"> <span *ngIf="provision.destroy.status === 'destroying'" class="spinner-border spinner-border-sm text-warning" role="status">
<span class="sr-only"></span> <span class="sr-only"></span>
</span> </span>
</span> </span>
- -
<a *ngIf="provision.destroyId" href (click)="showLogs($event, provision, 'destroy')" class="lui-text-info">Logs</a> <a *ngIf="provision.destroy" href (click)="showLogs($event, provision, 'destroy')" class="lui-text-info">Logs</a>
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">
<button style="margin-right: 5px;" class="lui-button" (click)="showResources($event,provision)"><span class="lui-icon lui-icon--info" aria-hidden="true"></span></button> <button style="margin-right: 5px;" class="lui-button" (click)="openModal(provision)"><span class="lui-icon lui-icon--info" aria-hidden="true"></span></button>
<button style="float:right;" title="Destroy provision" *ngIf="!provision.isDestroyed && provision.statusDestroy !== 'destroying' && provision.status !== 'provisioning'" (click)="destroy(provision)" class="lui-button"><span class="lui-icon lui-icon--remove" aria-hidden="true"></span></button> <button style="float:right;" title="Destroy provision" *ngIf="!provision.isDestroyed && (!provision.destroy || provision.destroy.status !== 'destroying') && provision.status !== 'provisioning'" (click)="setModal(provision, frame2)" class="lui-button"><span class="lui-icon lui-icon--remove" aria-hidden="true"></span></button>
<button style="float:right;" title="Remove entry" *ngIf="provision.isDestroyed && provision.statusDestroy === 'destroyed'" (click)="del(provision)" class="lui-button"><span class="lui-icon lui-icon--bin" aria-hidden="true"></span></button> <button style="float:right;" title="Remove entry" *ngIf="provision.isDestroyed && provision.destroy.status === 'destroyed'" (click)="del(provision)" class="lui-button"><span class="lui-icon lui-icon--bin" aria-hidden="true"></span></button>
</div> </div>
</div> </div>
</div> </div>
</div>
<div style="margin-top: 40px; margin-bottom: 100px;">
<h1>Scenarios</h1>
<app-scenarios (onStartProvision)="onStartProvision($event)"></app-scenarios>
</div>
<qmi-alert></qmi-alert>
<div mdbModal #frame2="mdbModal" class="modal fade top" id="frameModalTop" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true" (closed)="onModalClosed($event)">
<div class="modal-dialog modal-sm modal-notify modal-danger" role="document">
<!--Content-->
<div class="modal-content text-center">
<!--Header-->
<div class="modal-header d-flex justify-content-center">
<p class="heading">Confirm destroy this provision?</p>
</div>
<!--Body-->
<div class="modal-body">
<mdb-icon fas icon="times-circle" size="4x" class="animated rotateIn"></mdb-icon>
</div>
<!--Footer-->
<div class="modal-footer justify-content-center">
<a type="button" mdbBtn color="danger" outline="true" class="waves-effect" mdbWavesEffect (click)="destroyConfirmed(frame2);">Yes</a>
<a type="button" mdbBtn color="danger" class="waves-effect" mdbWavesEffect data-dismiss="modal" (click)="frame2.hide()">No</a>
</div>
</div>
<!--/.Content-->
</div>
</div> </div>

View File

@@ -4,6 +4,9 @@ import { Subscription, timer} from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { AuthGuard } from '../services/auth.guard'; import { AuthGuard } from '../services/auth.guard';
import { ScenariosService } from '../services/scenarios.service'; import { ScenariosService } from '../services/scenarios.service';
import { AlertService } from '../services/alert.service';
import { MDBModalService } from 'angular-bootstrap-md';
import { ModalInfoComponent } from '../alert/modalinfo.component';
@Component({ @Component({
@@ -22,27 +25,23 @@ export class ProvisionsComponent implements OnInit {
scenariosSub: Subscription; scenariosSub: Subscription;
logShow: boolean = false; logShow: boolean = false;
logstype: String = 'provision'; logstype: String = 'provision';
selectedprov: Object = null; public selectedprov: Object = null;
showInfo: boolean = false;
scenarios; scenarios;
constructor(private _provisionsService: ProvisionsService, private _scenariosService: ScenariosService, private _auth: AuthGuard) { constructor(private modalService: MDBModalService, private _alertService: AlertService, private _provisionsService: ProvisionsService, private _scenariosService: ScenariosService, private _auth: AuthGuard) {
this._auth.getUserInfo().subscribe( value => { this._auth.getUserInfo().subscribe( value => {
this._userId = value? value._id : null; this._userId = value? value._id : null;
}); });
} }
private _refresh(): void { private _refresh(): void {
this.instantSubs = this._provisionsService.getCombinedProvisions(this._userId).subscribe( pair=>{ this.instantSubs = this._provisionsService.getProvisions(this._userId).subscribe( provisions=>{
this._provisionsService.composePair(pair);
provisions.forEach(p=>{
pair[0].forEach(p=>{
p._scenario = this.scenarios.filter(s => s.name === p.scenario); p._scenario = this.scenarios.filter(s => s.name === p.scenario);
}); });
this.provisions = pair[0].filter(p => !p.statusDestroy || p.statusDestroy !== 'destroyed'); this.provisions = provisions.filter(p => !p.destroy || !p.destroy.status || p.destroy.status !== 'destroyed');
//this.destroys = pair[1]; this.destroys = provisions.filter(p => p.destroy && p.destroy.status === 'destroyed');
this.destroys = pair[0].filter(p => p.statusDestroy === 'destroyed');
this.instantSubs.unsubscribe(); this.instantSubs.unsubscribe();
}) })
} }
@@ -53,14 +52,12 @@ export class ProvisionsComponent implements OnInit {
this.scenarios = res; this.scenarios = res;
this.scenariosSub.unsubscribe(); this.scenariosSub.unsubscribe();
this.subscription = timer(0, 5000).pipe( switchMap(() => this._provisionsService.getCombinedProvisions(this._userId) ) ).subscribe(pair => { this.subscription = timer(0, 5000).pipe( switchMap(() => this._provisionsService.getProvisions(this._userId) ) ).subscribe(provisions => {
this._provisionsService.composePair(pair); provisions.forEach(p=>{
pair[0].forEach(p=>{
p._scenario = this.scenarios.filter(s => s.name === p.scenario); p._scenario = this.scenarios.filter(s => s.name === p.scenario);
}); });
this.provisions = pair[0].filter(p => !p.statusDestroy || p.statusDestroy !== 'destroyed'); this.provisions = provisions.filter(p => !p.destroy || !p.destroy.status || p.destroy.status !== 'destroyed');
//this.destroys = pair[1]; this.destroys = provisions.filter(p => p.destroy && p.destroy.status === 'destroyed');
this.destroys = pair[0].filter(p => p.statusDestroy === 'destroyed');
}) })
}); });
} }
@@ -72,27 +69,55 @@ export class ProvisionsComponent implements OnInit {
} }
} }
setModal(provision, frame) : void {
frame.show();
this._provisionsService.setSelectedProv(provision);
}
onModalClosed(event: any) {
this._provisionsService.setSelectedProv(null);
}
del(provision): void { del(provision): void {
this._provisionsService.delProvision(provision._id.toString(), this._userId).subscribe( res => { this._provisionsService.delProvision(provision._id.toString(), this._userId).subscribe( res => {
this._refresh(); this._refresh();
this._alertService.showAlert({
type: 'alert-primary',
text: `Provision entry '${provision.scenario}' was deleted from your history`
});
}) })
} }
destroy(provision) : void{ destroyConfirmed(frame) : void{
this._provisionsService.newDestroy({"id": provision._id.toString()}, this._userId).subscribe( res => { var selectedprov = this._provisionsService.getSelectedProv();
this._provisionsService.newDestroy({"id": selectedprov._id.toString()}, this._userId).subscribe( res => {
this._refresh(); this._refresh();
}) this._alertService.showAlert({
type: 'alert-primary',
text: `Provision of scenario '${selectedprov.scenario}' is going to be destroyed`
});
});
frame.hide();
} }
startVms(provision) : void { startVms(provision) : void {
this._provisionsService.startVms(provision._id.toString(), this._userId).subscribe( res => { this._provisionsService.startVms(provision._id.toString(), this._userId).subscribe( res => {
provision.statusVms = res.statusVms; provision.statusVms = res.statusVms;
this._alertService.showAlert({
type: 'alert-primary',
text: `Starting all VMs for scenario '${provision.scenario}'...`
});
}) })
} }
stopVms(provision) : void { stopVms(provision) : void {
this._provisionsService.stopVms(provision._id.toString(), this._userId).subscribe( res => { this._provisionsService.stopVms(provision._id.toString(), this._userId).subscribe( res => {
provision.statusVms = res.statusVms; provision.statusVms = res.statusVms;
this._alertService.showAlert({
type: 'alert-primary',
text: `Starting all VMs for scenario '${provision.scenario}'...`
});
}) })
} }
@@ -105,11 +130,20 @@ export class ProvisionsComponent implements OnInit {
this.logShow = true; this.logShow = true;
} }
showResources($event, provision): void { openModal(provision) {
$event.preventDefault(); var modalRef = this.modalService.show(ModalInfoComponent, {
$event.stopPropagation(); backdrop: true,
this.selectedprov = provision; keyboard: true,
this.showInfo = true; focus: true,
show: false,
ignoreBackdropClick: false,
class: 'modal-lg',
containerClass: '',
animated: true,
data: {
info:provision
}
} );
} }
onLogsClose(): void { onLogsClose(): void {
@@ -117,11 +151,12 @@ export class ProvisionsComponent implements OnInit {
this.logShow = false; this.logShow = false;
} }
onStartProvision(): void { onStartProvision(scenario): void {
console.log("onStartProvision"); this._alertService.showAlert({
type: 'alert-primary',
text: `Scenario '${scenario.name}' is going to be provisioned. Scroll down to your Provisions to watch out progress.`
});
this._refresh(); this._refresh();
} }
} }

View File

@@ -18,7 +18,7 @@ export class ScenariosComponent implements OnInit, OnDestroy {
}); });
} }
@Output() onStartProvision = new EventEmitter(); @Output() onStartProvision = new EventEmitter<object>();
_userId; _userId;
scenarios; scenarios;
@@ -53,7 +53,7 @@ export class ScenariosComponent implements OnInit, OnDestroy {
console.log("Provision scenario", scenario); console.log("Provision scenario", scenario);
this._provisionsService.newProvision({"scenario": scenario.name, "vmType": scenario.selectedVmType, "nodeCount": scenario.selectedNodeCount}, this._userId).subscribe( res => { this._provisionsService.newProvision({"scenario": scenario.name, "vmType": scenario.selectedVmType, "nodeCount": scenario.selectedNodeCount}, this._userId).subscribe( res => {
console.log("Done!", res); console.log("Done!", res);
this.onStartProvision.emit(); this.onStartProvision.emit(scenario);
}) })
} }

View File

@@ -0,0 +1,27 @@
import { Injectable, EventEmitter } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AlertService {
alertEmitter = new EventEmitter();
constructor() { }
to;
showAlert(alert) : void {
this.alertEmitter.emit(alert);
if ( this.to ) {
clearTimeout(this.to);
}
this.to = setTimeout (function(){
this.alertEmitter.emit(null);
}.bind(this), 5000);
}
getAlertEmitter(): EventEmitter<any> {
return this.alertEmitter;
}
}

View File

@@ -9,6 +9,7 @@ import { AuthGuard } from './auth.guard';
}) })
export class ProvisionsService { export class ProvisionsService {
selectedProv;
constructor( private httpClient: HttpClient ) { } constructor( private httpClient: HttpClient ) { }
@@ -57,21 +58,6 @@ export class ProvisionsService {
return this.httpClient.get(`/api/destroyprovisions/${id}/logs`, {responseType: 'text'}); return this.httpClient.get(`/api/destroyprovisions/${id}/logs`, {responseType: 'text'});
} }
composePair(pair) : any {
pair['0'].forEach(prov => {
var foundDes = pair['1'].filter(d=>{
return d.provId.toString() === prov._id.toString()
});
if (foundDes.length){
prov.destroyId = foundDes[0]._id.toString();
prov.statusDestroy = foundDes[0].status;
prov.dateDestroy = foundDes[0].created;
}
});
return pair;
}
stopVms(id, userId): Observable<any> { stopVms(id, userId): Observable<any> {
return this.httpClient.post(`/api/users/${userId}/provisions/${id}/deallocatevms`, null); return this.httpClient.post(`/api/users/${userId}/provisions/${id}/deallocatevms`, null);
} }
@@ -80,4 +66,16 @@ export class ProvisionsService {
return this.httpClient.post(`/api/users/${userId}/provisions/${id}/startvms`, null); return this.httpClient.post(`/api/users/${userId}/provisions/${id}/startvms`, null);
} }
setSelectedProv(provision : any) : void {
if ( provision ) {
this.selectedProv = provision;
} else {
this.selectedProv = null;
}
}
getSelectedProv() : any {
return this.selectedProv;
}
} }

View File

@@ -1,22 +1,24 @@
<div class="md-form"> <app-logs [show]="logShow" (onClose)="onLogsClose()" [type]="logstype" [selectedprov]="selectedprov"></app-logs>
<div class="md-form">
<input type="text" class="form-control" [(ngModel)]="searchText" (keyup)="searchItems()" id="search-input" <input type="text" class="form-control" [(ngModel)]="searchText" (keyup)="searchItems()" id="search-input"
mdbInput> mdbInput>
<label for="search-input">Search</label> <label for="search-input">Search</label>
</div> </div>
<table mdbTable #tableEl="mdbTable" stickyHeader="true" hover="true" class="z-depth-1 table-sm"> <table mdbTable #tableEl="mdbTable" stickyHeader="true" hover="true" class="z-depth-1 table-sm">
<thead class="sticky-top"> <thead class="sticky-top">
<tr> <tr>
<!--<th *ngFor="let head of headElements; let i = index" [mdbTableSort]="elements" [sortBy]="headElements[i]" <!--<th *ngFor="let head of headElements; let i = index" [mdbTableSort]="elements" [sortBy]="headElements[i]"
scope="col">{{head}} <mdb-icon fas icon="sort"></mdb-icon> scope="col">{{head}} <mdb-icon fas icon="sort"></mdb-icon>
</th>--> </th>-->
<th *ngFor="let head of headElements; let i = index" scope="col">{{head}} <th *ngFor="let head of headElements; let i = index" scope="col">{{head}}
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody #row> <tbody #row>
<tr mdbTableCol *ngFor="let provision of elements; let i = index"> <tr mdbTableCol *ngFor="let provision of elements; let i = index">
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)"> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">
<button style="margin-right: 5px;" class="lui-button" (click)="showResources($event,provision)"> <button style="margin-right: 5px;" class="lui-button" (click)="openInfoModal(provision)">
<span class="lui-icon lui-icon--info" aria-hidden="true"></span> <span class="lui-icon lui-icon--info" aria-hidden="true"></span>
</button> </button>
</td> </td>
@@ -24,7 +26,7 @@
<a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">{{ provision._id }}</a> <a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">{{ provision._id }}</a>
</td> </td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.created | date: 'MMM dd, yyyy - H:mm'}}</td> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.created | date: 'MMM dd, yyyy - H:mm'}}</td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" class="ell" title="{{provision.path}}" >{{provision.user}}</td> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" class="ell" title="{{provision.path}}" >{{provision.user.displayName}}</td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.scenario}}</td> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.scenario}}</td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.statusVms}}</td> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.statusVms}}</td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)"> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">
@@ -33,29 +35,81 @@
<span class="sr-only"></span> <span class="sr-only"></span>
</span> </span>
</td> </td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" style="border-left:2px solid #ccc;"><a *ngIf="provision.destroyId" href (click)="showLogs($event, provision, 'destroy')" class="lui-text-info">{{provision.destroyId}}</a></td> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" style="border-left:2px solid #ccc;"><a *ngIf="provision.destroy" href (click)="showLogs($event, provision, 'destroy')" class="lui-text-info">{{provision.destroy._id}}</a></td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" >{{provision.dateDestroy | date: 'MMM dd, yyyy - H:mm'}}</td> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" ><span *ngIf="provision.destroy">{{provision.destroy.created | date: 'MMM dd, yyyy - H:mm'}}</span></td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)"> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">
<span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-warning': provision.statusDestroy === 'destroying', 'badge-success': provision.statusDestroy === 'destroyed', 'badge-danger': provision.statusDestroy === 'error'}">{{provision.statusDestroy}}</span> <span *ngIf="provision.destroy">
<span *ngIf="provision.statusDestroy === 'destroying'" class="spinner-border spinner-border-sm text-warning" role="status"> <span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-warning': provision.destroy.status === 'destroying', 'badge-success': provision.destroy.status === 'destroyed', 'badge-danger': provision.destroy.status === 'error'}">{{provision.destroy.status}}</span>
<span *ngIf="provision.destroy.status === 'destroying'" class="spinner-border spinner-border-sm text-warning" role="status">
<span class="sr-only"></span> <span class="sr-only"></span>
</span> </span>
</span>
</td> </td>
<!--<td><span *ngIf="provision.isDestroyed" class="lui-icon lui-icon--tick lui-text-success"></span></td>--> <!--<td><span *ngIf="provision.isDestroyed" class="lui-icon lui-icon--tick lui-text-success"></span></td>-->
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)"> <td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">
<button title="Stop Vms" *ngIf="!provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="stopVms(provision)" class="lui-button"><span class="lui-icon lui-icon--stop" aria-hidden="true"></span></button> <button title="Stop Vms" *ngIf="!provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="stopVms(provision)" class="lui-button"><span class="lui-icon lui-icon--stop" aria-hidden="true"></span></button>
<button title="Start Vms" *ngIf="!provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="startVms(provision)" class="lui-button"><span class="lui-icon lui-icon--run" aria-hidden="true"></span></button> <button title="Start Vms" *ngIf="!provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="startVms(provision)" class="lui-button"><span class="lui-icon lui-icon--run" aria-hidden="true"></span></button>
<button title="Destroy provision" *ngIf="!provision.isDestroyed && provision.statusDestroy !== 'destroying' && provision.status !== 'provisioning'" (click)="destroy(provision)" class="lui-button lui-text-danger"><span class="lui-icon lui-icon--remove" aria-hidden="true"></span></button> <button title="Destroy provision" *ngIf="!provision.isDestroyed && (!provision.destroy || provision.destroy.status !== 'destroying') && provision.status !== 'provisioning'" (click)="setModal(provision, frame2)" class="lui-button lui-text-danger"><span class="lui-icon lui-icon--remove" aria-hidden="true"></span></button>
<button title="Remove entry" *ngIf="provision.isDestroyed && provision.statusDestroy === 'destroyed'" (click)="del(provision)" class="lui-button lui-text-danger"><span class="lui-icon lui-icon--bin" aria-hidden="true"></span></button> <button title="Remove entry" *ngIf="provision.isDestroyed && provision.destroy.status === 'destroyed'" (click)="setModal(provision, frame);" class="lui-button lui-text-danger"><span class="lui-icon lui-icon--bin" aria-hidden="true"></span></button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
<tfoot class="grey lighten-5 w-100"> <tfoot class="grey lighten-5 w-100">
<tr *ngIf="!pagingIsDisabled"> <tr *ngIf="!pagingIsDisabled">
<td colspan="11"> <td colspan="11">
<mdb-table-pagination [tableEl]="tableEl" paginationAlign="" [searchDataSource]="elements"></mdb-table-pagination> <mdb-table-pagination [tableEl]="tableEl" paginationAlign="" [searchDataSource]="elements"></mdb-table-pagination>
</td> </td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>
<div mdbModal #frame="mdbModal" class="modal fade top" id="frameModalTop" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true" (closed)="onModalClosed($event)">
<div class="modal-dialog modal-sm modal-notify modal-danger" role="document">
<!--Content-->
<div class="modal-content text-center">
<!--Header-->
<div class="modal-header d-flex justify-content-center">
<p class="heading">Confirm delete?</p>
</div>
<!--Body-->
<div class="modal-body">
<mdb-icon fas icon="trash-alt" size="4x" class="animated rotateIn"></mdb-icon>
</div>
<!--Footer-->
<div class="modal-footer justify-content-center">
<a type="button" mdbBtn color="danger" outline="true" class="waves-effect" mdbWavesEffect (click)="delConfirmed(frame);">Yes</a>
<a type="button" mdbBtn color="danger" class="waves-effect" mdbWavesEffect data-dismiss="modal" (click)="frame.hide()">No</a>
</div>
</div>
<!--/.Content-->
</div>
</div>
<div mdbModal #frame2="mdbModal" class="modal fade top" id="frameModalTop" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true" (closed)="onModalClosed($event)">
<div class="modal-dialog modal-sm modal-notify modal-danger" role="document">
<!--Content-->
<div class="modal-content text-center">
<!--Header-->
<div class="modal-header d-flex justify-content-center">
<p class="heading">Confirm destroy this provision?</p>
</div>
<!--Body-->
<div class="modal-body">
<mdb-icon fas icon="times-circle" size="4x" class="animated rotateIn"></mdb-icon>
</div>
<!--Footer-->
<div class="modal-footer justify-content-center">
<a type="button" mdbBtn color="danger" outline="true" class="waves-effect" mdbWavesEffect (click)="destroyConfirmed(frame2);">Yes</a>
<a type="button" mdbBtn color="danger" class="waves-effect" mdbWavesEffect data-dismiss="modal" (click)="frame2.hide()">No</a>
</div>
</div>
<!--/.Content-->
</div>
</div>

View File

@@ -0,0 +1,22 @@
.modal-popover {
position: fixed;
z-index: 10000;
width: 100%;
height: 100%;
top: 0px;
bottom: 0px;
left: 0px;
background: rgba(0,0,0,0.5);
.lui-popover {
max-width: 700px;
}
.popover-content {
position: relative;
top: 10%;
}
.lui-popover__body {
font-size: 14px;
max-height: 600px;
overflow: auto;
}
}

View File

@@ -1,5 +1,8 @@
import { Component, OnInit, ElementRef, HostListener, AfterViewInit, ViewChild, ChangeDetectorRef, Input } from '@angular/core'; import { Component, OnInit, ElementRef, HostListener, AfterViewInit, ViewChild, ChangeDetectorRef, Input } from '@angular/core';
import { MdbTableDirective, MdbTablePaginationComponent } from 'angular-bootstrap-md'; import { MdbTableDirective, MdbTablePaginationComponent, MDBModalService } from 'angular-bootstrap-md';
import { ProvisionsService } from '../services/provisions.service';
import { AlertService } from '../services/alert.service';
import { ModalInfoComponent } from '../alert/modalinfo.component';
@Component({ @Component({
selector: 'table-admin', selector: 'table-admin',
templateUrl: './table-admin.component.html', templateUrl: './table-admin.component.html',
@@ -11,33 +14,43 @@ export class TableAdminComponent implements OnInit, AfterViewInit {
@ViewChild('row', { static: true }) row: ElementRef; @ViewChild('row', { static: true }) row: ElementRef;
@Input() elements; @Input() elements;
@Input() headElements; headElements = ['', 'ProvisionID', 'Prov. Date', 'User', 'Scenario', 'Status VMs', 'Status', 'DestroyID', 'Dest. Date', 'Status', ''];
searchText: string = ''; searchText: string = '';
previous: string; previous: string;
pagingIsDisabled: Boolean = true; pagingIsDisabled: Boolean = true;
selectedprov: any = null;
showInfo: boolean = false;
logShow: boolean = false;
logstype: String = 'provision';
maxVisibleItems: number = 15; maxVisibleItems: number = 15;
constructor(private cdRef: ChangeDetectorRef) {} constructor(private modalService: MDBModalService, private _alertService: AlertService, private cdRef: ChangeDetectorRef, private _provisionsService: ProvisionsService) {}
@HostListener('input') oninput() { @HostListener('input') oninput() {
this.mdbTablePagination.searchText = this.searchText; this.mdbTablePagination.searchText = this.searchText;
} }
ngOnInit() { _init(): void {
this.mdbTable.setDataSource(this.elements); this.mdbTable.setDataSource(this.elements);
this.elements = this.mdbTable.getDataSource(); this.elements = this.mdbTable.getDataSource();
this.previous = this.mdbTable.getDataSource(); this.previous = this.mdbTable.getDataSource();
} }
ngOnInit() {
this._init();
}
ngAfterViewInit() { ngAfterViewInit() {
if ( this.mdbTablePagination ) {
this.mdbTablePagination.setMaxVisibleItemsNumberTo(this.maxVisibleItems); this.mdbTablePagination.setMaxVisibleItemsNumberTo(this.maxVisibleItems);
this.mdbTablePagination.calculateFirstItemIndex(); this.mdbTablePagination.calculateFirstItemIndex();
this.mdbTablePagination.calculateLastItemIndex(); this.mdbTablePagination.calculateLastItemIndex();
this.cdRef.detectChanges(); this.cdRef.detectChanges();
} }
}
searchItems() { searchItems() {
const prev = this.mdbTable.getDataSource(); const prev = this.mdbTable.getDataSource();
@@ -60,4 +73,101 @@ export class TableAdminComponent implements OnInit, AfterViewInit {
this.mdbTablePagination.calculateLastItemIndex(); this.mdbTablePagination.calculateLastItemIndex();
}); });
} }
showResources($event, provision): void {
$event.preventDefault();
$event.stopPropagation();
this.selectedprov = provision;
console.log("this.selectedprov", this.selectedprov);
this.showInfo = true;
}
showLogs($event, provision, type): void {
$event.preventDefault();
$event.stopPropagation();
this.logstype = type;
this.logShow = false;
this.selectedprov = provision;
this.logShow = true;
}
onLogsClose(): void {
this.selectedprov = null;
this.logShow = false;
}
startVms(provision): void {
var sub = this._provisionsService.startVms(provision._id.toString(), provision.user._id).subscribe( res => {
provision.startVms = res.startVms;
sub.unsubscribe();
this._alertService.showAlert({
type: 'alert-primary',
text: `Starting all VMs for scenario '${provision.scenario}'...`
});
})
}
stopVms(provision): void {
var sub = this._provisionsService.stopVms(provision._id.toString(), provision.user._id).subscribe( res => {
provision.startVms = res.startVms;
sub.unsubscribe();
this._alertService.showAlert({
type: 'alert-primary',
text: `Starting all VMs for scenario '${provision.scenario}'...`
});
})
}
onModalClosed(event: any) {
this._provisionsService.setSelectedProv(null);
}
setModal(provision, frame) : void {
frame.show();
this._provisionsService.setSelectedProv(provision);
}
delConfirmed(frame) : void {
var selectedprov = this._provisionsService.getSelectedProv();
this._provisionsService.delProvision(selectedprov._id, selectedprov.user._id).subscribe( res => {
this.elements = this.elements.filter(e=>{
return e._id.toString() !== selectedprov._id.toString();
});
this._init();
this._alertService.showAlert({
type: 'alert-primary',
text: `Provision entry '${selectedprov.scenario}' was deleted`
});
});
frame.hide();
}
destroyConfirmed(frame) : void{
var selectedprov = this._provisionsService.getSelectedProv();
this._provisionsService.newDestroy({"id": selectedprov._id.toString()}, selectedprov.user._id).subscribe( provUpdated => {
this._alertService.showAlert({
type: 'alert-primary',
text: `Provision of scenario '${selectedprov.scenario}' is going to be destroyed`
});
selectedprov.destroy = provUpdated.destroy;
})
frame.hide();
}
openInfoModal(provision) {
var modalRef = this.modalService.show(ModalInfoComponent, {
backdrop: true,
keyboard: true,
focus: true,
show: false,
ignoreBackdropClick: false,
class: 'modal-lg',
containerClass: '',
animated: true,
data: {
info: provision
}
} );
}
} }

View File

@@ -1,6 +1,8 @@
import { MdbTablePaginationComponent, MdbTableDirective } from 'angular-bootstrap-md'; import { MdbTablePaginationComponent, MdbTableDirective } from 'angular-bootstrap-md';
import { Component, OnInit, ViewChild, HostListener, AfterViewInit, ChangeDetectorRef, Input } from '@angular/core'; import { Component, OnInit, ViewChild, HostListener, AfterViewInit, ChangeDetectorRef, Input } from '@angular/core';
import { AuthGuard } from '../services/auth.guard';
import { UsersService } from '../services/users.service';
@Component({ @Component({
selector: 'table-users', selector: 'table-users',
@@ -14,20 +16,30 @@ export class TableUsersComponent implements OnInit, AfterViewInit {
searchText: string = ''; searchText: string = '';
maxVisibleItems: number = 8; maxVisibleItems: number = 8;
currentUser;
@Input() elements; @Input() elements;
@HostListener('input') oninput() { @HostListener('input') oninput() {
this.mdbTablePagination.searchText = this.searchText; this.mdbTablePagination.searchText = this.searchText;
} }
constructor(private cdRef: ChangeDetectorRef) { } constructor(private cdRef: ChangeDetectorRef, private _usersService: UsersService, private _auth: AuthGuard) {
this._auth.getUserInfo().subscribe( value => {
this.currentUser = value;
});
}
ngOnInit() { private _initElements(): void {
this.mdbTable.setDataSource(this.elements); this.mdbTable.setDataSource(this.elements);
this.elements = this.mdbTable.getDataSource(); this.elements = this.mdbTable.getDataSource();
this.previous = this.mdbTable.getDataSource(); this.previous = this.mdbTable.getDataSource();
} }
ngOnInit() {
this._initElements();
}
ngAfterViewInit() { ngAfterViewInit() {
this.mdbTablePagination.setMaxVisibleItemsNumberTo(this.maxVisibleItems); this.mdbTablePagination.setMaxVisibleItemsNumberTo(this.maxVisibleItems);
@@ -57,4 +69,25 @@ export class TableUsersComponent implements OnInit, AfterViewInit {
this.mdbTablePagination.calculateLastItemIndex(); this.mdbTablePagination.calculateLastItemIndex();
}); });
} }
setAdmin(user) : void {
this._usersService.updateUser(user._id, {"role": "admin"}).subscribe( res1 => {
console.log("Updated", res1);
this._usersService.getUsers().subscribe( res => {
this.elements = res;
this._initElements();
});
})
}
removeAdmin(user) : void {
this._usersService.updateUser(user._id, {"role": "user"}).subscribe( res1 => {
console.log("Updated", res1);
this._usersService.getUsers().subscribe( res => {
this.elements = res;
this._initElements();
});
})
}
} }

View File

@@ -10,8 +10,8 @@
<div class="navbar-expand ml-auto navbar-nav"> <div class="navbar-expand ml-auto navbar-nav">
<div class="navbar-nav"> <div class="navbar-nav">
<a *ngIf="user" class="nav-item nav-link " style="font-weight: bold;">Hello {{user.displayName}}</a> <a *ngIf="user" class="nav-item nav-link " style="font-weight: bold;">Hello {{user.displayName}}</a>
<a *ngIf="!user" class="nav-item nav-link" style="color: #009845 !important;" href="/login">Login <i class="fa fa-sign-in"></i></a> <a *ngIf="!user" class="nav-item nav-link" style="color: #009845 !important;" href="/login">Login <mdb-icon fas icon="sign-in-alt"></mdb-icon></a>
<a *ngIf="user" href (click)="logout($event)" class="nav-item nav-link" style="color: #009845 !important;">Logout <i class="fa fa-sign-out"></i></a> <a *ngIf="user" href (click)="logout($event)" class="nav-item nav-link" style="color: #009845 !important;">Logout <mdb-icon fas icon="sign-out-alt"></mdb-icon></a>
</div> </div>
</div> </div>
</nav> </nav>

View File

@@ -23,4 +23,4 @@ body {
font-weight: bold; font-weight: bold;
} }
@import "assets/album.css"; //@import "assets/album.css";