├── .gitignore ├── .gitmodules ├── README.md ├── ToDo.md ├── cc ├── cw ├── disks └── .gitignore ├── doc ├── disks.png ├── guest_creation.png ├── guests.png ├── host.png └── isos.png ├── index.js ├── isos └── .gitignore ├── lib ├── app.js ├── config.coffee ├── drives.js ├── events.js ├── host.js ├── parser.js ├── qemu.coffee ├── socketServer.coffee ├── src │ ├── args.coffee │ ├── disk.coffee │ ├── disk │ │ ├── create.coffee │ │ ├── delete.coffee │ │ └── info.coffee │ ├── host.coffee │ ├── parser.coffee │ ├── pin.coffee │ ├── process.coffee │ ├── qmp.coffee │ ├── usb.coffee │ ├── version.coffee │ ├── vm.coffee │ ├── vmCfg.coffee │ └── vmHandlerExtensions │ │ ├── RESET.coffee │ │ ├── RESUME.coffee │ │ ├── SHUTDOWN.coffee │ │ ├── START.coffee │ │ └── STOP.coffee ├── uuid.js ├── vm.js ├── vmHandler.coffee ├── vms.js ├── webServer.coffee └── webService.js ├── package.json ├── public ├── .gitignore ├── 0.html ├── abc.html ├── app.js ├── aql │ ├── aqlController.js │ └── aqlView.html ├── collection │ ├── collectionController.js │ └── collectionView.html ├── collections │ ├── collectionsController.js │ └── collectionsView.html ├── collectionsBar │ ├── collectionsBarController.js │ ├── collectionsBarView.html │ └── collectionsController.js ├── css │ ├── bootstrap-4-0-0.css │ ├── bootstrap-4-0-0.css.map │ ├── custom.css │ ├── custom2.css │ ├── font-awesome.css │ ├── img │ │ └── jsoneditor-icons.svg │ ├── jsoneditor-flex.css │ └── jsoneditor.css ├── directives │ ├── aqlResultTable.js │ ├── feedbackDirective.js │ └── journalSizeDirective.js ├── document │ ├── documentController.js │ ├── documentRouteController.js │ └── documentView.html ├── drives │ ├── drivesController.js │ └── drivesView.html ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── graph │ ├── graphController.js │ └── graphView.html ├── home │ ├── homeController.js │ └── homeView.html ├── index.html ├── lib │ ├── angular-1-4-9.js │ ├── angular-1-5-0.js │ ├── angular-1-5-5.js │ ├── angular-animate-1-4-9.js │ ├── angular-animate-1-5-0.js │ ├── angular-animate-1-5-5.js │ ├── angular-route-1-4-9.js │ ├── angular-route-1-5-0.js │ ├── angular-route-1-5-5.js │ ├── angular-sanitize-1-5-0.js │ ├── angular-sanitize-1-5-5.js │ ├── bootstrap-4-0-0.js │ ├── jquery-2-2-0.js │ ├── jquery-2-2-3.js │ ├── jsoneditor-5-1-3.js │ ├── ngjsoneditor-1-0-0.js │ ├── requirejs-2-1-11.js │ └── requirejs-2-2-0.js ├── main.js ├── manage │ ├── collectionsController.js │ └── collectionsView.html ├── navBar │ ├── navBarController.js │ └── navBarView.html ├── services │ ├── fastFilterService.js │ ├── formatService.js │ ├── messageBrokerService.js │ ├── queriesService.js │ ├── queryService.js │ └── testService.js └── vms │ ├── 0.html │ ├── 1.html │ ├── 2.html │ ├── 3.html │ ├── 4.html │ ├── 5.html │ ├── 6.html │ ├── 7.html │ ├── 8.html │ ├── from6.html │ ├── vmsController.js │ └── vmsView.html ├── qemu ├── config.js ├── img.js ├── process.js └── qmp.js ├── server.coffee ├── test ├── args.coffee ├── args.json └── confs.coffee ├── tests.js ├── toYaml.coffee ├── vmConfigs └── .gitignore └── webSrc ├── 0.html ├── app.js ├── aql ├── aqlController.js └── aqlView.html ├── collection ├── collectionController.js └── collectionView.html ├── collectionsBar ├── collectionsBarController.js └── collectionsBarView.html ├── css ├── bootstrap-4-0-0.css ├── bootstrap-4-0-0.css.map ├── custom.css ├── custom2.css ├── font-awesome.css ├── img │ └── jsoneditor-icons.svg ├── jsoneditor-flex.css └── jsoneditor.css ├── directives ├── aqlResultTable.js ├── feedbackDirective.js └── journalSizeDirective.js ├── document ├── documentController.js ├── documentRouteController.js └── documentView.html ├── drives ├── drivesController.js └── drivesView.html ├── graph ├── graphController.js └── graphView.html ├── home ├── homeController.js └── homeView.html ├── index.html ├── main.js ├── manage ├── collectionsController.js └── collectionsView.html ├── navBar ├── navBarController.js └── navBarView.html ├── services ├── fastFilterService.js ├── formatService.js ├── messageBrokerService.js ├── queriesService.js ├── queryService.js └── testService.js └── vms ├── 0.html ├── 1.html ├── 2.html ├── 3.html ├── 4.html ├── 5.html ├── 6.html ├── 7.html ├── 8.html ├── vmsController.js └── vmsView.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | test.* 3 | /node_modules 4 | /test_old 5 | /pids.json 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "public/vendor"] 2 | path = public/vendor 3 | url = https://github.com/baslr/web-client-vendor 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-qemu-server 2 | ================ 3 | node-qemu-server lets you control virtual machines in your webbrowser. 4 | 5 | ### Requirements 6 | 7 | #### Linux 8 | * LSB Linux x86_64 (tested with Debian (Sid) GNU/Linux) 9 | * qemu-system-x86 v1.6.1 10 | * nodejs v0.10.21 11 | * npm 12 | * packages: {numactl, usbutils} (usb and numa are only available on Linux) 13 | 14 | #### OS X 15 | * v10.8 / v10.9 x86_64 16 | * macports qemu v1.6.1 17 | 18 | ### Installation 19 | Install node-qemu-server on Debian GNU/Linux and OS X (assume you have installed qemu, node, npm and numactl) 20 | 21 | $ git clone https://github.com/baslr/node-qemu-server 22 | $ cd node-qemu-server 23 | $ npm install 24 | $ git submodule init 25 | $ git submodule update 26 | $ cd public/vendor/ 27 | $ bower install 28 | $ cd ../../ 29 | $ ./cc 30 | $ node server 31 | 32 | Now open your HTML5 Webbrowser and open http://127.0.0.1:4224 33 | 34 | ![gui_host](https://raw.github.com/baslr/node-qemu-server/feature/new-guest-creation/doc/host.png) 35 | ![gui_guests](https://raw.github.com/baslr/node-qemu-server/feature/new-guest-creation/doc/guests.png) 36 | ![gui_disks](https://raw.github.com/baslr/node-qemu-server/feature/new-guest-creation/doc/disks.png) 37 | ![gui_isos](https://raw.github.com/baslr/node-qemu-server/feature/new-guest-creation/doc/isos.png) 38 | ![guest_creation](https://raw.github.com/baslr/node-qemu-server/feature/new-guest-creation/doc/guest_creation.png) 39 | 40 | --- 41 | setup and control qemu instances with Node.js 42 | 43 | more to come in the future 44 | 45 | vision: 46 | setup and control qemu instances via web gui, lean and simple 47 | 48 | 49 | ### implemented qmp commands 50 | 51 | #### system commands 52 | ##### pause, reset, resume, shutdown, stop 53 | 54 | node-qemu command | qmp command 55 | :--------------|:------------------- 56 | qVm.pause() | {"name": "stop"} 57 | qVm.reset() | {"name": "system_reset"} 58 | qVm.resume() | {"name": "cont"} 59 | qVm.shutdown() | {"name": "system_powerdown"} 60 | qVm.stop() | {"name": "quit"} 61 | qVm.status() | {"name": "query-status"} 62 | 63 | 64 | # in work 65 | {"name": "qom-list-types"} 66 | {"name": "change-vnc-password"} 67 | {"name": "qom-get"} 68 | {"name": "qom-set"} 69 | {"name": "qom-list"} 70 | {"name": "query-block-jobs"} 71 | {"name": "query-balloon"} 72 | {"name": "query-migrate"} 73 | {"name": "query-uuid"} 74 | {"name": "query-name"} 75 | {"name": "query-spice"} 76 | {"name": "query-vnc"} 77 | {"name": "query-mice"} 78 | {"name": "query-kvm"} 79 | {"name": "query-pci"} 80 | {"name": "query-cpus"} 81 | {"name": "query-blockstats"} 82 | {"name": "query-block"} 83 | {"name": "query-chardev"} 84 | {"name": "query-commands"} 85 | {"name": "query-version"} 86 | {"name": "human-monitor-command"} 87 | {"name": "qmp_capabilities"} 88 | {"name": "add_client"} 89 | {"name": "expire_password"} 90 | {"name": "set_password"} 91 | {"name": "block_set_io_throttle"} 92 | {"name": "block_passwd"} 93 | {"name": "closefd"} 94 | {"name": "getfd"} 95 | {"name": "set_link"} 96 | {"name": "balloon"} 97 | {"name": "blockdev-snapshot-sync"} 98 | {"name": "transaction"} 99 | {"name": "block-job-cancel"} 100 | {"name": "block-job-set-speed"} 101 | {"name": "block-stream"} 102 | {"name": "block_resize"} 103 | {"name": "netdev_del"} 104 | {"name": "netdev_add"} 105 | {"name": "client_migrate_info"} 106 | {"name": "migrate_set_downtime"} 107 | {"name": "migrate_set_speed"} 108 | {"name": "migrate_cancel"} 109 | {"name": "migrate"} 110 | {"name": "xen-save-devices-state"} 111 | {"name": "inject-nmi"} 112 | {"name": "pmemsave"} 113 | {"name": "memsave"} 114 | {"name": "cpu"} 115 | {"name": "device_del"} 116 | {"name": "device_add"} 117 | 118 | {"name": "system_wakeup"} 119 | {"name": "screendump"} 120 | {"name": "change"} 121 | {"name": "eject"} 122 | -------------------------------------------------------------------------------- /ToDo.md: -------------------------------------------------------------------------------- 1 | #### different ways to crate an image 2 | qemu = require './lib/qemu' 3 | conf = {name:'myImage', size:10} # 10GiByte 4 | 5 | qemu.createImage conf, (ret) -> 6 | ret.image # image object 7 | ret.status # status 8 | 9 | qImage = new qemu.Image conf 10 | qImage.create (ret) -> 11 | if ret.status is 'success' 12 | # creation ok 13 | 14 | qImage = new qemu.Image() 15 | qImage.create conf, (ret) -> 16 | if ret.status is 'success' 17 | # creation ok 18 | 19 | 20 | 21 | @ToDo 22 | 23 | 24 | 25 | vmHandler.scanForRunningVms 26 | - qmp port scann de ports die belegt sind 27 | 28 | 29 | Delete disk 30 | - click on button 31 | - client emits delete-disk 32 | - server trys to delete disk 33 | - server emits delete-disk 34 | - client removes it from gui -------------------------------------------------------------------------------- /cc: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | 4 | ./node_modules/coffee-script/bin/coffee -c . 5 | -------------------------------------------------------------------------------- /cw: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | 4 | ./node_modules/coffee-script/bin/coffee -cw . 5 | -------------------------------------------------------------------------------- /disks/.gitignore: -------------------------------------------------------------------------------- 1 | *.img 2 | *.json 3 | -------------------------------------------------------------------------------- /doc/disks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/doc/disks.png -------------------------------------------------------------------------------- /doc/guest_creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/doc/guest_creation.png -------------------------------------------------------------------------------- /doc/guests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/doc/guests.png -------------------------------------------------------------------------------- /doc/host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/doc/host.png -------------------------------------------------------------------------------- /doc/isos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/doc/isos.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!./6-node 2 | 'use strict'; 3 | 4 | const qemuConfig = require('./qemu/config'); 5 | 6 | 7 | // read node version 8 | const versions = process.version.split('.'); 9 | const major = Number( versions[0].match(/\d+/)[0] ); 10 | const minor = Number( versions[1].match(/\d+/)[0] ); 11 | const patch = Number( versions[2].match(/\d+/)[0] ); 12 | 13 | if (versions.length != 3 || major < 5 || (major == 5 && minor < 11) ) { 14 | console.warn('Minimum supported node version is v5.11.0'); 15 | process.exit(); 16 | } 17 | console.log(`Found node version ${major}.${minor}.${patch}, OK`); 18 | 19 | 20 | if (qemuConfig.versions.major < 2 || (qemuConfig.versions.major == 2 && qemuConfig.versions.minor < 5) ) { 21 | console.log('Minimum supported qemu version is v2.5.0'); 22 | process.exit(); 23 | } 24 | console.log(`Found qemu version ${qemuConfig.version}, OK`); 25 | 26 | if (!qemuConfig.vncSupport) { 27 | console.log('qemu has no vnc support'); 28 | process.exit(); 29 | } 30 | console.log('Found qemu vnc, OK'); 31 | 32 | if (!qemuConfig.spiceSupport) { 33 | console.log('qemu has no spice support'); 34 | } else { 35 | console.log('Found qemu spice, OK'); 36 | } 37 | 38 | 39 | const app = require('./lib/app'); 40 | 41 | app.start(); 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | /* 50 | 51 | read qemu images 52 | read qemu isos 53 | 54 | read qemu machines aka configs 55 | -> check if all requirements are mett 56 | 57 | 58 | start webservice 59 | 60 | TODO: 61 | webservice: 62 | file management 63 | -create vm image 64 | -upload vm image 65 | -weget vm image 66 | 67 | -wget iso 68 | -upload iso 69 | 70 | mv management 71 | // result is emited to all connected clients 72 | -create 73 | -edit 74 | -delete (with image deletion) 75 | 76 | -start 77 | -pause / resume 78 | -stop 79 | -reboot 80 | 81 | -(migrate) 82 | 83 | */ 84 | 85 | 86 | /* 87 | 88 | TODO: 89 | - display network i/o 90 | - display hdd usage x of y GB 91 | - autorestart at x o'clock 92 | 93 | */ -------------------------------------------------------------------------------- /isos/.gitignore: -------------------------------------------------------------------------------- 1 | *.iso 2 | -------------------------------------------------------------------------------- /lib/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vms = require('./vms'); 4 | const webService = require('./webService'); 5 | 6 | class App { 7 | constructor() { 8 | this.vms = vms; 9 | this.webService = webService; 10 | 11 | vms.on('event', (msg) => { 12 | console.log(`VMS:on:event:${msg.vmUuid}:${msg.event}`); 13 | webService.send('vm-event', msg); 14 | }); 15 | vms.on('status', (msg) => { 16 | console.log(`VMS:on:status:${msg.vmUuid}:${msg.status}`); 17 | webService.send('vm-status', msg); 18 | }); 19 | vms.on('generic', (msg) => { 20 | console.log(`VMS:on:generic:${JSON.stringify(msg)}`); 21 | webService.send('vm-generic', msg); 22 | }); 23 | vms.on('proc-status', (msg) => { 24 | console.log(`VMS:on:proc-status:${JSON.stringify(msg)})`); 25 | webService.send('proc-status', msg); 26 | }); 27 | } // constructor 28 | 29 | start() { 30 | this.vms.restore(); 31 | 32 | this.webService.start(); 33 | } 34 | } 35 | 36 | const app = new App(); 37 | 38 | module.exports = app; 39 | -------------------------------------------------------------------------------- /lib/config.coffee: -------------------------------------------------------------------------------- 1 | 2 | # TODO implement ps for linux 3 | 4 | fs = require 'fs' 5 | os = require 'os' 6 | exec = require('child_process').exec 7 | 8 | vncPorts = {} 9 | qmpPorts = {} 10 | spicePorts = {} 11 | try 12 | pids = require "#{process.cwd()}/pids.json" 13 | catch 14 | pids = {} 15 | 16 | vncPorts[Number port] = false for port in [1..255] 17 | qmpPorts[Number port+15000] = false for port in [1..255] 18 | spicePorts[Number port+15300]= false for port in [1..255] 19 | 20 | module.exports.setToUsed = (proto, port) -> 21 | switch proto 22 | when 'qmp' then qmpPorts[Number port] = true 23 | when 'vnc' then vncPorts[Number port] = true 24 | when 'spice' then spicePorts[Number port] = true 25 | 26 | module.exports.getFreeQMPport = -> 27 | for port,used of qmpPorts 28 | if not used 29 | @setToUsed 'qmp', port 30 | return Number port 31 | 32 | module.exports.getFreeVNCport = -> 33 | for port,used of vncPorts 34 | if not used 35 | @setToUsed 'vnc', port 36 | return Number port 37 | 38 | module.exports.getFreeSPICEport = -> 39 | for port,used of spicePorts 40 | if not used 41 | @setToUsed 'spice', port 42 | return Number port 43 | 44 | 45 | module.exports.getIsoFiles = -> 46 | isoFiles = fs.readdirSync "#{process.cwd()}/isos/" 47 | isos = [] 48 | for isoName in isoFiles 49 | isos.push isoName if 0 < isoName.search /\.iso$/ 50 | return isos 51 | 52 | 53 | module.exports.getDiskFiles = -> 54 | diskFiles = fs.readdirSync "#{process.cwd()}/disks/" 55 | disks = [] 56 | for diskName in diskFiles 57 | disks.push diskName if 0 < diskName.search /\.img$/ 58 | return disks 59 | 60 | 61 | module.exports.getVmConfigs = -> 62 | guestConfs = fs.readdirSync "#{process.cwd()}/vmConfigs" 63 | guests = [] 64 | for name in guestConfs 65 | guests.push name if 0 < name.search /\.yml$/ 66 | return guests 67 | 68 | 69 | module.exports.getVmHandlerExtensions = -> 70 | filesIn = fs.readdirSync "#{process.cwd()}/lib/src/vmHandlerExtensions" 71 | files = {} 72 | 73 | files[file.split('.')[0]] = true for file in filesIn 74 | 75 | return (i for i of files) 76 | 77 | 78 | savePids = () -> fs.writeFileSync "#{process.cwd()}/pids.json", JSON.stringify pids 79 | 80 | module.exports.setPid = (pid, guestName) -> 81 | console.log "CONFIG: set pid:#{pid} for #{guestName}" 82 | pids[pid] = guestName 83 | 84 | savePids() 85 | 86 | module.exports.removePid = (pid) -> 87 | return if ! pids[pid]? 88 | 89 | delete pids[pid] 90 | console.log "CONFIG: removed pid:#{pid}" 91 | savePids() 92 | 93 | module.exports.getGuestNameByPid = (pid) -> pids[pid] if pids[pid]? 94 | 95 | module.exports.getRunningPids = (cb) -> 96 | if 'darwin' is os.type().toLowerCase() 97 | cmd = 'ps ax -o pid,etime,start,lstart,time,comm|grep qemu-system-x86_64' 98 | else if 'linux' is os.type().toLowerCase() 99 | cmd = 'ps --no-headers -o pid,etime,start,lstart,time,comm -C qemu-system-x86_64' 100 | 101 | exec cmd, (err, stdout, stderr) -> 102 | return cb [] if err 103 | 104 | tmpPids = stdout.trim().split '\n' 105 | tmpPids.pop() if '' is tmpPids[tmpPids.length-1] 106 | 107 | retPids = (Number pid.split(' ')[0] for pid in tmpPids) 108 | cb retPids 109 | 110 | console.log 'running pids found:' 111 | console.dir retPids 112 | 113 | # ls #{process.cwd()}/isos/*.iso|sort -f 114 | -------------------------------------------------------------------------------- /lib/drives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | 5 | class Drives { 6 | constructor() {} 7 | 8 | get all() { 9 | return fs.readdirSync(`${__dirname}/../disks/`) 10 | .filter(file => ~file.search(/\.json$/)).map(file => JSON.parse(fs.readFileSync(`${__dirname}/../disks/${file}`))); 11 | } 12 | } 13 | 14 | 15 | const drives = new Drives(); 16 | 17 | module.exports = drives; 18 | -------------------------------------------------------------------------------- /lib/events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const events = require('events'); 4 | 5 | class Events extends events {} 6 | 7 | const qemuEvents = new Events(); 8 | 9 | module.exports = qemuEvents; 10 | -------------------------------------------------------------------------------- /lib/host.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * get next free port 5 | * scan for virtual machines running 6 | * TODO: network interfaces 7 | */ 8 | 9 | const execSync = require('child_process').execSync; 10 | 11 | 12 | class Host { 13 | 14 | constructor() { 15 | 16 | } 17 | 18 | isPortUsed(port) { 19 | if ( isNaN(port) ) throw "Not a number"; 20 | return '0' == execSync(`nc -z 0.0.0.0 ${port} &> /dev/null; echo $?;`).toString('utf8').split('\n')[0]; 21 | } 22 | 23 | nextFreePort(start) { 24 | do { 25 | this.isPortUsed(start); 26 | if (!this.isPortUsed(start)) return start; 27 | start++; 28 | } while(65536 > start); 29 | 30 | throw "No free port"; 31 | } 32 | 33 | isVmProcRunning(uuid, procs) { return this.isVmRunning(uuid, procs); } 34 | 35 | isVmRunning(uuid, procsIn = null) { 36 | const procs = procsIn || this.vmProcesses(); 37 | const reg = new RegExp(`-uuid\\ (${uuid})`.replace('-', '\\-')); 38 | 39 | return 1 === procs.filter( (proc) => { 40 | const match = reg.exec(proc); 41 | return (match && uuid === match[1]); 42 | }).length; 43 | } // isVmRunning() 44 | 45 | 46 | vmProcesses() { 47 | let slice = -1; 48 | return execSync('ps aux').toString('utf8').split('\n').reduce( (prev, line) => { 49 | if (-1 === slice) { 50 | slice = line.search(/COMMAND$/); 51 | } else { // if 52 | if (~line.indexOf('qemu-system-x86_64')) { prev.push(line.slice(slice)); } 53 | } // else 54 | return prev; 55 | }, []); 56 | } // qemuProcess() 57 | } 58 | 59 | 60 | var host = new Host(); 61 | 62 | module.exports = host; 63 | 64 | 65 | // L I N U X 66 | // ps ax -o pid,etimes,lstart,cputime|less 67 | // cat /proc/{pid}/cmdline 68 | 69 | // O S X 70 | // ps ax -o pid,etime,lstart,cputime,comm 71 | -------------------------------------------------------------------------------- /lib/qemu.coffee: -------------------------------------------------------------------------------- 1 | 2 | Vm = require('./src/vm').Vm 3 | 4 | 5 | exports.Vm = Vm 6 | exports.Disk = require './src/disk' 7 | 8 | 9 | createVm = (cfg) -> 10 | vm = new Vm cfg 11 | return vm 12 | 13 | exports.createVm = createVm 14 | -------------------------------------------------------------------------------- /lib/socketServer.coffee: -------------------------------------------------------------------------------- 1 | 2 | vmHandler = require './vmHandler' 3 | ioServer = undefined 4 | Disk = require './src/disk' 5 | usb = require './src/usb' 6 | 7 | socks = {} 8 | 9 | module.exports.start = (httpServer) -> 10 | ioServer = require('socket.io').listen httpServer 11 | ioServer.set('log level', 1) 12 | 13 | ioServer.sockets.on 'connection', (sock) -> 14 | socks[sock.id] = sock 15 | console.log "SOCK -> CON #{sock.handshake.address.address} #{sock.id}" 16 | console.log "SOCK -> count: #{Object.keys(socks).length}" 17 | 18 | sock.emit('set-iso', iso) for iso in vmHandler.getIsos() # emit iso names, client drops duplicates 19 | 20 | for disk in vmHandler.getDisks() # emit disks, client drops duplicates 21 | Disk.info disk, (ret) -> 22 | sock.emit 'set-disk', ret.data 23 | 24 | sock.emit('set-vm', vm.cfg) for vm in vmHandler.getVms() # emit vms, client drops duplicates 25 | 26 | # # # # # # # # # # # # # # # # # # # # 27 | 28 | sock.on 'disconnect', -> 29 | console.log "SOCK -> DIS #{sock.id} #{sock.handshake.address.address}" 30 | delete socks[sock.id] 31 | 32 | sock.on 'qmp-command', (qmpCmd, vmName) -> 33 | console.log "QMP-Command #{qmpCmd}" 34 | vmHandler.qmpCommand qmpCmd, vmName 35 | 36 | sock.on 'relist-usb', -> 37 | console.log 'socket: relist-usb' 38 | usb.scan (usbs) -> sock.emit 'set-usbs', usbs 39 | 40 | sock.on 'create-disk', (disk) -> 41 | vmHandler.createDisk disk, (ret) -> 42 | sock.emit 'msg', ret 43 | if ret.status is 'success' 44 | sock.emit 'reset-create-disk-form' 45 | ioServer.sockets.emit 'set-disk', ret.data.data 46 | 47 | sock.on 'create-VM', (vmConf) -> 48 | vmHandler.createVm vmConf, (ret) -> 49 | sock.emit 'msg', ret 50 | sock.emit 'reset-create-vm-form' if ret.status is 'success' 51 | 52 | 53 | sock.on 'change-guest-conf-entry', (guestName, conf) -> 54 | vmHandler.changeGuestConfEntry guestName, conf 55 | 56 | 57 | 58 | sock.on 'delete-disk', (diskName) -> 59 | if vmHandler.deleteDisk diskName 60 | sock.emit 'msg', {type:'success', msg:'image deleted'} 61 | ioServer.sockets.emit 'delete-disk', diskName 62 | else 63 | sock.emit 'msg', {type:'error', msg:'cant delete image'} 64 | 65 | sock.on 'delete-iso', (isoName) -> 66 | if vmHandler.deleteIso isoName 67 | sock.emit 'msg', {type:'success', msg:"Deleted iso #{isoName}."} 68 | ioServer.sockets.emit 'delete-iso', isoName 69 | else 70 | sock.emit 'msg', {type:'error', msg:"Can't delete iso #{isoName}."} 71 | 72 | sock.on 'delete-guest', (guestName) -> 73 | if vmHandler.deleteGuest guestName 74 | sock.emit 'msg', {type:'success', msg:"Deleted guest #{guestName}."} 75 | ioServer.sockets.emit 'delete-guest', guestName 76 | else 77 | sock.emit 'msg', {type:'error', msg:"Can't delete iso #{guestName}."} 78 | 79 | 80 | module.exports.toAll = (msg, args...) -> 81 | ioServer.sockets.emit msg, args... if ioServer?.sockets? 82 | -------------------------------------------------------------------------------- /lib/src/disk.coffee: -------------------------------------------------------------------------------- 1 | 2 | module.exports[mod] = require "./disk/#{mod}" for mod in ['info', 'create', 'delete'] 3 | 4 | 5 | # module.exports.create = require './disk/info' 6 | # module.exports.info = require './disk/create' 7 | # module.exports.delete = require './disk/delete' -------------------------------------------------------------------------------- /lib/src/disk/create.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | exec = require('child_process').exec 3 | 4 | module.exports = (disk, cb) -> 5 | if not fs.existsSync "#{process.cwd()}/disks/#{disk.name}.img" 6 | exec "qemu-img create -f qcow2 disks/#{disk.name}.img #{disk.size}G", (err, stdout, stderr) => 7 | if err? or stderr isnt '' 8 | cb {status:'error', data:[err,stderr]} 9 | else 10 | cb {status:'success', data:disk} 11 | else 12 | cb {status:'error', data:['disk already existing']} 13 | -------------------------------------------------------------------------------- /lib/src/disk/delete.coffee: -------------------------------------------------------------------------------- 1 | 2 | fs = require 'fs' 3 | 4 | module.exports = (diskName) -> 5 | try 6 | fs.unlinkSync "#{process.cwd()}/disks/#{diskName}.img" 7 | return true 8 | catch e 9 | return false 10 | -------------------------------------------------------------------------------- /lib/src/disk/info.coffee: -------------------------------------------------------------------------------- 1 | exec = require('child_process').exec 2 | version = require '../version' 3 | 4 | module.exports = (name, cb) -> 5 | ver = version.getVersion() 6 | 7 | if (1 is ver[0] and 6 <= ver[1]) or 8 | (1 < ver[0]) 9 | exec "qemu-img info --output=json #{process.cwd()}/disks/#{name}.img", (err, stdout, stderr) => 10 | if err? or stderr isnt '' 11 | cb {status:'error', data:[err,stderr]} 12 | return 13 | 14 | info = JSON.parse stdout.split('\n').join '' 15 | 16 | for i,n of info 17 | if 0 < i.search /\-/ 18 | info[i.replace '-', '_'] = n 19 | delete info[i] 20 | 21 | info['disk_size'] = info['actual_size'] 22 | info['name'] = name 23 | info['percentUsed'] = 100/info['virtual_size']*info['disk_size'] 24 | 25 | cb status:'success', data:info 26 | else 27 | exec "qemu-img info #{process.cwd()}/disks/#{name}.img", (err, stdout, stderr) => 28 | if err? or stderr isnt '' 29 | cb {status:'error', data:[err,stderr]} 30 | return 31 | 32 | b = {} 33 | for row in stdout.split('\n') 34 | if row is '' 35 | continue 36 | b[row.split(':')[0].replace(' ', '_')] = row.split(':')[1].replace ' ', '' 37 | b['cluster_size'] = Number b['cluster_size'] 38 | b['name'] = name 39 | b['virtual_size'] = Number b['virtual_size'].split('(')[1].split(' ')[0] 40 | 41 | size = Number b['disk_size'].slice 0, -1 42 | letter = b['disk_size'].slice -1 43 | 44 | if letter is 'K' 45 | size = size * 1024 46 | else if letter is 'M' 47 | size = size * 1024 * 1024 48 | else if letter is 'G' 49 | size = size * 1024 * 1024 * 1024 50 | b['disk_size'] = size 51 | b['percentUsed'] = 100/b['virtual_size']*b['disk_size'] 52 | 53 | cb status:'success', data:b 54 | -------------------------------------------------------------------------------- /lib/src/host.coffee: -------------------------------------------------------------------------------- 1 | 2 | os = require 'os' 3 | 4 | host = {hostname:'', cpus:0, ram:'', freeRam:'', load:[]} 5 | 6 | host.hostname = os.hostname() 7 | host.cpus = os.cpus().length 8 | host.ram = os.totalmem() 9 | 10 | 11 | module.exports = -> 12 | host.freeRam = os.freemem() 13 | host.l = os.loadavg() 14 | return host -------------------------------------------------------------------------------- /lib/src/parser.coffee: -------------------------------------------------------------------------------- 1 | os = require 'os' 2 | qemu = require '../qemu' 3 | Args = require('./args').Args 4 | 5 | 6 | # 7 | # @call conf, cb 8 | # 9 | # @return cb ret, new args Obj 10 | # 11 | module.exports.guestConfToArgs = (conf) -> 12 | osType = os.type().toLowerCase() 13 | if typeof conf isnt 'object' 14 | throw 'conf must be an object' 15 | else if typeof conf.name isnt 'string' 16 | throw 'conf.name must be an string' 17 | else if typeof conf.hardware isnt 'object' 18 | throw 'conf.hardware must be an object' 19 | else if typeof conf.settings isnt 'object' 20 | throw 'conf.settings must be an object' 21 | 22 | hw = conf.hardware # h-w 23 | st = conf.settings # s-t 24 | 25 | args = new Args() 26 | 27 | args.nodefconfig() 28 | .nodefaults() 29 | # .noStart() 30 | # .noShutdown() 31 | 32 | args.accel('kvm').kvm() if osType is 'linux' 33 | 34 | args.ram( hw.ram) 35 | .vga( hw.vgaCard) 36 | .qmp( st.qmpPort) 37 | .keyboard(st.keyboard) 38 | 39 | # CPU CONF 40 | # cpu: Object 41 | # cores: n 42 | # model: s 43 | # sockets: n 44 | # threads: n 45 | cpu = hw.cpu 46 | 47 | # MODEL 48 | args.cpuModel cpu.model 49 | 50 | # CPU architecture 51 | args.cpus cpu.cores, cpu.threads, cpu.sockets 52 | 53 | # NET CONF 54 | if hw.net? 55 | net = hw.net 56 | 57 | if net.opts? 58 | args.net net.mac, net.nic, net.mode, net.opts 59 | else 60 | args.net net.mac, net.nic, net.mode 61 | 62 | ipAddr = [] 63 | for interfaceName,intfce of os.networkInterfaces() 64 | for m in intfce 65 | if m.family is 'IPv4' and m.internal is false and m.address isnt '127.0.0.1' 66 | ipAddr.push m.address 67 | console.log ipAddr 68 | 69 | if osType is 'linux' 70 | args.spice st.spice, ipAddr[0] if st.spice # -spice port=5900,addr=192.168.178.63,disable-ticketing 71 | 72 | args.usbOn() 73 | args.usbDevice u.vendorId, u.productId for u in (if hw.usb? then hw.usb else []) 74 | 75 | # NUMA CONF 76 | # numa: Object 77 | # cpuNode: n 78 | # memNode: n 79 | numa = st.numa 80 | args.hostNuma numa.cpuNode, numa.memNode 81 | 82 | 83 | if hw.disk 84 | args.hd hw.disk 85 | else if hw.partition 86 | args.partition hw.partition 87 | 88 | args.cd hw.iso if hw.iso 89 | args.vnc st.vnc if st.vnc 90 | 91 | # SNAPSHOT 92 | args.snapshot() if st.snapshot 93 | 94 | 95 | if st.boot then switch st.bootDevice 96 | when 'disk' then args.boot 'hd', false 97 | when 'iso' then args.boot 'cd', false 98 | 99 | return args 100 | -------------------------------------------------------------------------------- /lib/src/pin.coffee: -------------------------------------------------------------------------------- 1 | 2 | os = require 'os' 3 | exec = require('child_process').exec 4 | 5 | curPin = 0 6 | pinMask = for [1..os.cpus().length] then 0 7 | 8 | module.exports = (pid, cpuCount) -> 9 | if os.platform() isnt 'linux' 10 | console.log "PIN: pinning is only supported with gnu/linux" 11 | return 12 | 13 | cpuList = '' 14 | for [1..cpuCount] 15 | for pin,i in pinMask 16 | if curPin is pin 17 | if cpuList.length 18 | cpuList = "#{cpuList},#{i}" 19 | else cpuList = "#{i}" 20 | pinMask[i]++ 21 | 22 | if i is pinMask.length-1 23 | curPin++ 24 | break 25 | 26 | exec "taskset -c -p #{cpuList} #{pid}", {maxBuffer: 10*1024}, (e, stdout, stderr) -> 27 | if e? then console.dir e 28 | else console.log "taskset for pid #{pid} with cpulist #{cpuList} executed" 29 | -------------------------------------------------------------------------------- /lib/src/process.coffee: -------------------------------------------------------------------------------- 1 | proc = require 'child_process' 2 | parser = require './parser' 3 | config = require '../config' 4 | vmHandler = require '../vmHandler' 5 | 6 | class Process 7 | constructor: () -> 8 | @process = undefined 9 | 10 | getPid: () -> 11 | return @process.pid if @process 12 | 0 13 | 14 | start: (vmConf) -> 15 | try 16 | args = parser.guestConfToArgs vmConf 17 | console.log "QEMU-Process: Start-Parameters: #{args.args.join(' ')}" 18 | 19 | # shift first array element, its qemu-system-x86_64 xor numactl 20 | @process = proc.spawn args.args.shift(), args.args, {stdio: 'inherit', detached: true} 21 | 22 | @process.on 'exit', (code, signal) -> 23 | config.removePid @pid 24 | if code is 0 then console.log 'QEMU-Process: exit clean.' 25 | else console.error "QEMU-Process: exit with error: #{code}, signal: #{signal}" 26 | vmHandler.SHUTDOWN vmConf.name 27 | catch e 28 | console.error 'process:start:e' 29 | console.dir e 30 | vmHandler.SHUTDOWN vmConf.name 31 | vmHandler.stopQmp vmConf.name 32 | 33 | module.exports.Process = Process 34 | -------------------------------------------------------------------------------- /lib/src/qmp.coffee: -------------------------------------------------------------------------------- 1 | 2 | net = require 'net' 3 | vmHandler = require '../vmHandler' 4 | 5 | class Qmp 6 | constructor:(@vmName) -> 7 | @socket = undefined 8 | @port = undefined 9 | @dataCb = undefined 10 | 11 | connect: (port, cb) -> 12 | if typeof port is 'function' 13 | cb = port 14 | else if typeof port is 'number' 15 | @port = port 16 | 17 | console.log "QMP: try to connect to VM #{@vmName} with port #{@port}" 18 | 19 | tryConnect = () => 20 | @socket = net.connect @port 21 | 22 | @handleDataEvent() 23 | 24 | @socket.on 'connect', => 25 | console.log "qmp connected to #{@vmName}" 26 | @socket.write '{"execute":"qmp_capabilities"}' 27 | cb status:'success' 28 | 29 | @socket.on 'error', (e) => 30 | @socket = undefined 31 | if e.message is 'This socket has been ended by the other party' 32 | console.log 'QEMU closed connection' 33 | else 34 | console.error 'QMP: ConnectError try reconnect' 35 | @connect @port, cb 36 | 37 | @socket.on 'close', () => 38 | console.log 'QMP: socket closed' 39 | @socket = undefined 40 | 41 | intervalId = setInterval => 42 | console.log 'interval' 43 | if ! @socket 44 | console.log 'socket is undefined try connect' 45 | return tryConnect() 46 | 47 | console.log 'clear interval' 48 | clearInterval intervalId 49 | , 100 50 | 51 | handleDataEvent: () -> 52 | @socket.on 'data', (data) => 53 | jsons = data.toString().split '\r\n' 54 | jsons.pop() # remove last '' 55 | 56 | for json in jsons 57 | try 58 | parsedData = JSON.parse json.toString() 59 | if parsedData.event? then event = parsedData.event else event = undefined 60 | 61 | console.log " - - - QMP-START-DATA - - -" 62 | console.dir parsedData 63 | console.log " - - - QMP-END-DATA - - -" 64 | 65 | if parsedData.return?.status? and 66 | parsedData.return?.singlestep? and 67 | parsedData.return?.running? 68 | parsedData.timestamp = new Date().getTime() 69 | if parsedData.return.status is 'paused' 70 | event = 'STOP' 71 | else if parsedData.return.status is 'running' and parsedData.return.running is true 72 | parsedData.timestamp = new Date().getTime() 73 | event = 'RESUME' 74 | 75 | # handle events 76 | if parsedData.timestamp? and event? 77 | if vmHandler[event]? 78 | console.log "QMP: call vmHandler[#{event}] for VM #{@vmName}" 79 | vmHandler[event] @vmName 80 | 81 | if @dataCb? 82 | if parsedData.error? 83 | @dataCb 'error':parsedData.error 84 | else if parsedData.timestamp? 85 | continue 86 | else if parsedData.return? 87 | if 0 is Object.keys(parsedData.return).length 88 | @dataCb status:'success' 89 | else 90 | @dataCb 'data':parsedData.return 91 | else 92 | console.error 'Cant process Data:' 93 | console.dir parsedData 94 | @dataCb = undefined 95 | else 96 | # console.log "no callback defined:" 97 | # console.dir parsedData 98 | catch e 99 | console.error "cant parse returned json, Buffer is:" 100 | console.error json.toString() 101 | console.error "error is:" 102 | console.dir e 103 | 104 | sendCmd: (cmd, args, cb) -> 105 | if typeof args is 'function' 106 | @dataCb = args 107 | @socket.write JSON.stringify execute:cmd 108 | else 109 | @dataCb = cb 110 | @socket.write JSON.stringify {execute:cmd, arguments: args } 111 | 112 | reconnect: (port, cb) -> 113 | @connect port, cb 114 | 115 | ### 116 | # QMP commands 117 | ### 118 | pause: (cb) -> 119 | @sendCmd 'stop', cb 120 | 121 | reset: (cb) -> 122 | @sendCmd 'system_reset', cb 123 | 124 | resume: (cb) -> 125 | @sendCmd 'cont', cb 126 | 127 | shutdown: (cb) -> 128 | @sendCmd 'system_powerdown', cb 129 | 130 | stop: (cb) -> 131 | @sendCmd 'quit', cb 132 | 133 | status: -> 134 | @sendCmd 'query-status', -> 135 | 136 | balloon: (mem, cb) -> 137 | @sendCmd 'balloon', {value:mem}, cb 138 | 139 | exports.Qmp = Qmp 140 | -------------------------------------------------------------------------------- /lib/src/usb.coffee: -------------------------------------------------------------------------------- 1 | 2 | # Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub 3 | # Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub 4 | # Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub 5 | # Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub 6 | # Bus 001 Device 002: ID 05e3:0610 Genesys Logic, Inc. 7 | # Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 8 | # 01234567890123456789012345678901234567890 9 | # 00000000001111111111222222222233333333334 10 | # host:vendor_id:product_id 11 | 12 | # lsusb -v |grep -E '(Bus\ [0-9]{3,3}|iProduct|bInterfaceClass)' 13 | # Bus 004 Device 004: ID 13fd:1240 Initio Corporation 14 | # iProduct 2 External 15 | # bInterfaceClass 8 Mass Storage 16 | # Bus 004 Device 008: ID 0529:0001 Aladdin Knowledge Systems HASP v0.06 17 | # iProduct 2 HASP HL 2.16 18 | # bInterfaceClass 255 Vendor Specific Class 19 | # Bus 004 Device 007: ID 062a:7223 Creative Labs 20 | # iProduct 2 Full-Speed Mouse 21 | # bInterfaceClass 3 Human Interface Device 22 | # bInterfaceClass 3 Human Interface Device 23 | # Bus 004 Device 006: ID 05ac:9223 Apple, Inc. 24 | # iProduct 2 (error) 25 | # bInterfaceClass 3 Human Interface Device 26 | # Bus 004 Device 005: ID 05af:0802 Jing-Mold Enterprise Co., Ltd 27 | # iProduct 2 USB Keyboard 28 | # bInterfaceClass 3 Human Interface Device 29 | # bInterfaceClass 3 Human Interface Device 30 | # Bus 004 Device 003: ID 05ac:9131 Apple, Inc. 31 | # iProduct 0 32 | # bInterfaceClass 9 Hub 33 | # bInterfaceClass 9 Hub 34 | # Bus 004 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub 35 | # iProduct 0 36 | # bInterfaceClass 9 Hub 37 | # Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 38 | # iProduct 2 EHCI Host Controller 39 | # bInterfaceClass 9 Hub 40 | # Bus 003 Device 003: ID 0557:2221 ATEN International Co., Ltd Winbond Hermon 41 | # iProduct 2 (error) 42 | # bInterfaceClass 3 Human Interface Device 43 | # bInterfaceClass 3 Human Interface Device 44 | # Bus 003 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub 45 | # iProduct 0 46 | # bInterfaceClass 9 Hub 47 | # Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 48 | # iProduct 2 EHCI Host Controller 49 | # bInterfaceClass 9 Hub 50 | # Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub 51 | # iProduct 2 xHCI Host Controller 52 | # bInterfaceClass 9 Hub 53 | # Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 54 | # iProduct 2 xHCI Host Controller 55 | # bInterfaceClass 9 Hub 56 | 57 | exec = require('child_process').exec 58 | os = require 'os' 59 | 60 | usbs = [{text:'Test Device. Dont use it!', vendorId:'ffff', productId:'ffff'}] 61 | 62 | module.exports.scan = (cb) -> 63 | if os.type().toLowerCase() isnt 'linux' 64 | console.log "USB DEVICES only supported under linux." 65 | cb usbs if cb? 66 | return 67 | 68 | usbs = [] 69 | 70 | exec "lsusb -v |grep -E '(Bus\ [0-9]{3,3}|iProduct|bInterfaceClass)'", (e, stdout, sterr) -> 71 | tmpUsbs = [] 72 | cur = '' 73 | for line in stdout.split('\n')[0...-1] 74 | line = line.trim() 75 | if -1 < line.search(/^Bus\ [0-9]{3}\ Device\ [0-9]{3}\:\ ID\ [a-z0-9]{4}\:[a-z0-9]{4}\ /) 76 | cur = line 77 | tmpUsbs[line] = {} 78 | else 79 | tmpUsbs[cur][line] = true 80 | 81 | for i,n of tmpUsbs 82 | for j of n 83 | if j is 'bInterfaceClass 9 Hub' 84 | delete tmpUsbs[i] 85 | break 86 | 87 | usbs = [] 88 | 89 | for i,n of tmpUsbs 90 | usb = {text: i.slice(33).trim(), vendorId:i.substring(23,32).split(':')[0], productId:i.substring(23,32).split(':')[1]} 91 | for j of n 92 | usb.text = "#{usb.text}, #{j.slice 26}" 93 | usbs.push usb 94 | 95 | console.log "USB DEVICES:" 96 | console.dir usbs 97 | 98 | cb usbs if cb? 99 | 100 | module.exports.getDevices = -> usbs 101 | 102 | @scan() 103 | -------------------------------------------------------------------------------- /lib/src/version.coffee: -------------------------------------------------------------------------------- 1 | exec = require('child_process').exec 2 | 3 | version = [0,0,0] 4 | 5 | exec 'qemu-system-x86_64 --version', (e, stdout, sterr) -> 6 | version = stdout.slice(22,27).split '.' 7 | 8 | module.exports.getVersion = -> version 9 | -------------------------------------------------------------------------------- /lib/src/vm.coffee: -------------------------------------------------------------------------------- 1 | config = require '../config' 2 | proc = require './process' 3 | qmp = require './qmp' 4 | vmConf = require('./vmCfg') 5 | 6 | class Vm 7 | constructor: (@cfg) -> 8 | @name = @cfg.name 9 | @process = new proc.Process() 10 | @qmp = new qmp.Qmp @name 11 | @saveConf() 12 | 13 | saveConf: () -> vmConf.save @cfg 14 | 15 | setStatus: (status) -> 16 | @cfg.status = status 17 | @saveConf() 18 | 19 | start: (cb) -> 20 | @process.start @cfg 21 | config.setPid @process.getPid(), @name 22 | 23 | @qmp.connect @cfg.settings.qmpPort, (ret) => 24 | cb ret 25 | @status() 26 | 27 | connectQmp: (cb) -> 28 | @qmp.connect @cfg.settings.qmpPort, (ret) => 29 | cb ret 30 | @status() 31 | 32 | stopQMP: -> 33 | console.log "VM #{@name}: stopQMP called" 34 | delete @qmp 35 | @qmp = new qmp.Qmp @name 36 | 37 | ### 38 | # QMP commands 39 | ### 40 | pause: (cb) -> 41 | @qmp.pause cb 42 | 43 | reset: (cb) -> 44 | @qmp.reset cb 45 | 46 | resume: (cb) -> 47 | @qmp.resume cb 48 | 49 | shutdown: (cb) -> 50 | @qmp.shutdown cb 51 | 52 | stop: (cb) -> 53 | @qmp.stop cb 54 | 55 | status: -> 56 | @qmp.status() 57 | 58 | exports.Vm = Vm 59 | 60 | 61 | 62 | # qVM.gfx() 63 | # .ram(1024) 64 | # .cpus(2) 65 | # .accel('kvm') 66 | # .vnc(2) 67 | # .mac('52:54:00:12:34:52') 68 | # .net() 69 | # .qmp(4442) 70 | # .hd('ub1210.img') 71 | # .keyboard('de') 72 | 73 | 74 | # console.dir qVM.startArgs 75 | # 76 | # # qVM.start -> 77 | # # console.log "qemu VM startet" 78 | # 79 | # qVM.reconnectQmp 4442, -> 80 | # console.log "reconnected to qmp" 81 | 82 | # qemu -cpu kvm64 83 | 84 | 85 | ### 86 | 87 | # @qmpSocket.write '{"execute":"query-commands"}' 88 | 89 | ### 90 | -------------------------------------------------------------------------------- /lib/src/vmCfg.coffee: -------------------------------------------------------------------------------- 1 | 2 | fs = require 'fs' 3 | yaml = require 'js-yaml' 4 | 5 | save = (vmCfg) -> 6 | try 7 | ymlObj = yaml.safeDump vmCfg 8 | fs.writeFileSync "#{process.cwd()}/vmConfigs/#{vmCfg.name}.yml", ymlObj 9 | catch e 10 | console.error 'save error' 11 | console.dir e 12 | return false 13 | return true 14 | 15 | open = (confName, cb) -> 16 | try 17 | conf = yaml.safeLoad fs.readFileSync "#{process.cwd()}/vmConfigs/#{confName}", 'utf8' 18 | catch e 19 | console.log 'open error' 20 | console.dir e 21 | return false 22 | return conf 23 | 24 | exports.save = save 25 | exports.open = open 26 | -------------------------------------------------------------------------------- /lib/src/vmHandlerExtensions/RESET.coffee: -------------------------------------------------------------------------------- 1 | 2 | socketServer = require '../../socketServer' 3 | 4 | module.exports = (vm) -> 5 | console.log "vmHandler Extension received RESET event" 6 | 7 | vm.setStatus 'running' 8 | socketServer.toAll 'set-vm-status', vm.name, 'running' 9 | socketServer.toAll 'msg', {type:'success', msg:"VM #{vm.name} reset."} 10 | -------------------------------------------------------------------------------- /lib/src/vmHandlerExtensions/RESUME.coffee: -------------------------------------------------------------------------------- 1 | 2 | socketServer = require '../../socketServer' 3 | 4 | module.exports = (vm) -> 5 | console.log "vmHandler Extension received RESUME event" 6 | 7 | vm.setStatus 'running' 8 | socketServer.toAll 'set-vm-status', vm.name, 'running' 9 | socketServer.toAll 'msg', {type:'success', msg:"VM #{vm.name} resume."} 10 | -------------------------------------------------------------------------------- /lib/src/vmHandlerExtensions/SHUTDOWN.coffee: -------------------------------------------------------------------------------- 1 | 2 | socketServer = require '../../socketServer' 3 | 4 | module.exports = (vm) -> 5 | console.log "vmHandler Extension received SHUTDOWN event" 6 | console.log "qemu process exit VM #{vm.name}" 7 | 8 | vm.setStatus 'stopped' 9 | socketServer.toAll 'set-vm-status', vm.name, 'stopped' 10 | socketServer.toAll 'msg', {type:'success', msg:"VM #{vm.name} shutdown."} 11 | -------------------------------------------------------------------------------- /lib/src/vmHandlerExtensions/START.coffee: -------------------------------------------------------------------------------- 1 | 2 | socketServer = require '../../socketServer' 3 | 4 | module.exports = (vm) -> 5 | console.log "vmHandler Extension received START event" 6 | 7 | vm.setStatus 'running' 8 | socketServer.toAll 'set-vm-status', vm.name, 'running' 9 | socketServer.toAll 'msg', {type:'success', msg:"VM #{vm.name} start."} 10 | -------------------------------------------------------------------------------- /lib/src/vmHandlerExtensions/STOP.coffee: -------------------------------------------------------------------------------- 1 | 2 | socketServer = require '../../socketServer' 3 | 4 | module.exports = (vm) -> 5 | console.log "vmHandler Extension received STOP event" 6 | 7 | vm.setStatus 'paused' 8 | socketServer.toAll 'set-vm-status', vm.name, 'paused' 9 | socketServer.toAll 'msg', {type:'success', msg:"VM #{vm.name} paused."} 10 | -------------------------------------------------------------------------------- /lib/uuid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | 5 | class Uuid { 6 | new() { 7 | return crypto.randomBytes(32).toString('hex').match(/(.{8})(.{4})(.{4})(.{4})(.{12})/).slice(1).join('-'); 8 | } 9 | } 10 | 11 | const uuid = new Uuid(); 12 | 13 | module.exports = uuid; 14 | -------------------------------------------------------------------------------- /lib/vm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const spawn = require('child_process').spawn; 5 | 6 | const Qmp = require('../qemu/qmp'); 7 | const Img = require('../qemu/img'); 8 | const Parser = require('./parser'); 9 | const uuid = require('./uuid'); 10 | const vms = require('./vms'); 11 | const host = require('./host'); 12 | 13 | 14 | class Vm { 15 | constructor(name) { 16 | this.conf = {name:name}; 17 | this.readConf(); 18 | 19 | if (this.conf.settings.vnc && this.conf.settings.vnc.enabled && !this.conf.settings.vnc.port) { 20 | this.conf.settings.vnc.port = vms.nextFreeVncPort; 21 | this.saveConf(); 22 | } // if 23 | 24 | if (!this.conf.settings.qmp) { 25 | this.conf.settings.qmp = {port:vms.nextFreeQmpPort}; 26 | this.saveConf(); 27 | } // if 28 | 29 | this.qmp = new Qmp(this.uuid); 30 | 31 | if (host.isVmRunning(this.uuid) ) { 32 | console.log(`VM:${this.conf.uuid}:reconnect-qmp`); 33 | this.qmp.attach(this.conf.settings.qmp); 34 | } else if (this.conf.settings.autoBoot) { 35 | this.start(); 36 | } // else if 37 | 38 | vms.on('event', (msg) => { 39 | if (msg.vmUuid == this.uuid && msg.event == 'SHUTDOWN' && this.conf.settings.autoBoot) { 40 | const tryRestart = () => { 41 | if (!this.conf.settings.noShutdown && host.isVmRunning(this.uuid)) { 42 | console.log('settimeout lol'); 43 | setTimeout(tryRestart, 1000); 44 | } // if 45 | else { 46 | this.start(); 47 | } // else 48 | } // tryRestart() 49 | tryRestart(); 50 | } // if 51 | }); 52 | } // constructor() 53 | 54 | 55 | readConf() { 56 | this.conf = JSON.parse( fs.readFileSync(`${__dirname}/../vmConfigs/${this.conf.name}.json`) ); 57 | 58 | if (this.conf.hardware.drives[0].create) { 59 | const img = new Img(this.conf.hardware.drives[0]); 60 | this.conf.hardware.drives[0] = img.config; 61 | } // if 62 | 63 | if (!this.conf.uuid) { 64 | this.conf.uuid = uuid.new(); 65 | this.saveConf(); 66 | } // if 67 | } 68 | 69 | 70 | saveConf() { 71 | fs.writeFileSync(`${__dirname}/../vmConfigs/${this.conf.name}.json`, JSON.stringify(this.conf, false, 2)); 72 | } // saveConf() 73 | 74 | 75 | get uuid() { return this.conf.uuid; } 76 | 77 | 78 | start() { 79 | if (host.isVmRunning(this.uuid)) { 80 | console.log(`VM:${this.conf.name}:start:proc-already-running`); 81 | } else { 82 | console.log(`VM:${this.conf.name}:start:via:process:spawn`); 83 | this.startProcess(); 84 | } // else 85 | } // start() 86 | 87 | 88 | startProcess() { 89 | try { 90 | const args = (new Parser()).vmConfToArgs(this.conf); 91 | console.log(`VM:${this.conf.name}:startProcess:args:${args.join(' ')}`); 92 | const proc = spawn(args.shift(), args, {stdio: 'pipe', detached:true}); 93 | proc.on('error', (e) => { 94 | console.log(`process error ${e}`); 95 | }); 96 | proc.stdout.on('data', (d) => console.log(`Proc.stdout: ${d.toString()}`) ); 97 | proc.stderr.on('data', (d) => console.log(`Proc.stderr: ${d.toString()}`) ); 98 | proc.on('exit', (code, signal) => { 99 | if (0 == signal) { 100 | console.log(`Process exited with Code 0, Signal ${signal}. OK`); 101 | } else { 102 | console.log(`Process exited with Code ${code}, Signal ${signal}`); 103 | } // else 104 | 105 | if (host.isVmRunning(this.conf.uuid)) { 106 | vms.emit('proc-status', {status:'running', vmUuid:this.conf.uuid}); 107 | } else { 108 | vms.emit('proc-status', {status:'terminated', vmUuid:this.conf.uuid}); 109 | } 110 | 111 | if (this.conf.settings.autoBoot) { this.start(); } 112 | }); 113 | vms.emit('proc-status', {status:'running', vmUuid:this.conf.uuid}); 114 | this.qmp.attach(this.conf.settings.qmp); 115 | 116 | console.log(`VM:${this.conf.name}:startProcess:booted`); 117 | } catch(e) { 118 | console.log(e); 119 | console.log(e.stack); 120 | console.error(`Cant start vm ${this.conf.name}`); 121 | } 122 | } // startProcess() 123 | 124 | qmpCmd(cmd, args) { console.log(`VM:${this.conf.name}:qmpCmd:${cmd}:${args}`); if (this.qmp) this.qmp.cmd(cmd, args); } 125 | } 126 | 127 | module.exports = Vm; 128 | -------------------------------------------------------------------------------- /lib/vms.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const events = require('events'); 5 | const host = require('./host'); 6 | 7 | class Vms extends events { 8 | constructor() { 9 | super(); 10 | this.vms = []; 11 | } 12 | 13 | restore() { 14 | this.vms = fs.readdirSync(`${__dirname}/../vmConfigs`).reduce( (prev, file) => { 15 | if (~file.search(/\.json$/) ) { 16 | const vmName = file.slice(0,-5); 17 | console.log(`VMS:restore:vm:${vmName}`); 18 | try { 19 | const vm = new (require('./vm'))(vmName); 20 | prev.push(vm); 21 | console.log(`VMS:restore:loaded:${vmName}`); 22 | } catch(e) { 23 | console.log(e); 24 | console.log(e.stack.split('\n')); 25 | console.log(`VMS:restore:load:e:${vmName}`); 26 | } 27 | } // if 28 | return prev; 29 | }, []); 30 | } // restore() 31 | 32 | add(vmConfig) { 33 | fs.writeFileSync(`${__dirname}/../vmConfigs/${vmConfig.name}.json`, JSON.stringify(vmConfig, false, 2)); 34 | const vm = new (require('./vm'))(vmConfig.name, vmConfig); 35 | this.vms.push(vm); 36 | } 37 | 38 | 39 | start(uuid) { 40 | const vm = this.vm(uuid); 41 | vm && vm.start(); 42 | } 43 | 44 | 45 | procStatusToAll() { 46 | const procsRunning = host.vmProcesses(); 47 | this.vms.forEach( (vm) => { 48 | const msg = {vmUuid:vm.uuid, status:'terminated'}; 49 | if (host.isVmProcRunning(vm.uuid, procsRunning)) { 50 | msg.status = 'running'; 51 | } // if 52 | this.emit('proc-status', msg); 53 | }); 54 | } 55 | 56 | 57 | vm(uuid) { return this.vms.find( (vm) => vm.uuid === uuid); } 58 | 59 | get confs() { return this.vms.reduce( (prev, vm) => {prev.push(vm.conf); return prev;}, []); } 60 | 61 | _nextFreePort(start, fnc) { 62 | let idx = start; 63 | const portMap = {}; 64 | 65 | for(const vm of this.vms) { 66 | try { portMap[fnc(vm.conf)] = true; } catch(e) {} 67 | } 68 | 69 | do { 70 | if (!portMap[idx]) 71 | return idx; 72 | idx++; 73 | } while (idx < 65536) 74 | throw 'no more free ports'; 75 | } 76 | 77 | get nextFreeVncPort() { 78 | return this._nextFreePort(0, (conf) => conf.settings.vnc.port); 79 | } 80 | 81 | get nextFreeQmpPort() { 82 | return this._nextFreePort(15000, (conf) => conf.settings.qmp.port); 83 | } 84 | 85 | 86 | qmpToAll(cmd, args) { this.vms.forEach( (vm) => vm.qmpCmd(cmd, args) ); } 87 | qmpTo(uuid, cmd, args) { 88 | const vm = this.vm(uuid); 89 | vm && vm.qmpCmd(cmd, args); 90 | console.log('qmpTo', uuid, cmd, args); 91 | } // qmpTo() 92 | } 93 | 94 | const vms = new Vms(); 95 | 96 | module.exports = vms; 97 | -------------------------------------------------------------------------------- /lib/webServer.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | http = require 'http' 3 | nodeStatic = require 'node-static' 4 | vmHandler = require './vmHandler' 5 | 6 | 7 | webServer = undefined 8 | 9 | module.exports.start = -> 10 | staticS = new nodeStatic.Server "./public" 11 | 12 | webServer = http.createServer() 13 | webServer.listen 4224, '0.0.0.0' 14 | 15 | webServer.on 'error', (e) -> 16 | console.error "webServer error:" 17 | console.dir e 18 | 19 | webServer.on 'request', (req, res) -> 20 | if 0 is req.url.search '/iso-upload' 21 | isoName = req.url.split('/').pop() 22 | console.log "UPLOAD #{isoName}" 23 | req.pipe fs.createWriteStream "#{process.cwd()}/isos/#{isoName}" 24 | req.on 'end', -> 25 | res.end JSON.stringify {status:'ok'} 26 | vmHandler.newIso isoName 27 | 28 | else if -1 is req.url.search '/socket.io/1' # request to us 29 | staticS.serve req, res # we only serve files, all other stuff via websockets 30 | 31 | module.exports.getHttpServer = -> 32 | return webServer -------------------------------------------------------------------------------- /lib/webService.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const bodyParser = require('body-parser'); 5 | const vms = require('./vms'); 6 | const qemuConfig = require('../qemu/config'); 7 | const Img = require('../qemu/img'); 8 | const drives = require('./drives'); 9 | 10 | 11 | class WebService { 12 | constructor() { 13 | this.sseClients = []; 14 | } 15 | 16 | start() { 17 | this.setup(); 18 | } // start() 19 | 20 | setup() { 21 | this.app = express(); 22 | this.app.use( bodyParser.json() ); 23 | this.app.use( express.static(`${__dirname}/../public`) ); 24 | 25 | this.app.use( (req, res, next) => { 26 | console.log(`WEB:${req.method}:${req.url}`); 27 | next(); 28 | }); 29 | 30 | // add new vm 31 | this.app.post('/api/vms', (req, res) => { 32 | vms.add(req.body); 33 | res.sendStatus(204); 34 | }); 35 | 36 | this.app.get('/api/vms/confs', (req, res) => { 37 | res.json(vms.confs); 38 | }); 39 | 40 | this.app.get('/api/vms/qmp/:action', (req, res) => { 41 | vms.qmpToAll(req.params.action); 42 | res.sendStatus(204); 43 | }); 44 | 45 | this.app.get('/api/vms/proc/status', (req, res) => { 46 | vms.procStatusToAll(); 47 | res.sendStatus(204); 48 | }); 49 | 50 | this.app.get('/api/vm/:uuid/:action', (req, res) => { 51 | vms.qmpTo(req.params.uuid, req.params.action); 52 | res.sendStatus(204); 53 | }); 54 | 55 | this.app.get('/api/vm/:uuid/proc/start', (req, res) => { 56 | vms.start(req.params.uuid); 57 | res.sendStatus(204); 58 | }); 59 | 60 | 61 | // D R I V E S 62 | this.app.post('/api/drives', (req, res) => { 63 | console.log(req.body); 64 | const img = new Img(); 65 | img.create(req.body); 66 | res.sendStatus(204); 67 | }); 68 | 69 | this.app.get('/api/drives', (req, res) => res.json(drives.all)); 70 | 71 | 72 | this.app.get('/a', (req, res) => { 73 | const removeFromSse = (res) => { 74 | for(var idx in this.sseClients) { 75 | const r = this.sseClients[idx]; 76 | 77 | if (r === res) { 78 | console.log('remove response'); 79 | this.sseClients.splice(idx, 1); 80 | break; 81 | } // if 82 | } // for 83 | } // removeFromSse() 84 | 85 | res.on('error', () => removeFromSse(res) ); 86 | res.on('close', () => removeFromSse(res) ); 87 | res.setTimeout(0, () => console.log('response timeout out') ); 88 | 89 | this.sseClients.push(res); 90 | 91 | res.setHeader('content-type', 'text/event-stream'); 92 | 93 | res.write('data: initial response\n\n'); 94 | 95 | ['cpus', 'machines', 'nics'].forEach( (itm) => this.send('qemu-config', {selection:itm, data:qemuConfig[itm]}) ); 96 | }); 97 | 98 | this.app.use( (req, res) => { 99 | res.sendFile('index.html', {root:`${__dirname}/../public/`}); 100 | }); 101 | 102 | 103 | this.app.listen(4224, '0.0.0.0'); 104 | } // setup() 105 | 106 | send(event, data={}) { 107 | this.sseClients.forEach((res) => { 108 | console.log(`WEB:sse:${event}`); 109 | res.write(`event: ${event}\n`); 110 | res.write(`data: ${JSON.stringify(data)}\n\n`); 111 | }); 112 | } // send() 113 | } 114 | 115 | const webService = new WebService(); 116 | 117 | 118 | module.exports = webService; 119 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "babel": { 3 | "moduleRoot": "./", 4 | "plugins": [ 5 | "transform-es2015-modules-amd" 6 | ], 7 | "presets": [ 8 | "es2015" 9 | ] 10 | }, 11 | "scripts": { 12 | "build": "babel webSrc -d public --copy-files", 13 | "watch": "babel --watch webSrc -d public --copy-files" 14 | }, 15 | "dependencies": { 16 | "body-parser": "^1.15.2", 17 | "express": "^4.14.0", 18 | "serve-static": "^1.11.1" 19 | }, 20 | "devDependencies": { 21 | "babel-preset-es2015": "6.3.13", 22 | "babel-plugin-transform-es2015-modules-amd": "6.4.3", 23 | "babel-cli": "6.4.5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /src/node_modules -------------------------------------------------------------------------------- /public/0.html: -------------------------------------------------------------------------------- 1 | from 0 -------------------------------------------------------------------------------- /public/abc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | {{test}} 10 | 11 | rx {{format(cfg.stats.client.bytesReceivedPerSecond, 2, 'IT')}} tx {{format(cfg.stats.client.bytesSentPerSecond, 2, 'IT')}} | cons {{format(cfg.stats.client.httpConnections, 0)}} | mem {{format(cfg.stats.system.residentSize,2,'IT')}}/{{format(cfg.stats.system.virtualSize,2,'IT')}} 12 |
13 | 14 | 15 | 16 | 17 | 103 | -------------------------------------------------------------------------------- /public/app.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'angular', 'angular-route', 'angular-animate', 'angular-sanitize', 'jsoneditor', 'ngjsoneditor'], function (exports, _angular2, _angularRoute, _angularAnimate, _angularSanitize, _jsoneditor, _ngjsoneditor) { 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | 8 | var _angular3 = _interopRequireDefault(_angular2); 9 | 10 | var _angularRoute2 = _interopRequireDefault(_angularRoute); 11 | 12 | var _angularAnimate2 = _interopRequireDefault(_angularAnimate); 13 | 14 | var _angularSanitize2 = _interopRequireDefault(_angularSanitize); 15 | 16 | var _jsoneditor2 = _interopRequireDefault(_jsoneditor); 17 | 18 | var _ngjsoneditor2 = _interopRequireDefault(_ngjsoneditor); 19 | 20 | function _interopRequireDefault(obj) { 21 | return obj && obj.__esModule ? obj : { 22 | default: obj 23 | }; 24 | } 25 | 26 | /*** 27 | * (c) 2016 by duo.uno 28 | * 29 | ***/ 30 | 31 | window.JSONEditor = _jsoneditor2.default; 32 | 33 | var app = angular.module('app', ['ngRoute', 'ngAnimate', 'ngSanitize', 'ng.jsoneditor']); 34 | 35 | app.config(['$routeProvider', '$locationProvider', '$sceDelegateProvider', function (route, locationProvider, sceDelegateProvider) { 36 | 37 | locationProvider.html5Mode(true); 38 | 39 | // route.when('/database/:currentDatabase/manage/collections', { 40 | // controller: 'collectionsController', 41 | // templateUrl: 'manage/collectionsView.html' 42 | // }); 43 | 44 | // route.when('/database/:currentDatabase/collection/:currentCollection/:from/:to', { 45 | // controller: 'collectionController', 46 | // templateUrl: 'collection/collectionView.html' 47 | // }); 48 | 49 | // route.when('/database/:currentDatabase/collection/:currentCollection/:from/:to/:index/document/:documentKey', { 50 | // controller: 'documentController', 51 | // templateUrl: 'document/documentView.html' 52 | // // resolve: {'formatService':'formatService'} 53 | // }); 54 | 55 | // // A Q L 56 | // route.when('/database/:currentDatabase/aql', { 57 | // controller: 'aqlController', 58 | // templateUrl: 'aql/aqlView.html' 59 | // }); 60 | 61 | // // A Q L 62 | // route.when('/database/:currentDatabase/graph', { 63 | // controller: 'graphController', 64 | // templateUrl: 'graph/graphView.html' 65 | // }); 66 | 67 | // route.when('/collection/:collectionName/:from/:to/:index', { 68 | // controller: 'documentRouteController', 69 | // template:'' 70 | // }); 71 | 72 | // V M S 73 | route.when('/vms', { 74 | controller: 'vmsController', 75 | templateUrl: 'vms/vmsView.html' 76 | }); 77 | 78 | // D R I V E S 79 | route.when('/drives', { 80 | controller: 'drivesController', 81 | templateUrl: 'drives/drivesView.html' 82 | }); 83 | 84 | route.otherwise({ redirectTo: '/vms' }); 85 | }]); 86 | 87 | app.run(['$rootScope', '$location', 'messageBrokerService', '$routeParams', '$route', function (rootScope, location, messageBroker, routeParams, route) { 88 | 89 | rootScope.$on('$routeChangeError', function (a, b, c, d) { 90 | console.log('routeChangeError'); 91 | }); 92 | 93 | /* 94 | route change start 95 | location change success 96 | location change start 97 | route change success 98 | | route.current.params 99 | | | routeParams 100 | rcs 0 0 101 | lcs 0 0 102 | lcs 1 0 103 | rcs 1 1 104 | */ 105 | rootScope.$on('$routeChangeStart', function () { 106 | console.log('routeChangeStart'); 107 | // if(route.current) console.log(JSON.stringify(route.current.params, false, 2)); 108 | // console.log(JSON.stringify(routeParams,false, 2)); 109 | }); 110 | 111 | rootScope.$on('$locationChangeStart', function (e, newUrl, oldUrl) { 112 | console.log('locationChangeStart', oldUrl, newUrl); 113 | // if(route.current) console.log(JSON.stringify(route.current.params, false, 2)); 114 | // console.log(JSON.stringify(routeParams,false, 2)); 115 | }); 116 | 117 | rootScope.$on('$locationChangeSuccess', function () { 118 | console.log('locationChangeSuccess'); 119 | // if(route.current) console.log(JSON.stringify(route.current.params, false, 2)); 120 | // console.log(JSON.stringify(routeParams,false, 2)); 121 | }); 122 | 123 | rootScope.$on('$routeChangeSuccess', function () { 124 | console.log('routeChangeSuccess'); 125 | // if(route.current) console.log(JSON.stringify(route.current.params, false, 2)); 126 | // console.log(JSON.stringify(routeParams,false, 2)); 127 | }); 128 | }]); 129 | 130 | exports.default = app; 131 | }); -------------------------------------------------------------------------------- /public/aql/aqlView.html: -------------------------------------------------------------------------------- 1 | options: eval:(asap|blur|NUMms) max:(NUM|all) table:(true|false) 2 | 3 | 4 |
5 | {{lastError}} 6 |
7 |
8 | runtime: {{result.execTime}} ms, HTTP: {{result.httpTime}} ms 9 |
{{result.resultJson}}
10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /public/collection/collectionView.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |  {{collectionInfo.id}}    {{format(collectionInfo.count, 2, 'math')}}    4 |
5 |
6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
content_key
{{doc}}
{{doc._key}}
49 | -------------------------------------------------------------------------------- /public/collections/collectionsController.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$scope', 'messageBrokerService']; 13 | angularModule.push(function (scope, messageBroker) { 14 | messageBroker.pub('current.collection', ''); 15 | messageBroker.pub('collections.reload'); 16 | }); 17 | 18 | _app2.default.controller('collectionsController', angularModule); 19 | }); -------------------------------------------------------------------------------- /public/collections/collectionsView.html: -------------------------------------------------------------------------------- 1 | abcdef -------------------------------------------------------------------------------- /public/collectionsBar/collectionsBarController.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$scope', '$http', '$interval', 'messageBrokerService']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularModule.push(function (scope, http, interval, messageBroker) { 18 | console.log('define collectionsBarController'); 19 | 20 | messageBroker.sub('collectionsbar.status collections.reload current.collection current.database', scope); 21 | scope.cfg = {}; 22 | scope.status = 1; 23 | scope.collections = []; 24 | scope.currentCollection = ''; 25 | scope.currentDatabase = ''; 26 | 27 | scope.setCurrentCollection = function () { 28 | var _iteratorNormalCompletion = true; 29 | var _didIteratorError = false; 30 | var _iteratorError = undefined; 31 | 32 | try { 33 | for (var _iterator = scope.collections[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 34 | var col = _step.value; 35 | 36 | if (col.name == scope.currentCollection) { 37 | col.current = true; 38 | } else col.current = false; 39 | } 40 | } catch (err) { 41 | _didIteratorError = true; 42 | _iteratorError = err; 43 | } finally { 44 | try { 45 | if (!_iteratorNormalCompletion && _iterator.return) { 46 | _iterator.return(); 47 | } 48 | } finally { 49 | if (_didIteratorError) { 50 | throw _iteratorError; 51 | } 52 | } 53 | } 54 | }; 55 | 56 | scope.reloadCollections = function () { 57 | return http.get('/_db/' + scope.currentDatabase + '/_api/collection').then(function (data) { 58 | scope.collections = data.data.collections;scope.setCurrentCollection(); 59 | }); 60 | }; 61 | 62 | scope.$on('collectionsbar.status', function (e, status) { 63 | return scope.status = status; 64 | }); 65 | 66 | scope.$on('collections.reload', function () { 67 | return scope.reloadCollections(); 68 | }); 69 | 70 | scope.$on('current.collection', function (e, currentCollection) { 71 | scope.currentCollection = currentCollection;scope.setCurrentCollection(); 72 | }); 73 | 74 | scope.$on('current.database', function (e, database) { 75 | if (database === scope.currentDatabase) return; 76 | scope.currentDatabase = database; 77 | scope.reloadCollections(); 78 | }); 79 | }); 80 | 81 | _app2.default.controller('collectionsBarController', angularModule); 82 | }); -------------------------------------------------------------------------------- /public/collectionsBar/collectionsBarView.html: -------------------------------------------------------------------------------- 1 | 2 |    3 | 4 | 5 | 6 |  {{col.name}} 7 | 8 | 9 | 10 |  {{col.name}} 11 | 12 | -------------------------------------------------------------------------------- /public/collectionsBar/collectionsController.js: -------------------------------------------------------------------------------- 1 | define([], function () {}); -------------------------------------------------------------------------------- /public/css/custom.css: -------------------------------------------------------------------------------- 1 | * { padding: 0; margin: 0; } 2 | 3 | html, body { 4 | height: 100vh; 5 | width: 100vw; 6 | 7 | display: flex; 8 | flex-direction: row; 9 | flex: 1; 10 | } 11 | 12 | body > * { 13 | flex-shrink: 0; 14 | } 15 | 16 | .div1 { 17 | flex-grow: 1; 18 | max-width: 15vw; 19 | overflow: scroll; 20 | /*min-width: 1vw;*/ 21 | /*display: none;*/ 22 | } 23 | 24 | .div2 { 25 | display: flex; 26 | flex-direction:column; 27 | flex-grow: 99999; 28 | width: 85vw; 29 | /*width:100%;*/ 30 | /*max-width: 100vw;*/ 31 | } 32 | -------------------------------------------------------------------------------- /public/css/custom2.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | a { 4 | cursor: pointer; 5 | } 6 | 7 | * { 8 | cursor: default; 9 | } 10 | 11 | INPUT[type="text"], INPUT[type="number"] { 12 | cursor: text; 13 | } 14 | 15 | SELECT { 16 | cursor: pointer; 17 | } 18 | 19 | DIV.alert > DL.dl-horizontal { 20 | padding-left: 10px; 21 | } -------------------------------------------------------------------------------- /public/css/jsoneditor-flex.css: -------------------------------------------------------------------------------- 1 | /*DIV.jsoneditor {*/ 2 | /*width: inherit; 3 | height: inherit;*/ 4 | /* display: flex; 5 | flex-direction: column; 6 | flex-grow: 1; 7 | }*/ 8 | 9 | /*DIV.jsoneditor-menu { 10 | width: inherit; 11 | flex-grow: 1; 12 | }*/ 13 | 14 | /*DIV.jsoneditor-outer { 15 | width: inherit; 16 | height: inherit; 17 | display: flex; 18 | flex-direction: column; 19 | flex-grow: 99; 20 | }*/ 21 | 22 | /*DIV.jsoneditor-tree {*/ 23 | /* width: inherit; 24 | height: inherit;*/ 25 | /*position: inherit;*/ 26 | /* display: flex; 27 | flex-direction: column; 28 | flex-grow:99; 29 | }*/ 30 | 31 | div.jsoneditor-contextmenu ul li button.jsoneditor-default { 32 | width: 90px; 33 | } -------------------------------------------------------------------------------- /public/directives/aqlResultTable.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularDirective = ['$interpolate', '$sce']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularDirective.push(function (interpolate, sce) { 18 | return { 19 | restrict: 'E', 20 | scope: { 21 | data: "=" 22 | }, 23 | link: function link(scope, element) { 24 | 25 | window.requestAnimationFrame(function () { 26 | var table = document.createElement('table'); 27 | table.className = 'table table-sm'; 28 | 29 | var thead = document.createElement('thead'); 30 | var tr = document.createElement('tr'); 31 | var _iteratorNormalCompletion = true; 32 | var _didIteratorError = false; 33 | var _iteratorError = undefined; 34 | 35 | try { 36 | for (var _iterator = scope.data.keys[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 37 | var key = _step.value; 38 | 39 | var th = document.createElement('th'); 40 | th.textContent = key; 41 | tr.appendChild(th); 42 | } 43 | } catch (err) { 44 | _didIteratorError = true; 45 | _iteratorError = err; 46 | } finally { 47 | try { 48 | if (!_iteratorNormalCompletion && _iterator.return) { 49 | _iterator.return(); 50 | } 51 | } finally { 52 | if (_didIteratorError) { 53 | throw _iteratorError; 54 | } 55 | } 56 | } 57 | 58 | thead.appendChild(tr); 59 | table.appendChild(thead); 60 | 61 | var tbody = document.createElement('tbody'); 62 | 63 | var _iteratorNormalCompletion2 = true; 64 | var _didIteratorError2 = false; 65 | var _iteratorError2 = undefined; 66 | 67 | try { 68 | for (var _iterator2 = scope.data.result[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 69 | var doc = _step2.value; 70 | 71 | var _tr = document.createElement('tr'); 72 | var _iteratorNormalCompletion3 = true; 73 | var _didIteratorError3 = false; 74 | var _iteratorError3 = undefined; 75 | 76 | try { 77 | for (var _iterator3 = scope.data.keys[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 78 | var _key = _step3.value; 79 | 80 | var td = document.createElement('td'); 81 | td.textContent = interpolate('{{doc[key]}}')({ doc: doc, key: _key }); 82 | _tr.appendChild(td); 83 | } 84 | } catch (err) { 85 | _didIteratorError3 = true; 86 | _iteratorError3 = err; 87 | } finally { 88 | try { 89 | if (!_iteratorNormalCompletion3 && _iterator3.return) { 90 | _iterator3.return(); 91 | } 92 | } finally { 93 | if (_didIteratorError3) { 94 | throw _iteratorError3; 95 | } 96 | } 97 | } 98 | 99 | tbody.appendChild(_tr); 100 | } 101 | } catch (err) { 102 | _didIteratorError2 = true; 103 | _iteratorError2 = err; 104 | } finally { 105 | try { 106 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 107 | _iterator2.return(); 108 | } 109 | } finally { 110 | if (_didIteratorError2) { 111 | throw _iteratorError2; 112 | } 113 | } 114 | } 115 | 116 | table.appendChild(tbody); 117 | element.get(0).appendChild(table); 118 | }); 119 | } 120 | }; 121 | }); 122 | 123 | _app2.default.directive('aqlResultTable', angularDirective); 124 | }); -------------------------------------------------------------------------------- /public/directives/feedbackDirective.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularDirective = ['messageBrokerService']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularDirective.push(function () { 18 | return { 19 | restrict: 'E', 20 | scope: { 21 | listenTo: '@test' 22 | }, 23 | link: function link() { 24 | return console.log('directive link called'); 25 | }, 26 | template: '', 27 | controller: ['$scope', '$timeout', 'messageBrokerService', function (scope, timeout, messageBroker) { 28 | scope.msgs = []; 29 | 30 | messageBroker.sub(scope.listenTo, scope); 31 | scope.$on(scope.listenTo, function (ev, msg) { 32 | msg = Object.assign({ id: Date.now() + '' + Math.random(), type: 'info' }, msg); 33 | msg.timeout = timeout(function () { 34 | for (var idx in scope.msgs) { 35 | if (scope.msgs[idx].id == msg.id) { 36 | scope.msgs.splice(idx, 1); 37 | break; 38 | } // if 39 | } // for 40 | }, 10000); 41 | scope.msgs.push(msg); 42 | }); 43 | scope.$on('$destroy', function () { 44 | var _iteratorNormalCompletion = true; 45 | var _didIteratorError = false; 46 | var _iteratorError = undefined; 47 | 48 | try { 49 | for (var _iterator = scope.msgs[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 50 | var msg = _step.value; 51 | 52 | timeout.cancel(msg.timeout); 53 | } // for 54 | } catch (err) { 55 | _didIteratorError = true; 56 | _iteratorError = err; 57 | } finally { 58 | try { 59 | if (!_iteratorNormalCompletion && _iterator.return) { 60 | _iterator.return(); 61 | } 62 | } finally { 63 | if (_didIteratorError) { 64 | throw _iteratorError; 65 | } 66 | } 67 | } 68 | }); 69 | }] 70 | }; 71 | }); 72 | 73 | _app2.default.directive('feedbackMsgs', angularDirective); 74 | 75 | // feedbafeedbackDirective 76 | }); -------------------------------------------------------------------------------- /public/directives/journalSizeDirective.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularDirective = []; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularDirective.push(function () { 18 | return { 19 | restrict: 'A', 20 | require: '?ngModel', 21 | scope: { 22 | base: '@' 23 | }, 24 | link: function link(scope, element, attrs, ngModel) { 25 | if (!ngModel) return; 26 | 27 | ngModel.$formatters.push(function (value) { 28 | if (!isNaN(value)) { 29 | if (scope.base == '10') return value / 1000 / 1000;else return value / 1024 / 1024; 30 | } 31 | }); 32 | 33 | ngModel.$parsers.push(function (value) { 34 | if (isNaN(value)) return; 35 | if (scope.base == '10') return value * 1000 * 1000;else return value * 1024 * 1024; 36 | }); 37 | } 38 | }; 39 | }); 40 | 41 | _app2.default.directive('journalSize', angularDirective); 42 | 43 | // feedbafeedbackDirective 44 | 45 | // angular.module('customControl', ['ngSanitize']). 46 | // directive('contenteditable', ['$sce', function($sce) { 47 | // return { 48 | // restrict: 'A', // only activate on element attribute 49 | // require: '?ngModel', // get a hold of NgModelController 50 | // link: function(scope, element, attrs, ngModel) { 51 | // if (!ngModel) return; // do nothing if no ng-model 52 | 53 | // // Specify how UI should be updated 54 | // ngModel.$render = function() { 55 | // element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 56 | // }; 57 | 58 | // // Listen for change events to enable binding 59 | // element.on('blur keyup change', function() { 60 | // scope.$evalAsync(read); 61 | // }); 62 | // read(); // initialize 63 | 64 | // // Write data to the model 65 | // function read() { 66 | // var html = element.html(); 67 | // // When we clear the content editable the browser leaves a
behind 68 | // // If strip-br attribute is provided then we strip this out 69 | // if ( attrs.stripBr && html == '
' ) { 70 | // html = ''; 71 | // } 72 | // ngModel.$setViewValue(html); 73 | // } 74 | // } 75 | // }; 76 | // }]); 77 | }); -------------------------------------------------------------------------------- /public/document/documentController.js: -------------------------------------------------------------------------------- 1 | define(['app', 'jquery'], function (_app, _jquery) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | var _jquery2 = _interopRequireDefault(_jquery); 7 | 8 | function _interopRequireDefault(obj) { 9 | return obj && obj.__esModule ? obj : { 10 | default: obj 11 | }; 12 | } 13 | 14 | /*** 15 | * (c) 2016 by duo.uno 16 | * 17 | ***/ 18 | 19 | var angularModule = ['$scope', '$routeParams', '$http', 'testService']; 20 | 21 | angularModule.push(function (scope, params, http, testService) { 22 | console.log('init documentController'); 23 | scope.params = params; 24 | 25 | var from = scope.from = Number(params.from); 26 | var to = Number(params.to); 27 | var index = scope.index = Number(params.index); 28 | scope._doc = {}; 29 | 30 | testService.test().then(function (results) { 31 | var _results; 32 | 33 | return _results = results, scope.docsLink = _results.docsLink, scope.prevDocLink = _results.prevDocLink, scope.nextDocLink = _results.nextDocLink, scope._keys = _results._keys, _results; 34 | }); 35 | 36 | scope.obj = { data: {}, preferText: true, options: { mode: 'tree', 37 | change: function change(err) { 38 | if (err) { 39 | scope.obj.canSave = false; 40 | } else { 41 | // if 42 | scope.obj.canSave = true; 43 | } // else 44 | }, 45 | onEditable: function onEditable(node) { 46 | if (-1 < ['_key', '_id', '_rev'].indexOf(node.field) && node.path.length == 1) return false; 47 | return true; 48 | } }, 49 | canSave: true 50 | }; 51 | 52 | http.get('/_db/' + params.currentDatabase + '/_api/document/' + params.currentCollection + '/' + params.documentKey).then(function (data) { 53 | scope.obj.data = data.data; 54 | var _arr = ['_id', '_rev', '_key', '_from', '_to']; 55 | for (var _i = 0; _i < _arr.length; _i++) { 56 | var type = _arr[_i]; 57 | if (data.data[type] === undefined) continue; 58 | scope._doc[type] = data.data[type]; 59 | delete data.data[type]; 60 | } // for 61 | resize(); 62 | }); 63 | 64 | scope.saveDoc = function () { 65 | console.log(scope.obj.data); 66 | console.log(Object.assign({}, scope.obj.data, scope._doc)); 67 | http.put('/_db/' + params.currentDatabase + '/_api/document/' + params.currentCollection + '/' + params.documentKey, scope.obj.data).then(function (data) { 68 | console.log(data); 69 | }); 70 | }; 71 | 72 | // todo: fix uglyness maybe in css? 73 | var resize = function resize() { 74 | return $('DIV#ngeditor').css('height', window.document.documentElement.clientHeight - $('div#ngeditor').position().top + 'px'); 75 | }; 76 | $(window).on('resize', resize); 77 | scope.$on('$destroy', function () { 78 | console.log('destroy'); 79 | $(window).off('resize', resize); 80 | }); 81 | }); 82 | 83 | _app2.default.controller('documentController', angularModule); 84 | 85 | // FOR x IN @@collection LET att = SLICE(ATTRIBUTES(x), 0, 25) SORT TO_NUMBER(x._key) == 0 ? x._key : TO_NUMBER(x._key) LIMIT @offset, @count RETURN KEEP(x, att)" 86 | }); -------------------------------------------------------------------------------- /public/document/documentRouteController.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$scope', '$routeParams', '$http', '$location', 'queryService', '$route']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularModule.push(function (scope, params, http, location, query, route) { 18 | 19 | console.log('init documentRouteController'); 20 | var from = Number(params.from); 21 | var index = Number(params.index); 22 | 23 | query.query('for doc in ' + params.collectionName + ' limit ' + (from + index) + ',1 return doc._key').then(function (result) { 24 | console.log(location.path()); 25 | location.path(location.path() + '/document/' + result[0]); 26 | }); 27 | 28 | // location.path('/'); 29 | }); 30 | 31 | _app2.default.controller('documentRouteController', angularModule); 32 | 33 | // FOR x IN @@collection LET att = SLICE(ATTRIBUTES(x), 0, 25) SORT TO_NUMBER(x._key) == 0 ? x._key : TO_NUMBER(x._key) LIMIT @offset, @count RETURN KEEP(x, att)" 34 | }); -------------------------------------------------------------------------------- /public/document/documentView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |  {{_doc._id}}    {{_doc._rev}}    {{_doc._key}} 4 | 5 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
32 | -------------------------------------------------------------------------------- /public/drives/drivesController.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$scope', '$http']; /*** 13 | * (c) 2017 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularModule.push(function (scope, http) { 18 | console.log('init drivesController'); 19 | 20 | scope.editDrive = {}; 21 | scope.selections = { 22 | formats: ['raw', 'qcow2'], 23 | medias: ['disk', 'cdrom'] 24 | }; 25 | 26 | scope.createDrive = function () { 27 | http.post('/api/drives', scope.editDrive).then(function (data) { 28 | scope.reloadDrives(); 29 | scope.editDrive = {}; 30 | }); 31 | }; 32 | 33 | scope.reloadDrives = function () { 34 | return http.get('api/drives').then(function (data) { 35 | return scope.drives = data.data; 36 | }); 37 | }; 38 | scope.reloadDrives(); 39 | }); 40 | 41 | _app2.default.controller('drivesController', angularModule); 42 | }); -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/graph/graphView.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/home/homeController.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$scope', '$http']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularModule.push(function (scope, http) { 18 | 19 | console.log('init homeController'); 20 | scope.test = {}; 21 | 22 | scope.changelog = ''; 23 | 24 | http.get('/_db/_system/_api/version').then(function (data) { 25 | var version = data.data.version; 26 | if (0 == version.search(/^\d\.\d\.\d$/)) { 27 | version = version.split('.').slice(0, 2).join('.'); 28 | } 29 | http.get('https://raw.githubusercontent.com/arangodb/arangodb/' + version + '/CHANGELOG').then(function (data) { 30 | return scope.changelog = data.data; 31 | }); 32 | }); 33 | http.get('https://raw.githubusercontent.com/arangodb/arangodb/devel/CHANGELOG').then(function (data) { 34 | return scope.changelog = data.data; 35 | }); 36 | 37 | // scope.test.selected = 'a'; 38 | 39 | // scope.test.options = { 40 | // kc: {name:'name c', rule:'rule c'}, 41 | // ka: {name:'name a', rule:'rule a'}, 42 | // kb: {name:'name b', rule:'rule b'}, 43 | // kd: {name:'name d', rule:'rule d'} 44 | // } 45 | }); 46 | 47 | _app2.default.controller('homeController', angularModule); 48 | }); -------------------------------------------------------------------------------- /public/home/homeView.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/public/home/homeView.html -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QEMU-VM-MGMNT 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /public/main.js: -------------------------------------------------------------------------------- 1 | define([], function () { 2 | 'use strict'; 3 | 4 | /*** 5 | * (c) 2016 by duo.uno 6 | * 7 | ***/ 8 | 9 | var requireJsConfig = { 10 | waitSeconds: 0, 11 | shim: { 12 | 'angular': { 13 | deps: ['jquery'] // ! load angular bevor jquery to get not the jquery.event in the ng-mouse events 14 | }, 15 | 'angular-route': { 16 | deps: ['angular'] 17 | }, 18 | 'angular-animate': { 19 | deps: ['angular'] 20 | }, 21 | 'angular-sanitize': { 22 | deps: ['angular'] 23 | }, 24 | 'bootstrap': { 25 | deps: ['jquery'] 26 | }, 27 | 'ngjsoneditor': { 28 | deps: ['jsoneditor', 'angular'] 29 | } 30 | }, 31 | 32 | paths: {} 33 | }; 34 | 35 | var angularVersion = '1-5-5'; 36 | var requireJsPaths = { 37 | 'bootstrap': 'lib/bootstrap-4-0-0', 38 | 'jquery': 'lib/jquery-2-2-3', 39 | 'angular': 'lib/angular-' + angularVersion, 40 | 'angular-route': 'lib/angular-route-' + angularVersion, 41 | 'angular-animate': 'lib/angular-animate-' + angularVersion, 42 | 'angular-sanitize': 'lib/angular-sanitize-' + angularVersion, 43 | 'jsoneditor': 'lib/jsoneditor-5-1-3', 44 | 'ngjsoneditor': 'lib/ngjsoneditor-1-0-0' 45 | }; 46 | 47 | for (var path in requireJsPaths) { 48 | requireJsConfig.paths[path] = requireJsPaths[path]; 49 | } 50 | 51 | requirejs.config(requireJsConfig); 52 | 53 | requirejs(['jquery', 'angular', 'app', 'ngjsoneditor', 'vms/vmsController', 'drives/drivesController', 'navBar/navBarController', 'collectionsBar/collectionsBarController', 'collection/collectionController', 'document/documentController', 'document/documentRouteController', 'manage/collectionsController', 'aql/aqlController', 'graph/graphController', 'services/messageBrokerService', 'services/queryService', 'services/formatService', 'services/fastFilterService', 'services/queriesService', 'services/testService', 'directives/feedbackDirective', 'directives/journalSizeDirective', 'directives/aqlResultTable'], function ($) { 54 | $.ajaxSetup({ cache: false }); 55 | 56 | angular.bootstrap(document, ['app']); 57 | }); 58 | }); -------------------------------------------------------------------------------- /public/navBar/navBarController.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | var stat = 'LET r = (FOR d IN _statistics sort d.time desc limit @time RETURN d)\nlet x = MERGE(FOR t IN [\'http\', \'client\', \'system\']\nlet z = MERGE(FOR a IN ATTRIBUTES(r[0][t])\nfilter !CONTAINS(a, \'Percent\')\nRETURN {[a]: sum(r[*][t][a]) / @time})\nRETURN {[t]:z}) RETURN x'; 18 | 19 | var angularModule = ['$scope', '$http', '$interval', 'formatService', 'messageBrokerService', '$location']; 20 | 21 | angularModule.push(function (scope, http, interval, format, messageBroker, location) { 22 | console.log('init navBarController'); 23 | scope.format = format; 24 | 25 | scope.collectionsBarStatus = 1; 26 | scope.changeCollectionsBarStatus = function () { 27 | scope.collectionsBarStatus++;if (scope.collectionsBarStatus > 1) scope.collectionsBarStatus = 0;messageBroker.pub('collectionsbar.status', scope.collectionsBarStatus); 28 | }; 29 | 30 | http.get('/_db/_system/_api/version').then(function (data) { 31 | scope.cfg.arango = data.data; 32 | http.jsonp('https://www.arangodb.com/repositories/versions.php?jsonp=JSON_CALLBACK&version=' + scope.cfg.arango.version + '&callback=JSON_CALLBACK').then(function (data) { 33 | scope.cfg.availableVersions = Object.keys(data.data).sort().map(function (key) { 34 | return data.data[key].version + ' ' + key; 35 | }).join(', '); 36 | if (scope.cfg.availableVersions) { 37 | scope.cfg.availableVersions = '(' + scope.cfg.availableVersions + ' available)'; 38 | } // if 39 | }); 40 | }); 41 | http.get('/_api/database').then(function (data) { 42 | return scope.cfg.dbs = data.data.result; 43 | }); 44 | 45 | scope.cfg = {}; 46 | scope.cfg.arango = 'n/a'; 47 | scope.cfg.dbs = []; 48 | scope.cfg.selectedDb = ''; 49 | 50 | scope.databaseChanged = function () { 51 | return location.url('/database/' + scope.cfg.selectedDb); 52 | }; 53 | 54 | scope.$on('current.database', function (e, database) { 55 | return scope.cfg.selectedDb = database; 56 | }); 57 | messageBroker.sub('current.database', scope); 58 | 59 | scope.refreshStats = function () { 60 | http.post('/_db/_system/_api/cursor', { cache: false, query: stat, bindVars: { time: 6 } }).then(function (data) { 61 | return scope.cfg.stats = data.data.result[0]; 62 | }); 63 | }; 64 | scope.refreshStats(); 65 | interval(scope.refreshStats, 10 * 1000); 66 | }); 67 | 68 | _app2.default.controller('navBarController', angularModule); 69 | }); -------------------------------------------------------------------------------- /public/navBar/navBarView.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{cfg.arango.version}} {{cfg.arango.server}} {{cfg.availableVersions}} AQL 4 |  {{format(cfg.stats.client.bytesReceivedPerSecond, 2, 'IT')}}  {{format(cfg.stats.client.bytesSentPerSecond, 2, 'IT')}} | {{format(cfg.stats.client.httpConnections, 0)}} | {{format(cfg.stats.http.requestsTotalPerSecond, 2, 'math')}} | {{format(cfg.stats.system.residentSize,2,'IT')}}/{{format(cfg.stats.system.virtualSize,2,'IT')}} | {{format(cfg.stats.system.majorPageFaultsPerSecond, 1)}}/{{format(cfg.stats.system.minorPageFaultsPerSecond, 1)}} 5 |
6 | -------------------------------------------------------------------------------- /public/services/fastFilterService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$http', '$q', 'messageBrokerService']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularModule.push(function (http, q, messageBroker) { 18 | var collectionName = ''; 19 | 20 | http.get('collections').then(function (result) { 21 | collectionName = result.data.settings; 22 | reloadFilters(); 23 | }); 24 | 25 | var reloadFilters = function reloadFilters() { 26 | http.get('/_db/_system/_api/document/' + collectionName + '/fastFilter').then(function (data) { 27 | 28 | for (var key in data.data.rules) { 29 | fastFilter.rules[key] = data.data.rules[key]; 30 | } // for 31 | }); 32 | }; 33 | 34 | var fastFilter = { 35 | rules: { 'none': { name: 'none', rule: '// none' } }, 36 | currentRule: function currentRule() { 37 | return fastFilter.rules[messageBroker.last('current.fastFilter')].rule; 38 | }, 39 | save: function save() { 40 | http.patch('/_db/_system/_api/document/' + collectionName + '/fastFilter', { rules: fastFilter.rules }); 41 | } 42 | }; 43 | 44 | return fastFilter; 45 | }); 46 | 47 | _app2.default.service('fastFilterService', angularModule); 48 | }); -------------------------------------------------------------------------------- /public/services/formatService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = []; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularModule.push(function () { 18 | return function (str) { 19 | var fix = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1]; 20 | var ext = arguments.length <= 2 || arguments[2] === undefined ? undefined : arguments[2]; 21 | 22 | str = Number(str); 23 | if (ext) ext = ext.toLowerCase(); 24 | 25 | if (str > 1000 * 1000 * 1000) { 26 | str = (str / 1000 / 1000 / 1000).toFixed(fix); 27 | switch (ext) { 28 | case 'it': 29 | ext = ' GB'; 30 | break; 31 | case 'math': 32 | ext = ' G'; 33 | break; 34 | } // switch 35 | } else if (str > 1000 * 1000) { 36 | str = (str / 1000 / 1000).toFixed(fix); 37 | switch (ext) { 38 | case 'it': 39 | ext = ' MB'; 40 | break; 41 | case 'math': 42 | ext = ' M'; 43 | break; 44 | } // switch 45 | } else if (str > 1000) { 46 | str = (str / 1000).toFixed(fix); 47 | switch (ext) { 48 | case 'it': 49 | ext = ' KB'; 50 | break; 51 | case 'math': 52 | ext = ' K'; 53 | break; 54 | } // switch 55 | } else { 56 | str = str.toFixed(fix); 57 | switch (ext) { 58 | case 'it': 59 | ext = ' B'; 60 | break; 61 | case 'math': 62 | ext = ''; 63 | break; 64 | } // switch 65 | } 66 | if (ext) str = '' + str + ext; 67 | return str; 68 | }; 69 | }); 70 | 71 | _app2.default.service('formatService', angularModule); 72 | }); -------------------------------------------------------------------------------- /public/services/messageBrokerService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var transactions = {}; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | var lastData = {}; 18 | 19 | var MessageBrokerService = { 20 | sub: function sub(msgs, scope) { 21 | console.log('SUB', msgs); 22 | var _iteratorNormalCompletion = true; 23 | var _didIteratorError = false; 24 | var _iteratorError = undefined; 25 | 26 | try { 27 | var _loop = function _loop() { 28 | var msg = _step.value; 29 | 30 | (transactions[msg] || (transactions[msg] = [])).push(scope); 31 | scope.$on('$destroy', function () { 32 | return MessageBrokerService.usub(msg, scope); 33 | }); 34 | }; 35 | 36 | for (var _iterator = msgs.replace(/\+ /g, ' ').trim().split(' ')[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 37 | _loop(); 38 | } 39 | } catch (err) { 40 | _didIteratorError = true; 41 | _iteratorError = err; 42 | } finally { 43 | try { 44 | if (!_iteratorNormalCompletion && _iterator.return) { 45 | _iterator.return(); 46 | } 47 | } finally { 48 | if (_didIteratorError) { 49 | throw _iteratorError; 50 | } 51 | } 52 | } 53 | }, 54 | 55 | pub: function pub(msg, data) { 56 | console.log('PUB', msg, data); 57 | lastData[msg] = data; 58 | if (!transactions[msg]) return; 59 | var _iteratorNormalCompletion2 = true; 60 | var _didIteratorError2 = false; 61 | var _iteratorError2 = undefined; 62 | 63 | try { 64 | for (var _iterator2 = transactions[msg][Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 65 | var scope = _step2.value; 66 | 67 | scope.$emit(msg, data); 68 | } 69 | } catch (err) { 70 | _didIteratorError2 = true; 71 | _iteratorError2 = err; 72 | } finally { 73 | try { 74 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 75 | _iterator2.return(); 76 | } 77 | } finally { 78 | if (_didIteratorError2) { 79 | throw _iteratorError2; 80 | } 81 | } 82 | } 83 | }, 84 | 85 | last: function last(msg) { 86 | return lastData[msg]; 87 | }, 88 | 89 | usub: function usub(msgs, scope) { 90 | for (var _msg in msgs.replace(/\ +/g, ' ').trim().split(' ')) { 91 | if (!transactions[_msg]) continue; 92 | for (var idx in transactions[_msg]) { 93 | var s = transactions[_msg][idx]; 94 | if (s.$id === scope.$id) { 95 | transactions[_msg].splice(idx, 1); 96 | break; 97 | } 98 | } 99 | } 100 | } 101 | }; 102 | 103 | var mbcall = function mbcall() { 104 | return MessageBrokerService; 105 | }; 106 | 107 | _app2.default.service('messageBrokerService', [mbcall]); 108 | }); -------------------------------------------------------------------------------- /public/services/queriesService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$http', '$q', 'messageBrokerService']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularModule.push(function (http, q, messageBroker) { 18 | var collectionName = ''; 19 | 20 | http.get('collections').then(function (result) { 21 | collectionName = result.data.settings; 22 | reloadQueries(); 23 | }); 24 | 25 | var reloadQueries = function reloadQueries() { 26 | http.get('/_db/_system/_api/document/' + collectionName + '/savedQueries').then(function (data) { 27 | 28 | for (var key in data.data.queries) { 29 | queries.queries[key] = data.data.queries[key]; 30 | } // for 31 | }); 32 | }; 33 | 34 | var queries = { 35 | queries: { unsaved: { name: 'unsaved', options: { eval: 'blur', max: 1000 }, query: '// eval:asap max:all table:true name:unsaved\n// query\n' } }, 36 | currentName: function currentName() { 37 | return queries.queries[messageBroker.last('current.query')].name; 38 | }, 39 | save: function save() { 40 | http.patch('/_db/_system/_api/document/' + collectionName + '/savedQueries', { queries: queries.queries }); 41 | } 42 | }; 43 | 44 | return queries; 45 | }); 46 | 47 | _app2.default.service('queriesService', angularModule); 48 | }); -------------------------------------------------------------------------------- /public/services/queryService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$http', '$q', 'messageBrokerService']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | angularModule.push(function (http, q, messageBroker) { 18 | 19 | return { 20 | query: function query(_query) { 21 | var d = q.defer(); 22 | console.log('AQL-QUERY', _query); 23 | http.post('/_db/' + messageBroker.last('current.database') + '/_api/cursor', { cache: false, query: _query, options: { fullCount: true } }).then(function (data) { 24 | d.resolve(data.data); 25 | }); 26 | return d.promise; 27 | } 28 | }; 29 | }); 30 | 31 | _app2.default.service('queryService', angularModule); 32 | }); -------------------------------------------------------------------------------- /public/services/testService.js: -------------------------------------------------------------------------------- 1 | define(['app'], function (_app) { 2 | 'use strict'; 3 | 4 | var _app2 = _interopRequireDefault(_app); 5 | 6 | function _interopRequireDefault(obj) { 7 | return obj && obj.__esModule ? obj : { 8 | default: obj 9 | }; 10 | } 11 | 12 | var angularModule = ['$route', '$routeParams', 'queryService', 'fastFilterService', '$q']; /*** 13 | * (c) 2016 by duo.uno 14 | * 15 | ***/ 16 | 17 | // @todo add fastFilter statement maybe into query.query 18 | 19 | 20 | angularModule.push(function (route, params, query, fastFilter, q) { 21 | 22 | return { 23 | test: function test() { 24 | var d = q.defer(); 25 | 26 | var from = params.from; 27 | var to = params.to; 28 | var index = params.index; 29 | 30 | from = Number(from); 31 | to = Number(to); 32 | index = Number(index); 33 | var offset = from + index - 1; 34 | var batchSize = to - from + 1; 35 | var prevDocLink = '', 36 | nextDocLink = '', 37 | docsLink = ''; 38 | 39 | var limit = 3; 40 | if (offset < 0) { 41 | offset = 0; 42 | limit = 2; 43 | } // if 44 | query.query('for doc in ' + params.currentCollection + ' ' + fastFilter.currentRule() + '\n limit ' + offset + ',' + limit + ' return doc._key').then(function (result) { 45 | result = result.result; 46 | var newIndex = index + 1; 47 | if (newIndex > batchSize - 1) { 48 | nextDocLink = 'database/' + params.currentDatabase + '/collection/' + params.currentCollection + '/' + (from + batchSize) + '/' + (to + batchSize) + '/0/document/' + result.slice(-1)[0]; 49 | } else { 50 | nextDocLink = 'database/' + params.currentDatabase + '/collection/' + params.currentCollection + '/' + from + '/' + to + '/' + (index + 1) + '/document/' + result.slice(-1)[0]; 51 | } 52 | newIndex = index - 1; 53 | if (newIndex < 0) { 54 | prevDocLink = 'database/' + params.currentDatabase + '/collection/' + params.currentCollection + '/' + (from - batchSize) + '/' + (to - batchSize) + '/' + (batchSize - 1) + '/document/' + result.slice(0, 1)[0]; 55 | } else { 56 | prevDocLink = 'database/' + params.currentDatabase + '/collection/' + params.currentCollection + '/' + from + '/' + to + '/' + (index - 1) + '/document/' + result.slice(0, 1)[0]; 57 | } 58 | docsLink = 'database/' + params.currentDatabase + '/collection/' + params.currentCollection + '/' + from + '/' + to; 59 | d.resolve({ docsLink: docsLink, prevDocLink: prevDocLink, nextDocLink: nextDocLink, _keys: result }); 60 | }); 61 | return d.promise; 62 | } 63 | }; 64 | }); 65 | 66 | _app2.default.service('testService', angularModule); 67 | }); -------------------------------------------------------------------------------- /public/vms/0.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | 7 |

For > 1 VM names will add -0 -1 ... also autogenerate MAC addresses.

8 |
9 |
10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /public/vms/1.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | 7 |

VM name

8 |
9 |
10 |
11 | 12 |
13 | 14 |

Select your keyboard layout

15 |
16 |
17 |
18 | 19 |
20 | 21 |

Optional provide a UUID for your vm

22 |
23 |
24 |
25 |
Select your keyboard layout that you have. If you select the wrong format, you maybe can not type adequate.
26 | -------------------------------------------------------------------------------- /public/vms/2.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | 7 |

Select your machine

8 |
9 |
10 |
11 | 12 |
13 | 14 |

vga cards are none, standard (std) and paravirtual qxl or virtio

15 |
16 |
17 |
18 | 19 |
20 | 21 |

maximal size of RAM in MB

22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 | 32 |

Select your CPU model

33 |
34 |
35 |
36 | 37 |
38 | 39 |

Number of sockets

40 |
41 |
42 |
43 | 44 |
45 | 46 |

Number of cores per socket

47 |
48 |
49 |
50 | 51 |
52 | 53 |

Number of threads per core

54 |
55 |
56 |
57 |
If you want to use openGL, select 'qxl' or 'virtio'. For standard usage use 'std'. If you want no graphic output, then use 'none'.
58 |
Number of all CPUs ares sockets * cores * threads.
59 | -------------------------------------------------------------------------------- /public/vms/3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 |
8 | 9 |

Select a drive.

10 |
11 |
12 | 13 | 17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 |

Select the interface.

25 |
26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 | 37 |
38 |
39 |
40 | 41 | 42 |
43 | 44 |
45 |
46 | 49 |
50 |
51 |
52 | 53 | 54 |
55 | 56 |
57 |
58 | 61 |
62 |
63 |
64 | 65 | 66 |
67 | 68 |
69 |
70 | 73 |
74 |
75 |
76 | 77 | 78 |
79 | 80 |
81 | 82 |

Select the cache.

83 |
84 |
85 | 86 | 87 |
88 | 89 |
90 | 91 |

Select the aio (async io).

92 |
93 |
94 | 95 | 96 |
97 | 98 |
99 | 100 |

Select discard operation.

101 |
102 |
103 | 104 |
105 | 106 |
107 | 108 |
109 |
110 |
111 | -------------------------------------------------------------------------------- /public/vms/5.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | 10 |
11 |

Connect to the VM via VNC

12 |
13 |
14 |
15 | 16 |
17 |
18 | 21 |
22 |

Connect to the VM via SPICE

23 |
24 |
25 |
26 |
Use VNC or SPICE to connect to your vms virtual desktop environment or console screen.
27 | -------------------------------------------------------------------------------- /public/vms/6.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | 10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 |

Type in your Host-NUMA node config for the CPU. E.g. 0,1 or 1-3

18 |
19 |
20 |
21 | 22 |
23 | 24 |

type in your Host-NUMA node config for the RAM. E.g. 0,1 or 1-3

25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 | 33 |
34 |
35 | 38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 |

Type in your VM-NUMA node config for the CPU. E.g. 0,1 or 1-3

46 |
47 |
48 |
49 | 50 |
51 | 52 |

type in your VM-NUMA node config for the RAM. E.g. 0,1 or 1-3

53 |
54 |
55 |
56 |
The ram of an host system with more than one cpu socket are divided in local and foreign regions. Each cpu socked addresses a piece of ram. This is reffered as Local addressed ram. This porting is directly attached to the cpu socket and can accessed directly. Foreign ram has to be accessed via QPI (Intel QuickPath Interconnect) or HyperTransport (AMD). The cpu socket connects to the forreign memory controller in an other cpu socket and asks for the ram the cpu wants to accesss. This is much slower than local accessed ram. As a consequence Guests memory should be placed in local ram for better performance. For more informatinos about NUMA please reffere to the great RedHat documentation.
57 | -------------------------------------------------------------------------------- /public/vms/7.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | 10 |
11 |

keep vm process running while the vm os is shutdown / not running

12 |
13 |
14 |
15 | 16 |
17 |
18 | 21 |
22 |

when vm process is started, vm is paused

23 |
24 |
25 |
26 | 27 |
28 |
29 | 32 |
33 |

keep vm process running

34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /public/vms/8.html: -------------------------------------------------------------------------------- 1 | form 8 USB -------------------------------------------------------------------------------- /public/vms/from6.html: -------------------------------------------------------------------------------- 1 | from 6 -------------------------------------------------------------------------------- /public/vms/vmsView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 53 | 54 | 55 |
NameCPUsRAMVGAVNC-Port
16 |
{{stat(vm.uuid).procStatus}}
17 |
19 |
{{stat(vm.uuid).status}}   20 |  
21 | 22 |
{{vm.name}}{{vm.hardware.cpu.sockets * vm.hardware.cpu.cores * vm.hardware.cpu.threads}}{{vm.hardware.ram}} MB{{vm.hardware.vga}}{{vm.settings.vnc.port+5900}}
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | UUID: {{vm.uuid}} 39 | 40 |
41 |
42 |
43 |
Sockets
44 |
{{vm.hardware.cpu.sockets}}
45 |
Cores per Socket
46 |
{{vm.hardware.cpu.cores}}
47 |
Threads per Core
48 |
{{vm.hardware.cpu.threads}}
49 |
50 |
51 |
52 |
56 | 57 |
58 | 59 |
60 |
61 |

Settings

62 | 63 |
64 | {{s}} 65 |
66 |
67 |
68 |
69 |
70 |

{{settings[curSetting.idx]}}

71 |
72 |
73 |
74 |
75 |
76 | 77 |
{{editVm | json}}
78 | 79 |
{{editDrive | json}}
80 | 81 |
{{curSetting | json}}
82 | -------------------------------------------------------------------------------- /qemu/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | read qemu config from actually qemu binary 5 | */ 6 | 7 | const execSync = require('child_process').execSync; 8 | 9 | 10 | class QemuConfig { 11 | constructor() { 12 | 13 | // V E R S I O N 14 | this.versions = {major:0,minor:0,patch:0}; 15 | execSync('qemu-system-x86_64 --version 2>&1').toString('utf8').split('\n').forEach( (line) => { 16 | const match = line.match(/^QEMU\ emulator\ version\ (\d+)\.(\d+)\.(\d+)/); 17 | if (match) { 18 | this.versions.major = match[1]; 19 | this.versions.minor = match[2]; 20 | this.versions.patch = match[3]; 21 | } // if 22 | }); 23 | this.version = `${this.versions.major}.${this.versions.minor}.${this.versions.patch}`; 24 | 25 | // C P U S 26 | this.cpus = execSync('qemu-system-x86_64 -cpu help').toString('utf8').split('\n').reduce( (prev, line) => { 27 | const match = line.match(/^x86\ +(\S+)\ ?(.*)/); 28 | if (match) { 29 | prev.push({name:match[1], description:match[2].trim().replace(/\ +/g, ' ')}); 30 | } // if 31 | return prev; 32 | }, []); 33 | 34 | 35 | // M A C H I N E S 36 | this.machines = execSync('qemu-system-x86_64 -machine help').toString('utf8').split('\n').reduce( (prev, line) => { 37 | const match = line.match(/^(\S+)\ {2,}(.*)/); 38 | if (match) { 39 | prev.push({name:match[1], description:match[2]}); 40 | } // if 41 | return prev; 42 | }, []); 43 | 44 | 45 | // N E T M O D E L S 46 | this.nics = this.netModels = this.nicModels = execSync('qemu-system-x86_64 -net nic,model=help 2>&1').toString('utf8').split('\n').reduce( (prev, line) => { 47 | const match = line.match(/models:\ (\S+)/); 48 | 49 | if (match) { 50 | match[1].split(',').forEach( (model) => prev.push({name:model, description:''})); 51 | } 52 | return prev; 53 | }, []); 54 | 55 | 56 | // V N C S U P P O R T 57 | this.vncSupport = false; 58 | try { 59 | execSync('qemu-system-x86_64 -nvc 2>&1'); 60 | } catch(e) { 61 | if ('qemu-system-x86_64: -nvc: invalid option' === e.stdout.toString('utf8').split('\n').shift()) { 62 | this.vncSupport = true; 63 | } 64 | } 65 | 66 | 67 | // S P I C E S U P P O R T 68 | this.spiceSupport = false; 69 | try { 70 | execSync('qemu-system-x86_64 -spice 2>&1'); 71 | } catch(e) { 72 | if ('qemu-system-x86_64: -spice: invalid option' === e.stdout.toString('utf8').split('\n').shift()) { 73 | this.spiceSupport = true; 74 | } 75 | } 76 | } // constructor() 77 | } 78 | 79 | const qemuConfig = new QemuConfig(); 80 | 81 | module.exports = qemuConfig; 82 | -------------------------------------------------------------------------------- /qemu/img.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const execSync = require('child_process').execSync; 5 | const uuid = require('../lib/uuid'); 6 | 7 | class Img { 8 | constructor(conf = {}) { 9 | if (conf.create) { 10 | conf.uuid = uuid.new(); 11 | } // if 12 | 13 | if (conf.create && 'file' === conf.type) { 14 | const createArgs = ['qemu-img', 'create', '-f', conf.format]; 15 | 16 | if (conf.useBacking) { 17 | createArgs.push('-o', `backing_file=${conf.backingPath},backing_fmt=qcow2${conf.size?',size='+conf.size:''}`) 18 | } // if 19 | 20 | createArgs.push(conf.uuid+'.img'); 21 | 22 | if (!conf.useBacking && conf.size) { 23 | createArgs.push('-o', `size=${conf.size}`); 24 | } // if 25 | 26 | const ret = execSync(createArgs.join(' '), {shell:'/bin/bash', cwd:`${__dirname}/../disks/`}); 27 | fs.writeFileSync(`${__dirname}/../disks/${conf.uuid}.json`, JSON.stringify(conf, false, 2)); 28 | 29 | console.log(ret.toString()); 30 | } // if 31 | this.conf = JSON.parse(JSON.stringify(conf)); 32 | } 33 | 34 | create(conf) { 35 | if (!conf.uuid) { 36 | conf.uuid = uuid.new(); 37 | } // if 38 | 39 | const createArgs = ['qemu-img', 'create', `${conf.uuid}.img`, '-f', conf.format]; 40 | 41 | switch (conf.type) { 42 | case 'new': 43 | createArgs.push('-o', `size=${conf.size}`); 44 | 45 | if (conf.useBacking) { 46 | createArgs.push('-o', `backing_file=${conf.backingPath}`); 47 | } 48 | 49 | const ret = execSync(createArgs.join(' '), {shell:'/bin/bash', cwd:`${__dirname}/../disks/`}); 50 | conf.path = `disks/${conf.uuid}.img`; 51 | fs.writeFileSync(`${__dirname}/../disks/${conf.uuid}.json`, JSON.stringify(conf, false, 2)); 52 | 53 | console.log(ret.toString()); 54 | break; 55 | 56 | case 'path': 57 | fs.writeFileSync(`${__dirname}/../disks/${conf.uuid}.json`, JSON.stringify(conf, false, 2)); 58 | break; 59 | } // switch 60 | } 61 | 62 | get config() { 63 | return { 64 | path:`disks/${this.conf.uuid}.img`, 65 | type:'disk', 66 | interface:'virtio' 67 | }; 68 | } 69 | } 70 | 71 | module.exports = Img; 72 | 73 | /* 74 | 75 | name: 'w10', 76 | type: 'new', 77 | size: '20G', 78 | format: 'qcow2', 79 | media: 'disk' 80 | 81 | 82 | const img = new Img({backingPath:"nbd:192.168.2.103:10115", 83 | format:"qcow2", 84 | type:'file', 85 | size:'20G', 86 | name:"arangodrive-3", 87 | useBacking:true,create:true}); 88 | 89 | console.log(img.config); 90 | */ 91 | 92 | 93 | // qemu-img create -o backing_file=nbd:192.168.2.103:13140,backing_fmt=qcow2 -f qcow2 newin10.img 94 | 95 | /* 96 | backingPath:"nbd:192.168.2.103:10115" 97 | format:"qcow2" 98 | name:"arangodrive-3" 99 | size:"20G" 100 | useBacking:true 101 | */ -------------------------------------------------------------------------------- /qemu/process.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/qemu/process.js -------------------------------------------------------------------------------- /qemu/qmp.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | const vms = require('../lib/vms'); 5 | const host = require('../lib/host'); 6 | 7 | class Qmp { 8 | constructor(vmUuid) { 9 | this.vmUuid = vmUuid; 10 | this.count = 0; 11 | this.lines = []; 12 | this.cmdStack = []; 13 | }; 14 | 15 | attach(conf) { 16 | console.log(`QMP attach to ${this.vmUuid}`); 17 | clearTimeout(this.timeout); 18 | this.conf = conf; 19 | this.timeout = undefined; 20 | this.cmdStack.length = 0; 21 | 22 | var socket = this.socket = net.connect(conf.port); 23 | const onError = (e) => { 24 | console.log('socket error', e); 25 | if (e.code == 'ECONNREFUSED') { 26 | console.log('try reconnect', this.count++); 27 | socket.removeListener('error', onError); 28 | socket.removeListener('data', onData); 29 | socket.removeListener('connect', onConnect); 30 | socket.removeListener('close', onClose); 31 | socket = this.socket = null; 32 | 33 | this.timeout = setTimeout( () => { 34 | this.attach(conf); 35 | }, 1000); 36 | } // if 37 | }; 38 | const onConnect = () => { 39 | console.log('connected to socket'); 40 | this.count = 0; 41 | this.cmdStack.push('qmp_capabilities'); 42 | this.cmd('qmp_capabilities', 'query-status', 'query-vnc'); 43 | }; 44 | const onClose = (hadError) => { 45 | console.log('socket close had error', hadError); 46 | socket = null; 47 | vms.emit('status', {status:'N/A', vmUuid:this.vmUuid}); 48 | 49 | if (!host.isVmProcRunning(this.vmUuid)) { 50 | vms.emit('proc-status', {status:'terminated', vmUuid:this.vmUuid}); 51 | } // if 52 | }; 53 | const onData = (d) => { 54 | this.lines.push.apply(this.lines, d.toString('utf8').split('\r\n')); 55 | 56 | var json = []; 57 | while(this.lines.length ) { 58 | const line = this.lines.shift() 59 | if ('' == line) continue; 60 | json.push(line); 61 | 62 | if ('}' == line) { 63 | var msg = JSON.parse(json.join('')); 64 | console.log(`QMP:cmd:res:pre: ${JSON.stringify(msg)}`); 65 | if (msg.return) { msg = msg.return; } 66 | msg.vmUuid = this.vmUuid; 67 | msg.wasCmd = this.cmdStack.shift(); 68 | if (msg.timestamp) { 69 | msg.timestamp = Number(`${msg.timestamp.seconds}.${msg.timestamp.microseconds}`); 70 | } else { 71 | msg.timestamp = Date.now()/1000; 72 | } // else 73 | console.log(`QMP:cmd:res:post:${JSON.stringify(msg)}`); 74 | if (msg.event) { 75 | vms.emit('event', msg); 76 | } else if (msg.status) { // if 77 | vms.emit('status', msg); 78 | } else { // else if 79 | vms.emit('generic', msg); 80 | } // else 81 | json = []; 82 | } // if 83 | } // while 84 | this.lines.unshift.apply(this.lines, json); 85 | }; 86 | 87 | socket.on('error', onError); 88 | socket.on('connect', onConnect); 89 | socket.on('close', onClose); 90 | socket.on('data', onData); 91 | } // attach() 92 | 93 | 94 | cmd(cmd, args) { 95 | if (!this.socket) { 96 | vms.emit('status', {status:'N/A', vmUuid:this.vmUuid}); 97 | } else if (typeof args == 'object') { 98 | this.cmdStack.push(cmd); 99 | console.log(`QMP:cmd:${cmd}:args:${JSON.stringify}`); 100 | this.socket.write(JSON.stringify({execute:cmd, arguments: args}) ); 101 | } 102 | else { 103 | for(var cmd of arguments) { 104 | if (typeof cmd != 'string') continue; 105 | this.cmdStack.push(cmd); 106 | console.log(`QMP:cmd:${cmd}`); 107 | this.socket.write(JSON.stringify({execute:cmd}) ); 108 | } // for 109 | } 110 | } // cmd() 111 | } 112 | 113 | module.exports = Qmp; 114 | -------------------------------------------------------------------------------- /server.coffee: -------------------------------------------------------------------------------- 1 | 2 | vmHandler = require './lib/vmHandler' 3 | webServer = require './lib/webServer' 4 | socketServer = require './lib/socketServer' 5 | 6 | webServer.start() 7 | socketServer.start webServer.getHttpServer() 8 | 9 | vmHandler.loadExtensions() 10 | 11 | vmHandler.loadFiles() 12 | vmHandler.reconnectVms() 13 | -------------------------------------------------------------------------------- /test/args.coffee: -------------------------------------------------------------------------------- 1 | 2 | os = require 'os' 3 | 4 | osTypeBackup = os.type 5 | 6 | 7 | resArgs = require './args.json' 8 | parser = require '../lib/src/parser' 9 | assert = require 'assert' 10 | 11 | 12 | test = (conf, i) -> 13 | describe 'config check', -> 14 | it conf.name, -> 15 | 16 | if 0 is conf.name.search /^linux/ 17 | os.type = -> 'linux' 18 | else 19 | os.type = osTypeBackup 20 | 21 | args = parser.guestConfToArgs(conf).args 22 | console.dir args 23 | assert.deepEqual args, resArgs[i] 24 | 25 | 26 | for conf,i in require './confs' 27 | test conf,i 28 | 29 | 30 | 31 | #process.exit() -------------------------------------------------------------------------------- /test/args.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ "numactl", 3 | "--cpunodebind=1", 4 | "--membind=1", 5 | "--", 6 | "qemu-system-x86_64", 7 | "-nographic", 8 | "-nodefconfig", 9 | "-nodefaults", 10 | "-machine", 11 | "accel=kvm", 12 | "-enable-kvm", 13 | "-m", 14 | "128M", 15 | "-vga", 16 | "none", 17 | "-qmp", 18 | "tcp:127.0.0.1:24,server", 19 | "-k", 20 | "de", 21 | "-cpu", 22 | "host", 23 | "-smp", 24 | "cpus=8,cores=2,threads=4,sockets=1", 25 | "-net", 26 | "nic,model=virtio,macaddr=ab:cd:ef", 27 | "-net", 28 | "tap", 29 | "-spice port=42,addr=192.168.178.48,disable-ticketing", 30 | "-usb", 31 | "-usbdevice host:101:911", 32 | "-drive", 33 | "file=disks/Platte.img.img,media=disk,if=ide", 34 | "-drive", 35 | "file=isos/Platte.iso,media=cdrom,if=ide", 36 | "-vnc", 37 | ":5900", 38 | "-boot", 39 | "d" ], 40 | [ "numactl", 41 | "--cpunodebind=1", 42 | "--membind=1", 43 | "--", 44 | "qemu-system-x86_64", 45 | "-nographic", 46 | "-nodefconfig", 47 | "-nodefaults", 48 | "-machine", 49 | "accel=kvm", 50 | "-enable-kvm", 51 | "-m", 52 | "128M", 53 | "-vga", 54 | "none", 55 | "-qmp", 56 | "tcp:127.0.0.1:24,server", 57 | "-k", 58 | "de", 59 | "-cpu", 60 | "host", 61 | "-smp", 62 | "cpus=8,cores=2,threads=4,sockets=1", 63 | "-net", 64 | "nic,model=virtio,macaddr=ab:cd:ef", 65 | "-net", 66 | "tap", 67 | "-usb", 68 | "-drive", 69 | "file=disks/Platte.img.img,media=disk,if=ide", 70 | "-drive", 71 | "file=isos/Platte.iso,media=cdrom,if=ide" ], 72 | [ "qemu-system-x86_64", 73 | "-nographic", 74 | "-nodefconfig", 75 | "-nodefaults", 76 | "-m", 77 | "128M", 78 | "-vga", 79 | "std", 80 | "-qmp", 81 | "tcp:127.0.0.1:24,server", 82 | "-k", 83 | "de", 84 | "-cpu", 85 | "host", 86 | "-smp", 87 | "cpus=8,cores=2,threads=4,sockets=1", 88 | "-drive", 89 | "file=disks/Platte.img.img,media=disk,if=ide", 90 | "-drive", 91 | "file=isos/none,media=cdrom,if=ide" ] 92 | ] -------------------------------------------------------------------------------- /test/confs.coffee: -------------------------------------------------------------------------------- 1 | conf = [] 2 | conf[0] = 3 | name: 'linux, default, with [boot,vnc,spice]=true +usb' 4 | hardware: 5 | ram: 128 6 | vgaCard: 'none' 7 | disk: 'Platte.img' 8 | iso: 'Platte.iso' 9 | cpu: 10 | model: 'host' 11 | cores:2 12 | threads:4 13 | sockets:1 14 | net: 15 | mac: 'ab:cd:ef' 16 | nic: 'virtio' 17 | usb: 18 | [ 19 | vendorId: 101 20 | productId: 911 21 | ] 22 | settings: 23 | vnc: 5900 24 | qmpPort: 24 25 | spice: 42 26 | keyboard: 'de' 27 | boot: true 28 | bootDevice: 'iso' 29 | numa: 30 | cpuNode: 1 31 | memNode: 1 32 | 33 | conf[1] = 34 | name: 'linux, default, with [boot,vnc,spice]=false -usb' 35 | hardware: 36 | ram: 128 37 | vgaCard: 'none' 38 | disk: 'Platte.img' 39 | iso: 'Platte.iso' 40 | cpu: 41 | model: 'host' 42 | cores:2 43 | threads:4 44 | sockets:1 45 | net: 46 | mac: 'ab:cd:ef' 47 | nic: 'virtio' 48 | settings: 49 | keyboard: 'de' 50 | qmpPort: 24 51 | vnc: false 52 | spice: false 53 | boot: false 54 | bootDevice: 'disk' 55 | numa: 56 | cpuNode: 1 57 | memNode: 1 58 | 59 | conf[2] = 60 | name: 'default, with [boot,vnc,spice]=false -[usb,net],vga:std' 61 | hardware: 62 | ram: 128 63 | vgaCard: 'std' 64 | disk: 'Platte.img' 65 | iso: 'none' 66 | cpu: 67 | model: 'host' 68 | cores:2 69 | threads:4 70 | sockets:1 71 | settings: 72 | keyboard: 'de' 73 | qmpPort: 24 74 | vnc: false 75 | spice: false 76 | boot: false 77 | bootDevice: 'disk' 78 | numa: 79 | cpuNode: 0 80 | memNode: 0 81 | 82 | module.exports = conf -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Parser = require('./lib/parser'); 4 | const assert = require('assert'); 5 | 6 | 7 | const startArgs = (new Parser().args); 8 | const sliceLength = startArgs.length; 9 | 10 | 11 | 12 | const tests = [ 13 | ['nodefconfig', [], ['-nodefconfig']] 14 | ,['nodefaults', [], ['-nodefaults']] 15 | ,['noShutdown', [], ['-no-shutdown']] 16 | ,['noStart', [], ['-S']] 17 | ,['name', ['myName'], ['-name', '"myName"']] 18 | ,['uuid', ['myUuid'], ['-uuid', 'myUuid']] 19 | ,['tablet', [], ['-usbdevice', 'tablet']] 20 | ,['mouse', [], ['-usbdevice', 'mouse']] 21 | 22 | 23 | // cpus ram gfx 24 | ,['smp', [2,3,4], ['-smp', 'cpus=24,sockets=2,cores=3,threads=4']] 25 | ,['smp', [1,4,1], ['-smp', 'cpus=4,sockets=1,cores=4,threads=1']] 26 | ,['smp', [1,4,2], ['-smp', 'cpus=8,sockets=1,cores=4,threads=2']] 27 | ,['smp', [2], ['-smp', 'cpus=2,sockets=2,cores=1,threads=1']] 28 | ,['smp', [2,3], ['-smp', 'cpus=6,sockets=2,cores=3,threads=1']] 29 | 30 | ,['cpuModel', [], ['-cpu', 'qemu64']] 31 | ,['cpuModel', ['myModel'], ['-cpu', 'myModel']] 32 | 33 | ,['ram', [1024], ['-m', '1024M']] 34 | ,['vga', ['qxl'], ['-vga', 'qxl']] 35 | ,['keyboard', ['de'], ['-k', 'de']] 36 | 37 | 38 | // machine and accelerator 39 | ,['machine', ['myMachine'], ['-machine', 'type=myMachine']] 40 | ,['accel', ['kvm'], ['-machine', 'accel=kvm']] 41 | ,['kvm', [], ['-enable-kvm']] 42 | 43 | 44 | // vnc, qmp, spice 45 | ,['vnc', [4], ['-vnc', ':4']] 46 | ,['qmp', [6], ['-qmp-pretty', 'tcp:127.0.0.1:6,server']] 47 | ,['spice', [8, '1.2.3.4'], ['-spice', 'port=8,addr=1.2.3.4,disable-ticketing']] 48 | ,['spice', [8, '1.2.3.4', 'myPassword'], ['-spice', `port=8,addr=1.2.3.4,password='myPassword'`]] 49 | 50 | // net 51 | ,['net', ['myMacAddr', 'myCard', 'user'], ['-net', 'nic,model=myCard,macaddr=myMacAddr', '-net', 'user']] 52 | ,['net', ['myMacAddr', 'myCard', 'user', {ip:'gIp',hostToVmPortFwd:[{hostIp:'hIp',hostPort:'hPort', vmPort:'gPort'}]}], ['-net', 'nic,model=myCard,macaddr=myMacAddr', '-net', 'user,dhcpstart=gIp,hostfwd=tcp:hIp:hPort-gIp:gPort']] 53 | ,['net', ['myMacAddr', 'myCard', 'user', {ip:'gIp',vmToHostPortFwd:[{hostIp:'hIp',hostPort:'hPort', vmPort:'gPort'}]}], ['-net', 'nic,model=myCard,macaddr=myMacAddr', '-net', 'user,dhcpstart=gIp,guestfwd=tcp:gIp:gPort-hIp:hPort']] 54 | 55 | ,['net', ['myMacAddr', 'myCard', 'user', {ip:'gIp',hostToVmPortFwd:[{hostIp:'hIp',hostPort:'hPort', vmPort:'gPort'}], 56 | vmToHostPortFwd:[{hostIp:'hIp',hostPort:'hPort', vmPort:'gPort'}]}], ['-net', 'nic,model=myCard,macaddr=myMacAddr', '-net', 'user,dhcpstart=gIp,hostfwd=tcp:hIp:hPort-gIp:gPort,guestfwd=tcp:gIp:gPort-hIp:hPort']] 57 | 58 | 59 | // drives 60 | ,['drive', [{type:'disk', path:'/myPath.img', interface:'virtio'}], ['-drive', 'snapshot=off,file=/myPath.img,media=disk,if=virtio']] 61 | ,['drive', [{type:'raw', path:'/dev/sdb', interface:'virtio'}], ['-drive', 'snapshot=off,file=/dev/sdb,media=disk,if=virtio,format=raw']] 62 | ,['drive', [{type:'partition', path:'/dev/sdb1', interface:'virtio'}], ['-drive', 'snapshot=off,file=/dev/sdb1,media=disk,if=virtio,cache=none']] 63 | ,['drive', [{type:'cdrom', path:'/myCd.iso', interface:'virtio'}], ['-drive', 'snapshot=off,file=/myCd.iso,media=cdrom,if=virtio']] 64 | ,['drive', [{type:'disk', path:'/myCd.iso', interface:'virtio', snapshot:'on'}], ['-drive', 'snapshot=on,file=/myCd.iso,media=disk,if=virtio']] 65 | ] 66 | 67 | 68 | for(const test of tests) { 69 | const parser = new Parser(); 70 | const cmd = test[0]; 71 | const args = test[1]; 72 | const cmp = test[2]; 73 | 74 | const ret = parser[cmd].apply(parser, args).args; 75 | 76 | try { 77 | assert.deepStrictEqual(ret.slice(sliceLength), cmp); 78 | console.log(`Passed ${cmd} with ${args.length} Args. ${JSON.stringify(args)}`); 79 | } catch(e) { 80 | console.log(e); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /toYaml.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | yaml = require 'js-yaml' 3 | 4 | files = [] 5 | 6 | for file in fs.readdirSync './vmConfigs' 7 | files.push file if 0 < file.search /\.json/ 8 | 9 | for file in files 10 | jsonObj = require "./vmConfigs/#{file}" 11 | ymlObj = yaml.safeDump jsonObj 12 | fileName = file.split '.' 13 | fileName.pop() 14 | fileName = fileName.join '.' 15 | fs.writeFileSync "./vmConfigs/#{fileName}.yml", ymlObj 16 | -------------------------------------------------------------------------------- /vmConfigs/.gitignore: -------------------------------------------------------------------------------- 1 | /*.json 2 | /*.yml -------------------------------------------------------------------------------- /webSrc/0.html: -------------------------------------------------------------------------------- 1 | from 0 -------------------------------------------------------------------------------- /webSrc/app.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import _angular from 'angular' 7 | import angularRoute from 'angular-route' 8 | import angularAnimate from 'angular-animate' 9 | import anguarSanitize from 'angular-sanitize' 10 | import jsoneditor from 'jsoneditor' 11 | import ngjsoneditor from 'ngjsoneditor' 12 | 13 | window.JSONEditor = jsoneditor; 14 | 15 | let app = angular.module('app', ['ngRoute', 'ngAnimate', 'ngSanitize', 'ng.jsoneditor']); 16 | 17 | app.config(['$routeProvider', '$locationProvider', '$sceDelegateProvider', (route, locationProvider, sceDelegateProvider) => { 18 | 19 | locationProvider.html5Mode(true) 20 | 21 | // route.when('/database/:currentDatabase/manage/collections', { 22 | // controller: 'collectionsController', 23 | // templateUrl: 'manage/collectionsView.html' 24 | // }); 25 | 26 | // route.when('/database/:currentDatabase/collection/:currentCollection/:from/:to', { 27 | // controller: 'collectionController', 28 | // templateUrl: 'collection/collectionView.html' 29 | // }); 30 | 31 | // route.when('/database/:currentDatabase/collection/:currentCollection/:from/:to/:index/document/:documentKey', { 32 | // controller: 'documentController', 33 | // templateUrl: 'document/documentView.html' 34 | // // resolve: {'formatService':'formatService'} 35 | // }); 36 | 37 | // // A Q L 38 | // route.when('/database/:currentDatabase/aql', { 39 | // controller: 'aqlController', 40 | // templateUrl: 'aql/aqlView.html' 41 | // }); 42 | 43 | // // A Q L 44 | // route.when('/database/:currentDatabase/graph', { 45 | // controller: 'graphController', 46 | // templateUrl: 'graph/graphView.html' 47 | // }); 48 | 49 | // route.when('/collection/:collectionName/:from/:to/:index', { 50 | // controller: 'documentRouteController', 51 | // template:'' 52 | // }); 53 | 54 | 55 | 56 | 57 | // V M S 58 | route.when('/vms', { 59 | controller: 'vmsController', 60 | templateUrl: 'vms/vmsView.html' 61 | }); 62 | 63 | // D R I V E S 64 | route.when('/drives', { 65 | controller: 'drivesController', 66 | templateUrl: 'drives/drivesView.html' 67 | }); 68 | 69 | route.otherwise({redirectTo: '/vms'}); 70 | }]); 71 | 72 | app.run(['$rootScope', '$location', 'messageBrokerService', '$routeParams', '$route', (rootScope, location, messageBroker, routeParams, route) => { 73 | 74 | rootScope.$on('$routeChangeError', (a, b, c, d) => { 75 | console.log('routeChangeError'); 76 | }); 77 | 78 | /* 79 | route change start 80 | location change success 81 | location change start 82 | route change success 83 | | route.current.params 84 | | | routeParams 85 | rcs 0 0 86 | lcs 0 0 87 | lcs 1 0 88 | rcs 1 1 89 | */ 90 | rootScope.$on('$routeChangeStart', () => { 91 | console.log('routeChangeStart'); 92 | // if(route.current) console.log(JSON.stringify(route.current.params, false, 2)); 93 | // console.log(JSON.stringify(routeParams,false, 2)); 94 | }); 95 | 96 | rootScope.$on('$locationChangeStart', (e, newUrl, oldUrl) => { 97 | console.log('locationChangeStart', oldUrl, newUrl); 98 | // if(route.current) console.log(JSON.stringify(route.current.params, false, 2)); 99 | // console.log(JSON.stringify(routeParams,false, 2)); 100 | }); 101 | 102 | rootScope.$on('$locationChangeSuccess', () => { 103 | console.log('locationChangeSuccess'); 104 | // if(route.current) console.log(JSON.stringify(route.current.params, false, 2)); 105 | // console.log(JSON.stringify(routeParams,false, 2)); 106 | 107 | }); 108 | 109 | rootScope.$on('$routeChangeSuccess', () => { 110 | console.log('routeChangeSuccess'); 111 | // if(route.current) console.log(JSON.stringify(route.current.params, false, 2)); 112 | // console.log(JSON.stringify(routeParams,false, 2)); 113 | }); 114 | }]); 115 | 116 | export default app; 117 | -------------------------------------------------------------------------------- /webSrc/aql/aqlView.html: -------------------------------------------------------------------------------- 1 | options: eval:(asap|blur|NUMms) max:(NUM|all) table:(true|false) 2 | 3 | 4 |
5 | {{lastError}} 6 |
7 |
8 | runtime: {{result.execTime}} ms, HTTP: {{result.httpTime}} ms 9 |
{{result.resultJson}}
10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /webSrc/collection/collectionView.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |  {{collectionInfo.id}}    {{format(collectionInfo.count, 2, 'math')}}    4 |
5 |
6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
content_key
{{doc}}
{{doc._key}}
49 | -------------------------------------------------------------------------------- /webSrc/collectionsBar/collectionsBarController.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | let angularModule = ['$scope', '$http', '$interval', 'messageBrokerService']; 9 | 10 | 11 | angularModule.push((scope, http, interval, messageBroker) => { 12 | console.log('define collectionsBarController'); 13 | 14 | messageBroker.sub('collectionsbar.status collections.reload current.collection current.database', scope); 15 | scope.cfg = {}; 16 | scope.status = 1; 17 | scope.collections = []; 18 | scope.currentCollection = ''; 19 | scope.currentDatabase = ''; 20 | 21 | scope.setCurrentCollection = () => { 22 | for(let col of scope.collections) { 23 | if(col.name == scope.currentCollection) { 24 | col.current = true; 25 | } 26 | else 27 | col.current = false; 28 | } 29 | } 30 | 31 | scope.reloadCollections = () => http.get(`/_db/${scope.currentDatabase}/_api/collection`).then(data => {scope.collections = data.data.collections; scope.setCurrentCollection();}); 32 | 33 | scope.$on('collectionsbar.status', (e,status) => scope.status = status); 34 | 35 | scope.$on('collections.reload', () => scope.reloadCollections()); 36 | 37 | scope.$on('current.collection', (e, currentCollection) => { scope.currentCollection = currentCollection; scope.setCurrentCollection();}); 38 | 39 | scope.$on('current.database', (e, database) => { 40 | if(database === scope.currentDatabase) return; 41 | scope.currentDatabase = database; 42 | scope.reloadCollections(); 43 | }); 44 | }); 45 | 46 | 47 | app.controller('collectionsBarController', angularModule); 48 | -------------------------------------------------------------------------------- /webSrc/collectionsBar/collectionsBarView.html: -------------------------------------------------------------------------------- 1 | 2 |    3 | 4 | 5 | 6 |  {{col.name}} 7 | 8 | 9 | 10 |  {{col.name}} 11 | 12 | -------------------------------------------------------------------------------- /webSrc/css/custom.css: -------------------------------------------------------------------------------- 1 | * { padding: 0; margin: 0; } 2 | 3 | html, body { 4 | height: 100vh; 5 | width: 100vw; 6 | 7 | display: flex; 8 | flex-direction: row; 9 | flex: 1; 10 | } 11 | 12 | body > * { 13 | flex-shrink: 0; 14 | } 15 | 16 | .div1 { 17 | flex-grow: 1; 18 | max-width: 15vw; 19 | overflow: scroll; 20 | /*min-width: 1vw;*/ 21 | /*display: none;*/ 22 | } 23 | 24 | .div2 { 25 | display: flex; 26 | flex-direction:column; 27 | flex-grow: 99999; 28 | width: 85vw; 29 | /*width:100%;*/ 30 | /*max-width: 100vw;*/ 31 | } 32 | -------------------------------------------------------------------------------- /webSrc/css/custom2.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | a { 4 | cursor: pointer; 5 | } 6 | 7 | * { 8 | cursor: default; 9 | } 10 | 11 | INPUT[type="text"], INPUT[type="number"] { 12 | cursor: text; 13 | } 14 | 15 | SELECT { 16 | cursor: pointer; 17 | } 18 | 19 | DIV.alert > DL.dl-horizontal { 20 | padding-left: 10px; 21 | } -------------------------------------------------------------------------------- /webSrc/css/jsoneditor-flex.css: -------------------------------------------------------------------------------- 1 | /*DIV.jsoneditor {*/ 2 | /*width: inherit; 3 | height: inherit;*/ 4 | /* display: flex; 5 | flex-direction: column; 6 | flex-grow: 1; 7 | }*/ 8 | 9 | /*DIV.jsoneditor-menu { 10 | width: inherit; 11 | flex-grow: 1; 12 | }*/ 13 | 14 | /*DIV.jsoneditor-outer { 15 | width: inherit; 16 | height: inherit; 17 | display: flex; 18 | flex-direction: column; 19 | flex-grow: 99; 20 | }*/ 21 | 22 | /*DIV.jsoneditor-tree {*/ 23 | /* width: inherit; 24 | height: inherit;*/ 25 | /*position: inherit;*/ 26 | /* display: flex; 27 | flex-direction: column; 28 | flex-grow:99; 29 | }*/ 30 | 31 | div.jsoneditor-contextmenu ul li button.jsoneditor-default { 32 | width: 90px; 33 | } -------------------------------------------------------------------------------- /webSrc/directives/aqlResultTable.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | 9 | let angularDirective = ['$interpolate', '$sce']; 10 | 11 | angularDirective.push((interpolate, sce) => { 12 | return { 13 | restrict: 'E', 14 | scope: { 15 | data: "=" 16 | }, 17 | link: (scope, element) => { 18 | 19 | window.requestAnimationFrame( () => { 20 | let table = document.createElement('table'); 21 | table.className = 'table table-sm'; 22 | 23 | let thead = document.createElement('thead'); 24 | let tr = document.createElement('tr'); 25 | for(let key of scope.data.keys) { 26 | let th = document.createElement('th'); 27 | th.textContent = key; 28 | tr.appendChild(th); 29 | } 30 | thead.appendChild(tr); 31 | table.appendChild(thead); 32 | 33 | let tbody = document.createElement('tbody'); 34 | 35 | for(let doc of scope.data.result) { 36 | let tr = document.createElement('tr'); 37 | for(let key of scope.data.keys) { 38 | let td = document.createElement('td'); 39 | td.textContent = interpolate('{{doc[key]}}')({doc:doc, key:key}); 40 | tr.appendChild(td); 41 | } 42 | tbody.appendChild(tr); 43 | } 44 | table.appendChild(tbody); 45 | element.get(0).appendChild(table); 46 | }); 47 | } 48 | } 49 | }); 50 | 51 | app.directive('aqlResultTable', angularDirective); 52 | -------------------------------------------------------------------------------- /webSrc/directives/feedbackDirective.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | 9 | let angularDirective = ['messageBrokerService']; 10 | 11 | angularDirective.push(() => { 12 | return { 13 | restrict: 'E', 14 | scope: { 15 | listenTo: '@test' 16 | }, 17 | link: () => console.log('directive link called'), 18 | template: ``, 19 | controller:['$scope', '$timeout', 'messageBrokerService', (scope, timeout, messageBroker) => { 20 | scope.msgs = []; 21 | 22 | messageBroker.sub(scope.listenTo, scope); 23 | scope.$on(scope.listenTo, (ev, msg) => { 24 | msg = Object.assign({id:Date.now()+''+Math.random(), type:'info'}, msg); 25 | msg.timeout = timeout(() => { 26 | for(let idx in scope.msgs) { 27 | if(scope.msgs[idx].id == msg.id) { 28 | scope.msgs.splice(idx, 1); 29 | break; 30 | } // if 31 | } // for 32 | }, 10000); 33 | scope.msgs.push(msg); 34 | }); 35 | scope.$on('$destroy', () => { 36 | for(let msg of scope.msgs) { 37 | timeout.cancel(msg.timeout); 38 | } // for 39 | }); 40 | }] 41 | } 42 | }); 43 | 44 | app.directive('feedbackMsgs', angularDirective); 45 | 46 | // feedbafeedbackDirective 47 | -------------------------------------------------------------------------------- /webSrc/directives/journalSizeDirective.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | 9 | let angularDirective = []; 10 | 11 | angularDirective.push(() => { 12 | return { 13 | restrict: 'A', 14 | require: '?ngModel', 15 | scope: { 16 | base: '@' 17 | }, 18 | link: (scope, element, attrs, ngModel) => { 19 | if (!ngModel) return; 20 | 21 | ngModel.$formatters.push((value) => { 22 | if( !isNaN(value)) { 23 | if(scope.base == '10') 24 | return value/1000/1000; 25 | else 26 | return value/1024/1024; 27 | } 28 | }); 29 | 30 | ngModel.$parsers.push((value) => { 31 | if (isNaN(value)) return; 32 | if(scope.base == '10') 33 | return value*1000*1000; 34 | else 35 | return value*1024*1024; 36 | }); 37 | } 38 | } 39 | }); 40 | 41 | app.directive('journalSize', angularDirective); 42 | 43 | // feedbafeedbackDirective 44 | 45 | 46 | // angular.module('customControl', ['ngSanitize']). 47 | // directive('contenteditable', ['$sce', function($sce) { 48 | // return { 49 | // restrict: 'A', // only activate on element attribute 50 | // require: '?ngModel', // get a hold of NgModelController 51 | // link: function(scope, element, attrs, ngModel) { 52 | // if (!ngModel) return; // do nothing if no ng-model 53 | 54 | // // Specify how UI should be updated 55 | // ngModel.$render = function() { 56 | // element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 57 | // }; 58 | 59 | // // Listen for change events to enable binding 60 | // element.on('blur keyup change', function() { 61 | // scope.$evalAsync(read); 62 | // }); 63 | // read(); // initialize 64 | 65 | // // Write data to the model 66 | // function read() { 67 | // var html = element.html(); 68 | // // When we clear the content editable the browser leaves a
behind 69 | // // If strip-br attribute is provided then we strip this out 70 | // if ( attrs.stripBr && html == '
' ) { 71 | // html = ''; 72 | // } 73 | // ngModel.$setViewValue(html); 74 | // } 75 | // } 76 | // }; 77 | // }]); -------------------------------------------------------------------------------- /webSrc/document/documentController.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | import jq from 'jquery' 9 | 10 | 11 | let angularModule = ['$scope', '$routeParams', '$http', 'testService'] 12 | 13 | angularModule.push((scope, params, http, testService) => { 14 | console.log('init documentController'); 15 | scope.params = params; 16 | 17 | let from = scope.from = Number(params.from); 18 | let to = Number(params.to); 19 | let index = scope.index = Number(params.index); 20 | scope._doc = {}; 21 | 22 | testService.test().then(results => ({docsLink: scope.docsLink, prevDocLink: scope.prevDocLink, nextDocLink: scope.nextDocLink, _keys: scope._keys} = results)); 23 | 24 | scope.obj = {data: {}, preferText:true, options: { mode: 'tree', 25 | change:(err) => { 26 | if(err) { 27 | scope.obj.canSave = false; 28 | } else { // if 29 | scope.obj.canSave = true; 30 | } // else 31 | }, 32 | onEditable:(node) => { 33 | if(-1 < ['_key', '_id', '_rev'].indexOf(node.field) && node.path.length == 1) 34 | return false; 35 | return true; 36 | }}, 37 | canSave:true 38 | }; 39 | 40 | http.get(`/_db/${params.currentDatabase}/_api/document/${params.currentCollection}/${params.documentKey}`).then(data => { 41 | scope.obj.data = data.data; 42 | for(let type of ['_id', '_rev', '_key', '_from', '_to']) { 43 | if(data.data[type] === undefined) continue; 44 | scope._doc[type] = data.data[type]; 45 | delete data.data[type]; 46 | } // for 47 | resize(); 48 | }); 49 | 50 | scope.saveDoc = () => { 51 | console.log(scope.obj.data); 52 | console.log(Object.assign({}, scope.obj.data, scope._doc)); 53 | http.put(`/_db/${params.currentDatabase}/_api/document/${params.currentCollection}/${params.documentKey}`, scope.obj.data).then( data => { 54 | console.log(data); 55 | }); 56 | 57 | }; 58 | 59 | // todo: fix uglyness maybe in css? 60 | let resize = () => $('DIV#ngeditor').css('height', `${window.document.documentElement.clientHeight - $('div#ngeditor').position().top}px`); 61 | $(window).on('resize', resize); 62 | scope.$on('$destroy', () => { 63 | console.log('destroy'); 64 | $(window).off('resize', resize); 65 | }); 66 | }); 67 | 68 | app.controller('documentController', angularModule); 69 | 70 | 71 | 72 | // FOR x IN @@collection LET att = SLICE(ATTRIBUTES(x), 0, 25) SORT TO_NUMBER(x._key) == 0 ? x._key : TO_NUMBER(x._key) LIMIT @offset, @count RETURN KEEP(x, att)" 73 | -------------------------------------------------------------------------------- /webSrc/document/documentRouteController.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | let angularModule = ['$scope', '$routeParams', '$http', '$location', 'queryService', '$route']; 9 | 10 | angularModule.push((scope, params, http, location, query, route) => { 11 | 12 | console.log('init documentRouteController'); 13 | let from = Number(params.from); 14 | let index = Number(params.index); 15 | 16 | query.query(`for doc in ${params.collectionName} limit ${from + index},1 return doc._key`).then(result => { 17 | console.log( location.path() ); 18 | location.path(`${location.path()}/document/${result[0]}`); 19 | }); 20 | 21 | 22 | 23 | // location.path('/'); 24 | 25 | }); 26 | 27 | app.controller('documentRouteController', angularModule); 28 | 29 | 30 | 31 | // FOR x IN @@collection LET att = SLICE(ATTRIBUTES(x), 0, 25) SORT TO_NUMBER(x._key) == 0 ? x._key : TO_NUMBER(x._key) LIMIT @offset, @count RETURN KEEP(x, att)" 32 | -------------------------------------------------------------------------------- /webSrc/document/documentView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |  {{_doc._id}}    {{_doc._rev}}    {{_doc._key}} 4 | 5 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
32 | -------------------------------------------------------------------------------- /webSrc/drives/drivesController.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2017 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app'; 7 | 8 | 9 | const angularModule = ['$scope', '$http']; 10 | 11 | angularModule.push((scope, http) => { 12 | console.log('init drivesController'); 13 | 14 | scope.editDrive = {}; 15 | scope.selections = { 16 | formats:['raw', 'qcow2'], 17 | medias:['disk', 'cdrom'], 18 | }; 19 | 20 | scope.createDrive = () => { 21 | http.post('/api/drives', scope.editDrive).then( (data) => { 22 | scope.reloadDrives(); 23 | scope.editDrive = {}; 24 | }); 25 | } 26 | 27 | scope.reloadDrives = () => http.get('api/drives').then(data => scope.drives = data.data); 28 | scope.reloadDrives(); 29 | }); 30 | 31 | app.controller('drivesController', angularModule); 32 | -------------------------------------------------------------------------------- /webSrc/graph/graphView.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webSrc/home/homeController.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | 9 | let angularModule = ['$scope', '$http']; 10 | 11 | angularModule.push((scope, http) => { 12 | 13 | console.log('init homeController'); 14 | scope.test = {}; 15 | 16 | scope.changelog = ''; 17 | 18 | http.get('/_db/_system/_api/version').then(data => { 19 | let version = data.data.version; 20 | if(0 == version.search(/^\d\.\d\.\d$/)) { 21 | version = version.split('.').slice(0,2).join('.'); 22 | } 23 | http.get(`https://raw.githubusercontent.com/arangodb/arangodb/${version}/CHANGELOG`).then(data => scope.changelog = data.data); 24 | }); 25 | http.get(`https://raw.githubusercontent.com/arangodb/arangodb/devel/CHANGELOG`).then(data => scope.changelog = data.data); 26 | 27 | 28 | // scope.test.selected = 'a'; 29 | 30 | 31 | // scope.test.options = { 32 | // kc: {name:'name c', rule:'rule c'}, 33 | // ka: {name:'name a', rule:'rule a'}, 34 | // kb: {name:'name b', rule:'rule b'}, 35 | // kd: {name:'name d', rule:'rule d'} 36 | // } 37 | 38 | }); 39 | 40 | app.controller('homeController', angularModule); 41 | -------------------------------------------------------------------------------- /webSrc/home/homeView.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baslr/node-qemu-server/154d9c1a3b0eca0ddcd59e93fead5c17d29d13ed/webSrc/home/homeView.html -------------------------------------------------------------------------------- /webSrc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QEMU-VM-MGMNT 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /webSrc/main.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | let requireJsConfig = { 7 | waitSeconds: 0, 8 | shim: { 9 | 'angular': { 10 | deps: ['jquery'] // ! load angular bevor jquery to get not the jquery.event in the ng-mouse events 11 | }, 12 | 'angular-route': { 13 | deps: ['angular'] 14 | }, 15 | 'angular-animate': { 16 | deps: ['angular'] 17 | }, 18 | 'angular-sanitize': { 19 | deps: ['angular'] 20 | }, 21 | 'bootstrap': { 22 | deps: ['jquery'] 23 | }, 24 | 'ngjsoneditor': { 25 | deps: ['jsoneditor', 'angular'] 26 | } 27 | }, 28 | 29 | paths: {} 30 | } 31 | 32 | let angularVersion = '1-5-5'; 33 | let requireJsPaths = { 34 | 'bootstrap' : 'lib/bootstrap-4-0-0', 35 | 'jquery' : 'lib/jquery-2-2-3', 36 | 'angular' : `lib/angular-${angularVersion}`, 37 | 'angular-route' : `lib/angular-route-${angularVersion}`, 38 | 'angular-animate' : `lib/angular-animate-${angularVersion}`, 39 | 'angular-sanitize': `lib/angular-sanitize-${angularVersion}`, 40 | 'jsoneditor' : 'lib/jsoneditor-5-1-3', 41 | 'ngjsoneditor' : 'lib/ngjsoneditor-1-0-0' 42 | } 43 | 44 | for(var path in requireJsPaths) { 45 | requireJsConfig.paths[path] = requireJsPaths[path]; 46 | } 47 | 48 | requirejs.config(requireJsConfig); 49 | 50 | requirejs(['jquery', 'angular', 'app', 'ngjsoneditor', 51 | 'vms/vmsController', 52 | 'drives/drivesController', 53 | 54 | 'navBar/navBarController', 55 | 'collectionsBar/collectionsBarController', 56 | 'collection/collectionController', 57 | 58 | 'document/documentController', 59 | 'document/documentRouteController', 60 | 61 | 'manage/collectionsController', 62 | 63 | 'aql/aqlController', 64 | 65 | 'graph/graphController', 66 | 67 | 'services/messageBrokerService', 68 | 'services/queryService', 69 | 'services/formatService', 70 | 'services/fastFilterService', 71 | 'services/queriesService', 72 | 'services/testService', 73 | 74 | 'directives/feedbackDirective', 75 | 'directives/journalSizeDirective', 76 | 'directives/aqlResultTable' ], ($) => { 77 | $.ajaxSetup({cache:false}); 78 | 79 | angular.bootstrap(document, ['app']); 80 | }); 81 | -------------------------------------------------------------------------------- /webSrc/navBar/navBarController.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | let stat = `LET r = (FOR d IN _statistics sort d.time desc limit @time RETURN d) 7 | let x = MERGE(FOR t IN ['http', 'client', 'system'] 8 | let z = MERGE(FOR a IN ATTRIBUTES(r[0][t]) 9 | filter !CONTAINS(a, 'Percent') 10 | RETURN {[a]: sum(r[*][t][a]) / @time}) 11 | RETURN {[t]:z}) RETURN x` 12 | 13 | import app from 'app' 14 | 15 | let angularModule = ['$scope', '$http', '$interval', 'formatService', 'messageBrokerService', '$location']; 16 | 17 | 18 | angularModule.push((scope, http, interval, format, messageBroker, location) => { 19 | console.log('init navBarController'); 20 | scope.format = format; 21 | 22 | scope.collectionsBarStatus = 1; 23 | scope.changeCollectionsBarStatus = () => { 24 | scope.collectionsBarStatus++; if(scope.collectionsBarStatus>1)scope.collectionsBarStatus=0; messageBroker.pub('collectionsbar.status', scope.collectionsBarStatus);} 25 | 26 | http.get('/_db/_system/_api/version').then(data => { 27 | scope.cfg.arango = data.data; 28 | http.jsonp(`https://www.arangodb.com/repositories/versions.php?jsonp=JSON_CALLBACK&version=${scope.cfg.arango.version}&callback=JSON_CALLBACK`).then(data => { 29 | scope.cfg.availableVersions = Object.keys(data.data).sort().map( (key) => `${data.data[key].version} ${key}`).join(', '); 30 | if(scope.cfg.availableVersions) { 31 | scope.cfg.availableVersions = `(${scope.cfg.availableVersions} available)`; 32 | } // if 33 | }); 34 | }); 35 | http.get('/_api/database').then(data => scope.cfg.dbs = data.data.result); 36 | 37 | scope.cfg = {}; 38 | scope.cfg.arango = 'n/a'; 39 | scope.cfg.dbs = []; 40 | scope.cfg.selectedDb = ''; 41 | 42 | scope.databaseChanged = () => location.url(`/database/${scope.cfg.selectedDb}`); 43 | 44 | scope.$on('current.database', (e, database) => scope.cfg.selectedDb = database); 45 | messageBroker.sub('current.database', scope); 46 | 47 | scope.refreshStats = () => { 48 | http.post(`/_db/_system/_api/cursor`, {cache:false,query:stat,bindVars:{time:6}}).then(data => scope.cfg.stats = data.data.result[0]); 49 | } 50 | scope.refreshStats(); 51 | interval(scope.refreshStats, 10 * 1000); 52 | }); 53 | 54 | app.controller('navBarController', angularModule); 55 | -------------------------------------------------------------------------------- /webSrc/navBar/navBarView.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{cfg.arango.version}} {{cfg.arango.server}} {{cfg.availableVersions}} AQL 4 |  {{format(cfg.stats.client.bytesReceivedPerSecond, 2, 'IT')}}  {{format(cfg.stats.client.bytesSentPerSecond, 2, 'IT')}} | {{format(cfg.stats.client.httpConnections, 0)}} | {{format(cfg.stats.http.requestsTotalPerSecond, 2, 'math')}} | {{format(cfg.stats.system.residentSize,2,'IT')}}/{{format(cfg.stats.system.virtualSize,2,'IT')}} | {{format(cfg.stats.system.majorPageFaultsPerSecond, 1)}}/{{format(cfg.stats.system.minorPageFaultsPerSecond, 1)}} 5 |
6 | -------------------------------------------------------------------------------- /webSrc/services/fastFilterService.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | let angularModule = ['$http', '$q', 'messageBrokerService']; 9 | 10 | 11 | angularModule.push((http, q, messageBroker) => { 12 | let collectionName = ''; 13 | 14 | 15 | http.get('collections').then(result => { 16 | collectionName = result.data.settings; 17 | reloadFilters(); 18 | }); 19 | 20 | let reloadFilters = () => { 21 | http.get(`/_db/_system/_api/document/${collectionName}/fastFilter`).then(data => { 22 | 23 | for(let key in data.data.rules) { 24 | fastFilter.rules[key] = data.data.rules[key]; 25 | } // for 26 | }); 27 | } 28 | 29 | let fastFilter = { 30 | rules: { 'none': {name:'none', rule:`// none`}}, 31 | currentRule: () => fastFilter.rules[messageBroker.last('current.fastFilter')].rule, 32 | save: () => { 33 | http.patch(`/_db/_system/_api/document/${collectionName}/fastFilter`, {rules:fastFilter.rules}); 34 | } 35 | } 36 | 37 | return fastFilter; 38 | }); 39 | 40 | app.service('fastFilterService', angularModule); 41 | -------------------------------------------------------------------------------- /webSrc/services/formatService.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | let angularModule = []; 9 | 10 | 11 | angularModule.push(() => { 12 | return (str, fix=0, ext=undefined) => { 13 | str = Number(str); 14 | if(ext) ext = ext.toLowerCase(); 15 | 16 | if(str > 1000*1000*1000) { 17 | str = (str / 1000 / 1000 / 1000).toFixed(fix); 18 | switch(ext) { 19 | case 'it': 20 | ext = ' GB'; 21 | break; 22 | case 'math': 23 | ext = ' G'; 24 | break; 25 | } // switch 26 | } else if(str > 1000*1000) { 27 | str = (str / 1000 / 1000).toFixed(fix); 28 | switch(ext) { 29 | case 'it': 30 | ext = ' MB'; 31 | break; 32 | case 'math': 33 | ext = ' M'; 34 | break; 35 | } // switch 36 | } else if(str > 1000) { 37 | str = (str / 1000).toFixed(fix); 38 | switch(ext) { 39 | case 'it': 40 | ext = ' KB'; 41 | break; 42 | case 'math': 43 | ext = ' K'; 44 | break; 45 | } // switch 46 | } else { 47 | str = str.toFixed(fix); 48 | switch(ext) { 49 | case 'it': 50 | ext = ' B'; 51 | break; 52 | case 'math': 53 | ext = ''; 54 | break; 55 | } // switch 56 | } 57 | if(ext) str=`${str}${ext}`; 58 | return str; 59 | } 60 | }); 61 | 62 | app.service('formatService', angularModule); 63 | -------------------------------------------------------------------------------- /webSrc/services/messageBrokerService.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | 9 | let transactions = {} 10 | let lastData = {} 11 | 12 | 13 | let MessageBrokerService = { 14 | sub: (msgs, scope) => { 15 | console.log('SUB', msgs); 16 | for(let msg of msgs.replace(/\+ /g, ' ').trim().split(' ')) { 17 | (transactions[msg] || (transactions[msg] = [])).push(scope); 18 | scope.$on('$destroy', () => MessageBrokerService.usub(msg, scope)); 19 | } 20 | }, 21 | 22 | pub: (msg, data) => { 23 | console.log('PUB', msg, data); 24 | lastData[msg] = data; 25 | if(!transactions[msg]) return; 26 | for(let scope of transactions[msg]) { 27 | scope.$emit(msg, data); 28 | 29 | } 30 | }, 31 | 32 | last: (msg) => { return lastData[msg]; 33 | }, 34 | 35 | usub: (msgs, scope) => { 36 | for(let msg in msgs.replace(/\ +/g, ' ').trim().split(' ')) { 37 | if(!transactions[msg]) continue; 38 | for(let idx in transactions[msg]) { 39 | let s = transactions[msg][idx]; 40 | if(s.$id === scope.$id) { 41 | transactions[msg].splice(idx, 1); 42 | break; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | 50 | 51 | let mbcall = () => MessageBrokerService; 52 | 53 | app.service('messageBrokerService', [mbcall]); 54 | -------------------------------------------------------------------------------- /webSrc/services/queriesService.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | let angularModule = ['$http', '$q', 'messageBrokerService']; 9 | 10 | 11 | angularModule.push((http, q, messageBroker) => { 12 | let collectionName = ''; 13 | 14 | 15 | http.get('collections').then(result => { 16 | collectionName = result.data.settings; 17 | reloadQueries(); 18 | }); 19 | 20 | let reloadQueries = () => { 21 | http.get(`/_db/_system/_api/document/${collectionName}/savedQueries`).then(data => { 22 | 23 | for(let key in data.data.queries) { 24 | queries.queries[key] = data.data.queries[key]; 25 | } // for 26 | }); 27 | } 28 | 29 | let queries = { 30 | queries: {unsaved: {name:'unsaved', options:{eval:'blur', max:1000}, query:'// eval:asap max:all table:true name:unsaved\n// query\n'}}, 31 | currentName: () => queries.queries[messageBroker.last('current.query')].name, 32 | save: () => { 33 | http.patch(`/_db/_system/_api/document/${collectionName}/savedQueries`, {queries:queries.queries}); 34 | } 35 | } 36 | 37 | return queries; 38 | }); 39 | 40 | app.service('queriesService', angularModule); 41 | -------------------------------------------------------------------------------- /webSrc/services/queryService.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | import app from 'app' 7 | 8 | let angularModule = ['$http', '$q', 'messageBrokerService']; 9 | 10 | 11 | angularModule.push((http, q, messageBroker) => { 12 | 13 | return { 14 | query:(query) => { 15 | let d = q.defer(); 16 | console.log('AQL-QUERY', query); 17 | http.post(`/_db/${messageBroker.last('current.database')}/_api/cursor`, {cache:false,query:query,options:{fullCount:true}}).then(data => { 18 | d.resolve(data.data); 19 | }); 20 | return d.promise; 21 | } 22 | } 23 | }); 24 | 25 | app.service('queryService', angularModule); 26 | -------------------------------------------------------------------------------- /webSrc/services/testService.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * (c) 2016 by duo.uno 3 | * 4 | ***/ 5 | 6 | // @todo add fastFilter statement maybe into query.query 7 | import app from 'app' 8 | 9 | let angularModule = ['$route', '$routeParams', 'queryService', 'fastFilterService', '$q']; 10 | 11 | 12 | angularModule.push((route, params, query, fastFilter, q) => { 13 | 14 | return { 15 | test:() => { 16 | let d = q.defer(); 17 | 18 | let {from, to, index} = params; 19 | from = Number(from); 20 | to = Number(to); 21 | index = Number(index); 22 | let offset = from + index - 1; 23 | let batchSize = to - from + 1; 24 | let prevDocLink = '', nextDocLink = '', docsLink = ''; 25 | 26 | let limit = 3; 27 | if(offset < 0) { 28 | offset = 0; 29 | limit = 2; 30 | } // if 31 | query.query(`for doc in ${params.currentCollection} ${fastFilter.currentRule()}\n limit ${offset},${limit} return doc._key`).then(result => { 32 | result = result.result; 33 | let newIndex = index + 1; 34 | if(newIndex > batchSize-1) { 35 | nextDocLink = `database/${params.currentDatabase}/collection/${params.currentCollection}/${from + batchSize}/${to + batchSize}/0/document/${result.slice(-1)[0]}`; 36 | } else { 37 | nextDocLink = `database/${params.currentDatabase}/collection/${params.currentCollection}/${from}/${to}/${index+1}/document/${result.slice(-1)[0]}`; 38 | } 39 | newIndex = index-1; 40 | if(newIndex < 0) { 41 | prevDocLink = `database/${params.currentDatabase}/collection/${params.currentCollection}/${from - batchSize}/${to - batchSize}/${batchSize-1}/document/${result.slice(0,1)[0]}`; 42 | } else { 43 | prevDocLink = `database/${params.currentDatabase}/collection/${params.currentCollection}/${from}/${to}/${index-1}/document/${result.slice(0,1)[0]}`; 44 | } 45 | docsLink = `database/${params.currentDatabase}/collection/${params.currentCollection}/${from}/${to}`; 46 | d.resolve({docsLink, prevDocLink, nextDocLink, _keys:result}); 47 | }); 48 | return d.promise; 49 | } 50 | } 51 | }); 52 | 53 | app.service('testService', angularModule); 54 | -------------------------------------------------------------------------------- /webSrc/vms/0.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | 7 |

For > 1 VM names will add -0 -1 ... also autogenerate MAC addresses.

8 |
9 |
10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /webSrc/vms/1.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | 7 |

VM name

8 |
9 |
10 |
11 | 12 |
13 | 14 |

Select your keyboard layout

15 |
16 |
17 |
18 | 19 |
20 | 21 |

Optional provide a UUID for your vm

22 |
23 |
24 |
25 |
Select your keyboard layout that you have. If you select the wrong format, you maybe can not type adequate.
26 | -------------------------------------------------------------------------------- /webSrc/vms/2.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 | 7 |

Select your machine

8 |
9 |
10 |
11 | 12 |
13 | 14 |

vga cards are none, standard (std) and paravirtual qxl or virtio

15 |
16 |
17 |
18 | 19 |
20 | 21 |

maximal size of RAM in MB

22 |
23 |
24 | 25 |
26 |
27 | 28 |
29 | 30 |
31 | 32 |

Select your CPU model

33 |
34 |
35 |
36 | 37 |
38 | 39 |

Number of sockets

40 |
41 |
42 |
43 | 44 |
45 | 46 |

Number of cores per socket

47 |
48 |
49 |
50 | 51 |
52 | 53 |

Number of threads per core

54 |
55 |
56 |
57 |
If you want to use openGL, select 'qxl' or 'virtio'. For standard usage use 'std'. If you want no graphic output, then use 'none'.
58 |
Number of all CPUs ares sockets * cores * threads.
59 | -------------------------------------------------------------------------------- /webSrc/vms/3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
6 | 7 |
8 | 9 |

Select a drive.

10 |
11 |
12 | 13 | 17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 |

Select the interface.

25 |
26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 | 37 |
38 |
39 |
40 | 41 | 42 |
43 | 44 |
45 |
46 | 49 |
50 |
51 |
52 | 53 | 54 |
55 | 56 |
57 |
58 | 61 |
62 |
63 |
64 | 65 | 66 |
67 | 68 |
69 |
70 | 73 |
74 |
75 |
76 | 77 | 78 |
79 | 80 |
81 | 82 |

Select the cache.

83 |
84 |
85 | 86 | 87 |
88 | 89 |
90 | 91 |

Select the aio (async io).

92 |
93 |
94 | 95 | 96 |
97 | 98 |
99 | 100 |

Select discard operation.

101 |
102 |
103 | 104 |
105 | 106 |
107 | 108 |
109 |
110 |
111 | -------------------------------------------------------------------------------- /webSrc/vms/5.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | 10 |
11 |

Connect to the VM via VNC

12 |
13 |
14 |
15 | 16 |
17 |
18 | 21 |
22 |

Connect to the VM via SPICE

23 |
24 |
25 |
26 |
Use VNC or SPICE to connect to your vms virtual desktop environment or console screen.
27 | -------------------------------------------------------------------------------- /webSrc/vms/6.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | 10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 |

Type in your Host-NUMA node config for the CPU. E.g. 0,1 or 1-3

18 |
19 |
20 |
21 | 22 |
23 | 24 |

type in your Host-NUMA node config for the RAM. E.g. 0,1 or 1-3

25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 | 33 |
34 |
35 | 38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 |

Type in your VM-NUMA node config for the CPU. E.g. 0,1 or 1-3

46 |
47 |
48 |
49 | 50 |
51 | 52 |

type in your VM-NUMA node config for the RAM. E.g. 0,1 or 1-3

53 |
54 |
55 |
56 |
The ram of an host system with more than one cpu socket are divided in local and foreign regions. Each cpu socked addresses a piece of ram. This is reffered as Local addressed ram. This porting is directly attached to the cpu socket and can accessed directly. Foreign ram has to be accessed via QPI (Intel QuickPath Interconnect) or HyperTransport (AMD). The cpu socket connects to the forreign memory controller in an other cpu socket and asks for the ram the cpu wants to accesss. This is much slower than local accessed ram. As a consequence Guests memory should be placed in local ram for better performance. For more informatinos about NUMA please reffere to the great RedHat documentation.
57 | -------------------------------------------------------------------------------- /webSrc/vms/7.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 | 10 |
11 |

keep vm process running while the vm os is shutdown / not running

12 |
13 |
14 |
15 | 16 |
17 |
18 | 21 |
22 |

when vm process is started, vm is paused

23 |
24 |
25 |
26 | 27 |
28 |
29 | 32 |
33 |

keep vm process running

34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /webSrc/vms/8.html: -------------------------------------------------------------------------------- 1 | form 8 USB -------------------------------------------------------------------------------- /webSrc/vms/vmsView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 53 | 54 | 55 |
NameCPUsRAMVGAVNC-Port
16 |
{{stat(vm.uuid).procStatus}}
17 |
19 |
{{stat(vm.uuid).status}}   20 |  
21 | 22 |
{{vm.name}}{{vm.hardware.cpu.sockets * vm.hardware.cpu.cores * vm.hardware.cpu.threads}}{{vm.hardware.ram}} MB{{vm.hardware.vga}}{{vm.settings.vnc.port+5900}}
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | UUID: {{vm.uuid}} 39 | 40 |
41 |
42 |
43 |
Sockets
44 |
{{vm.hardware.cpu.sockets}}
45 |
Cores per Socket
46 |
{{vm.hardware.cpu.cores}}
47 |
Threads per Core
48 |
{{vm.hardware.cpu.threads}}
49 |
50 |
51 |
52 |
56 | 57 |
58 | 59 |
60 |
61 |

Settings

62 | 63 |
64 | {{s}} 65 |
66 |
67 |
68 |
69 |
70 |

{{settings[curSetting.idx]}}

71 |
72 |
73 |
74 |
75 |
76 | 77 |
{{editVm | json}}
78 | 79 |
{{editDrive | json}}
80 | 81 |
{{curSetting | json}}
82 | --------------------------------------------------------------------------------