23 Commits

Author SHA1 Message Date
Manuel Romero
d742a4ff2f More to admin provisions 2020-05-10 12:44:45 +02:00
Manuel Romero
990b0d80e3 Fix 2020-05-10 11:57:23 +02:00
Manuel Romero
2d512b49be Disable scenario property 2020-05-10 11:50:35 +02:00
Manuel Romero
61b57d5bc7 Setting isExternalAccess from UI 2020-05-08 16:27:54 +02:00
Manuel Romero
8f48cfbc69 lastLogin 2020-05-07 17:43:31 +02:00
Manuel Romero
9cee830fd4 Admin enhance 2020-05-07 17:18:19 +02:00
Manuel Romero
4f481fd88f vmtypes endpoint 2020-05-07 12:32:21 +02:00
Manuel Romero
2faf109353 disksize default 2020-05-07 12:21:09 +02:00
Manuel Romero
19a0fa715e 128 as minimum disk size 2020-05-07 12:10:54 +02:00
Manuel Romero
2e194b72b8 display disk size on UI 2020-05-07 12:06:02 +02:00
Manuel Romero
039a13bd30 set disk size from UI 2020-05-07 11:56:14 +02:00
Manuel Romero
26fa09541a less logs and oid as index 2020-05-07 09:13:55 +02:00
Manuel Romero
cfbe52efc1 paging 2020-05-06 17:37:28 +02:00
Manuel Romero
a267fedaef Adding notifications to Admin 2020-05-06 08:59:57 +02:00
Manuel Romero
d6cb0fc78f No auto refresh provisions 2020-05-05 16:34:31 +02:00
Manuel Romero
1fbbbde1a1 No needed log 2020-05-05 14:59:16 +02:00
Manuel Romero
08721bb810 populate user for ApiKey model 2020-05-05 14:25:21 +02:00
Manuel Romero
68d2bef6ba populate user for ApiKey model 2020-05-05 14:23:59 +02:00
Manuel Romero
5199cabd26 fix 2020-05-05 14:19:51 +02:00
Manuel Romero
59546838ac fix 2020-05-05 14:15:21 +02:00
Manuel Romero
4740163572 fix 2020-05-05 13:59:13 +02:00
Manuel Romero
22af7f903e reorg shell scripts and cron 2020-05-05 13:55:38 +02:00
Manuel Romero
e921182575 Getting scenarios from git 2020-05-05 13:26:49 +02:00
43 changed files with 476 additions and 154 deletions

View File

@@ -9,5 +9,5 @@
<link rel="stylesheet" href="styles.529f751cbb5308365172.css"></head>
<body>
<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.6866cf66954a0b739d41.js" defer></script><script src="main.b7d3a2d901b927013a9e.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.6866cf66954a0b739d41.js" defer></script><script src="main.ab99167120bc660b9a38.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

@@ -83,6 +83,7 @@ services:
- REDIS_URL=redis://redis
- MONGO_URI=mongodb://root:example@mongo/qmicloud?authSource=admin
- PROJECT_PATH=${PWD}
- GIT_SCENARIOS=git::git@gitlab.com:qmi/qmi-cloud-scenarios.git
- SSHPATH=/Users/aor/.ssh
command: "sh -c 'npm run worker:dev'"
volumes:

View File

@@ -1,5 +0,0 @@
#!/bin/bash
branch=master
rm -fr ./az-tf-templates
git clone -b $branch git@gitlab.com:qmi/qmi-cloud-scenarios.git ./az-tf-templates

View File

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

View File

@@ -6,8 +6,8 @@ if ( myArgs.length < 3 ) {
process.exit(0);
}
var db = require('./mongo');
const sendEmail = require("./send-email");
var db = require('../mongo');
const sendEmail = require("../send-email");
const moment = require('moment');
const fetch = require('node-fetch');
@@ -36,7 +36,7 @@ async function postDestroy(provision) {
}
})
.then(res => res.json())
.then(json => console.log(json));
.then(json => console.log(json));
}

View File

@@ -11,10 +11,10 @@ const RUNNING_PERIOD = myArgs[1]; //Days
const RUNNING_LIMIT_HOURS_WARNING = 24*(RUNNING_PERIOD-1);
const RUNNING_LIMIT_HOURS_STOP = 24*RUNNING_PERIOD;
var db = require('./mongo');
const sendEmail = require("./send-email");
var db = require('../mongo');
const sendEmail = require("../send-email");
const moment = require('moment');
const azurecli = require('./azurecli');
const azurecli = require('../azurecli');
function timeRunning(p) {
let runningFromTime = p.runningFrom? new Date(p.runningFrom).getTime() : new Date(p.created).getTime();

View File

@@ -28,6 +28,10 @@ const provisionSchema = new mongoose.Schema({
logFile: String,
outputs: Object,
path: String,
isExternalAccess: {
type: Boolean,
default: false
},
isDestroyed: {
type: Boolean,
default: false

View File

@@ -32,6 +32,10 @@ const scenarioSchema = new mongoose.Schema({
type: Boolean,
default: false
},
isDisabled: {
type: Boolean,
default: false
},
title: {
type: String,
requred: true

View File

@@ -14,11 +14,17 @@ const userSchema = new mongoose.Schema({
},
displayName: String,
upn: String,
oid: String,
oid: {
type: String,
index: true
},
role: {
type: String,
default: "user"
}
},
lastLogin: {
type: Date
},
});

View File

@@ -5,7 +5,8 @@ mongoose.set('useFindAndModify', false);
const userSchema = new mongoose.Schema({
type: String,
desc: String
desc: String,
costHour: Number
});

View File

@@ -53,27 +53,42 @@ const getNewTimeRunning = function (provision) {
return Math.floor(minutesFromLastRunning + timeRunning);
};
const get = async (model, filter, extras, reply) => {
var sort = extras && extras.sort? extras.sort : {created: -1};
const get = async (model, filter, skip, limit, reply) => {
var sort = {created: -1};
try {
var exec = model.find(filter).sort(sort);
var totalDocs = await model.countDocuments(filter);
if ( extras && extras.skip ) {
exec = exec.skip(extras.skip);
}
if ( extras && extras.limit ) {
exec = exec.limit(extras.limit);
skip = skip? parseInt(skip) : 0;
exec = exec.skip(skip);
if ( limit ) {
limit = parseInt(limit);
exec = exec.limit(limit);
}
if ( model === Provision ) {
exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate('destroy');
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
const entity = await exec;
return {
total: await model.countDocuments(filter),
var out = {
total: totalDocs,
count: entity.length,
results: entity
};
}
if ( limit && (skip + limit) < totalDocs) {
out.nextSkip = skip+limit;
out.nextLimit = limit;
}
return out;
} catch (err) {
throw boom.boomify(err)
}
@@ -85,6 +100,9 @@ const getById = async (model, id, reply) => {
if ( model === Provision ) {
exec = exec.populate({ path: 'user', select: 'displayName upn'}).populate('destroy');
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
const entity = await exec;
return entity;
} catch (err) {
@@ -98,6 +116,9 @@ const getOne = async (model, filter, reply) => {
if ( model === Provision ) {
exec = exec.populate('user').populate('destroy');
}
if ( model === ApiKey ) {
exec = exec.populate('user');
}
const entity = await exec;
return entity;
} catch (err) {
@@ -141,8 +162,8 @@ const del = async (model, id, reply) => {
function _m(model) {
return {
get: async (filter, extras, reply) => {
return get(model, filter, extras, reply);
get: async (filter, skip, limit, reply) => {
return get(model, filter, skip, limit, reply);
},
getById: async (id, reply) => {
return getById(model, id, reply);

View File

@@ -82,28 +82,30 @@ passport.use(new OIDCStrategy({
if ( !profile.oid ) {
return done(new Error("No oid found"), null);
}
console.log("accessToken", accessToken);
console.log("iss", iss);
console.log("sub", sub);
console.log("refreshToken", refreshToken);
console.log("jwtClaims", jwtClaims);
console.log("params", params);
console.log("profile", profile);
//console.log("accessToken", accessToken);
//console.log("iss", iss);
//console.log("sub", sub);
//console.log("refreshToken", refreshToken);
//console.log("jwtClaims", jwtClaims);
//console.log("params", params);
console.log("New Auth: profile", profile);
// asynchronous verification, for effect...
process.nextTick(function () {
_findByOid(profile.oid, async function(err, user) {
if (err) {
return done(err);
return done(err);
}
if (!user) {
// "Auto-registration"
user = await db.user.add({
"oid": profile.oid,
"upn": profile.upn,
"displayName": profile.displayName
"displayName": profile.displayName,
"lastLogin": new Date()
});
return done(null, user);
}
db.user.update(user._id, {"lastLogin": new Date()});
return done(null, user);
});
});
@@ -198,13 +200,16 @@ module.exports.init = function(app){
};
async function isApiKeyAuthenticated(req) {
if (req.query && req.query.apiKey){
if (req.query && req.query.apiKey){
let key = req.query.apiKey;
var result = await db.apiKey.get({apiKey: key});
if ( result.length > 0 ) {
req.user = result[0].user;
var result = await db.apiKey.getOne({"apiKey": key});
if ( result ) {
req.user = result.user;
return true;
} else {
return false;
}
return result.length > 0;
}else {
return false;
}

View File

@@ -0,0 +1,28 @@
const express = require('express');
const router = express.Router();
const db = require('../mongo');
const passport = require('../passport');
/**
* @swagger
* /notifications:
* get:
* description: Get all notifications (Only admin)
* summary: Get all notifications (Only admin)
* produces:
* - application/json
* responses:
* 200:
* description: Notifications
*/
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
const result = await db.notification.get();
return res.json(result);
} catch (error) {
next(error);
}
});
module.exports = router;

View File

@@ -22,28 +22,44 @@ const fs = require('fs-extra');
* application/json:
* schema:
* type: object
* - name: extras
* - name: skip
* in: query
* required: false
* type: object
* content:
* application/json:
* schema:
* type: object
* type: integer
* - name: limit
* in: query
* required: false
* type: integer
* responses:
* 200:
* description: JSON Array
*/
router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => {
try {
let filter = req.query.filter? JSON.parse(req.query.filter) : {};
if ( filter.isDeleted === undefined ) {
filter.isDeleted = false;
}
let extras = req.query.extras? JSON.parse(req.query.extras) : {};
const result = await db.provision.get(filter, extras);
return res.json(result);
const result = await db.provision.get(filter, req.query.skip, req.query.limit);
var out = {
total: result.total,
count: result.count
};
if ( result.nextSkip && result.nextLimit ) {
out.nextUrl = new URL(req.protocol + '://' + req.get('Host') + req.baseUrl);
if ( req.query.filter ) {
out.nextUrl.searchParams.append("filter", req.query.filter);
}
out.nextUrl.searchParams.append("skip", result.nextSkip);
out.nextUrl.searchParams.append("limit", result.nextLimit);
}
out.results = result.results;
return res.json(out);
} catch (error) {
next(error);
}

View File

@@ -32,6 +32,7 @@ router.get('/', passport.ensureAuthenticated, async (req, res, next) => {
if (req.user.role === "user") {
filter.isAdminOnly = false;
}
filter.isDisabled = filter.isDisabled || false;
const result = await db.scenario.get(filter);
return res.json(result);
@@ -42,7 +43,7 @@ router.get('/', passport.ensureAuthenticated, async (req, res, next) => {
/**
* @swagger
* /scenarios:
* /scenarios/vmtypes:
* get:
* description: Get scenarios VM Types
* summary: Get scenarios VM Types

View File

@@ -9,6 +9,7 @@ const routesApiScenarios = require('./routes/api-scenarios');
const routesApiUsers = require('./routes/api-users');
const routesApiProvisions = require('./routes/api-provisions');
const routesApiDestroyProvisions = require('./routes/api-destroyprovisions');
const routesApiNotifications = require('./routes/api-notifications');
const swaggerUi = require('swagger-ui-express');
const swaggerJsdoc = require('swagger-jsdoc');
const cookieParser = require('cookie-parser');
@@ -75,6 +76,7 @@ app.use("/api/v1/scenarios", routesApiScenarios);
app.use("/api/v1/users", routesApiUsers);
app.use("/api/v1/provisions", routesApiProvisions);
app.use("/api/v1/destroyprovisions", routesApiDestroyProvisions);
app.use("/api/v1/notifications", routesApiNotifications);
app.get('/*',(req, res, next) =>{
if (req.originalUrl.indexOf("/api-docs") !== -1 || req.originalUrl.indexOf("/arena") !== -1 ) {
@@ -147,6 +149,7 @@ const options = {
'server/routes/api-users.js',
'server/routes/api-provisions.js',
'server/routes/api-destroyprovisions.js',
'server/routes/api-notifications.js',
]
};

View File

@@ -2,10 +2,9 @@ const Docker = require('dockerode');
const docker = new Docker({
'socketPath': '/home/docker.sock'
});
const path = require('path');
const fs = require('fs');
const PROJECT_PATH = process.env.PROJECT_PATH;
const DOCKERIMAGE = "qlikgear/terraform:1.0.0";
const GIT_SCENARIOS = process.env.GIT_SCENARIOS;
const DOCKERIMAGE = "qlikgear/terraform:1.0.1";
const SSHPATH = process.env.SSHPATH;
function hook_stdout(callback) {
@@ -48,11 +47,21 @@ function _buildExec( exec, provision ) {
exec.push(`vm_type_${key}=${provision.vmImage[key].vmType}`);
}
if (provision.vmImage[key].diskSizeGb) {
exec.push('-var');
exec.push(`disk_size_gb_${key}=${provision.vmImage[key].diskSizeGb}`);
}
if (provision.vmImage[key].version) {
exec.push('-var');
exec.push(`image_reference_${key}=${provision.vmImage[key].version.image}`);
}
}
}
if (provision.isExternalAccess) {
exec.push('-var');
exec.push(`is_external_access=${provision.isExternalAccess}`);
}
return exec;
@@ -60,11 +69,10 @@ function _buildExec( exec, provision ) {
const init = function( provMongo ) {
const templatePath = path.join(PROJECT_PATH, 'az-tf-templates', provMongo.scenario);
const name = `qmi-tf-init-${provMongo._id}`;
console.log(`Init: will spin up container: ${name}`);
var processStream = fs.createWriteStream(provMongo.logFile, {flags:'a'});
let exec = ['terraform', 'init', '-no-color', '-from-module=/template'];
let exec = ['terraform', 'init', '-no-color', `-from-module=${GIT_SCENARIOS}//${provMongo.scenario}`];
console.log('Init: exec: '+exec.join(" "));
return docker.run(DOCKERIMAGE, exec, processStream, {
@@ -73,8 +81,7 @@ const init = function( provMongo ) {
"WorkingDir": "/app",
"HostConfig": {
"Binds": [
`${provMongo.path}:/app`,
`${templatePath}:/template`
`${provMongo.path}:/app`
]
}
}).then(function(data) {

5
shell-utils/checkdestroy.sh Executable file
View File

@@ -0,0 +1,5 @@
BASEDIR=$(dirname "$0")
d=`date`
echo "------ $d"
echo "------ TYPE: $1"
MONGO_URI=mongodb://root:example@localhost:27017/qmicloud?authSource=admin API_KEY="c229219ccdd72d11e8ea253fd3876d247e5f489c9c84922cabdfb0cc194d8ff398a8d8d6528d8241efc99add2207e0ec75122a1b2c5598cc340cbe6b7c3c0dbf" node $BASEDIR/../server/cronjobs/destroy5.js $1 $2 $3

View File

@@ -1,4 +1,5 @@
BASEDIR=$(dirname "$0")
d=`date`
echo "------ $d"
MONGO_URI=mongodb://root:example@localhost:27017/qmicloud?authSource=admin node $BASEDIR/stop5.js $1 $2 $3
echo "------ TYPE: $1"
MONGO_URI=mongodb://root:example@localhost:27017/qmicloud?authSource=admin node $BASEDIR/../server/cronjobs/stop5.js $1 $2 $3

6
shell-utils/mongodump.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
id=`docker ps | grep 'mongo:4.2' | awk '{ print $1 }'`
d=$(date +%Y-%m-%d-T%H:%M:%S%z)
docker exec $id sh -c 'exec mongodump --uri="mongodb://root:example@mongo/qmicloud?authSource=admin" --archive' > $1/qmicloud-$d.archive

View File

@@ -1,12 +1,6 @@
<ul style="margin-top: 80px;" class="nav nav-pills nav-fill">
<li class="nav-item">
<a class="nav-link" (click)="tabSelect($event, 'Provisions')" [ngClass]="{'active': tab === 'Provisions'}">Provisions</a>
</li>
<li class="nav-item">
<a class="nav-link" (click)="tabSelect($event, 'Users')" [ngClass]="{'active': tab === 'Users'}">Users</a>
</li>
<li class="nav-item">
<a class="nav-link" (click)="tabSelect($event, 'Scenarios')" [ngClass]="{'active': tab === 'Scenarios'}">Scenarios</a>
<li *ngFor="let item of sections; let i = index" class="nav-item">
<a class="nav-link" (click)="tabSelect($event, item)" [ngClass]="{'active': tab === item}">{{item}}</a>
</li>
</ul>
@@ -24,4 +18,9 @@
<!--<h1>Scenarios</h1>-->
<table-scenarios></table-scenarios>
</div>
<div *ngIf="tab === 'Notifications'">
<!--<h1>Scenarios</h1>-->
<table-notifications></table-notifications>
</div>
<qmi-alert></qmi-alert>

View File

@@ -7,22 +7,18 @@ import { Component, OnInit } from '@angular/core';
})
export class AdminComponent implements OnInit {
sections = ['Provisions', 'Users', 'Scenarios', 'Notifications'];
tab : string = 'Provisions';
constructor() { }
ngOnInit() {
}
tabSelect($event, tab) {
$event.preventDefault();
$event.stopPropagation();
this.tab = tab;
}
}

View File

@@ -24,7 +24,7 @@
<b>Product version: </b> <span>{{item.value.version.name}}</span>
</div>
<div *ngIf="item.value.vmType" >
<b>Instance Type: </b> <span>{{item.value.vmType}}</span> <span *ngIf="item.value.nodeCount">( {{item.value.nodeCount}} nodes )</span>
<b>Instance Type: </b> <span>{{item.value.vmType}} </span> <span *ngIf="item.value.diskSizeGb">( {{item.value.diskSizeGb}} GiB on disk ) </span> <span *ngIf="item.value.nodeCount">( {{item.value.nodeCount}} nodes )</span>
</div>
</div>
<h5 *ngIf="info.outputs" class="info-subtitle">Connection resources</h5>

View File

@@ -6,37 +6,57 @@
</button>
<h4 class="modal-title w-100 font-weight-bold">New provision</h4>
</div>
<div class="modal-body">
<h5>{{scenario.title}}</h5>
<div class="modal-body" style="max-height: 650px; overflow: auto;">
<!--<h5>{{scenario.title}}</h5>-->
<div class="md-form">
<div *ngFor="let server of scenario.availableProductVersions">
<div>
<div>Purpose: (*)</div>
<textarea [(ngModel)]="sendData.description" type="text" class="md-textarea form-control" rows="1" mdbInput placeholder="Short description or Salesforce opportunity"></textarea>
</div>
</div>
<section *ngIf="scenario.isExternal" style="padding: 20px 0px; margin: 0px 20px">
<mdb-checkbox [default]="false" (change)="checkOnchange($event)">Enable External Access</mdb-checkbox>
</section>
<section *ngIf="!scenario.isExternal" style="padding: 20px 0px; margin: 0px 20px">
<mdb-checkbox [default]="false" (change)="checkOnchange($event)" [disabled]="true"><span style="color:#ccc">Enable External Access</span><br><i><mdb-icon fas icon="exclamation-triangle"></mdb-icon> This scenario only allows access from VPN</i></mdb-checkbox>
</section>
<div style="padding-top: 10px" class="md-form" >
<div *ngFor="let server of scenario.availableProductVersions" style="margin-bottom: 15px;">
<h5 style="font-weight: 600; margin-bottom: 0px;">- {{server.product}}</h5>
<div *ngIf="server.versions && server.versions.length">
<div >
<label>Product version for {{server.product}}: (*)</label>
<label>Product version: (*)</label>
</div>
<select id="pversion" class="browser-default custom-select" [(ngModel)]="selectedProductVersion[server.index]">
<select id="pversion" class="browser-default custom-select custom-select-sm" [(ngModel)]="selectedProductVersion[server.index]">
<option *ngFor="let v of server.versions" [value]="v.name">{{v.name}}</option>
</select>
</div>
<div>
<label>VM size for {{server.product}}: (*)</label>
<label>VM type: (*)</label>
</div>
<select id="vmtype" class="browser-default custom-select" [(ngModel)]="selectedVmType[server.index]">
<select id="vmtype" class="browser-default custom-select custom-select-sm" [(ngModel)]="selectedVmType[server.index]">
<option *ngFor="let item of vmTypes" [value]="item.type">{{item.type}} ({{item.desc}})</option>
</select>
<div>
<label>Disk size (GiB): (*)</label>
</div>
<select class="browser-default custom-select custom-select-sm" [(ngModel)]="selectedDiskSizeGb[server.index]">
<option *ngFor="let item of [128,250,500,750,1000]" [value]="item">{{item}}</option>
</select>
<div *ngIf="server.nodeCount">
<label>Num. nodes: (*)</label>
</div>
<select *ngIf="scenario.nodeCount" id="pnodes" class="browser-default custom-select" [(ngModel)]="selectedNodeCount[server.index]">
<select *ngIf="scenario.nodeCount" class="browser-default custom-select custom-select-sm" [(ngModel)]="selectedNodeCount[server.index]">
<option *ngFor="let item of [1,2,3,4,5,6]" [value]="item">{{item}}</option>
</select>
</div>
<div style="padding-top: 20px;">
<div>Set purpose (short description): (*)</div>
<textarea [(ngModel)]="sendData.description" type="text" class="md-textarea form-control" rows="2" mdbInput></textarea>
</div>
</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()">Cancel</button>
<button mdbBtn color="dark-green" class="waves-light" size="sm" mdbWavesEffect (click)="confirm();">Create</button>

View File

@@ -1,3 +1,12 @@
label {
position: relative;
}
.md-form {
margin-top: 0px;
margin-bottom: 0px;
}
::placeholder {
color: #ccc;
}

View File

@@ -15,11 +15,13 @@ export class NewProvisionConfirmComponent implements OnInit, OnDestroy {
shortDesc: string;
sendData = {
description: "",
servers: null
servers: null,
isExternalAccess: false,
};
selectedProductVersion: any = {};
selectedVmType: any = {};
selectedNodeCount: any = {};
selectedDiskSizeGb: any = {};
vmTypesSub: Subscription;
vmTypes: any;
servers: any = {};
@@ -39,6 +41,9 @@ export class NewProvisionConfirmComponent implements OnInit, OnDestroy {
if ( server.nodeCount ) {
this.selectedNodeCount[server.index] = server.nodeCount;
}
this.selectedDiskSizeGb[server.index] = server.diskSizeGbDefault || 500;
if ( server.versions && server.versions.length ) {
let lastIndex = server.versions.length - 1;
this.selectedProductVersion[server.index] = server.productVersionDefault? server.productVersionDefault : server.versions[lastIndex].name;
@@ -72,6 +77,10 @@ export class NewProvisionConfirmComponent implements OnInit, OnDestroy {
this.sendData.servers[key].nodeCount = this.selectedNodeCount[key];
}
if ( this.selectedDiskSizeGb[key] ) {
this.sendData.servers[key].diskSizeGb = this.selectedDiskSizeGb[key];
}
this.scenario.availableProductVersions.forEach(server => {
server.versions.forEach(v=> {
if (v.name === this.selectedProductVersion[key]){
@@ -80,8 +89,14 @@ export class NewProvisionConfirmComponent implements OnInit, OnDestroy {
})
});
}
this.action.next(this.sendData);
console.log("sendData", this.sendData);
//this.action.next(this.sendData);
this.modalRef.hide();
}
checkOnchange($event) {
console.log("Checked?", $event.checked);
this.sendData.isExternalAccess = $event.checked;
}
}

View File

@@ -24,6 +24,7 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { TableProvisionsAdminComponent } from './tables/table-provisions.component';
import { TableScenariosComponent } from './tables/table-scenarios.component';
import { TableUsersComponent } from './tables/table-users.component';
import { TableNotificationsComponent } from './tables/table-notifications.component';
import { AlertComponent } from './alert/alert.component';
import { AlertService } from './services/alert.service';
import { ModalInfoComponent } from './alert/modalinfo.component';
@@ -60,7 +61,8 @@ export function markedOptions(): MarkedOptions {
FilterPipe,
FaqComponent,
NewProvisionConfirmComponent,
TableScenariosComponent
TableScenariosComponent,
TableNotificationsComponent
],
imports: [
BrowserModule,

View File

@@ -1,18 +1,19 @@
<p>
<mdb-icon fas icon="globe-americas" size="lg" class="grey-text pr-1" aria-hidden="true"></mdb-icon>Scenario with External Access available
<span *ngIf="user && (user.role === 'admin' || user.role === 'superadmin')"><mdb-icon fas icon="user-secret" size="lg" class="ml-5 grey-text pr-1" aria-hidden="true"></mdb-icon>Only Administrators can see</span>
</p>
<div class="md-form">
<mdb-icon fas icon="search" aria-hidden="true"></mdb-icon>
<input type="search" [(ngModel)]="searchText" class="ml-2" placeholder="Search text">
</div>
<p>
<mdb-icon fas icon="globe-americas" size="lg" class="grey-text pr-1" aria-hidden="true"></mdb-icon>Scenario with External Access
<span *ngIf="user && (user.role === 'admin' || user.role === 'superadmin')"><mdb-icon fas icon="user-secret" size="lg" class="ml-5 grey-text pr-1" aria-hidden="true"></mdb-icon>Only Administrators can see</span>
</p>
<div class="flexcontainer">
<mdb-card class="qmicard" *ngFor="let s of scenarios | filter: searchText">
<!--Card content-->
<div class="card-badge">
<mdb-icon *ngIf="s.isExternal" fas icon="globe-americas" size="lg" class="grey-text" aria-hidden="true" mdbTooltip="External Access" placement="top"></mdb-icon>
<mdb-icon *ngIf="s.isExternal" fas icon="globe-americas" size="lg" class="grey-text" aria-hidden="true" mdbTooltip="External Access available" placement="top"></mdb-icon>
<mdb-icon *ngIf="s.isAdminOnly" fas icon="user-secret" size="lg" class="grey-text" aria-hidden="true" mdbTooltip="Only Administrators can see" placement="top"></mdb-icon>
</div>
<mdb-card-header>

View File

@@ -40,7 +40,6 @@ export class ScenariosComponent implements OnInit, OnDestroy {
ngOnDestroy() {}
openNewProvisionConfirmModal(scenario) {
console.log("scenario", scenario);
var modalRef = this.modalService.show(NewProvisionConfirmComponent, {
class: 'modal-md modal-notify',
containerClass: '',
@@ -54,16 +53,13 @@ export class ScenariosComponent implements OnInit, OnDestroy {
const postData = {
scenario: scenario.name,
description: data.description
description: data.description,
isExternalAccess: data.isExternalAccess
};
if ( data.servers ) {
if ( data.servers ) {
postData["vmImage"] = data.servers;
}
console.log("postData", postData);
this._provisionsService.newProvision(postData, this.user._id).subscribe( res => {
console.log("Done!", res);

View File

@@ -21,4 +21,8 @@ export class UsersService {
updateUser(userId, patchData): Observable<any> {
return this.httpClient.put(`${environment.apiVersionPath}/users/${userId}`, patchData);
}
getNotifications(): Observable<any> {
return this.httpClient.get(`${environment.apiVersionPath}/notifications`);
}
}

View File

@@ -0,0 +1,46 @@
<div class="md-form">
<input type="text" class="form-control w-25" [(ngModel)]="searchText" (keyup)="searchItems()" id="search-input2"
mdbInput>
<label for="search-input2">Search</label>
</div>
<div style="padding: 5px 0px;">
<button *ngIf="loading" mdbBtn color="grey" outline="true" type="button" size="sm" disabled mdbWavesEffect>
<span>Refreshing...</span>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</button>
<button *ngIf="!loading" (click)="refreshData();" mdbBtn type="button" size="sm" color="grey" outline="true" mdbWavesEffect>
Refresh<mdb-icon fas icon="redo-alt" class="ml-1" ></mdb-icon>
</button>
<span *ngIf="elements && elements.length">Total: {{elements.length}}</span>
</div>
<table mdbTable #tableEl="mdbTable" stickyHeader="true" hover="true" class="z-depth-1 table table-sm">
<thead class="sticky-top">
<tr>
<th style="width: 155px;">Date</th>
<th>Type</th>
<th>ProvisionID</th>
<th>Message</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of elements; let i = index">
<th *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"
scope="row">{{item.created | date: 'MMM dd, yyyy - H:mm'}}</th>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">
<span style="text-transform: capitalize;" class="badge" [ngClass]="{'badge-warning': item.type.indexOf('warning') !== -1, 'badge-secondary': item.type.indexOf('warning') === -1}">
{{item.type.replace("warning", "")}}
</span>
</td>
<th *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{item.provision}}</th>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{item.message}}</td>
</tr>
</tbody>
<tfoot class="grey lighten-5 w-100">
<tr>
<td colspan="4">
<mdb-table-pagination [tableEl]="tableEl" [searchDataSource]="elements"></mdb-table-pagination>
</td>
</tr>
</tfoot>
</table>

View File

@@ -0,0 +1,3 @@
.table td, .table th {
vertical-align: middle;
}

View File

@@ -0,0 +1,82 @@
import { MdbTablePaginationComponent, MdbTableDirective } from 'angular-bootstrap-md';
import { Component, OnInit, ViewChild, HostListener, AfterViewInit, ChangeDetectorRef } from '@angular/core';
import { UsersService } from '../services/users.service';
@Component({
selector: 'table-notifications',
templateUrl: './table-notifications.component.html',
styleUrls: ['./table-notifications.component.scss']
})
export class TableNotificationsComponent implements OnInit, AfterViewInit {
@ViewChild(MdbTablePaginationComponent, { static: true }) mdbTablePagination: MdbTablePaginationComponent;
@ViewChild(MdbTableDirective, { static: true }) mdbTable: MdbTableDirective;
previous: any = [];
searchText: string = '';
maxVisibleItems: number = 25;
loading: boolean = false;
elements = [];
@HostListener('input') oninput() {
this.mdbTablePagination.searchText = this.searchText;
}
constructor(private cdRef: ChangeDetectorRef, private _usersService: UsersService) {
}
private _initElements(): void {
this.mdbTable.setDataSource(this.elements);
this.elements = this.mdbTable.getDataSource();
this.previous = this.mdbTable.getDataSource();
}
ngOnInit() {
this.refreshData();
}
refreshData() {
this.loading = true;
this.searchText = "";
var sub = this._usersService.getNotifications().subscribe( res => {
sub.unsubscribe();
this.elements = res.results;
this.loading = false;
this._initElements();
});
}
ngAfterViewInit() {
this.mdbTablePagination.setMaxVisibleItemsNumberTo(this.maxVisibleItems);
this.mdbTablePagination.calculateFirstItemIndex();
this.mdbTablePagination.calculateLastItemIndex();
this.cdRef.detectChanges();
}
searchItems() {
const prev = this.mdbTable.getDataSource();
if (!this.searchText) {
this.mdbTable.setDataSource(this.previous);
this.elements = this.mdbTable.getDataSource();
}
if (this.searchText) {
this.elements = this.mdbTable.searchLocalDataBy(this.searchText);
this.mdbTable.setDataSource(prev);
}
this.mdbTablePagination.calculateFirstItemIndex();
this.mdbTablePagination.calculateLastItemIndex();
this.mdbTable.searchDataObservable(this.searchText).subscribe(() => {
this.mdbTablePagination.calculateFirstItemIndex();
this.mdbTablePagination.calculateLastItemIndex();
});
}
}

View File

@@ -7,7 +7,16 @@
<label for="search-input">Search</label>
</div>
<p *ngIf="elements && elements.length">Total: {{elements.length}}</p>
<div style="padding: 5px 0px;">
<button *ngIf="loading" mdbBtn color="grey" outline="true" type="button" size="sm" disabled mdbWavesEffect>
<span>Refreshing...</span>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</button>
<button *ngIf="!loading" (click)="refreshData();" mdbBtn type="button" size="sm" color="grey" outline="true" mdbWavesEffect>
Refresh<mdb-icon fas icon="redo-alt" class="ml-1" ></mdb-icon>
</button>
<span *ngIf="elements && elements.length">Total: {{elements.length}}</span>
</div>
<table mdbTable #tableEl="mdbTable" stickyHeader="true" hover="true" class="z-depth-1 table table-sm">
<thead class="sticky-top">
@@ -16,7 +25,8 @@
<th [mdbTableSort]="elements" sortBy="created" >Prov. Date <mdb-icon fas icon="sort"></mdb-icon></th>
<th [mdbTableSort]="elements" sortBy="user.displayName">User <mdb-icon fas icon="sort"></mdb-icon></th>
<th [mdbTableSort]="elements" sortBy="scenario">Scenario <mdb-icon fas icon="sort"></mdb-icon></th>
<th [mdbTableSort]="elements" sortBy="statusVms">Status VMs (Running time)<mdb-icon fas icon="sort"></mdb-icon></th>
<th [mdbTableSort]="elements" sortBy="isExternalAccess">Ext. Access? <mdb-icon fas icon="sort"></mdb-icon></th>
<th [mdbTableSort]="elements" sortBy="statusVms">VMs (Running time)<mdb-icon fas icon="sort"></mdb-icon></th>
<th [mdbTableSort]="elements" sortBy="status">Status <mdb-icon fas icon="sort"></mdb-icon></th>
<th style="border-left:2px solid #ccc;">DestroyID</th>
<th>Dest. Date</th>
@@ -32,6 +42,7 @@
<td (click)="openInfoModal(provision)" *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.created | date: 'MMM dd, yyyy - H:mm'}}</td>
<td (click)="openInfoModal(provision)" *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)" class="ell" title="{{provision.path}}" >{{provision.user.displayName}}</td>
<td (click)="openInfoModal(provision)"*ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.scenario}}</td>
<td style="text-align: center;" (click)="openInfoModal(provision)"*ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)"><mdb-icon *ngIf="provision.isExternalAccess" fas icon="check"></mdb-icon></td>
<td (click)="openInfoModal(provision)" *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">
<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>
@@ -84,6 +95,9 @@
<button style="margin-right: 3px;" title="Remove entry" *ngIf="provision.isDestroyed && provision.destroy.status === 'destroyed'" (click)="openConfirmDeleteModal(provision);" class="lui-button lui-text-danger">
<span class="lui-icon lui-icon--bin" aria-hidden="true"></span>
</button>
<button style="margin-right: 3px;" title="Extend Running VMs 4 days" *ngIf="!provision.isDestroyed && provision.status === 'provisioned' && provision.statusVms === 'Running'" (click)="extend(provision)" class="lui-button">
+4d
</button>
</td>
</tr>
</tbody>

View File

@@ -21,6 +21,10 @@
}
}
tr:hover {
.table tr:hover {
cursor: pointer;
}
.table td {
vertical-align: middle;
}

View File

@@ -15,7 +15,6 @@ import { ScenariosService } from '../services/scenarios.service';
export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterViewInit {
scenarios;
provisions;
subscription: Subscription;
filter = {
showDestroyed : false
@@ -23,6 +22,7 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
filterParams : any = {
isDestroyed: false
};
loading: boolean = false;
pagingIsDisabled: Boolean = false;
@@ -63,10 +63,10 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
p._scenario = this.scenarios.filter(s => s.name === p.scenario);
this._provisionsService.timeRunning(p);
});
if ( !this.provisions ) {
this.provisions = provisions;
if ( this.elements.length === 0 ) {
this.elements = provisions;
} else {
this.provisions.forEach( function(p, index, object) {
this.elements.forEach( function(p, index, object) {
let found = provisions.filter(a=>a._id.toString() === p._id.toString());
if ( found.length ) {
p.status = found[0].status;
@@ -81,14 +81,13 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
}.bind(this));
provisions.forEach(function(p) {
let found = this.provisions.filter(a=>a._id.toString() === p._id.toString());
let found = this.elements.filter(a=>a._id.toString() === p._id.toString());
if (found.length === 0){
this.provisions.unshift(p);
this.elements.unshift(p);
}
}.bind(this));
}
this.elements = this.provisions;
this._initElements();
}
@@ -99,9 +98,12 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
scenariosSub.unsubscribe();
this.scenarios = res.results;
this.subscription = timer(0, 8000).pipe( switchMap(() => this._provisionsService.getProvisionsAdmin(this.filterParams) ) ).subscribe(provisions => {
/*this.subscription = timer(0, 8000).pipe( switchMap(() => this._provisionsService.getProvisionsAdmin(this.filterParams) ) ).subscribe(provisions => {
this._process(provisions.results);
});
});*/
this.refreshData();
});
//this._initElements();
@@ -289,11 +291,14 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
});
}
private _refresh(): void {
this.provisions = null;
refreshData() {
this.elements = [];
this.loading = true;
this.searchText = "";
var instantSubs = this._provisionsService.getProvisionsAdmin(this.filterParams).subscribe( provisions=>{
instantSubs.unsubscribe();
this._process(provisions.results);
this.loading = false;
this._process(provisions.results);
});
}
@@ -302,7 +307,20 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
if ( !this.filter.showDestroyed ) {
this.filterParams.isDestroyed = false;
}
this._refresh();
this.refreshData();
}
extend(provision) : void {
this._provisionsService.extend(provision._id.toString(), provision.user._id).subscribe( res => {
provision.countExtend = res.countExtend;
provision.timeRunning = res.timeRunning;
provision.runningFrom = res.runningFrom;
this._provisionsService.timeRunning(provision);
this._alertService.showAlert({
type: 'alert-primary',
text: `Running period extended another ${this._provisionsService.RUNNING_PERIOD} days (from now) for provision '${provision.scenario}'`
});
})
}
}

View File

@@ -13,6 +13,7 @@
<th>IsAdminOnly</th>
<th>IsExternal</th>
<th>IsWafPolicyAppGw</th>
<th>IsDisabled</th>
<th>NewImageName(gen)</th>
</tr>
@@ -27,6 +28,7 @@
<td style="text-align: center;" *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"><mdb-checkbox [checked]="item.isAdminOnly" [default]="false" (change)="FieldsChange(item, 'isAdminOnly', $event)"></mdb-checkbox></td>
<td style="text-align: center;" *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"><mdb-checkbox [checked]="item.isExternal" [default]="false" (change)="FieldsChange(item, 'isExternal', $event)"></mdb-checkbox></td>
<td style="text-align: center;" *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"><mdb-checkbox [checked]="item.isWafPolicyAppGw" [default]="false" (change)="FieldsChange(item, 'isWafPolicyAppGw', $event)"></mdb-checkbox></td>
<td style="text-align: center;" *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"><mdb-checkbox [checked]="item.isDisabled" [default]="false" (change)="FieldsChange(item, 'isDisabled', $event)"></mdb-checkbox></td>
<td contenteditable="true" (keyup)="changeValue(item._id, 'newImageName', $event)" (blur)="updateList(item, 'newImageName', $event)" *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{item.newImageName}}</td>
</tr>

View File

@@ -3,34 +3,51 @@
mdbInput>
<label for="search-input2">Search</label>
</div>
<div style="padding: 5px 0px;">
<button *ngIf="loading" mdbBtn color="grey" outline="true" type="button" size="sm" disabled mdbWavesEffect>
<span>Refreshing...</span>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</button>
<button *ngIf="!loading" (click)="refreshData();" mdbBtn type="button" size="sm" color="grey" outline="true" mdbWavesEffect>
Refresh<mdb-icon fas icon="redo-alt" class="ml-1" ></mdb-icon>
</button>
<span *ngIf="elements && elements.length">Total: {{elements.length}}</span>
</div>
<table mdbTable #tableEl="mdbTable" stickyHeader="true" hover="true" class="z-depth-1 table table-sm">
<thead class="sticky-top">
<tr>
<th>Id</th>
<th [mdbTableSort]="elements" sortBy="created">Created <mdb-icon fas icon="sort"></mdb-icon></th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th></th>
<th style="text-align: center;">Is Admin?</th>
<th [mdbTableSort]="elements" sortBy="lastLogin">LastLogin <mdb-icon fas icon="sort"></mdb-icon></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of elements; let i = index">
<tr mdbTableCol *ngFor="let user of elements; let i = index">
<th *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"
scope="row">{{user._id}}</th>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{user.created | date: 'MMM dd, yyyy - H:mm'}}</td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{user.displayName}}</td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">{{user.upn}}</td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"><span *ngIf="user.role">{{user.role}}</span></td>
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"><span>{{user.role}}</span></td>
<td style="text-align: center;" *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">
<mdb-checkbox *ngIf="user.role !== 'superadmin' && currentUser && currentUser._id.toString() !== user._id.toString()" [checked]="user.role === 'admin' || user.role === 'superadmin'" [default]="false" (change)="FieldsChange(user, $event)"></mdb-checkbox>
</td>
<!--<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex">
<div *ngIf="currentUser && currentUser._id.toString() !== user._id.toString()">
<button *ngIf="user.role !== 'admin' && user.role !== 'superadmin'" class="lui-button" (click)="setAdmin(user)">Set Admin</button>
<button *ngIf="user.role === 'admin'" class="lui-button" (click)="removeAdmin(user)">Remove Admin</button>
</div>
</td>
</td>-->
<td *ngIf="i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex"><span *ngIf="user.lastLogin">{{user.lastLogin | date: 'MMM dd, yyyy - H:mm'}}</span></td>
</tr>
</tbody>
<tfoot class="grey lighten-5 w-100">
<tr>
<td colspan="5">
<td colspan="6">
<mdb-table-pagination [tableEl]="tableEl" [searchDataSource]="elements"></mdb-table-pagination>
</td>
</tr>

View File

@@ -0,0 +1,3 @@
.table td {
vertical-align: middle;
}

View File

@@ -19,7 +19,7 @@ export class TableUsersComponent implements OnInit, AfterViewInit {
maxVisibleItems: number = 25;
currentUser;
loading: boolean = false;
elements = [];
@HostListener('input') oninput() {
@@ -39,12 +39,21 @@ export class TableUsersComponent implements OnInit, AfterViewInit {
}
ngOnInit() {
this.refreshData();
}
refreshData() {
this.loading = true;
this.searchText = "";
var usersSub = this._usersService.getUsers().subscribe( res => {
usersSub.unsubscribe();
res.results.forEach(u=>{
u.lastLogin = u.lastLogin || u.created;
});
this.elements = res.results;
this.loading = false;
this._initElements();
});
}
ngAfterViewInit() {
@@ -78,22 +87,11 @@ export class TableUsersComponent implements OnInit, AfterViewInit {
});
}
setAdmin(user) : void {
this._usersService.updateUser(user._id, {"role": "admin"}).subscribe( res1 => {
this._usersService.getUsers().subscribe( res => {
this.elements = res.results;
this._initElements();
});
})
}
removeAdmin(user) : void {
this._usersService.updateUser(user._id, {"role": "user"}).subscribe( res1 => {
this._usersService.getUsers().subscribe( res => {
this.elements = res.results;
this._initElements();
});
})
FieldsChange(user: any, value:any) {
var patchData = {"role": value.checked? "admin": "user"};
this._usersService.updateUser(user._id, patchData).subscribe( res1 => {
user.role = res1.role;
});
}
}

View File

@@ -1,11 +0,0 @@
#!/bin/bash
BASEDIR=$(dirname "$0")
branch=master
cd $BASEDIR/az-tf-templates
git checkout master
git checkout .
git pull origin $branch
cd $BASEDIR