42 Commits

Author SHA1 Message Date
Manuel Romero
6283dddb22 Share 2021-11-26 11:08:45 +01:00
Manuel Romero
c3b8bd119f more stuff for sharing provisions 2021-11-25 18:02:37 +01:00
Manuel Romero
2e31fe279c more stuff 2021-11-25 14:12:29 +01:00
Manuel Romero
899fd2fda7 share provisions with others 2021-11-25 12:37:42 +01:00
Manuel Romero
dc116c9f7f fix destroy 2021-11-24 12:06:32 +01:00
Manuel Romero
455cc3e75d fix 2021-11-24 10:34:07 +01:00
Manuel Romero
16f574f744 child being destroyed check 2021-11-24 09:53:28 +01:00
Manuel Romero
1486e2fd21 check if destroyed or destroying 2021-11-24 08:35:06 +01:00
Manuel Romero
c0349f440e fix open in Azure 2021-11-19 17:47:55 +01:00
Manuel Romero
1a99589c8b Del temp apikeys for QDI 2021-11-10 10:01:18 +01:00
Manuel Romero
b54a590f51 fix 2021-11-09 16:10:50 +01:00
Manuel Romero
eafd78f91c fix 2021-11-09 16:04:28 +01:00
Manuel Romero
243c65c64b filter all users 2021-11-09 15:36:35 +01:00
Manuel Romero
672ba4194a revert 2021-11-09 15:19:11 +01:00
Manuel Romero
9f539bd96a prep of apikey for QDI 2021-11-09 14:56:10 +01:00
Manuel Romero
55b9f0c032 improve admin prov query 2021-11-09 11:46:53 +01:00
Manuel Romero
00dcb33872 fix alerts 2021-11-08 18:08:55 +01:00
Manuel Romero
429838cfc5 new version 2021-11-08 14:13:10 +01:00
Manuel Romero
3a2d6eb0b6 fix ui 2021-11-08 14:12:06 +01:00
Manuel Romero
4290406c19 fix 2021-11-08 13:35:02 +01:00
Manuel Romero
9057f58342 ui fixes 2021-11-08 13:20:30 +01:00
Manuel Romero
6d03a4f6ed Fixes ui 2021-11-08 13:02:35 +01:00
Manuel Romero
5d914c890f new laf 2021-11-05 18:03:54 +01:00
Manuel Romero
b1286a1b2b new look and feel 2021-11-05 17:59:57 +01:00
Manuel Romero
a5b5f2a8ca fix 2021-11-05 13:53:22 +01:00
Manuel Romero
8bb7856102 destroy also children provisions 2021-11-05 13:08:00 +01:00
Manuel Romero
8280b32872 user_oid 2021-10-28 11:27:55 +02:00
Manuel Romero
39ab3fe6dd Merge branch 'dev' 2021-10-26 17:33:08 +02:00
Manuel Romero
fbe30b740c synapse user 2021-10-26 14:04:16 +02:00
Manuel Romero
ab5ab80765 Test watchtower 2021-10-06 17:48:02 +02:00
Manuel Romero
180a20c8d6 fix 2021-10-05 10:02:39 +02:00
Manuel Romero
91d7017b03 vm1 stuff 2021-10-04 17:13:08 +02:00
Manuel Romero
5b6cb73b09 vm1 stuff 2021-10-04 16:54:48 +02:00
Manuel Romero
2003a039f2 new version available 2021-08-03 16:19:42 +02:00
Manuel Romero
96b1302520 fix 2021-07-13 10:38:03 +02:00
Manuel Romero
891acb714e Go to user 2021-07-13 10:21:33 +02:00
Manuel Romero
7cf4e7b5e7 adding try catch cli 2021-07-12 14:12:09 +02:00
Manuel Romero
0c78992958 change to promise 2021-07-12 13:02:36 +02:00
Manuel Romero
ed44eb7fef change to promise 2021-07-12 12:59:53 +02:00
Manuel Romero
8ed6612686 fix3 2021-07-09 13:33:58 +02:00
Manuel Romero
793b8f8739 fix2 2021-07-09 13:15:30 +02:00
Manuel Romero
a9b3b57414 fix 2021-07-09 12:48:25 +02:00
59 changed files with 1336 additions and 221 deletions

View File

@@ -6,8 +6,8 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/favicon.ico">
<link rel="stylesheet" href="styles.d596c43fc1f81eecfae1.css"></head>
<link rel="stylesheet" href="styles.921aafa95031aeb74181.css"></head>
<body>
<app-root></app-root>
<script src="runtime.689ba4fd6cadb82c1ac2.js" defer></script><script src="polyfills-es5.2f1b30b563fe6f309b2d.js" nomodule defer></script><script src="polyfills.60117177d3b4f4827ace.js" defer></script><script src="scripts.73c34722d75b092f2620.js" defer></script><script src="main.ece894ac50d4edc475b5.js" defer></script></body>
<script src="runtime.689ba4fd6cadb82c1ac2.js" defer></script><script src="polyfills-es5.feb8e3dfdd8e1cace860.js" nomodule defer></script><script src="polyfills.60117177d3b4f4827ace.js" defer></script><script src="scripts.73c34722d75b092f2620.js" defer></script><script src="main.eca58c33a1ad840ee769.js" defer></script></body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-app",
"version": "1.3.0",
"version": "1.3.1",
"scripts": {
"start": "node -r esm server/server.js",
"start:dev": "nodemon -r esm server/server.js",

View File

@@ -62,7 +62,8 @@ async function init(type) {
"isDestroyed":false,
"isDeleted": false,
"statusVms": "Stopped",
"vmImage": {"$exists": true}
"vmImage": {"$exists": true},
"vmImage.vm1": { "$exists": true }
};
if ( type === "warning" ) {
filter.pendingNextAction = {$ne: "destroy"};

View File

@@ -62,7 +62,8 @@ async function init(type) {
"isDestroyed": false,
"isDeleted": false,
"statusVms": "Running",
"vmImage": { "$exists": true }
"vmImage": { "$exists": true },
"vmImage.vm1": { "$exists": true }
};
if ( type === "warning" ) {
filter.pendingNextAction = { $ne: "stopVms" };

View File

@@ -20,13 +20,69 @@ function _getRegion(provision) {
return region;
}
async function deallocate(provision, isSendEmailAfter) {
function deallocate(provision, isSendEmailAfter) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
return new Promise((resolve, reject) => {
var ec2 = new AWS.EC2({apiVersion: '2016-11-15', region: region});
var params = {
DryRun: false,
Filters: [
{
Name: 'tag:Name',
Values: [ `fort-${provision._id}` ]
},
]
};
ec2.describeInstances(params, async function (err, data) {
if (err) {
console.log("AWSCLI# ERROR describing EC2s: "+rgName, err.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
reject(err);
} else if (data && data.Reservations && data.Reservations.length) {
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
console.log("AWSCLI# Stopping Ec2s ids...", ec2Ids);
params = {
DryRun: false,
InstanceIds: ec2Ids
};
ec2.stopInstances(params, async function(err1, data) {
if (err1) {
console.log("AWSCLI# ERROR stopping EC2s: "+rgName, err1.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Running" });
reject(err1);
} else {
console.log("AWSCLI# Ec2s stopped!");
await utils.afterStopVms( provision, isSendEmailAfter );
resolve(data);
}
});
} else {
console.log("AWSCLI# No Ec2 Instances found: "+rgName);
resolve(data);
}
});
});
}
function start(provision) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
return new Promise((resolve, reject) => {
var ec2 = new AWS.EC2({apiVersion: '2016-11-15', region: region});
var params = {
DryRun: false,
@@ -41,66 +97,32 @@ async function deallocate(provision, isSendEmailAfter) {
ec2.describeInstances(params, async function (err, data) {
if (err) {
console.log("AWSCLI# ERROR describing EC2s: "+rgName, err.stack);
} else {
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
reject(err);
} else if (data && data.Reservations && data.Reservations.length) {
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
console.log("AWSCLI# Stopping EC2s ids", ec2Ids);
console.log("AWSCLI# Starting Ec2s ids...", ec2Ids);
params = {
DryRun: false,
InstanceIds: ec2Ids
};
ec2.stopInstances(params, async function(err, data) {
if (err) {
console.log("AWSCLI# ERROR stopping EC2s: "+rgName, err.stack);
ec2.startInstances(params, async function(err1, data) {
if (err1) {
console.log("AWSCLI# ERROR starting EC2s: "+rgName, err1.stack);
await db.provision.update(provision._id.toString(), {"statusVms": "Stopped" });
reject(err1);
} else {
console.log("AWSCLI# EC2s stopped!");
utils.afterStopVms( provision, isSendEmailAfter );
console.log("AWSCLI# Ec2s started!");
await utils.afterStartVms( provision );
resolve(data);
}
});
}
});
}
async function start(provision) {
let rgName = _getRgName(provision);
let region = _getRegion(provision);
console.log("AWSCLI# Stopping EC2s for resource group: "+rgName);
var ec2 = new AWS.EC2({apiVersion: '2016-11-15', region: region});
var params = {
DryRun: false,
Filters: [
{
Name: 'tag:Name',
Values: [ `fort-${provision._id}` ]
},
]
};
ec2.describeInstances(params, async function (err, data) {
if (err) {
console.log("AWSCLI# ERROR describing EC2s: "+rgName, err.stack);
} else {
var ec2Ids = data.Reservations[0].Instances.map(ec2=> ec2.InstanceId);
console.log("AWSCLI# Starting EC2s ids", ec2Ids);
params = {
DryRun: false,
InstanceIds: ec2Ids
};
ec2.startInstances(params, async function(err, data) {
if (err) {
console.log("AWSCLI# ERROR starting EC2s: "+rgName, err.stack);
} else if (data) {
console.log("AWSCLI# EC2s started!");
utils.afterStartVms( provision );
console.log("AWSCLI# No Ec2 Instances found: "+rgName);
resolve(data);
}
});
}
});
}

View File

@@ -47,13 +47,13 @@ async function deallocate(provision, isSendEmailAfter ) {
let rgName = _getRgName(provision);
console.log("AzureCLI# Deallocating VMs for resource group: "+rgName);
try {
var computeClient = await _getClient(provision.scenario);
let finalResult = await computeClient.virtualMachines.list(rgName);
if ( finalResult && finalResult.length > 0 ) {
db.provision.update(provision._id, {"statusVms": "Stopping"});
}
try {
await asyncForEach(finalResult, async function(vm) {
await computeClient.virtualMachines.deallocate(rgName, vm.name);
});
@@ -72,6 +72,8 @@ async function start(provision){
let rgName = _getRgName(provision);
console.log("AzureCLI# Starting VMs for resource group: "+rgName);
try {
var computeClient = await _getClient(provision.scenario);
let finalResult = await computeClient.virtualMachines.list(rgName);
@@ -79,12 +81,12 @@ async function start(provision){
db.provision.update(provision._id, {"statusVms": "Starting"});
}
try {
await asyncForEach(finalResult, async function(vm) {
await computeClient.virtualMachines.start(rgName, vm.name);
});
await utils.afterStartVms( provision );
console.log("AzureCLI# All VMs RUNNING for resource group: "+rgName);
} catch ( error ) {
console.log("AzureCLI# ERROR starting VMs: "+rgName, error);

View File

@@ -4,33 +4,42 @@ const db = require("./mongo");
async function deallocate(provId, isSendEmailAfter ) {
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
azurecli.deallocate(provision, isSendEmailAfter);
if (provision.scenario === 'azqmi-fort'){
awscli.stop(provision, isSendEmailAfter);
return awscli.deallocate(provision, isSendEmailAfter);
} else {
azurecli.deallocate(provision, isSendEmailAfter);
return azurecli.deallocate(provision, isSendEmailAfter);
}
} catch (err) {
console.log("CLI# ERROR stopping VMs", err);
}
}
async function start(provId){
try {
let provision = await db.provision.getById(provId);
if ( !provision ) return;
if (provision.scenario === 'azqmi-fort'){
awscli.start(provision);
return awscli.start(provision);
} else {
azurecli.start(provision);
return azurecli.start(provision);
}
} catch (err) {
console.log("CLI# ERROR starting VMs", err);
}
}
async function updateVmsTags(provId, tagsEdit) {
azurecli.updateVmsTags(provId, tagsEdit);
try {
return azurecli.updateVmsTags(provId, tagsEdit);
} catch (err) {
console.log("CLI# ERROR updateVmsTags", err);
}
}
module.exports.deallocate = deallocate;

View File

@@ -8,6 +8,9 @@ const schema = new mongoose.Schema({
user: {
type: mongoose.Types.ObjectId, ref: 'User'
},
description: {
type: String
},
created: {
type: Date,
default: Date.now,

View File

@@ -102,6 +102,10 @@ const provisionSchema = new mongoose.Schema({
},
version: {
type: Number
},
parent: {
type: mongoose.Types.ObjectId,
ref: 'Provision'
}
},{
toObject: {virtuals:true},

View File

@@ -0,0 +1,36 @@
const mongoose = require('mongoose')
mongoose.set('useFindAndModify', false);
//mongoose.set('debug', true)
const schema = new mongoose.Schema({
created: {
type: Date,
default: Date.now,
index : true
},
updated: {
type: Date,
default: Date.now
},
user: {
type: mongoose.Types.ObjectId,
ref: 'User',
index: true
},
provision: {
type: mongoose.Types.ObjectId,
ref: 'Provision',
index: true
},
sharedWithUser: {
type: mongoose.Types.ObjectId,
ref: 'User',
index: true
},
canManage: {
type: Boolean,
default: false
}
});
module.exports = mongoose.model('sharedProvision', schema);

View File

@@ -43,6 +43,7 @@ const ApiKey = require('./models/ApiKey');
const Notification = require('./models/Notification');
const Subscription = require('./models/Subscription');
const Event = require('./models/Event');
const SharedProvision = require('./models/SharedProvision');
const getNewCountExtend = function(provision) {
@@ -94,6 +95,9 @@ const getPage = async ( model, filter, page, populates, select ) => {
if ( model === Provision ) {
exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate({path:'destroy', select: "-user -jobId"}).populate({path:'_scenarioDoc', select: "-availableProductVersions -updated -created"}).populate({path: "schedule"}).populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
@@ -161,6 +165,18 @@ const get = async (model, filter, select, skip, limit, populates, reply) => {
exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate({path:'destroy', select: "-user -jobId"}).populate({path:'_scenarioDoc', select: "-availableProductVersions -updated -created"}).populate({path: "schedule"}).populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate({path:'provision', populate: [{
path: 'schedule',
},{
path: '_scenarioDoc',
select: "-availableProductVersions -updated -created"
},{
path: 'destroy',
select: "-user -jobId"
}]}).populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
@@ -193,6 +209,9 @@ const getById = async (model, id, reply) => {
if ( model === Provision ) {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
@@ -212,6 +231,9 @@ const getOne = async (model, filter, reply) => {
if ( model === Provision ) {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
@@ -243,6 +265,9 @@ const update = async (model, id, body, reply) => {
if ( model === Provision ) {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
@@ -267,6 +292,9 @@ const updateMany = async (model, filter, body, reply) => {
if ( model === Provision ) {
exec = exec.populate('user').populate('destroy').populate('_scenarioDoc').populate("schedule").populate('deployOpts');
}
if ( model === SharedProvision ) {
exec = exec.populate('provision').populate({path: 'sharedWithUser', select: 'displayName upn'}).populate({path: 'user', select: 'displayName upn'});
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
@@ -283,8 +311,15 @@ const updateMany = async (model, filter, body, reply) => {
const del = async (model, id, reply) => {
try {
const entity = await model.findByIdAndRemove(id)
return entity;
return await model.findByIdAndRemove(id);
} catch (err) {
throw boom.boomify(err)
}
}
const delMany = async(model, filter, reply) => {
try {
return await model.deleteMany(filter);
} catch (err) {
throw boom.boomify(err)
}
@@ -327,6 +362,9 @@ function _m(model) {
},
count: async (filter, reply) => {
return count(model, filter, reply);
},
delMany: async(filter, reply) => {
return delMany(model, filter, reply);
}
}
}
@@ -343,6 +381,7 @@ module.exports = {
subscription: _m(Subscription),
event: _m(Event),
user: _m(User),
sharedProvision: _m(SharedProvision),
utils: {
getNewTimeRunning: getNewTimeRunning,
getNewCountExtend: getNewCountExtend
@@ -358,7 +397,8 @@ module.exports = {
Notification: Notification,
ApiKey: ApiKey,
Subscription: Subscription,
Event: Event
Event: Event,
SharedProvision: SharedProvision
}
};

View File

@@ -75,7 +75,6 @@ async function afterStartVms( provision ) {
msg += `TotalTimeRunning so far: ${provision.timeRunning} mins`;
await db.provision.update(provision._id.toString(), patch);
console.log("AzureCLI# All VMs RUNNING for resource group: "+rgName);
db.event.add({ user: provision.user._id, provision: provision._id, type: 'vms.start-ondemand', message: msg });

View File

@@ -15,4 +15,5 @@ FROM node:15.12.0-alpine AS production
WORKDIR /app
COPY --from=sources /app ./
CMD ["node", "-r", "esm", "index.js"]

View File

@@ -43,7 +43,7 @@ module.exports = async function(job) {
const dateNow = new Date();
let patch = {
"status": "provisioning",
"statusVms": prov.vmImage? "Running" : "N/A",
"statusVms": (prov.vmImage && prov.vmImage.vm1)? "Running" : "N/A",
"runningFrom": dateNow,
"runningTime": 0,
"countExtend": 0

View File

@@ -81,6 +81,11 @@ function _buildVarsExec( exec, provision, scenario ) {
exec.push(`user_email=${provision.user.upn}`);
}
if ( provision.scenario.indexOf('azqmi-synapse') !== -1 || provision.scenario.indexOf('azqmi-qdi') !== -1 ) {
exec.push('-var');
exec.push(`user_oid=${provision.user.oid}`);
}
if (!provision.vmImage) {
//Old way
if ( provision.vmType ) {

View File

@@ -1,6 +1,6 @@
{
"name": "qmi-cloud-worker",
"version": "1.3.0",
"version": "1.3.1",
"scripts": {
"start": "node -r esm index.js",
"start:dev": "nodemon -r esm index.js",

View File

@@ -44,6 +44,12 @@ module.exports = async function(job){
return { destroy: update, provision: update2 };
}).then(async function(res) {
console.log(`ProcessorDestroy# Provision destroyed!` );
if ( res.provision.scenario === "azqmi-qdi" ) {
let tempApiKey = await db.apiKey.getOne({"description": res.provision._id});
if (tempApiKey && tempApiKey._id){
db.apiKey.del(tempApiKey._id);
}
}
db.event.add({ user: provMongo.user._id, provision: provMongo._id, type: 'provision.destroy-finished' });
return Promise.resolve({"success": true, job: res});
}).catch(function(err) {

View File

@@ -12,6 +12,15 @@ const passport = require('../passport');
* summary: Get all API keys
* tags:
* - admin
* parameters:
* - name: filter
* in: query
* required: false
* type: object
* content:
* application/json:
* schema:
* type: object
* produces:
* - application/json
* responses:
@@ -20,7 +29,8 @@ const passport = require('../passport');
*/
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.apiKey.get();
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
const result = await db.apiKey.get(filter);
return res.json(result);
} catch (error) {
next(error);
@@ -42,6 +52,15 @@ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) =>
* in: path
* type: string
* required: true
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* description:
* type: string
* responses:
* 200:
* description: API KEY
@@ -52,9 +71,8 @@ router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, n
if ( !user ) {
res.status(404).json({"err": "user not found"});
}
var body = {
user: req.params.userId
}
var body = req.body;
body.user = req.params.userId;
const result = await db.apiKey.add(body);
return res.json(result);
} catch (error) {
@@ -64,10 +82,10 @@ router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, n
/**
* @swagger
* /apikeys/{id}:
* /apikeys/{id}/revoke:
* put:
* description: Deactivate API Key
* summary: Deactivate API Key
* description: Revoke API Key
* summary: Revoke API Key
* tags:
* - admin
* produces:
@@ -81,7 +99,7 @@ router.post('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, n
* 200:
* description: API KEY
*/
router.put('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
router.put('/:id/revoke', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.apiKey.update(req.params.id, {"isActive": false});
return res.json(result);
@@ -90,4 +108,32 @@ router.put('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next)
}
});
/**
* @swagger
* /apikeys/{id}:
* delete:
* description: Delete API Key
* summary: Delete API Key
* tags:
* - admin
* produces:
* - application/json
* parameters:
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: API KEY
*/
router.delete('/:id', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.apiKey.del(req.params.id);
return res.json(result);
} catch (error) {
next(error);
}
});
module.exports = router;

View File

@@ -7,6 +7,11 @@ const fs = require('fs-extra');
const cli = require('qmi-cloud-common/cli');
const barracuda = require('qmi-cloud-common/barracuda');
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CONTAINER_QUEUE } from 'qmi-cloud-common/queues';
@@ -18,6 +23,15 @@ import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CO
* summary: Get all users
* tags:
* - admin
* parameters:
* - name: filter
* in: query
* required: false
* type: object
* content:
* application/json:
* schema:
* type: object
* produces:
* - application/json
* responses:
@@ -26,7 +40,8 @@ import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE, STOP_CO
*/
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.user.get();
const filter = req.query.filter? JSON.parse(req.query.filter) : {};
const result = await db.user.get(filter);
return res.json(result);
} catch (error) {
next(error);
@@ -768,6 +783,14 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
return res.status(404).json({"msg": "Not found privision with id "+req.params.id});
}
if ( provision.destroy && provision.destroy.status !== 'error' ) {
console.log(`APIUser# This provision is already destroyed or being destroyed right now: ${provision._id}`);
return res.status(200).json(provision);
}
//
console.log(`APIUser# Queueing destroy provision: ${provision._id}`);
const destroyJob = await db.destroy.add({ "user": userId });
provision = await db.provision.update(req.params.id, {"destroy": destroyJob._id});
const scenarioSource = await db.scenario.getOne({name: provision.scenario});
@@ -780,6 +803,30 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
_scenario: scenarioSource
});
//Check children provisions
let children = await db.provision.get({ "parent": provision._id, "isDestroyed": false, "isDeleted": false });
if (children.results.length > 0 ) {
await asyncForEach(children.results, async function(child) {
if ( !child.destroy || child.destroy.status === 'error' ) {
console.log(`APIUser# Queueing destroy children provision: ${child._id}`);
let destroyJobChild = await db.destroy.add({ "user": userId });
await db.provision.update(child._id, {"destroy": destroyJobChild._id});
let scenarioSourceChild = await db.scenario.getOne({name: child.scenario});
queues[TF_DESTROY_QUEUE].add("tf_destroy_job", {
scenario: child.scenario,
provId: child._id,
user: req.user,
id: destroyJobChild._id,
_scenario: scenarioSourceChild
});
} else {
console.log(`APIUser# This child provision is already destroyed or being destroyed right now: ${child._id}`);
}
})
}
return res.status(200).json(provision);
} catch (error) {
@@ -787,6 +834,218 @@ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIs
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/share/{withUserId}:
* put:
* description: Share provision with another user
* summary: Share provision with another user
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* - name: withUserId
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.put('/:userId/provisions/:id/share/:withUserId', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
if ( req.params.withUserId === userId ) {
return res.status(400).json({"msg": "Can't share with the same user"});
}
let provision = await db.provision.getById(req.params.id);
if (!provision){
return res.status(404).json({"msg": "Not found privision with id "+req.params.id});
}
let found = await db.sharedProvision.getOne({
"user": userId,
"provision": provision._id,
"sharedWithUser": req.params.withUserId
});
if ( !found ) {
found = await db.sharedProvision.add({"user": userId, "provision": provision._id, "sharedWithUser": req.params.withUserId})
}
return res.json(found);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/share/{withUserId}:
* delete:
* description: Stop sharing this provision with another user
* summary: Stop sharing this provision with another user
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* - name: withUserId
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.delete('/:userId/provisions/:id/share/:withUserId', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
if ( req.params.withUserId === userId ) {
return res.status(400).json({"msg": "Can't share with the same user"});
}
const result = await db.sharedProvision.delMany({"user": userId, "provision": req.params.id, "sharedWithUser": req.params.withUserId});
return res.json(result);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/share:
* delete:
* description: Stop sharing this provision with everybody
* summary: Stop sharing this provision with everybody
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.delete('/:userId/provisions/:id/share', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
if ( req.params.withUserId === userId ) {
return res.status(400).json({"msg": "Can't share with the same user"});
}
const result = await db.sharedProvision.delMany({"user": userId, "provision": req.params.id});
return res.json(result);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/provisions/{id}/share:
* get:
* description: Get shares of this provision
* summary: Get shares of this provision
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* - name: id
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.get('/:userId/provisions/:id/share', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
const result = await db.sharedProvision.get({"user": userId, "provision": req.params.id});
return res.json(result);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/sharedprovisions:
* get:
* description: Get provision shared with me
* summary: Get provision shared with me
* produces:
* - application/json
* parameters:
* - name: userId
* in: path
* type: string
* required: true
* responses:
* 200:
* description: Provision
* 404:
* description: Not found
*
*/
router.get('/:userId/sharedprovisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => {
try {
const userId = req.params.userId === 'me'? req.user._id : req.params.userId;
let result = await db.sharedProvision.get({"sharedWithUser": userId});
return res.json(result);
} catch (error) {
return res.status(error.output.statusCode).json({"err":error});
}
});
/**
* @swagger
* /users/{userId}/provisions:

View File

@@ -39,5 +39,3 @@
<!--<h1>Scenarios</h1>-->
<table-vmtypes></table-vmtypes>
</div>
<qmi-alert></qmi-alert>

View File

@@ -7,11 +7,15 @@ import { HomeComponent } from './home/home.component';
import { AuthGuard } from './services/auth.guard';
import { FaqComponent } from './faq/faq.component';
import { UserDashboardComponent } from './user/user-dashboard.component';
import { ScenariosSectionComponent } from './scenarios/scenarios-section.component';
import { ProvisionsSharedComponent } from './provisions/provisions-shared.component';
const routes: Routes = [
{ path: 'home', component: HomeComponent},
{ path: 'faq', component: FaqComponent},
{ path: 'scenarios', component: ScenariosSectionComponent, canActivate: [AuthGuard]},
{ path: 'provisions', component: ProvisionsComponent, canActivate: [AuthGuard]},
{ path: 'sharedprovision', component: ProvisionsSharedComponent, canActivate: [AuthGuard]},
{ path: 'admin', component: AdminComponent, canActivate: [AuthGuard]},
{ path: 'admin/:tab', component: AdminComponent, canActivate: [AuthGuard]},
{ path: 'stats', component: StatsComponent, canActivate: [AuthGuard]},

View File

@@ -6,6 +6,7 @@ import { AppComponent } from './app.component';
import { UiModule } from './ui/ui.module';
import { HomeComponent } from './home/home.component';
import { ProvisionsComponent } from './provisions/provisions.component';
import { ProvisionsSharedComponent } from './provisions/provisions-shared.component';
import { AuthGuard } from './services/auth.guard';
import { ProvisionsService } from './services/provisions.service';
import { ScenariosService } from './services/scenarios.service';
@@ -16,6 +17,7 @@ import { MarkdownModule, MarkedOptions, MarkedRenderer } from 'ngx-markdown';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { LogsComponent } from './logs/logs.component';
import { ScenariosComponent } from './scenarios/scenarios.component';
import { ScenariosSectionComponent } from './scenarios/scenarios-section.component';
import { AdminComponent } from './admin/admin.component';
import { PopoverconfirmComponent } from './popoverconfirm/popoverconfirm.component';
import { FormsModule } from '@angular/forms';
@@ -33,6 +35,7 @@ import { FilterPipe } from './filter.pipe';
import { FaqComponent } from './faq/faq.component';
import { NewProvisionConfirmComponent } from './modals/new-provision.component';
import { ScenarioModalComponent } from './modals/edit-scenario.component';
import { ShareModalComponent } from './modals/share.component';
import { SubscriptionModalComponent } from './modals/edit-subscription.component';
import { TableSubsComponent } from './tables/table-subscriptions.component';
import { TableVmTypesComponent } from './tables/table-vmtypes.component';
@@ -66,8 +69,10 @@ export function markedOptions(): MarkedOptions {
AppComponent,
HomeComponent,
ProvisionsComponent,
ProvisionsSharedComponent,
LogsComponent,
ScenariosComponent,
ScenariosSectionComponent,
AdminComponent,
PopoverconfirmComponent,
TableProvisionsAdminComponent,
@@ -82,6 +87,7 @@ export function markedOptions(): MarkedOptions {
TableNotificationsComponent,
ScenarioModalComponent,
SubscriptionModalComponent,
ShareModalComponent,
TableSubsComponent,
TableApiKeysComponent,
ApikeyModalComponent,

View File

@@ -1,14 +1,16 @@
<div class="qmialert" *ngIf="alert">
<div #qmialert class="alert alert-dismissible fade show" [ngClass]="alert.type" role="alert">
<h5 class="alert-heading"><span>{{alert.type === 'alert-primary'? 'Done!' : 'Oops'}}</span></h5>
<h4 class="alert-heading">
<span *ngIf="alert.type === 'alert-dark'" class="fa fa-info-circle"></span>
<span *ngIf="alert.type === 'alert-danger'" class="fa fa-warning"></span>
<span>&nbsp;&nbsp;{{alert.type === 'alert-dark'? 'Done!' : 'Oops!'}}</span>
</h4>
<button type="button" class="close" aria-label="Close" (click)="closeAlert()">
<span aria-hidden="true">&times;</span>
</button>
<hr>
<h6 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>
<span [innerHTML]="alert.text"></span>
</h6>
</div>
</div>

View File

@@ -1,9 +1,10 @@
.qmialert {
position: fixed;
bottom: 0px;
left: 0px;
bottom: 10px;
left: 50%;
padding: 0px;
width: 100%;
width: 90%;
z-index: 1;
text-align: center;
transform: translate(-50%, -50%);
}

View File

@@ -13,6 +13,7 @@ export class AlertComponent implements OnInit, OnDestroy {
subscription: Subscription;
alert : any = null;
constructor(private _alertService: AlertService) {}
ngOnInit() {

View File

@@ -14,11 +14,14 @@
<option *ngFor="let item of users" [value]="item._id">{{item.displayName}}</option>
</select>
</section>
<div class="md-form">
<input mdbInput type="text" name="text" [(ngModel)]="sendData.description" id="description" class="form-control">
<label for="description" class="">Description *:</label>
</div>
</div>
</div>
<div class="modal-footer d-flex justify-content-center">
<button style="margin-right: 100px;" *ngIf="sendData._id" mdbBtn color="danger" outline="true" class="waves-light" size="sm" mdbWavesEffect (click)="delete();">Delete</button>
<button mdbBtn color="dark-green" size="sm" outline="true" class="waves-effect" mdbWavesEffect (click)="modalRef.hide()">Cancel</button>
<button mdbBtn color="dark-green" class="waves-light" size="sm" mdbWavesEffect (click)="confirm();">Save</button>
</div>

View File

@@ -43,7 +43,7 @@ export class ApikeyModalComponent implements OnInit, OnDestroy {
this.sendData.user = this.selectedUser;
console.log("sendData", this.sendData);
this._usersService.addApikey(this.sendData.user).subscribe( res=> {
this._usersService.addApikey(this.selectedUser, this.sendData).subscribe( res=> {
console.log("done", res);
this.action.next("DONE!!!");
this.modalRef.hide();
@@ -51,12 +51,4 @@ export class ApikeyModalComponent implements OnInit, OnDestroy {
}
delete() : void {
this._usersService.delApikey(this.sendData._id).subscribe( res=> {
console.log("done", res);
this.action.next("DONE!!!");
this.modalRef.hide();
});
}
}

View File

@@ -100,7 +100,8 @@ export class ProvisionModalComponent implements OnInit, OnDestroy {
console.log("SendData", this.sendData);
this._provisionsService.updateProvisionUser(this.provision._id, this.provision.user._id, this.sendData ).subscribe( res=>{
this.action.next(res);
let user = this.users.filter( u => u._id === this.selectedUser);
this.action.next({"user": user[0]});
this.modalRef.hide();
});
}

View File

@@ -55,7 +55,7 @@
<div class="mydata">{{item.value}}</div>
</div>
<h5 *ngIf="events" class="info-subtitle">Events</h5>
<table *ngIf="events" mdbTable stickyHeader="true" hover="true" class="table table-sm">
<table *ngIf="events" class="table table-sm">
<tbody>
<tr *ngFor="let item of events; let i = index">
<td style="min-width:120px;">{{item.created | date: 'MMM dd, yyyy - H:mm'}}</td>

View File

@@ -0,0 +1,43 @@
<!--Content-->
<div class="modal-content">
<div class="modal-header text-center">
<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 font-weight-bold">Share provision with others</h4>
</div>
<div class="modal-body" style="max-height: 650px; overflow: auto;">
<div *ngIf="sharedUsers && sharedUsers.length > 0">
<label>Sharing this provision with: </label>
<table>
<tr *ngFor="let withUser of sharedUsers; let i = index">
<td>{{withUser.displayName}}</td>
<td>
<button style="margin-left: 10px;" class="lui-button lui-text-danger" (click)="stopShare(withUser._id);">
<span class="lui-icon lui-icon--bin" aria-hidden="true"></span>
</button>
</td>
</tr>
</table>
</div>
<div>
<section style="padding: 20px 0px;">
<label><mdb-icon fas icon="user"></mdb-icon> Share with:</label>
<select style="width: 250px;" class="browser-default custom-select custom-select-sm" [(ngModel)]="selectedUser">
<option *ngFor="let item of users" [value]="item._id">{{item.displayName}}</option>
</select>
<button class="lui-button text-success" (click)="share();">
Share
</button>
</section>
</div>
</div>
<div class="modal-footer d-flex justify-content-center">
<button mdbBtn color="dark-green" size="sm" outline="true" class="waves-effect" mdbWavesEffect (click)="modalRef.hide()">Close</button>
</div>
</div>
<!--/.Content-->

View File

@@ -0,0 +1,19 @@
label {
position: relative;
font-weight: bold;
}
.md-form {
margin-top: 0px;
margin-bottom: 0px;
}
.utc {
padding-left: 20px;
padding-top: 5px;
font-size: 12px
}
.disabled {
opacity: 0.2;
}

View File

@@ -0,0 +1,64 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { MDBModalRef } from 'angular-bootstrap-md';
import { Subject } from 'rxjs';
import { UsersService } from '../services/users.service';
import { ProvisionsService } from '../services/provisions.service';
import { AuthGuard } from '../services/auth.guard';
@Component({
selector: 'qmi-share-provision',
templateUrl: './share.component.html',
styleUrls: ['./share.component.scss']
})
export class ShareModalComponent implements OnInit, OnDestroy {
provision;
action: Subject<any> = new Subject();
users;
sharedUsers;
selectedUser;
constructor( private _auth: AuthGuard, public modalRef: MDBModalRef, private _usersService: UsersService, private _provisionsService: ProvisionsService ) {
}
refresh() : void {
this._provisionsService.getSharesForProvision(this.provision.user._id, this.provision._id).subscribe(res=> {
this.sharedUsers = res;
});
}
ngOnInit() {
this._usersService.getUsers(true).subscribe(res=> {
this.users = res.results;
});
this.refresh();
}
ngOnDestroy() {
}
share() : void {
//this.modalRef.hide();
this._provisionsService.shareProvision(this.provision.user._id, this.provision._id, this.selectedUser ).subscribe( res=>{
this.refresh();
//this.modalRef.hide();
});
}
stopShare(withUserId) : void {
//this.modalRef.hide();
this._provisionsService.stopShareProvision(this.provision.user._id, this.provision._id, withUserId ).subscribe( res=>{
this.refresh();
//this.modalRef.hide();
});
}
}

View File

@@ -0,0 +1,108 @@
<app-logs [show]="logShow" (onClose)="onLogsClose()" [type]="logstype" [selectedprov]="selectedprov"></app-logs>
<div style="margin-top: 80px; min-height: 650px;">
<h1>Shared Provisions</h1>
<div *ngIf="provisions && provisions.length" class="flexcontainer">
<div *ngFor="let provision of provisions; let i = index" class="box">
<div class="title desc">
<div>Shared by <b>{{provision.owner.displayName}}</b></div>
</div>
<div class="title desc {{provision.status}} {{provision.statusVms}} {{provision.destroy? provision.destroy.status : ''}}">
<div mdbTooltip="{{provision.description}}" class="subtitle">{{provision.description}}</div>
</div>
<div style="border-top: 1px dashed #fff;" class="title {{provision.status}} {{provision.statusVms}} {{provision.destroy? provision.destroy.status : ''}}">
<div class="maintitle"><span *ngIf="provision._scenarioDoc">{{provision._scenarioDoc.title}}</span><span *ngIf="!provision._scenarioDoc">Old scenario (please destroy)</span></div>
<div style="font-size: 14px;">{{provision.scenario}} (v{{provision.scenarioVersion}}) <span *ngIf="provision.scenarioVersion !== provision._scenarioDoc.version" class="text-danger newversion">New version available!</span></div>
</div>
<div class="contentbox">
<div *ngIf="provision.vmImage && provision.vmImage.vm1">
<div *ngIf="provision.statusVms">
Current VMs status:&nbsp;
<span>
<span style="text-transform: capitalize;" class="badge badge-secondary" [ngClass]="{'badge-success': provision.statusVms === 'Running', 'badge-warning': provision.statusVms === 'Stopped' || provision.statusVms === 'Stopping' || provision.statusVms === 'Starting'}">{{provision.statusVms}}</span>
<span *ngIf="provision.statusVms === 'Starting' || provision.statusVms === 'Stopping'" class="spinner-border spinner-border-sm text-warning" role="status">
<span class="sr-only"></span>
</span>
</span>
<span *ngIf="provision.statusVms === 'Stopped'"> (for {{provision.inactiveAsDays}})</span>
</div>
<div *ngIf="provision.statusVms">
<mdb-icon fas icon="play-circle"></mdb-icon> Accumulated Running Time: <span style="font-weight: bold;">{{provision.runningAsDays}}</span>
</div>
<div *ngIf="provision.schedule && !provision.schedule.is24x7">
<span mdbTooltip="{{!provision.schedule.isStartupTimeEnable? 'Scheduled startup deactivated. Re-enable it in the schedule panel.' : ''}}" [ngClass]="{'linethrough': !provision.schedule.isStartupTimeEnable}" *ngIf="provision.statusVms === 'Stopped'">
<mdb-icon far icon="clock"></mdb-icon> VMs startup scheduled at <b>{{provision.schedule.localeStartupTime}}h</b> ({{provision.schedule.localTimezone}})
</span>
<span *ngIf="provision.statusVms === 'Running'"><mdb-icon far icon="clock"></mdb-icon> VMs shutdown scheduled at <b>{{provision.schedule.localeShutdownTime}}h</b> ({{provision.schedule.localTimezone}})</span>
</div>
<div *ngIf="provision.statusVms === 'Running' || provision.statusVms === 'Stopping'">
<div *ngIf="!provision.schedule || provision.schedule.is24x7">
VMs will run <b>nonstop</b> for <b>{{provision.autoshutdownAsDays}}</b><span *ngIf="provision.status === 'provisioned'"> - <a mdbTooltip="Renew 24x7 period" href (click)="openConfirmExtendModal($event, provision)"><mdb-icon fas icon="redo-alt"></mdb-icon></a></span>
</div>
</div>
<!--<div *ngIf="provision.statusVms === 'Running' || provision.statusVms === 'Stopping'">
<mdb-icon *ngIf="provision.schedule && !provision.schedule.is24x7" fas icon="stopwatch"></mdb-icon>
<span *ngIf="provision.schedule && !provision.schedule.is24x7"> Scheduled shutdown in ~ <span style="font-weight: bold;">{{provision.autoshutdownAsHours}}</span></span>
</div>-->
<div *ngIf="provision.statusVms === 'Stopped' || provision.statusVms === 'Starting'">
<span *ngIf="!provision.schedule || provision.schedule.is24x7 || (provision.schedule && !provision.schedule.is24x7 && !provision.schedule.isStartupTimeEnable)">
<mdb-icon fas icon="times-circle"></mdb-icon> Scheduled auto-destroy in ~ <b>{{provision.autoDestroyAsDays}}</b>
</span>
</div>
<hr>
</div>
<div>
Provision ({{provision.created | date: 'MMM dd, yyyy'}})
-
<span>
<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 === 'error_init' || provision.status === 'error_plan' }">{{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>
</span>
-
<a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">Logs</a>
</div>
<div *ngIf="provision.destroy">
Destroy ({{provision.destroy.created | date: 'MMM dd'}})
-
<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.destroy.status === 'destroying'" class="spinner-border spinner-border-sm text-warning" role="status">
<span class="sr-only"></span>
</span>
</span>
-
<a *ngIf="provision.destroy._id" href (click)="showLogs($event, provision, 'destroy')" class="lui-text-info">Logs</a>
</div>
</div>
<div class="buttons">
<button mdbTooltip="Provision information" style="margin-right: 5px;" class="lui-button" (click)="openModal(provision)">
<span class="lui-icon lui-icon--info" aria-hidden="true"></span>
</button>
<button style="margin-right: 5px;" mdbTooltip="Edit running schedule" *ngIf="provision.canManage && provision.vmImage && provision.vmImage.vm1 && provision._scenarioDoc.isDivvyEnabled && provision.status === 'provisioned'" (click)="openScheduleModal(provision)" class="lui-button">
<mdb-icon far icon="clock"></mdb-icon>
</button>
<button style="margin-right: 5px;" mdbTooltip="Stop VMs" *ngIf="provision.vmImage && provision.vmImage.vm1 && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--stop" aria-hidden="true"></span>
</button>
<button style="margin-right: 5px;" mdbTooltip="Start VMs" *ngIf="provision.vmImage && provision.vmImage.vm1 && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--run" aria-hidden="true"></span>
</button>
<!--<button style="margin-right: 5px;" mdbTooltip="Renew 24x7 period" *ngIf="(!provision.schedule || provision.schedule.is24x7) && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmExtendModal(provision)" class="lui-button">
<mdb-icon fas icon="redo-alt"></mdb-icon> 24x7
</button>-->
<button style="float: right;" mdbTooltip="Destroy provision" *ngIf="provision.canManage && !provision.isDestroyed && (provision.status === 'provisioned' || provision.status === 'error' || provision.status === 'aborted') && (!provision.destroy || provision.destroy.status !== 'destroying')" (click)="openConfirmDestroyModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--remove lui-text-danger" aria-hidden="true"></span>
</button>
</div>
</div>
</div>
<div *ngIf="!provisions || provisions.length === 0" style="text-align: center;">
<p>No one has shared any provisions with you yet.</p>
</div>
</div>
<qmi-alert></qmi-alert>

View File

@@ -0,0 +1,250 @@
import { Component, OnInit } from '@angular/core';
import { ProvisionsService } from '../services/provisions.service';
import { Subscription, timer} from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { AuthGuard } from '../services/auth.guard';
import { AlertService } from '../services/alert.service';
import { MDBModalService } from 'angular-bootstrap-md';
import { ModalInfoComponent } from '../modals/modalinfo.component';
import { ModalConfirmComponent } from '../modals/confirm.component';
import { ProvisionModalComponent } from '../modals/edit-provision.component';
@Component({
selector: 'app-provisions-shared',
templateUrl: './provisions-shared.component.html',
styleUrls: ['./provisions.component.scss'],
providers: [ProvisionsService]
})
export class ProvisionsSharedComponent implements OnInit {
private _userId;
provisions;
destroys;
subscription: Subscription;
instantSubs: Subscription;
logShow: boolean = false;
logstype: String = 'provision';
public selectedprov: Object = null;
history: Boolean = false;
constructor(private modalService: MDBModalService, private _alertService: AlertService, private _provisionsService: ProvisionsService, private _auth: AuthGuard) {
this._auth.getUserInfo().subscribe( value => {
this._userId = value? value._id : null;
});
}
private _refresh(): void {
this.instantSubs = this._provisionsService.getProvisionsSharedWithMe(this._userId).subscribe( provisions=>{
provisions = provisions.results.map(p=>{
p.provision.owner = p.user;
p.provision.canManage = p.canManage;
return p.provision;
});
this.provisions = provisions.filter(p => !p.destroy || !p.destroy.status || p.destroy.status !== 'destroyed');
this.instantSubs.unsubscribe();
})
}
ngOnInit() {
this.subscription = timer(0, 8000).pipe( switchMap(() => this._provisionsService.getProvisionsSharedWithMe(this._userId) ) ).subscribe(provisions => {
provisions = provisions.results.map(p=>{
p.provision.owner = p.user;
p.provision.canManage = p.canManage;
return p.provision;
});
this.provisions = provisions.filter(p => !p.destroy || !p.destroy.status || p.destroy.status !== 'destroyed');
if ( this.provisions && this.provisions.length === 0 ) {
this.subscription.unsubscribe();
}
})
}
ngOnDestroy() {
this.subscription.unsubscribe();
if ( this.instantSubs ) {
this.instantSubs.unsubscribe();
}
}
setModal(provision, frame) : void {
frame.show();
this._provisionsService.setSelectedProv(provision);
}
del(provision): void {
this._provisionsService.delProvision(provision._id.toString(), this._userId).subscribe( res => {
this._refresh();
this._alertService.showAlert({
type: 'alert-dark',
text: `Provision entry <b>${provision.scenario}</b> was deleted from your history`
});
})
}
toggleHistory(): void {
this.history = !this.history;
}
openConfirmDestroyModal(provision) {
var modalRef = this.modalService.show(ModalConfirmComponent, {
class: 'modal-sm modal-notify modal-danger',
containerClass: '',
data: {
info: {
title: 'Confirm destroy this provision?',
icon: 'times-circle'
}
}
} );
var sub = modalRef.content.action.subscribe( (result: any) => {
sub.unsubscribe();
this._provisionsService.newDestroy(provision._id.toString(), this._userId).subscribe( res => {
this._refresh();
this._alertService.showAlert({
type: 'alert-dark',
text: `Provision of scenario <b>${provision.scenario}</b> is going to be destroyed.`
});
});
});
}
openConfirmStopModal(provision) {
var modalRef = this.modalService.show(ModalConfirmComponent, {
class: 'modal-sm modal-notify modal-info',
containerClass: '',
data: {
info: {
title: 'Confirm Stop VMs?',
icon: 'stop',
buttonColor: 'grey'
},
provision: provision
}
} );
var sub = modalRef.content.action.subscribe( (result: any) => {
console.log("Confirm result", result);
sub.unsubscribe();
this._provisionsService.stopVms(provision._id.toString(), this._userId, result).subscribe( res => {
provision.statusVms = res.statusVms;
provision.timeRunning = res.timeRunning;
provision.runningFrom = res.runningFrom;
this._alertService.showAlert({
type: 'alert-dark',
text: `Stopping all VMs for scenario <b>${provision.scenario}</b>...`
});
})
});
}
openConfirmStartModal(provision) {
var modalRef = this.modalService.show(ModalConfirmComponent, {
class: 'modal-sm modal-notify modal-info',
containerClass: '',
data: {
info: {
title: 'Confirm Start VMs?',
icon: 'play',
buttonColor: 'grey'
},
provision: provision
}
} );
var sub = modalRef.content.action.subscribe( (result: any) => {
sub.unsubscribe();
this._provisionsService.startVms(provision._id.toString(), this._userId).subscribe( res => {
provision.statusVms = res.statusVms;
provision.timeRunning = res.timeRunning;
provision.runningFrom = res.runningFrom;
this._alertService.showAlert({
type: 'alert-dark',
text: `Starting all VMs for scenario <b>${provision.scenario}</b>...`
});
})
});
}
openConfirmExtendModal($event, provision) {
$event.preventDefault();
var modalRef = this.modalService.show(ModalConfirmComponent, {
class: 'modal-sm modal-notify modal-info',
containerClass: '',
data: {
info: {
title: `Renew 24x7 period?`,
icon: 'redo-alt',
buttonColor: 'grey'
}
}
} );
var sub = modalRef.content.action.subscribe( (result: any) => {
sub.unsubscribe();
this._provisionsService.extend(provision._id.toString(), this._userId).subscribe( res => {
provision.countExtend = res.countExtend;
provision.timeRunning = res.timeRunning;
provision.runningFrom = res.runningFrom;
this._alertService.showAlert({
type: 'alert-dark',
text: `Running period extended another ${provision._scenarioDoc.allowed24x7RunningDays} days (from now) for provision <b>${provision.scenario}</b>`
});
});
});
}
openScheduleModal(provision) {
var modalRef = this.modalService.show(ProvisionModalComponent, {
class: 'modal-lg modal-notify',
containerClass: '',
data: {
provision: provision
}
} );
var sub = modalRef.content.action.subscribe( (data: any) => {
sub.unsubscribe();
console.log("openScheduleModal returns", data);
});
}
showLogs($event, provision, type): void {
$event.preventDefault();
$event.stopPropagation();
this.logstype = type;
this.logShow = false;
this.selectedprov = provision;
this.logShow = true;
}
openModal(provision) {
this.modalService.show(ModalInfoComponent, {
class: 'modal-lg',
containerClass: '',
data: {
info:provision
}
} );
}
onLogsClose(): void {
this.selectedprov = null;
this.logShow = false;
}
onStartProvision(scenario): void {
this._alertService.showAlert({
type: 'alert-dark',
text: `Scenario <b>${scenario.name}</b> is going to be provisioned. Scroll up to your Provisions to watch out progress.`
});
this._refresh();
}
}

View File

@@ -1,7 +1,6 @@
<app-logs [show]="logShow" (onClose)="onLogsClose()" [type]="logstype" [selectedprov]="selectedprov"></app-logs>
<div style="margin-top: 80px;">
<h1>Provisions</h1>
<h4 style="border-bottom: 1px solid #ccc;">Current</h4>
<div style="margin-top: 80px; min-height: 650px;">
<h1>Provisions <span *ngIf="provisions && provisions.length">({{provisions.length}})</span></h1>
<div *ngIf="provisions && provisions.length" class="flexcontainer">
<div *ngFor="let provision of provisions; let i = index" class="box">
<div class="title desc {{provision.status}} {{provision.statusVms}} {{provision.destroy? provision.destroy.status : ''}}">
@@ -9,10 +8,10 @@
</div>
<div style="border-top: 1px dashed #fff;" class="title {{provision.status}} {{provision.statusVms}} {{provision.destroy? provision.destroy.status : ''}}">
<div class="maintitle"><span *ngIf="provision._scenarioDoc">{{provision._scenarioDoc.title}}</span><span *ngIf="!provision._scenarioDoc">Old scenario (please destroy)</span></div>
<div style="font-size: 14px;">{{provision.scenario}} (v{{provision.scenarioVersion}})</div>
<div style="font-size: 14px;">{{provision.scenario}} (v{{provision.scenarioVersion}}) <span *ngIf="provision.scenarioVersion !== provision._scenarioDoc.version" class="text-danger newversion">New version available!</span></div>
</div>
<div class="contentbox">
<div *ngIf="provision.vmImage">
<div *ngIf="provision.vmImage && provision.vmImage.vm1">
<div *ngIf="provision.statusVms">
Current VMs status:&nbsp;
<span>
@@ -80,13 +79,13 @@
<button mdbTooltip="Provision information" style="margin-right: 5px;" class="lui-button" (click)="openModal(provision)">
<span class="lui-icon lui-icon--info" aria-hidden="true"></span>
</button>
<button style="margin-right: 5px;" mdbTooltip="Edit running schedule" *ngIf="provision.vmImage && provision._scenarioDoc.isDivvyEnabled && provision.status === 'provisioned'" (click)="openScheduleModal(provision)" class="lui-button">
<button style="margin-right: 5px;" mdbTooltip="Edit running schedule" *ngIf="provision.vmImage && provision.vmImage.vm1 && provision._scenarioDoc.isDivvyEnabled && provision.status === 'provisioned'" (click)="openScheduleModal(provision)" class="lui-button">
<mdb-icon far icon="clock"></mdb-icon>
</button>
<button style="margin-right: 5px;" mdbTooltip="Stop VMs" *ngIf="provision.vmImage && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<button style="margin-right: 5px;" mdbTooltip="Stop VMs" *ngIf="provision.vmImage && provision.vmImage.vm1 && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--stop" aria-hidden="true"></span>
</button>
<button style="margin-right: 5px;" mdbTooltip="Start VMs" *ngIf="provision.vmImage && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<button style="margin-right: 5px;" mdbTooltip="Start VMs" *ngIf="provision.vmImage && provision.vmImage.vm1 && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--run" aria-hidden="true"></span>
</button>
<!--<button style="margin-right: 5px;" mdbTooltip="Renew 24x7 period" *ngIf="(!provision.schedule || provision.schedule.is24x7) && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmExtendModal(provision)" class="lui-button">
@@ -98,15 +97,19 @@
<button style="float: right;" mdbTooltip="Remove entry" *ngIf="(provision.isDestroyed && provision.destroy.status === 'destroyed') || provision.status === 'error_init' || provision.status === 'error_plan'" (click)="del(provision)" class="lui-button">
<span class="lui-icon lui-icon--bin lui-text-danger" aria-hidden="true"></span>
</button>
<button style="float: right;" mdbTooltip="Share provision" class="lui-button" (click)="share(provision)">
<span class="lui-icon lui-icon--share" aria-hidden="true"></span>
</button>
</div>
</div>
</div>
<div *ngIf="!provisions || provisions.length === 0">
<p>There are no current Running provisions</p>
<div *ngIf="!provisions || provisions.length === 0" style="text-align: center;">
<p>You don't have any active provisions.</p>
<p>Search available <a routerLink="/scenarios" routerLinkActive="active">Scenarios</a> and start provisioning now.</p>
</div>
<h4 style="padding-top: 40px; border-bottom: 1px solid #ccc;" *ngIf="destroys && destroys.length">History</h4>
<div *ngIf="destroys && destroys.length" class="flexcontainer">
<h4 style="padding-top: 40px;" *ngIf="destroys && destroys.length">History <button mdbTooltip="Show/Hide History" (click)="toggleHistory()" class="lui-button"><span class="lui-icon lui-icon--triangle-{{history? 'top' : 'bottom'}}" aria-hidden="true"></span></button></h4>
<div *ngIf="destroys && destroys.length && history" class="flexcontainer">
<div *ngFor="let provision of destroys; let i = index" class="box">
<div class="title desc {{provision.status}} {{provision.destroy.status}}">
<div mdbTooltip="{{provision.description}}" class="subtitle">{{provision.description}}</div>
@@ -115,7 +118,7 @@
<div class="maintitle"><span *ngIf="provision._scenarioDoc">{{provision._scenarioDoc.title}}</span><span *ngIf="!provision._scenarioDoc">Old scenario (please delete)</span></div>
<div style="font-size: 14px;">{{provision.scenario}} (v{{provision.scenarioVersion}})</div>
</div>
<div class="contentbox">
<div class="contentbox destroyed">
<div>
Provision ({{provision.created | date: 'MMM dd, yyyy - H:mm'}}h)
-
@@ -151,9 +154,10 @@
</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>

View File

@@ -13,6 +13,8 @@ h1 {
display: flex;
flex-direction: row;
flex-wrap: wrap;
border-top: 1px solid #ccc;
padding-top: 10px;
.box {
border: 1px solid #ccc;
@@ -81,6 +83,9 @@ h1 {
font-size: 12px;
padding: 10px;
min-height: 200px;
&.destroyed {
min-height: 100px;
}
}
.buttons {
@@ -130,3 +135,11 @@ h1 {
.linethrough {
text-decoration: line-through;
}
.newversion {
font-weight: bold;
font-size: 10px;
margin-left: 8px;
padding: 2px;
background: #fff;
}

View File

@@ -8,6 +8,7 @@ import { MDBModalService } from 'angular-bootstrap-md';
import { ModalInfoComponent } from '../modals/modalinfo.component';
import { ModalConfirmComponent } from '../modals/confirm.component';
import { ProvisionModalComponent } from '../modals/edit-provision.component';
import { ShareModalComponent } from '../modals/share.component';
@Component({
@@ -26,6 +27,7 @@ export class ProvisionsComponent implements OnInit {
logShow: boolean = false;
logstype: String = 'provision';
public selectedprov: Object = null;
history: Boolean = false;
constructor(private modalService: MDBModalService, private _alertService: AlertService, private _provisionsService: ProvisionsService, private _auth: AuthGuard) {
this._auth.getUserInfo().subscribe( value => {
@@ -47,6 +49,11 @@ export class ProvisionsComponent implements OnInit {
provisions = provisions.results;
this.provisions = provisions.filter(p => !p.destroy || !p.destroy.status || p.destroy.status !== 'destroyed');
this.destroys = provisions.filter(p => p.destroy && p.destroy.status === 'destroyed');
if ( this.provisions && this.provisions.length === 0 ) {
this.subscription.unsubscribe();
}
})
}
@@ -67,12 +74,16 @@ export class ProvisionsComponent implements OnInit {
this._provisionsService.delProvision(provision._id.toString(), this._userId).subscribe( res => {
this._refresh();
this._alertService.showAlert({
type: 'alert-primary',
text: `Provision entry '${provision.scenario}' was deleted from your history`
type: 'alert-dark',
text: `Provision entry <b>${provision.scenario}</b> was deleted from your history`
});
})
}
toggleHistory(): void {
this.history = !this.history;
}
openConfirmDestroyModal(provision) {
var modalRef = this.modalService.show(ModalConfirmComponent, {
class: 'modal-sm modal-notify modal-danger',
@@ -90,8 +101,8 @@ export class ProvisionsComponent implements OnInit {
this._provisionsService.newDestroy(provision._id.toString(), this._userId).subscribe( res => {
this._refresh();
this._alertService.showAlert({
type: 'alert-primary',
text: `Provision of scenario '${provision.scenario}' is going to be destroyed`
type: 'alert-dark',
text: `Provision of scenario <b>${provision.scenario}</b> is going to be destroyed.`
});
});
});
@@ -119,13 +130,28 @@ export class ProvisionsComponent implements OnInit {
provision.timeRunning = res.timeRunning;
provision.runningFrom = res.runningFrom;
this._alertService.showAlert({
type: 'alert-primary',
text: `Stopping all VMs for scenario '${provision.scenario}'...`
type: 'alert-dark',
text: `Stopping all VMs for scenario <b>${provision.scenario}</b>...`
});
})
});
}
share(provision) {
var modalRef = this.modalService.show(ShareModalComponent, {
class: 'modal-md modal-notify',
containerClass: '',
data: {
provision: provision
}
} );
var sub = modalRef.content.action.subscribe( (result: any) => {
sub.unsubscribe();
});
}
openConfirmStartModal(provision) {
var modalRef = this.modalService.show(ModalConfirmComponent, {
class: 'modal-sm modal-notify modal-info',
@@ -147,8 +173,8 @@ export class ProvisionsComponent implements OnInit {
provision.timeRunning = res.timeRunning;
provision.runningFrom = res.runningFrom;
this._alertService.showAlert({
type: 'alert-primary',
text: `Starting all VMs for scenario '${provision.scenario}'...`
type: 'alert-dark',
text: `Starting all VMs for scenario <b>${provision.scenario}</b>...`
});
})
});
@@ -176,8 +202,8 @@ export class ProvisionsComponent implements OnInit {
provision.timeRunning = res.timeRunning;
provision.runningFrom = res.runningFrom;
this._alertService.showAlert({
type: 'alert-primary',
text: `Running period extended another ${provision._scenarioDoc.allowed24x7RunningDays} days (from now) for provision '${provision.scenario}'`
type: 'alert-dark',
text: `Running period extended another ${provision._scenarioDoc.allowed24x7RunningDays} days (from now) for provision <b>${provision.scenario}</b>`
});
});
});
@@ -225,8 +251,8 @@ export class ProvisionsComponent implements OnInit {
onStartProvision(scenario): void {
this._alertService.showAlert({
type: 'alert-primary',
text: `Scenario '${scenario.name}' is going to be provisioned. Scroll up to your Provisions to watch out progress.`
type: 'alert-dark',
text: `Scenario <b>${scenario.name}</b> is going to be provisioned. Scroll up to your Provisions to watch out progress.`
});
this._refresh();
}

View File

@@ -0,0 +1,5 @@
<div style="margin-top: 80px;">
<h1>Scenarios</h1>
<app-scenarios (onStartProvision)="onStartProvision($event)"></app-scenarios>
</div>
<qmi-alert></qmi-alert>

View File

@@ -0,0 +1,35 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { AlertService } from '../services/alert.service';
@Component({
selector: 'app-scenarios-section',
templateUrl: './scenarios-section.component.html',
styleUrls: ['./scenarios-section.component.scss']
})
export class ScenariosSectionComponent implements OnInit, OnDestroy {
constructor(private router: Router, private _alertService: AlertService) {
}
ngOnInit() {
}
ngOnDestroy() {}
onStartProvision(scenario): void {
//this.router.navigate(['provisions']);
this._alertService.showAlert({
type: 'alert-dark',
text: `Scenario <b>${scenario.name}</b> is going to be provisioned. Go to <a class='alert-link' href='/provisions'><b>My Provisions</b></a> to watch out the progress.`
});
}
}

View File

@@ -38,7 +38,7 @@
<mdb-card-body style="position: relative;">
<!--Text-->
<mdb-card-text>
<mdb-card-text class="qmicardtext">
<div [innerHTML]="s.description"></div>
</mdb-card-text>

View File

@@ -1,3 +1,5 @@
.flexcontainer{
display: flex;
flex-direction: row;
@@ -7,11 +9,13 @@
box-shadow: none;
}
.qmicard {
margin-bottom: 20px;
margin-right: 20px;
position: relative;
max-width: 500px;
max-width: 525px;
.card-badge {
position: absolute;
@@ -22,10 +26,7 @@
.card-body {
padding:10px 15px;
}
.card-text {
font-size: .8rem;
font-weight: 300;
}
.card-icon {
margin-left: 5px;
cursor: pointer;

View File

@@ -85,7 +85,7 @@ export class ScenariosComponent implements OnInit, OnDestroy {
this.onStartProvision.emit(scenario);
}, data => {
this._alertService.showAlert({
type: 'alert-warning',
type: 'alert-danger',
text: `${data.error.msg}`
});
});

View File

@@ -17,7 +17,7 @@ export class AlertService {
}
this.to = setTimeout (function(){
this.alertEmitter.emit(null);
}.bind(this), 5000);
}.bind(this), 7000);
}
getAlertEmitter(): EventEmitter<any> {

View File

@@ -25,10 +25,14 @@ export class ProvisionsService {
if ( filter ){
params = params.append("filter", JSON.stringify(filter));
}
/*params = params.append("populates", JSON.stringify([
{ path: 'user', select: 'displayName upn' },
{ path:'destroy', select: "-user -jobId" }
]));*/
params = params.append("select", "-path -terraformImage -updated -jobId -logFile -pendingNextAction -outputs -vmImage");
params = params.append("populates", JSON.stringify([
{ path: 'user', select: 'displayName' },
{ path: 'destroy', select: "-user -jobId" },
{ path: 'schedule' },
{ path: '_scenarioDoc', select: "allowed24x7RunningDays allowedInnactiveDays isDivvyEnabled"},
{ path: 'deployOpts', select: "location subsId"}
]));
return this.httpClient.get(`${environment.apiVersionPath}/provisions`, { params: params }).pipe(map((provisions:any)=>{
provisions.results.forEach(p => {
if (!p.deployOpts){
@@ -49,6 +53,32 @@ export class ProvisionsService {
}));
}
getProvisionsSharedWithMe(userId): Observable<any> {
return this.httpClient.get(`${environment.apiVersionPath}/users/${userId}/sharedprovisions`).pipe(map((res:any)=>{
res.results.forEach(item => {
this.timeRunning(item.provision);
});
return res;
}));
}
getSharesForProvision(userId, provisionId): Observable<any> {
return this.httpClient.get(`${environment.apiVersionPath}/users/${userId}/provisions/${provisionId}/share`).pipe(map((res:any)=>{
let users = res.results.map(item => {
return item.sharedWithUser;
})
return users;
}));
}
shareProvision(userId, provisionId, withUserId): Observable<any> {
return this.httpClient.put(`${environment.apiVersionPath}/users/${userId}/provisions/${provisionId}/share/${withUserId}`, null);
}
stopShareProvision(userId, provisionId, withUserId): Observable<any> {
return this.httpClient.delete(`${environment.apiVersionPath}/users/${userId}/provisions/${provisionId}/share/${withUserId}`);
}
getProvisionById(id) : Observable<any> {
return this.httpClient.get(`${environment.apiVersionPath}/provisions/${id}`).pipe(map((p:any)=>{
this.timeRunning(p);

View File

@@ -44,12 +44,16 @@ export class UsersService {
return this.httpClient.get(`${environment.apiVersionPath}/apikeys`);
}
addApikey(userId): Observable<any> {
return this.httpClient.post(`${environment.apiVersionPath}/apikeys/${userId}`, null);
addApikey(userId, body): Observable<any> {
return this.httpClient.post(`${environment.apiVersionPath}/apikeys/${userId}`, body);
}
revokeApikey(id): Observable<any> {
return this.httpClient.put(`${environment.apiVersionPath}/apikeys/${id}/revoke`, null);
}
delApikey(id): Observable<any> {
return this.httpClient.put(`${environment.apiVersionPath}/apikeys/${id}`, null);
return this.httpClient.delete(`${environment.apiVersionPath}/apikeys/${id}`);
}
getScenarios(userId) : Observable<any> {

View File

@@ -11,8 +11,9 @@
<thead class="sticky-top">
<tr>
<th style="width: 155px;">Created</th>
<th>Key</th>
<th>User (role)</th>
<th>Description</th>
<th>Key</th>
<th>Is Active?</th>
<th></th>
</tr>
@@ -21,17 +22,21 @@
<tr *ngFor="let item of elements; let i = index">
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"
scope="row">{{item.created | date: 'MMM dd, yyyy - H:mm'}}</td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{item.apiKey}}</td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{item.user.displayName}} ({{item.user.role}})</td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{item.description}}</td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{item.apiKey}}</td>
<td style="text-align: center;" *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">
<mdb-icon *ngIf="item.isActive" fas icon="check"></mdb-icon>
<mdb-icon *ngIf="!item.isActive" class="red-text" fas icon="times"></mdb-icon>
</td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">
<button *ngIf="item.isActive" title="Revoke" (click)="openConfirmDeleteModal(item)" class="lui-button">
<button style="margin-right: 3px;" *ngIf="item.isActive" title="Revoke" (click)="openConfirmRevokeModal(item)" class="lui-button">
Revoke
</button>
<button style="margin-right: 3px;" *ngIf="!item.isActive" title="Delete" (click)="openConfirmDeleteModal(item)" class="lui-button">
<span class="lui-icon lui-icon--bin" aria-hidden="true"></span>
</button>
</td>
</tr>
</tbody>

View File

@@ -98,7 +98,7 @@ export class TableApiKeysComponent implements OnInit, AfterViewInit {
});
}
openConfirmDeleteModal(apiKey) {
openConfirmRevokeModal(apiKey) {
var modalRef = this.modalService.show(ModalConfirmComponent, {
class: 'modal-sm modal-notify modal-danger',
containerClass: '',
@@ -110,6 +110,27 @@ export class TableApiKeysComponent implements OnInit, AfterViewInit {
}
} );
var sub = modalRef.content.action.subscribe( (result: any) => {
sub.unsubscribe();
this._usersService.revokeApikey(apiKey._id).subscribe( res=> {
console.log("done", res);
this.refreshData();
});
});
}
openConfirmDeleteModal(apiKey) {
var modalRef = this.modalService.show(ModalConfirmComponent, {
class: 'modal-sm modal-notify modal-danger',
containerClass: '',
data: {
info: {
title: 'Confirm delete?',
icon: 'trash-alt'
}
}
} );
var sub = modalRef.content.action.subscribe( (result: any) => {
sub.unsubscribe();
this._usersService.delApikey(apiKey._id).subscribe( res=> {
@@ -119,4 +140,5 @@ export class TableApiKeysComponent implements OnInit, AfterViewInit {
});
}
}

View File

@@ -70,8 +70,9 @@
<i style="display: block;" *ngIf="provision.schedule && !provision.schedule.is24x7 && !provision.schedule.isStartupTimeEnable"><mdb-icon fas icon="times-circle"></mdb-icon> Destroy in: {{provision.autoDestroyAsDays}}</i>
</div>
</td>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" class="ell" title="{{provision.path}}" >
<a href (click)="openOwnerChange($event, provision)" class="lui-text-info">{{provision.user.displayName}}</a>
<td *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" class="ell" >
<a href="/user/{{provision.user._id}}" target="_blank" class="lui-text-info" title="Go to User">{{provision.user.displayName}}</a>
<a style="padding-left: 8px;" href (click)="openOwnerChange($event, provision)" class="fa fa-pencil" title="Change owner and more"></a>
</td>
<td (click)="openInfoModal(provision._id)" *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">
<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 === 'error_init' || provision.status === 'error_plan') }">{{provision.status}}</span>
@@ -101,10 +102,10 @@
<button style="margin-right: 3px;" title="Abort provision" *ngIf="!provision.isDestroyed && provision.status === 'provisioning'" (click)="openConfirmAbortModal(provision)" class="lui-button lui-text-danger">
Abort
</button>
<button style="margin-right: 3px;" title="Stop Vms" *ngIf="provision.vmImage && !provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<button style="margin-right: 3px;" title="Stop Vms" *ngIf="provision.vmImage && provision.vmImage.vm1 && !provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="openConfirmStopModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--stop" aria-hidden="true"></span>
</button>
<button style="margin-right: 3px;" title="Start Vms" *ngIf="provision.vmImage && !provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<button style="margin-right: 3px;" title="Start Vms" *ngIf="provision.vmImage && provision.vmImage.vm1 && !provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Stopped'" (click)="openConfirmStartModal(provision)" class="lui-button">
<span class="lui-icon lui-icon--run" aria-hidden="true"></span>
</button>
<button style="margin-right: 3px;" title="Destroy provision" *ngIf="!provision.isDestroyed && (!provision.destroy || provision.destroy.status !== 'destroying') && (provision.status === 'provisioned' || provision.status === 'error' || provision.status === 'aborted' )" (click)="openConfirmDestroyModal(provision)" class="lui-button lui-text-danger">

View File

@@ -146,8 +146,8 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
sub.unsubscribe();
provision.user = result.user;
this._alertService.showAlert({
type: 'alert-primary',
text: `New owner ${provision.user.displayName} assigned to this provision`
type: 'alert-dark',
text: `New owner <b>${provision.user.displayName}</b> assigned to this provision`
});
});
}
@@ -215,8 +215,8 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
provision.startVms = res.startVms;
sub.unsubscribe();
this._alertService.showAlert({
type: 'alert-primary',
text: `Starting all VMs for scenario '${provision.scenario}'...`
type: 'alert-dark',
text: `Starting all VMs for scenario <b>${provision.scenario}</b>...`
});
})
}
@@ -266,8 +266,8 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
provision.startVms = res.startVms;
sub.unsubscribe();
this._alertService.showAlert({
type: 'alert-primary',
text: `Stopping all VMs for scenario '${provision.scenario}'...`
type: 'alert-dark',
text: `Stopping all VMs for scenario <b>${provision.scenario}</b>...`
});
})
}
@@ -277,8 +277,8 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
provision.status = res.status;
sub.unsubscribe();
this._alertService.showAlert({
type: 'alert-primary',
text: `Aborting provision for scenario '${provision.scenario}'...`
type: 'alert-dark',
text: `Aborting provision for scenario <b>${provision.scenario}</b>...`
});
})
}
@@ -319,8 +319,8 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
sub.unsubscribe();
this._provisionsService.newDestroy(provision._id.toString(), provision.user._id).subscribe( provUpdated => {
this._alertService.showAlert({
type: 'alert-primary',
text: `Provision of scenario '${provision.scenario}' is going to be destroyed`
type: 'alert-dark',
text: `Provision of scenario <b>${provision.scenario}</b> is going to be destroyed`
});
provision.destroy = provUpdated.destroy;
})
@@ -347,8 +347,8 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
});
this._initElements();
this._alertService.showAlert({
type: 'alert-primary',
text: `Provision entry '${provision.scenario}' was deleted`
type: 'alert-dark',
text: `Provision entry <b>${provision.scenario}</b> was deleted from History.`
});
});
});
@@ -395,8 +395,8 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
provision.timeRunning = res.timeRunning;
provision.runningFrom = res.runningFrom;
this._alertService.showAlert({
type: 'alert-primary',
text: `Running period extended another ${provision._scenarioDoc.allowed24x7RunningDays} days (from now) for provision '${provision.scenario}'`
type: 'alert-dark',
text: `Running period extended another ${provision._scenarioDoc.allowed24x7RunningDays} days (from now) for provision <b>${provision.scenario}</b>`
});
})
});

View File

@@ -7,14 +7,17 @@
</a>
<div class="navbar-expand mr-auto">
<div class="navbar-nav">
<a class="nav-item nav-link" routerLink="/home" routerLinkActive="active">Home</a>
<a *ngIf="user" class="nav-item nav-link" routerLink="/provisions" routerLinkActive="active">Provisions</a>
<a *ngIf="user && (user.role === 'admin' || user.role === 'superadmin')" class="nav-item nav-link" routerLink="/admin" routerLinkActive="active">Admin</a>
<!--<a *ngIf="user" class="nav-item nav-link" routerLink="/stats" routerLinkActive="active">Stats</a>-->
<!--<a class="nav-item nav-link" routerLink="/home" routerLinkActive="active">Home</a>-->
<a *ngIf="user" class="nav-item nav-link" routerLink="/scenarios" routerLinkActive="active">Scenarios</a>
<a *ngIf="user" class="nav-item nav-link" routerLink="/provisions" routerLinkActive="active">My Provisions</a>
<a *ngIf="user" class="nav-item nav-link" routerLink="/sharedprovision" routerLinkActive="active">Shared Provisions</a>
<!--<a *ngIf="user && (user.role === 'admin' || user.role === 'superadmin')" class="nav-item nav-link" routerLink="/admin" routerLinkActive="active">ADMIN</a>
<a *ngIf="user" class="nav-item nav-link" routerLink="/stats" routerLinkActive="active">Stats</a>-->
</div>
</div>
<div class="navbar-expand ml-auto navbar-nav">
<div class="navbar-nav">
<a *ngIf="user && (user.role === 'admin' || user.role === 'superadmin')" class="nav-item nav-link" routerLink="/admin" routerLinkActive="active">Admin</a>
<a class="nav-item nav-link" routerLink="/faq" routerLinkActive="active">FAQ</a>
<a *ngIf="user && (user.role === 'admin' || user.role === 'superadmin')" class="nav-item nav-link" href="/api-docs" target="_blank">API Docs</a>
<a *ngIf="user" href (click)="logout($event)" class="nav-item nav-link" style="padding-left: 40px; color: #009845 !important;">Logout <i class="fas fa-sign-out-alt"></i></a>

View File

@@ -11,7 +11,7 @@
</div>
<h1>Provisions <span *ngIf="provisions && provisions.length">({{provisions.length}})</span></h1>
<h4 style="border-bottom: 1px solid #ccc;">Current</h4>
<h4>Current</h4>
<div *ngIf="provisions && provisions.length" class="flexcontainer">
<div *ngFor="let provision of provisions; let i = index" class="box">
<div class="title desc {{provision.status}} {{provision.statusVms}} {{provision.destroy? provision.destroy.status : ''}}">
@@ -19,10 +19,10 @@
</div>
<div style="border-top: 1px dashed #fff;" class="title {{provision.status}} {{provision.statusVms}} {{provision.destroy? provision.destroy.status : ''}}">
<div class="maintitle"><span *ngIf="provision._scenarioDoc">{{provision._scenarioDoc.title}}</span><span *ngIf="!provision._scenarioDoc">Old scenario (please destroy)</span></div>
<div style="font-size: 14px;">{{provision.scenario}} (v{{provision.scenarioVersion}})</div>
<div style="font-size: 14px;">{{provision.scenario}} (v{{provision.scenarioVersion}}) <span *ngIf="provision.scenarioVersion !== provision._scenarioDoc.version" class="text-danger newversion">New version available!</span></div>
</div>
<div class="contentbox">
<div *ngIf="provision.vmImage">
<div *ngIf="provision.vmImage && provision.vmImage.vm1">
<div *ngIf="provision.statusVms">
Current VMs status:&nbsp;
<span>
@@ -116,8 +116,8 @@
<p>There are no current Running provisions</p>
</div>
<h4 style="padding-top: 40px; border-bottom: 1px solid #ccc;" *ngIf="destroys && destroys.length">History</h4>
<div *ngIf="destroys && destroys.length" class="flexcontainer">
<h4 style="padding-top: 40px;" *ngIf="destroys && destroys.length">History <button mdbTooltip="Show/Hide History" (click)="toggleHistory()" class="lui-button"><span class="lui-icon lui-icon--triangle-{{history? 'top' : 'bottom'}}" aria-hidden="true"></span></button></h4>
<div *ngIf="destroys && destroys.length && history" class="flexcontainer">
<div *ngFor="let provision of destroys; let i = index" class="box">
<div class="title desc {{provision.status}} {{provision.destroy.status}}">
<div mdbTooltip="{{provision.description}}" class="subtitle">{{provision.description}}</div>
@@ -126,7 +126,7 @@
<div class="maintitle"><span *ngIf="provision._scenarioDoc">{{provision._scenarioDoc.title}}</span><span *ngIf="!provision._scenarioDoc">Old scenario (please delete)</span></div>
<div style="font-size: 14px;">{{provision.scenario}} (v{{provision.scenarioVersion}})</div>
</div>
<div class="contentbox">
<div class="contentbox destroyed">
<div>
Provision ({{provision.created | date: 'MMM dd, yyyy - H:mm'}}h)
-

View File

@@ -13,6 +13,8 @@ h1 {
display: flex;
flex-direction: row;
flex-wrap: wrap;
border-top: 1px solid #ccc;
padding-top: 10px;
.box {
border: 1px solid #ccc;
@@ -81,6 +83,9 @@ h1 {
font-size: 12px;
padding: 10px;
min-height: 200px;
&.destroyed {
min-height: 100px;
}
}
.buttons {
@@ -130,3 +135,11 @@ h1 {
.linethrough {
text-decoration: line-through;
}
.newversion {
font-weight: bold;
font-size: 10px;
margin-left: 8px;
padding: 2px;
background: #fff;
}

View File

@@ -21,6 +21,7 @@ export class UserDashboardComponent implements OnInit, OnDestroy{
private sub: any;
private instantSubs: any;
public selectedprov: Object = null;
history: Boolean = false;
logShow: boolean = false;
@@ -61,6 +62,10 @@ export class UserDashboardComponent implements OnInit, OnDestroy{
this.logShow = true;
}
toggleHistory(): void {
this.history = !this.history;
}
onLogsClose(): void {
this.selectedprov = null;
this.logShow = false;

View File

@@ -10,6 +10,28 @@
-moz-osx-font-smoothing: grayscale;
}
::-webkit-scrollbar {
-webkit-appearance: none;
width: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 5px;
background-color: rgba(0,0,0,.5);
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
}
/*
p.card-text.qmicardtext {
max-height: 350px;
overflow:scroll;
}*/
.alert-dark .alert-link {
text-decoration: underline;
}
body {
font-family: "Source Sans Pro",sans-serif !important;
color: #162a4b !important;