qlik-embed
This commit is contained in:
19
dist/qmi-cloud/index.html
vendored
19
dist/qmi-cloud/index.html
vendored
@@ -8,8 +8,25 @@
|
||||
<link rel="icon" href="assets/favicon.ico">
|
||||
<!-- Load environment variables -->
|
||||
<script src="env.js"></script>
|
||||
<script
|
||||
crossorigin="anonymous"
|
||||
type="application/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components"
|
||||
data-host="https://qmicloud-dev.qliktech.com/proxy"
|
||||
></script>
|
||||
|
||||
<!--<script
|
||||
crossorigin="anonymous"
|
||||
type="application/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components"
|
||||
data-host="https://gear-presales.eu.qlikcloud.com"
|
||||
data-web-integration-id="n4kMLH62hvXXC84q2vdfW15WUvrUw-HU"
|
||||
data-cross-site-cookies="true"
|
||||
></script>-->
|
||||
|
||||
|
||||
<link rel="stylesheet" href="styles.fc71de1623889098932b.css"></head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script src="runtime.c51bd5b1c616d9ffddc1.js" defer></script><script src="polyfills-es5.6fef7e679f78bcc42760.js" nomodule defer></script><script src="polyfills.51f5cc3d1309de3a873d.js" defer></script><script src="scripts.1af868998801499c8755.js" defer></script><script src="main.05dfdb69a237b856ebb4.js" defer></script></body>
|
||||
<script src="runtime.c51bd5b1c616d9ffddc1.js" defer></script><script src="polyfills-es5.6fef7e679f78bcc42760.js" nomodule defer></script><script src="polyfills.51f5cc3d1309de3a873d.js" defer></script><script src="scripts.1af868998801499c8755.js" defer></script><script src="main.218f4cae1bfdda7b6e1a.js" defer></script></body>
|
||||
</html>
|
||||
|
||||
1
dist/qmi-cloud/main.05dfdb69a237b856ebb4.js
vendored
1
dist/qmi-cloud/main.05dfdb69a237b856ebb4.js
vendored
File diff suppressed because one or more lines are too long
1
dist/qmi-cloud/main.218f4cae1bfdda7b6e1a.js
vendored
Normal file
1
dist/qmi-cloud/main.218f4cae1bfdda7b6e1a.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "qmi-cloud-app",
|
||||
"version": "3.0.0",
|
||||
"version": "4.0.0",
|
||||
"scripts": {
|
||||
"start": "node -r esm server/server.js",
|
||||
"start:dev": "nodemon -r esm server/server.js",
|
||||
@@ -33,6 +33,7 @@
|
||||
"bull-arena": "^2.6.4",
|
||||
"chart.js": "^2.9.3",
|
||||
"connect-mongo": "^3.0.0",
|
||||
"cookie": "^0.6.0",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"core-js": "^2.5.4",
|
||||
"esm": "^3.2.25",
|
||||
@@ -56,6 +57,8 @@
|
||||
"rxjs": "~6.5.4",
|
||||
"swagger-jsdoc": "3.5.0",
|
||||
"swagger-ui-express": "4.1.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.14.2",
|
||||
"zone.js": "~0.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
52
server/certs/privatekey.pem
Normal file
52
server/certs/privatekey.pem
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCzYn0nGRdheWZf
|
||||
X9Gvz5Fc0RH0oeJe1HfC0E7HrDFlogkmErJyvr6vZMwjx7jxSYDbYgNJtUXmalKZ
|
||||
a3fgF4nRmlv+aPG1pNjeCThoTHTof7hMS70iAIyrbubU6v+SKvbmcWM/RkEkEXXa
|
||||
7TCOi/ZbN7S+dFQAA9ObNF6dsYHLWBQO4O8ZoydTcxTc1enc+v1Q6/Bqm7//fBY+
|
||||
VV8tIi4hiIgLTEGnsBCnH34a3Ie8i6JDXEMmD+NiZjeYLrr5YToniDu4drkrpzIp
|
||||
2P9X2wbdwI25WC6RcWLwSIkjiRY8Z/jDgowjieCDNXHVCYv47+8peeOx/Z//YuKy
|
||||
Hul2ob25KjK51k6t55NKNj3kTrzXyRIgGOdI6Q+IBl85lfhIiXPRSCobLoMVbZgG
|
||||
5qTZpEHyp2RN5CPLGlshslrQigunUzuA6sPGR+qnT8ZwGndiV8n5oaWx7fmIoK1k
|
||||
SYBe5zKehYHh4hprThvzvqqPfJwzAFAOCSJJsch7SuZGlz/2xSx0P1e27vRJ9at/
|
||||
zSKL3FIFqHpk3I0FKf/FjNZu29RPmyLIyX8uaB/Iud6cg45THaUs+GXeiLY3mt/u
|
||||
3BRADZF9AXzJZR02Ubtre1gE5PSu0NOnkudoL+9fnCK5PGFlifdtp68R8UiKAnXj
|
||||
OjMljbq+yqn5tf6DB0y82rEk0qXCoQIDAQABAoICAAhFqkF4zz1f+fVmTPGfZUko
|
||||
nPwxJj1ll9uPJpWKSRbc44YdcPGIqUYbpFb2uVPAe3riaLC9SsARjBs7ZkzvrkZ7
|
||||
qJ1b8orZU2Td0PuXoavXuUR6GPqDQvlkL6yG5QqKWlYNyYaxSIS6HLtf77rLFS0S
|
||||
Aw7l/LpUHYMikEW+WfQUwjazbw3kGlv8n8F/8ye3wqG4156FmH4sFzcq/FdvpD1I
|
||||
ogQUsVG4dUl3qCWN9jZ0IU3w6GnOFsLCtZ1EyRDXR8rAkLHKIRzfD274QkInaAgm
|
||||
09ebC5QKbLEURJAJHNI5SzKc3QswgFQcoqdb0tgZHGgcZlXJZ9eWPvT5fEj2Xsd3
|
||||
i0Q46D6y7r+dAMhhE9hdZA/Ke1fkcBgANLRRdoHHFUey1en7uQsliGfNzm1i/OR4
|
||||
AW9XdyNiB5F2LqYfnrRXEsG7FHAnCVkN1k5ur8GsBZpfYtzWof9yKXCaVUQODKqW
|
||||
0gUndI38omx8liZoFruP5VteRJgVYEIkcyAAfeG3OlIaywGmwdTGyViXhWZcihse
|
||||
3pJMsUI7P72SkSjGKAjVxCf08bwCIT6ls8V79epiUzhMnORAZC4hZxayZrBphS8U
|
||||
sVzrMGQeVexlXso3EoqjWCaM7XkbvE/8ZOBbFxyJWAkIMuD4vUuW19Fk8IOTzAL4
|
||||
VKlcq+vfZxWbA7Mq6w7vAoIBAQDeJF82Lv/IUXL3JkgYmSnze7rGg+rwsuUxBXd5
|
||||
CNPjrlwbCEIVEreQHUPOzih2Rvhrd2iCVpyCO8Kdo2AE5B7wnPePAitnEwZ6hFcD
|
||||
xMubuiRqlaILfDi8Ph7juL0mmSaz5D+HkzUdCUfvBUFXxvLyeDsNFjgqBGOahL0b
|
||||
1E1FDct/6Mltc+ATlavWxQxn+UEs8/3ssy+f4gmeLgwgevt+A8cB3ppwSFCOnu4I
|
||||
iX/vdti5MHjdUBjJGV/qjTOsUWioRXY1ay87H5RiQju3GpTQ8zlkyRv9MMll0lqi
|
||||
OQwtVvVpRd6O4qHFmgJOfPt+Z81b6cPrA0StbwAG/t5qYH7/AoIBAQDOucsdyj5k
|
||||
XmlcDo51VyEr95FsJ/SZOPOBh4MpRm4IYUgLsQTacUueTCiY17TUR6CUc+si76gb
|
||||
YYAcF0KV/17lvPosoWkdZNsqq1L8nrVq3+z2WsTkCs8m10FZ0aajf3N8WTT0v6VX
|
||||
DsOrZLsD2xxIU4f3Lf4FnL6PI5BDT83Up5paHSIhE+omAp+x7Bzf0KSpsDu2uZTH
|
||||
r8yJf1ghet+Z923/yUUTHDM8Vpt8hhoEjXWJd3r96OSIoFUwHzLgNFpiPgHkGJUU
|
||||
xRZV4Au+GeHuz9nLWPRaYVG5CxX/IbPyMzGJHchVFywIySqYK2l+VvVM17M7AuCq
|
||||
V4SZ3y7Ky15fAoIBAHahp/MwwEqDLMlOOVxhl2S/Y+yWEIbAkuNODxKlIztJJ0kM
|
||||
bPYCC+O7rTWpJTSdDBegKkDI7kYikflLgYC7LsbCnPZTa0hdga02NZ3+n9mnW8FL
|
||||
7cECcu4corRsOR9+1ItnToIhnFDIXxEHlnDA/4d7q9V+UzolI+gmETPmeelxx4ak
|
||||
k8WPB1COMrm8e7afBy5xkt6whrN0rDw8TR+fbeVLMSEPdxyVkefIekg23grNRkoH
|
||||
19Qg7Uuf8Hg7NihFRYXvqoQ2nH+Pite6lVdgq6625aSsPfVF85gb8WkG3DjuYpr4
|
||||
xDU8VLZJXAf8ePZ1itcWDRnZofiY+cPCopbet5MCggEAGb7l3wXrE1D2yjI957s8
|
||||
NF+WyuOHAPYozX71BNTyqzSCZoJbWmE1y7csbyyeJrns89Aj/qveQdq4u8bh0hCF
|
||||
3xLUDW7kynZfHUdNBI03huHwfxX643O9LNcuGmOT31TmKxxpDfo4O0lpcRUQfYBy
|
||||
W0eb7VrbAhPtX6JMOzXbKprdDFAIihoS1T0Kanw/dFhlyYRbS3x9XQk17gHgFftZ
|
||||
kbFRD8QfSCwA7YjTwIRrBRohA0fQF4NDwwhE08Nu8KFUiFu0nJW7K2UITRWkIL7U
|
||||
douIUlz3wbHRHbyVtrqZ0JYzmyIMaxyBrW5wUZdGgieOUU2j0rufA1f2+brj9vmw
|
||||
/QKCAQBDuCwPOBuZ6qFu1zBAfDyElTsIve1Uh32q+jbL5w8kkBhsiGaxFcmC3gG6
|
||||
WkdtKZKSRaf23wl4LgjvAYD5lPX+p3127pHxG+haWFkXweDMw4WrvGamm/X0H8JD
|
||||
8CX9PNWi2yxVDjNXiJ5tSbsFnhBf2CijHinrZexxlVTId6yuptOWDaq4XeYnMmKt
|
||||
FRgpnwnKAkxZKmoKIXOeSQOUVJzzxufkX3QG3CJqu60DGEDsLxM00HoP/Vxa81un
|
||||
XT+DxB6kgSYLC0a7KsHsXm8CbCoikqVEtANzIvahbsKDLNah/OWmOo9O1UphhYN+
|
||||
HQe3R2QQtfmWZ320cdhukaok4yj3
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -10,6 +10,8 @@ const axios = require('axios');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
var sessionStore;
|
||||
|
||||
// Start QuickStart here
|
||||
|
||||
var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
|
||||
@@ -167,21 +169,27 @@ module.exports.init = function(app){
|
||||
|
||||
// set up session middleware
|
||||
if (config.useMongoDBSessionStore) {
|
||||
sessionStore = new MongoStore({
|
||||
mongooseConnection: mongoose.connection,
|
||||
//url: process.env.MONGO_URI,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 10
|
||||
//clear_interval: config.mongoDBSessionMaxAge
|
||||
});
|
||||
//mongoose.connect(config.databaseUri);
|
||||
app.use(expressSession({
|
||||
secret: 'secret',
|
||||
cookie: {maxAge: config.mongoDBSessionMaxAge * 1000},
|
||||
store: new MongoStore({
|
||||
mongooseConnection: mongoose.connection,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 10
|
||||
//clear_interval: config.mongoDBSessionMaxAge
|
||||
}),
|
||||
store: sessionStore,
|
||||
resave: true,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
} else {
|
||||
app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false }));
|
||||
app.use(expressSession({
|
||||
secret: 'keyboard cat',
|
||||
resave: true,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -301,4 +309,6 @@ module.exports.ensureAuthenticatedAndIsMe = async function (req, res, next) {
|
||||
}
|
||||
}
|
||||
return res.status(401).send("Error: Unauthorized");
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.sessionStore = sessionStore;
|
||||
248
server/routes/qsProxy.js
Normal file
248
server/routes/qsProxy.js
Normal file
@@ -0,0 +1,248 @@
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
|
||||
const jsonwebtoken = require("jsonwebtoken");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const cookie = require("cookie");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const axios = require("axios");
|
||||
const { v4: uuidv4 } = require("uuid");
|
||||
const ws = require("ws");
|
||||
const WebSocketServer = ws.WebSocketServer;
|
||||
const WebSocket = ws.WebSocket;
|
||||
const passport = require('../passport');
|
||||
|
||||
|
||||
// This is the frontend application uri used for responding to requests.
|
||||
const frontendUri = "https://outstanding-desert-gatsby.glitch.me";
|
||||
|
||||
|
||||
const privKey = fs.readFileSync(path.resolve(__dirname, '..', 'certs', 'privateKey.pem'));
|
||||
const TENANT_DOMAIN = process.env["TENANT_DOMAIN"] || "gear-presales.eu.qlikcloud.com";
|
||||
const JWT_KEYID = process.env["JWT_KEYID"] || "8889be0d-6cf0-44eb-9fef-24bbc83712c5";
|
||||
const JWT_ISSUER = process.env["JWT_ISSUER"] || TENANT_DOMAIN;
|
||||
const JWT_USER_GROUPS = ["AnonJWTGroup"];
|
||||
|
||||
console.log("TENANT_DOMAIN", TENANT_DOMAIN);
|
||||
|
||||
const auth = {
|
||||
generateToken: function (user) {
|
||||
// kid and issuer have to match with the IDP config and the audience has to be qlik.api/jwt-login-session
|
||||
|
||||
const signingOptions = {
|
||||
keyid: JWT_KEYID,
|
||||
algorithm: "RS256",
|
||||
issuer: JWT_ISSUER,
|
||||
expiresIn: "60m",
|
||||
notBefore: "0s",
|
||||
audience: "qlik.api/login/jwt-session",
|
||||
};
|
||||
|
||||
// These are the claims that will be accepted and mapped anything else will be ignored. sub, subtype and name are mandatory.
|
||||
const uuid = uuidv4();
|
||||
userEmail = user.mail;
|
||||
console.log("JWT for user: ",userEmail);
|
||||
const payload = {
|
||||
jti: uuid,
|
||||
sub: `az/${user.sub}`,
|
||||
subType: "user",
|
||||
name: user.displayName,
|
||||
email: userEmail,
|
||||
email_verified: true,
|
||||
groups: JWT_USER_GROUPS,
|
||||
};
|
||||
|
||||
const token = jsonwebtoken.sign(payload, privKey, signingOptions);
|
||||
return token;
|
||||
},
|
||||
getQlikSessionCookie: async function (token) {
|
||||
|
||||
try {
|
||||
const resp = await axios(`https://${TENANT_DOMAIN}/login/jwt-session`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (resp.status === 200) {
|
||||
console.log("#QSProxy - login/jwt-session", resp.headers['set-cookie'])
|
||||
return resp.headers['set-cookie']
|
||||
.map((e) => {
|
||||
return e.split(";")[0];
|
||||
})
|
||||
.join(";");
|
||||
}
|
||||
return "";
|
||||
} catch (e) {
|
||||
console.log("ERRRRRRROE", e);
|
||||
return "";
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
const getStoreData = function (sidParsed) {
|
||||
const store = passport.sessionStore;
|
||||
return new Promise((resolve) => {
|
||||
store.get(sidParsed, (err, session) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
return resolve(session);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
router.get("/*.js", async (req, res) => {
|
||||
setCors(res);
|
||||
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// fetch resource from qlik using a redirect instead of proxy
|
||||
// This endpoint is necessary when your web application uses the capability API.
|
||||
router.get("/resources/*", async (req, res) => {
|
||||
setCors(res);
|
||||
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
router.get('/assets/*', async (req, res) => {
|
||||
setCors(res);
|
||||
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Issues the necessary pre-flight request to make sure the browser
|
||||
// knows how to work with the web application.
|
||||
router.options("/*", async (req, res) => {
|
||||
setCors(res);
|
||||
res.status(200).end();
|
||||
});
|
||||
|
||||
function setCors(res) {
|
||||
//res.set("Access-Control-Allow-Origin", "http://localhost:3000");
|
||||
res.set("Access-Control-Allow-Methods", "GET, OPTIONS");
|
||||
res.set("Access-Control-Allow-Headers", "Content-Type, x-proxy-session-id");
|
||||
res.set("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
|
||||
// Intercepts a request to one of Qlik's REST APIs and proxies the request to
|
||||
// Qlik Cloud.
|
||||
router.get("/api/v1/*", passport.ensureAuthenticated, async (req, res) => {
|
||||
|
||||
setCors(res);
|
||||
|
||||
console.log("#QSProxy: ");
|
||||
var session = req.session;
|
||||
|
||||
const reqHeaders = {};
|
||||
if (session && session.id && session.qlikSession) {
|
||||
console.log("# QSProxy - COOKIE FROM session", session.qlikSession);
|
||||
reqHeaders.cookie = decodeURIComponent(session.qlikSession);
|
||||
} else {
|
||||
|
||||
console.log("# QSProxy - New cookie request for user");
|
||||
console.log(req.user);
|
||||
// Read userEmail from cookies
|
||||
const jwtToken = auth.generateToken(req.user);
|
||||
|
||||
console.log("# QSProxy - JWT token: ", jwtToken);
|
||||
|
||||
const qlikSession = await auth.getQlikSessionCookie(jwtToken);
|
||||
|
||||
session.qlikSession = encodeURIComponent(qlikSession);
|
||||
|
||||
reqHeaders.cookie = decodeURIComponent(session.qlikSession);
|
||||
}
|
||||
|
||||
console.log("# QSProxy - qlikSession", reqHeaders.cookie);
|
||||
const { status, data } = await axios(`https://${TENANT_DOMAIN}${req.path}`, {
|
||||
headers: reqHeaders,
|
||||
});
|
||||
|
||||
|
||||
res.status(status).end(data);
|
||||
|
||||
});
|
||||
|
||||
router.get('/qlik-embed-iframe/*', async (req, res) => {
|
||||
setCors(res);
|
||||
res.redirect(`https://${TENANT_DOMAIN}${req.path}`);
|
||||
res.end();
|
||||
});
|
||||
|
||||
function init (server) {
|
||||
|
||||
// Websocket section for intercepting websocket requests from the
|
||||
// frontend application. When the front end application communicates
|
||||
// communicates with the backend using websockets, this set of
|
||||
// functions will be invoked.
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
wss.on("connection", async function connection(ws, req) {
|
||||
let isOpened = false;
|
||||
// WebSockets do not have access to session information.
|
||||
// To get the session you need to parse the 1st-party cookie.
|
||||
// This will give you access to the Qlik Cloud cookie in order
|
||||
// to proxy requests.
|
||||
const cookieString = req.headers.cookie;
|
||||
|
||||
let qlikCookie = "";
|
||||
if (cookieString) {
|
||||
const cookieParsed = cookie.parse(cookieString);
|
||||
const appCookie = cookieParsed["connect.sid"];
|
||||
if (appCookie) {
|
||||
const sidParsed = cookieParser.signedCookie(appCookie, "secret");
|
||||
var session = await getStoreData(sidParsed);
|
||||
|
||||
qlikCookie = decodeURIComponent(session.qlikSession);
|
||||
}
|
||||
}
|
||||
|
||||
const appId = req.url.match("/app/(.*)\\?")[1];
|
||||
const csrfToken= qlikCookie.match("_csrfToken=(.*);")[1];
|
||||
|
||||
var wsConnUrl = `wss://${TENANT_DOMAIN}/app/${appId}?qlik-csrf-token=${csrfToken}`;
|
||||
console.log("# QSProxy - wsConnUrl", wsConnUrl);
|
||||
const qlikWebSocket = new WebSocket(
|
||||
wsConnUrl,
|
||||
{
|
||||
headers: {
|
||||
cookie: qlikCookie,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
qlikWebSocket.on("error", console.error);
|
||||
const openPromise = new Promise((resolve) => {
|
||||
qlikWebSocket.on("open", function open() {
|
||||
console.log("# QSProxy - wsConnUrl connnection open");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
ws.on("message", async function message(data) {
|
||||
if (!isOpened) {
|
||||
await openPromise;
|
||||
isOpened = true;
|
||||
}
|
||||
|
||||
qlikWebSocket.send(data.toString());
|
||||
});
|
||||
|
||||
qlikWebSocket.on("message", function message(data) {
|
||||
console.log("wsConnUrl message", data.toString());
|
||||
ws.send(data.toString());
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
module.exports.router = router;
|
||||
module.exports.init = init;
|
||||
|
||||
@@ -16,6 +16,7 @@ const routesApiApikeys = require('./routes/api-apikeys');
|
||||
const routesApiStats = require('./routes/api-stats');
|
||||
const routesApiTraining = require('./routes/api-training');
|
||||
const swaggerUi = require('swagger-ui-express');
|
||||
const qsProxy = require("./routes/qsProxy");
|
||||
const swaggerJsdoc = require('swagger-jsdoc');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const bodyParser = require('body-parser');
|
||||
@@ -99,6 +100,7 @@ app.use("/api/v1/deployopts", routesApiDeployOpts);
|
||||
app.use("/api/v1/apikeys", routesApiApikeys);
|
||||
app.use("/api/v1/stats", routesApiStats);
|
||||
app.use("/api/v1/training", routesApiTraining);
|
||||
app.use("/proxy", qsProxy.router);
|
||||
|
||||
function _isAllowedPath(path){
|
||||
const allowedPaths = [ '/api-docs', '/arena', '/costexport', '/backendlogs', '/photos/user/' ];
|
||||
@@ -214,9 +216,7 @@ dirs.forEach(d => {
|
||||
/**
|
||||
* Start App
|
||||
*/
|
||||
app.listen(3000, () => {
|
||||
console.log(`Server listening on port 3000`)
|
||||
});
|
||||
var server;
|
||||
|
||||
if ( process.env.CERT_PFX_PASSWORD && process.env.CERT_PFX_FILENAME) {
|
||||
var optionsHttps = {
|
||||
@@ -224,9 +224,15 @@ if ( process.env.CERT_PFX_PASSWORD && process.env.CERT_PFX_FILENAME) {
|
||||
passphrase: process.env.CERT_PFX_PASSWORD
|
||||
};
|
||||
|
||||
https.createServer(optionsHttps, app).listen(3100, function(){
|
||||
server = https.createServer(optionsHttps, app).listen(3100, function(){
|
||||
console.log(`Secure server listening on port 3100`);
|
||||
});
|
||||
} else {
|
||||
server = app.listen(3000, () => {
|
||||
console.log(`Server listening on port 3000`)
|
||||
});
|
||||
}
|
||||
|
||||
qsProxy.init(server);
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule, SecurityContext } from '@angular/core';
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
@@ -11,6 +11,7 @@ import { AuthGuard } from './services/auth.guard';
|
||||
import { ProvisionsService } from './services/provisions.service';
|
||||
import { ScenariosService } from './services/scenarios.service';
|
||||
import { UsersService } from './services/users.service';
|
||||
import { QlikService } from './services/qs.service';
|
||||
import { MDBBootstrapModule } from 'angular-bootstrap-md';
|
||||
import { MarkdownModule, MarkedOptions, MarkedRenderer } from 'ngx-markdown';
|
||||
|
||||
@@ -113,7 +114,7 @@ export function markedOptions(): MarkedOptions {
|
||||
SessionModalComponent,
|
||||
SessionInfoModalComponent,
|
||||
UserModalComponent,
|
||||
TableSessionsComponent
|
||||
TableSessionsComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@@ -138,8 +139,10 @@ export function markedOptions(): MarkedOptions {
|
||||
AuthGuard,
|
||||
FeatureGuard,
|
||||
StatsService,
|
||||
TrainingService
|
||||
TrainingService,
|
||||
QlikService
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
bootstrap: [AppComponent],
|
||||
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
@@ -3,5 +3,26 @@
|
||||
<p>
|
||||
Cost dataset with history data from <b>June 2021 (Azure)</b> and <b>January 2022 (AWS)</b>. Updated on daily basis.
|
||||
</p>
|
||||
<!--
|
||||
<iframe id="cost-analysis" *ngIf="qcsSheetUrl" class="cost-analysis" [src]="qcsSheetUrl" width="100%" height="1000px"></iframe>
|
||||
|
||||
-->
|
||||
|
||||
<div style="width:200px; height: 200px">
|
||||
<qlik-embed
|
||||
id="visualization"
|
||||
ui="object"
|
||||
app-id="1d2a43cf-8bc7-422c-90bd-d021cb232776"
|
||||
object-id="GmhFFG"
|
||||
></qlik-embed>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; height:1000px;">
|
||||
<qlik-embed
|
||||
ui="analytics/sheet"
|
||||
app-id="1d2a43cf-8bc7-422c-90bd-d021cb232776"
|
||||
object-id="1ad3eb62-330d-45ac-8456-a6c8a76e044b"
|
||||
>
|
||||
</qlik-embed>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,6 +17,7 @@ export class CostComponent implements OnInit,OnDestroy {
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
/*
|
||||
this.subs = this._provisionsService.getCurrentQCSUser().subscribe( value => {
|
||||
console.log("value", value);
|
||||
this.qcsSheetUrl = this.sanitizer.bypassSecurityTrustResourceUrl(`${this._provisionsService.urlQlikServer}/single/?appid=1d2a43cf-8bc7-422c-90bd-d021cb232776&sheet=1ad3eb62-330d-45ac-8456-a6c8a76e044b&theme=breeze&opt=ctxmenu,currsel`);
|
||||
@@ -24,13 +25,12 @@ export class CostComponent implements OnInit,OnDestroy {
|
||||
console.log('oops', error);
|
||||
const url = `${this._provisionsService.urlQlikServer}/login?qlik-web-integration-id=${this._provisionsService.webIntegrationId}&returnto=${window.location.href}`;
|
||||
window.location.href = url;
|
||||
});
|
||||
|
||||
|
||||
});*/
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.unsubscribe();
|
||||
//this.subs.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="contentbox">
|
||||
<div *ngIf="costData">
|
||||
<b>{{getCost(info._id)}} spent so far</b>
|
||||
</div>
|
||||
<div *ngIf="!info.isDestroyed && info.statusVms">
|
||||
Resources status:
|
||||
<span>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AlertService } from '../services/alert.service';
|
||||
import { AuthGuard } from '../services/auth.guard';
|
||||
import { ProvisionsService } from '../services/provisions.service';
|
||||
import { ModalConfirmComponent } from './confirm.component';
|
||||
import { QlikService } from '../services/qs.service';
|
||||
|
||||
@Component({
|
||||
selector: 'qmi-modalinfo',
|
||||
@@ -19,8 +20,9 @@ export class ModalInfoComponent implements OnInit, OnDestroy {
|
||||
currentUser;
|
||||
private _userId;
|
||||
sharedUsers;
|
||||
costData;
|
||||
|
||||
constructor( private modalService: MDBModalService, private _alertService: AlertService, private router: Router, public modalRef: MDBModalRef, private _provisionsService: ProvisionsService, private _auth: AuthGuard ) {
|
||||
constructor( private modalService: MDBModalService, private _alertService: AlertService, private router: Router, public modalRef: MDBModalRef, private _provisionsService: ProvisionsService, private _auth: AuthGuard, private _qlikService: QlikService ) {
|
||||
this._auth.getUserInfo().subscribe( value => {
|
||||
this.currentUser = value;
|
||||
this._userId = value? value._id : null;
|
||||
@@ -49,6 +51,15 @@ export class ModalInfoComponent implements OnInit, OnDestroy {
|
||||
this._refreshShares();
|
||||
mains.unsubscribe();
|
||||
});
|
||||
|
||||
this._qlikService.costSubject.subscribe(function(value){
|
||||
console.log("value", value);
|
||||
this.costData = value || {};
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
getCost(id){
|
||||
return this.costData && this.costData[id]? this.costData[id].dollars : "n/a";
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="contentbox">
|
||||
<div *ngIf="costData">
|
||||
<b>{{getCost(provision._id)}} spent so far</b>
|
||||
</div>
|
||||
<div *ngIf="!provision.isDestroyed && provision.statusVms">
|
||||
Resources status:
|
||||
<span>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { MDBModalService } from 'angular-bootstrap-md';
|
||||
import { AlertService } from '../services/alert.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ModalConfirmComponent } from '../modals/confirm.component';
|
||||
import { QlikService } from '../services/qs.service';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -25,9 +26,10 @@ export class ProvComponent implements OnInit {
|
||||
logShow: boolean = false;
|
||||
sharedUsers;
|
||||
acl = { "type": "view"};
|
||||
costData;
|
||||
|
||||
|
||||
constructor( private modalService: MDBModalService, private _alertService: AlertService, private _provisionsService: ProvisionsService, private _auth: AuthGuard, private route: ActivatedRoute ) {
|
||||
constructor( private modalService: MDBModalService, private _alertService: AlertService, private _provisionsService: ProvisionsService, private _auth: AuthGuard, private route: ActivatedRoute, private _qlikService: QlikService ) {
|
||||
|
||||
this._auth.getUserInfo().subscribe( value => {
|
||||
this._userId = value? value._id : null;
|
||||
@@ -60,6 +62,20 @@ export class ProvComponent implements OnInit {
|
||||
this._refreshEvents();
|
||||
this._refreshShares();
|
||||
});
|
||||
|
||||
this._qlikService.costSubject.subscribe(function(value){
|
||||
console.log("value", value);
|
||||
this.costData = value || {};
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
getCost(id) : string {
|
||||
|
||||
if (this.costData && this.costData[id] ) {
|
||||
return this.costData[id].dollars;
|
||||
}
|
||||
return "n/a";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<app-logs [show]="logShow" (onClose)="onLogsClose()" [type]="logstype" [selectedprov]="selectedprov"></app-logs>
|
||||
<div style="margin-top: 80px; min-height: 650px;">
|
||||
<!--<div style="width:200px; height: 200px">
|
||||
<qlik-embed
|
||||
id="visualization"
|
||||
ui="object"
|
||||
app-id="1d2a43cf-8bc7-422c-90bd-d021cb232776"
|
||||
object-id="020620cf-458f-4f2a-92b2-84dfb21d593a"
|
||||
></qlik-embed>
|
||||
</div>-->
|
||||
<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">
|
||||
@@ -10,7 +18,10 @@
|
||||
<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 class="contentbox">
|
||||
<div *ngIf="costData">
|
||||
<b>{{getCost(provision._id)}} spent so far</b>
|
||||
</div>
|
||||
<div *ngIf="provision.statusVms">
|
||||
Resources status:
|
||||
<span>
|
||||
|
||||
@@ -83,9 +83,9 @@ h1 {
|
||||
.contentbox {
|
||||
font-size: 12px;
|
||||
padding: 10px;
|
||||
min-height: 200px;
|
||||
min-height: 220px;
|
||||
&.destroyed {
|
||||
min-height: 100px;
|
||||
min-height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, AfterViewInit } from '@angular/core';
|
||||
import { ProvisionsService } from '../services/provisions.service';
|
||||
import { Subscription, timer} from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
@@ -9,6 +9,7 @@ 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';
|
||||
import { QlikService } from '../services/qs.service';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -17,7 +18,7 @@ import { ShareModalComponent } from '../modals/share.component';
|
||||
styleUrls: ['./provisions.component.scss'],
|
||||
providers: [ProvisionsService]
|
||||
})
|
||||
export class ProvisionsComponent implements OnInit {
|
||||
export class ProvisionsComponent implements OnInit, AfterViewInit {
|
||||
|
||||
private _userId;
|
||||
provisions;
|
||||
@@ -28,10 +29,15 @@ export class ProvisionsComponent implements OnInit {
|
||||
logstype: String = 'provision';
|
||||
public selectedprov: Object = null;
|
||||
history: Boolean = false;
|
||||
costData: any = null;
|
||||
trigram;
|
||||
|
||||
|
||||
constructor(private modalService: MDBModalService, private _alertService: AlertService, private _provisionsService: ProvisionsService, private _auth: AuthGuard) {
|
||||
|
||||
constructor(private modalService: MDBModalService, private _alertService: AlertService, private _provisionsService: ProvisionsService, private _auth: AuthGuard, private _qlikService: QlikService) {
|
||||
this._auth.getUserInfo().subscribe( value => {
|
||||
this._userId = value? value._id : null;
|
||||
this.trigram = value? value.upn.split("@")[0] : null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,6 +62,13 @@ export class ProvisionsComponent implements OnInit {
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
this._qlikService.costSubject.subscribe(function(value){
|
||||
console.log("value", value);
|
||||
this.costData = value || {};
|
||||
}.bind(this));
|
||||
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@@ -70,6 +83,15 @@ export class ProvisionsComponent implements OnInit {
|
||||
this._provisionsService.setSelectedProv(provision);
|
||||
}
|
||||
|
||||
async ngAfterViewInit() {
|
||||
//let qsEmbed = document.getElementById("visualization");
|
||||
//this._qlikService.setCostDataAsync(qsEmbed);
|
||||
}
|
||||
|
||||
getCost(id){
|
||||
return this.costData && this.costData[id]? this.costData[id].dollars : "n/a";
|
||||
}
|
||||
|
||||
|
||||
del(provision): void {
|
||||
this._provisionsService.delProvision(provision._id.toString(), this._userId).subscribe( res => {
|
||||
|
||||
94
src/app/services/qs.service.ts
Normal file
94
src/app/services/qs.service.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Injectable, EventEmitter } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class QlikService {
|
||||
|
||||
costSubject = new BehaviorSubject(null);
|
||||
costData;
|
||||
|
||||
private formatMoney (num) {
|
||||
const dollars = new Intl.NumberFormat(`en-US`, {
|
||||
currency: `USD`,
|
||||
style: 'currency',
|
||||
}).format(num);
|
||||
return dollars;
|
||||
}
|
||||
|
||||
async setCostDataAsync( qsEmbed ) {
|
||||
console.log("qsEmbed", qsEmbed);
|
||||
if (!this.costData ) {
|
||||
const refApi = await qsEmbed.getRefApi();
|
||||
const doc = await refApi.getDoc();
|
||||
const properties = {
|
||||
qInfo: {
|
||||
qType: 'my-straight-hypercube',
|
||||
},
|
||||
qHyperCubeDef: {
|
||||
qDimensions: [
|
||||
{
|
||||
qDef: { qFieldDefs: ['_id'] },
|
||||
},
|
||||
{
|
||||
qDef: { qFieldDefs: ['WD.Trigram'] },
|
||||
},
|
||||
{
|
||||
qDef: { qFieldDefs: ['isActive'] },
|
||||
},
|
||||
{
|
||||
qDef: { qFieldDefs: ['isDeleted'] },
|
||||
}
|
||||
],
|
||||
qMeasures: [
|
||||
{
|
||||
qDef: { qDef: '=Sum(CostUSD)' },
|
||||
},
|
||||
],
|
||||
qInitialDataFetch: [
|
||||
{
|
||||
qHeight: 2000,
|
||||
qWidth: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
/*const field1 = await doc.getField("WD.Trigram");
|
||||
await field1.selectValues([
|
||||
{
|
||||
qText: trigram,
|
||||
},
|
||||
], false, true);
|
||||
*/
|
||||
//await field1.lock();
|
||||
await doc.clearAll();
|
||||
const field1 = await doc.getField("isActive");
|
||||
await field1.selectValues([
|
||||
{
|
||||
qText: "YES",
|
||||
}
|
||||
], false, true);
|
||||
|
||||
|
||||
const obj = await doc.createSessionObject(properties);
|
||||
const layout = await obj.getLayout();
|
||||
let cost = {};
|
||||
console.log(layout);
|
||||
layout.qHyperCube.qDataPages[0].qMatrix.forEach(m=>{
|
||||
cost[m[0].qText.toLowerCase()] = {amount: m[4].qNum, dollars: this.formatMoney(m[4].qNum)};
|
||||
|
||||
});
|
||||
this.costData = cost;
|
||||
this.costSubject.next(this.costData);
|
||||
|
||||
doc.clearAll();
|
||||
|
||||
} else {
|
||||
console.log("DO NOTHING");
|
||||
this.costSubject.next(this.costData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
<thead class="sticky-top white-text">
|
||||
<tr>
|
||||
<th style="width: 255px;">Provision ID</th>
|
||||
<th>Cost</th>
|
||||
<th [mdbTableSort]="elements" sortBy="created" >Prov. Date <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="deployOpts.location">Region <mdb-icon fas icon="sort"></mdb-icon></th>
|
||||
@@ -43,6 +44,7 @@
|
||||
<a href (click)="showLogs($event, provision, 'provision')" class="lui-text-info">{{ provision._id }}</a>
|
||||
<div style="font-size: 10px;">{{provision.description}}</div>
|
||||
</td>
|
||||
<td (click)="openInfoModal(provision._id)" *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{getCost(provision._id)}}</td>
|
||||
<td (click)="openInfoModal(provision._id)" *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">{{provision.created | date: 'dd-MM-yyyy H:mm'}}</td>
|
||||
<td (click)="openInfoModal(provision._id)" *ngIf="pagingIsDisabled || (i+1 >= mdbTablePagination.firstItemIndex && i < mdbTablePagination.lastItemIndex)">
|
||||
<div><mdb-icon mdbTooltip="External Access enabled" fas icon="globe-americas" *ngIf="provision.isExternalAccess" aria-hidden="true"></mdb-icon> {{provision.scenario}}</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ModalConfirmComponent } from '../modals/confirm.component';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ProvisionModalComponent } from '../modals/edit-provision.component';
|
||||
import * as moment from 'moment-timezone';
|
||||
import { QlikService } from '../services/qs.service';
|
||||
|
||||
@Component({
|
||||
selector: 'table-provisions',
|
||||
@@ -48,11 +49,12 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
|
||||
logShow: boolean = false;
|
||||
logstype: String = 'provision';
|
||||
timeInterval;
|
||||
costData;
|
||||
|
||||
maxVisibleItems: number = 25;
|
||||
|
||||
|
||||
constructor(private modalService: MDBModalService, private _alertService: AlertService, private cdRef: ChangeDetectorRef, private _provisionsService: ProvisionsService) {
|
||||
constructor(private modalService: MDBModalService, private _alertService: AlertService, private cdRef: ChangeDetectorRef, private _provisionsService: ProvisionsService, private _qlikService: QlikService) {
|
||||
|
||||
}
|
||||
|
||||
@@ -110,6 +112,21 @@ export class TableProvisionsAdminComponent implements OnInit, OnDestroy, AfterVi
|
||||
this.timeInterval = setInterval(() => {
|
||||
this.nowTimeUTC = moment().utc().format("H:mm");
|
||||
}, 60 * 1000);
|
||||
|
||||
|
||||
this._qlikService.costSubject.subscribe(function(value){
|
||||
console.log("value", value);
|
||||
this.costData = value || {};
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
getCost(id) : string {
|
||||
|
||||
if (this.costData && this.costData[id] ) {
|
||||
return this.costData[id].dollars;
|
||||
}
|
||||
return "n/a";
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,8 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, AfterViewInit } from '@angular/core';
|
||||
import { AuthGuard } from '../../services/auth.guard';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ProvisionsService } from 'src/app/services/provisions.service';
|
||||
import { QlikService } from 'src/app/services/qs.service';
|
||||
|
||||
const _adminRoles = ['superadmin', 'admin'];
|
||||
|
||||
@@ -9,12 +11,12 @@ const _adminRoles = ['superadmin', 'admin'];
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss']
|
||||
})
|
||||
export class HeaderComponent implements OnInit {
|
||||
export class HeaderComponent implements OnInit, AfterViewInit {
|
||||
|
||||
user;
|
||||
subs: Subscription;
|
||||
|
||||
constructor( private _auth: AuthGuard){
|
||||
constructor( private _auth: AuthGuard, private _qlikService: QlikService){
|
||||
this.subs = this._auth.getUserInfo().subscribe( value => {
|
||||
this.user = value;
|
||||
});
|
||||
@@ -39,6 +41,9 @@ export class HeaderComponent implements OnInit {
|
||||
event.target.src = 'https://ui-avatars.com/api/?name='+user.displayName+'&background=random&size=40'
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
}
|
||||
|
||||
private _isAdmin(user) : boolean {
|
||||
return user && _adminRoles.includes(user.role);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
<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="costData">
|
||||
<b>{{getCost(provision._id)}} spent so far</b>
|
||||
</div>
|
||||
<div *ngIf="provision.statusVms">
|
||||
Resources status:
|
||||
<span>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ModalInfoComponent } from '../modals/modalinfo.component';
|
||||
import { ProvisionModalComponent } from '../modals/edit-provision.component';
|
||||
import { ShareModalComponent } from '../modals/share.component';
|
||||
import { ModalConfirmComponent } from '../modals/confirm.component';
|
||||
import { QlikService } from '../services/qs.service';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -25,11 +26,11 @@ export class UserDashboardComponent implements OnInit, OnDestroy{
|
||||
public selectedprov: Object = null;
|
||||
history: Boolean = false;
|
||||
|
||||
|
||||
costData;
|
||||
logShow: boolean = false;
|
||||
logstype: String = 'provision';
|
||||
|
||||
constructor(private modalService: MDBModalService,private route: ActivatedRoute, private _provisionsService: ProvisionsService, private _userService: UsersService) { }
|
||||
constructor(private modalService: MDBModalService,private route: ActivatedRoute, private _provisionsService: ProvisionsService, private _userService: UsersService, private _qlikService: QlikService) { }
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
@@ -49,6 +50,19 @@ export class UserDashboardComponent implements OnInit, OnDestroy{
|
||||
this.user = user;
|
||||
})
|
||||
});
|
||||
this._qlikService.costSubject.subscribe(function(value){
|
||||
console.log("value", value);
|
||||
this.costData = value || {};
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
getCost(id) : string {
|
||||
|
||||
if (this.costData && this.costData[id] ) {
|
||||
return this.costData[id].dollars;
|
||||
}
|
||||
return "n/a";
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
||||
@@ -8,6 +8,23 @@
|
||||
<link rel="icon" href="assets/favicon.ico">
|
||||
<!-- Load environment variables -->
|
||||
<script src="env.js"></script>
|
||||
<script
|
||||
crossorigin="anonymous"
|
||||
type="application/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components"
|
||||
data-host="https://qmicloud-dev.qliktech.com/proxy"
|
||||
></script>
|
||||
|
||||
<!--<script
|
||||
crossorigin="anonymous"
|
||||
type="application/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/@qlik/embed-web-components"
|
||||
data-host="https://gear-presales.eu.qlikcloud.com"
|
||||
data-web-integration-id="n4kMLH62hvXXC84q2vdfW15WUvrUw-HU"
|
||||
data-cross-site-cookies="true"
|
||||
></script>-->
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
|
||||
30
yarn.lock
30
yarn.lock
@@ -2856,11 +2856,11 @@ aws4@^1.8.0:
|
||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||
|
||||
axios@^0.21.1:
|
||||
version "0.21.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
|
||||
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
|
||||
version "0.21.4"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
|
||||
dependencies:
|
||||
follow-redirects "^1.10.0"
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
babel-code-frame@^6.22.0:
|
||||
version "6.26.0"
|
||||
@@ -3979,6 +3979,11 @@ cookie@0.4.0:
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||
|
||||
cookie@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
|
||||
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
||||
|
||||
copy-concurrently@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
|
||||
@@ -5639,11 +5644,16 @@ flush-write-stream@^1.0.0:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
|
||||
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
|
||||
|
||||
follow-redirects@^1.14.0:
|
||||
version "1.15.3"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
|
||||
integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
|
||||
|
||||
font-awesome@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
|
||||
@@ -12083,6 +12093,11 @@ uuid@^8.3.0, uuid@^8.3.2:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuid@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
|
||||
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
|
||||
|
||||
valid-url@^1.0.6:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200"
|
||||
@@ -12457,6 +12472,11 @@ ws@^7.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
|
||||
integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
|
||||
|
||||
ws@^8.14.2:
|
||||
version "8.14.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
|
||||
integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==
|
||||
|
||||
ws@~3.3.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
|
||||
|
||||
Reference in New Issue
Block a user