Skip to content

Commit

Permalink
Credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
cawoodm committed Sep 8, 2019
1 parent 9f356a4 commit d0d18a1
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 263 deletions.
241 changes: 156 additions & 85 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,164 @@
"use strict";
const electron = require("electron");
const appConfig = require("electron-settings");
const path = require("path");const fs = require("fs");
const app = electron.app;
const pathtoElectron = process.platform.match(/win/)?path.resolve(process.env.APPDATA, "npm/electron.cmd"):null;
const dp = console.log;
//const shell = require("node-powershell");
const shell = require("./node-powershell.js");
const config = require('./config.js');
let app = {};
process.chdir(__dirname);

if (process.env.NODE_ENV!=="prod") {
require("electron-reload")(__dirname, {
electron: pathtoElectron,
hardResetMethod: "exit"
});
require("electron-debug")();
}

let mainWindow;
function onClosed() {
mainWindow = null;
}

function createMainWindow() {
const mainWindowStateKeeper = windowStateKeeper("main");

const win = new electron.BrowserWindow({
title: "main",
x: mainWindowStateKeeper.x,
y: mainWindowStateKeeper.y,
width: mainWindowStateKeeper.width,
height: mainWindowStateKeeper.height,
webPreferences: {
nodeIntegration: true,
allowEval: false
}
});
mainWindowStateKeeper.track(win);
Vue.component('main-section', {
props: ['name'],
data: function() {
let dynamicFlex = 'xs'+Math.floor(12 / config.environments.length);
return {environments: config.environments, dynamicFlex: dynamicFlex};
},
template: `
<v-layout row wrap>
<v-flex :[dynamicFlex]="true" v-for="env in environments" :key="env.key">
<single-environment :name="env.key"></single-environment>
</v-flex>
</v-layout>
`
});

win.loadURL(`file://${__dirname}/index.html`);
win.on("closed", onClosed);
Vue.component('single-environment', {
props: ['name'],
data: function() {
let data = {};
dp(this.name)
data.config = config.environments.filter(f=>f.key==this.name)[0];
data.hosts = data.config.hosts;
return data;
},
template: `
<div>{{config.label}}
<single-host v-for="host in hosts"
:key="host.key"
:hostname="host.hostname"
:label="host.label"
:filter="host.services"
:credential="host.credential"
:color="host.color
"></single-host>
</div>
`
});

return win;
}
Vue.component('single-host', {
props: ['hostname', 'filter', 'label', 'color', 'credential'],
data: function () {
this.message=''; //PS.exec("Get-Location").Path
this.PS = new shell({ executionPolicy: 'Bypass', noProfile: false, verbose: false });
if (!this.data) this.data = {results: null, message: this.message}
this.data.errormessage = null;
this.data.pid = this.PS.pid;
this.data.collapse=false;
try {
this.getResults();
} catch(e) {
console.log(e.message)
}
return this.data;
},
//created() {},
methods: {
serviceMessage: function(SvcName, Action) {
try {
Actions={start:"Start",stop:"Stop",restart:"Restart"}
Action=Actions[Action];
this.PS.addCommand(`${Action}-Service ${SvcName}`);
this.PS.invoke()
.then((output) => {
this.getResults();
}).catch((err) => {
alert(`Unable to ${Action} '${SvcName}'. Try running in Admin mode.\nSee the debug console (F12) for more details.`);
console.warn("ERROR", err);
});
} catch(e) {
alert(e)
}
},
toggle: function() {
console.log("collapse", this.collapse, this.data.collapse)
this.collapse=!this.collapse;
},
getResults: function() {
try {
this.PS.clear();
let remoteCommand = `Get-Service ${this.filter.toString()}`
let local = false;
let statii = null;
let startTypes = null;
if (!this.hostname) {
statii=[null,"Stopped", "StartPending", "StopPending", "Running", "ContinuePending", "PausePending", "Paused"];
startTypes=["Boot","System", "Automatic", "Manual", "Disabled"];
this.PS.addCommand(remoteCommand + " | Select Name, Status, DisplayName, StartType, CanStop | ConvertTo-Json -Compress");
} else {
let credUser = config.credentials[this.credential].username;
let credPath = config.credentials[this.credential].path;
this.PS.addCommand(`$pdd = Get-Content "${credPath}" | ConvertTo-SecureString`);
this.PS.addCommand(`$cred = New-Object -TypeName System.Management.Automation.PSCredential -Argumentlist "${credUser}",$pdd`)
this.PS.addCommand(`Invoke-Command -ComputerName ${this.hostname} -ScriptBlock { ${remoteCommand} } -credential $cred | Select Name, Status, DisplayName, StartType, CanStop | ConvertTo-Json -Compress`)
}
//dp(this.label, this.filter);
let timeout = Math.round(Math.random()*1000);
this.PS.invoke()
.then((output) => {
//dp(this.pid, "OUTPUT", output);
this.data.results = JSON.parse(output)
if (!Array.isArray(this.data.results)) this.data.results = [this.data.results];
if (statii) this.data.results.forEach((s)=>{
s.Status = statii[s.Status]
s.StartType = startTypes[s.StartType]
});
//console.log("Results", this.label, this.data.results[0])
}).catch((err) => {
this.errormessage=err.message.substr(0,70)+(err.message.length>70?"...":"");
console.error("ERROR", err)
});
} catch (e) {
this.$root._data.snack.message = e;
this.$root._data.snack.show = true
console.error(e);
}
}
},
template: `
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
<v-card>
<v-alert :value="!!this.errormessage" type="error">{{errormessage}}</v-alert>
app.on("activate", () => {
if (!mainWindow) {
mainWindow = createMainWindow();
}
<v-toolbar :color="color || 'primary lighten-1'">
<v-btn icon @click="toggle()"><v-icon>list</v-icon></v-btn>
<v-toolbar-title :title="hostname">{{label}}</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn flat small icon @click="getResults()"><v-icon>replay</v-icon></v-btn>
<v-btn icon @click="search()"><v-icon>search</v-icon></v-btn>
</v-toolbar>
<v-list style="max-height:300px; overflow-y:scroll; overflow-x:hidden;" v-if="!collapse">
<v-list-tile v-for="res in results" :key="res.Name">
<v-icon :color="res.Status=='Running'?'green':'red'">check_circle</v-icon>
<v-list-tile-content>
<v-list-tile-title :title="res.Name" v-html="res.DisplayName"></v-list-tile-title>
<v-list-tile-sub-title v-html="res.Status + ' (' + res.StartType + ')'"></v-list-tile-sub-title>
</v-list-tile-content>
<v-btn flat small icon @click="serviceMessage(res.Name, 'restart')" color="blue" :disabled="res.Status!='Running'"><v-icon>replay</v-icon></v-btn>
<v-btn flat small icon @click="serviceMessage(res.Name, 'start')" color="green" :disabled="res.Status=='Running'"><v-icon>play_arrow</v-icon></v-btn>
<v-btn flat small icon @click="serviceMessage(res.Name, 'stop')" color="red" :disabled="!res.CanStop"><v-icon>stop</v-icon></v-btn>
</v-list-tile>
</v-list>
<v-divider></v-divider>
</v-card>
`
});

app.on("ready", () => {
mainWindow = createMainWindow();
app.root = new Vue({
el: '#root',
data: {
},
mounted: function() {
}
});

function windowStateKeeper(windowName) {
let window, windowState;
function setBounds() {
// Restore from appConfig
if (appConfig.has(`windowState.${windowName}`)) {
windowState = appConfig.get(`windowState.${windowName}`);
return;
}
// Default
windowState = {
x: undefined,
y: undefined,
width: 1000,
height: 800,
};
}
function saveState() {
if (!windowState.isMaximized) windowState = window.getBounds();
windowState.isMaximized = window.isMaximized();
appConfig.set(`windowState.${windowName}`, windowState);
}
function track(win) {
window = win;
["resize", "move", "close"].forEach(event => win.on(event, saveState));
}
setBounds();
return({
x: windowState.x,
y: windowState.y,
width: windowState.width,
height: windowState.height,
isMaximized: windowState.isMaximized,
track
});
}
process.on('exit', function () {
alert("Closing")
//a.kill();
});
48 changes: 48 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* CONFIG
* Group your hosts by column using the environment
* Each host can have a hostname or be null (for local services)
* Each host can have credentials which reference the credentials section
* Credentials:
* Credentials consist of a username and a password encoded as a PowerShell secure string
* This file must be created by you, the user of this app so that you can decode the file.
* File paths are relative to the application folder (i.e. "mycred.cred" must be in the same folder as this config.js)
*/
module.exports = {
"environments": [
{
"key": "dev",
"label": "DEV",
"hosts": [
{"key": "local1", "hostname": null, "label": "ECOM-APP02-DEV", "credential": "local", "services": ["WSearch", "SysMain", "Fax", "AJRouter"], "color": "blue lighten-1"},
{"key": "local2", "hostname": "localhost", "label": "ECOM-APP00-DEV", "credential": "local", "services": ["a*", "MSiSCSI", "LxssManager"], "color": "blue lighten-1"}
]
},
{
"key": "test",
"label": "TEST",
"hosts": [
{"key": "test1", "hostname": "localhost", "label": "ECOM-APP02-TEST", "credential": "local", "services": ["dbupdate", "smphost", "Power"] ,"color": "green"},
{"key": "test2", "hostname": "localhost", "label": "ECOM-APP01-TEST", "credential": "local", "services": ["stisvc", "SENS", "Dhcp"], "color": "green"}
]
},
{
"key": "prod",
"label": "PROD",
"hosts": [
{"key": "test1", "hostname": "localhost", "label": "ECOM-APP02-PROD", "credential": "local", "services": ["dbupdate", "smphost", "Power"] ,"color": "red"},
{"key": "test2", "hostname": "localhost", "label": "ECOM-APP01-PROD", "credential": "local", "services": ["stisvc", "SENS", "Dhcp"], "color": "red"}
]
}
],
"credentials": {
"local": {
"username": "marc",
"path": "credential1.cred"
},
"dmz": {
"username": "marc",
"path": "credential1.cred"
}
}
};
24 changes: 0 additions & 24 deletions config.json

This file was deleted.

Loading

0 comments on commit d0d18a1

Please sign in to comment.