-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
302 additions
and
263 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
}; |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.