diff --git a/src/common/user-store.ts b/src/common/user-store.ts index 731afcbd18..46793b43fa 100644 --- a/src/common/user-store.ts +++ b/src/common/user-store.ts @@ -19,6 +19,7 @@ export interface UserStoreModel { seenContexts: string[]; preferences: UserPreferences; token: Token; + lastLoggedInUser: string; } export interface UserPreferences { @@ -113,6 +114,8 @@ export class UserStore extends BaseStore { refreshToken: "" } + @observable lastLoggedInUser = ""; + get isNewVersion() { return semver.gt(getAppVersion(), this.lastSeenAppVersion); } @@ -188,8 +191,11 @@ export class UserStore extends BaseStore { isTokenExpired(validTill: number): boolean { // Create a current UnixTime style date in ms - const timeNow = Date.now(); - if ((new Date(validTill * 1000).getMinutes() - new Date().getMinutes()) / 1000 / 60 < 0) { + const timeNow = Math.round(Date.now()); + console.log(`isTokenExpired: timeNow: ${new Date(timeNow).toString()}`); + console.log(`isTokenExpired: validTill: ${new Date(validTill).toString()}`); + //if ((new Date(validTill).getMinutes() - new Date().getMinutes()) / 1000 / 60 < 0) { + if (timeNow > validTill) { return true; } return false; @@ -197,6 +203,7 @@ export class UserStore extends BaseStore { @action setTokenDetails(token: string, refreshToken: string) { + let tokenDecoded = this.decodeToken(token); let refreshTokenDecoded = this.decodeToken(refreshToken); @@ -205,15 +212,21 @@ export class UserStore extends BaseStore { this.token.preferredUserName = tokenDecoded.preferred_username; // Create a current UnixTime style date in secs - const timeNow = Math.round(Date.now() / 1000); - this.token.tokenValidTill = timeNow + tokenDecoded.exp; - this.token.refreshTokenValidTill = timeNow + refreshTokenDecoded.exp; + this.token.tokenValidTill = tokenDecoded.exp * 1000; + this.token.refreshTokenValidTill = refreshTokenDecoded.exp * 1000; console.info('The saved token object is: ' + JSON.stringify(this.token)); + const tokenSavedAt = new Date(); + console.log(`keycloak token retrieved at: ${tokenSavedAt.toLocaleTimeString()}`); console.info('Check if token date is expired: ' + this.isTokenExpired(this.token.tokenValidTill)); } + @action + saveLastLoggedInUser(user: string) { + this.lastLoggedInUser = user; + } + @action protected async fromStore(data: Partial = {}) { const { lastSeenAppVersion, seenContexts = [], preferences, kubeConfigPath, token } = data @@ -235,6 +248,7 @@ export class UserStore extends BaseStore { seenContexts: Array.from(this.seenContexts), preferences: this.preferences, token: this.token, + lastLoggedInUser: this.lastLoggedInUser, } return toJS(model, { recurseEverything: true, diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 48bf01ee02..681fc19fc1 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -125,8 +125,12 @@ export class Cluster implements ClusterModel { } async activate(init = false) { - logger.info(`[CLUSTER]: activate`, this.getMeta()); + const clusterActivatedAt = new Date(); + logger.info(`[CLUSTER]: activate@${clusterActivatedAt.toString()}`, this.getMeta()); await this.whenInitialized; + logger.info("[CLUSTER]: activate now initialized") + logger.info(`[CLUSTER]: activate eventDisposers length: ${this.eventDisposers.length}`); + logger.info(`[CLUSTER]: activate eventDisposers: ${this.eventDisposers}`); if (!this.eventDisposers.length) { this.bindEvents(); } @@ -158,7 +162,8 @@ export class Cluster implements ClusterModel { @action async refresh() { - logger.info(`[CLUSTER]: refresh`, this.getMeta()); + const clusterRefreshAt = new Date(); + logger.info(`[CLUSTER]: refresh@${clusterRefreshAt.toString()}`, this.getMeta()); await this.refreshConnectionStatus(); // refresh "version", "online", etc. if (this.accessible) { this.kubeCtl = new Kubectl(this.version) diff --git a/src/main/decc-manager.ts b/src/main/decc-manager.ts index 3bf3f63837..6dd169cadd 100644 --- a/src/main/decc-manager.ts +++ b/src/main/decc-manager.ts @@ -1,7 +1,7 @@ import "../common/cluster-ipc"; import type http from "http" import { autorun } from "mobx"; -import { ClusterStore, clusterStore, getClusterIdFromHost } from "../common/cluster-store" +import { ClusterModel, ClusterStore, clusterStore, getClusterIdFromHost } from "../common/cluster-store" import { Cluster } from "./cluster" import logger from "./logger"; import { apiKubePrefix } from "../common/vars"; @@ -11,6 +11,10 @@ import * as request from "request-promise-native"; import { v4 as uuid } from "uuid"; import {kubeconfig} from '../common/utils/k8sTemplates'; import YAML from 'yaml'; +import { readFile } from "fs-extra" +import { getNodeWarningConditions, loadConfig, podHasIssues } from "../common/kube-helpers" +import { customRequestPromise } from "../common/request"; +import orderBy from "lodash/orderBy"; const ignoredDECCNamespaces = [ 'kube-system', 'kube-public', 'openstack-provider-system', 'system', @@ -18,145 +22,245 @@ const ignoredDECCNamespaces = [ ]; export class DECCManager { - constructor(protected keycloakServer: http.Server, protected deccURL: string) { + constructor(protected deccURL: string) { } - // auto-init clusters - // autorun(() => { - // clusterStore.clusters.forEach(cluster => { - // if (!cluster.initialized) { - // logger.info(`[CLUSTER-MANAGER]: init cluster`, cluster.getMeta()); - // cluster.init(port); - // } - // }); - // }); - // // auto-stop removed clusters - // autorun(() => { - // const removedClusters = Array.from(clusterStore.removedClusters.values()); - // if (removedClusters.length > 0) { - // const meta = removedClusters.map(cluster => cluster.getMeta()); - // logger.info(`[CLUSTER-MANAGER]: removing clusters`, meta); - // removedClusters.forEach(cluster => cluster.disconnect()); - // clusterStore.removedClusters.clear(); - // } - // }, { - // delay: 250 - // }); - // } - - getNamespacesForUser() { - var namespacesUserCanAccess: Array = workspaceStore.workspacesList; - var parsedToken = userStore.decodeToken (userStore.getTokenDetails().token); - - // get all namespaces this id has access to - const namespaces = { - method: 'GET', - url: `http://${this.deccURL}/api/v1/namespaces`, + async getNamespaces(): Promise<[]> { + const res = await customRequestPromise({ + uri: `http://${this.deccURL}/api/v1/namespaces`, headers: { 'Authorization': 'Bearer ' + userStore.getTokenDetails().token }, - json: true - }; + json: true, + resolveWithFullResponse: true, + timeout: 10000, + }); + // logger.info(`getNamespaces: res - ${JSON.stringify(res)}`); + return res.body; + } - request(namespaces) - .then(function(response) { - //API call ok.... - const deccNamespaces = response["items"]; - // logger.info(JSON.stringify(deccNamespaces)); - - deccNamespaces.forEach(function(namespace) { + async getClustersByNamespace(ns: string): Promise<[]> { + const res = await customRequestPromise({ + uri: `http://${this.deccURL}/apis/cluster.k8s.io/v1alpha1/namespaces/${ns}/clusters`, + headers: { + 'Authorization': 'Bearer ' + userStore.getTokenDetails().token + }, + json: true, + resolveWithFullResponse: true, + timeout: 10000, + }); + // logger.info(`getClustersByNamespace: res - ${JSON.stringify(res)}`); + return res.body; + } + + async getDECCNamespaces() { + try { + const res = await this.getNamespaces(); + // logger.info(`getDECCNamespaces: res - ${JSON.stringify(res)}`); + var deccNamespaces = []; + res["items"].forEach(function(namespace) { if (!ignoredDECCNamespaces.includes(namespace.metadata.name)) { - // console.log("Namespace Name: " + namespace.metadata.name); - let ns = namespace.metadata.name; - //console.log("parsedToken.iam_roles: ", parsedToken.iam_roles); - if (parsedToken.iam_roles.includes(`m:kaas:${ns}@reader`) || parsedToken.iam_roles.includes(`m:kaas:${ns}@writer`)) { - // add namespace to workspaceStore if not present - console.log(`User: ${parsedToken.preferred_username} has access to namespace: ${ns}`); - if (!workspaceStore.getByName(ns)) { - workspaceStore.saveWorkspace({id: uuid(), name: ns, description: `DECC Namespace: ${ns}`}); - console.log(`Added new workspace: ${ns}`); - namespacesUserCanAccess.push(ns); - } - }; + // logger.info(`getDECCNamespaces: Found namespace: ${namespace.metadata.name}`); + deccNamespaces.push(namespace.metadata.name); }; }); + return deccNamespaces; - return namespacesUserCanAccess; - }) - .catch(function (err) { - // API call failed... - console.log(err); + } catch (err) { + logger.error(`getDECCNamespaces: ${String(err)}`); + } + } + + getDECCNamespacesForUser(deccNamespaces, userIAMRoles: string[], username: string) { + var deccNamespacesForUser = []; + deccNamespaces.forEach(function(ns) { + if (userIAMRoles.includes(`m:kaas:${ns}@reader`) || userIAMRoles.includes(`m:kaas:${ns}@writer`)) { + // add namespace to workspaceStore if not present + //logger.info(`getDECCNamespacesForUser: User ${username} has access to namespace ${ns}`); + deccNamespacesForUser.push(ns); + } + }); + return deccNamespacesForUser; + } + + async getDECCClustersForNamespace(ns: string) { + try { + const res = await this.getClustersByNamespace(ns); + //logger.info(`getDECCClustersForNamespace: res - ${JSON.stringify(res)}`); + + var deccClustersForNamespace = []; + //API call ok.... + res["items"].forEach(function(deccCluster) { + deccClustersForNamespace.push(deccCluster); + }); + return deccClustersForNamespace; + } catch(err) { + logger.error(`getDECCClustersForNamespace: ${String(err)}`); + } + } + + addLensDECCWorkspace(ws: string) { + const wsPrefix = `decc`; + + if (!workspaceStore.getByName(`${wsPrefix}-${ws}`)) { + workspaceStore.saveWorkspace({id: uuid(), name: `${wsPrefix}-${ws}`, description: `DECC Namespace: ${ws}`}); + logger.info(`Added new workspace: ${wsPrefix}-${ws}`); + } + } + + addLensClusterToDECCWorkspace(deccCluster, idToken: string, refreshToken: string, username: string, workspace: Workspace) { + // check if cluster is already in the cluster store + var clusterPresent = false; + const clusterPrefix = `decc` + + clusterStore.getByWorkspaceId(workspace.id).forEach(cluster => { + if (cluster.preferences.clusterName === `${username}@${clusterPrefix}-${deccCluster.metadata.name}`) { + clusterPresent = true; + } + }); + + if ("status" in deccCluster && !clusterPresent) { + let ucpDashboard = `https://${deccCluster.status.providerStatus.ucpDashboard.split(":", 2).reverse()[0].substring(2)}:443`; + //logger.info(`addLensClusterToDECCWorkspace: ucpDashboard - ${ucpDashboard}`); + + const jsConfig = kubeconfig({ + username: username, + clusterName: `${clusterPrefix}-${deccCluster.metadata.name}`, + clientId: deccCluster.status.providerStatus.oidc.clientId, + idpCertificateAuthorityData: deccCluster.status.providerStatus.oidc.certificate, + idpIssuerUrl: deccCluster.status.providerStatus.oidc.issuerUrl, + server: ucpDashboard, + apiCertificate: deccCluster.status.providerStatus.apiServerCertificate, + idToken: idToken, + refreshToken: refreshToken + }); + + //console.log(`Generated kubeconfig: ${YAML.stringify(jsConfig)}`) + + let newClusters: ClusterModel[] = []; + let newCluster = new Cluster({ + id: deccCluster.metadata.uid, + contextName: `${username}@${clusterPrefix}-${deccCluster.metadata.name}`, + preferences: { + clusterName: `${username}@${clusterPrefix}-${deccCluster.metadata.name}`, + httpsProxy: undefined, + }, + kubeConfigPath: ClusterStore.embedCustomKubeConfig(deccCluster.metadata.uid, YAML.stringify(jsConfig)), + workspace: workspace.id, + }); + + newClusters.push(newCluster); + clusterStore.addCluster(...newClusters); + logger.info(`addLensClusterToDECCWorkspace: Created Cluster Name: ${username}@${clusterPrefix}-${deccCluster.metadata.name}, Cluster UCP Dashboard URL: ${ucpDashboard}`) + }; + } + + addLensClustersToDECCWorkspace(deccClusters, idToken: string, refreshToken: string, username: string, wsName: string) { + const wsPrefix = `decc`; + const workspace = workspaceStore.getByName(`${wsPrefix}-${wsName}`); + + //logger.info(`addLensClustersToDECCWorkspace: Processing clusters in Workspace ${workspace.name} for User ${username}`); + + deccClusters.forEach(cluster => { + this.addLensClusterToDECCWorkspace(cluster, idToken, refreshToken, username, workspace) }); } - addClustersToWorkspace() { - var parsedToken = userStore.decodeToken (userStore.getTokenDetails().token); - console.log(`deccURL: ${this.deccURL}`); + deleteLensDECCClustersByWorkspace(ws) { + logger.info(`deleteLensDECCClustersByWorkspace: Removing all Lens DECC clusters for Workspace ${ws.name}`) + clusterStore.removeByWorkspaceId(ws.id); + } - workspaceStore.workspacesList.forEach(function(ws) { - console.log(`Adding clusters for ws: ${ws.name}`); - let clusters = { - method: 'GET', - url: `http://a09bfce9ea3074e25b8e5e7b1df576fd-1162277427.eu-west-2.elb.amazonaws.com/apis/cluster.k8s.io/v1alpha1/namespaces/${ws.name}/clusters`, - headers: { - 'Authorization': 'Bearer ' + userStore.getTokenDetails().token - }, - json: true - }; - - request(clusters) - .then(function(response) { - //API call ok.... - const deccClusters = response["items"]; - deccClusters.forEach(function(deccCluster: object) { - // check if cluster is already in the cluster store - let clusterPresent = false; - clusterStore.getByWorkspaceId(ws.id).forEach(wsCluster => { - if (wsCluster.contextName === `${parsedToken.preferred_username}@${deccCluster.metadata.name}`) { - clusterPresent = true; - } - }); - - if ("status" in deccCluster && !clusterPresent) { - // clusterUCPURL = cluster.status. - - let ucpDashboard = `https://${deccCluster.status.providerStatus.ucpDashboard.split(":", 2).reverse()[0].substring(2)}:443`; - console.log (`ucpDashboard: ${ucpDashboard}`); - - const jsConfig = kubeconfig({ - username: parsedToken.preferred_username, - clusterName: deccCluster.metadata.name, - clientId: deccCluster.status.providerStatus.oidc.clientId, - idpCertificateAuthorityData: deccCluster.status.providerStatus.oidc.certificate, - idpIssuerUrl: deccCluster.status.providerStatus.oidc.issuerUrl, - server: ucpDashboard, - apiCertificate: deccCluster.status.providerStatus.apiServerCertificate, - idToken: userStore.getTokenDetails().token, - refreshToken: userStore.getTokenDetails().refreshToken - }); - - console.log(`Generated kubeconfig: ${YAML.stringify(jsConfig)}`) - - console.log(`Cluster Name: ${deccCluster.metadata.name}, Cluster UCP Dashboard URL: ${deccCluster.status.providerStatus.ucpDashboard}`) - let newCluster = new Cluster({ - id: uuid(), - contextName: `${parsedToken.preferred_username}@${deccCluster.metadata.name}`, - preferences: { - clusterName: deccCluster.metadata.name, - httpsProxy: undefined, - }, - kubeConfigPath: ClusterStore.embedCustomKubeConfig(deccCluster.metadata.uid, YAML.stringify(jsConfig)), - workspace: ws.name, - }); - - clusterStore.addCluster(newCluster); - }; - }); - }) - .catch(function (err: string) { - // API call failed... - console.log(err); + deleteLensDECCWorkspace(workspaceId) { + workspaceStore.removeWorkspace(workspaceId); + } + + deleteLensDECCWorkspaces(userDECCNamespaces) { + const wsPrefix = `decc`; + workspaceStore.workspacesList.forEach(ws => { + let strippedPrefixWorkspaceName = ws.name.slice(5); + //logger.info(`deleteLensDECCWorkspaces: Existing Workspace ${ws.name} being checked against ${userDECCNamespaces.toString()}`) + if (ws.name != "default") { + //logger.info(`deleteLensDECCWorkspaces: Stripped Workspace ${strippedPrefixWorkspaceName} being checked against ${userDECCNamespaces.toString()}`) + if (!userDECCNamespaces.includes(`${strippedPrefixWorkspaceName}`)) { + logger.info(`deleteLensDECCWorkspaces: User does not have access to existing Workspace ${ws.name} - Deleting`) + this.deleteLensDECCClustersByWorkspace(ws); + this.deleteLensDECCWorkspace(ws.id); + } + } + }); + } + + refreshLensDECCClusterKubeconfigs(idToken:string , refreshToken: string, username: string, workspace: string) { + //logger.info(`refreshLensDECCClusterKubeconfigs: Processing Workspace Name ${workspace}`); + const wsPrefix = `decc`; + const ws = workspaceStore.getByName(`${wsPrefix}-${workspace}`); + + if (ws === undefined) { return } + + //logger.info(`refreshLensDECCClusterKubeconfigs: Processing Workspace ${JSON.stringify(ws)}`) + clusterStore.getByWorkspaceId(ws.id).forEach(cluster => { + const currentKubeConfig = loadConfig(cluster.kubeConfigPath); //readFile(cluster.kubeConfigPath, "utf8"); + // console.log(`refreshClusterKubeConfigs: Read kubeconfig from file: ${cluster.kubeConfigPath}. Contents: ${YAML.stringify(kubeConfig)}`); + // console.log(`refreshClusterKubeConfigs: kubeconfig users[0]: ${YAML.stringify(kubeConfig.users[0])}`); + // console.log(`refreshClusterKubeConfigs: kubeconfig users[0] id-token: ${YAML.stringify(kubeConfig.users[0].authProvider.config["id-token"])}`); + // console.log(`refreshClusterKubeConfigs: kubeconfig users[0] refresh-token: ${YAML.stringify(kubeConfig.users[0].authProvider.config["refresh-token"])}`); + const jsConfig = kubeconfig({ + username: username, + clusterName: currentKubeConfig.clusters[0].name, + clientId: currentKubeConfig.users[0].authProvider.config["client-id"], + idpCertificateAuthorityData: currentKubeConfig.users[0].authProvider.config["idp-certificate-authority-data"], + idpIssuerUrl: currentKubeConfig.users[0].authProvider.config["idp-issuer-url"], + server: currentKubeConfig.clusters[0].server, + apiCertificate: currentKubeConfig.clusters[0].caData, + idToken: idToken, + refreshToken: refreshToken }); + + ClusterStore.embedCustomKubeConfig(cluster.id, YAML.stringify(jsConfig)); + logger.info(`refreshLensDECCClusterKubeconfigs: Updated Cluster ${cluster.preferences.clusterName} kubeconfig with new token values`); + cluster.refresh(); }); } + + async createDECCLensEnv() { + const idToken = userStore.token.token; + const parsedIdToken = userStore.decodeToken (userStore.token.token); + const refreshToken = userStore.token.refreshToken; + const username = parsedIdToken.preferred_username; + const userIAMRoles = parsedIdToken.iam_roles; + + // get all available DECC Namespaces + const deccNamespaces = await this.getDECCNamespaces(); + // logger.info(`createDECCLensEnv: The following namespaces exist in DECC - ${deccNamespaces.toString()}`); + + // get all DECC Namespaces the user has access to + const userDECCNamespaces: string[] = await this.getDECCNamespacesForUser(deccNamespaces, userIAMRoles, username); + if (userDECCNamespaces.length > 0) { + userDECCNamespaces.sort(); + logger.info(`createDECCLensEnv: The following namespaces exist in DECC for User ${username} - ${userDECCNamespaces.toString()}`); + + // lets remove workspaces this user does not have access to + this.deleteLensDECCWorkspaces(userDECCNamespaces); + + userDECCNamespaces.forEach(async (ns) => { + try { + let deccClustersByNamespace = await this.getDECCClustersForNamespace(ns); + //logger.info(`createDECCLensEnv: The following clusters exist in Namespace ${ns} - ${JSON.stringify(deccClustersByNamespace)}`); + + // refresh tokens for any existing clusters + this.refreshLensDECCClusterKubeconfigs(idToken, refreshToken, username, ns); + + // now lets add the workspace in Lens + this.addLensDECCWorkspace(ns); + + // now lets add the clusters to the workspace + this.addLensClustersToDECCWorkspace(deccClustersByNamespace, idToken, refreshToken, username, ns); + + } catch (err) { + logger.error(`createDECCLensEnv: ${String(err)}`); + } + }); + } + } } diff --git a/src/main/index.ts b/src/main/index.ts index 7d94342bce..73b1b28727 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -50,11 +50,6 @@ const keycloakWinURL = process.env.NODE_ENV === 'development' : `file://${__static}/keycloak_index.html` const { ipcMain } = require('electron') -const ignoredDECCNamespaces = [ - 'kube-system', 'kube-public', 'openstack-provider-system', 'system', - 'kaas', 'lcm-system', 'istio-system', 'kube-node-lease', 'stacklight' -]; - async function main() { await shellSync(); logger.info(`🚀 Starting Lens from "${workingDir}"`) @@ -103,7 +98,7 @@ async function main() { }).listen(3000); // create cluster manager - deccManager = new DECCManager(keycloakServer, 'a09bfce9ea3074e25b8e5e7b1df576fd-1162277427.eu-west-2.elb.amazonaws.com'); + //deccManager = new DECCManager(keycloakServer, 'a09bfce9ea3074e25b8e5e7b1df576fd-1162277427.eu-west-2.elb.amazonaws.com'); // create window manager and open app @@ -141,22 +136,31 @@ app.on("will-quit", async (event) => { ipcMain.on('keycloak-token', (event, idToken, refreshToken) => { logger.info('test keycloak close main win'); userStore.setTokenDetails(idToken, refreshToken); - logger.info('saved id token and refreshToken to userStore'); + //logger.info('saved id token and refreshToken to userStore'); - logger.info('the idToken is: ' + userStore.getTokenDetails().token); + //logger.info('the idToken is: ' + userStore.getTokenDetails().token); var parsedToken = userStore.decodeToken (idToken); + // create cluster manager + deccManager = new DECCManager('a09bfce9ea3074e25b8e5e7b1df576fd-1162277427.eu-west-2.elb.amazonaws.com'); + deccManager.createDECCLensEnv(); - deccManager.getNamespacesForUser(); - deccManager.addClustersToWorkspace(); - + // deccManager.refreshClusterKubeConfigs(); + // deccManager.getNamespacesForUser(); + // deccManager.addClustersToWorkspace(); + //TODO: refresh token! windowManager.showMain(); }); -ipcMain.on('keycloak-token-update', (event, token) => { - logger.error('token refresh receivied:' + token); - //TODO: handle refresh token! +ipcMain.on('keycloak-token-update', (event, idToken, refreshToken) => { + logger.info('token refresh receivied:' + idToken); + if(userStore.isTokenExpired(userStore.token.tokenValidTill)) { + userStore.setTokenDetails(idToken, refreshToken); + logger.info('saved new id token and refreshToken to userStore'); + logger.info('the idToken is: ' + userStore.getTokenDetails().token); + //deccManager.refreshClusterKubeConfigs(); + }; }); ipcMain.on('keycloak-logout', (event, data) => { diff --git a/src/main/menu.ts b/src/main/menu.ts index 1b0f3434d7..14b42e556a 100644 --- a/src/main/menu.ts +++ b/src/main/menu.ts @@ -7,6 +7,11 @@ import { preferencesURL } from "../renderer/components/+preferences/preferences. import { whatsNewURL } from "../renderer/components/+whats-new/whats-new.route"; import { clusterSettingsURL } from "../renderer/components/+cluster-settings/cluster-settings.route"; import logger from "./logger"; +import { landingURL } from "../renderer/components/+landing-page/landing-page.route"; + +const keycloakWinURL = process.env.NODE_ENV === 'development' +? `http://localhost:3000/keycloak_index.html` +: `file://${__static}/keycloak_index.html` export function initMenu(windowManager: WindowManager) { autorun(() => buildMenu(windowManager), { @@ -37,6 +42,10 @@ export function buildMenu(windowManager: WindowManager) { }) } + function showKeycloak() { + windowManager.showKeycloak() + } + function showAbout(browserWindow: BrowserWindow) { const appInfo = [ `${appName}: ${app.getVersion()}`, @@ -121,7 +130,15 @@ export function buildMenu(windowManager: WindowManager) { }, { type: 'separator' }, { role: 'quit' } - ]) + ]), + { type: 'separator' }, + { + label: 'Logout', + click() { + //navigate(keycloakWinURL) + windowManager.showLogout() + } + } ] }; mt.push(fileMenu) diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 48ab0e8328..7369e2b084 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -4,6 +4,7 @@ import { BrowserWindow, dialog, ipcMain, shell, webContents } from "electron" import windowStateKeeper from "electron-window-state" import { observable } from "mobx"; import { initMenu } from "./menu"; +import { userStore } from "../common/user-store"; export class WindowManager { protected mainView: BrowserWindow; @@ -95,6 +96,16 @@ export class WindowManager { } } + public async showLogout() { + try { + userStore.saveLastLoggedInUser(userStore.token.preferredUserName); + await this.mainView.loadURL(`http://localhost:${this.keycloakPort}?logout=true`) + this.mainView.show(); + } catch (err) { + dialog.showErrorBox("ERROR!", err.toString()) + } + } + public async showMain() { try { //await this.showSplash(); diff --git a/static/keycloak_index.html b/static/keycloak_index.html index 3c0ee8e51c..e8d9646b78 100644 --- a/static/keycloak_index.html +++ b/static/keycloak_index.html @@ -27,15 +27,14 @@ if(logoutUser){ console.log("keycloak object: "+ JSON.stringify(keycloak)); ipcRenderer.send('keycloak-token', keycloak.idToken, keycloak.refreshToken); - //TODO: check if token refresh is possible here - /* - setInterval(() => { - console.log("interval"); - keycloak.updateToken(10).error(() => keycloak.logout()); - console.log(keycloak.token); - ipcRenderer.send('keycloak-token-update', keycloak.token); - }, 10000); - */ + //TODO: check if token refresh is possible here + setInterval(() => { + let tokenRefreshAt = new Date(); + console.log(`keycloak interval: ${tokenRefreshAt.toString()}`); + keycloak.updateToken(10).error(() => keycloak.logout()); + console.log(keycloak.token); + ipcRenderer.send('keycloak-token-update', keycloak.idToken, keycloak.refreshToken); + }, 10000); } }).error(function(error) { console.log('error: ' + JSON.stringify(error)); diff --git a/tsconfig.json b/tsconfig.json index 95f8861c59..93fe8fcb4d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "moduleResolution": "Node", "sourceMap": true, "strict": false, - "noImplicitAny": true, + "noImplicitAny": false, "noUnusedLocals": false, "noImplicitReturns": false, "experimentalDecorators": true,