├── .gitignore ├── .npm_ignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── app.js ├── bin └── vtop.js ├── docs └── example.gif ├── package.json ├── sensors ├── cpu.js ├── memory.js └── process.js ├── themes ├── acid.json ├── acid.png ├── becca.json ├── becca.png ├── brew.json ├── brew.png ├── certs.json ├── certs.png ├── dark.json ├── dark.png ├── gooey.json ├── gruvbox.json ├── monokai.json ├── monokai.png ├── nord.json ├── nord.png ├── parallax.json ├── seti.json ├── wizard.json └── wizard.png ├── upgrade.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.npm_ignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 7 5 | - 8 6 | install: 7 | - npm -g install yarn 8 | - yarn 9 | cache: 10 | directories: 11 | - ~/.yarn 12 | - ~/.nvm 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.5.0 4 | 5 | - Upgrade drawille and blessed deps 6 | 7 | ## 0.4.10 8 | 9 | - Fix for issue #2 where people had set a different TERM variable 10 | 11 | ## 0.4.6 12 | 13 | - Fix for systems that called the node binary 'nodejs' 14 | 15 | ## 0.4.0 16 | 17 | - Add sort by CPU and Memory 18 | 19 | ## 0.3.3 20 | 21 | - Fix bug after auto update showing multiple selected items 22 | 23 | ## 0.3.2 24 | 25 | - Add colors for item selection to the other themes 26 | 27 | ## 0.3.1 28 | 29 | - Add keyboard and mouse support 30 | - Add killall support 31 | 32 | ## 0.2.8 33 | 34 | - Improve Makefile 35 | 36 | ## 0.2.7 - 2014-06-12 37 | 38 | - Add Becca theme. http://www.zeldman.com/2014/06/10/the-color-purple/ 39 | 40 | ## 0.2.6 - 2014-06-11 41 | 42 | - Fix major perf problem that some users were having relating to 43 | drawing the header for update notifications. 44 | 45 | ## 0.2.5 46 | 47 | - Delete old data points to improve performance 48 | 49 | ## 0.2.4, 0.2.3, 0.2.2 - 2014-06-11 50 | 51 | - Fixes to the upgrade script 52 | - Added --version and -V command line arguments for version 53 | 54 | ## 0.2.0 - 2014-06-11 55 | 56 | - Add new theme 'dark' 57 | - Add auto-update mechanism 58 | 59 | ## 0.1.7 - 2014-06-11 60 | 61 | - Add a fix for user submitted ps outputs - #5 #6 #7 62 | - Fix issue with 'NaN' appearing for some users 63 | 64 | ## 0.1.4 - 2014-06-09 65 | 66 | - Slice the row strings smaller in table view 67 | 68 | ## 0.1.0 - 2014-06-08 69 | 70 | - First release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 James Hall, Parallax Agency Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | npm install 3 | 4 | test: 5 | # todo - Add unit tests here 6 | @echo "Testing..." 7 | @echo "" 8 | @echo "Performance stats after 5 seconds:" 9 | @screen time node app.js --quit-after 5 10 | 11 | release-patch: test 12 | npm version patch -m "Release vtop patch version %s" 13 | sudo npm publish 14 | git push origin master 15 | 16 | release-minor: test 17 | npm version minor -m "Release vtop minor version %s" 18 | sudo npm publish 19 | git push origin master 20 | 21 | release-major: test 22 | npm version major -m "Release vtop major version %s" 23 | sudo npm publish 24 | git push origin master 25 | 26 | clean: 27 | rm -Rf node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vtop 2 | ========= 3 | 4 | [![Greenkeeper badge](https://badges.greenkeeper.io/MrRio/vtop.svg)](https://greenkeeper.io/) [![Build Status](https://travis-ci.org/MrRio/vtop.svg?branch=master)](https://travis-ci.org/MrRio/vtop) 5 | 6 | A graphical activity monitor for the command line. 7 | 8 | ![](https://raw.githubusercontent.com/MrRio/vtop/master/docs/example.gif) 9 | 10 | How to install 11 | --- 12 | 13 | If you haven't already got Node.js, then [go get it](http://nodejs.org/). 14 | 15 | ``` 16 | npm install -g vtop 17 | ``` 18 | 19 | If you're on macOS, or get an error about file permissions, you may need to do ```sudo npm install -g vtop```. Don't do this if you're using [nvm](https://github.com/creationix/nvm). 20 | 21 | Running 22 | --- 23 | 24 | This is pretty simple too. 25 | 26 | ``` 27 | vtop 28 | ``` 29 | 30 | If you *really* like vtop, but your finger muscle memory means you keep typing 'top' then why not add an alias to ~/.bashrc. 31 | 32 | ``` 33 | alias top="vtop" 34 | alias oldtop="/usr/bin/top" 35 | ``` 36 | 37 | Keyboard shortcuts 38 | --- 39 | 40 | * Press 'u' to update to the latest version of vtop. 41 | * Arrow up or k to move up the process list. 42 | * Arrow down or j to move down. 43 | * Arrow left or h to zoom the graphs in. 44 | * Arrow right or l to zoom the graphs out. 45 | * g to go to the top of the process list. 46 | * G to move to the end of the list. 47 | * dd to kill all the processes in that group 48 | 49 | Mouse control 50 | --- 51 | 52 | If your terminal supports mouse events (like iTerm) then 53 | you can click on the items in the process list. As well as 54 | use the scroll wheel. You can disable mouse control with 55 | the `vtop --no-mouse` option. 56 | 57 | FAQs 58 | ---- 59 | 60 | ### How does it work? 61 | 62 | It uses [drawille](https://github.com/madbence/node-drawille) to draw CPU and Memory charts with Unicode braille characters, helping you visualize spikes. We also group processes with the same name together. 63 | 64 | ### I think the CPU % is coming out wrong. 65 | 66 | We calculate the CPU percentage as a total of your overall system power. 100% is all cores and HyperThreads maxed out. This is different to how Apple Activity monitor works. 67 | 68 | ### Can I change the color scheme? 69 | 70 | Sure, just do: 71 | 72 | ``` 73 | vtop --theme wizard 74 | ``` 75 | 76 | This loads the theme file in themes/ with the same name. Make your own and send me a Pull Request :) 77 | 78 | You could add this to your aliases if you'd like to use it always. 79 | 80 | ``` 81 | alias vtop="vtop --theme brew" 82 | ``` 83 | 84 | ### What about measuring server req/s, log entries, etc etc? 85 | 86 | Yeah that's on the list :) Feel free to send a pull request though. Check out the sensors/ folder. 87 | 88 | ### What license is this under? 89 | 90 | MIT – do what you like with it :) 91 | 92 | ### Contributing 93 | 94 | Get stuck in – click the fork button, then clone to your local machine. Use the [GitHub Desktop client](https://desktop.github.com/) if you don't know Git. Tinker with the code then run this from the command line: 95 | 96 | ``` 97 | ./bin/vtop.js 98 | ``` 99 | 100 | When you push it'll run the Standard JS checker http://standardjs.com/. If you run 'npm test' in your own terminal too, this runs in Travis, your PR will fail the test if this command fails. 101 | 102 | [![Standard - JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 103 | 104 | 105 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const App = ((() => { 4 | // Load in required libs 5 | const Canvas = require('drawille') 6 | const blessed = require('blessed') 7 | const os = require('os') 8 | const cli = require('commander') 9 | const upgrade = require('./upgrade.js') 10 | const VERSION = require('./package.json').version 11 | const childProcess = require('child_process') 12 | const glob = require('glob') 13 | const path = require('path') 14 | let themes = '' 15 | let program = blessed.program() 16 | 17 | const files = glob.sync(path.join(__dirname, 'themes', '*.json')) 18 | for (var i = 0; i < files.length; i++) { 19 | let themeName = files[i].replace(path.join(__dirname, 'themes') + path.sep, '').replace('.json', '') 20 | themes += `${themeName}|` 21 | } 22 | themes = themes.slice(0, -1) 23 | 24 | // Set up the commander instance and add the required options 25 | cli 26 | .option('-t, --theme [name]', `set the vtop theme [${themes}]`, 'parallax') 27 | .option('--no-mouse', 'Disables mouse interactivity') 28 | .option('--quit-after [seconds]', 'Quits vtop after interval', '0') 29 | .option('--update-interval [milliseconds]', 'Interval between updates', '300') 30 | .version(VERSION) 31 | .parse(process.argv) 32 | 33 | /** 34 | * Instance of blessed screen, and the charts object 35 | */ 36 | let screen 37 | const charts = [] 38 | let loadedTheme 39 | const intervals = [] 40 | 41 | let upgradeNotice = false 42 | let disableTableUpdate = false 43 | let disableTableUpdateTimeout = setTimeout(() => {}, 0) 44 | 45 | let graphScale = 1 46 | 47 | // Private variables 48 | 49 | /** 50 | * This is the number of data points drawn 51 | * @type {Number} 52 | */ 53 | let position = 0 54 | 55 | const size = { 56 | pixel: { 57 | width: 0, 58 | height: 0 59 | }, 60 | character: { 61 | width: 0, 62 | height: 0 63 | } 64 | } 65 | 66 | // @todo: move this into charts array 67 | // This is an instance of Blessed Box 68 | let graph 69 | 70 | let graph2 71 | let processList 72 | let processListSelection 73 | 74 | // Private functions 75 | 76 | /** 77 | * Draw header 78 | * @param {string} left This is the text to go on the left 79 | * @param {string} right This is the text for the right 80 | * @return {void} 81 | */ 82 | const drawHeader = () => { 83 | let headerText 84 | let headerTextNoTags 85 | if (upgradeNotice) { 86 | upgradeNotice = `${upgradeNotice}` 87 | headerText = ` {bold}vtop{/bold}{white-fg} for ${os.hostname()} {red-bg} Press 'u' to upgrade to v${upgradeNotice} {/red-bg}{/white-fg}` 88 | headerTextNoTags = ` vtop for ${os.hostname()} Press 'u' to upgrade to v${upgradeNotice} ` 89 | } else { 90 | headerText = ` {bold}vtop{/bold}{white-fg} for ${os.hostname()} ` 91 | headerTextNoTags = ` vtop for ${os.hostname()} ` 92 | } 93 | 94 | const header = blessed.text({ 95 | top: 'top', 96 | left: 'left', 97 | width: headerTextNoTags.length, 98 | height: '1', 99 | fg: loadedTheme.title.fg, 100 | content: headerText, 101 | tags: true 102 | }) 103 | const date = blessed.text({ 104 | top: 'top', 105 | right: 0, 106 | width: 9, 107 | height: '1', 108 | align: 'right', 109 | content: '', 110 | tags: true 111 | }) 112 | const loadAverage = blessed.text({ 113 | top: 'top', 114 | height: '1', 115 | align: 'center', 116 | content: '', 117 | tags: true, 118 | left: Math.floor(program.cols / 2 - (28 / 2)) 119 | }) 120 | screen.append(header) 121 | screen.append(date) 122 | screen.append(loadAverage) 123 | 124 | const zeroPad = input => (`0${input}`).slice(-2) 125 | 126 | const updateTime = () => { 127 | const time = new Date() 128 | date.setContent(`${zeroPad(time.getHours())}:${zeroPad(time.getMinutes())}:${zeroPad(time.getSeconds())} `) 129 | screen.render() 130 | } 131 | 132 | const updateLoadAverage = () => { 133 | const avg = os.loadavg() 134 | loadAverage.setContent(`Load Average: ${avg[0].toFixed(2)} ${avg[1].toFixed(2)} ${avg[2].toFixed(2)}`) 135 | screen.render() 136 | } 137 | 138 | updateTime() 139 | updateLoadAverage() 140 | setInterval(updateTime, 1000) 141 | setInterval(updateLoadAverage, 1000) 142 | } 143 | 144 | /** 145 | * Draw the footer 146 | * 147 | * @todo This appears to break on some viewports 148 | */ 149 | const drawFooter = () => { 150 | const commands = { 151 | 'dd': 'Kill process', 152 | 'j': 'Down', 153 | 'k': 'Up', 154 | 'g': 'Jump to top', 155 | 'G': 'Jump to bottom', 156 | 'c': 'Sort by CPU', 157 | 'm': 'Sort by Mem' 158 | } 159 | let text = '' 160 | for (const c in commands) { 161 | const command = commands[c] 162 | text += ` {white-bg}{black-fg}${c}{/black-fg}{/white-bg} ${command}` 163 | } 164 | text += '{|}http://parall.ax/vtop' 165 | const footerRight = blessed.box({ 166 | width: '100%', 167 | top: program.rows - 1, 168 | tags: true, 169 | fg: loadedTheme.footer.fg 170 | }) 171 | footerRight.setContent(text) 172 | screen.append(footerRight) 173 | } 174 | 175 | /** 176 | * Repeats a string 177 | * @var string The string to repeat 178 | * @var integer The number of times to repeat 179 | * @return {string} The repeated chars as a string. 180 | */ 181 | const stringRepeat = (string, num) => { 182 | if (num < 0) { 183 | return '' 184 | } 185 | return new Array(num + 1).join(string) 186 | } 187 | 188 | /** 189 | * This draws a chart 190 | * @param {int} chartKey The key of the chart. 191 | * @return {string} The text output to draw. 192 | */ 193 | const drawChart = chartKey => { 194 | const chart = charts[chartKey] 195 | const c = chart.chart 196 | c.clear() 197 | 198 | if (!charts[chartKey].plugin.initialized) { 199 | return false 200 | } 201 | 202 | const dataPointsToKeep = 5000 203 | 204 | charts[chartKey].values[position] = charts[chartKey].plugin.currentValue 205 | 206 | const computeValue = input => chart.height - Math.floor(((chart.height + 1) / 100) * input) - 1 207 | 208 | if (position > dataPointsToKeep) { 209 | delete charts[chartKey].values[position - dataPointsToKeep] 210 | } 211 | 212 | for (const pos in charts[chartKey].values) { 213 | if (graphScale >= 1 || (graphScale < 1 && pos % (1 / graphScale) === 0)) { 214 | const p = parseInt(pos, 10) + (chart.width - charts[chartKey].values.length) 215 | // calculated x-value based on graphScale 216 | const x = (p * graphScale) + ((1 - graphScale) * chart.width) 217 | 218 | // draws top line of chart 219 | if (p > 1 && computeValue(charts[chartKey].values[pos - 1]) > 0) { 220 | c.set(x, computeValue(charts[chartKey].values[pos - 1])) 221 | } 222 | 223 | // Start deleting old data points to improve performance 224 | // @todo: This is not be the best place to do this 225 | 226 | // fills all area underneath top line 227 | for (let y = computeValue(charts[chartKey].values[pos - 1]); y < chart.height; y++) { 228 | if (graphScale > 1 && p > 0 && y > 0) { 229 | const current = computeValue(charts[chartKey].values[pos - 1]) 230 | const next = computeValue(charts[chartKey].values[pos]) 231 | const diff = (next - current) / graphScale 232 | 233 | // adds columns between data if graph is zoomed in, takes average where data is missing to make smooth curve 234 | for (let i = 0; i < graphScale; i++) { 235 | c.set(x + i, y + (diff * i)) 236 | for (let j = y + (diff * i); j < chart.height; j++) { 237 | c.set(x + i, j) 238 | } 239 | } 240 | } else if (graphScale <= 1) { 241 | // magic number used to calculate when to draw a value onto the chart 242 | // @TODO: Remove this? 243 | // var allowedPValues = (charts[chartKey].values.length - ((graphScale * charts[chartKey].values.length) + 1)) * -1 244 | c.set(x, y) 245 | } 246 | } 247 | } 248 | } 249 | 250 | // Add percentage to top right of the chart by splicing it into the braille data 251 | const textOutput = c.frame().split('\n') 252 | const percent = ` ${chart.plugin.currentValue}` 253 | textOutput[0] = `${textOutput[0].slice(0, textOutput[0].length - 4)}{white-fg}${percent.slice(-3)}%{/white-fg}` 254 | 255 | return textOutput.join('\n') 256 | } 257 | 258 | /** 259 | * Draws a table. 260 | * @param {int} chartKey The key of the chart. 261 | * @return {string} The text output to draw. 262 | */ 263 | const drawTable = chartKey => { 264 | const chart = charts[chartKey] 265 | const columnLengths = {} 266 | // Clone the column array 267 | const columns = chart.plugin.columns.slice(0) 268 | columns.reverse() 269 | let removeColumn = false 270 | const lastItem = columns[columns.length - 1] 271 | 272 | const minimumWidth = 12 273 | let padding = 1 274 | 275 | if (chart.width > 50) { 276 | padding = 2 277 | } 278 | 279 | if (chart.width > 80) { 280 | padding = 3 281 | } 282 | // Keep trying to reduce the number of columns 283 | do { 284 | let totalUsed = 0 285 | let firstLength = 0 286 | // var totalColumns = columns.length 287 | // Allocate space for each column in reverse order 288 | for (const column in columns) { 289 | const item = columns[column] 290 | i++ 291 | // If on the last column (actually first because of array order) 292 | // then use up all the available space 293 | if (item === lastItem) { 294 | columnLengths[item] = chart.width - totalUsed 295 | firstLength = columnLengths[item] 296 | } else { 297 | columnLengths[item] = item.length + padding 298 | } 299 | totalUsed += columnLengths[item] 300 | } 301 | if (firstLength < minimumWidth && columns.length > 1) { 302 | totalUsed = 0 303 | columns.shift() 304 | removeColumn = true 305 | } else { 306 | removeColumn = false 307 | } 308 | } while (removeColumn) 309 | 310 | // And back again 311 | columns.reverse() 312 | let titleOutput = '{bold}' 313 | for (const headerColumn in columns) { 314 | var colText = ` ${columns[headerColumn]}` 315 | titleOutput += (colText + stringRepeat(' ', columnLengths[columns[headerColumn]] - colText.length)) 316 | } 317 | titleOutput += '{/bold}' + '\n' 318 | 319 | const bodyOutput = [] 320 | for (const row in chart.plugin.currentValue) { 321 | const currentRow = chart.plugin.currentValue[row] 322 | let rowText = '' 323 | for (const bodyColumn in columns) { 324 | let colText = ` ${currentRow[columns[bodyColumn]]}` 325 | rowText += (colText + stringRepeat(' ', columnLengths[columns[bodyColumn]] - colText.length)).slice(0, columnLengths[columns[bodyColumn]]) 326 | } 327 | bodyOutput.push(rowText) 328 | } 329 | return { 330 | title: titleOutput, 331 | body: bodyOutput, 332 | processWidth: columnLengths[columns[0]] 333 | } 334 | } 335 | 336 | // This is set to the current items displayed 337 | let currentItems = [] 338 | let processWidth = 0 339 | /** 340 | * Overall draw function, this should poll and draw results of 341 | * the loaded sensors. 342 | */ 343 | const draw = () => { 344 | position++ 345 | 346 | const chartKey = 0 347 | graph.setContent(drawChart(chartKey)) 348 | graph2.setContent(drawChart(chartKey + 1)) 349 | 350 | if (!disableTableUpdate) { 351 | const table = drawTable(chartKey + 2) 352 | processList.setContent(table.title) 353 | 354 | // If we keep the stat numbers the same immediately, then update them 355 | // after, the focus will follow. This is a hack. 356 | 357 | const existingStats = {} 358 | // Slice the start process off, then store the full stat, 359 | // so we can inject the same stat onto the new order for a brief render 360 | // cycle. 361 | for (var stat in currentItems) { 362 | var thisStat = currentItems[stat] 363 | existingStats[thisStat.slice(0, table.processWidth)] = thisStat 364 | } 365 | processWidth = table.processWidth 366 | // Smush on to new stats 367 | const tempStats = [] 368 | for (let stat in table.body) { 369 | let thisStat = table.body[stat] 370 | tempStats.push(existingStats[thisStat.slice(0, table.processWidth)]) 371 | } 372 | // Move cursor position with temp stats 373 | // processListSelection.setItems(tempStats); 374 | 375 | // Update the numbers 376 | processListSelection.setItems(table.body) 377 | 378 | processListSelection.focus() 379 | 380 | currentItems = table.body 381 | } 382 | 383 | screen.render() 384 | } 385 | 386 | // Public function (just the entry point) 387 | return { 388 | 389 | init () { 390 | let theme 391 | if (typeof process.theme !== 'undefined') { 392 | theme = process.theme 393 | } else { 394 | theme = cli.theme 395 | } 396 | /** 397 | * Quits running vtop after so many seconds 398 | * This is mainly for perf testing. 399 | */ 400 | if (cli['quitAfter'] !== '0') { 401 | setTimeout(() => { 402 | process.exit(0) 403 | }, parseInt(cli['quitAfter'], 10) * 1000) 404 | } 405 | 406 | try { 407 | loadedTheme = require(`./themes/${theme}.json`) 408 | } catch (e) { 409 | console.log(`The theme '${theme}' does not exist.`) 410 | process.exit(1) 411 | } 412 | // Create a screen object. 413 | screen = blessed.screen() 414 | 415 | // Configure 'q', esc, Ctrl+C for quit 416 | let upgrading = false 417 | 418 | const doCheck = () => { 419 | upgrade.check(v => { 420 | upgradeNotice = v 421 | drawHeader() 422 | }) 423 | } 424 | 425 | doCheck() 426 | // Check for updates every 5 minutes 427 | // setInterval(doCheck, 300000); 428 | 429 | let lastKey = '' 430 | 431 | screen.on('keypress', (ch, key) => { 432 | if (key === 'up' || key === 'down' || key === 'k' || key === 'j') { 433 | // Disable table updates for half a second 434 | disableTableUpdate = true 435 | clearTimeout(disableTableUpdateTimeout) 436 | disableTableUpdateTimeout = setTimeout(() => { 437 | disableTableUpdate = false 438 | }, 1000) 439 | } 440 | 441 | if ( 442 | upgrading === false && 443 | ( 444 | key.name === 'q' || 445 | key.name === 'escape' || 446 | (key.name === 'c' && key.ctrl === true) 447 | ) 448 | ) { 449 | return process.exit(0) 450 | } 451 | // dd killall 452 | // @todo: Factor this out 453 | if (lastKey === 'd' && key.name === 'd') { 454 | let selectedProcess = processListSelection.getItem(processListSelection.selected).content 455 | selectedProcess = selectedProcess.slice(0, processWidth).trim() 456 | 457 | childProcess.exec(`killall "${selectedProcess}"`, () => {}) 458 | } 459 | 460 | if (key.name === 'c' && charts[2].plugin.sort !== 'cpu') { 461 | charts[2].plugin.sort = 'cpu' 462 | charts[2].plugin.poll() 463 | setTimeout(() => { 464 | processListSelection.select(0) 465 | }, 200) 466 | } 467 | if (key.name === 'm' && charts[2].plugin.sort !== 'mem') { 468 | charts[2].plugin.sort = 'mem' 469 | charts[2].plugin.poll() 470 | setTimeout(() => { 471 | processListSelection.select(0) 472 | }, 200) 473 | } 474 | lastKey = key.name 475 | 476 | if (key.name === 'u' && upgrading === false) { 477 | upgrading = true 478 | // Clear all intervals 479 | for (const interval in intervals) { 480 | clearInterval(intervals[interval]) 481 | } 482 | processListSelection.detach() 483 | program = blessed.program() 484 | program.clear() 485 | program.disableMouse() 486 | program.showCursor() 487 | program.normalBuffer() 488 | 489 | // @todo: show changelog AND smush existing data into it :D 490 | upgrade.install('vtop', [ 491 | { 492 | 'theme': theme 493 | } 494 | ]) 495 | } 496 | 497 | if ((key.name === 'left' || key.name === 'h') && graphScale < 8) { 498 | graphScale *= 2 499 | } else if ((key.name === 'right' || key.name === 'l') && graphScale > 0.125) { 500 | graphScale /= 2 501 | } 502 | }) 503 | 504 | drawHeader() 505 | 506 | // setInterval(drawHeader, 1000); 507 | drawFooter() 508 | 509 | graph = blessed.box({ 510 | top: 1, 511 | left: 'left', 512 | width: '100%', 513 | height: '50%', 514 | content: '', 515 | fg: loadedTheme.chart.fg, 516 | tags: true, 517 | border: loadedTheme.chart.border 518 | }) 519 | 520 | screen.append(graph) 521 | 522 | let graph2appended = false 523 | 524 | const createBottom = () => { 525 | if (graph2appended) { 526 | screen.remove(graph2) 527 | screen.remove(processList) 528 | } 529 | graph2appended = true 530 | graph2 = blessed.box({ 531 | top: graph.height + 1, 532 | left: 'left', 533 | width: '50%', 534 | height: graph.height - 2, 535 | content: '', 536 | fg: loadedTheme.chart.fg, 537 | tags: true, 538 | border: loadedTheme.chart.border 539 | }) 540 | screen.append(graph2) 541 | 542 | processList = blessed.box({ 543 | top: graph.height + 1, 544 | left: '50%', 545 | width: screen.width - graph2.width, 546 | height: graph.height - 2, 547 | keys: true, 548 | mouse: cli.mouse, 549 | fg: loadedTheme.table.fg, 550 | tags: true, 551 | border: loadedTheme.table.border 552 | }) 553 | screen.append(processList) 554 | 555 | processListSelection = blessed.list({ 556 | height: processList.height - 3, 557 | top: 1, 558 | width: processList.width - 2, 559 | left: 0, 560 | keys: true, 561 | vi: true, 562 | search (jump) { 563 | // @TODO 564 | // jump('string of thing to jump to'); 565 | }, 566 | style: loadedTheme.table.items, 567 | mouse: cli.mouse 568 | }) 569 | processList.append(processListSelection) 570 | processListSelection.focus() 571 | screen.render() 572 | } 573 | 574 | screen.on('resize', () => { 575 | createBottom() 576 | }) 577 | createBottom() 578 | 579 | screen.append(graph) 580 | screen.append(processList) 581 | 582 | // Render the screen. 583 | screen.render() 584 | 585 | const setupCharts = () => { 586 | size.pixel.width = (graph.width - 2) * 2 587 | size.pixel.height = (graph.height - 2) * 4 588 | 589 | const plugins = ['cpu', 'memory', 'process'] 590 | 591 | for (const plugin in plugins) { 592 | let width 593 | let height 594 | let currentCanvas 595 | // @todo Refactor this 596 | switch (plugins[plugin]) { 597 | case 'cpu': 598 | width = (graph.width - 3) * 2 599 | height = (graph.height - 2) * 4 600 | currentCanvas = new Canvas(width, height) 601 | break 602 | case 'memory': 603 | width = (graph2.width - 3) * 2 604 | height = ((graph2.height - 2) * 4) 605 | currentCanvas = new Canvas(width, height) 606 | break 607 | case 'process': 608 | width = processList.width - 3 609 | height = processList.height - 2 610 | break 611 | } 612 | 613 | // If we're reconfiguring a plugin, then preserve the already recorded values 614 | let values 615 | if (typeof charts[plugin] !== 'undefined' && typeof charts[plugin].values !== 'undefined') { 616 | values = charts[plugin].values 617 | } else { 618 | values = [] 619 | } 620 | charts[plugin] = { 621 | chart: currentCanvas, 622 | values, 623 | plugin: require(`./sensors/${plugins[plugin]}.js`), 624 | width, 625 | height 626 | } 627 | charts[plugin].plugin.poll() 628 | } 629 | // @TODO Make this less hard-codey 630 | graph.setLabel(` ${charts[0].plugin.title} `) 631 | graph2.setLabel(` ${charts[1].plugin.title} `) 632 | processList.setLabel(` ${charts[2].plugin.title} `) 633 | } 634 | 635 | setupCharts() 636 | screen.on('resize', setupCharts) 637 | intervals.push(setInterval(draw, parseInt(cli['updateInterval'], 10))) 638 | 639 | // @todo Make this more sexy 640 | intervals.push(setInterval(charts[0].plugin.poll, charts[0].plugin.interval)) 641 | intervals.push(setInterval(charts[1].plugin.poll, charts[1].plugin.interval)) 642 | intervals.push(setInterval(charts[2].plugin.poll, charts[2].plugin.interval)) 643 | } 644 | } 645 | })()) 646 | 647 | App.init() 648 | -------------------------------------------------------------------------------- /bin/vtop.js: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ':' //; # This line below fixes xterm color bug on Mac - https://github.com/MrRio/vtop/issues/2 3 | ':' //; export TERM=xterm-256color 4 | ':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@" 5 | 'use strict' 6 | require('../app.js'); 7 | -------------------------------------------------------------------------------- /docs/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/docs/example.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vtop", 3 | "version": "0.6.1", 4 | "description": "Wow such top. So stats", 5 | "homepage": "http://parall.ax/vtop", 6 | "main": "app.js", 7 | "preferGlobal": true, 8 | "engines": { 9 | "node": ">= 4" 10 | }, 11 | "scripts": { 12 | "test": "make test", 13 | "precommit": "standard" 14 | }, 15 | "standard": { 16 | "ignore": [ 17 | "bin/vtop.js" 18 | ] 19 | }, 20 | "bin": { 21 | "vtop": "./bin/vtop.js" 22 | }, 23 | "author": { 24 | "name": "James Hall", 25 | "email": "james@parall.ax" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git@github.com:MrRio/vtop.git" 30 | }, 31 | "license": "MIT", 32 | "readmeFilename": "README.md", 33 | "dependencies": { 34 | "blessed": "0.1.81", 35 | "commander": "2.11.0", 36 | "drawille": "1.1.0", 37 | "glob": "7.1.2", 38 | "husky": "^0.14.3", 39 | "os-utils": "0.0.14", 40 | "read": "1.0.7", 41 | "sudo": "1.0.3", 42 | "use-strict": "^1.0.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sensors/cpu.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * CPU Usage sensor 4 | * 5 | * (c) 2014 James Hall 6 | */ 7 | 'use strict' 8 | 9 | const os = require('os-utils') 10 | const plugin = { 11 | /** 12 | * This appears in the title of the graph 13 | */ 14 | title: 'CPU Usage', 15 | /** 16 | * The type of sensor 17 | * @type {String} 18 | */ 19 | type: 'chart', 20 | /** 21 | * The default interval time in ms that this plugin should be polled. 22 | * More costly benchmarks should be polled less frequently. 23 | */ 24 | interval: 200, 25 | 26 | initialized: false, 27 | 28 | currentValue: 0, 29 | /** 30 | * Grab the current value, from 0-100 31 | */ 32 | poll () { 33 | os.cpuUsage(v => { 34 | plugin.currentValue = (Math.floor(v * 100)) 35 | plugin.initialized = true 36 | }) 37 | } 38 | } 39 | 40 | module.exports = exports = plugin 41 | -------------------------------------------------------------------------------- /sensors/memory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Memory Usage sensor 3 | * 4 | * (c) 2014 James Hall 5 | */ 6 | 'use strict' 7 | 8 | const os = require('os-utils') 9 | const _os = require('os') 10 | const child = require('child_process') 11 | 12 | const plugin = { 13 | /** 14 | * This appears in the title of the graph 15 | */ 16 | title: 'Memory Usage', 17 | /** 18 | * The type of sensor 19 | * @type {String} 20 | */ 21 | type: 'chart', 22 | /** 23 | * The default interval time in ms that this plugin should be polled. 24 | * More costly benchmarks should be polled less frequently. 25 | */ 26 | interval: 200, 27 | 28 | initialized: false, 29 | 30 | currentValue: 0, 31 | 32 | isLinux: _os.platform().includes('linux'), 33 | 34 | isMac: _os.platform().includes('darwin'), 35 | 36 | /** 37 | * Grab the current value, from 0-100 38 | */ 39 | poll () { 40 | const computeUsage = (used, total) => Math.round(100 * (used / total)) 41 | 42 | if (plugin.isLinux) { 43 | child.exec('free -m', (err, stdout, stderr) => { 44 | if (err) { 45 | console.error(err) 46 | } 47 | const data = stdout.split('\n')[1].replace(/[\s\n\r]+/g, ' ').split(' ') 48 | const used = parseInt(data[2], 10) 49 | const total = parseInt(data[1], 10) 50 | plugin.currentValue = computeUsage(used, total) 51 | }) 52 | } else if (plugin.isMac) { 53 | child.exec('ps -caxm -orss,comm', (err, stdout, stderr) => { 54 | if (err) { 55 | throw err 56 | } 57 | let sp = stdout.split('\n') 58 | let total = 0 // kb 59 | for (var i = 0; i < sp.length; i++) { 60 | if (!isNaN(parseInt(sp[i].replace(/([a-zA-Z]).*/, '')))) { 61 | total += parseInt(sp[i].replace(/([a-zA-Z]).*/, '')) 62 | } 63 | } 64 | let usedmem = total / 1024 ^ 2 65 | let freemem = os.totalmem() - usedmem 66 | let per = freemem / os.totalmem() 67 | plugin.currentValue = Math.round((1 - per) * 100) 68 | }) 69 | } else { 70 | plugin.currentValue = Math.round((1 - os.freememPercentage()) * 100) 71 | } 72 | 73 | plugin.initialized = true 74 | } 75 | } 76 | 77 | module.exports = exports = plugin 78 | -------------------------------------------------------------------------------- /sensors/process.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Process monitor sensor 4 | * 5 | * (c) 2014 James Hall 6 | */ 7 | 'use strict' 8 | 9 | const os = require('os') 10 | const childProcess = require('child_process') 11 | 12 | const plugin = { 13 | /** 14 | * * This appears in the title of the graph 15 | */ 16 | title: 'Process List', 17 | description: ` 18 | This returns a process list, grouped by executable name. CPU % is divided by the number of cores. 19 | 100% CPU Usage is all cores being maxed out. Unlike other tools that define the maximum as 800% for 8 cores for example.`, 20 | /** 21 | * The type of sensor 22 | * @type {String} 23 | */ 24 | type: 'table', 25 | /** 26 | * The default interval time in ms that this plugin should be polled. 27 | * More costly benchmarks should be polled less frequently. 28 | */ 29 | interval: 2000, 30 | 31 | initialized: false, 32 | 33 | sort: 'cpu', 34 | 35 | columns: ['Command', 'CPU %', 'Count', 'Memory %'], 36 | currentValue: [{ 37 | 'Command': 'Google Chrome', 38 | 'Count': '4', 39 | 'CPU %': '0.4', 40 | 'Memory %': '1' 41 | }, { 42 | 'Command': 'Sublime Text 2', 43 | 'Count': '1', 44 | 'CPU %': '0.1', 45 | 'Memory': '5' 46 | }], 47 | 48 | /** 49 | * Grab the current value for the table 50 | */ 51 | poll () { 52 | const stats = {} 53 | // @todo If you can think of a better way of getting process stats, 54 | // then please feel free to send me a pull request. This is version 0.1 55 | // and needs some love. 56 | childProcess.exec('ps -ewwwo %cpu,%mem,comm', (error, stdout, stderr) => { 57 | if (error) { 58 | console.error(error) 59 | } 60 | const lines = stdout.split('\n') 61 | // Ditch the first line 62 | lines[0] = '' 63 | for (const line in lines) { 64 | const currentLine = lines[line].trim().replace(' ', ' ') 65 | const words = currentLine.split(' ') 66 | if (typeof words[0] !== 'undefined' && typeof words[1] !== 'undefined') { 67 | const cpu = words[0].replace(',', '.') 68 | const mem = words[1].replace(',', '.') 69 | const offset = cpu.length + mem.length + 2 70 | let comm = currentLine.slice(offset) 71 | // If we're on Mac then remove the path 72 | if (/^darwin/.test(process.platform)) { 73 | comm = comm.split('/') 74 | comm = comm[comm.length - 1] 75 | } else { 76 | // Otherwise assume linux and remove the unnecessary /1 info like 77 | // you get on kworker 78 | comm = comm.split('/') 79 | comm = comm[0] 80 | } 81 | // If already exists, then add them together 82 | if (typeof stats[comm] !== 'undefined') { 83 | stats[comm] = { 84 | cpu: parseFloat(stats[comm].cpu, 10) + parseFloat(cpu), 85 | mem: parseFloat(stats[comm].mem, 10) + parseFloat(mem), 86 | comm, 87 | count: parseInt(stats[comm].count, 10) + 1 88 | } 89 | } else { 90 | stats[comm] = { 91 | cpu, 92 | mem, 93 | comm, 94 | count: 1 95 | } 96 | } 97 | } 98 | } 99 | const statsArray = [] 100 | for (const stat in stats) { 101 | // Divide by number of CPU cores 102 | const cpuRounded = parseFloat(stats[stat].cpu / os.cpus().length).toFixed(1) 103 | const memRounded = parseFloat(stats[stat].mem).toFixed(1) 104 | statsArray.push({ 105 | 'Command': stats[stat].comm, 106 | 'Count': stats[stat].count, 107 | 'CPU %': cpuRounded, 108 | 'Memory %': memRounded, 109 | 'cpu': stats[stat].cpu, 110 | 'mem': stats[stat].mem // exact cpu for comparison 111 | }) 112 | } 113 | statsArray.sort((a, b) => parseFloat(b[plugin.sort]) - parseFloat(a[plugin.sort])) 114 | 115 | plugin.currentValue = statsArray 116 | plugin.initialized = true 117 | }) 118 | } 119 | } 120 | module.exports = exports = plugin 121 | -------------------------------------------------------------------------------- /themes/acid.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Acid", 3 | "author": "lefoy", 4 | "title": { 5 | "fg": "#97c124" 6 | }, 7 | "chart": { 8 | "fg": "#97c124", 9 | "border": { 10 | "type": "line", 11 | "fg": "#97c124" 12 | } 13 | }, 14 | "table": { 15 | "fg": "fg", 16 | "items": { 17 | "selected": { 18 | "bg": "#97c124", 19 | "fg": "bg" 20 | }, 21 | "item": { 22 | "fg": "fg", 23 | "bg": "bg" 24 | } 25 | }, 26 | "border": { 27 | "type": "line", 28 | "fg": "#97c124" 29 | } 30 | }, 31 | "footer": { 32 | "fg": "fg" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /themes/acid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/themes/acid.png -------------------------------------------------------------------------------- /themes/becca.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Becca", 3 | "author": "James Hall", 4 | "description": "In memory of Becca #663399. This is as close as we can get to that color in xterm", 5 | "title": { 6 | "fg": "#800080" 7 | }, 8 | "chart": { 9 | "fg": "#800080", 10 | "border": { 11 | "type": "line", 12 | "fg": "#800080" 13 | } 14 | }, 15 | "table": { 16 | "fg": "white", 17 | "items": { 18 | "selected": { 19 | "bg": "#800080", 20 | "fg": "bg" 21 | }, 22 | "item": { 23 | "fg": "fg", 24 | "bg": "bg" 25 | } 26 | }, 27 | "border": { 28 | "type": "line", 29 | "fg": "#800080" 30 | } 31 | }, 32 | "footer": { 33 | "fg": "fg" 34 | } 35 | } -------------------------------------------------------------------------------- /themes/becca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/themes/becca.png -------------------------------------------------------------------------------- /themes/brew.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Brew", 3 | "author": "James Hall", 4 | "title": { 5 | "fg": "#187dc1" 6 | }, 7 | "chart": { 8 | "fg": "#187dc1", 9 | "border": { 10 | "type": "line", 11 | "fg": "#56a0d1" 12 | } 13 | }, 14 | "table": { 15 | "fg": "fg", 16 | "items": { 17 | "selected": { 18 | "bg": "#56a0d1", 19 | "fg": "bg" 20 | }, 21 | "item": { 22 | "fg": "fg", 23 | "bg": "bg" 24 | } 25 | }, 26 | "border": { 27 | "type": "line", 28 | "fg": "#56a0d1" 29 | } 30 | }, 31 | "footer": { 32 | "fg": "fg" 33 | } 34 | } -------------------------------------------------------------------------------- /themes/brew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/themes/brew.png -------------------------------------------------------------------------------- /themes/certs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "certs", 3 | "author": "Ivan Brennan", 4 | "title": { 5 | "fg": "fg" 6 | }, 7 | "chart": { 8 | "fg": "#55B5DB", 9 | "border": { 10 | "type": "line", 11 | "fg": "#000000" 12 | } 13 | }, 14 | "table": { 15 | "fg": "fg", 16 | "items": { 17 | "selected": { 18 | "bg": "#262b30", 19 | "fg": "#FFFFFF" 20 | }, 21 | "item": { 22 | "fg": "fg" 23 | } 24 | }, 25 | "border": { 26 | "type": "line", 27 | "fg": "#000000" 28 | } 29 | }, 30 | "footer": { 31 | "fg": "fg" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /themes/certs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/themes/certs.png -------------------------------------------------------------------------------- /themes/dark.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark", 3 | "author": "boboman13", 4 | "title": { 5 | "fg": "#232323" 6 | }, 7 | "chart": { 8 | "fg": "#00A779", 9 | "border": { 10 | "type": "line", 11 | "fg": "#232323" 12 | } 13 | }, 14 | "table": { 15 | "fg": "#626262", 16 | "items": { 17 | "selected": { 18 | "bg": "#000000", 19 | "fg": "#ffffff" 20 | }, 21 | "item": { 22 | "bg": "#888888", 23 | "fg": "#000000" 24 | } 25 | }, 26 | "border": { 27 | "type": "line", 28 | "fg": "#232323" 29 | } 30 | }, 31 | "footer": { 32 | "fg": "white" 33 | } 34 | } -------------------------------------------------------------------------------- /themes/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/themes/dark.png -------------------------------------------------------------------------------- /themes/gooey.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gooey", 3 | "author": "Matt Harris", 4 | "description": "Unofficial colour scheme based off the atom-gooey syntax theme", 5 | "title": { 6 | "fg": "#ee829f" 7 | }, 8 | "chart": { 9 | "fg": "#97bbf7", 10 | "border": { 11 | "type": "line", 12 | "fg": "#a5ffe1" 13 | } 14 | }, 15 | "table": { 16 | "fg": "fg", 17 | "items": { 18 | "selected": { 19 | "bg": "#6f8ab7", 20 | "fg": "#ffffff" 21 | }, 22 | "item": { 23 | "fg": "fg", 24 | "bg": "bg" 25 | } 26 | }, 27 | "border": { 28 | "type": "line", 29 | "fg": "#ffffff" 30 | } 31 | }, 32 | "footer": { 33 | "fg": "fg" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /themes/gruvbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gruvbox", 3 | "author": "Danil Antoshkin", 4 | "title": { 5 | "fg": "#ebdbb2" 6 | }, 7 | "chart": { 8 | "fg": "#fb4934", 9 | "border": { 10 | "type": "line", 11 | "fg": "#ebdbb2" 12 | } 13 | }, 14 | "table": { 15 | "fg": "fg", 16 | "items": { 17 | "selected": { 18 | "bg": "#ebdbb2", 19 | "fg": "#282828" 20 | }, 21 | "item": { 22 | "fg": "#ebdbb2" 23 | } 24 | }, 25 | "border": { 26 | "type": "line", 27 | "fg": "#ebdbb2" 28 | } 29 | }, 30 | "footer": { 31 | "fg": "#ebdbb2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /themes/monokai.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monokai", 3 | "author": "Dean Eigenmann", 4 | "title": { 5 | "fg": "#767150" 6 | }, 7 | "chart": { 8 | "fg": "#66D9EF", 9 | "border": { 10 | "type": "line", 11 | "fg": "#272823" 12 | } 13 | }, 14 | "table": { 15 | "fg": "fg", 16 | "items": { 17 | "selected": { 18 | "bg": "#F92672", 19 | "fg": "fg" 20 | }, 21 | "item": { 22 | "fg": "fg" 23 | } 24 | }, 25 | "border": { 26 | "type": "line", 27 | "fg": "#272823" 28 | } 29 | }, 30 | "footer": { 31 | "fg": "fg" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /themes/monokai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/themes/monokai.png -------------------------------------------------------------------------------- /themes/nord.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Nord", 3 | "author": "Valentin Klinghammewr", 4 | "description": "Unofficial color scheme based on the great color palette from https://github.com/arcticicestudio/nord", 5 | "title": { 6 | "fg": "#b48ead" 7 | }, 8 | "chart": { 9 | "fg": "#81a1c1", 10 | "border": { 11 | "type": "line", 12 | "fg": "#d8dee9" 13 | } 14 | }, 15 | "table": { 16 | "fg": "fg", 17 | "items": { 18 | "selected": { 19 | "bg": "#5e81ac", 20 | "fg": "#eceff4" 21 | }, 22 | "item": { 23 | "fg": "fg", 24 | "bg": "bg" 25 | } 26 | }, 27 | "border": { 28 | "type": "line", 29 | "fg": "#d8dee9" 30 | } 31 | }, 32 | "footer": { 33 | "fg": "fg" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /themes/nord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/themes/nord.png -------------------------------------------------------------------------------- /themes/parallax.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Parallax", 3 | "author": "James Hall", 4 | "title": { 5 | "fg": "#a537fd" 6 | }, 7 | "chart": { 8 | "fg": "#a537fd", 9 | "border": { 10 | "type": "line", 11 | "fg": "#00ebbe" 12 | } 13 | }, 14 | "table": { 15 | "fg": "fg", 16 | "items": { 17 | "selected": { 18 | "bg": "#a537fd", 19 | "fg": "fg" 20 | }, 21 | "item": { 22 | "fg": "fg" 23 | } 24 | }, 25 | "border": { 26 | "type": "line", 27 | "fg": "#00ebbe" 28 | } 29 | }, 30 | "footer": { 31 | "fg": "fg" 32 | } 33 | } -------------------------------------------------------------------------------- /themes/seti.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seti", 3 | "author": "Kevin Niedermayr", 4 | "title": { 5 | "fg": "#FFFFFF" 6 | }, 7 | "chart": { 8 | "fg": "#55B5DB", 9 | "border": { 10 | "type": "line", 11 | "fg": "#000000" 12 | } 13 | }, 14 | "table": { 15 | "fg": "fg", 16 | "items": { 17 | "selected": { 18 | "bg": "#262b30", 19 | "fg": "fg" 20 | }, 21 | "item": { 22 | "fg": "fg" 23 | } 24 | }, 25 | "border": { 26 | "type": "line", 27 | "fg": "#000000" 28 | } 29 | }, 30 | "footer": { 31 | "fg": "fg" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /themes/wizard.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wizard", 3 | "author": "James Hall", 4 | "title": { 5 | "fg": "#f43059" 6 | }, 7 | "chart": { 8 | "fg": "#f43059", 9 | "border": { 10 | "type": "line", 11 | "fg": "white" 12 | } 13 | }, 14 | "table": { 15 | "fg": "white", 16 | "items": { 17 | "selected": { 18 | "bg": "#f43059", 19 | "fg": "#000000" 20 | }, 21 | "item": { 22 | "fg": "#ffffff", 23 | "bg": "#000000" 24 | } 25 | }, 26 | "border": { 27 | "type": "line", 28 | "fg": "white" 29 | } 30 | }, 31 | "footer": { 32 | "fg": "white" 33 | } 34 | } -------------------------------------------------------------------------------- /themes/wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRio/vtop/e39e183e232e2b9eadae2198948d1f87aa277921/themes/wizard.png -------------------------------------------------------------------------------- /upgrade.js: -------------------------------------------------------------------------------- 1 | /** 2 | * npm package updater 3 | * 4 | * @copyright 2014 James HAll 5 | * 6 | * This will detect if a package needs and update, 7 | * and also update it 8 | */ 9 | 10 | var upgrade = (function () { 11 | return { 12 | /** 13 | * Should call the callback with a new version number, or false 14 | */ 15 | check: function (callback) { 16 | try { 17 | var packageObj = require('./package.json') 18 | 19 | var childProcess = require('child_process') 20 | childProcess.exec('npm info --json ' + packageObj.name, function (error, stdout, stderr) { 21 | if (error) { 22 | callback(null, null) 23 | return 24 | } 25 | var output 26 | try { 27 | output = JSON.parse(stdout) 28 | } catch (e) { 29 | callback(null, null) 30 | return 31 | } 32 | if (output['dist-tags']['latest'] !== packageObj.version) { 33 | callback(output['dist-tags']['latest']) 34 | } else { 35 | callback(null, null) 36 | } 37 | }) 38 | } catch (e) { 39 | callback(null, null) 40 | } 41 | }, 42 | /** 43 | * This will install the update and relaunch 44 | */ 45 | install: function (packageName, vars) { 46 | var sudo = require('sudo') 47 | console.log('') 48 | console.log('Installing vtop update...') 49 | console.log('') 50 | console.log(' ** You will need to enter your password to upgrade ** ') 51 | console.log('') 52 | 53 | var args = ['npm', 'install', '-g', 'vtop'] 54 | console.log(args.join(' ')) 55 | 56 | var options = { 57 | cachePassword: false, 58 | prompt: 'Password:', 59 | spawnOptions: { stdio: 'inherit' } 60 | } 61 | var child = sudo(args, options) 62 | 63 | var path = false 64 | child.stdout.on('data', function (data) { 65 | console.log(data.toString()) 66 | 67 | if (data.toString().indexOf('vtop.js') !== -1) { 68 | path = data.toString().trim().split(' ')[2] 69 | } 70 | }) 71 | child.stderr.on('data', function (data) { 72 | console.log(data.toString()) 73 | }) 74 | 75 | child.on('close', function () { 76 | for (var file in require.cache) { 77 | delete require.cache[file] 78 | } 79 | console.log('Finished updating. Clearing cache and relaunching...') 80 | setTimeout(function () { 81 | for (var v in vars) { 82 | process[v] = vars[v] 83 | } 84 | require(path) 85 | }, 1000) 86 | }) 87 | } 88 | } 89 | }()) 90 | 91 | module.exports = upgrade 92 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | balanced-match@^1.0.0: 6 | version "1.0.0" 7 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 8 | 9 | blessed@0.1.81: 10 | version "0.1.81" 11 | resolved "https://registry.yarnpkg.com/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129" 12 | 13 | brace-expansion@^1.1.7: 14 | version "1.1.11" 15 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 16 | dependencies: 17 | balanced-match "^1.0.0" 18 | concat-map "0.0.1" 19 | 20 | commander@2.11.0: 21 | version "2.11.0" 22 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" 23 | 24 | concat-map@0.0.1: 25 | version "0.0.1" 26 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 27 | 28 | drawille@1.1.0: 29 | version "1.1.0" 30 | resolved "https://registry.yarnpkg.com/drawille/-/drawille-1.1.0.tgz#5f7cec246d31b5a10044be84cedd9a486fa31ced" 31 | 32 | fs.realpath@^1.0.0: 33 | version "1.0.0" 34 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 35 | 36 | glob@7.1.2: 37 | version "7.1.2" 38 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 39 | dependencies: 40 | fs.realpath "^1.0.0" 41 | inflight "^1.0.4" 42 | inherits "2" 43 | minimatch "^3.0.4" 44 | once "^1.3.0" 45 | path-is-absolute "^1.0.0" 46 | 47 | husky: 48 | version "0.11.9" 49 | resolved "https://registry.yarnpkg.com/husky/-/husky-0.11.9.tgz#28cd1dc16bffdca1d4d93592814e5f3c327b38ee" 50 | dependencies: 51 | is-ci "^1.0.9" 52 | normalize-path "^1.0.0" 53 | 54 | inflight@^1.0.4: 55 | version "1.0.6" 56 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 57 | dependencies: 58 | once "^1.3.0" 59 | wrappy "1" 60 | 61 | inherits@2: 62 | version "2.0.3" 63 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 64 | 65 | inpath@~1.0.2: 66 | version "1.0.2" 67 | resolved "https://registry.yarnpkg.com/inpath/-/inpath-1.0.2.tgz#4ac219710ec7a72f460ff94bf424dd3ef0e52817" 68 | 69 | is-ci@^1.0.9: 70 | version "1.0.9" 71 | resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.9.tgz#de2c5ffe49ab3237fda38c47c8a3bbfd55bbcca7" 72 | 73 | minimatch@^3.0.4: 74 | version "3.0.4" 75 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 76 | dependencies: 77 | brace-expansion "^1.1.7" 78 | 79 | mute-stream@~0.0.4: 80 | version "0.0.6" 81 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" 82 | 83 | normalize-path@^1.0.0: 84 | version "1.0.0" 85 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" 86 | 87 | once@^1.3.0: 88 | version "1.4.0" 89 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 90 | dependencies: 91 | wrappy "1" 92 | 93 | os-utils@0.0.14: 94 | version "0.0.14" 95 | resolved "https://registry.yarnpkg.com/os-utils/-/os-utils-0.0.14.tgz#29e511697b1982b8c627722175fe39797ef64156" 96 | 97 | path-is-absolute@^1.0.0: 98 | version "1.0.1" 99 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 100 | 101 | pidof@~1.0.2: 102 | version "1.0.2" 103 | resolved "https://registry.yarnpkg.com/pidof/-/pidof-1.0.2.tgz#fba0eae1c8335a11eb8099f5d0f3efbc45cb4e90" 104 | 105 | read@1.0.7, read@~1.0.3: 106 | version "1.0.7" 107 | resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" 108 | dependencies: 109 | mute-stream "~0.0.4" 110 | 111 | sudo@1.0.3: 112 | version "1.0.3" 113 | resolved "https://registry.yarnpkg.com/sudo/-/sudo-1.0.3.tgz#ccf28669120f8b74f82b846dff7f1c95120eff20" 114 | dependencies: 115 | inpath "~1.0.2" 116 | pidof "~1.0.2" 117 | read "~1.0.3" 118 | 119 | use-strict: 120 | version "1.0.1" 121 | resolved "https://registry.yarnpkg.com/use-strict/-/use-strict-1.0.1.tgz#0bb80d94f49a4a05192b84a8c7d34e95f1a7e3a0" 122 | 123 | wrappy@1: 124 | version "1.0.2" 125 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 126 | --------------------------------------------------------------------------------