initial commit
55
logging.htm
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/default-fonts.css">
|
||||
<style>
|
||||
.logLine{font-family:bitstream;font-size:10px;color:#505050;margin:0 8px;padding:0;}
|
||||
.logLine.spacing{margin:10px;}
|
||||
button[type=button],a.button{font-family:arimo;font-size:11px;position:relative;display:inline-block;padding:5px 10px;border:1px solid #E8E8E8;border-radius:5px;margin:7px 14px 0 0;text-decoration:none;white-space:nowrap;cursor:pointer;outline:none;color:#303030;background:-webkit-radial-gradient(#F0F0F0,#C8C8C8);background:linear-gradient(#F0F0F0,#C8C8C8);}
|
||||
button:hover[type=button],a.button:hover{border-color:#6AB034;text-shadow:-1px -1px 0 rgba(0,0,0,0.3);color:#FFFFFF;background:-webkit-radial-gradient(#5E9E2E,#8FD956);background:linear-gradient(#5E9E2E,#8FD956);}
|
||||
button:active[type=button]{border-color:#6AB034;box-shadow:inset 0 0 8px 4px #548C29,0 1px 0 0 #E0E0E0;}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
var progressframe = parent.document.getElementById('progressFrame');
|
||||
if (progressframe) progressframe.style.zIndex = 10;
|
||||
var lastLine = 0;
|
||||
var cursor;
|
||||
function addLog(logLine) {
|
||||
var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode).scrollTop;
|
||||
var clientHeight = (document.documentElement || document.body.parentNode).clientHeight;
|
||||
var scrollHeight = (document.documentElement || document.body.parentNode).scrollHeight;
|
||||
var isScrolledToBottom = scrollHeight - clientHeight <= scrollTop + 1;
|
||||
if (lastLine == 0) {
|
||||
lastLine = document.body.innerHTML.length;
|
||||
cursor = lastLine;
|
||||
}
|
||||
if (logLine.slice(-1) == "\n") {
|
||||
document.body.innerHTML = document.body.innerHTML.slice(0,cursor) + logLine.slice(0,-1) + "<br>";
|
||||
lastLine = document.body.innerHTML.length;
|
||||
cursor = lastLine;
|
||||
}
|
||||
else if (logLine.slice(-1) == "\r") {
|
||||
document.body.innerHTML = document.body.innerHTML.slice(0,cursor) + logLine.slice(0,-1);
|
||||
cursor = lastLine;
|
||||
}
|
||||
else if (logLine.slice(-1) == "\b") {
|
||||
if (logLine.length > 1)
|
||||
document.body.innerHTML = document.body.innerHTML.slice(0,cursor) + logLine.slice(0,-1);
|
||||
cursor += logLine.length-2;
|
||||
}
|
||||
else {
|
||||
document.body.innerHTML += logLine;
|
||||
cursor += logLine.length;
|
||||
}
|
||||
if (isScrolledToBottom) {
|
||||
window.scrollTo(0,document.body.scrollHeight);
|
||||
}
|
||||
}
|
||||
function addCloseButton() {
|
||||
addLog("<br><center><button class='logLine' type='button' onclick='" + (top.Shadowbox ? "top.Shadowbox" : "window") + ".close()'>Done</button></center>");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="logLine spacing" onload="addCloseButton()"></body>
|
||||
</html>
|
||||
11
plugins/dynamix.apcupsd/LICENSE
Normal file
@@ -0,0 +1,11 @@
|
||||
Copyright 2015, by Dan Landon
|
||||
|
||||
This plugin provides APCUPSD support for unRAID V6. The plugin was modified from the original
|
||||
work done by seeDrs.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version 2,
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
36
plugins/dynamix.apcupsd/UPSdetails.page
Normal file
@@ -0,0 +1,36 @@
|
||||
Menu="UPSsettings"
|
||||
Title="UPS Details"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Dan Landon.
|
||||
* Copyright 2015, Bergware International.
|
||||
* Copyright 2015, Lime Technology
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<script>
|
||||
function getUPSstatus() {
|
||||
$.get('/plugins/dynamix.apcupsd/include/UPSstatus.php',{all:'true'},function(data) {
|
||||
if (data) {
|
||||
data = data.split(/\n/);
|
||||
$('#ups_summary').html(data[0]);
|
||||
$('#ups_status').html(data[1]);
|
||||
}
|
||||
<?if (($display['refresh']>0 || ($display['refresh']<0 && $var['mdResync']==0))):?>
|
||||
setTimeout(getUPSstatus,<?=max(abs($display['refresh']),15000)?>);
|
||||
<?endif;?>
|
||||
});
|
||||
}
|
||||
$(getUPSstatus);
|
||||
</script>
|
||||
|
||||
<table class="tablesorter shift">
|
||||
<thead><tr><th style="width:15%">Key</th><th style="width:35%">Value</th><th style="width:15%">Key</th><th style="width:35%">Value</th></tr></thead>
|
||||
<tbody id="ups_status"><tr><td colspan="4"><center><i class="fa fa-spinner fa-spin icon"></i><em>Please wait, retrieving UPS information...</em></center></td></tr></tbody>
|
||||
</table>
|
||||
153
plugins/dynamix.apcupsd/UPSsettings.page
Normal file
@@ -0,0 +1,153 @@
|
||||
Menu="OtherSettings"
|
||||
Type="xmenu"
|
||||
Title="UPS Settings"
|
||||
Icon="dynamix.apcupsd.png"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Dan Landon.
|
||||
* Copyright 2015, Bergware International.
|
||||
* Copyright 2015, Lime Technology
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
|
||||
<?
|
||||
$sName = "dynamix.apcupsd";
|
||||
$cfg = parse_plugin_cfg($sName);
|
||||
?>
|
||||
|
||||
<style>
|
||||
table.ups thead tr th{width:16.6%;padding-left:10px;}
|
||||
table.ups tbody tr td{padding-left:10px;font-weight:bold;}
|
||||
</style>
|
||||
<script>
|
||||
function toggleCustomCable(form) {
|
||||
var readonly = form.UPSCABLE.value != 'custom';
|
||||
form.CUSTOMUPSCABLE.readOnly = readonly;
|
||||
if (readonly) form.CUSTOMUPSCABLE.value = '';
|
||||
}
|
||||
function toggleDevice(form) {
|
||||
var readonly = form.UPSTYPE.value == 'usb';
|
||||
form.DEVICE.readOnly = readonly;
|
||||
if (readonly) form.DEVICE.value = '';
|
||||
}
|
||||
$(function() {
|
||||
showStatus('pid','apcupsd');
|
||||
toggleCustomCable(document.apcupsd_settings);
|
||||
toggleDevice(document.apcupsd_settings);
|
||||
});
|
||||
</script>
|
||||
<table class="tablesorter shift ups" <?if (!file_exists("/var/run/apcupsd.pid")):?>style="display:none"<?endif;?>>
|
||||
<thead><tr><th>UPS Status</th><th>Battery Charge</th><th>Runtime Left</th><th>Nominal Power</th><th>UPS Load</th><th>UPS Load %</th></tr></thead>
|
||||
<tbody id="ups_summary"><tr><td colspan="6"> </td></tr></tbody>
|
||||
</table>
|
||||
|
||||
<form markdown="1" name="apcupsd_settings" method="POST" action="/update.php" target="progressFrame">
|
||||
<input type="hidden" name="#file" value="<?=$sName?>/<?=$sName?>.cfg">
|
||||
<input type="hidden" name="#include" value="/plugins/<?=$sName?>/include/update.apcupsd.php">
|
||||
<span style="float:right;margin-right:10px"><a href="http://apcupsd.org/manual/manual.html" target="_blank" title="APC UPS Daemon user manual"><i class="fa fa-file-text-o"></i> <u>Online Manual</u></a></span>
|
||||
|
||||
Start APC UPS daemon:
|
||||
: <select name="SERVICE" size="1">
|
||||
<?=mk_option($cfg['SERVICE'], "disable", "No");?>
|
||||
<?=mk_option($cfg['SERVICE'], "enable", "Yes");?>
|
||||
</select>
|
||||
|
||||
> Set to 'Yes' to enable apcupsd and start the daemon, set to 'No' to disable apcupsd and stop the daemon.
|
||||
|
||||
UPS cable:
|
||||
: <select name="UPSCABLE" size="1" onChange="toggleCustomCable(this.form)">
|
||||
<?=mk_option($cfg['UPSCABLE'], "usb", "USB");?>
|
||||
<?=mk_option($cfg['UPSCABLE'], "simple", "Simple");?>
|
||||
<?=mk_option($cfg['UPSCABLE'], "smart", "Smart");?>
|
||||
<?=mk_option($cfg['UPSCABLE'], "ether", "Ether");?>
|
||||
<?=mk_option($cfg['UPSCABLE'], "custom", "Custom");?>
|
||||
</select>
|
||||
|
||||
> Defines the type of cable connecting the UPS to your computer.Possible generic choices for 'cable' are:
|
||||
>
|
||||
> + USB, Simple, Smart, Ether, or Custom to specify a special cable.
|
||||
|
||||
Custom UPS cable:
|
||||
: <input type="text" name="CUSTOMUPSCABLE" class="narrow" maxlength="40" value="<?=$cfg['CUSTOMUPSCABLE'];?>">
|
||||
|
||||
> Specify a special cable by model number, only applicable when *UPS cable* is set to Custom.
|
||||
>
|
||||
> + 940-0119A, 940-0127A, 940-0128A, 940-0020B
|
||||
> + 940-0020C, 940-0023A, 940-0024B, 940-0024C
|
||||
> + 940-1524C, 940-0024G, 940-0095A, 940-0095B
|
||||
> + 940-0095C, 940-0625A, M-04-02-2000
|
||||
|
||||
UPS type:
|
||||
: <select name="UPSTYPE" size="1" onChange="toggleDevice(this.form)">
|
||||
<?=mk_option($cfg['UPSTYPE'], "usb", "USB");?>
|
||||
<?=mk_option($cfg['UPSTYPE'], "apcsmart", "APCsmart");?>
|
||||
<?=mk_option($cfg['UPSTYPE'], "net", "Net");?>
|
||||
<?=mk_option($cfg['UPSTYPE'], "snmp", "SNMP");?>
|
||||
<?=mk_option($cfg['UPSTYPE'], "dumb", "Dumb");?>
|
||||
<?=mk_option($cfg['UPSTYPE'], "pcnet", "PCnet");?>
|
||||
<?=mk_option($cfg['UPSTYPE'], "modbus", "ModBus");?>
|
||||
</select>
|
||||
|
||||
> Define a *UPS type*, which corresponds to the type of UPS you have (see the Description for more details).
|
||||
>
|
||||
> + **USB** - most new UPSes are USB
|
||||
> + **APCsmart** - newer serial character device, appropriate for SmartUPS models using a serial cable (not USB)
|
||||
> + **Net** - network link to a master apcupsd through apcupsd's Network Information Server. This is used if the UPS powering your computer is connected to a different computer for monitoring
|
||||
> + **SNMP** - SNMP network link to an SNMP-enabled UPS device
|
||||
> + **Dumb** - old serial character device for use with simple-signaling UPSes
|
||||
> + **PCnet** - PowerChute Network Shutdown protocol which can be used as an alternative to SNMP with the AP9617 family of smart slot cards
|
||||
> + **ModBus** - serial device for use with newest SmartUPS models supporting the MODBUS protocol
|
||||
|
||||
Device:
|
||||
: <input type="text" name="DEVICE" maxlength="40" value="<?=$cfg['DEVICE'];?>">
|
||||
|
||||
> Enter the *device* which correspondes to your situation, only applicable when *UPS type* is not set to USB.
|
||||
>
|
||||
> + **apcsmart** - /dev/tty**
|
||||
> + **net** - hostname:port. Hostname is the IP address of the NIS server. The deafult port is 3551
|
||||
> + **snmp** - hostname:port:vendor:community. Hostname is the ip address or hostname of the UPS on the network. Vendor can be can be "APC" or "APC_NOTRAP". "APC_NOTRAP" will disable SNMP trap catching; you usually want "APC". Port is usually 161. Community is usually "private"
|
||||
> + **dumb** - /dev/tty**
|
||||
> + **pcnet** - ipaddr:username:passphrase:port. ipaddr is the IP address of the UPS management card. username and passphrase are the credentials for which the card has been configured. port is the port number on which to listen for messages from the UPS, normally 3052. If this parameter is empty or missing, the default of 3052 will be used
|
||||
> + **modbus** - /dev/tty**
|
||||
|
||||
Battery level to initiate shutdown (%):
|
||||
: <input type="text" name="BATTERYLEVEL" class="narrow" maxlength="3" value="<?=$cfg['BATTERYLEVEL'];?>">
|
||||
|
||||
> If during a power failure, the remaining battery percentage (as reported by the UPS) is below or equal to *Battery level*, apcupsd will initiate a system shutdown.
|
||||
|
||||
Runtime left to initiate shutdown (minutes):
|
||||
: <input type="text" name="MINUTES" class="narrow" maxlength="3" value="<?=$cfg['MINUTES'];?>">
|
||||
|
||||
> If during a power failure, the remaining runtime in minutes (as calculated internally by the UPS) is below or equal to *minutes*, apcupsd, will initiate a system shutdown.
|
||||
|
||||
Time on battery before shutdown (seconds):
|
||||
: <input type="text" name="TIMEOUT" class="narrow" maxlength="4" value="<?=$cfg['TIMEOUT'];?>">
|
||||
|
||||
> If during a power failure, the UPS has run on batteries for *time-out* many seconds or longer; apcupsd will initiate a system shutdown. A value of zero disables this timer.
|
||||
>
|
||||
> If you have a Smart UPS, you will most likely want to disable this timer by setting it to zero.
|
||||
> That way, your UPS will continue on batteries until either the % charge remaining drops to or below *Battery level* or the remaining battery runtime drops to or below *minutes*.
|
||||
>
|
||||
> Of course - when testing - setting this to 60 causes a quick system shutdown if you pull the power plug.
|
||||
> If you have an older dumb UPS, you will want to set this to less than the time you know you can run on batteries.
|
||||
<div></div>
|
||||
> **Note:** *Battery level*, *Runtime left*, and *Time on battery* work in conjunction, so the first that occurs will cause the initiation of a shutdown.
|
||||
|
||||
Turn off UPS after shutdown:
|
||||
: <select name="KILLUPS" size="1">
|
||||
<?=mk_option($cfg['KILLUPS'], "no", "No");?>
|
||||
<?=mk_option($cfg['KILLUPS'], "yes", "Yes");?>
|
||||
</select>
|
||||
|
||||
> Set to *Yes* to turn off the power to the UPS after a shutdown.
|
||||
|
||||
<input type="submit" name="#default" value="Default">
|
||||
: <input type="submit" name="#apply" value="Apply"><input type="button" value="Done" onclick="done()">
|
||||
</form>
|
||||
36
plugins/dynamix.apcupsd/UPSsummary.page
Normal file
@@ -0,0 +1,36 @@
|
||||
Menu="Dashboard:2"
|
||||
Title="UPS Summary"
|
||||
Cond="file_exists('/var/run/apcupsd.pid')"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Dan Landon.
|
||||
* Copyright 2015, Bergware International.
|
||||
* Copyright 2015, Lime Technology
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<style>
|
||||
table.ups thead tr th{width:16.6%;padding-left:10px;}
|
||||
table.ups tbody tr td{padding-left:10px;font-weight:bold;}
|
||||
</style>
|
||||
<script>
|
||||
function getUPSstatus() {
|
||||
$.get('/plugins/dynamix.apcupsd/include/UPSstatus.php',{all:'false'},function(data) {
|
||||
if (data) $('#ups_summary').html(data);
|
||||
<?if (($display['refresh']>0 || ($display['refresh']<0 && $var['mdResync']==0))):?>
|
||||
setTimeout(getUPSstatus, <?=max(abs($display['refresh']),15000)?>);
|
||||
<?endif;?>
|
||||
});
|
||||
}
|
||||
$(getUPSstatus);
|
||||
</script>
|
||||
<table class="tablesorter shift ups">
|
||||
<thead><tr><th>UPS Status</th><th>Battery Charge</th><th>Runtime Left</th><th>Nominal Power</th><th>UPS Load</th><th>UPS Load %</th></tr></thead>
|
||||
<tbody id="ups_summary"><tr><td colspan="6"> </td></tr></tbody>
|
||||
</table>
|
||||
5
plugins/dynamix.apcupsd/apcupsd.notify
Executable file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Send system notify message from apcupsd
|
||||
#
|
||||
read MESSAGE
|
||||
/usr/local/emhttp/webGui/scripts/notify -e "unRAID Server Alert" -s "UPS Alert" -d "$MESSAGE" -i "alert"
|
||||
9
plugins/dynamix.apcupsd/default.cfg
Normal file
@@ -0,0 +1,9 @@
|
||||
SERVICE="disable"
|
||||
UPSCABLE="usb"
|
||||
CUSTOMUPSCABLE=""
|
||||
UPSTYPE="usb"
|
||||
DEVICE=""
|
||||
BATTERYLEVEL="10"
|
||||
MINUTES="10"
|
||||
TIMEOUT="0"
|
||||
KILLUPS="no"
|
||||
30
plugins/dynamix.apcupsd/event/driver_loaded
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
conf=/etc/apcupsd/apcupsd.conf
|
||||
cfg=/boot/config/plugins/dynamix.apcupsd/dynamix.apcupsd.cfg
|
||||
|
||||
# Daemon already running or no custom file?
|
||||
[[ -f /var/run/apcupsd.pid || ! -f $cfg ]] && exit
|
||||
|
||||
# Read settings
|
||||
source $cfg
|
||||
|
||||
# Apply settings
|
||||
sed -i -e '/^NISIP/c\\NISIP 0.0.0.0' $conf
|
||||
sed -i -e '/^UPSTYPE/c\\UPSTYPE '$UPSTYPE'' $conf
|
||||
sed -i -e '/^DEVICE/c\\DEVICE '$DEVICE'' $conf
|
||||
sed -i -e '/^BATTERYLEVEL/c\\BATTERYLEVEL '$BATTERYLEVEL'' $conf
|
||||
sed -i -e '/^MINUTES/c\\MINUTES '$MINUTES'' $conf
|
||||
sed -i -e '/^TIMEOUT/c\\TIMEOUT '$TIMEOUT'' $conf
|
||||
if [[ $UPSCABLE == custom ]]; then
|
||||
sed -i -e '/^UPSCABLE/c\\UPSCABLE '$CUSTOMUPSCABLE'' $conf
|
||||
else
|
||||
sed -i -e '/^UPSCABLE/c\\UPSCABLE '$UPSCABLE'' $conf
|
||||
fi
|
||||
if [[ $KILLUPS == yes && $SERVICE == enable ]]; then
|
||||
! grep -q apccontrol /etc/rc.d/rc.6 && sed -i -e 's:/sbin/poweroff:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:' /etc/rc.d/rc.6
|
||||
else
|
||||
grep -q apccontrol /etc/rc.d/rc.6 && sed -i -e 's:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:/sbin/poweroff:' /etc/rc.d/rc.6
|
||||
fi
|
||||
|
||||
# Start daemon
|
||||
[[ $SERVICE == enable ]] && /etc/rc.d/rc.apcupsd start |& logger
|
||||
BIN
plugins/dynamix.apcupsd/icons/upsdetails.png
Normal file
|
After Width: | Height: | Size: 952 B |
BIN
plugins/dynamix.apcupsd/icons/upssettings.png
Normal file
|
After Width: | Height: | Size: 704 B |
BIN
plugins/dynamix.apcupsd/icons/upssummary.png
Normal file
|
After Width: | Height: | Size: 704 B |
BIN
plugins/dynamix.apcupsd/images/dynamix.apcupsd.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
69
plugins/dynamix.apcupsd/include/UPSstatus.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Dan Landon.
|
||||
* Copyright 2015, Bergware International.
|
||||
* Copyright 2015, Lime Technology
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$state = [
|
||||
'TRIM ONLINE' => 'Online (trim)',
|
||||
'BOOST ONLINE' => 'Online (boost)',
|
||||
'ONLINE' => 'Online',
|
||||
'ONBATT' => 'On battery',
|
||||
'COMMLOST' => 'Lost communication',
|
||||
'NOBATT' => 'No battery detected'
|
||||
];
|
||||
|
||||
$red = "class='red-text'";
|
||||
$green = "class='green-text'";
|
||||
$orange = "class='orange-text'";
|
||||
$status = array_fill(0,6,"<td>-</td>");
|
||||
$all = $_GET['all']=='true';
|
||||
$result = array();
|
||||
|
||||
if (file_exists("/var/run/apcupsd.pid")) {
|
||||
exec("/sbin/apcaccess 2>/dev/null", $rows);
|
||||
for ($i=0; $i<count($rows); $i++) {
|
||||
$row = array_map('trim', explode(':', $rows[$i], 2));
|
||||
$key = $row[0];
|
||||
$val = strtr($row[1], $state);
|
||||
switch ($key) {
|
||||
case 'STATUS':
|
||||
$status[0] = $val ? (stripos($val,'online')===false ? "<td $red>$val</td>" : "<td $green>$val</td>") : "<td $orange>Refreshing...</td>";
|
||||
break;
|
||||
case 'BCHARGE':
|
||||
$status[1] = strtok($val,' ')<=10 ? "<td $red>$val</td>" : "<td $green>$val</td>";
|
||||
break;
|
||||
case 'TIMELEFT':
|
||||
$status[2] = strtok($val,' ')<=5 ? "<td $red>$val</td>" : "<td $green>$val</td>";
|
||||
break;
|
||||
case 'NOMPOWER':
|
||||
$power = strtok($val,' ');
|
||||
$status[3] = $power==0 ? "<td $red>$val</td>" : "<td $green>$val</td>";
|
||||
break;
|
||||
case 'LOADPCT':
|
||||
$load = strtok($val,' ');
|
||||
$status[5] = $load>=90 ? "<td $red>$val</td>" : "<td $green>$val</td>";
|
||||
break;
|
||||
}
|
||||
if ($all) {
|
||||
if ($i%2==0) $result[] = "<tr>";
|
||||
$result[]= "<td><strong>$key</strong></td><td>$val</td>";
|
||||
if ($i%2==1) $result[] = "</tr>";
|
||||
}
|
||||
}
|
||||
if ($all && count($rows)%2==1) $result[] = "<td></td><td></td></tr>";
|
||||
if ($power && $load) $status[4] = ($load>=90 ? "<td $red>" : "<td $green>").intval($power*$load/100)." Watts</td>";
|
||||
}
|
||||
if ($all && !$rows) $result[] = "<tr><td colspan='4'><center>No information available</center></td></tr>";
|
||||
|
||||
echo "<tr>".implode('', $status)."</tr>";
|
||||
if ($all) echo "\n".implode('', $result);
|
||||
?>
|
||||
34
plugins/dynamix.apcupsd/include/update.apcupsd.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Dan Landon.
|
||||
* Copyright 2015, Bergware International.
|
||||
* Copyright 2015, Lime Technology
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$conf = "/etc/apcupsd/apcupsd.conf";
|
||||
$new = array_replace_recursive($_POST, $default);
|
||||
$cable = $new['UPSCABLE']=='custom' ? $new['CUSTOMUPSCABLE'] : $new['UPSCABLE'];
|
||||
|
||||
exec("/etc/rc.d/rc.apcupsd stop");
|
||||
exec("sed -i -e '/^NISIP/c\\NISIP 0.0.0.0' $conf");
|
||||
exec("sed -i -e '/^UPSTYPE/c\\UPSTYPE '{$new['UPSTYPE']}'' $conf");
|
||||
exec("sed -i -e '/^DEVICE/c\\DEVICE '{$new['DEVICE']}'' $conf");
|
||||
exec("sed -i -e '/^BATTERYLEVEL/c\\BATTERYLEVEL '{$new['BATTERYLEVEL']}'' $conf");
|
||||
exec("sed -i -e '/^MINUTES/c\\MINUTES '{$new['MINUTES']}'' $conf");
|
||||
exec("sed -i -e '/^TIMEOUT/c\\TIMEOUT '{$new['TIMEOUT']}'' $conf");
|
||||
exec("sed -i -e '/^UPSCABLE/c\\UPSCABLE '{$cable}'' $conf");
|
||||
|
||||
if ($new['KILLUPS']=='yes' && $new['SERVICE']=='enable')
|
||||
exec("! grep -q apccontrol /etc/rc.d/rc.6 && sed -i -e 's:/sbin/poweroff:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:' /etc/rc.d/rc.6");
|
||||
else
|
||||
exec("grep -q apccontrol /etc/rc.d/rc.6 && sed -i -e 's:/etc/apcupsd/apccontrol killpower; /sbin/poweroff:/sbin/poweroff:' /etc/rc.d/rc.6");
|
||||
|
||||
if ($new['SERVICE']=='enable') exec("/etc/rc.d/rc.apcupsd start");
|
||||
?>
|
||||
22
plugins/dynamix.docker.manager/AddContainer.page
Normal file
@@ -0,0 +1,22 @@
|
||||
Title="Add Container"
|
||||
Cond="(pgrep('docker')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<div style="text-align:right;vertical-align:top;position:relative;top:-44px;height:0;display:none;">
|
||||
<span class="toggleMode" onclick="toggleMode();"><i id="toggleMode" class="fa fa-lg"></i> Advanced View</span>
|
||||
</div>
|
||||
<?PHP
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php');
|
||||
?>
|
||||
3
plugins/dynamix.docker.manager/Docker.page
Normal file
@@ -0,0 +1,3 @@
|
||||
Menu="Tasks:60"
|
||||
Type="xmenu"
|
||||
Cond="(pgrep('docker')!==false)"
|
||||
247
plugins/dynamix.docker.manager/DockerContainers.page
Normal file
@@ -0,0 +1,247 @@
|
||||
Menu="Docker:1"
|
||||
Title="Docker Containers"
|
||||
Cond="(pgrep('docker')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
// Add the Docker JSON client
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php');
|
||||
$docker = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="/plugins/dynamix.docker.manager/styles/gh-buttons.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.ui.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.switchbutton.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/context.standalone.css">
|
||||
<style>
|
||||
body {-webkit-overflow-scrolling: touch;}
|
||||
img.started{opacity:1.0;}
|
||||
img.stopped{opacity:0.3;}
|
||||
.log{cursor:zoom-in;}
|
||||
.exec{cursor:pointer;}
|
||||
.fa-custom{font-size: 1.3em;}
|
||||
.basic{display: block;}
|
||||
.advanced{display:none;white-space: nowrap;}
|
||||
.iconstatus{position:absolute;z-index:2;bottom:-4px;right:-4px;font-size:1.2em;text-shadow:0 0 2px #FFF;}
|
||||
.iconstatus.started{font-size:1.3em;}
|
||||
.iconstatus.stopped{font-size:1.2em;}
|
||||
.started{color:#009900;}
|
||||
.stopped{color:#EF3D47;}
|
||||
</style>
|
||||
<form id="formAutostart" method="POST" action="/plugins/dynamix.docker.manager/include/UpdateConfig.php" target="progressFrame">
|
||||
<input type="hidden" name="action" value="autostart" />
|
||||
<input type="hidden" name="container" value="none" />
|
||||
</form>
|
||||
<form id="formStartStop" method="POST" action="/update.php" target="progressFrame">
|
||||
<input type="hidden" id="cmdStartStop" name="#command" value="">
|
||||
<input type="hidden" id="cmdArg1" name="#arg[1]" value="">
|
||||
<input type="hidden" id="cmdArg2" name="#arg[2]" value="">
|
||||
</form>
|
||||
<div id="dialog-confirm" style="display:none;" title="Dialog Title"></div>
|
||||
<div id="iframe-popup" style="display:none;-webkit-overflow-scrolling:touch;"></div>
|
||||
<span class="status" style="margin-top: -42px;"><input type="checkbox" class="advancedview"></span>
|
||||
<table class="tablesorter shift" id="docker_containers">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Application</th>
|
||||
<th>Author / Repo</th>
|
||||
<th>Version</th>
|
||||
<th>Port Mappings <small>(App to Host)</small></th>
|
||||
<th>Volume Mappings <small>(App to Host)</small></th>
|
||||
<th width="80px" style="text-align: center">Autostart</th>
|
||||
<th>Log</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?
|
||||
$all_containers = $docker->getDockerContainers();
|
||||
if ( ! $all_containers) {
|
||||
$all_containers = array();
|
||||
echo "<tr><td></td><td colspan=\"7\">No Docker Containers Installed</td></tr>";
|
||||
}
|
||||
$info = $DockerTemplates->getAllInfo();
|
||||
$contextMenus = array();
|
||||
|
||||
$IP = $var["IPADDR"];
|
||||
foreach($all_containers as $ct){
|
||||
$name = $ct["Name"];
|
||||
$is_autostart = ( $info[$name]['autostart'] ) ? 'true' : 'false';
|
||||
$updateStatus = $info[$name]['updated'];
|
||||
$updateStatus = ($updateStatus == "true" or $updateStatus == "undef" ) ? 'true' : 'false';
|
||||
$running = ($ct['Running']) ? 'true' : 'false';
|
||||
$webGuiUrl = $info[$name]['url'];
|
||||
$contextMenus[] = sprintf("addDockerContainerContext('%s', '%s', '%s', %s, %s, %s, '%s');", addslashes($ct['Name']), addslashes($ct['ImageId']), addslashes($info[$name]['template']), $running, $updateStatus, $is_autostart, addslashes($webGuiUrl));
|
||||
$shape = ($ct["Running"]) ? "play" : "square";
|
||||
$status = ($ct["Running"]) ? "started" : "stopped";
|
||||
|
||||
$Icon = $info[$name]['icon'];
|
||||
if ( $Icon == "#" ){
|
||||
$Icon = "/plugins/dynamix.docker.manager/images/question.png";
|
||||
}
|
||||
|
||||
$ports = array();
|
||||
foreach ($ct['Ports'] as $p) {
|
||||
if (strlen($p['PublicPort'])){
|
||||
$ipAddr = sprintf("%s:%s", $IP, $p['PublicPort']);
|
||||
$outFormat = sprintf('<a href="http://%s" target="_blank">%s/%s <i class="fa fa-arrows-h" style="margin: 0 3px"></i> %s</a>', $ipAddr, $p['PrivatePort'], $p['Type'], htmlspecialchars($ipAddr));
|
||||
} else {
|
||||
$outFormat = sprintf("%s/%s", $p['PrivatePort'], $p['Type']);
|
||||
}
|
||||
$ports[] = $outFormat;
|
||||
}
|
||||
$paths = array();
|
||||
if (count($ct['Volumes'])){
|
||||
foreach ($ct['Volumes'] as $value) {
|
||||
if (preg_match('/localtime/', $value) == TRUE){ continue; }
|
||||
list($host_path, $container_path, $access_mode) = explode(":", $value);
|
||||
|
||||
$tip = 'Container volume \'' . $container_path . '\' has ' . ($access_mode == 'ro' ? 'read-only' : 'read-write') . ' access to Host path \'' . $host_path . '\'';
|
||||
|
||||
$paths[] = sprintf('<a href="/Shares/Browse?dir=%s" target="_blank" title="%s">%s <i class="fa fa-%s" style="margin: 0 3px"></i> %s</a>', urlencode($host_path), htmlspecialchars($tip), htmlspecialchars($container_path), ($access_mode == 'ro' ? 'long-arrow-left' : 'arrows-h'), htmlspecialchars($host_path));
|
||||
}
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td style="width: 48px; padding: 4px">
|
||||
<?
|
||||
echo " <div id=\"context-" . htmlspecialchars($name) . "\" style=\"display:block; cursor:pointer\">
|
||||
<div style=\"position: relative; width: 48px; height: 48px; margin: 0px auto;\">
|
||||
<img src=\"" . $Icon . "\" class=\"$status\" style=\"position: absolute; z-index: 1; top: 0; bottom: 0; left: 0; right: 0; width: 48px; height: 48px;\"/>
|
||||
<i class=\"fa iconstatus fa-$shape $status\" title='$status'></i>
|
||||
</div>
|
||||
</div>";
|
||||
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?if($info[$ct['Name']]['template']):?>
|
||||
<a class="exec" onclick="editContainer('<?=addslashes($ct['Name']);?>','<?=addslashes($info[$ct['Name']]['template']);?>');"><?=htmlspecialchars($ct['Name']);?></a>
|
||||
<?else:?>
|
||||
<?=htmlspecialchars($ct['Name']);?>
|
||||
<?endif;?>
|
||||
<div class="advanced" style="color:#888; width: 160px;">Container ID: <?=htmlspecialchars($ct['Id']);?></div>
|
||||
</td>
|
||||
<td>
|
||||
<?
|
||||
$Registry = $info[$ct['Name']]['registry'];
|
||||
if ($Registry){
|
||||
printf("<a href=\"%s\" target=\"_blank\">%s</a>", htmlspecialchars($Registry), htmlspecialchars($ct['Image']) );
|
||||
} else {
|
||||
echo htmlspecialchars($ct['Image']);
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td class="updatecolumn">
|
||||
<?
|
||||
$updateStatus = $info[$ct['Name']]['updated'];
|
||||
if ($updateStatus == "false"){
|
||||
echo "<a class=\"exec\" onclick=\"updateContainer('" . addslashes($ct["Name"]) . "');\"><span style=\"white-space:nowrap;\"><i class=\"fa fa-cloud-download\"></i> update ready</span></a>";
|
||||
} else if ($updateStatus == "true"){
|
||||
echo "<span style=\"color:#44B012;white-space:nowrap;\"><i class=\"fa fa-check\"></i> up-to-date</span>";
|
||||
echo "<div class=\"advanced\"><a class=\"exec\" onclick=\"updateContainer('" . addslashes($ct["Name"]) . "');\" style=\"color:#888;\"><span style=\"white-space:nowrap;\"><i class=\"fa fa-cloud-download\"></i> force update</span></a></div>";
|
||||
} else {
|
||||
echo "<span style=\"color:#FF2400;white-space:nowrap;\"><i class=\"fa fa-exclamation-triangle\"></i> not available</span>";
|
||||
echo "<div class=\"advanced\"><a class=\"exec\" onclick=\"updateContainer('" . addslashes($ct["Name"]) . "');\" style=\"color:#888;\"><span style=\"white-space:nowrap;\"><i class=\"fa fa-cloud-download\"></i> force update</span></a></div>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td style="white-space:nowrap;"><?= implode("<br>", $ports); ?></td>
|
||||
<td style="word-break:break-all;"><?= implode("<br>", $paths); ?></td>
|
||||
<td data-sort-value="<?=$is_autostart?>"><input type="checkbox" class="autostart" container="<?=htmlspecialchars($ct['Name']);?>" <?=$info[$ct['Name']]['autostart'] ? "checked" : ""?>></td>
|
||||
<td><a class="log" onclick="containerLogs('<?=addslashes($ct['Name']);?>','Log for <?=addslashes($ct['Name']);?>', false, false)"><img class="basic" src="/plugins/dynamix/icons/log.png"/><div class="advanced" style="width: 124px;"><?=htmlspecialchars($ct['Status'])?></div><div class="advanced" style="color:#888;">Created <?=htmlspecialchars($ct['Created'])?></div></a></td>
|
||||
</tr>
|
||||
<?}?>
|
||||
|
||||
|
||||
<?
|
||||
$all_images = $docker->getDockerImages();
|
||||
if ( ! $all_images) { $all_images = array(); }
|
||||
|
||||
foreach($all_images as $image){
|
||||
if (count($image['usedBy'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contextMenus[] = sprintf("addDockerImageContext('%s', '%s');", $image['Id'], implode(', ', $image['Tags']));
|
||||
|
||||
?>
|
||||
<tr class="advanced">
|
||||
<td style="width: 48px; padding: 4px">
|
||||
<?
|
||||
echo " <div id='context-" . $image['Id'] . "' style='display:block; cursor:pointer'>
|
||||
<div style=\"position: relative; width: 48px; height: 48px; margin: 0px auto;\">
|
||||
<img src=\"/webGui/images/disk.png\" style=\"position: absolute; z-index: 1; opacity: 0.3; top: 0; bottom: 0; left: 0; right: 0; width: 48px; height: 48px;\"/>
|
||||
</div>
|
||||
</div>";
|
||||
|
||||
?>
|
||||
</td>
|
||||
<td data-sort-value="ZZZZZZZZZZZ">
|
||||
<i>(orphan image)</i>
|
||||
<div class="advanced" style="color:#888; width: 160px;">Image ID: <?=$image['Id']?></div>
|
||||
</td>
|
||||
<td><?=implode("<br>", $image['Tags']);?></td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td><div class="advanced" style="width: 124px; color:#888;">Created <?=$image['Created']?></div></td>
|
||||
</tr>
|
||||
<?}?>
|
||||
</tbody>
|
||||
</table>
|
||||
<input type="button" onclick="addContainer()" value="Add Container"/>
|
||||
<input type="button" onclick="reloadUpdate()" value="Check for Updates"/>
|
||||
|
||||
<script src="/webGui/javascript/jquery.switchbutton.js"></script>
|
||||
<script src="/webGui/javascript/context.js"></script>
|
||||
<script src="/plugins/dynamix.docker.manager/javascript/docker.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
if ($.cookie('docker_listview_mode') == 'advanced') {
|
||||
$('.advanced').show();
|
||||
$('.basic').hide();
|
||||
}
|
||||
|
||||
$('.autostart').switchButton({
|
||||
labels_placement: "right"
|
||||
});
|
||||
$('.autostart').change(function () {
|
||||
$.post( "/plugins/dynamix.docker.manager/include/UpdateConfig.php", { action: "autostart", container: $(this).attr('container'), response: "json" }, function( data ) {
|
||||
$(this).prop('checked', data.autostart );
|
||||
}, "json");
|
||||
});
|
||||
|
||||
$('.advancedview').switchButton({
|
||||
labels_placement: "left",
|
||||
on_label: 'Advanced View',
|
||||
off_label: 'Basic View',
|
||||
checked: $.cookie('docker_listview_mode') == 'advanced'
|
||||
});
|
||||
$('.advancedview').change(function () {
|
||||
$('.advanced').toggle('slow');
|
||||
$('.basic').toggle('slow');
|
||||
$.cookie('docker_listview_mode', $('.advancedview').is(':checked') ? 'advanced' : 'basic', { expires: 3650 });
|
||||
});
|
||||
|
||||
$('#docker_containers').tablesorter( {sortList: [[1,0]], headers: {0: {sorter: false }, 7: {sorter: false }}});
|
||||
$('#docker_containers tr:even').addClass('odd');
|
||||
|
||||
context.init({ preventDoubleContext: false });
|
||||
<?=implode("\n\t", $contextMenus);?>
|
||||
});
|
||||
</script>
|
||||
39
plugins/dynamix.docker.manager/DockerRepositories.page
Normal file
@@ -0,0 +1,39 @@
|
||||
Menu="Docker:2"
|
||||
Title="Docker Repositories"
|
||||
Cond="(pgrep('docker')!==false)"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
// Add the Docker JSON client
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php');
|
||||
$docker = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
|
||||
// Repos file
|
||||
$template_repos = $dockerManPaths['template-repos'];
|
||||
?>
|
||||
<form markdown="1" method="POST" action="/plugins/dynamix.docker.manager/include/UpdateConfig.php" target="progressFrame">
|
||||
<input type="hidden" name="#action" value="templates" />
|
||||
Template repositories:
|
||||
: <textarea name="template_repos" rows="5" cols="100" style="width:550px"><?=@file_get_contents($template_repos);?></textarea>
|
||||
|
||||
> Use this field to add template repositories.
|
||||
> Docker templates are used to facilitate the creation and re-creation of Docker containers. Please setup one per line.
|
||||
>
|
||||
> For a list of popular community-supported repositories, visit here: <a href="http://lime-technology.com/forum/index.php?topic=37958.0" target="_blank">http://lime-technology.com/forum/index.php?topic=37958.0</a>
|
||||
|
||||
|
||||
: <input type="submit" value="Save" />
|
||||
</form>
|
||||
233
plugins/dynamix.docker.manager/DockerSettings.page
Normal file
@@ -0,0 +1,233 @@
|
||||
Menu="OtherSettings"
|
||||
Title="Docker"
|
||||
Icon="dynamix.docker.manager.png"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
if ($var['fsState'] != "Started") {
|
||||
echo "<p class='notice'>Array must be Started to manage Docker.</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the Docker JSON client
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php');
|
||||
$docker = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
|
||||
// Docker configuration file
|
||||
$cfgfile = "/boot/config/docker.cfg";
|
||||
|
||||
if (!file_exists($cfgfile)) {
|
||||
echo "<p class='notice'>Missing docker.cfg file!</p>";
|
||||
return;
|
||||
}
|
||||
$dockercfg = parse_ini_file($cfgfile);
|
||||
if (!array_key_exists('DOCKER_ENABLED', $dockercfg)) {
|
||||
$dockercfg['DOCKER_ENABLED'] = 'no';
|
||||
}
|
||||
|
||||
// Check for nodatacow flag on Docker file; display warning
|
||||
$realfile = $dockercfg['DOCKER_IMAGE_FILE'];
|
||||
if (file_exists($realfile)) {
|
||||
if (strpos($realfile, '/mnt/user/') === 0) {
|
||||
$tmp = parse_ini_string(shell_exec("getfattr --absolute-names -n user.LOCATION " . escapeshellarg($dockercfg['DOCKER_IMAGE_FILE']) . " | grep user.LOCATION"));
|
||||
$realfile = str_replace('user', $tmp['user.LOCATION'], $realfile); // replace 'user' with say 'cache' or 'disk1' etc
|
||||
}
|
||||
|
||||
if (exec("stat -c %T -f " . escapeshellarg($realfile)) == "btrfs") {
|
||||
if (shell_exec("lsattr " . escapeshellarg($realfile) . " | grep \"\\-C\"") == "") {
|
||||
echo '<p class="notice">Your existing Docker image file needs to be recreated due to an issue from an earlier beta of unRAID 6. Failure to do so may result in your docker image suffering corruption at a later time. Please do this NOW!</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css">
|
||||
<style>
|
||||
.errortext{color:#EF3D47;display:none;}
|
||||
.fileTree{width:305px;max-height:150px;overflow:scroll;position:absolute;z-index:100;display:none;}
|
||||
</style>
|
||||
<?if (pgrep('docker') === false):?>
|
||||
<form id="settingsForm" markdown="1" method="POST" action="/update.php" target="progressFrame">
|
||||
<input type="hidden" name="#file" value="<?=$cfgfile;?>" />
|
||||
<input type="hidden" id="command" name="#command" value="" />
|
||||
|
||||
Enable Docker:
|
||||
: <select id="DOCKER_ENABLED" name="DOCKER_ENABLED" class="narrow">
|
||||
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'no', 'No'); ?>
|
||||
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'yes', 'Yes'); ?>
|
||||
</select>
|
||||
|
||||
> Before you can start the Docker service for the first time, please specify an image
|
||||
> file for Docker to install to. Once started, Docker will always automatically start
|
||||
> after the array has been started.
|
||||
|
||||
Default image size:
|
||||
: <input id="DOCKER_IMAGE_SIZE" type="number" name="DOCKER_IMAGE_SIZE" value="<?=$dockercfg['DOCKER_IMAGE_SIZE'];?>" class="narrow" required="required" />GB <span id="SIZE_ERROR" class="errortext"></span>
|
||||
|
||||
> If the system needs to create a new docker image file, this is the default size to use
|
||||
> specified in GB.
|
||||
>
|
||||
> To resize an existing image file, specify the new size here. Next time the Docker service is
|
||||
> started the file (and file system) will increased to the new size (but never decreased).
|
||||
|
||||
Docker image:
|
||||
: <input id="DOCKER_IMAGE_FILE" type="text" name="DOCKER_IMAGE_FILE" value="<?=$dockercfg['DOCKER_IMAGE_FILE'];?>" placeholder="e.g. /mnt/disk1/docker.img" data-pickcloseonfile="true" data-pickfilter="img" data-pickroot="/mnt/" data-pickfolders="true" required="required" /> <?php if (file_exists($dockercfg['DOCKER_IMAGE_FILE'])) { ?><span id="deletePanel"><label><input type="checkbox" id="deleteCheckbox" /> Delete Image File</label></span><?php } ?> <span id="IMAGE_ERROR" class="errortext"></span>
|
||||
|
||||
> You must specify an image file for Docker. The system will automatically
|
||||
> create this file when the Docker service is first started. If you do not want Docker
|
||||
> to run at all, set this field blank and click **Start**.
|
||||
|
||||
|
||||
: <input id="applyBtn" type="button" value="Apply"/><input type="button" value="Done" onclick="done()">
|
||||
|
||||
</form>
|
||||
<?else:
|
||||
// IF DOCKER STARTED
|
||||
?>
|
||||
<form id="settingsForm" markdown="1" method="POST" action="/update.php" target="progressFrame">
|
||||
<input type="hidden" name="#file" value="<?=$cfgfile;?>" />
|
||||
<input type="hidden" id="command" name="#command" value="" />
|
||||
|
||||
Enable Docker:
|
||||
: <select id="DOCKER_ENABLED" name="DOCKER_ENABLED" class="narrow">
|
||||
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'no', 'No'); ?>
|
||||
<?= mk_option($dockercfg['DOCKER_ENABLED'], 'yes', 'Yes'); ?>
|
||||
</select>
|
||||
|
||||
> Stopping the Docker service will first stop all the running containers.
|
||||
|
||||
Docker version:
|
||||
: <? $arrInfo = $docker->getInfo(); echo $arrInfo['Version']; ?>
|
||||
|
||||
> This is the docker version.
|
||||
|
||||
Docker image:
|
||||
: <?=$dockercfg['DOCKER_IMAGE_FILE'];?>
|
||||
|
||||
> This is the docker volume.
|
||||
|
||||
|
||||
: <input type="button" id="applyBtn" value="Apply"><input type="button" value="Done" onclick="done()">
|
||||
|
||||
</form>
|
||||
<div id="title"><span class="left"><img src="/plugins/dynamix.docker.manager/icons/vcard.png" class="icon">Docker volume info</span></div>
|
||||
|
||||
btrfs filesystem show:
|
||||
: <?echo "<pre>".shell_exec("btrfs filesystem show /var/lib/docker")."</pre>";?>
|
||||
|
||||
<form markdown="1" method="POST" action="/update.php" target="progressFrame">
|
||||
<?exec("/usr/local/emhttp/webGui/scripts/btrfs_scrub status /var/lib/docker", $scrub_status, $retval);?>
|
||||
|
||||
btrfs scrub status:
|
||||
: <?echo "<pre>" . implode("\n", $scrub_status) . "</pre>";?>
|
||||
|
||||
<?if ($retval != 0):?>
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_scrub">
|
||||
<input type="hidden" name="#arg[1]" value="start">
|
||||
<input type="hidden" name="#arg[2]" value="/var/lib/docker">
|
||||
|
||||
|
||||
: <input type="submit" value="Scrub"><label><input type="checkbox" name="#arg[3]" value="-r" checked> Don't fix file system errors</label>
|
||||
|
||||
> **Scrub** runs the *btrfs scrub* program to check file system integrity.
|
||||
>
|
||||
> If repair is needed you should uncheck the *Don't fix file system errors* option and
|
||||
> run a second Scrub pass; this will permit *btrfs scrub* to fix the file system.
|
||||
|
||||
<?else:?>
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_scrub">
|
||||
<input type="hidden" name="#arg[1]" value="cancel">
|
||||
<input type="hidden" name="#arg[2]" value="/var/lib/docker">
|
||||
|
||||
|
||||
: <input type="button" value="Refresh" onclick="refresh()"><input type="submit" value="Cancel"> *Running*
|
||||
|
||||
> **Cancel** will cancel the Scrub operation in progress.
|
||||
|
||||
</form>
|
||||
<?endif;?>
|
||||
<?endif;?>
|
||||
<script src="/webGui/javascript/jquery.filetree.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
//$.ajaxSetup({ async: false });
|
||||
$("#applyBtn").click(function(){
|
||||
if ($("#deleteCheckbox").length && $("#deleteCheckbox").is(":checked")) {
|
||||
$("#command").val("/plugins/dynamix.docker.manager/scripts/docker_rm");
|
||||
$("#DOCKER_IMAGE_FILE").val("");
|
||||
$("#settingsForm").submit();
|
||||
return;
|
||||
}
|
||||
|
||||
$("#command").val("/plugins/dynamix.docker.manager/event/" + ($("#DOCKER_ENABLED").val()=="yes" ? "started" : "stopping_svcs"));
|
||||
|
||||
if ($("#DOCKER_IMAGE_SIZE").length && $("#DOCKER_IMAGE_FILE").length) {
|
||||
var isError = false;
|
||||
var size = $("#DOCKER_IMAGE_SIZE").val();
|
||||
var target = $("#SIZE_ERROR");
|
||||
if (! $.isNumeric( size )){
|
||||
target.fadeIn().html('Error: value must be a number.');
|
||||
isError = true;
|
||||
} else {
|
||||
target.fadeOut();
|
||||
}
|
||||
var image = $("#DOCKER_IMAGE_FILE").val();
|
||||
|
||||
target = $("#IMAGE_ERROR");
|
||||
$.getJSON( "/plugins/dynamix.docker.manager/include/UpdateConfig.php?is_dir=" + image).done(function( json ) {
|
||||
if (json.is_dir === true){
|
||||
target.fadeIn().html('Error: must be a file; directory provided.');
|
||||
isError = true;
|
||||
} else {
|
||||
target.fadeOut();
|
||||
}
|
||||
|
||||
if (!isError) {$("#settingsForm").submit();}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
$("#settingsForm").submit();
|
||||
});
|
||||
|
||||
if ($("#DOCKER_IMAGE_FILE").length) {
|
||||
$("#DOCKER_IMAGE_FILE").on("change keyup", function(){
|
||||
$("#IMAGE_ERROR").fadeOut();
|
||||
$("#applyBtn").prop("disabled", false);
|
||||
<? if (file_exists($dockercfg['DOCKER_IMAGE_FILE'])) { ?>
|
||||
if ($(this).val() != "<?=$dockercfg['DOCKER_IMAGE_FILE']?>") {
|
||||
$("#deleteCheckbox").prop("disabled", true).attr("checked", false);
|
||||
$("#deletePanel").fadeOut();
|
||||
} else {
|
||||
$("#deleteCheckbox").attr("checked", false).prop("disabled", false);
|
||||
$("#deletePanel").fadeIn();
|
||||
}
|
||||
<? } ?>
|
||||
});
|
||||
|
||||
$("#DOCKER_IMAGE_FILE").fileTreeAttach(null, null, function(folder) {
|
||||
$("#DOCKER_IMAGE_FILE").val(folder + 'docker.img').change();
|
||||
});
|
||||
|
||||
$("#deleteCheckbox").change(function(){
|
||||
var checked = $(this).is(":checked");
|
||||
$("#DOCKER_ENABLED").prop("disabled", checked).val('no');
|
||||
$("#DOCKER_IMAGE_SIZE").prop("disabled", checked);
|
||||
$("#DOCKER_IMAGE_FILE").prop("disabled", checked).val("<?=$dockercfg['DOCKER_IMAGE_FILE']?>");
|
||||
$("#applyBtn").val(checked ? "Delete" : "Apply");
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
12
plugins/dynamix.docker.manager/LICENSE
Normal file
@@ -0,0 +1,12 @@
|
||||
EXTENDED DOCKER CONFIGURATION PAGE
|
||||
Copyright (C) 2014 Guilherme Jardim
|
||||
|
||||
|
||||
FORKED FROM:
|
||||
**Copyright 2005-2014 Lime Technology.**
|
||||
|
||||
This Software is licensed under [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html).
|
||||
|
||||
unRAID is a registered trademark of [Lime Technology](http://lime-technology.com).
|
||||
|
||||
This file shall be included in all copies or substantial portions of the Software.
|
||||
22
plugins/dynamix.docker.manager/UpdateContainer.page
Normal file
@@ -0,0 +1,22 @@
|
||||
Title="Update Container"
|
||||
Cond="(pgrep('docker')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<div style="text-align:right;vertical-align:top;position:relative;top:-44px;height:0;display:none;">
|
||||
<span class="toggleMode" onclick="toggleMode();"><i id="toggleMode" class="fa fa-lg"></i> Advanced View</span>
|
||||
</div>
|
||||
<?PHP
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.docker.manager/include/CreateDocker.php');
|
||||
?>
|
||||
11
plugins/dynamix.docker.manager/event/started
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Only start if array has started in Normal operation mode
|
||||
if grep -q 'fsState="Started"' /var/local/emhttp/var.ini && grep -q 'startMode="Normal"' /var/local/emhttp/var.ini; then
|
||||
# Start Docker.io
|
||||
if [ -x /etc/rc.d/rc.docker ]; then
|
||||
echo "Starting Docker..."
|
||||
/etc/rc.d/rc.docker start | logger
|
||||
/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/dockerupdate.php | logger
|
||||
fi
|
||||
fi
|
||||
7
plugins/dynamix.docker.manager/event/stopping_svcs
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Shutdown Docker.io
|
||||
if [ -x /etc/rc.d/rc.docker ]; then
|
||||
echo "Stopping Docker..."
|
||||
/etc/rc.d/rc.docker stop | logger
|
||||
fi
|
||||
BIN
plugins/dynamix.docker.manager/icons/addcontainer.png
Normal file
|
After Width: | Height: | Size: 899 B |
BIN
plugins/dynamix.docker.manager/icons/default.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
plugins/dynamix.docker.manager/icons/docker.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
plugins/dynamix.docker.manager/icons/dockercontainers.png
Normal file
|
After Width: | Height: | Size: 853 B |
BIN
plugins/dynamix.docker.manager/icons/dockerrepositories.png
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
plugins/dynamix.docker.manager/icons/extraparams.png
Normal file
|
After Width: | Height: | Size: 733 B |
BIN
plugins/dynamix.docker.manager/icons/network.png
Normal file
|
After Width: | Height: | Size: 444 B |
BIN
plugins/dynamix.docker.manager/icons/paths.png
Normal file
|
After Width: | Height: | Size: 785 B |
BIN
plugins/dynamix.docker.manager/icons/preferences.png
Normal file
|
After Width: | Height: | Size: 695 B |
BIN
plugins/dynamix.docker.manager/icons/updatecontainer.png
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
plugins/dynamix.docker.manager/icons/vcard.png
Normal file
|
After Width: | Height: | Size: 533 B |
BIN
plugins/dynamix.docker.manager/images/dynamix.docker.manager.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
plugins/dynamix.docker.manager/images/plus.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
plugins/dynamix.docker.manager/images/question.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
plugins/dynamix.docker.manager/images/reload.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
plugins/dynamix.docker.manager/images/remove.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
plugins/dynamix.docker.manager/images/spacer.png
Normal file
|
After Width: | Height: | Size: 931 B |
940
plugins/dynamix.docker.manager/include/CreateDocker.php
Normal file
@@ -0,0 +1,940 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
ignore_user_abort(true);
|
||||
require_once("/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php");
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
|
||||
function prepareDir($dir){
|
||||
if (strlen($dir)){
|
||||
if ( ! is_dir($dir) && ! is_file($dir)){
|
||||
mkdir($dir, 0777, true);
|
||||
chown($dir, 'nobody');
|
||||
chgrp($dir, 'users');
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ContainerExist($container){
|
||||
global $DockerClient;
|
||||
|
||||
$all_containers = $DockerClient->getDockerContainers();
|
||||
if ( ! $all_containers) { return FALSE; }
|
||||
foreach ($all_containers as $ct) {
|
||||
if ($ct['Name'] == $container){
|
||||
return True;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return False;
|
||||
}
|
||||
|
||||
function trimLine($text){
|
||||
return preg_replace("/([\n^])[\s]+/", '$1', $text);
|
||||
}
|
||||
|
||||
function pullImage($image) {
|
||||
if (! preg_match("/:[\w]*$/i", $image)) $image .= ":latest";
|
||||
readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm");
|
||||
echo '<script>function add_to_id(m){$(".id:last").append(" "+m);}</script>';
|
||||
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>Pulling image: " . $image . "</legend><p class=\"logLine\" id=\"logBody\"></p></fieldset>');</script>";
|
||||
@flush();
|
||||
|
||||
$fp = stream_socket_client('unix:///var/run/docker.sock', $errno, $errstr);
|
||||
if ($fp === false) {
|
||||
echo "Couldn't create socket: [$errno] $errstr";
|
||||
return NULL;
|
||||
}
|
||||
$out="POST /images/create?fromImage=$image HTTP/1.1\r\nConnection: Close\r\n\r\n";
|
||||
fwrite($fp, $out);
|
||||
$cid = "";
|
||||
$cstatus="";
|
||||
$alltotals = [];
|
||||
while (!feof($fp)) {
|
||||
$cnt = json_decode( fgets($fp, 5000), TRUE );
|
||||
$id = ( isset( $cnt['id'] )) ? $cnt['id'] : "";
|
||||
if ($id != $cid && strlen($id)) {
|
||||
$cid = $id;
|
||||
$cstatus = "";
|
||||
echo "<script>addLog('IMAGE ID [". $id ."]: <span class=\"id\"></span>');</script>";
|
||||
@flush();
|
||||
}
|
||||
$status = ( isset( $cnt['status'] )) ? $cnt['status'] : "";
|
||||
if ($status != $cstatus && strlen($status)) {
|
||||
if ( isset($cnt['progressDetail']['total']) && $cnt['progressDetail']['total'] > 0 ) {
|
||||
$alltotals[$cnt['id']] = $cnt['progressDetail']['total'];
|
||||
}
|
||||
$cstatus = $status;
|
||||
echo "<script>add_to_id('". $status ."<span class=\"progress\"></span>.');</script>";
|
||||
@flush();
|
||||
}
|
||||
if ($status == "Downloading") {
|
||||
$total = $cnt['progressDetail']['total'];
|
||||
$current = $cnt['progressDetail']['current'];
|
||||
$alltotals[$cnt['id']] = $cnt['progressDetail']['current'];
|
||||
if ($total > 0) {
|
||||
$percentage = round(($current/$total) * 100);
|
||||
echo "<script>show_Prog(' ". $percentage ."% of " . sizeToHuman($total) . "');</script>\n";
|
||||
} else {
|
||||
// Docker must not know the total download size (http-chunked or something?)
|
||||
// just show the current download progress without the percentage
|
||||
echo "<script>show_Prog(' " . sizeToHuman($current) . "');</script>\n";
|
||||
}
|
||||
@flush();
|
||||
}
|
||||
}
|
||||
echo "<script>addLog('<br><b>TOTAL DATA PULLED:</b> " . sizeToHuman(array_sum($alltotals)) . "<span class=\"progress\"></span>');</script>\n";
|
||||
}
|
||||
|
||||
function sizeToHuman($size) {
|
||||
$units = ['B','KB','MB','GB'];
|
||||
$unitsIndex = 0;
|
||||
while ($size > 1024 && (($unitsIndex+1) < count($units))) {
|
||||
$size /= 1024;
|
||||
$unitsIndex++;
|
||||
}
|
||||
return ceil($size) . " " . $units[$unitsIndex];
|
||||
}
|
||||
|
||||
function xmlToCommand($xmlFile){
|
||||
global $var;
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadXML($xmlFile);
|
||||
|
||||
$Name = $doc->getElementsByTagName( "Name" )->item(0)->nodeValue;
|
||||
$cmdName = (strlen($Name)) ? '--name="' . $Name . '"' : "";
|
||||
$Privileged = $doc->getElementsByTagName( "Privileged" )->item(0)->nodeValue;
|
||||
$cmdPrivileged = (strtolower($Privileged) == 'true') ? '--privileged="true"' : "";
|
||||
$Repository = $doc->getElementsByTagName( "Repository" )->item(0)->nodeValue;
|
||||
$Mode = $doc->getElementsByTagName( "Mode" )->item(0)->nodeValue;
|
||||
$cmdMode = '--net="'.strtolower($Mode).'"';
|
||||
$BindTime = $doc->getElementsByTagName( "BindTime" )->item(0)->nodeValue;
|
||||
// $cmdBindTime = (strtolower($BindTime) == "true") ? '"/etc/localtime":"/etc/localtime":ro' : '';
|
||||
$cmdBindTime = (strtolower($BindTime) == "true") ? 'TZ="' . $var['timeZone'] . '"' : '';
|
||||
|
||||
$Ports = array('');
|
||||
foreach($doc->getElementsByTagName('Port') as $port){
|
||||
$ContainerPort = $port->getElementsByTagName( "ContainerPort" )->item(0)->nodeValue;
|
||||
if (! strlen($ContainerPort)){ continue; }
|
||||
$HostPort = $port->getElementsByTagName( "HostPort" )->item(0)->nodeValue;
|
||||
$Protocol = $port->getElementsByTagName( "Protocol" )->item(0)->nodeValue;
|
||||
$Ports[] = sprintf("%s:%s/%s", $HostPort, $ContainerPort, $Protocol);
|
||||
}
|
||||
|
||||
$Volumes = array('');
|
||||
foreach($doc->getElementsByTagName('Volume') as $volume){
|
||||
$ContainerDir = $volume->getElementsByTagName( "ContainerDir" )->item(0)->nodeValue;
|
||||
if (! strlen($ContainerDir)){ continue; }
|
||||
$HostDir = $volume->getElementsByTagName( "HostDir" )->item(0)->nodeValue;
|
||||
$DirMode = $volume->getElementsByTagName( "Mode" )->item(0)->nodeValue;
|
||||
$Volumes[] = sprintf( '"%s":"%s":%s', $HostDir, $ContainerDir, $DirMode);
|
||||
}
|
||||
|
||||
// if (strlen($cmdBindTime)) {
|
||||
// $Volumes[] = $cmdBindTime;
|
||||
// }
|
||||
|
||||
$Variables = array('');
|
||||
foreach($doc->getElementsByTagName('Variable') as $variable){
|
||||
$VariableName = $variable->getElementsByTagName( "Name" )->item(0)->nodeValue;
|
||||
if (! strlen($VariableName)){ continue; }
|
||||
$VariableValue = $variable->getElementsByTagName( "Value" )->item(0)->nodeValue;
|
||||
$Variables[] = sprintf('%s="%s"', $VariableName, $VariableValue);
|
||||
}
|
||||
|
||||
if (strlen($cmdBindTime)) {
|
||||
$Variables[] = $cmdBindTime;
|
||||
}
|
||||
|
||||
$templateExtraParams = '';
|
||||
if ( $doc->getElementsByTagName( "ExtraParams" )->length > 0 ) {
|
||||
$templateExtraParams = $doc->getElementsByTagName( "ExtraParams" )->item(0)->nodeValue;
|
||||
}
|
||||
|
||||
$cmd = sprintf('/plugins/dynamix.docker.manager/scripts/docker run -d %s %s %s %s %s %s %s %s', $cmdName, $cmdMode, $cmdPrivileged, implode(' -e ', $Variables),
|
||||
implode(' -p ', $Ports), implode(' -v ', $Volumes), $templateExtraParams, $Repository);
|
||||
$cmd = preg_replace('/\s+/', ' ', $cmd);
|
||||
|
||||
return array($cmd, $Name, $Repository);
|
||||
}
|
||||
|
||||
function addElement($doc, $el, $elName, $elVal){
|
||||
$node = $el->appendChild($doc->createElement($elName));
|
||||
$node->appendChild($doc->createTextNode($elVal));
|
||||
return $node;
|
||||
}
|
||||
|
||||
function postToXML($post, $setOwnership = FALSE){
|
||||
global $DockerUpdate;
|
||||
$doc = new DOMDocument('1.0', 'utf-8');
|
||||
$doc->preserveWhiteSpace = false;
|
||||
$doc->formatOutput = true;
|
||||
$root = $doc->createElement('Container');
|
||||
$root = $doc->appendChild($root);
|
||||
|
||||
$docName = $root->appendChild($doc->createElement('Name'));
|
||||
if ( isset( $post[ 'Description' ] )) addElement($doc, $root, 'Description', $post[ 'Description' ]);
|
||||
if ( isset( $post[ 'Registry' ] )) addElement($doc, $root, 'Registry', $post[ 'Registry' ]);
|
||||
$docRepository = $root->appendChild($doc->createElement('Repository'));
|
||||
$BindTime = $root->appendChild($doc->createElement('BindTime'));
|
||||
$Privileged = $root->appendChild($doc->createElement('Privileged'));
|
||||
$Environment = $root->appendChild($doc->createElement('Environment'));
|
||||
$docNetworking = $root->appendChild($doc->createElement('Networking'));
|
||||
$Data = $root->appendChild($doc->createElement('Data'));
|
||||
$Version = $root->appendChild($doc->createElement('Version'));
|
||||
$Mode = $docNetworking->appendChild($doc->createElement('Mode'));
|
||||
$Publish = $docNetworking->appendChild($doc->createElement('Publish'));
|
||||
$Name = preg_replace('/\s+/', '', $post["containerName"]);
|
||||
|
||||
// Editor Values
|
||||
if ( isset( $post[ 'WebUI' ] )) addElement($doc, $root, 'WebUI', $post[ 'WebUI' ]);
|
||||
if ( isset( $post[ 'Banner' ] )) addElement($doc, $root, 'Banner', $post[ 'Banner' ]);
|
||||
if ( isset( $post[ 'Icon' ] )) addElement($doc, $root, 'Icon', $post[ 'Icon' ]);
|
||||
|
||||
if ( isset( $post[ 'ExtraParams' ] )) addElement($doc, $root, 'ExtraParams', $post[ 'ExtraParams' ]);
|
||||
|
||||
$docName->appendChild($doc->createTextNode($Name));
|
||||
$docRepository->appendChild($doc->createTextNode($post["Repository"]));
|
||||
$BindTime->appendChild($doc->createTextNode((strtolower($post["BindTime"]) == 'on') ? 'true' : 'false'));
|
||||
$Privileged->appendChild($doc->createTextNode((strtolower($post["Privileged"]) == 'on') ? 'true' : 'false'));
|
||||
$Mode->appendChild($doc->createTextNode(strtolower($post["NetworkType"])));
|
||||
|
||||
for ($i = 0; $i < count($post["hostPort"]); $i++){
|
||||
if (! strlen($post["containerPort"][$i])) { continue; }
|
||||
$protocol = $post["portProtocol"][$i];
|
||||
$Port = $Publish->appendChild($doc->createElement('Port'));
|
||||
$HostPort = $Port->appendChild($doc->createElement('HostPort'));
|
||||
$ContainerPort = $Port->appendChild($doc->createElement('ContainerPort'));
|
||||
$Protocol = $Port->appendChild($doc->createElement('Protocol'));
|
||||
$HostPort->appendChild($doc->createTextNode(trim($post["hostPort"][$i])));
|
||||
$ContainerPort->appendChild($doc->createTextNode($post["containerPort"][$i]));
|
||||
$Protocol->appendChild($doc->createTextNode($protocol));
|
||||
}
|
||||
|
||||
for ($i = 0; $i < count($post["VariableName"]); $i++){
|
||||
if (! strlen($post["VariableName"][$i])) { continue; }
|
||||
$Variable = $Environment->appendChild($doc->createElement('Variable'));
|
||||
$VariableName = $Variable->appendChild($doc->createElement('Name'));
|
||||
$VariableValue = $Variable->appendChild($doc->createElement('Value'));
|
||||
$VariableName->appendChild($doc->createTextNode(trim($post["VariableName"][$i])));
|
||||
$VariableValue->appendChild($doc->createTextNode(trim($post["VariableValue"][$i])));
|
||||
}
|
||||
|
||||
for ($i = 0; $i < count($post["hostPath"]); $i++){
|
||||
if (! strlen($post["hostPath"][$i])) { continue; }
|
||||
if (! strlen($post["containerPath"][$i])) { continue; }
|
||||
$tmpMode = $post["hostWritable"][$i];
|
||||
if ($setOwnership){
|
||||
prepareDir($post["hostPath"][$i]);
|
||||
}
|
||||
$Volume = $Data->appendChild($doc->createElement('Volume'));
|
||||
$HostDir = $Volume->appendChild($doc->createElement('HostDir'));
|
||||
$ContainerDir = $Volume->appendChild($doc->createElement('ContainerDir'));
|
||||
$DirMode = $Volume->appendChild($doc->createElement('Mode'));
|
||||
$HostDir->appendChild($doc->createTextNode($post["hostPath"][$i]));
|
||||
$ContainerDir->appendChild($doc->createTextNode($post["containerPath"][$i]));
|
||||
$DirMode->appendChild($doc->createTextNode($tmpMode));
|
||||
}
|
||||
|
||||
$currentVersion = $DockerUpdate->getRemoteVersion($post["Registry"], $post["Repository"]);
|
||||
$Version->appendChild($doc->createTextNode($currentVersion));
|
||||
|
||||
return $doc->saveXML();
|
||||
}
|
||||
|
||||
if ($_POST){
|
||||
|
||||
$postXML = postToXML($_POST, TRUE);
|
||||
|
||||
// Get the command line
|
||||
list($cmd, $Name, $Repository) = xmlToCommand($postXML);
|
||||
|
||||
// Saving the generated configuration file.
|
||||
$userTmplDir = $dockerManPaths['templates-user'];
|
||||
if(is_dir($userTmplDir) === FALSE){
|
||||
mkdir($userTmplDir, 0777, true);
|
||||
}
|
||||
|
||||
if(strlen($Name)) {
|
||||
$filename = sprintf('%s/my-%s.xml', $userTmplDir, $Name);
|
||||
file_put_contents($filename, $postXML);
|
||||
}
|
||||
|
||||
// Pull image
|
||||
pullImage($Repository);
|
||||
|
||||
// Remove existing container
|
||||
if (ContainerExist($Name)){
|
||||
$_GET['cmd'] = "/plugins/dynamix.docker.manager/scripts/docker rm -f $Name";
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
}
|
||||
|
||||
// Remove old container if renamed
|
||||
$existing = isset($_POST['existingContainer']) ? $_POST['existingContainer'] : FALSE;
|
||||
if ($existing && ContainerExist($existing)){
|
||||
$_GET['cmd'] = "/plugins/dynamix.docker.manager/scripts/docker rm -f $existing";
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
}
|
||||
|
||||
// Injecting the command in $_GET variable and executing.
|
||||
$_GET['cmd'] = $cmd;
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
|
||||
$DockerTemplates->removeInfo($Name);
|
||||
$DockerUpdate->syncVersions($Name);
|
||||
|
||||
echo '<center><input type="button" value="Done" onclick="done()"></center><br>';
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
if ($_GET['updateContainer']){
|
||||
foreach ($_GET['ct'] as $value) {
|
||||
$Name = urldecode($value);
|
||||
$tmpl = $DockerTemplates->getUserTemplate($Name);
|
||||
|
||||
if (! $tmpl){
|
||||
echo 'Configuration not found. Was this container created using this plugin?';
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = new DOMDocument('1.0', 'utf-8');
|
||||
$doc->preserveWhiteSpace = false;
|
||||
$doc->load( $tmpl );
|
||||
$doc->formatOutput = TRUE;
|
||||
|
||||
$Repository = $doc->getElementsByTagName( "Repository" )->item(0)->nodeValue;
|
||||
$Registry = $doc->getElementsByTagName( "Registry" )->item(0)->nodeValue;
|
||||
|
||||
readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm");
|
||||
echo "<script>addLog('<p>Preparing to update: " . $Repository . "</p>');</script>";
|
||||
@flush();
|
||||
|
||||
$CurrentVersion = $DockerUpdate->getRemoteVersion($Registry, $Repository);
|
||||
|
||||
if ($CurrentVersion){
|
||||
if ( $doc->getElementsByTagName( "Version" )->length == 0 ) {
|
||||
$root = $doc->getElementsByTagName( "Container" )->item(0);
|
||||
$Version = $root->appendChild($doc->createElement('Version'));
|
||||
} else {
|
||||
$Version = $doc->getElementsByTagName( "Version" )->item(0);
|
||||
}
|
||||
$Version->nodeValue = $CurrentVersion;
|
||||
|
||||
file_put_contents($tmpl, $doc->saveXML());
|
||||
}
|
||||
|
||||
$oldContainerID = $DockerClient->getImageID($Repository);
|
||||
list($cmd, $Name, $Repository) = xmlToCommand($doc->saveXML());
|
||||
|
||||
// Pull image
|
||||
flush();
|
||||
pullImage($Repository);
|
||||
|
||||
$_GET['cmd'] = "/plugins/dynamix.docker.manager/scripts/docker rm -f $Name";
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
|
||||
$_GET['cmd'] = $cmd;
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
|
||||
$DockerTemplates->removeInfo($Name);
|
||||
$newContainerID = $DockerClient->getImageID($Repository);
|
||||
if ( $oldContainerID and $oldContainerID != $newContainerID){
|
||||
$_GET['cmd'] = sprintf("/plugins/dynamix.docker.manager/scripts/docker rmi %s", $oldContainerID);
|
||||
include($dockerManPaths['plugin'] . "/include/Exec.php");
|
||||
}
|
||||
|
||||
$DockerTemplates->removeInfo($Name);
|
||||
$DockerUpdate->syncVersions($Name);
|
||||
}
|
||||
|
||||
echo '<center><input type="button" value="Done" onclick="window.parent.jQuery(\'#iframe-popup\').dialog(\'close\');"></center><br>';
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
if($_GET['rmTemplate']){
|
||||
unlink($_GET['rmTemplate']);
|
||||
}
|
||||
|
||||
if($_GET['xmlTemplate']){
|
||||
list($xmlType, $xmlTemplate) = split(':', urldecode($_GET['xmlTemplate']));
|
||||
if(is_file($xmlTemplate)){
|
||||
$doc = new DOMDocument();
|
||||
$doc->load($xmlTemplate);
|
||||
|
||||
$templateRepository = $doc->getElementsByTagName( "Repository" )->item(0)->nodeValue;
|
||||
$templateName = $doc->getElementsByTagName( "Name" )->item(0)->nodeValue;
|
||||
$Registry = $doc->getElementsByTagName( "Registry" )->item(0)->nodeValue;
|
||||
$templatePrivileged = (strtolower($doc->getElementsByTagName( "Privileged" )->item(0)->nodeValue) == 'true') ? 'checked' : "";
|
||||
$templateMode = $doc->getElementsByTagName( "Mode" )->item(0)->nodeValue;;
|
||||
$readonly = ($xmlType == 'default') ? 'readonly="readonly"' : '';
|
||||
$required = ($xmlType == 'default') ? 'required' : '';
|
||||
$disabled = ($xmlType == 'default') ? 'disabled="disabled"' : '';
|
||||
|
||||
if ( $doc->getElementsByTagName( "Description" )->length > 0 ) {
|
||||
$templateDescription = $doc->getElementsByTagName( "Description" )->item(0)->nodeValue;
|
||||
} else {
|
||||
$templateDescription = $DockerTemplates->getTemplateValue($templateRepository, "Description", "default");
|
||||
}
|
||||
|
||||
if ( $doc->getElementsByTagName( "Registry" )->length > 0 ) {
|
||||
$templateRegistry = $doc->getElementsByTagName( "Registry" )->item(0)->nodeValue;
|
||||
} else {
|
||||
$templateRegistry = $DockerTemplates->getTemplateValue($templateRepository, "Registry", "default");
|
||||
}
|
||||
|
||||
if ( $doc->getElementsByTagName( "WebUI" )->length > 0 ) {
|
||||
$templateWebUI = $doc->getElementsByTagName( "WebUI" )->item(0)->nodeValue;
|
||||
} else {
|
||||
$templateWebUI = $DockerTemplates->getTemplateValue($templateRepository, "WebUI", "default");
|
||||
}
|
||||
|
||||
if ( $doc->getElementsByTagName( "Banner" )->length > 0 ) {
|
||||
$templateBanner = $doc->getElementsByTagName( "Banner" )->item(0)->nodeValue;
|
||||
} else {
|
||||
$templateBanner = $DockerTemplates->getTemplateValue($templateRepository, "Banner", "default");
|
||||
}
|
||||
|
||||
if ( $doc->getElementsByTagName( "Icon" )->length > 0 ) {
|
||||
$templateIcon = $doc->getElementsByTagName( "Icon" )->item(0)->nodeValue;
|
||||
} else {
|
||||
$templateIcon = $DockerTemplates->getTemplateValue($templateRepository, "Icon", "default");
|
||||
}
|
||||
|
||||
if ( $doc->getElementsByTagName( "ExtraParams" )->length > 0 ) {
|
||||
$templateExtraParams = $doc->getElementsByTagName( "ExtraParams" )->item(0)->nodeValue;
|
||||
} else {
|
||||
$templateExtraParams = $DockerTemplates->getTemplateValue($templateRepository, "ExtraParams", "default");
|
||||
}
|
||||
|
||||
$templateDescription = stripslashes($templateDescription);
|
||||
$templateRegistry = stripslashes($templateRegistry);
|
||||
$templateWebUI = stripslashes($templateWebUI);
|
||||
$templateBanner = stripslashes($templateBanner);
|
||||
$templateIcon = stripslashes($templateIcon);
|
||||
$templateExtraParams = stripslashes($templateExtraParams);
|
||||
|
||||
$templateDescBox = preg_replace('/\[/', '<', $templateDescription);
|
||||
$templateDescBox = preg_replace('/\]/', '>', $templateDescBox);
|
||||
|
||||
$templatePorts = '';
|
||||
$row = '
|
||||
<tr id="portNum%s">
|
||||
<td>
|
||||
<input type="number" min="1" max="65535" name="containerPort[]" value="%s" class="textPort" %s title="Set the port your app uses inside the container."/>
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" min="1" max="65535" name="hostPort[]" value="%s" class="textPort" %s title="Set the port you use to interact with the app."/>
|
||||
</td>
|
||||
<td>
|
||||
<select name="portProtocol[]" class="narrow">
|
||||
<option value="tcp">TCP</option>
|
||||
<option value="udp" %s>UDP</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="button" value="Remove" onclick="removePort(%s);" %s/>
|
||||
</td>
|
||||
</tr>';
|
||||
|
||||
$i = 1;
|
||||
foreach($doc->getElementsByTagName('Port') as $port){
|
||||
$j = $i + 100;
|
||||
$ContainerPort = $port->getElementsByTagName( "ContainerPort" )->item(0)->nodeValue;
|
||||
if (! strlen($ContainerPort)){ continue; }
|
||||
$HostPort = $port->getElementsByTagName( "HostPort" )->item(0)->nodeValue;
|
||||
$Protocol = $port->getElementsByTagName( "Protocol" )->item(0)->nodeValue;
|
||||
$select = ($Protocol == 'udp') ? 'selected' : '';
|
||||
$templatePorts .= sprintf($row, $j, htmlspecialchars($ContainerPort), $readonly, htmlspecialchars($HostPort), $required, $select, $j, $disabled);
|
||||
$i++;
|
||||
}
|
||||
|
||||
$templateVolumes = '';
|
||||
$row = '
|
||||
<tr id="pathNum%s">
|
||||
<td>
|
||||
<input type="text" name="containerPath[]" value="%s" class="textPath" onclick="hideBrowser(%s);" %s title="The directory your app uses inside the container. Ex: /config"/>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="hostPath%s" name="hostPath[]" value="%s" class="textPath" onclick="toggleBrowser(%s);" %s title="The directory in your array the app have access to. Ex: /mnt/user/Movies"/>
|
||||
<div id="fileTree%s" class="textarea fileTree"></div>
|
||||
</td>
|
||||
<td>
|
||||
<select name="hostWritable[]">
|
||||
<option value="rw">Read/Write</option>
|
||||
<option value="ro" %s>Read Only</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input type="button" value="Remove" onclick="removePath(%s);" %s/>
|
||||
</td>
|
||||
</tr>';
|
||||
|
||||
$i = 1;
|
||||
foreach($doc->getElementsByTagName('Volume') as $volume){
|
||||
$j = $i + 100;
|
||||
$ContainerDir = $volume->getElementsByTagName( "ContainerDir" )->item(0)->nodeValue;
|
||||
if (! strlen($ContainerDir)){ continue; }
|
||||
$HostDir = $volume->getElementsByTagName( "HostDir" )->item(0)->nodeValue;
|
||||
$Mode = $volume->getElementsByTagName( "Mode" )->item(0)->nodeValue;
|
||||
$Mode = ($Mode == "ro") ? "selected" : '';
|
||||
$templateVolumes .= sprintf($row, $j, htmlspecialchars($ContainerDir), $j, $readonly, $j, htmlspecialchars($HostDir), $j, $required, $j, $Mode, $j, $disabled);
|
||||
$i++;
|
||||
}
|
||||
|
||||
$templateVariables = '';
|
||||
$row = '
|
||||
<tr id="varNum%s">
|
||||
<td>
|
||||
<input type="text" name="VariableName[]" value="%s" class="textEnv" %s/>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="VariableValue[]" value="%s" class="textEnv" %s/>
|
||||
<input type="button" value="Remove" onclick="removeEnv(%s);" %s/>
|
||||
</td>
|
||||
</tr>';
|
||||
|
||||
$i = 1;
|
||||
foreach($doc->getElementsByTagName('Variable') as $variable){
|
||||
$j = $i + 100;
|
||||
$VariableName = $variable->getElementsByTagName( "Name" )->item(0)->nodeValue;
|
||||
if (! strlen($VariableName)){ continue; }
|
||||
$VariableValue = $variable->getElementsByTagName( "Value" )->item(0)->nodeValue;
|
||||
$templateVariables .= sprintf($row, $j, htmlspecialchars($VariableName), $readonly, htmlspecialchars($VariableValue), $required, $j, $disabled);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$showAdditionalInfo = true;
|
||||
?>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/font-awesome.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css" media="screen">
|
||||
<style>
|
||||
body{-webkit-overflow-scrolling:touch;}
|
||||
.fileTree{width:240px;height:150px;overflow:scroll;position:absolute;z-index:100;display:none;}
|
||||
#TemplateSelect{width:255px;}
|
||||
option.list{padding:0 0 0 7px;font-size:11px;}
|
||||
optgroup.bold{font-weight:bold;font-size:12px;margin-top:5px;}
|
||||
optgroup.title{background-color:#625D5D;color:#FFFFFF;text-align:center;margin-top:10px;}
|
||||
input.textPath{width:240px;}
|
||||
input.textTemplate,textarea.textTemplate{width:555px;}
|
||||
input.textEnv{width:230px;}
|
||||
input.textPort{width:100px;}
|
||||
table.pathTab{width:700px;}
|
||||
table.portRows{width:400px;}
|
||||
table.envTab{width:620px;}
|
||||
table.Preferences{width:100%;}
|
||||
.show{display:block;}
|
||||
table td{font-size:14px;vertical-align:bottom;text-align:left;}
|
||||
.inline_help{font-size:12px;}
|
||||
.desc{padding:6px;line-height:15px;width:inherit;}
|
||||
.toggleMode{cursor:pointer;color:#a3a3a3;letter-spacing:0;padding:0;padding-right:10px;font-family:arimo;font-size:12px;line-height:1.3em;font-weight:bold;margin:0;}
|
||||
.toggleMode:hover,.toggleMode:focus,.toggleMode:active,.toggleMode .active{color:#625D5D;}
|
||||
</style>
|
||||
<form method="GET" id="formTemplate">
|
||||
<input type="hidden" id="xmlTemplate" name="xmlTemplate" value="" />
|
||||
<input type="hidden" id="rmTemplate" name="rmTemplate" value="" />
|
||||
</form>
|
||||
|
||||
<div id="canvas" style="z-index:1;">
|
||||
<form method="post" id="createContainer">
|
||||
<table class="Preferences">
|
||||
<? if($xmlType == "edit"):
|
||||
if (ContainerExist($templateName)): echo "<input type='hidden' name='existingContainer' value='${templateName}'>\n"; endif;
|
||||
else:?>
|
||||
<tr>
|
||||
<td style="width: 150px;">Template:</td>
|
||||
<td >
|
||||
<select id="TemplateSelect" size="1">
|
||||
<option value="">Select a template</option>
|
||||
<?
|
||||
$rmadd = '';
|
||||
$all_templates = array();
|
||||
$all_templates['user'] = $DockerTemplates->getTemplates("user");
|
||||
$all_templates['default'] = $DockerTemplates->getTemplates("default");
|
||||
foreach ($all_templates as $key => $templates) {
|
||||
if ($key == "default") $title = "Default templates";
|
||||
if ($key == "user") $title = "User defined templates";
|
||||
printf("\t\t\t\t\t<optgroup class=\"title bold\" label=\"[ %s ]\"></optgroup>\n", htmlspecialchars($title));
|
||||
$prefix = '';
|
||||
foreach ($templates as $value){
|
||||
if ($value["prefix"] != $prefix) {
|
||||
if ($prefix != '') {
|
||||
printf("\t\t\t\t\t</optgroup>\n");
|
||||
}
|
||||
$prefix = $value["prefix"];
|
||||
printf("\t\t\t\t\t<optgroup class=\"bold\" label=\"[ %s ]\">\n", htmlspecialchars($prefix));
|
||||
}
|
||||
//$value['name'] = str_replace("my-", '', $value['name']);
|
||||
$selected = (isset($xmlTemplate) && $value['path'] == $xmlTemplate) ? ' selected ' : '';
|
||||
if ($selected && ($key == "default")) $showAdditionalInfo = false;
|
||||
if (strlen($selected) && $key == 'user' ){ $rmadd = $value['path']; }
|
||||
printf("\t\t\t\t\t\t<option class=\"list\" value=\"%s:%s\" {$selected} >%s</option>\n", htmlspecialchars($key), htmlspecialchars($value['path']), htmlspecialchars($value['name']));
|
||||
}
|
||||
printf("\t\t\t\t\t</optgroup>\n");
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<? if (!empty($rmadd)) {
|
||||
echo "<a onclick=\"rmTemplate('" . addslashes($rmadd) . "');\" style=\"cursor:pointer;\"><img src=\"/plugins/dynamix.docker.manager/images/remove.png\" title=\"" . htmlspecialchars($rmadd) . "\" width=\"30px\"></a>";
|
||||
}?>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="inline_help" style="display: none">
|
||||
<td colspan="2">
|
||||
<blockquote class="inline_help">
|
||||
<p>Templates are a quicker way to setting up Docker Containers on your unRAID server. There are two types of templates:</p>
|
||||
|
||||
<p>
|
||||
<b>Default templates</b><br>
|
||||
When valid repositories are added to your Docker Repositories page, they will appear in a section on this drop down for you to choose (master categorized by author, then by application template). After selecting a default template, the page will populate with new information about the application in the Description field, and will typically provide instructions for how to setup the container. Select a default template when it is the first time you are configuring this application.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>User-defined templates</b><br>
|
||||
Once you've added an application to your system through a Default template, the settings you specified are saved to your USB flash device to make it easy to rebuild your applications in the event an upgrade were to fail or if another issue occurred. To rebuild, simply select the previously loaded application from the User-defined list and all the settings for the container will appear populated from your previous setup. Clicking create will redownload the necessary files for the application and should restore you to a working state. To delete a User-defined template, select it from the list above and click the red X to the right of it.
|
||||
</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<?endif;?>
|
||||
<?if(!empty($templateDescBox)){?>
|
||||
<tr>
|
||||
<td style="vertical-align: top;">Description:</td>
|
||||
<td>
|
||||
<div class="textarea desc">
|
||||
<?
|
||||
echo $templateDescBox;
|
||||
if(!empty($Registry)){
|
||||
echo "<br><br>Container Page: <a href=\"" . htmlspecialchars($Registry) . "\" target=\"_blank\">" . htmlspecialchars($Registry) . "</a>";
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?};?>
|
||||
<tr>
|
||||
<td>Name:</td>
|
||||
|
||||
<td><input type="text" name="containerName" class="textPath" value="<? if(isset($templateName)){ echo htmlspecialchars(trim($templateName));} ?>"></td>
|
||||
</tr>
|
||||
<tr class="inline_help" style="display: none">
|
||||
<td colspan="2">
|
||||
<blockquote class="inline_help">
|
||||
<p>Give the container a name or leave it as default.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="additionalFields" style="display:none">
|
||||
<td>Repository:</td>
|
||||
|
||||
<td><input type="text" name="Repository" class="textPath" value="<? if(isset($templateRepository)){ echo htmlspecialchars(trim($templateRepository));} ?>"></td>
|
||||
</tr>
|
||||
<tr class="additionalFields" style="display:none">
|
||||
<td colspan="2" class="inline_help" style="display:none">
|
||||
<blockquote class="inline_help">
|
||||
<p>The repository for the application on the Docker Registry. Format of authorname/appname. Optionally you can add a : after appname and request a specific version for the container image.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Network type:</td>
|
||||
|
||||
<td><select id="NetworkType" name="NetworkType" class="narrow">
|
||||
<? foreach (array('bridge', 'host', 'none') as $value) {
|
||||
$selected = ($templateMode == $value) ? "selected" : "";
|
||||
echo "<option value=\"{$value}\" {$selected}>".ucwords($value)."</option>";
|
||||
}?>
|
||||
</select></td>
|
||||
</tr>
|
||||
<tr class="inline_help" style="display:none">
|
||||
<td colspan="2">
|
||||
<blockquote class="inline_help">
|
||||
<p>If the Bridge type is selected, the application’s network access will be restricted to only communicating on the ports specified in the port mappings section. If the Host type is selected, the application will be given access to communicate using any port on the host that isn’t already mapped to another in-use application/service. Generally speaking, it is recommended to leave this setting to its default value as specified per application template.</p>
|
||||
<p>IMPORTANT NOTE: If adjusting port mappings, do not modify the settings for the Container port as only the Host port can be adjusted.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="additionalFields" style="display:none">
|
||||
<td>Privileged:</td>
|
||||
|
||||
<td><input type="checkbox" name="Privileged" <?if(isset($templatePrivileged)) {echo $templatePrivileged;}?>></td>
|
||||
</tr>
|
||||
<tr class="additionalFields" style="display:none">
|
||||
<td colspan="2" class="inline_help" style="display:none">
|
||||
<blockquote class="inline_help">
|
||||
<p>For containers that require the use of host-device access directly or need full exposure to host capabilities, this option will need to be selected. For more information, see this link: <a href="https://docs.docker.com/reference/run/#runtime-privilege-linux-capabilities-and-lxc-configuration" target="_blank">https://docs.docker.com/reference/run/#runtime-privilege-linux-capabilities-and-lxc-configuration</a></p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="additionalFields" style="display:none">
|
||||
<td>Bind time:</td>
|
||||
|
||||
<td><input type="checkbox" name="BindTime" checked></td>
|
||||
</tr>
|
||||
<tr class="additionalFields" style="display:none">
|
||||
<td colspan="2" class="inline_help" style="display:none">
|
||||
<blockquote class="inline_help">
|
||||
<p>There's two ways of bind time: one is to mount /etc/localtime to the container (chosen method); the other is to set a variable TZ with the time zone.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id="title">
|
||||
<span class="left"><img src="/plugins/dynamix.docker.manager/icons/paths.png" class="icon">Volume Mappings</span>
|
||||
</div>
|
||||
|
||||
<table id="pathRows" class="pathTab">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Container volume:</td>
|
||||
<td>Host path:</td>
|
||||
<td>Access:</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="containerPath1" name="containerPath[]" class="textPath" onfocus="hideBrowser(1);" title="The directory your app uses inside the container. Ex: /config">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="hostPath1" name="hostPath[]" class="textPath" autocomplete="off" onclick="toggleBrowser(1);" title="The directory in your array the app have access to. Ex: /mnt/user/Movies">
|
||||
<div id="fileTree1" class="textarea fileTree"></div>
|
||||
</td>
|
||||
<td><select id="hostWritable1" name="hostWritable[]">
|
||||
<option value="rw" selected="selected">Read/Write</option>
|
||||
<option value="ro">Read Only</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input onclick="addPath(this.form);" type="button" value="Add path" class="btn">
|
||||
</td>
|
||||
</tr>
|
||||
<?if(isset($templateVolumes)){echo $templateVolumes;}?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<blockquote class="inline_help">
|
||||
<p>Applications can be given read and write access to your data by mapping a directory path from the container to a directory path on the host. When looking at the volume mappings section, the Container volume represents the path from the container that will be mapped. The Host path represents the path the Container volume will map to on your unRAID system. All applications should require at least one volume mapping to store application metadata (e.g., media libraries, application settings, user profile data, etc.). Clicking inside these fields provides a "picker" that will let you navigate to where the mapping should point. Additional mappings can be manually created by clicking the Add Path button. Most applications will need you to specify additional mappings in order for the application to interact with other data on the system (e.g., with Plex Media Server, you should specify an additional mapping to give it access to your media files). It is important that when naming Container volumes that you specify a path that won’t conflict with already existing folders present in the container. If unfamiliar with Linux, using a prefix such as "unraid_" for the volume name is a safe bet (e.g., "/unraid_media" is a valid Container volume name).</p>
|
||||
</blockquote>
|
||||
|
||||
<div id="titlePort">
|
||||
<div id="title">
|
||||
<span class="left"><img src="/plugins/dynamix.docker.manager/icons/network.png" class="icon">Port Mappings</span>
|
||||
</div>
|
||||
|
||||
<table id="portRows" class="portRows">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Container port:</td>
|
||||
<td>Host port:</td>
|
||||
<td>Protocol:</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<input type="number" min="1" max="65535" id="containerPort1" name="containerPort[]" class="textPort" title="Set the port your app uses inside the container.">
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" min="1" max="65535" id="hostPort1" name="hostPort[]" class="textPort" title="Set the port you use to interact with the app.">
|
||||
</td>
|
||||
<td>
|
||||
<select id="portProtocol1" name="portProtocol[]" class="narrow">
|
||||
<option value="tcp" selected="selected">TCP</option>
|
||||
<option value="udp">UDP</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<input onclick="addPort(this.form);" type="button" value="Add port" class="btn">
|
||||
</td>
|
||||
</tr>
|
||||
<?if(isset($templatePorts)){echo $templatePorts;}?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<blockquote class="inline_help">
|
||||
<p>When the network type is set to Bridge, you will be given the option of customizing what ports the container will use. While applications may be configured to talk to a specific port by default, we can remap those to different ports on our host with Docker. This means that while three different apps may all want to use port 8000, we can map each app to a unique port on the host (e.g., 8000, 8001, and 8002). When the network type is set to Host, the container will be allowed to use any available port on your system. Additional port mappings can be created, similar to Volumes, although this is not typically necessary when working with templates as port mappings should already be specified.</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<div class="additionalFields" style="display:none">
|
||||
<div id="title">
|
||||
<span class="left"><img src="/plugins/dynamix.docker.manager/icons/default.png" class="icon">Environment Variables</span>
|
||||
</div>
|
||||
|
||||
<table id="envRows" class="envTab">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Variable Name:</td>
|
||||
<td>Variable Value:</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="text" id="VariableName1" name="VariableName[]" class="textEnv">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="VariableValue1" name="VariableValue[]" class="textEnv">
|
||||
<input onclick="addEnv(this.form);" type="button" value="Add Variable">
|
||||
</td>
|
||||
</tr>
|
||||
<?if(isset($templateVariables)){echo $templateVariables;}?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<blockquote class="inline_help">
|
||||
<p>For details, see this link: <a href="https://docs.docker.com/reference/run/#env-environment-variables" target="_blank">https://docs.docker.com/reference/run/#env-environment-variables</a></p>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<div <?= empty($templateExtraParams) ? 'class="additionalFields" style="display:none"' : '' ?>>
|
||||
<div id="title">
|
||||
<span class="left"><img src="/plugins/dynamix.docker.manager/icons/extraparams.png" class="icon">Extra Parameters</span>
|
||||
</div>
|
||||
|
||||
<input type="text" name="ExtraParams" class="textTemplate" value="<? if(isset($templateExtraParams)){ echo htmlspecialchars(trim($templateExtraParams));} ?>"/>
|
||||
|
||||
<blockquote class="inline_help">
|
||||
<p>If you wish to append additional commands to your Docker container at run-time, you can specify them here. For example, if you wish to pin an application to live on a specific CPU core, you can enter "--cpuset=0" in this field. Change 0 to the core # on your system (starting with 0). You can pin multiple cores by separation with a comma or a range of cores by separation with a dash. For all possible Docker run-time commands, see here: <a href="https://docs.docker.com/reference/run/" target="_blank">https://docs.docker.com/reference/run/</a></p>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<div <?= $showAdditionalInfo ? 'class="additionalFields"' : '' ?> style="display:none">
|
||||
<div id="title">
|
||||
<span class="left"><img src="/plugins/dynamix.docker.manager/icons/vcard.png" class="icon">Additional Fields</span>
|
||||
</div>
|
||||
<table class="Template">
|
||||
<tr>
|
||||
<td style="width: 150px;">Docker Hub URL:</td>
|
||||
<td>
|
||||
<input type="url" name="Registry" class="textTemplate" placeholder="e.g. https://registry.hub.docker.com/u/username/image" value="<? if(isset($templateRegistry)){ echo htmlspecialchars(trim($templateRegistry));} ?>"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="inline_help" style="display: none">
|
||||
<td colspan="2">
|
||||
<blockquote class="inline_help">
|
||||
<p>The path to the container's repository location on the Docker Hub.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 150px;">WebUI:</td>
|
||||
<td>
|
||||
<input type="text" name="WebUI" class="textTemplate" placeholder="e.g. http://[IP]:[PORT:8080]/" value="<? if(isset($templateWebUI)){ echo htmlspecialchars(trim($templateWebUI));} ?>"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="inline_help" style="display: none">
|
||||
<td colspan="2">
|
||||
<blockquote class="inline_help">
|
||||
<p>When you click on an application icon from the Docker Containers page, the WebUI option will link to the path in this field. Use [IP} to identify the IP of your host and [PORT:####] replacing the #'s for your port.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 150px;">Banner:</td>
|
||||
<td>
|
||||
<input type="url" name="Banner" class="textTemplate" placeholder="e.g. http://address.to/banner.png" value="<? if(isset($templateBanner)){ echo htmlspecialchars(trim($templateBanner));} ?>"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="inline_help" style="display: none">
|
||||
<td colspan="2">
|
||||
<blockquote class="inline_help">
|
||||
<p>Link to the banner image for your application (only displayed on dashboard if Show Dashboard apps under Display Settings is set to Banners).</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 150px;">Icon:</td>
|
||||
<td>
|
||||
<input type="url" name="Icon" class="textTemplate" placeholder="e.g. http://address.to/icon.png" value="<? if(isset($templateIcon)){ echo htmlspecialchars(trim($templateIcon));} ?>"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="inline_help" style="display: none">
|
||||
<td colspan="2">
|
||||
<blockquote class="inline_help">
|
||||
<p>Link to the icon image for your application (only displayed on dashboard if Show Dashboard apps under Display Settings is set to Icons).</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width: 150px; vertical-align: top;">Description:</td>
|
||||
<td>
|
||||
<textarea name="Description" rows="10" cols="71" class="textTemplate"><? if(isset($templateDescription)){ echo htmlspecialchars(trimLine($templateDescription));} ?></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="inline_help" style="display: none">
|
||||
<td colspan="2">
|
||||
<blockquote class="inline_help">
|
||||
<p>A description for the application container. Supports basic HTML mark-up.</p>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<input type="submit" value="<?= ($xmlType != 'edit') ? 'Create' : 'Save' ?>">
|
||||
<input type="button" value="Cancel" onclick="done()">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/webGui/javascript/jquery.filetree.js"></script>
|
||||
<script src="/plugins/dynamix.docker.manager/javascript/addDocker.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
$(document).mouseup(function (e) {
|
||||
var container = $(".fileTree");
|
||||
if (!container.is(e.target) && container.has(e.target).length === 0) {
|
||||
container.slideUp('fast', function () {
|
||||
$(this).html("");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if ($("#NetworkType").val() != 'bridge') {
|
||||
$("#titlePort").css({'display': "none"});
|
||||
}
|
||||
$("#NetworkType").change(function() {
|
||||
if ($(this).val() != "bridge") {
|
||||
$("#titlePort").css({'display': "none"});
|
||||
} else {
|
||||
$("#titlePort").css({'display': "block"});
|
||||
}
|
||||
});
|
||||
$("#TemplateSelect").change(function() {
|
||||
if ($(this).val() !== "") {
|
||||
$("#xmlTemplate").val($(this).val());
|
||||
$("#formTemplate").submit();
|
||||
}
|
||||
});
|
||||
$("#toggleMode").addClass("fa-toggle-off");
|
||||
$("#toggleMode").removeClass("fa-toggle-on");
|
||||
$("#toggleMode").closest("div").fadeIn('slow');
|
||||
});
|
||||
</script>
|
||||
666
plugins/dynamix.docker.manager/include/DockerClient.php
Normal file
@@ -0,0 +1,666 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
|
||||
## BETA 11
|
||||
$dockerManPaths = array(
|
||||
'plugin' => '/usr/local/emhttp/plugins/dynamix.docker.manager',
|
||||
'autostart-file' => '/var/lib/docker/unraid-autostart',
|
||||
'template-repos' => '/boot/config/plugins/dockerMan/template-repos',
|
||||
'templates-user' => '/boot/config/plugins/dockerMan/templates-user',
|
||||
'templates-storage' => '/boot/config/plugins/dockerMan/templates',
|
||||
'images-ram' => '/usr/local/emhttp/state/plugins/dynamix.docker.manager/images',
|
||||
'images-storage' => '/boot/config/plugins/dockerMan/images',
|
||||
'webui-info' => '/usr/local/emhttp/state/plugins/dynamix.docker.manager/docker.json',
|
||||
'update-status' => '/var/lib/docker/unraid-update-status.json',
|
||||
);
|
||||
|
||||
//## BETA 9
|
||||
// $dockerManPaths = array(
|
||||
// 'plugin' => '/usr/local/emhttp/plugins/dockerMan',
|
||||
// 'autostart-file' => '/var/lib/docker/unraid-autostart',
|
||||
// 'template-repos' => '/boot/config/plugins/dockerMan/template-repos',
|
||||
// 'templates-user' => '/boot/config/plugins/dockerMan/templates-user',
|
||||
// 'templates-storage' => '/boot/config/plugins/dockerMan/templates',
|
||||
// 'images-ram' => '/usr/local/emhttp/state/plugins/dockerMan/images',
|
||||
// 'images-storage' => '/boot/config/plugins/dockerMan/images',
|
||||
// 'webui-info' => '/usr/local/emhttp/state/plugins/dockerMan/docker.json',
|
||||
// );
|
||||
|
||||
//## BETA 8
|
||||
// $dockerManPaths = array(
|
||||
// 'plugin' => '/usr/local/emhttp/plugins/dockerMan',
|
||||
// 'autostart-file' => '/var/lib/docker/unraid-autostart',
|
||||
// 'template-repos' => '/boot/config/plugins/dockerMan/template-repos',
|
||||
// 'templates-user' => '/var/lib/docker/unraid-templates',
|
||||
// 'templates-storage' => '/boot/config/plugins/dockerMan/templates',
|
||||
// 'images-ram' => '/usr/local/emhttp/state/plugins/dockerMan/images',
|
||||
// 'images-storage' => '/boot/config/plugins/dockerMan/images',
|
||||
// 'webui-info' => '/usr/local/emhttp/state/plugins/dockerMan/docker.json',
|
||||
// );
|
||||
|
||||
#load emhttp variables if needed.
|
||||
if (! isset($var)){
|
||||
if (! is_file("/usr/local/emhttp/state/var.ini")) shell_exec("wget -qO /dev/null localhost:$(lsof -nPc emhttp | grep -Po 'TCP[^\d]*\K\d+')");
|
||||
$var = @parse_ini_file("/usr/local/emhttp/state/var.ini");
|
||||
}
|
||||
|
||||
######################################
|
||||
## DOCKERTEMPLATES CLASS ##
|
||||
######################################
|
||||
|
||||
class DockerTemplates {
|
||||
|
||||
public $verbose = FALSE;
|
||||
|
||||
private function debug($m) {
|
||||
if($this->verbose) echo $m."\n";
|
||||
}
|
||||
|
||||
public function download_url($url, $path = "", $bg = FALSE){
|
||||
exec("curl --max-time 60 --silent --insecure --location --fail ".($path ? " -o " . escapeshellarg($path) : "")." " . escapeshellarg($url) . " ".($bg ? ">/dev/null 2>&1 &" : "2>/dev/null"), $out, $exit_code );
|
||||
return ($exit_code === 0 ) ? implode("\n", $out) : FALSE;
|
||||
}
|
||||
|
||||
public function listDir($root, $ext=NULL) {
|
||||
$iter = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($root,
|
||||
RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST,
|
||||
RecursiveIteratorIterator::CATCH_GET_CHILD);
|
||||
$paths = array();
|
||||
foreach ($iter as $path => $fileinfo) {
|
||||
$fext = $fileinfo->getExtension();
|
||||
if ($ext && ( $ext != $fext )) continue;
|
||||
if ( $fileinfo->isFile()) $paths[] = array('path' => $path, 'prefix' => basename(dirname($path)), 'name' => $fileinfo->getBasename(".$fext"));
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
|
||||
|
||||
public function getTemplates($type) {
|
||||
global $dockerManPaths;
|
||||
$tmpls = array();
|
||||
$dirs = array();
|
||||
if ($type == "all"){
|
||||
$dirs[] = $dockerManPaths['templates-user'];
|
||||
$dirs[] = $dockerManPaths['templates-storage'];
|
||||
|
||||
} else if ($type == "user"){
|
||||
$dirs[] = $dockerManPaths['templates-user'];
|
||||
|
||||
} else if ($type == "default"){
|
||||
$dirs[] = $dockerManPaths['templates-storage'];
|
||||
} else {
|
||||
$dirs[] = $type;
|
||||
}
|
||||
foreach ($dirs as $dir) {
|
||||
if (! is_dir( $dir)) @mkdir( $dir, 0770, true);
|
||||
$tmpls = array_merge($tmpls, $this->listDir($dir, "xml"));
|
||||
}
|
||||
return $tmpls;
|
||||
}
|
||||
|
||||
|
||||
private function removeDir($path){
|
||||
if (is_dir($path) === true) {
|
||||
$files = array_diff(scandir($path), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
$this->removeDir(realpath($path) . '/' . $file);
|
||||
}
|
||||
return rmdir($path);
|
||||
} else if (is_file($path) === true) {
|
||||
return unlink($path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function downloadTemplates($Dest=NULL, $Urls=NULL){
|
||||
global $dockerManPaths;
|
||||
$Dest = ($Dest) ? $Dest : $dockerManPaths['templates-storage'];
|
||||
$Urls = ($Urls) ? $Urls : $dockerManPaths['template-repos'];
|
||||
$repotemplates = array();
|
||||
$output = "";
|
||||
$tmp_dir = "/tmp/tmp-".mt_rand();
|
||||
if (!file_exists($dockerManPaths['template-repos'])) {
|
||||
@mkdir(dirname($dockerManPaths['template-repos']), 0777, true);
|
||||
@file_put_contents($dockerManPaths['template-repos'], "https://github.com/limetech/docker-templates");
|
||||
}
|
||||
$urls = @file($Urls, FILE_IGNORE_NEW_LINES);
|
||||
if ( ! is_array($urls)) return false;
|
||||
$this->debug("\nURLs:\n " . implode("\n ", $urls));
|
||||
foreach ($urls as $url) {
|
||||
$api_regexes = array(
|
||||
0 => '%/.*github.com/([^/]*)/([^/]*)/tree/([^/]*)/(.*)$%i',
|
||||
1 => '%/.*github.com/([^/]*)/([^/]*)/tree/([^/]*)$%i',
|
||||
2 => '%/.*github.com/([^/]*)/(.*).git%i',
|
||||
3 => '%/.*github.com/([^/]*)/(.*)%i',
|
||||
);
|
||||
for ($i=0; $i < count($api_regexes); $i++) {
|
||||
if ( preg_match($api_regexes[$i], $url, $matches) ){
|
||||
$github_api['user'] = ( isset( $matches[1] )) ? $matches[1] : "";
|
||||
$github_api['repo'] = ( isset( $matches[2] )) ? $matches[2] : "";
|
||||
$github_api['branch'] = ( isset( $matches[3] )) ? $matches[3] : "master";
|
||||
$github_api['path'] = ( isset( $matches[4] )) ? $matches[4] : "";
|
||||
$github_api['url'] = sprintf("https://github.com/%s/%s/archive/%s.tar.gz", $github_api['user'], $github_api['repo'], $github_api['branch']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( $this->download_url($github_api['url'], "$tmp_dir.tar.gz") === FALSE) {
|
||||
$this->debug("\n Download ". $github_api['url'] ." has failed.");
|
||||
return NULL;
|
||||
} else {
|
||||
@mkdir($tmp_dir, 0777, TRUE);
|
||||
shell_exec("tar -zxf $tmp_dir.tar.gz --strip=1 -C $tmp_dir/ 2>&1");
|
||||
unlink("$tmp_dir.tar.gz");
|
||||
}
|
||||
$tmplsStor = array();
|
||||
$templates = $this->getTemplates($tmp_dir);
|
||||
$this->debug("\n Templates found in ". $github_api['url']);
|
||||
foreach ($templates as $template) {
|
||||
$storPath = sprintf("%s/%s", $Dest, str_replace($tmp_dir."/", "", $template['path']) );
|
||||
$tmplsStor[] = $storPath;
|
||||
if (! is_dir( dirname( $storPath ))) @mkdir( dirname( $storPath ), 0777, true);
|
||||
if ( is_file($storPath) ){
|
||||
if ( sha1_file( $template['path'] ) === sha1_file( $storPath )) {
|
||||
$this->debug(" Skipped: ".$template['prefix'].'/'.$template['name']);
|
||||
continue;
|
||||
} else {
|
||||
@copy($template['path'], $storPath);
|
||||
$this->debug(" Updated: ".$template['prefix'].'/'.$template['name']);
|
||||
}
|
||||
} else {
|
||||
@copy($template['path'], $storPath);
|
||||
$this->debug(" Added: ".$template['prefix'].'/'.$template['name']);
|
||||
}
|
||||
}
|
||||
$repotemplates = array_merge($repotemplates, $tmplsStor);
|
||||
$output[$url] = $tmplsStor;
|
||||
$this->removeDir($tmp_dir);
|
||||
}
|
||||
// Delete any templates not in the repos
|
||||
foreach ($this->listDir($Dest, "xml") as $arrLocalTemplate) {
|
||||
if (!in_array($arrLocalTemplate['path'], $repotemplates)) {
|
||||
unlink($arrLocalTemplate['path']);
|
||||
$this->debug(" Removed: ".$arrLocalTemplate['prefix'].'/'.$arrLocalTemplate['name']."\n");
|
||||
// Any other files left in this template folder? if not delete the folder too
|
||||
$files = array_diff(scandir(dirname($arrLocalTemplate['path'])), array('.', '..'));
|
||||
if (empty($files)) {
|
||||
rmdir(dirname($arrLocalTemplate['path']));
|
||||
$this->debug(" Removed: ".$arrLocalTemplate['prefix']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
public function getTemplateValue($Repository, $field, $scope = "all"){
|
||||
$tmpls = $this->getTemplates($scope);
|
||||
|
||||
foreach ($tmpls as $file) {
|
||||
$doc = new DOMDocument();
|
||||
$doc->load($file['path']);
|
||||
$TemplateRepository = $doc->getElementsByTagName( "Repository" )->item(0)->nodeValue;
|
||||
if (! preg_match("/:[\w]*$/i", $TemplateRepository)) {
|
||||
$Repo = preg_replace("/:[\w]*$/i", "", $Repository);
|
||||
}else{
|
||||
$Repo = $Repository;
|
||||
}
|
||||
|
||||
if ( $Repo == $TemplateRepository ) {
|
||||
$TemplateField = $doc->getElementsByTagName( $field )->item(0)->nodeValue;
|
||||
return trim($TemplateField);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
public function getUserTemplate($Container){
|
||||
foreach ($this->getTemplates("user") as $file) {
|
||||
$doc = new DOMDocument('1.0', 'utf-8');
|
||||
$doc->load( $file['path'] );
|
||||
$Name = $doc->getElementsByTagName( "Name" )->item(0)->nodeValue;
|
||||
if ($Name == $Container){
|
||||
return $file['path'];
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
public function getControlURL($name){
|
||||
global $var;
|
||||
$DockerClient = new DockerClient();
|
||||
$IP = $var["IPADDR"];
|
||||
$Repository = "";
|
||||
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
if ($ct['Name'] == $name) {
|
||||
$Repository = preg_replace("/:[\w]*$/i", "", $ct['Image']);
|
||||
$Ports = $ct["Ports"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$WebUI = $this->getTemplateValue($Repository, "WebUI");
|
||||
|
||||
if (preg_match("%\[IP\]%", $WebUI)) {
|
||||
$WebUI = preg_replace("%\[IP\]%", $IP, $WebUI);
|
||||
preg_match("%\[PORT:(\d+)\]%", $WebUI, $matches);
|
||||
$ConfigPort = $matches[1];
|
||||
if ($ct["NetworkMode"] == "bridge"){
|
||||
foreach ($Ports as $key){
|
||||
if ($key["PrivatePort"] == $ConfigPort){
|
||||
$ConfigPort = $key["PublicPort"];
|
||||
}
|
||||
}
|
||||
}
|
||||
$WebUI = preg_replace("%\[PORT:\d+\]%", $ConfigPort, $WebUI);
|
||||
}
|
||||
return $WebUI;
|
||||
}
|
||||
|
||||
|
||||
public function removeInfo($container){
|
||||
global $dockerManPaths;
|
||||
$dockerIni = $dockerManPaths['webui-info'];
|
||||
if (! is_dir( dirname( $dockerIni ))) @mkdir( dirname( $dockerIni ), 0770, true);
|
||||
$info = (is_file($dockerIni)) ? json_decode(file_get_contents($dockerIni), TRUE) : array();
|
||||
if (! count($info) ) $info = array();
|
||||
|
||||
if (isset($info[$container])) unset($info[$container]);
|
||||
file_put_contents($dockerIni, json_encode($info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
$update_file = $dockerManPaths['update-status'];
|
||||
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
|
||||
if (isset($updateStatus[$container])) unset($updateStatus[$container]);
|
||||
file_put_contents($update_file, json_encode($updateStatus, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
|
||||
public function getAllInfo($reload = FALSE){
|
||||
global $dockerManPaths;
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
$new_info = array();
|
||||
|
||||
$dockerIni = $dockerManPaths['webui-info'];
|
||||
if (! is_dir( dirname( $dockerIni ))) @mkdir( dirname( $dockerIni ), 0770, true);
|
||||
$info = (is_file($dockerIni)) ? json_decode(file_get_contents($dockerIni), TRUE) : array();
|
||||
if (! count($info) ) $info = array();
|
||||
|
||||
$containers = $DockerClient->getDockerContainers();
|
||||
if (! count($containers) ) $containers = array();
|
||||
|
||||
$autostart_file = $dockerManPaths['autostart-file'];
|
||||
$allAutoStart = @file($autostart_file, FILE_IGNORE_NEW_LINES);
|
||||
if ($allAutoStart===FALSE) $allAutoStart = array();
|
||||
|
||||
$update_file = $dockerManPaths['update-status'];
|
||||
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
|
||||
|
||||
foreach ($containers as $ct) {
|
||||
$name = $ct['Name'];
|
||||
$image = $ct['Image'];
|
||||
$tmp = ( count($info[$name]) ) ? $info[$name] : array() ;
|
||||
|
||||
$tmp['running'] = $ct['Running'];
|
||||
$tmp['autostart'] = in_array($name, $allAutoStart);
|
||||
|
||||
$img = $this->getBannerIcon( $image );
|
||||
$tmp['banner'] = ( $img['banner'] ) ? $img['banner'] : "#";
|
||||
$tmp['icon'] = ( $img['icon'] ) ? $img['icon'] : "#";
|
||||
|
||||
$WebUI = $this->getControlURL($name);
|
||||
$tmp['url'] = ($WebUI) ? $WebUI : "#";
|
||||
|
||||
$Registry = $this->getTemplateValue($image, "Registry");
|
||||
$tmp['registry'] = ( $Registry ) ? $Registry : "#";
|
||||
|
||||
if ($reload) {
|
||||
$nv = $DockerUpdate->getUpdateStatus($name, $image);
|
||||
if ($nv != 'undef'){
|
||||
$updateStatus[$name] = $nv;
|
||||
}
|
||||
}
|
||||
$tmp['updated'] = (array_key_exists($name, $updateStatus)) ? $updateStatus[$name] : 'undef';
|
||||
|
||||
$tmp['template'] = $this->getUserTemplate($name);
|
||||
|
||||
$this->debug("\n$name");foreach ($tmp as $c => $d) $this->debug(sprintf(" %-10s: %s", $c, $d));
|
||||
$new_info[$name] = $tmp;
|
||||
}
|
||||
file_put_contents($dockerIni, json_encode($new_info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
if($reload) {
|
||||
foreach ($updateStatus as $ct => $update) if (!isset($new_info[$ct])) unset($updateStatus[$ct]);
|
||||
file_put_contents($update_file, json_encode($updateStatus, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
return $new_info;
|
||||
}
|
||||
|
||||
|
||||
public function getBannerIcon($Repository){
|
||||
global $dockerManPaths;
|
||||
$out = array();
|
||||
$Images = array();
|
||||
|
||||
$Images = array('banner' => $this->getTemplateValue($Repository, "Banner"),
|
||||
'icon' => $this->getTemplateValue($Repository, "Icon") );
|
||||
|
||||
$defaultImages = array('banner' => '/plugins/dynamix.docker.manager/images/spacer.png',
|
||||
'icon' => '/plugins/dynamix.docker.manager/images/question.png');
|
||||
|
||||
foreach ($Images as $type => $imgUrl) {
|
||||
preg_match_all("/(.*?):([\w]*$)/i", $Repository, $matches);
|
||||
$tempPath = sprintf("%s/%s-%s-%s.png", $dockerManPaths[ 'images-ram' ], preg_replace('%\/|\\\%', '-', $matches[1][0]), $matches[2][0], $type);
|
||||
$storagePath = sprintf("%s/%s-%s-%s.png", $dockerManPaths[ 'images-storage' ], preg_replace('%\/|\\\%', '-', $matches[1][0]), $matches[2][0], $type);
|
||||
if (! is_dir( dirname( $tempPath ))) @mkdir( dirname( $tempPath ), 0770, true);
|
||||
if (! is_dir( dirname( $storagePath ))) @mkdir( dirname( $storagePath ), 0770, true);
|
||||
if (! is_file( $tempPath )) {
|
||||
if ( is_file( $storagePath )){
|
||||
@copy($storagePath, $tempPath);
|
||||
} else {
|
||||
$this->download_url($imgUrl, $storagePath);
|
||||
@copy($storagePath, $tempPath);
|
||||
}
|
||||
}
|
||||
$out[ $type ] = ( is_file( $tempPath ) ) ? str_replace('/usr/local/emhttp', '', $tempPath) : "";
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
######################################
|
||||
## DOCKERUPDATE CLASS ##
|
||||
######################################
|
||||
class DockerUpdate{
|
||||
|
||||
public function download_url($url, $path = "", $bg = FALSE){
|
||||
exec("curl --max-time 30 --silent --insecure --location --fail ".($path ? " -o " . escapeshellarg($path) : "")." " . escapeshellarg($url) . " ".($bg ? ">/dev/null 2>&1 &" : "2>/dev/null"), $out, $exit_code );
|
||||
return ($exit_code === 0 ) ? implode("\n", $out) : FALSE;
|
||||
}
|
||||
|
||||
|
||||
public function getRemoteVersion($RegistryUrl, $image){
|
||||
preg_match_all("/:([\w]*$)/i", $image, $matches);
|
||||
$tag = isset($matches[1][0]) ? $matches[1][0] : "latest";
|
||||
preg_match("#/u/([^/]*)/([^/]*)#", $RegistryUrl, $matches);
|
||||
$apiUrl = sprintf("http://index.docker.io/v1/repositories/%s/%s/tags/%s", $matches[1], $matches[2], $tag);
|
||||
$apiContent = $this->download_url($apiUrl);
|
||||
return ( $apiContent === FALSE ) ? NULL : substr(json_decode($apiContent, TRUE)[0]['id'],0,16);
|
||||
}
|
||||
|
||||
|
||||
public function getLocalVersion($file){
|
||||
if(is_file($file)){
|
||||
$doc = new DOMDocument();
|
||||
$doc->load($file);
|
||||
if ( ! $doc->getElementsByTagName( "Version" )->length == 0 ) {
|
||||
return $doc->getElementsByTagName( "Version" )->item(0)->nodeValue;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getUpdateStatus($container, $image) {
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
$RegistryUrl = $DockerTemplates->getTemplateValue($image, "Registry");
|
||||
$userFile = $DockerTemplates->getUserTemplate($container);
|
||||
$localVersion = $this->getLocalVersion($userFile);
|
||||
$remoteVersion = $this->getRemoteVersion($RegistryUrl, $image);
|
||||
// echo "\n $localVersion => $remoteVersion";
|
||||
return ($localVersion && $remoteVersion) ? (($remoteVersion == $localVersion) ? "true" : "false") : "undef" ;
|
||||
}
|
||||
|
||||
|
||||
public function syncVersions($container) {
|
||||
global $dockerManPaths;
|
||||
$update_file = $dockerManPaths['update-status'];
|
||||
$updateStatus = (is_file($update_file)) ? json_decode(file_get_contents($update_file), TRUE) : array();
|
||||
$updateStatus[$container] = 'true';
|
||||
file_put_contents($update_file, json_encode($updateStatus, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
######################################
|
||||
## DOCKERCLIENT CLASS ##
|
||||
######################################
|
||||
class DockerClient {
|
||||
|
||||
private function build_sorter($key) {
|
||||
return function ($a, $b) use ($key) {
|
||||
return strnatcmp(strtolower($a[$key]), strtolower($b[$key]));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private function humanTiming ($time){
|
||||
$time = time() - $time; // to get the time since that moment
|
||||
$tokens = array (31536000 => 'year',
|
||||
2592000 => 'month',
|
||||
604800 => 'week',
|
||||
86400 => 'day',
|
||||
3600 => 'hour',
|
||||
60 => 'minute',
|
||||
1 => 'second'
|
||||
);
|
||||
foreach ($tokens as $unit => $text) {
|
||||
if ($time < $unit) continue;
|
||||
$numberOfUnits = floor($time / $unit);
|
||||
return $numberOfUnits.' '.$text.(($numberOfUnits>1)?'s':'')." ago";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function unchunk($result) {
|
||||
return preg_replace_callback(
|
||||
'/(?:(?:\r\n|\n)|^)([0-9A-F]+)(?:\r\n|\n){1,2}(.*?)'
|
||||
.'((?:\r\n|\n)(?:[0-9A-F]+(?:\r\n|\n))|$)/si',
|
||||
create_function('$matches','return hexdec($matches[1]) == strlen($matches[2]) ? $matches[2] :$matches[0];'), $result);
|
||||
}
|
||||
|
||||
|
||||
private function formatBytes($size){
|
||||
if ($size == 0){ return "0 B";}
|
||||
$base = log($size) / log(1024);
|
||||
$suffix = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
|
||||
return round(pow(1024, $base - floor($base)), 1) ." ". $suffix[floor($base)];
|
||||
}
|
||||
|
||||
|
||||
private function getDockerJSON($url, $method = "GET"){
|
||||
$fp = stream_socket_client('unix:///var/run/docker.sock', $errno, $errstr);
|
||||
|
||||
if ($fp === false) {
|
||||
echo "Couldn't create socket: [$errno] $errstr";
|
||||
return NULL;
|
||||
}
|
||||
$out="$method {$url} HTTP/1.1\r\nConnection: Close\r\n\r\n";
|
||||
fwrite($fp, $out);
|
||||
// Strip headers out
|
||||
while (($line = fgets($fp)) !== false) {
|
||||
if (rtrim($line) == '') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$data = '';
|
||||
while (($line = fgets($fp)) !== false) {
|
||||
$data .= $line;
|
||||
}
|
||||
fclose($fp);
|
||||
$data = $this->unchunk($data);
|
||||
$json = json_decode( $data, true );
|
||||
if ($json === null) {
|
||||
$json = array();
|
||||
} else if (!array_key_exists(0, $json) && !empty($json)) {
|
||||
$json = [ $json ];
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
public function getInfo(){
|
||||
$info = $this->getDockerJSON("/info");
|
||||
$version = $this->getDockerJSON("/version");
|
||||
return array_merge($info[0], $version[0]);
|
||||
}
|
||||
|
||||
|
||||
private function getContainerDetails($id){
|
||||
$json = $this->getDockerJSON("/containers/{$id}/json");
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
public function startContainer($id){
|
||||
$json = $this->getDockerJSON("/containers/${id}/start", "POST");
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
public function removeImage($id){
|
||||
$json = $this->getDockerJSON("/images/{$id}", "DELETE");
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
public function stopContainer($id){
|
||||
$json = $this->getDockerJSON("/containers/${id}/stop", "POST");
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
private function getImageDetails($id){
|
||||
$json = $this->getDockerJSON("/images/$id/json");
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
public function getDockerContainers(){
|
||||
$containers = array();
|
||||
$json = $this->getDockerJSON("/containers/json?all=1");
|
||||
|
||||
if (! $json ){ return $containers; }
|
||||
|
||||
foreach($json as $obj){
|
||||
$c = array();
|
||||
$status = $obj['Status'] ? $obj['Status'] : "None";
|
||||
preg_match("/\b^Up\b/", $status, $matches);
|
||||
$running = $matches ? TRUE : FALSE;
|
||||
$details = $this->getContainerDetails($obj['Id']);
|
||||
|
||||
// echo "<pre>".print_r($details,TRUE)."</pre>";
|
||||
|
||||
// Docker 1.7 doesn't automatically append the tag 'latest', so we do that now if there's no tag
|
||||
preg_match_all("/:([\w]*$)/i", $obj['Image'], $matches2);
|
||||
|
||||
$c["Image"] = $obj['Image'] . (isset($matches2[1][0]) ? "" : ":latest");
|
||||
$c["ImageId"] = substr($details[0]["Image"],0,12);
|
||||
$c["Name"] = substr($details[0]['Name'], 1);
|
||||
$c["Status"] = $status;
|
||||
$c["Running"] = $running;
|
||||
$c["Cmd"] = $obj['Command'];
|
||||
$c["Id"] = substr($obj['Id'],0,12);
|
||||
$c['Volumes'] = $details[0]["HostConfig"]['Binds'];
|
||||
$c["Created"] = $this->humanTiming($obj['Created']);
|
||||
$c["NetworkMode"] = $details[0]['HostConfig']['NetworkMode'];
|
||||
|
||||
$Ports = $details[0]['HostConfig']['PortBindings'];
|
||||
$Ports = (count ( $Ports )) ? $Ports : array();
|
||||
$c["Ports"] = array();
|
||||
if ($c["NetworkMode"] != 'host'){
|
||||
foreach ($Ports as $port => $value) {
|
||||
list($PrivatePort, $Type) = explode("/", $port);
|
||||
$PublicPort = $value[0]['HostPort'];
|
||||
$c["Ports"][] = array('PrivatePort' => $PrivatePort,
|
||||
'PublicPort' => $PublicPort,
|
||||
'Type' => $Type );
|
||||
}
|
||||
}
|
||||
|
||||
$containers[] = $c;
|
||||
}
|
||||
usort($containers, $this->build_sorter('Name'));
|
||||
return $containers;
|
||||
}
|
||||
|
||||
|
||||
public function getImageID($Image){
|
||||
$allImages = $this->getDockerImages();
|
||||
foreach ($allImages as $img) {
|
||||
preg_match("%" . preg_quote($Image, "%") ."%", $img["Tags"][0], $matches);
|
||||
if( $matches){
|
||||
return $img["Id"];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
private function usedBy($imageId){
|
||||
$out = array();
|
||||
$Containers = $this->getDockerContainers();
|
||||
$Containers = ( count( $Containers )) ? $Containers : array();
|
||||
foreach ($Containers as $ct) {
|
||||
if ($ct["ImageId"] == $imageId){
|
||||
$out[] = $ct["Name"];
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
public function getDockerImages(){
|
||||
|
||||
$images = array();
|
||||
$c = array();
|
||||
$json = $this->getDockerJSON("/images/json?all=0");
|
||||
|
||||
if (! $json){ return $images; }
|
||||
|
||||
foreach($json as $obj){
|
||||
$c = array();
|
||||
$tags = array();
|
||||
foreach($obj['RepoTags'] as $t){
|
||||
$tags[] = htmlentities($t);
|
||||
}
|
||||
|
||||
$c["Created"] = $this->humanTiming($obj['Created']);//date('Y-m-d H:i:s', $obj['Created']);
|
||||
$c["Id"] = substr($obj['Id'],0,12);
|
||||
$c["ParentId"] = substr($obj['ParentId'],0,12);
|
||||
$c["Size"] = $this->formatBytes($obj['Size']);
|
||||
$c["VirtualSize"] = $this->formatBytes($obj['VirtualSize']);
|
||||
$c["Tags"] = $tags;
|
||||
$c["usedBy"] = $this->usedBy($c["Id"]);
|
||||
|
||||
$imgDetails = $this->getImageDetails($obj['Id']);
|
||||
// echo "<pre>".print_r($imgDetails,TRUE)."</pre>";
|
||||
$a = $imgDetails[0]['Config']['Volumes'];
|
||||
$b = $imgDetails[0]['Config']['ExposedPorts'];
|
||||
$c['ImageType'] = (! count($a) && ! count($b)) ? 'base' : 'user';
|
||||
|
||||
$images[] = $c;
|
||||
|
||||
}
|
||||
return $images;
|
||||
}
|
||||
}
|
||||
?>
|
||||
50
plugins/dynamix.docker.manager/include/Exec.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
readfile("/usr/local/emhttp/plugins/dynamix.docker.manager/log.htm");
|
||||
|
||||
if ( isset( $_GET['cmd'] )) {
|
||||
$commands = urldecode(($_GET['cmd']));
|
||||
$descriptorspec = array(
|
||||
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
|
||||
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
|
||||
2 => array("pipe", "w") // stderr is a pipe that the child will write to
|
||||
);
|
||||
|
||||
foreach (explode(';', $commands) as $command){
|
||||
$parts = explode(" ", $command);
|
||||
$command = escapeshellcmd(realpath($_SERVER['DOCUMENT_ROOT'].array_shift($parts)));
|
||||
if (!$command) continue;
|
||||
$command .= " ".implode(" ", $parts); // should add 'escapeshellarg' here, but this requires changes in all the original arguments
|
||||
$id = mt_rand();
|
||||
$output = array();
|
||||
echo "<p class=\"logLine\" id=\"logBody\"></p>";
|
||||
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>Command:</legend>";
|
||||
echo "root@localhost:# {$command}<br>";
|
||||
echo "<span id=\"wait{$id}\">Please wait </span>";
|
||||
echo "<p class=\"logLine\" id=\"logBody\"></p></fieldset>');</script>";
|
||||
echo "<script>show_Wait({$id});</script>";
|
||||
$proc = proc_open($command." 2>&1", $descriptorspec, $pipes, '/', array());
|
||||
while ($out = fgets( $pipes[1] )) {
|
||||
$out = preg_replace("%[\t\n\x0B\f\r]+%", '', $out );
|
||||
@flush();
|
||||
echo "<script>addLog(\"" . htmlentities($out) . "\");</script>\n";
|
||||
@flush();
|
||||
}
|
||||
$retval = proc_close($proc);
|
||||
echo "<script>stop_Wait($id);</script>\n";
|
||||
$out = $retval ? "The command failed." : "The command finished successfully!";
|
||||
echo "<script>addLog('<br><b>".$out. "</b>');</script>";
|
||||
}
|
||||
}
|
||||
?>
|
||||
55
plugins/dynamix.docker.manager/include/UpdateConfig.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
require_once("/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php");
|
||||
|
||||
// Autostart file
|
||||
global $dockerManPaths;
|
||||
$autostart_file = $dockerManPaths['autostart-file'];
|
||||
$template_repos = $dockerManPaths['template-repos'];
|
||||
|
||||
// Update the start/stop configuration
|
||||
if ($_POST['action'] == "autostart" ){
|
||||
$json = ($_POST['response'] == 'json') ? true : false;
|
||||
|
||||
if (! $json) readfile("/usr/local/emhttp/update.htm");
|
||||
|
||||
$container = urldecode(($_POST['container']));
|
||||
unset($_POST['container']);
|
||||
|
||||
$allAutoStart = @file($autostart_file, FILE_IGNORE_NEW_LINES);
|
||||
if ($allAutoStart===FALSE) $allAutoStart = array();
|
||||
$key = array_search($container, $allAutoStart);
|
||||
if ($key===FALSE) {
|
||||
array_push($allAutoStart, $container);
|
||||
if ($json) echo json_encode(array( 'autostart' => true ));
|
||||
}
|
||||
else {
|
||||
unset($allAutoStart[$key]);
|
||||
if ($json) echo json_encode(array( 'autostart' => false ));
|
||||
}
|
||||
file_put_contents($autostart_file, implode(PHP_EOL, $allAutoStart).(count($allAutoStart)? PHP_EOL : ""));
|
||||
}
|
||||
|
||||
if ($_POST['#action'] == "templates" ){
|
||||
readfile("/usr/local/emhttp/update.htm");
|
||||
$repos = $_POST['template_repos'];
|
||||
file_put_contents($template_repos, $repos);
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
$DockerTemplates->downloadTemplates();
|
||||
}
|
||||
|
||||
if ( isset($_GET['is_dir'] )) {
|
||||
echo json_encode( array( 'is_dir' => is_dir( $_GET['is_dir'] )));
|
||||
}
|
||||
?>
|
||||
155
plugins/dynamix.docker.manager/javascript/addDocker.js
Normal file
@@ -0,0 +1,155 @@
|
||||
var pathNum = 2;
|
||||
var portNum = 0;
|
||||
var varNum = 0;
|
||||
var currentPath = "/mnt/";
|
||||
|
||||
if (!String.prototype.format) {
|
||||
String.prototype.format = function() {
|
||||
var args = arguments;
|
||||
return this.replace(/{(\d+)}/g, function(match, number) {
|
||||
return typeof args[number] != 'undefined' ? args[number] : match;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function rmTemplate(tmpl) {
|
||||
var name = tmpl.split(/[\/]+/).pop();
|
||||
swal({title:"Are you sure?",text:"Remove template: "+name,type:"warning",showCancelButton:true},function(){$("#rmTemplate").val(tmpl);$("#formTemplate").submit();});
|
||||
}
|
||||
|
||||
function toggleBrowser(N) {
|
||||
var el = $('#fileTree' + N);
|
||||
if (el.is(':visible')) {
|
||||
hideBrowser(N);
|
||||
} else {
|
||||
$( el ).fileTree({
|
||||
root: currentPath,
|
||||
filter: 'HIDE_FILES_FILTER'
|
||||
},
|
||||
function(file) {},
|
||||
function(folder) {
|
||||
$("#hostPath" + N).val(folder);
|
||||
});
|
||||
$( el ).slideDown('fast');
|
||||
}
|
||||
}
|
||||
|
||||
function hideBrowser(N) {
|
||||
$("#fileTree" + N).slideUp('fast', function () {
|
||||
$(this).html("");
|
||||
});
|
||||
}
|
||||
|
||||
function addPort(frm) {
|
||||
portNum++;
|
||||
var hostPort = $("#hostPort1");
|
||||
var containerPort = $("#containerPort1");
|
||||
var portProtocol = $("#portProtocol1");
|
||||
|
||||
var select = "";
|
||||
if (portProtocol.val() == "udp"){
|
||||
select = "selected";
|
||||
}
|
||||
|
||||
var row = [
|
||||
'<tr id="portNum{0}" style="display: none;">',
|
||||
'<td>',
|
||||
'<input type="number" min="1" max="65535" name="containerPort[]" value="{2}" class="textPort" title="Set the port your app uses inside the container.">',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<input type="number" min="1" max="65535" name="hostPort[]" value="{1}" class="textPort" title="Set the port you use to interact with the app.">',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<select name="portProtocol[]">',
|
||||
'<option value="tcp">tcp</option>',
|
||||
'<option value="udp" {3}>udp</option>',
|
||||
'</select>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<input type="button" value="Remove" onclick="removePort({0});">',
|
||||
'</td>',
|
||||
'</tr>'
|
||||
].join('').format(portNum, hostPort.val(), containerPort.val(), select);
|
||||
|
||||
$(row).appendTo('#portRows').fadeIn("fast");
|
||||
hostPort.val('');
|
||||
containerPort.val('');
|
||||
portProtocol.val('tcp');
|
||||
}
|
||||
|
||||
function removePort(rnum) {
|
||||
$('#portNum' + rnum).fadeOut("fast", function() { $(this).remove(); });
|
||||
}
|
||||
|
||||
function addPath(frm) {
|
||||
pathNum++;
|
||||
var hostPath = $("#hostPath1");
|
||||
var containerPath = $("#containerPath1");
|
||||
var hostWritable = $("#hostWritable1");
|
||||
|
||||
var select = "";
|
||||
if (hostWritable.val() == "ro"){
|
||||
select = "selected";
|
||||
}
|
||||
|
||||
var row = [
|
||||
'<tr id="pathNum{0}" style="display: none;">',
|
||||
'<td>',
|
||||
'<input type="text" name="containerPath[]" value="{2}" class="textPath" onclick="hideBrowser({0});" title="The directory your app uses inside the container. Ex: /config">',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<input type="text" id="hostPath{0}" name="hostPath[]" value="{1}" class="textPath" onclick="toggleBrowser({0});" title="The directory in your array the app have access to. Ex: /mnt/user/Movies"/>',
|
||||
'<div id="fileTree{0}" class="fileTree"></div>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<select name="hostWritable[]">',
|
||||
'<option value="rw">Read/Write</option>',
|
||||
'<option value="ro" {3}>Read Only</option>',
|
||||
'</select>',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<input type="button" value="Remove" onclick="removePath({0});"></td></tr>',
|
||||
'</td>',
|
||||
'</tr>'
|
||||
].join('').format(pathNum, hostPath.val(), containerPath.val(), select);
|
||||
|
||||
$(row).appendTo('#pathRows tbody').fadeIn("fast");
|
||||
hostPath.val('');
|
||||
containerPath.val('');
|
||||
hostWritable.val('rw');
|
||||
}
|
||||
|
||||
function removePath(rnum) {
|
||||
$('#pathNum' + rnum).fadeOut("fast", function() { $(this).remove(); });
|
||||
}
|
||||
|
||||
function addEnv(frm) {
|
||||
varNum++;
|
||||
var VariableName = $("#VariableName1");
|
||||
var VariableValue = $("#VariableValue1");
|
||||
|
||||
var row = [
|
||||
'<tr id="varNum{0}" style="display: none;">',
|
||||
'<td>',
|
||||
'<input type="text" name="VariableName[]" value="{1}" class="textEnv">',
|
||||
'</td>',
|
||||
'<td>',
|
||||
'<input type="text" name="VariableValue[]" value="{2}" class="textEnv">',
|
||||
'<input type="button" value="Remove" onclick="removeEnv({0});">',
|
||||
'</td>',
|
||||
'</tr>'
|
||||
].join('').format(varNum, VariableName.val(), VariableValue.val());
|
||||
|
||||
$(row).appendTo('#envRows tbody').fadeIn("fast");
|
||||
VariableName.val('');
|
||||
VariableValue.val('');
|
||||
}
|
||||
|
||||
function removeEnv(rnum) {
|
||||
$('#varNum' + rnum).fadeOut("fast", function() { $(this).remove(); });
|
||||
}
|
||||
|
||||
function toggleMode(){
|
||||
$("#toggleMode").toggleClass("fa-toggle-off fa-toggle-on");
|
||||
$(".additionalFields").slideToggle();
|
||||
}
|
||||
242
plugins/dynamix.docker.manager/javascript/docker.js
Normal file
@@ -0,0 +1,242 @@
|
||||
function addDockerContainerContext(container, image, template, started, update, autostart, webui){
|
||||
var opts = [{header: container, image: "/plugins/dynamix.docker.manager/images/dynamix.docker.manager.png"}];
|
||||
if (started && (webui != "#")) {
|
||||
opts.push({text: 'WebUI', icon:'fa-globe', href: webui, target: '_blank' });
|
||||
opts.push({divider: true});
|
||||
}
|
||||
if (! update){
|
||||
opts.push({text: 'Update', icon:'fa-arrow-down', action: function(e){ e.preventDefault(); execUpContainer(container); }});
|
||||
opts.push({divider: true});
|
||||
}
|
||||
if (started){
|
||||
opts.push({text: 'Stop', icon:'fa-stop', action: function(e){ e.preventDefault(); containerControl(container, 'stop'); }});
|
||||
opts.push({text: 'Restart', icon:'fa-refresh', action: function(e){ e.preventDefault(); containerControl(container, 'restart'); }});
|
||||
} else {
|
||||
opts.push({text: 'Start', icon:'fa-play', action: function(e){ e.preventDefault(); containerControl(container, 'start'); }});
|
||||
}
|
||||
opts.push({divider: true});
|
||||
if (location.pathname.indexOf("/Dashboard") === 0) {
|
||||
opts.push({text: 'Logs', icon:'fa-navicon', action: function(e){ e.preventDefault(); containerLogs(container); }});
|
||||
}
|
||||
if (template) {
|
||||
opts.push({text: 'Edit', icon:'fa-wrench', action: function(e){ e.preventDefault(); editContainer(container, template); }});
|
||||
}
|
||||
opts.push({divider: true});
|
||||
opts.push({text: 'Remove', icon:'fa-trash', action: function(e){ e.preventDefault(); rmContainer(container, image); }});
|
||||
context.attach('#context-'+container, opts);
|
||||
}
|
||||
|
||||
function addDockerImageContext(image, imageTag){
|
||||
var opts = [{header: '(orphan image)'}];
|
||||
opts.push({text: 'Remove', icon:'fa-trash', action: function(e){ e.preventDefault(); rmImage(image, imageTag); }});
|
||||
context.attach('#context-'+image, opts);
|
||||
}
|
||||
|
||||
function execUpContainer(container){
|
||||
var title = 'Updating the container: ' + container;
|
||||
var address = "/plugins/dynamix.docker.manager/include/CreateDocker.php?updateContainer=true&ct[]=" + encodeURIComponent(container);
|
||||
popupWithIframe(title, address, true);
|
||||
}
|
||||
|
||||
function popupWithIframe(title, cmd, reload) {
|
||||
pauseEvents();
|
||||
|
||||
$( "#iframe-popup" ).html('<iframe id="myIframe" frameborder="0" scrolling="yes" width="100%" height="99%"></iframe>');
|
||||
$("#iframe-popup").dialog({
|
||||
autoOpen: true,
|
||||
title: title,
|
||||
draggable: true,
|
||||
width : 800,
|
||||
height : ((screen.height/5)*4)||0,
|
||||
resizable : true,
|
||||
modal : true,
|
||||
show : {effect: 'fade' , duration: 250},
|
||||
hide : {effect: 'fade' , duration: 250},
|
||||
open: function(ev, ui){
|
||||
$('#myIframe').attr('src', cmd);
|
||||
},
|
||||
close: function( event, ui ) {
|
||||
if (reload && !$('#myIframe').contents().find('#canvas').length){
|
||||
location = window.location.href;
|
||||
} else {
|
||||
resumeEvents();
|
||||
}
|
||||
}
|
||||
});
|
||||
$(".ui-dialog .ui-dialog-titlebar").addClass('menu');
|
||||
$(".ui-dialog .ui-dialog-content").css('padding','0');
|
||||
$(".ui-dialog .ui-dialog-title").css('text-align','center');
|
||||
$(".ui-dialog .ui-dialog-title").css('width', "100%");
|
||||
//$('.ui-widget-overlay').click(function() { $("#iframe-popup").dialog("close"); });
|
||||
}
|
||||
|
||||
function addContainer() {
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
|
||||
location = path + '/AddContainer';
|
||||
}
|
||||
|
||||
function editContainer(container, template) {
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
|
||||
location = path + '/UpdateContainer?xmlTemplate=edit:' + template;
|
||||
}
|
||||
|
||||
function rmContainer(containers, images){
|
||||
var ctCmd = "/plugins/dynamix.docker.manager/scripts/docker rm -f";
|
||||
var imgCmd = "/plugins/dynamix.docker.manager/scripts/docker rmi";
|
||||
var ctTitle = "";
|
||||
if (typeof containers === "object") {
|
||||
for (var i = 0; i < containers.length; i++) {
|
||||
ctCmd += " " + containers[i];
|
||||
imgCmd += " " + images[i];
|
||||
ctTitle += containers[i] + "<br>";
|
||||
}
|
||||
} else {
|
||||
ctCmd += " " + containers;
|
||||
imgCmd += " " + images;
|
||||
ctTitle += containers + "<br>";
|
||||
}
|
||||
var title = 'Removing container';
|
||||
$( "#dialog-confirm" ).html(ctTitle);
|
||||
$( "#dialog-confirm" ).append( "<br><span style='color: #E80000;'>Are you sure?</span>" );
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
title: title,
|
||||
resizable: false,
|
||||
width: 500,
|
||||
modal: true,
|
||||
show : {effect: 'fade' , duration: 250},
|
||||
hide : {effect: 'fade' , duration: 250},
|
||||
buttons: {
|
||||
"Just the container": function() {
|
||||
$( this ).dialog( "close" );
|
||||
var cmd = '/plugins/dynamix.docker.manager/include/Exec.php?cmd=' + encodeURIComponent(ctCmd);
|
||||
popupWithIframe(title, cmd, true);
|
||||
},
|
||||
"Container and image": function() {
|
||||
$( this ).dialog( "close" );
|
||||
var cmd = '/plugins/dynamix.docker.manager/include/Exec.php?cmd=' + encodeURIComponent(ctCmd + ";" + imgCmd);
|
||||
popupWithIframe(title, cmd, true);
|
||||
},
|
||||
Cancel: function() {
|
||||
$( this ).dialog( "close" );
|
||||
$( this ).html("");
|
||||
}
|
||||
}
|
||||
});
|
||||
$(".ui-dialog .ui-dialog-titlebar").addClass('menu');
|
||||
$(".ui-dialog .ui-dialog-title").css('text-align','center').css( 'width', "100%");
|
||||
$(".ui-dialog .ui-dialog-content").css('padding-top','15px').css('font-weight','bold');
|
||||
$(".ui-button-text").css('padding','0px 5px');
|
||||
}
|
||||
|
||||
function updateContainer(containers){
|
||||
var ctCmd ="";
|
||||
var ctTitle = "";
|
||||
if (typeof containers === "object") {
|
||||
for (var i = 0; i < containers.length; i++) {
|
||||
ctCmd += "&ct[]=" + encodeURIComponent(containers[i]);
|
||||
ctTitle += containers[i] + "<br>";
|
||||
}
|
||||
} else {
|
||||
ctCmd += "&ct[]=" + encodeURIComponent(containers);
|
||||
ctTitle += containers + "<br>";
|
||||
}
|
||||
var title = 'Updating container';
|
||||
$( "#dialog-confirm" ).html(ctTitle);
|
||||
$( "#dialog-confirm" ).append( "<br><span style='color: #E80000;'>Are you sure?</span>" );
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
title: title,
|
||||
resizable: false,
|
||||
width: 500,
|
||||
modal: true,
|
||||
show : {effect: 'fade' , duration: 250},
|
||||
hide : {effect: 'fade' , duration: 250},
|
||||
buttons: {
|
||||
"Just do it!": function() {
|
||||
$( this ).dialog( "close" );
|
||||
var cmd = "/plugins/dynamix.docker.manager/include/CreateDocker.php?updateContainer=true" + ctCmd;
|
||||
popupWithIframe(title, cmd, true);
|
||||
},
|
||||
Cancel: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
});
|
||||
$(".ui-dialog .ui-dialog-titlebar").addClass('menu');
|
||||
$(".ui-dialog .ui-dialog-title").css('text-align','center');
|
||||
$(".ui-dialog .ui-dialog-content").css('padding-top','15px');
|
||||
$(".ui-dialog .ui-dialog-content").css('font-weight','bold');
|
||||
$(".ui-button-text").css('padding','0px 5px');
|
||||
$( ".ui-dialog .ui-dialog-title" ).css( 'width', "100%");
|
||||
}
|
||||
|
||||
function rmImage(images, imageName){
|
||||
var imgCmd = "/plugins/dynamix.docker.manager/scripts/docker rmi";
|
||||
var imgTitle = "";
|
||||
if (typeof images === "object") {
|
||||
for (var i = 0; i < images.length; i++) {
|
||||
imgCmd += " " + images[i];
|
||||
imgTitle += imageName[i] + "<br>";
|
||||
}
|
||||
} else {
|
||||
imgCmd += " " + images;
|
||||
imgTitle += imageName + "<br>";
|
||||
}
|
||||
var title = "Removing image";
|
||||
$( "#dialog-confirm" ).html(imgTitle);
|
||||
$( "#dialog-confirm" ).append( "<br><span style='color: #E80000;'>Are you sure?</span>" );
|
||||
$( "#dialog-confirm" ).dialog({
|
||||
title: title,
|
||||
dialogClass: "alert",
|
||||
resizable: false,
|
||||
width: 500,
|
||||
modal: true,
|
||||
show : {effect: 'fade' , duration: 250},
|
||||
hide : {effect: 'fade' , duration: 250},
|
||||
buttons: {
|
||||
"Just do it!": function() {
|
||||
$( this ).dialog( "close" );
|
||||
var cmd = '/plugins/dynamix.docker.manager/include/Exec.php?cmd=' + encodeURIComponent(imgCmd);
|
||||
popupWithIframe(title, cmd, true);
|
||||
},
|
||||
Cancel: function() {
|
||||
$( this ).dialog( "close" );
|
||||
}
|
||||
}
|
||||
});
|
||||
$(".ui-dialog .ui-dialog-titlebar").addClass('menu');
|
||||
$(".ui-dialog .ui-dialog-title").css('text-align','center');
|
||||
$(".ui-dialog .ui-dialog-content").css('padding-top','15px');
|
||||
$(".ui-dialog .ui-dialog-content").css('font-weight','bold');
|
||||
$(".ui-button-text").css('padding','0px 5px');
|
||||
$( ".ui-dialog .ui-dialog-title" ).css( 'width', "100%");
|
||||
}
|
||||
|
||||
function containerControl(container, action){
|
||||
$("#cmdStartStop").val("/plugins/dynamix.docker.manager/scripts/docker");
|
||||
$("#cmdArg1").val(action);
|
||||
$("#cmdArg2").val(container);
|
||||
$("#formStartStop").submit();
|
||||
}
|
||||
|
||||
function reloadUpdate(){
|
||||
$(".updatecolumn").html("<span style=\"color:#267CA8;white-space:nowrap;\"><i class=\"fa fa-spin fa-refresh\"></i> checking...</span>");
|
||||
$("#cmdStartStop").val("/plugins/dynamix.docker.manager/scripts/dockerupdate.php");
|
||||
$("#cmdArg1").remove();
|
||||
$("#cmdArg2").remove();
|
||||
$("#formStartStop").submit();
|
||||
}
|
||||
|
||||
function autoStart(container, event){
|
||||
document.getElementsByName("container")[0].value = container;
|
||||
$("#formStartStop").submit();
|
||||
}
|
||||
|
||||
function containerLogs(container){
|
||||
openWindow('/plugins/dynamix.docker.manager/scripts/docker&arg1=logs&arg2=--tail=350&arg3=-f&arg4=' + container, 'Log for: ' + container, 600, 900);
|
||||
}
|
||||
42
plugins/dynamix.docker.manager/log.htm
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/default-fonts.css">
|
||||
|
||||
<script src="/webGui/javascript/dynamix.js"></script>
|
||||
|
||||
<style >
|
||||
.logLine{font-family:bitstream;font-size:11px;text-align:left;}
|
||||
legend{font-size:12px;font-weight:bold;}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var dots = [];
|
||||
var span = [];
|
||||
function show_Wait(id){
|
||||
span[id] = document.getElementById("wait" + id);
|
||||
dots[id] = setInterval(function() {
|
||||
if ((span[id].innerHTML += '.').length == 18) {
|
||||
span[id].innerHTML = 'Please wait ';
|
||||
}}, 500);
|
||||
}
|
||||
|
||||
function stop_Wait(id){
|
||||
span[id].innerHTML = '';
|
||||
clearInterval( dots[id] );
|
||||
}
|
||||
|
||||
function addLog(logLine) {
|
||||
$(".logLine:last").append(logLine + "<br>");
|
||||
window.scrollTo(0,document.body.scrollHeight);
|
||||
}
|
||||
|
||||
function show_Prog(prog) {
|
||||
$(".progress:last").text(prog);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="margin:10;padding:0" id="content"><p class="logLine" id="logBody"></p></div>
|
||||
</body>
|
||||
</html>
|
||||
3
plugins/dynamix.docker.manager/scripts/docker
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
# Just a wrapper.
|
||||
exec /usr/bin/docker "$@"
|
||||
10
plugins/dynamix.docker.manager/scripts/docker_rm
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
# delete the docker image file
|
||||
if [ -f /boot/config/docker.cfg ]; then
|
||||
. /boot/config/docker.cfg
|
||||
if [ "${DOCKER_IMAGE_FILE}" != "" -a -f "${DOCKER_IMAGE_FILE}" ]; then
|
||||
echo "Deleting ${DOCKER_IMAGE_FILE} ..."
|
||||
rm "${DOCKER_IMAGE_FILE}"
|
||||
fi
|
||||
fi
|
||||
52
plugins/dynamix.docker.manager/scripts/dockerupdate.php
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/php
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
require_once("/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerClient.php");
|
||||
|
||||
$docker = new DockerTemplates();
|
||||
|
||||
foreach ($argv as $arg) {
|
||||
switch ($arg) {
|
||||
case '-v' : $docker->verbose = true; break;
|
||||
case 'check': $check = true; break;}
|
||||
}
|
||||
|
||||
if (!isset($check)) {
|
||||
echo " Updating templates... ";
|
||||
$docker->downloadTemplates();
|
||||
echo " Updating info... ";
|
||||
$docker->getAllInfo(true);
|
||||
echo " Done.";
|
||||
} else {
|
||||
require_once("/usr/local/emhttp/webGui/include/Wrappers.php");
|
||||
$client = new DockerClient();
|
||||
$update = new DockerUpdate();
|
||||
$notify = "/usr/local/emhttp/webGui/scripts/notify";
|
||||
$unraid = parse_plugin_cfg("dynamix",true);
|
||||
$server = strtoupper($var['NAME']);
|
||||
$output = $unraid['notify']['docker_notify'];
|
||||
|
||||
$list = $client->getDockerContainers();
|
||||
$info = $docker->getAllInfo();
|
||||
foreach ($list as $ct) {
|
||||
$name = $ct['Name'];
|
||||
$image = $ct['Image'];
|
||||
if ($info[$name]['updated'] == "false") {
|
||||
$new = $update->getRemoteVersion($docker->getTemplateValue($image, "Registry"), $image);
|
||||
exec("$notify -e 'Docker - $name [$new]' -s 'Notice [$server] - Docker update $new' -d 'A new version of $name is available' -i 'normal $output' -x");
|
||||
}
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
?>
|
||||
440
plugins/dynamix.docker.manager/styles/gh-buttons.css
Normal file
@@ -0,0 +1,440 @@
|
||||
/* ------------------------------------------
|
||||
* CSS3 GITHUB BUTTONS (Nicolas Gallagher)
|
||||
* Licensed under Unlicense
|
||||
* http://github.com/necolas/css3-github-buttons
|
||||
* --------------------------------------- */
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Base Button
|
||||
========================================================================== */
|
||||
|
||||
.button {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
display: inline-block;
|
||||
padding: 0.5em 1em;
|
||||
border: 1px solid #d4d4d4;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px 0 #fff;
|
||||
font:11px/normal sans-serif;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: #ececec;
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f4f4f4), to(#ececec));
|
||||
background-image: -moz-linear-gradient(#f4f4f4, #ececec);
|
||||
background-image: -ms-linear-gradient(#f4f4f4, #ececec);
|
||||
background-image: -o-linear-gradient(#f4f4f4, #ececec);
|
||||
background-image: linear-gradient(#f4f4f4, #ececec);
|
||||
-moz-background-clip: padding; /* for Firefox 3.6 */
|
||||
background-clip: padding-box;
|
||||
border-radius: 0.2em;
|
||||
/* IE hacks */
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus,
|
||||
.button:active,
|
||||
.button.active {
|
||||
border-color: #3072b3;
|
||||
border-bottom-color: #2a65a0;
|
||||
text-decoration: none;
|
||||
text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
|
||||
color: #fff;
|
||||
background-color: #3c8dde;
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#599bdc), to(#3072b3));
|
||||
background-image: -moz-linear-gradient(#599bdc, #3072b3);
|
||||
background-image: -o-linear-gradient(#599bdc, #3072b3);
|
||||
background-image: linear-gradient(#599bdc, #3072b3);
|
||||
}
|
||||
|
||||
.button:active,
|
||||
.button.active {
|
||||
border-color: #2a65a0;
|
||||
border-bottom-color: #3884cd;
|
||||
background-color: #3072b3;
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#3072b3), to(#599bdc));
|
||||
background-image: -moz-linear-gradient(#3072b3, #599bdc);
|
||||
background-image: -ms-linear-gradient(#3072b3, #599bdc);
|
||||
background-image: -o-linear-gradient(#3072b3, #599bdc);
|
||||
background-image: linear-gradient(#3072b3, #599bdc);
|
||||
}
|
||||
|
||||
/* overrides extra padding on button elements in Firefox */
|
||||
.button::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Button icons
|
||||
========================================================================== */
|
||||
|
||||
.button.icon:before {
|
||||
content: "";
|
||||
position: relative;
|
||||
top: 1px;
|
||||
float:left;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin: 0 0.75em 0 -0.25em;
|
||||
background: url(gh-icons.png) 0 99px no-repeat;
|
||||
}
|
||||
|
||||
.button.arrowup.icon:before { background-position: 0 0; }
|
||||
.button.arrowup.icon:hover:before,
|
||||
.button.arrowup.icon:focus:before,
|
||||
.button.arrowup.icon:active:before { background-position: -12px 0; }
|
||||
|
||||
.button.arrowdown.icon:before { background-position: 0 -12px; }
|
||||
.button.arrowdown.icon:hover:before,
|
||||
.button.arrowdown.icon:focus:before,
|
||||
.button.arrowdown.icon:active:before { background-position: -12px -12px; }
|
||||
|
||||
.button.arrowleft.icon:before { background-position: 0 -24px; }
|
||||
.button.arrowleft.icon:hover:before,
|
||||
.button.arrowleft.icon:focus:before,
|
||||
.button.arrowleft.icon:active:before { background-position: -12px -24px; }
|
||||
|
||||
.button.arrowright.icon:before { float:right; margin: 0 -0.25em 0 0.5em; background-position: 0 -36px; }
|
||||
.button.arrowright.icon:hover:before,
|
||||
.button.arrowright.icon:focus:before,
|
||||
.button.arrowright.icon:active:before { background-position: -12px -36px; }
|
||||
|
||||
.button.approve.icon:before { background-position: 0 -48px; }
|
||||
.button.approve.icon:hover:before,
|
||||
.button.approve.icon:focus:before,
|
||||
.button.approve.icon:active:before { background-position: -12px -48px; }
|
||||
|
||||
.button.add.icon:before { background-position: 0 -288px; }
|
||||
.button.add.icon:hover:before,
|
||||
.button.add.icon:focus:before,
|
||||
.button.add.icon:active:before { background-position: -12px -288px; }
|
||||
|
||||
.button.remove.icon:before { background-position: 0 -60px; }
|
||||
.button.remove.icon:hover:before,
|
||||
.button.remove.icon:focus:before,
|
||||
.button.remove.icon:active:before { background-position: -12px -60px; }
|
||||
|
||||
.button.log.icon:before { background-position: 0 -72px; }
|
||||
.button.log.icon:hover:before,
|
||||
.button.log.icon:focus:before,
|
||||
.button.log.icon:active:before { background-position: -12px -72px; }
|
||||
|
||||
.button.calendar.icon:before { background-position: 0 -84px; }
|
||||
.button.calendar.icon:hover:before,
|
||||
.button.calendar.icon:focus:before,
|
||||
.button.calendar.icon:active:before { background-position: -12px -84px; }
|
||||
|
||||
.button.chat.icon:before { background-position: 0 -96px; }
|
||||
.button.chat.icon:hover:before,
|
||||
.button.chat.icon:focus:before,
|
||||
.button.chat.icon:active:before { background-position: -12px -96px; }
|
||||
|
||||
.button.clock.icon:before { background-position: 0 -108px; }
|
||||
.button.clock.icon:hover:before,
|
||||
.button.clock.icon:focus:before,
|
||||
.button.clock.icon:active:before { background-position: -12px -108px; }
|
||||
|
||||
.button.settings.icon:before { background-position: 0 -120px; }
|
||||
.button.settings.icon:hover:before,
|
||||
.button.settings.icon:focus:before,
|
||||
.button.settings.icon:active:before { background-position: -12px -120px; }
|
||||
|
||||
.button.comment.icon:before { background-position: 0 -132px; }
|
||||
.button.comment.icon:hover:before,
|
||||
.button.comment.icon:focus:before,
|
||||
.button.comment.icon:active:before { background-position: -12px -132px; }
|
||||
|
||||
.button.fork.icon:before { background-position: 0 -144px; }
|
||||
.button.fork.icon:hover:before,
|
||||
.button.fork.icon:focus:before,
|
||||
.button.fork.icon:active:before { background-position: -12px -144px; }
|
||||
|
||||
.button.like.icon:before { background-position: 0 -156px; }
|
||||
.button.like.icon:hover:before,
|
||||
.button.like.icon:focus:before,
|
||||
.button.like.icon:active:before { background-position: -12px -156px; }
|
||||
|
||||
.button.favorite.icon:before { background-position: 0 -348px; }
|
||||
.button.favorite.icon:hover:before,
|
||||
.button.favorite.icon:focus:before,
|
||||
.button.favorite.icon:active:before { background-position: -12px -348px; }
|
||||
|
||||
.button.home.icon:before { background-position: 0 -168px; }
|
||||
.button.home.icon:hover:before,
|
||||
.button.home.icon:focus:before,
|
||||
.button.home.icon:active:before { background-position: -12px -168px; }
|
||||
|
||||
.button.key.icon:before { background-position: 0 -180px; }
|
||||
.button.key.icon:hover:before,
|
||||
.button.key.icon:focus:before,
|
||||
.button.key.icon:active:before { background-position: -12px -180px; }
|
||||
|
||||
.button.lock.icon:before { background-position: 0 -192px; }
|
||||
.button.lock.icon:hover:before,
|
||||
.button.lock.icon:focus:before,
|
||||
.button.lock.icon:active:before { background-position: -12px -192px; }
|
||||
|
||||
.button.unlock.icon:before { background-position: 0 -204px; }
|
||||
.button.unlock.icon:hover:before,
|
||||
.button.unlock.icon:focus:before,
|
||||
.button.unlock.icon:active:before { background-position: -12px -204px; }
|
||||
|
||||
.button.loop.icon:before { background-position: 0 -216px; }
|
||||
.button.loop.icon:hover:before,
|
||||
.button.loop.icon:focus:before,
|
||||
.button.loop.icon:active:before { background-position: -12px -216px; }
|
||||
|
||||
.button.search.icon:before { background-position: 0 -228px; }
|
||||
.button.search.icon:hover:before,
|
||||
.button.search.icon:focus:before,
|
||||
.button.search.icon:active:before { background-position: -12px -228px; }
|
||||
|
||||
.button.mail.icon:before { background-position: 0 -240px; }
|
||||
.button.mail.icon:hover:before,
|
||||
.button.mail.icon:focus:before,
|
||||
.button.mail.icon:active:before { background-position: -12px -240px; }
|
||||
|
||||
.button.move.icon:before { background-position: 0 -252px; }
|
||||
.button.move.icon:hover:before,
|
||||
.button.move.icon:focus:before,
|
||||
.button.move.icon:active:before { background-position: -12px -252px; }
|
||||
|
||||
.button.edit.icon:before { background-position: 0 -264px; }
|
||||
.button.edit.icon:hover:before,
|
||||
.button.edit.icon:focus:before,
|
||||
.button.edit.icon:active:before { background-position: -12px -264px; }
|
||||
|
||||
.button.pin.icon:before { background-position: 0 -276px; }
|
||||
.button.pin.icon:hover:before,
|
||||
.button.pin.icon:focus:before,
|
||||
.button.pin.icon:active:before { background-position: -12px -276px; }
|
||||
|
||||
.button.reload.icon:before { background-position: 0 -300px; }
|
||||
.button.reload.icon:hover:before,
|
||||
.button.reload.icon:focus:before,
|
||||
.button.reload.icon:active:before { background-position: -12px -300px; }
|
||||
|
||||
.button.rss.icon:before { background-position: 0 -312px; }
|
||||
.button.rss.icon:hover:before,
|
||||
.button.rss.icon:focus:before,
|
||||
.button.rss.icon:active:before { background-position: -12px -312px; }
|
||||
|
||||
.button.tag.icon:before { background-position: 0 -324px; }
|
||||
.button.tag.icon:hover:before,
|
||||
.button.tag.icon:focus:before,
|
||||
.button.tag.icon:active:before { background-position: -12px -324px; }
|
||||
|
||||
.button.trash.icon:before { background-position: 0 -336px; }
|
||||
.button.trash.icon:hover:before,
|
||||
.button.trash.icon:focus:before,
|
||||
.button.trash.icon:active:before { background-position: -12px -336px; }
|
||||
|
||||
.button.user.icon:before { background-position: 0 -360px; }
|
||||
.button.user.icon:hover:before,
|
||||
.button.user.icon:focus:before,
|
||||
.button.user.icon:active:before { background-position: -12px -360px; }
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Button extensions
|
||||
========================================================================== */
|
||||
|
||||
/* Primary button
|
||||
========================================================================== */
|
||||
|
||||
.button.primary {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Danger button
|
||||
========================================================================== */
|
||||
|
||||
.button.danger {
|
||||
color: #900;
|
||||
}
|
||||
|
||||
.button.danger:hover,
|
||||
.button.danger:focus,
|
||||
.button.danger:active {
|
||||
border-color: #b53f3a;
|
||||
border-bottom-color: #a0302a;
|
||||
color: #fff;
|
||||
background-color: #dc5f59;
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#dc5f59), to(#b33630));
|
||||
background-image: -moz-linear-gradient(#dc5f59, #b33630);
|
||||
background-image: -ms-linear-gradient(#dc5f59, #b33630);
|
||||
background-image: -o-linear-gradient(#dc5f59, #b33630);
|
||||
background-image: linear-gradient(#dc5f59, #b33630);
|
||||
}
|
||||
|
||||
.button.danger:active,
|
||||
.button.danger.active {
|
||||
border-color: #a0302a;
|
||||
border-bottom-color: #bf4843;
|
||||
background-color: #b33630;
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b33630), to(#dc5f59));
|
||||
background-image: -moz-linear-gradient(#b33630, #dc5f59);
|
||||
background-image: -ms-linear-gradient(#b33630, #dc5f59);
|
||||
background-image: -o-linear-gradient(#b33630, #dc5f59);
|
||||
background-image: linear-gradient(#b33630, #dc5f59);
|
||||
}
|
||||
|
||||
/* Green button
|
||||
========================================================================== */
|
||||
|
||||
.button.green {
|
||||
color: #009900;
|
||||
}
|
||||
|
||||
.button.blue {
|
||||
color: #0519FF;
|
||||
}
|
||||
|
||||
.button.blue:hover,
|
||||
.button.blue:focus,
|
||||
.button.blue:active {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.button.green:hover,
|
||||
.button.green:focus,
|
||||
.button.green:active {
|
||||
color: #FFF;
|
||||
text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.25);
|
||||
background-color: #60B044;
|
||||
background-image: linear-gradient(#8ADD6D, #60B044);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #5CA941;
|
||||
}
|
||||
|
||||
.button.green:active,
|
||||
.button.green.active {
|
||||
color: #FFF;
|
||||
text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.25);
|
||||
background-color: #60B044;
|
||||
background-image: linear-gradient(#60B044, #8ADD6D);
|
||||
background-repeat: repeat-x;
|
||||
border-color: #5CA941;
|
||||
}
|
||||
|
||||
/* Pill button
|
||||
========================================================================== */
|
||||
|
||||
.button.pill {
|
||||
border-radius: 50em;
|
||||
}
|
||||
|
||||
/* Disabled button
|
||||
========================================================================== */
|
||||
|
||||
.button.disable {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Big button
|
||||
========================================================================== */
|
||||
|
||||
.button.big {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.button.big.icon:before {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Button groups
|
||||
========================================================================== */
|
||||
|
||||
/* Standard group
|
||||
========================================================================== */
|
||||
|
||||
.button-group {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
/* IE hacks */
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
}
|
||||
|
||||
.button + .button,
|
||||
.button + .button-group,
|
||||
.button-group + .button,
|
||||
.button-group + .button-group {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.button-group li {
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.button-group .button {
|
||||
float: left;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.button-group > .button:not(:first-child):not(:last-child),
|
||||
.button-group li:not(:first-child):not(:last-child) .button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.button-group > .button:first-child,
|
||||
.button-group li:first-child .button {
|
||||
margin-left: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.button-group > .button:last-child,
|
||||
.button-group li:last-child > .button {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
/* Minor group
|
||||
========================================================================== */
|
||||
|
||||
.button-group.minor-group .button {
|
||||
border: 1px solid #d4d4d4;
|
||||
text-shadow: none;
|
||||
background-image: none;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.button-group.minor-group .button:hover,
|
||||
.button-group.minor-group .button:focus {
|
||||
background-color: #599bdc;
|
||||
}
|
||||
|
||||
.button-group.minor-group .button:active,
|
||||
.button-group.minor-group .button.active {
|
||||
background-color: #3072b3;
|
||||
}
|
||||
|
||||
.button-group.minor-group .button.icon:before {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
Button container (mixing buttons and groups, e.g., nav bar)
|
||||
========================================================================== */
|
||||
|
||||
.button-container .button,
|
||||
.button-container .button-group {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
34
plugins/dynamix.plugin.manager/PluginInstall.page
Normal file
@@ -0,0 +1,34 @@
|
||||
Menu="Plugins"
|
||||
Title="Install Plugin"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<script>
|
||||
function installPlugin(name) {
|
||||
var file = name.trim();
|
||||
if (file) openBox('/plugins/dynamix.plugin.manager/scripts/plugin&arg1=install&arg2='+file,'Install Plugin',600,900,true);
|
||||
}
|
||||
</script>
|
||||
**Enter URL of remote plugin file or local plugin file**
|
||||
|
||||
<form name="plugin_install" method="POST" target="progressFrame">
|
||||
<input type="text" name="file" id="plugin_file" maxlength="1024" value="" style="width:33%">
|
||||
<input type="button" value="Install" onclick="installPlugin(this.form.file.value)">
|
||||
</form>
|
||||
|
||||
> To download and install a plugin, enter the plg file URL and click **Install**. A window will open
|
||||
> that displays install progress. Do not close this window until install has completed. You may also specify
|
||||
> the local file name of an extension.
|
||||
|
||||
**Select local plugin file**
|
||||
<div id="plugin_tree" class="textarea"></div>
|
||||
49
plugins/dynamix.plugin.manager/Plugins.page
Normal file
@@ -0,0 +1,49 @@
|
||||
Menu="Tasks:50"
|
||||
Type="xmenu"
|
||||
Title="Installed Plugins"
|
||||
Tabs="true"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
|
||||
<?
|
||||
// Remove stale /tmp/plugin/*.plg entries
|
||||
$tmp_stale = ($path != $prev);
|
||||
if ($tmp_stale) foreach (glob("/tmp/plugins/*.{plg,txt}", GLOB_NOSORT+GLOB_BRACE) as $entry) if (!file_exists("/var/log/plugins/".basename($entry))) @unlink($entry);
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css">
|
||||
<style>
|
||||
#plugin_tree{width:33%;height:200px;overflow:scroll;}
|
||||
</style>
|
||||
<script src="/webGui/javascript/jquery.filetree.js"></script>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{stale:'<?=$tmp_stale?>'},function(data) {
|
||||
if (data) $('#plugin_list').html(data);
|
||||
$('#plugin_table').tablesorter({headers:{0:{sorter:false},5:{sorter:false}}});
|
||||
$('.desc_readmore').readmore({maxHeight:58});
|
||||
});
|
||||
$('#plugin_tree').fileTree({root:'/boot/',filter:'plg'}, function(file) {$('#plugin_file').val(file);});
|
||||
$('.tabs').append("<span class='status vhshift'><input type='button' value='Check for Updates' onclick='openBox(\"/plugins/dynamix.plugin.manager/scripts/plugin&arg1=checkall\",\"Plugin Update Check\",490,430,true)'></span>");
|
||||
});
|
||||
</script>
|
||||
|
||||
<blockquote class="inline_help ontop">
|
||||
Click <strong><big>check for updates</big></strong> to check all plugins. This page might take some time to load depending on your internet connection and how many plugins need to be checked.
|
||||
</blockquote>
|
||||
|
||||
<table class='tablesorter shift plugins' id='plugin_table'>
|
||||
<thead><tr><th></th><th>Plugin</th><th>Author</th><th>Version</th><th>Status</th><th></th></tr></thead>
|
||||
<tbody id='plugin_list'><tr><td colspan='6'><br><i class="fa fa-spinner fa-spin icon"></i><em>Please wait, retrieving plugin information ...</em></td><tr></tbody>
|
||||
</table>
|
||||
11
plugins/dynamix.plugin.manager/PluginsAvailable.page-
Normal file
@@ -0,0 +1,11 @@
|
||||
Menu="Plugins"
|
||||
Title="Available Plugins"
|
||||
---
|
||||
Here should come an online repository of available plugins, which can be downloaded from the web
|
||||
and instantly installed.
|
||||
|
||||
Problably this should be hosted on a wiki page of Limetech, with control over which plugins can be
|
||||
displayed. This may be combined with a validation procedure to ensure that published plugins are
|
||||
fully compatible with unRAID.
|
||||
|
||||
For the time being only manual installation of plugins exist.
|
||||
39
plugins/dynamix.plugin.manager/PluginsError.page
Normal file
@@ -0,0 +1,39 @@
|
||||
Menu="Plugins"
|
||||
Title="Plugin File Install Errors"
|
||||
Cond="glob('/boot/config/plugins-error/*.plg')"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
|
||||
<?
|
||||
require_once('plugins/dynamix.plugin.manager/include/PluginHelpers.php');
|
||||
|
||||
echo "<table class='tablesorter' id='plugin_table'><thead>";
|
||||
echo "<tr><th>Plugin File</th><th>Status</th></tr>";
|
||||
echo "</thead><tbody>";
|
||||
|
||||
foreach (glob("/boot/config/plugins-error/*.plg", GLOB_NOSORT) as $plugin_file) {
|
||||
// status info
|
||||
$status_info = "ERROR<br />" . make_link("delete", $plugin_file);
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td>{$plugin_file}</td>";
|
||||
echo "<td><span style=\"white-space:nowrap\">{$status_info}</span></td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
echo "</tbody></table>";
|
||||
?>
|
||||
|
||||
> These plugins were not installed because of some kind of installation error. You should delete these
|
||||
> plugins and then **reboot** your server.*
|
||||
70
plugins/dynamix.plugin.manager/PluginsStale.page
Normal file
@@ -0,0 +1,70 @@
|
||||
Menu="Plugins"
|
||||
Title="Plugin History"
|
||||
Cond="glob('/boot/config/plugins-stale/*.plg')"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
|
||||
<?
|
||||
require_once('plugins/dynamix.plugin.manager/include/PluginHelpers.php');
|
||||
|
||||
echo "<table class='tablesorter plugins shift' id='plugin_table'><thead>";
|
||||
echo "<tr><th></th><th>Plugin</th><th>Author</th><th>Version</th><th>Status</th><th></th></tr>";
|
||||
echo "</thead><tbody>";
|
||||
|
||||
foreach (glob("/boot/config/plugins-stale/*.plg", GLOB_NOSORT) as $plugin_file) {
|
||||
// plugin name
|
||||
$name = plugin("name", $plugin_file);
|
||||
if ($name === false) $name = basename($plugin_file, ".plg");
|
||||
|
||||
// icon
|
||||
$icon = icon($name);
|
||||
|
||||
// desc
|
||||
$readme = "plugins/{$name}/README.md";
|
||||
if (file_exists($readme))
|
||||
$desc = Markdown(file_get_contents($readme));
|
||||
else
|
||||
$desc = Markdown("**{$name}**");
|
||||
|
||||
// author
|
||||
$author = plugin("author", $plugin_file);
|
||||
if ($author === false) $author = "anonymous";
|
||||
|
||||
// version
|
||||
$version = plugin("version", $plugin_file);
|
||||
if ($version === false) $version = "unknown";
|
||||
|
||||
// version info
|
||||
$version_info = $version;
|
||||
|
||||
// status info
|
||||
$status_info = "STALE";
|
||||
|
||||
// action
|
||||
$action = make_link("delete", $plugin_file);
|
||||
|
||||
// echo our plugin information
|
||||
echo "<tr>";
|
||||
echo "<td><img src=\"/{$icon}\" /></td>";
|
||||
echo "<td>{$desc}</td>";
|
||||
echo "<td>{$author}</td>";
|
||||
echo "<td>{$version_info}</td>";
|
||||
echo "<td>{$status_info}</td>";
|
||||
echo "<td>{$action}</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
echo "</tbody></table>";
|
||||
?>
|
||||
> These plugins were not installed because newer code already exists. It is safe to simply delete these.
|
||||
BIN
plugins/dynamix.plugin.manager/icons/availableplugins.png
Normal file
|
After Width: | Height: | Size: 829 B |
BIN
plugins/dynamix.plugin.manager/icons/installedplugins.png
Normal file
|
After Width: | Height: | Size: 759 B |
BIN
plugins/dynamix.plugin.manager/icons/installplugin.png
Normal file
|
After Width: | Height: | Size: 758 B |
BIN
plugins/dynamix.plugin.manager/icons/pluginhistory.png
Normal file
|
After Width: | Height: | Size: 746 B |
BIN
plugins/dynamix.plugin.manager/images/dynamix.plugin.manager.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
47
plugins/dynamix.plugin.manager/include/PluginHelpers.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
// Invoke the plugin command with indicated method
|
||||
function plugin($method, $arg) {
|
||||
exec("/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/plugin $method $arg", $output, $retval);
|
||||
if ($retval != 0) return false;
|
||||
return implode("\n", $output);
|
||||
}
|
||||
|
||||
function make_link($method, $arg) {
|
||||
$id = basename($arg, ".plg").$method;
|
||||
$check = $method=='update' ? "" : "<input type='checkbox' onClick='document.getElementById(\"$id\").disabled=!this.checked'>";
|
||||
$disabled = $check ? " disabled" : "";
|
||||
$cmd = $method == "delete" ? "/plugins/dynamix.plugin.manager/scripts/plugin_rm&arg1=$arg" : "/plugins/dynamix.plugin.manager/scripts/plugin&arg1=$method&arg2=$arg";
|
||||
return "{$check}<input type='button' id='$id' value='{$method}' onclick='openBox(\"{$cmd}\",\"{$method} Plugin\",600,900,true)'{$disabled}>";
|
||||
}
|
||||
|
||||
// trying our best to find an icon
|
||||
function icon($name) {
|
||||
// this should be the default location and name
|
||||
$icon = "plugins/{$name}/images/{$name}.png";
|
||||
if (file_exists($icon)) return $icon;
|
||||
// try alternatives if default is not present
|
||||
$plugin = strtok($name, '.');
|
||||
$icon = "plugins/{$plugin}/images/{$plugin}.png";
|
||||
if (file_exists($icon)) return $icon;
|
||||
$icon = "plugins/{$plugin}/images/{$name}.png";
|
||||
if (file_exists($icon)) return $icon;
|
||||
$icon = "plugins/{$plugin}/{$plugin}.png";
|
||||
if (file_exists($icon)) return $icon;
|
||||
$icon = "plugins/{$plugin}/{$name}.png";
|
||||
if (file_exists($icon)) return $icon;
|
||||
// last resort - plugin manager icon
|
||||
return "plugins/dynamix.plugin.manager/images/dynamix.plugin.manager.png";
|
||||
}
|
||||
?>
|
||||
27
plugins/dynamix.plugin.manager/include/ShowChanges.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/default-fonts.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/default-white.css">
|
||||
</head>
|
||||
<body style="margin:14px 10px">
|
||||
<?
|
||||
require_once 'webGui/include/Markdown.php';
|
||||
|
||||
$file = $_GET['file'];
|
||||
if (file_exists($file)) echo Markdown(file_get_contents($file)); else echo Markdown("*No release notes available!*");
|
||||
?>
|
||||
<br><center><input type="button" value="Done" onclick="top.Shadowbox.close()"></center>
|
||||
</body>
|
||||
</html>
|
||||
81
plugins/dynamix.plugin.manager/include/ShowPlugins.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
require_once('webGui/include/Markdown.php');
|
||||
require_once('plugins/dynamix.plugin.manager/include/PluginHelpers.php');
|
||||
|
||||
foreach (glob("/var/log/plugins/*.plg", GLOB_NOSORT) as $plugin_link) {
|
||||
// only consider symlinks
|
||||
$plugin_file = @readlink($plugin_link);
|
||||
if ($plugin_file === false) continue;
|
||||
// plugin name
|
||||
$name = plugin("name", $plugin_file);
|
||||
if ($name === false) $name = basename($plugin_file, ".plg");
|
||||
// link/icon
|
||||
$icon = icon($name);
|
||||
if ($launch = plugin("launch", $plugin_file))
|
||||
$link = "<a href='/$launch'><img src='/$icon' width='48px'/></a>";
|
||||
else
|
||||
$link = "<img src='/{$icon}' width='48px'/>";
|
||||
// desc
|
||||
$readme = "plugins/{$name}/README.md";
|
||||
if (file_exists($readme))
|
||||
$desc = Markdown(file_get_contents($readme));
|
||||
else
|
||||
$desc = Markdown("**{$name}**");
|
||||
// author
|
||||
$author = plugin("author", $plugin_file);
|
||||
if ($author === false) $author = "anonymous";
|
||||
// version
|
||||
$version = plugin("version", $plugin_file);
|
||||
if ($version === false) $version = "unknown";
|
||||
// version info
|
||||
$version_info = $version;
|
||||
// status info
|
||||
$status_info = "no update";
|
||||
$changes_file = $plugin_file;
|
||||
$URL = plugin("pluginURL", $plugin_file);
|
||||
if ($URL !== false) {
|
||||
$filename = "/tmp/plugins/".basename($URL);
|
||||
if (file_exists($filename)) {
|
||||
$latest = plugin("version", $filename);
|
||||
if ($latest && strcmp($latest, $version) > 0) {
|
||||
$version_info .= "<br><span class='red-text'>{$latest}</span>";
|
||||
$status_info = make_link("update", basename($plugin_file));
|
||||
$changes_file = $filename;
|
||||
} else {
|
||||
$status_info = "up-to-date";
|
||||
}
|
||||
} else {
|
||||
if ($_GET['stale']) $status_info = "unknown";
|
||||
}
|
||||
}
|
||||
$changes = plugin("changes", $changes_file);
|
||||
if ($changes !== false) {
|
||||
$txtfile = "/tmp/plugins/".basename($plugin_file,'.plg').".txt";
|
||||
file_put_contents($txtfile, $changes);
|
||||
$version_info .= " <a href='#' title='View Release Notes' onclick=\"openBox('/plugins/dynamix.plugin.manager/include/ShowChanges.php?file=".urlencode($txtfile)."','Release Notes',600,900); return false\"><img src='/webGui/images/information.png' class='icon'></a>";
|
||||
}
|
||||
// action
|
||||
$action = strpos($plugin_file, "/usr/local/emhttp/plugins") !== 0 ? make_link("remove", basename($plugin_file)) : "built-in";
|
||||
// write plugin information
|
||||
echo "<tr>";
|
||||
echo "<td style='vertical-align:top'><p>{$link}</p></td>";
|
||||
echo "<td><span class='desc_readmore' style='display:block'>{$desc}</span></td>";
|
||||
echo "<td>{$author}</td>";
|
||||
echo "<td>{$version_info}</td>";
|
||||
echo "<td>{$status_info}</td>";
|
||||
echo "<td>{$action}</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
?>
|
||||
540
plugins/dynamix.plugin.manager/scripts/plugin
Executable file
@@ -0,0 +1,540 @@
|
||||
#!/usr/bin/php
|
||||
<?PHP
|
||||
// Copyright 2015, Lime Technology LLC.
|
||||
// License: GPLv2 only
|
||||
//
|
||||
// Program updates made by Bergware International (December 2014)
|
||||
$usage = <<<EOF
|
||||
Process plugin files.
|
||||
|
||||
Usage: plugin install PLUGIN-FILE
|
||||
install a plugin
|
||||
|
||||
PLUGIN-FILE is a plugin definition XML file with ".plg" extension.
|
||||
|
||||
PLUGIN-FILE can be a local file, or a URL. If a URL, the plugin file is first downloaded to /tmp/plugins.
|
||||
|
||||
This command will process all FILE elements in PLUGIN-FILE which are tagged with the "install" method (or
|
||||
that have no method tag).
|
||||
|
||||
This command has two major use cases:
|
||||
|
||||
1) Invoked at system startup by /etc/rc.d/rc.local on each .plg file found int /boot/config/plugins.
|
||||
|
||||
Upon success we register the plugin as "installed" by creating a symlink to it in /var/log/plugins.
|
||||
|
||||
If any kind of error, we move the file to /boot/config/plugins-error.
|
||||
|
||||
If a symlink already exists for the plugin file, this indicates a plugin replacing a "built-in" plugin. In
|
||||
this case, if the version of PLUGIN-FILE is newer than the built-in plugin, we go ahead and install normally;
|
||||
otherwise, we move to /boot/config/plugins-stale.
|
||||
|
||||
2) Invoked manually or via Plugin Manager for a .plg file not in /boot/config/plugins.
|
||||
|
||||
If a symlink already exists for the plugin file, this indicates a plugin update. In this case, if the version of
|
||||
PLUGIN-FILE is newer than the built-in plugin, we go ahead and install normally and then move the old plugin
|
||||
to /boot/config/plugins-stale.
|
||||
|
||||
Upon success we copy PLUGIN-FILE to /boot/config/plugins and register it as "installed" by creating a
|
||||
symlink to it in /var/log/plugins.
|
||||
|
||||
Usage: plugin remove PLUGIN
|
||||
remove a plugin
|
||||
|
||||
PLUGIN is the file basename of a plugin, e.g., "myplugin.plg".
|
||||
|
||||
If PLUGIN is found in /var/log/plugins then this command will process all FILE elements in PLUGIN which are
|
||||
tagged with the "remove" method. Upon success we delete /var/log/plugins/PLUGIN and move the plugin
|
||||
file to /boot/config/plugins-removed
|
||||
|
||||
Usage: plugin check PLUGIN
|
||||
check and output the latest version of PLUGIN
|
||||
|
||||
We exract the pluginURL attribute from PLUGIN and use it to download (presumably the latest
|
||||
version of) the plugin file to /tmp/plugins/ directory, and then output the version string.
|
||||
|
||||
Usage: plugin checkall
|
||||
check all installed plugins
|
||||
|
||||
Runs 'plugin check PLUGIN' for each plugin file linked-to in /var/lib/plugins.
|
||||
|
||||
Usage: plugin update PLUGIN
|
||||
update the plugin
|
||||
|
||||
We look for the new plugin in /tmp/plugins/ directory. If found then we first execute the "install"
|
||||
method of each FILE in the new plugin. (If necessary, a plugin can detect that this is an
|
||||
"update" by checking for the existence of /var/log/plugins/PLUGIN.) If successful, we
|
||||
delete the "old" plugin file from /boot/config/plugins/, copy the "new" plugin file from
|
||||
/tmp/plugins/ to /boot/config/plugins/, and finally create the new symlink.
|
||||
|
||||
Note: to support `plugin check` and `plugin update` the plugin file must contain both "pluginURL" and
|
||||
"version" attributes.
|
||||
|
||||
Usage: plugin [attribute name] PLUGIN
|
||||
|
||||
Any method which is not one of the actions listed above is assumed to be the name of an attribute of
|
||||
the <PLUGIN> tag within PLUGIN-FILE. If the attribute exists, its value (a string) is output and the command
|
||||
exit status is 0. If the attribute does not exist, command exit status is 1.
|
||||
|
||||
The plugin manager recognizes this set of attributes for the <PLUGIN> tag:
|
||||
|
||||
name - MANDATORY plugin name, e.g., "myplugin" or "my-plugin" etc.
|
||||
This tag defines the name of the plugin. The name should omit embedded information such as architecture,
|
||||
version, author, etc.
|
||||
|
||||
The plugin should create a directory under `/usr/local/emhttp/plugins` named after
|
||||
the plugin, e.g., `/usr/local/emhttp/plugins/myplugin`. Any webGui pages, icons, README files, etc, should
|
||||
be created inside this directory.
|
||||
|
||||
The plugin should also create a directory under `/boot/config/plugins` named after the plugin, e.g.,
|
||||
`/boot/config/plugins/myplugin`. Here is where you store plugin-specific files such as a configuration
|
||||
file and icon file. Note that this directory exists on the users USB Flash device and writes should be
|
||||
minimized.
|
||||
|
||||
Upon successful installation, the plugin manager will copy the input plugin file to `/boot/config/plugins`
|
||||
on the users USB Flash device, and create a symlink in `/var/log/plugins` also named after the plugin,
|
||||
e.g., `/var/log/plugins/myplugin`. Each time the unRaid server is re-booted, all plugins stored
|
||||
in `/boot/config/plugins` are automatically installed; plugin authors should be aware of this behavior.
|
||||
|
||||
author - OPTIONAL
|
||||
Whatever you put here will show up under the **Author** column in the Plugin List. If this attribute
|
||||
is omitted we display "anonymous".
|
||||
|
||||
version - MANDATORY
|
||||
Use a string suitable for comparison to determine if one version is older/same/newer than another version.
|
||||
Any format is acceptable but LimeTech uses "YYYY.MM.DD", e.g., "2014.02.18" (if multiple versions happen
|
||||
to get posted on the same day we add a letter suffix, e.g., "2014.02.18a").
|
||||
|
||||
pluginURL - OPTIONAL but MANDATORY if you want "check for updates" to work with your plugin
|
||||
This is the URL of the plugin file to download and extract the **version** attribute from to determine if
|
||||
this is a new version.
|
||||
|
||||
system - OPTIONAL
|
||||
If present the plugin is considered a system plugin and is installed in '/boot/plugins'.
|
||||
User plugins get installed in '/boot/config/plugins', which is the default.
|
||||
|
||||
More attributes may be defined in the future.
|
||||
|
||||
Here is the set of directories and files used by the plugin system:
|
||||
|
||||
/boot/config/plugins/
|
||||
This directory contains the plugin files for plugins to be (re)installed at boot-time. Upon
|
||||
successful `plugin install`, the plugin file is copied here (if not here already). Upon successful
|
||||
`plugin remove`, the plugin file is deleted from here.
|
||||
|
||||
/boot/config/plugins-error/
|
||||
This directory contains plugin files that failed to install.
|
||||
|
||||
/boot/config/plugins-removed/
|
||||
This directory contains plugin files that have been removed.
|
||||
|
||||
/boot/config/plugins-stale/
|
||||
This directory contains plugin files that failed to install because a newer version of the same plugin is
|
||||
already installed.
|
||||
|
||||
/tmp/plugins/
|
||||
This directory is used as a target for downloaded plugin files. The `plugin check` operation
|
||||
downloads the plugin file here and the `plugin update` operation looks for the plugin to update here.
|
||||
|
||||
/var/log/plugins/
|
||||
This directory contains a symlink named after the plugin name (not the plugin file name) which points to
|
||||
the actual plugin file used to install the plugin. The existence of this file indicates successful
|
||||
install of the plugin.
|
||||
|
||||
EOF;
|
||||
|
||||
// Download a file from a URL.
|
||||
// Returns TRUE if success else FALSE and fills in error.
|
||||
//
|
||||
function download($URL, $name, &$error) {
|
||||
if ($file = popen("wget --progress=dot -O $name $URL 2>&1", 'r')) {
|
||||
echo "plugin: downloading: $URL ...\r";
|
||||
$level = -1;
|
||||
while (!feof($file)) {
|
||||
if (preg_match("/\d+%/", fgets($file), $matches)) {
|
||||
$percentage = substr($matches[0],0,-1);
|
||||
if ($percentage > $level) {
|
||||
echo "plugin: downloading: $URL ... $percentage%\r";
|
||||
$level = $percentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pclose($file) != -1) {
|
||||
echo "plugin: downloading: $URL ... done\n";
|
||||
return true;
|
||||
} else {
|
||||
echo "plugin: downloading: $URL ... failed\n";
|
||||
$error = "wget: $URL download failure";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$error = "wget: $URL failed to open";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Deal with logging message.
|
||||
//
|
||||
function logger($message) {
|
||||
// echo "$message\n";
|
||||
shell_exec( "logger $message");
|
||||
}
|
||||
|
||||
// Interpret a plugin file
|
||||
// Returns TRUE if success, else FALSE and fills in error string.
|
||||
//
|
||||
// If a FILE element does not have a Method attribute, we treat as though Method is "install".
|
||||
// A FILE Method attribute can list multiple methods separated by spaces in which case that file
|
||||
// is processed for any of those methods.
|
||||
//
|
||||
function plugin($method, $plugin_file, &$error) {
|
||||
$methods = array("install", "remove");
|
||||
|
||||
// parse plugin definition XML file
|
||||
$xml = simplexml_load_file($plugin_file, NULL, LIBXML_NOCDATA);
|
||||
if ($xml === false) {
|
||||
$error = "xml parse error";
|
||||
return false;
|
||||
}
|
||||
|
||||
// dump
|
||||
if ($method == "dump") {
|
||||
// echo $xml->asXML();
|
||||
echo print_r($xml);
|
||||
return true;
|
||||
}
|
||||
|
||||
// release notes
|
||||
if ($method == 'changes') {
|
||||
if (!$xml->CHANGES) return false;
|
||||
return trim($xml->CHANGES);
|
||||
}
|
||||
|
||||
// check if $method is an attribute
|
||||
if (!in_array($method, $methods)) {
|
||||
foreach ($xml->attributes() as $key => $value) {
|
||||
if ($method == $key) return $value;
|
||||
}
|
||||
$error = "$method attribute not present";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process FILE elements in order
|
||||
//
|
||||
foreach ($xml->FILE as $file) {
|
||||
// skip if not our $method
|
||||
if (isset($file->attributes()->Method)) {
|
||||
if (!in_array($method, explode(" ", $file->attributes()->Method))) continue;
|
||||
} else if ($method != "install") continue;
|
||||
|
||||
// Name can be missing but only makes sense if Run attribute is present
|
||||
$name = $file->attributes()->Name;
|
||||
if ($name) {
|
||||
// Ensure parent directory exists
|
||||
//
|
||||
if (!file_exists(dirname($name))) {
|
||||
if (!mkdir(dirname($name), 0770, true)) {
|
||||
$error = "plugin: error: unable to create parent directory for $name";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If file already exists, do not overwrite
|
||||
//
|
||||
if (file_exists($name)) {
|
||||
logger("plugin: skipping: $name already exists");
|
||||
} elseif ($file->LOCAL) {
|
||||
// Create the file
|
||||
//
|
||||
// for local file, just copy it
|
||||
logger("plugin: creating: $name - copying LOCAL file $file->LOCAL");
|
||||
if (!copy($file->LOCAL, $name)) {
|
||||
$error = "unable to copy LOCAL file: $name";
|
||||
@unlink($name);
|
||||
return false;
|
||||
}
|
||||
} elseif ($file->INLINE) {
|
||||
// for inline file, create with inline contents
|
||||
logger("plugin: creating: $name - from INLINE content");
|
||||
$contents = trim($file->INLINE).PHP_EOL;
|
||||
if ($file->attributes()->Type == 'base64') {
|
||||
logger("plugin: decoding: $name as base64");
|
||||
$contents = base64_decode($contents);
|
||||
if ($contents === false) {
|
||||
$error = "unable to decode inline base64: $name";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!file_put_contents($name, $contents)) {
|
||||
$error = "unable to create file: $name";
|
||||
@unlink($name);
|
||||
return false;
|
||||
}
|
||||
} elseif ($file->URL) {
|
||||
// for download file, download and maybe verify the file MD5
|
||||
logger("plugin: creating: $name - downloading from URL $file->URL");
|
||||
if (download($file->URL, $name, $error) === false) {
|
||||
@unlink($name);
|
||||
return false;
|
||||
}
|
||||
if ($file->MD5) {
|
||||
logger("plugin: checking: $name - MD5");
|
||||
if (md5_file($name) != $file->MD5) {
|
||||
$error = "bad file MD5: $name";
|
||||
unlink($name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Maybe change the file mode
|
||||
//
|
||||
if ($file->attributes()->Mode) {
|
||||
// if file has 'Mode' attribute, apply it
|
||||
$mode = $file->attributes()->Mode;
|
||||
logger("plugin: setting: $name - mode to $mode");
|
||||
if (!chmod($name, octdec($mode))) {
|
||||
$error = "chmod failure: $name";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Maybe "run" the file now
|
||||
//
|
||||
if ($file->attributes()->Run) {
|
||||
$command = $file->attributes()->Run;
|
||||
if ($name) {
|
||||
logger("plugin: running: $name");
|
||||
system("$command $name", $retval);
|
||||
} elseif ($file->LOCAL) {
|
||||
logger("plugin: running: $file->LOCAL");
|
||||
system("$command $file->LOCAL", $retval);
|
||||
} elseif ($file->INLINE) {
|
||||
logger("plugin: running: 'anonymous'");
|
||||
$inline = escapeshellarg($file->INLINE);
|
||||
passthru("echo $inline | $command", $retval);
|
||||
}
|
||||
if ($retval) {
|
||||
$error = "run failed: $command retval: $retval";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function move($src_file, $tar_dir) {
|
||||
@mkdir($tar_dir);
|
||||
return rename($src_file, $tar_dir."/".basename($src_file));
|
||||
}
|
||||
|
||||
// In following code,
|
||||
// $plugin - is a basename of a plugin, eg, "myplugin.plg"
|
||||
// $plugin_file - is an absolute path, eg, "/boot/config/plugins/myplugin.plg"
|
||||
//
|
||||
if ($argc < 2) {
|
||||
echo $usage;
|
||||
exit(1);
|
||||
}
|
||||
$method = $argv[1];
|
||||
|
||||
// plugin checkall
|
||||
// check all installed plugins
|
||||
//
|
||||
if ($method == "checkall") {
|
||||
foreach (glob("/var/log/plugins/*", GLOB_NOSORT) as $link) {
|
||||
// only consider symlinks
|
||||
$installed_plugin_file = @readlink($link);
|
||||
if ($installed_plugin_file === false) continue;
|
||||
if (plugin("pluginURL", $installed_plugin_file, $error) === false) continue;
|
||||
$plugin = basename($installed_plugin_file);
|
||||
echo "plugin: checking $plugin ...\n";
|
||||
exec(realpath($argv[0]) . " check $plugin", $output, $retval);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ($argc < 3) {
|
||||
echo $usage;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// plugin install [plugin_file]
|
||||
// cases:
|
||||
// a) dirname of [plugin_file] is /boot/config/plugins (system startup)
|
||||
// b) [plugin_file] is a URL
|
||||
// c) dirname of [plugin_file] is not /boot/config/plugins
|
||||
//
|
||||
if ($method == "install") {
|
||||
echo "plugin: installing: $argv[2]\n";
|
||||
// check for URL
|
||||
if ((strpos($argv[2], "http://") === 0) || (strpos($argv[2], "https://") === 0)) {
|
||||
$pluginURL = $argv[2];
|
||||
echo "plugin: downloading $pluginURL\n";
|
||||
$plugin_file = "/tmp/plugins/".basename($pluginURL);
|
||||
if (!download($pluginURL, $plugin_file, $error)) {
|
||||
echo "plugin: $error\n";
|
||||
@unlink($plugin_file);
|
||||
exit(1);
|
||||
}
|
||||
} else
|
||||
$plugin_file = realpath($argv[2]);
|
||||
|
||||
$plugin = basename($plugin_file);
|
||||
|
||||
// check for re-install
|
||||
$installed_plugin_file = @readlink("/var/log/plugins/$plugin");
|
||||
if ($installed_plugin_file !== false) {
|
||||
if ($plugin_file == $installed_plugin_file) {
|
||||
echo "plugin: not re-installing same plugin\n";
|
||||
exit(1);
|
||||
}
|
||||
// must have version attributes for re-install
|
||||
$version = plugin("version", $plugin_file, $error);
|
||||
if ($version === false) {
|
||||
echo "plugin: $error\n";
|
||||
exit(1);
|
||||
}
|
||||
$installed_version = plugin("version", $installed_plugin_file, $error);
|
||||
if ($installed_version === false) {
|
||||
echo "plugin: $error\n";
|
||||
exit(1);
|
||||
}
|
||||
// do not re-install if same plugin already installed or has higher version
|
||||
if (strcmp($version, $installed_version) <= 0) {
|
||||
if (strcmp($version, $installed_version) < 0) {
|
||||
echo "plugin: not installing older version\n";
|
||||
}
|
||||
if (strcmp($plugin_version, $installed_version) == 0) {
|
||||
echo "plugin: not reinstalling same version\n";
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
if (plugin("install", $plugin_file, $error) === false) {
|
||||
echo "plugin: $error\n";
|
||||
if (dirname($plugin_file) == "/boot/config/plugins") {
|
||||
move($plugin_file, "/boot/config/plugins-error");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
unlink("/var/log/plugins/$plugin");
|
||||
} else {
|
||||
// fresh install
|
||||
if (plugin("install", $plugin_file, $error) === false) {
|
||||
echo "plugin: $error\n";
|
||||
if (dirname($plugin_file) == "/boot/config/plugins") {
|
||||
move($plugin_file, "/boot/config/plugins-error");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// register successful install
|
||||
// Bergware change: add user or system plugin selection
|
||||
$plugintype = plugin("plugintype", $plugin_file, $error);
|
||||
$target = $plugintype != "system" ? "/boot/config/plugins/$plugin" : "/boot/plugins/$plugin";
|
||||
if ($target != $plugin_file) copy($plugin_file, $target);
|
||||
symlink($target, "/var/log/plugins/$plugin");
|
||||
echo "plugin: installed\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// plugin remove [plugin]
|
||||
// only .plg files should have a remove method
|
||||
//
|
||||
if ($method == "remove") {
|
||||
echo "plugin: removing: {$argv[2]}\n";
|
||||
$plugin = $argv[2];
|
||||
$installed_plugin_file = @readlink("/var/log/plugins/$plugin");
|
||||
if ($installed_plugin_file !== false) {
|
||||
// remove the symlink
|
||||
unlink("/var/log/plugins/$plugin");
|
||||
if (plugin("remove", $installed_plugin_file, $error) === false) {
|
||||
// but if can't remove, restore the symlink
|
||||
symlink($installed_plugin_file, "/var/log/plugins/$plugin");
|
||||
echo "plugin: $error\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// remove the plugin file
|
||||
move($installed_plugin_file, "/boot/config/plugins-removed");
|
||||
echo "plugin: removed\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// plugin check [plugin]
|
||||
// We use the pluginURL attribute to download the latest plg file into the "/tmp/plugins/"
|
||||
// directory.
|
||||
//
|
||||
if ($method == "check") {
|
||||
echo "plugin: checking: {$argv[2]}\n";
|
||||
$plugin = $argv[2];
|
||||
$installed_plugin_file = @readlink("/var/log/plugins/$plugin");
|
||||
if ($installed_plugin_file === false) {
|
||||
echo "plugin: not installed\n";
|
||||
exit(1);
|
||||
}
|
||||
$installed_pluginURL = plugin("pluginURL", $installed_plugin_file, $error);
|
||||
if ($installed_pluginURL === false) {
|
||||
echo "plugin: $error\n";
|
||||
exit(1);
|
||||
}
|
||||
$plugin_file = "/tmp/plugins/$plugin";
|
||||
if (!download($installed_pluginURL, $plugin_file, $error)) {
|
||||
echo "plugin: $error\n";
|
||||
@unlink($plugin_file);
|
||||
exit(1);
|
||||
}
|
||||
$version = plugin("version", $plugin_file, $error);
|
||||
if ($version === false) {
|
||||
echo "plugin: $error\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "$version\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// plugin update [plugin]
|
||||
// [plugin] is the plg file we are going to be replacing, eg, "old.plg".
|
||||
// We assume a "check" has already been done, ie, "/tmp/plugins/new.plg" already exists.
|
||||
// We execute the "install" method of new.plg. If this fails, then we mark old.plg "not installed";
|
||||
// the plugin manager will recognize this as an install error.
|
||||
// If install new.plg succeeds, then we remove old.plg and copy new.plg in place.
|
||||
// Finally we mark the new.plg "installed".
|
||||
//
|
||||
if ($method == "update") {
|
||||
echo "plugin: updating: {$argv[2]}\n";
|
||||
$plugin = $argv[2];
|
||||
$installed_plugin_file = @readlink("/var/log/plugins/$plugin");
|
||||
if ($installed_plugin_file === false) {
|
||||
echo "plugin: not installed\n";
|
||||
exit(1);
|
||||
}
|
||||
// verify previous check has been done
|
||||
$plugin_file = "/tmp/plugins/$plugin";
|
||||
if (!file_exists($plugin_file)) {
|
||||
echo "plugin: $plugin_file does not exist, check first\n";
|
||||
exit (1);
|
||||
}
|
||||
// install the updated plugin
|
||||
if (plugin("install", $plugin_file, $error) === false) {
|
||||
echo "plugin: $error\n";
|
||||
exit(1);
|
||||
}
|
||||
// install was successful, save the updated plugin so it installs again next boot
|
||||
unlink("/var/log/plugins/$plugin");
|
||||
copy($plugin_file, "/boot/config/plugins/$plugin");
|
||||
symlink("/boot/config/plugins/$plugin", "/var/log/plugins/$plugin");
|
||||
echo "plugin: updated\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// <attribute>
|
||||
//
|
||||
$plugin_file = $argv[2];
|
||||
$value = plugin($method, $plugin_file, $error);
|
||||
if ($value === false) {
|
||||
echo "plugin: $error\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "$value\n";
|
||||
exit(0);
|
||||
?>
|
||||
6
plugins/dynamix.plugin.manager/scripts/plugin_rm
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# put some restrictions on 'rm'
|
||||
echo "Deleting $1 ..."
|
||||
[[ $1 == /boot/config/plugins-error/* ]] && rm $1
|
||||
[[ $1 == /boot/config/plugins-stale/* ]] && rm $1
|
||||
42
plugins/dynamix.plugin.manager/scripts/plugincheck
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/php
|
||||
<?PHP
|
||||
/* Copyright 2015, Bergware International.
|
||||
* Copyright 2015, Lime Technology
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
require_once('/usr/local/emhttp/webGui/include/Wrappers.php');
|
||||
|
||||
$notify = "/usr/local/emhttp/webGui/scripts/notify";
|
||||
$plugin = "/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/plugin";
|
||||
|
||||
exec("wget -qO /dev/null 127.0.0.1:$(lsof -lbnPi4 -sTCP:LISTEN|grep -Pom1 '^emhttp .*TCP[^\d]+\K\d+')/update.htm?cmdStatus=apply");
|
||||
|
||||
$var = parse_ini_file("/var/local/emhttp/var.ini");
|
||||
$unraid = parse_plugin_cfg("dynamix",true);
|
||||
$server = strtoupper($var['NAME']);
|
||||
$output = $unraid['notify']['plugin'];
|
||||
|
||||
exec("$plugin checkall >/dev/null");
|
||||
foreach (glob("/tmp/plugins/*.plg", GLOB_NOSORT) as $file) {
|
||||
$plg = basename($file);
|
||||
$old = exec("$plugin version '/var/log/plugins/$plg'");
|
||||
unset($new);
|
||||
exec("$plugin version '$file'", $new, $error);
|
||||
// Silently suppress bad download of PLG file
|
||||
if ($error) continue;
|
||||
$new = $new[0];
|
||||
if (strcmp($new, $old) > 0) {
|
||||
$name = basename($file, '.plg');
|
||||
exec("$notify -e 'Plugin - $name [$new]' -s 'Notice [$server] - Version update $new' -d 'A new version of $name is available' -i 'normal $output' -x");
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
?>
|
||||
20
plugins/dynamix.vm.manager/AddVM.page
Normal file
@@ -0,0 +1,20 @@
|
||||
Title="Add VM"
|
||||
Cond="(pgrep('libvirtd')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
|
||||
<?
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/VMedit.php');
|
||||
?>
|
||||
20
plugins/dynamix.vm.manager/UpdateVM.page
Normal file
@@ -0,0 +1,20 @@
|
||||
Title="Update VM"
|
||||
Cond="(pgrep('libvirtd')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
|
||||
<?
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/VMedit.php');
|
||||
?>
|
||||
643
plugins/dynamix.vm.manager/VMMachines.page
Normal file
@@ -0,0 +1,643 @@
|
||||
Menu="VMs"
|
||||
Title="Virtual Machines"
|
||||
Cond="(pgrep('libvirtd')!==false)"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
|
||||
<?
|
||||
$action = array_key_exists('action', $_GET) ? $_GET['action'] : '';
|
||||
$subaction = array_key_exists('subaction', $_GET) ? $_GET['subaction'] : '';
|
||||
$uuid = $_GET['uuid'];
|
||||
if ($_GET['refresh']) {
|
||||
$name = $_GET['name'];
|
||||
if($lv->domain_is_active($name)){
|
||||
echo "<meta http-equiv='refresh' content='5; url=/VMs?name=$name&refresh=true'>";
|
||||
$msg = "Waiting for $name to shutdown...";
|
||||
}else{
|
||||
echo "<script>clearHistory();</script>";
|
||||
$msg = "$name has been shutdown";
|
||||
}
|
||||
}
|
||||
if ($action) {
|
||||
|
||||
}else{
|
||||
if ($subaction) {
|
||||
$domName = $lv->domain_get_name_by_uuid($uuid);
|
||||
if ($subaction == 'domain-diskdev') {
|
||||
$msg = $lv->domain_set_disk_dev($domName, $_GET['olddev'], $_POST['diskdev']) ?
|
||||
$domName.' disk dev has been changed from '.$_GET['olddev'].' to '.$_POST['diskdev']:
|
||||
'Error: '.$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'disk-resize') {
|
||||
$capacity = str_replace(array("KB","MB","GB","TB","PB", " ", ","), array("K","M","G","T","P", "", ""), strtoupper($_POST['cap']));
|
||||
$oldcap = str_replace(array("KB","MB","GB","TB","PB", " ", ","), array("K","M","G","T","P", "", ""), strtoupper($_GET['oldcap']));
|
||||
if (substr($oldcap,0,-1) < substr($capacity,0,-1)){
|
||||
shell_exec("qemu-img resize -q " . escapeshellarg($_GET['disk']) . " $capacity");
|
||||
$msg = $domName." disk capacity has been changed to $capacity";
|
||||
}else {
|
||||
$msg = "Error: disk capacity has to be greater than {$_GET['oldcap']}";
|
||||
}
|
||||
}
|
||||
elseif ($subaction == 'cdrom-change') {
|
||||
$msg = $lv->domain_change_cdrom($domName, $_POST['cdrom'], $_GET['dev'], $_GET['bus']) ?
|
||||
"$domName cdrom has been changed" :
|
||||
"Error: ".$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'memory-change') {
|
||||
$msg = $lv->domain_set_memory($domName, $_GET['memory']*1024) ?
|
||||
"$domName memory has been changed to ".$_GET['memory']." MiB" :
|
||||
"Error: ".$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'vcpu-change') {
|
||||
$vcpu = $_GET['vcpu'];
|
||||
$msg = $lv->domain_set_vcpu($domName, $vcpu) ?
|
||||
"$domName vcpu number has been changed to $vcpu" :
|
||||
"Error: ".$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'bootdev-change') {
|
||||
$bootdev = $_GET['bootdev'];
|
||||
$msg = $lv->domain_set_boot_device($domName, $bootdev) ?
|
||||
"$domName boot device has been changed to $bootdev" :
|
||||
"Error: ".$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'disk-remove') {
|
||||
$msg = $lv->domain_disk_remove($domName, $_GET['dev']) ?
|
||||
"$domName disk has been removed" :
|
||||
'Error: '.$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'snap-create') {
|
||||
$msg = $lv->domain_snapshot_create($domName) ?
|
||||
"Snapshot for $domName has been created" :
|
||||
'Error: '.$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'snap-delete') {
|
||||
$msg = $lv->domain_snapshot_delete($domName, $_GET['snap']) ?
|
||||
"Snapshot for $domName has been deleted" :
|
||||
'Error: '.$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'snap-revert') {
|
||||
$msg = $lv->domain_snapshot_revert($domName, $_GET['snap']) ?
|
||||
"$domName has been reverted" :
|
||||
'Error: '.$lv->get_last_error();
|
||||
}
|
||||
elseif ($subaction == 'snap-desc') {
|
||||
$msg = $lv->snapshot_set_metadata($domName, $_GET['snap'], $_POST['snapdesc']) ?
|
||||
"Snapshot description for $domName has been saved":
|
||||
'Error: '.$lv->get_last_error();
|
||||
}
|
||||
echo "<script>clearHistory();</script>";
|
||||
}
|
||||
echo "<table class='tablesorter kvm' id='kvm_table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class='header'><i class='fa fa-th-list'></i></th>
|
||||
<th class='header'>Name</th>
|
||||
<th class='header'>CPUs</th>
|
||||
<th class='header'>Memory</th>
|
||||
<th class='header'>Hard Drives</th>
|
||||
<th class='header'>VNC Port</th>
|
||||
<th class='header'>Autostart</th>
|
||||
<th class='header'>Log</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>";
|
||||
//Get domain variables for each domain
|
||||
if ($libvirt_running == "yes")
|
||||
$doms = $lv->get_domains();
|
||||
if(!is_array($doms)){
|
||||
if ($libvirt_running == "yes") {
|
||||
echo "<tr><td></td><td colspan=\"7\">No Virtual Machines Installed</td></tr>";
|
||||
$msg = "No VMs defined. Create from template or add XML.";
|
||||
} else {
|
||||
$msg = "Libvirt is not running. Goto Settings tab then click Start.";
|
||||
}
|
||||
} else {
|
||||
sort($doms);
|
||||
$contextMenus = array();
|
||||
|
||||
for ($i = 0; $i < sizeof($doms); $i++) {
|
||||
$name = $doms[$i];
|
||||
$res = $lv->get_domain_by_name($name);
|
||||
$uuid = $lv->domain_get_uuid($res);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$id = $lv->domain_get_id($res);
|
||||
$is_autostart = $lv->domain_get_autostart($res);
|
||||
$state = $lv->domain_state_translate($dom['state']);
|
||||
switch ($state) {
|
||||
case 'running':
|
||||
$shape = 'play';
|
||||
$status = 'started';
|
||||
break;
|
||||
case 'paused': //no break on purpose
|
||||
case 'pmsuspended':
|
||||
$shape = 'pause';
|
||||
$status = 'paused';
|
||||
break;
|
||||
default:
|
||||
$shape = 'square';
|
||||
$status = 'stopped';
|
||||
break;
|
||||
}
|
||||
if ($state == 'running') {
|
||||
$mem = $dom['memory'] / 1024;
|
||||
}else{
|
||||
$mem = $lv->domain_get_memory($res)/1024;
|
||||
}
|
||||
$mem = number_format($mem, 0);
|
||||
$vcpu = $dom['nrVirtCpu'];
|
||||
$auto = $is_autostart ? 'checked="checked"':"";
|
||||
$template = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@name');
|
||||
if (empty($template)) {
|
||||
$template = 'Custom';
|
||||
}
|
||||
$log = (is_file('/var/log/libvirt/qemu/' . $name . '.log') ? 'libvirt/qemu/' . $name . '.log' : '');
|
||||
if (($diskcnt = $lv->get_disk_count($res)) > 0) {
|
||||
$disks = $diskcnt.' / '.$lv->get_disk_capacity($res);
|
||||
$diskdesc = 'Current physical size: '.$lv->get_disk_capacity($res, true);
|
||||
}else{
|
||||
$disks = '-';
|
||||
$diskdesc = '';
|
||||
}
|
||||
|
||||
$vncport = $lv->domain_get_vnc_port($res);
|
||||
|
||||
$vnc = '';
|
||||
if ($vncport > 0) {
|
||||
$wsport = $lv->domain_get_ws_port($res);
|
||||
$vnc = '/plugins/dynamix.vm.manager/vnc.html?autoconnect=true&host=' . $var['IPADDR'] . '&port=' . $wsport;
|
||||
} else {
|
||||
$vncport = ($vncport < 0) ? "auto" : "";
|
||||
}
|
||||
|
||||
$bootdev = $lv->domain_get_boot_devices($res)[0];
|
||||
|
||||
unset($tmp);
|
||||
if (!$id)
|
||||
$id = '-';
|
||||
unset($dom);
|
||||
|
||||
$contextMenus[] = sprintf("addVMContext('%s', '%s', '%s', '%s', '%s', '%s');", addslashes($name), addslashes($uuid), addslashes($template), $state, addslashes($vnc), addslashes($log));
|
||||
|
||||
// fallback icon for users that created VMs before metadata support was added
|
||||
$vmicon = '/plugins/dynamix.vm.manager/templates/images/' . ($lv->domain_get_clock_offset($res) == 'localtime' ? 'windows.png' : 'linux.png');
|
||||
|
||||
$vmtemplateicon = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@icon');
|
||||
if (!empty($vmtemplateicon)) {
|
||||
if (file_exists($vmtemplateicon)) {
|
||||
$vmicon = $vmtemplateicon;
|
||||
} else if (file_exists('/usr/local/emhttp/plugins/dynamix.vm.manager/templates/images/' . $vmtemplateicon)) {
|
||||
$vmicon = '/plugins/dynamix.vm.manager/templates/images/' . $vmtemplateicon;
|
||||
}
|
||||
}
|
||||
|
||||
//Domain information
|
||||
echo "<tr style='background-color:".bcolor($i,$theme)."'>
|
||||
<td style=\"width: 48px; padding: 4px\">
|
||||
<div id=\"vm-" . htmlspecialchars($uuid) . "\" style=\"display:block; cursor:pointer\">
|
||||
<div style=\"position: relative; width: 48px; height: 48px; margin: 0px auto;\">
|
||||
<img src=\"" . $vmicon . "\" class=\"$status\" style=\"position: absolute; z-index: 1; top: 0; bottom: 0; left: 0; right: 0; width: 48px; height: 48px;\"/>
|
||||
<i class=\"fa iconstatus fa-$shape $status\" title='$status'></i>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><a href='#' onclick=\"return toggle_id('name$i');\" title='click for more VM info'>$name</a></td>
|
||||
<td>$vcpu</td>
|
||||
<td>$mem</td>
|
||||
<td title='$diskdesc'>$disks</td>
|
||||
<td>$vncport</td>
|
||||
<td><input class='autostart' type='checkbox' name='auto_$name' title='Toggle VM auostart' $auto uuid='$uuid'></td>";
|
||||
|
||||
// Log file
|
||||
if (!empty($log)) {
|
||||
echo "<td><a class='log' href='#' onclick=\"openWindow('/webGui/scripts/tail_log&arg1=$log', '$name QEMU Log', 600, 900); return false;\"><img class='basic' src='/webGui/icons/log.png'/></a></td>";
|
||||
} else {
|
||||
echo "<td><img class='basic' src='/webGui/icons/log.png' style='opacity: 0.3' title='Log not available'/></a></td>";
|
||||
}
|
||||
|
||||
echo "</tr>
|
||||
<tr id='name$i' style='display: none'>";
|
||||
/* Disk device information */
|
||||
echo "<td colspan='8' style='overflow: hidden'>
|
||||
<table class='tablesorter domdisk' id='domdisk_table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class='header'><i class='fa fa-hdd-o'></i><b> Disk devices </b>
|
||||
<select name='boot_dev' class='narrow' onchange='location = this.options[this.selectedIndex].value;' title='change boot device'>
|
||||
<option ";
|
||||
//boot device select
|
||||
if($bootdev == 'hd')
|
||||
echo "selected ";
|
||||
echo "value='?subaction=bootdev-change&uuid=$uuid&bootdev=hd'>hd</option>
|
||||
<option ";
|
||||
if($bootdev == 'cdrom')
|
||||
echo "selected ";
|
||||
echo "value='?subaction=bootdev-change&uuid=$uuid&bootdev=cdrom'>cd</option>
|
||||
</select><font size='-6'>(boot)</font>
|
||||
</th>
|
||||
<th class='header'>Driver type</th>
|
||||
<th class='header'>Dev Name</th>
|
||||
<th class='header'>Capacity</th>
|
||||
<th class='header'>Allocation</th>
|
||||
<th class='header'>Actions</th><th class='header'></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>";
|
||||
/* Display domain disks */
|
||||
$tmp = $lv->get_disk_stats($res);
|
||||
if (!empty($tmp)) {
|
||||
for ($ii = 0; $ii < sizeof($tmp); $ii++) {
|
||||
$capacity = $lv->format_size($tmp[$ii]['capacity'], 0);
|
||||
$allocation = $lv->format_size($tmp[$ii]['allocation'], 2);
|
||||
$disk = (array_key_exists('file', $tmp[$ii])) ? $tmp[$ii]['file'] : $tmp[$ii]['partition'];
|
||||
$type = $tmp[$ii]['type'];
|
||||
$dev = $tmp[$ii]['device'];
|
||||
echo "<tr>
|
||||
<td>".basename($disk)."</td>
|
||||
<td>$type</td>
|
||||
<td title='Click to change Dev Name'><form method='post' action='?subaction=domain-diskdev&uuid=$uuid&olddev=$dev' /><span class='diskdev' style='width:30px'>
|
||||
<span class='text' ><a href='#'> $dev </a></span>
|
||||
<input class='input' type='text' style='width:36px' name='diskdev' value='$dev' val='diskdev' hidden />
|
||||
</span></form>
|
||||
</td>
|
||||
<td title='Click to increase Disk Size'>
|
||||
<form method='post' action='?subaction=disk-resize&uuid=$uuid&disk=$disk&oldcap=$capacity' /><span class='diskresize' style='width:30px'>
|
||||
<span class='text' ><a href='#'> $capacity </a></span>
|
||||
<input class='input' type='text' style='width:46px' name='cap' value='$capacity' val='diskresize' hidden />
|
||||
</span></form>
|
||||
</td>
|
||||
<td>$allocation</td>
|
||||
<td>";
|
||||
/* add detach button if shutoff */
|
||||
if ($state == 'shutoff')
|
||||
echo "detach <a href='#' onclick=\"swal({title:'Are you sure?',text:'Detach ".basename($disk)." from VM: ".$name."',type:'warning',showCancelButton:true},function(){ajaxVMDispatch('attached',{action:'disk-remove',uuid:'".$uuid."',dev:'".$dev."'});});return false;\" title='detach disk from VM'>
|
||||
<i class='fa fa-eject blue'></i></a>";
|
||||
else
|
||||
echo "N/A";
|
||||
echo '</td>
|
||||
</tr>';
|
||||
}
|
||||
}
|
||||
/*end display disk display*/
|
||||
|
||||
/* Display domain cdroms */
|
||||
$tmp = $lv->get_cdrom_stats($res);
|
||||
if (!empty($tmp)) {
|
||||
for ($ii = 0; $ii < sizeof($tmp); $ii++) {
|
||||
$capacity = $lv->format_size($tmp[$ii]['capacity'], 2);
|
||||
$allocation = $lv->format_size($tmp[$ii]['allocation'], 2);
|
||||
$disk = (array_key_exists('file', $tmp[$ii])) ? $tmp[$ii]['file'] : $tmp[$ii]['partition'];
|
||||
$type = $tmp[$ii]['type'];
|
||||
$dev = $tmp[$ii]['device'];
|
||||
$bus = $tmp[$ii]['bus'];
|
||||
echo "<tr>
|
||||
<td>
|
||||
<form method='post' id='cdrom_form$i$ii' name='cdromform' action='?subaction=cdrom-change&uuid=$uuid&dev=$dev&bus=$bus' />
|
||||
<span>
|
||||
<span class='text'><a href='#'>".basename($disk)."</a></span>
|
||||
<input class='input cdrom_file' id='cdrom_file$i$ii'
|
||||
onclick='CDRomTree(cdrom_tree$i$ii, cdrom_file$i$ii, cdrom_form$i$ii);' hidden type='text' name='cdrom' value='$disk' title=change cdrom image'>
|
||||
<div class='cdrom_tree' id='cdrom_tree$i$ii' hidden></div>
|
||||
</span>
|
||||
</form>
|
||||
</td>
|
||||
<td>$type</td>
|
||||
<td title='Click to change Dev Name'><form method='post' action='?subaction=domain-diskdev&uuid=$uuid&dev=$dev' /><span class='diskdev' style='width:30px'>
|
||||
<span class='text' ><a href='#'> $dev </a></span>
|
||||
<input class='input' type='text' style='width:36px' name='diskdev' value='$dev' val='diskdev' hidden />
|
||||
</span></form>
|
||||
</td>
|
||||
<td>$capacity</td>
|
||||
<td>$allocation</td>
|
||||
<td>";
|
||||
/* add detach button if shutoff */
|
||||
if ($state == 'shutoff')
|
||||
echo "detach <a href='#' onclick=\"swal({title:'Are you sure?',text:'Detach ".basename($disk)." from VM: ".$name."',type:'warning',showCancelButton:true},function(){ajaxVMDispatch('attached',{action:'disk-remove',uuid:'".$uuid."',dev:'".$dev."'});});return false;\" title='detach disk from VM'>
|
||||
<i class='fa fa-eject blue'></i></a>";
|
||||
else
|
||||
echo "N/A";
|
||||
echo '</td>
|
||||
</tr>';
|
||||
}
|
||||
}
|
||||
/*end display cdrom display*/
|
||||
echo "</tbody></table>";
|
||||
unset($capacity, $dev, $disk, $type, $vcpu);
|
||||
/* Backup information */
|
||||
/* echo "<table class='tablesorter domsnap' id='backup_table'>
|
||||
<tr>
|
||||
<thead>
|
||||
<th class='header'><i class='fa fa-floppy-o'></i><b> Backups </b>";
|
||||
if ($state == 'shutoff')
|
||||
echo "<a href='?subaction=backup-create&uuid=$uuid' title='create a backup of current domain'><i class='fa fa-plus green'></i></a>";
|
||||
echo "</th>
|
||||
<th class='header'>Name</th>
|
||||
<th class='header'>Date</th>
|
||||
<th class='header'>Time</th>
|
||||
<th class='header'>Description</th>
|
||||
<th class='header'>Actions ";
|
||||
if ($state == 'running')
|
||||
echo " <small>(stop to enable)</small>";
|
||||
echo "</th><th class='header'></th><th class='header'></th>
|
||||
</thead>
|
||||
</tr>";
|
||||
if (false) {
|
||||
sort($tmp);
|
||||
for ($ii = 0; $ii < sizeof($tmp); $ii++) {
|
||||
$backup = $tmp[$ii];
|
||||
$date = date("D d M Y",$name);
|
||||
$time = date("H:i:s",$backup);
|
||||
$info = $lv->domain_backup_get_info($name, $backup);
|
||||
if(empty($info)){
|
||||
$info = "Click to change description";
|
||||
$val = "";}
|
||||
else
|
||||
$val = $info;
|
||||
echo "<tr style='background-color:".bcolor($ii,$theme)."'>
|
||||
<td>".($ii+1)."</td>
|
||||
<td>$backup</td>
|
||||
<td>$date</td>
|
||||
<td>$time</td>
|
||||
<td><form method='post' action='?subaction=backup-desc&uuid=$uuid&backup=$backup' /><span class='backdesc'>
|
||||
<span class='text'><a href='#'> $info </a></span>
|
||||
<input class='input' type='text' name='backdesc' value='$val' val='backdesc' hidden placeholder='Click to change description' title='Click to change description'/>
|
||||
</span></form>
|
||||
</td>
|
||||
<td>
|
||||
revert <a href='?subaction=backup-revert&uuid=$uuid&backup=$backup'><i class='fa fa-refresh lightblue'></i></a>
|
||||
</td>
|
||||
<td>
|
||||
delete <a href='?subaction=backup-delete&uuid=$uuid&backup=$backup'><i class='fa fa-remove red'></i></a>
|
||||
</td>
|
||||
</tr>";
|
||||
}
|
||||
}
|
||||
else
|
||||
echo "<tr><td>no backups</td>
|
||||
<td>none</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
</tr>";
|
||||
echo '</table>';*/
|
||||
|
||||
/* End Backup information*/
|
||||
|
||||
/* Snapshot information */
|
||||
/*
|
||||
echo "<table class='tablesorter domsnap' id='domsnap_table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class='header'><i class='fa fa-camera-retro'></i><b> Snapshots </b>";
|
||||
if ($state == 'shutoff')
|
||||
echo "<a href='?subaction=snap-create&uuid=$uuid' title='create a snapshot of current domain state'><i class='fa fa-plus green'></i></a>";
|
||||
echo "</th>
|
||||
<th class='header'>Name</th>
|
||||
<th class='header'>Date</th>
|
||||
<th class='header'>Time</th>
|
||||
<th class='header'>Description</th>
|
||||
<th class='header'>Actions ";
|
||||
if ($state == 'running')
|
||||
echo " <small>(stop to enable)</small>";
|
||||
echo "</th><th class='header'></th><th class='header'></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>";
|
||||
$tmp = $lv->domain_snapshots_list($res);
|
||||
if (!empty($tmp)) {
|
||||
sort($tmp);
|
||||
for ($ii = 0; $ii < sizeof($tmp); $ii++) {
|
||||
$snap = $tmp[$ii];
|
||||
$date = date("D d M Y",$snap);
|
||||
$time = date("H:i:s",$snap);
|
||||
$info = $lv->domain_snapshot_get_info($res, $snap);
|
||||
if(empty($info)){
|
||||
$info = "Click to change description";
|
||||
$val = "";}
|
||||
else
|
||||
$val = $info;
|
||||
echo "<tr style='background-color:".bcolor($ii,$theme)."'>
|
||||
<td>".($ii+1)."</td>
|
||||
<td>$snap</td>
|
||||
<td>$date</td>
|
||||
<td>$time</td>
|
||||
<td><form method='post' action='?subaction=snap-desc&uuid=$uuid&snap=$snap' /><span class='snapdesc'>
|
||||
<span class='text'><a href='#'> $info </a></span>
|
||||
<input class='input' type='text' name='snapdesc' value='$val' val='snapdesc' hidden placeholder='Click to change description' title='Click to change description'/>
|
||||
</span></form>
|
||||
</td>
|
||||
<td>
|
||||
revert <a href='?subaction=snap-revert&uuid=$uuid&snap=$snap'><i class='fa fa-refresh lightblue'></i></a>
|
||||
</td>
|
||||
<td>
|
||||
delete <a href='?subaction=snap-delete&uuid=$uuid&snap=$snap'><i class='fa fa-trash red'></i></a>
|
||||
</td>
|
||||
</tr>";
|
||||
}
|
||||
}
|
||||
else
|
||||
echo "<tr><td>no snapshots</td>
|
||||
<td>none</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
<td>N/A</td>
|
||||
</tr>";
|
||||
echo '</tbody></table>';
|
||||
*/
|
||||
echo '</td></tr>';
|
||||
}
|
||||
}
|
||||
echo '</tbody></table>';
|
||||
}
|
||||
/* End Snapshot information */
|
||||
unset($name, $val);
|
||||
if($msg){
|
||||
if(strpos($msg, "rror:"))
|
||||
$color = 'red';
|
||||
else
|
||||
$color = 'green';
|
||||
echo "<script type='text/javascript'>$(function() { $('#countdown').html('<font class=\"".$color."\">".$msg."</font>');}); </script>";
|
||||
}
|
||||
?>
|
||||
|
||||
<input type="button" id="btnAddVM" value="Add VM"/>
|
||||
|
||||
<script>
|
||||
function vncOpen() {
|
||||
$.post('/plugins/dynamix.vm.manager/classes/vnc.php',{cmd:'open',root:'<?=$docroot?>',file:'/usr/local/emhttp/plugins/dynamix.vm.manager/vncconnect.vnc'},function(data) {
|
||||
window.location.href = data;
|
||||
});
|
||||
}
|
||||
|
||||
function toggle_id(itemID){
|
||||
if ((document.getElementById(itemID).style.display == 'none')) {
|
||||
slideDownRows($('#'+itemID));
|
||||
} else {
|
||||
slideUpRows($('#'+itemID));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function showInput(){
|
||||
$(this).off('click');
|
||||
$(this).siblings('input').each(function(){$(this).show();});
|
||||
$(this).siblings('input').focus();
|
||||
$(this).hide();
|
||||
}
|
||||
|
||||
function hideInput(){
|
||||
$(this).hide();
|
||||
$(this).siblings('span').show();
|
||||
$(this).siblings('span').click(showInput);
|
||||
}
|
||||
|
||||
function ajaxVMDispatch(currentstate, params){
|
||||
$.post("/plugins/dynamix.vm.manager/VMajax.php", params, function( data ) {
|
||||
if (data.error) {
|
||||
swal({title:"Execution error",text:data.error,type:"error"});
|
||||
} else {
|
||||
if (!data.state || currentstate != data.state) {
|
||||
location = window.location.href;
|
||||
}
|
||||
}
|
||||
}, "json");
|
||||
}
|
||||
|
||||
function addVMContext(name, uuid, template, state, vncurl, log){
|
||||
var opts = [{header: name, image: "/plugins/dynamix.vm.manager/images/dynamix.vm.manager.png"}];
|
||||
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
|
||||
if (vncurl !== "") {
|
||||
opts.push({text: "VNC Remote", icon: "fa-desktop", action: function(e){ e.preventDefault(); window.open(vncurl, '_blank', 'scrollbars=yes,resizable=yes'); } });
|
||||
opts.push({divider: true});
|
||||
}
|
||||
|
||||
if (state == "running") {
|
||||
|
||||
opts.push({text: "Stop", icon: "fa-stop", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-stop", uuid: uuid });
|
||||
}});
|
||||
|
||||
opts.push({text: "Pause", icon: "fa-pause", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-pause", uuid: uuid });
|
||||
}});
|
||||
|
||||
opts.push({text: "Restart", icon: "fa-refresh", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-restart", uuid: uuid });
|
||||
}});
|
||||
|
||||
opts.push({text: "Force Stop", icon: "fa-bomb", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-destroy", uuid: uuid });
|
||||
}});
|
||||
|
||||
} else if (state == "pmsuspended") {
|
||||
|
||||
opts.push({text: "Resume", icon: "fa-play", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-pmwakeup", uuid: uuid });
|
||||
}});
|
||||
|
||||
opts.push({text: "Force Stop", icon: "fa-bomb", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-destroy", uuid: uuid });
|
||||
}});
|
||||
|
||||
} else if (state == "paused" || state == "unknown") {
|
||||
|
||||
opts.push({text: "Resume", icon: "fa-play", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-resume", uuid: uuid });
|
||||
}});
|
||||
|
||||
opts.push({text: "Force Stop", icon: "fa-bomb", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-destroy", uuid: uuid });
|
||||
}});
|
||||
|
||||
} else {
|
||||
|
||||
opts.push({text: "Start", icon: "fa-play", action: function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch(state, { action: "domain-start", uuid: uuid });
|
||||
}});
|
||||
|
||||
}
|
||||
|
||||
opts.push({divider: true});
|
||||
|
||||
if (log !== "") {
|
||||
if (location.pathname.indexOf("/Dashboard") === 0) {
|
||||
opts.push({text: "Logs", icon: "fa-navicon", action: function(e){ e.preventDefault(); openWindow('/webGui/scripts/tail_log&arg1=' + log + ')', 'Log for: ' + name, 600, 900); } });
|
||||
}
|
||||
}
|
||||
|
||||
if (state == "shutoff") {
|
||||
|
||||
opts.push({text: "Edit", icon: "fa-pencil", href: path+'/UpdateVM?uuid='+uuid });
|
||||
opts.push({text: "Edit XML", icon: "fa-code", href: path+'/UpdateVM?template=XML_Expert&uuid='+uuid });
|
||||
|
||||
opts.push({divider: true});
|
||||
|
||||
opts.push({text: "Remove VM", icon: "fa-minus", action: function(e) {
|
||||
e.preventDefault();
|
||||
swal({title:"Are you sure?",text:"Remove definition: "+name,type:"warning",showCancelButton:true},function(){ajaxVMDispatch(state,{action:"domain-undefine",uuid:uuid});});
|
||||
}});
|
||||
|
||||
if (template != 'OpenELEC') {
|
||||
opts.push({text: "Remove VM + Disks", icon: "fa-trash", action: function(e) {
|
||||
e.preventDefault();
|
||||
swal({title:"Are you sure?",text:"Completely REMOVE "+name+" disk image and definition",type:"warning",showCancelButton:true},function(){ajaxVMDispatch(state,{action:"domain-delete",uuid:uuid});});
|
||||
}});
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
opts.push({text: "View XML", icon: "fa-code", href: path+'/UpdateVM?template=XML_Expert&uuid='+uuid });
|
||||
|
||||
}
|
||||
|
||||
context.attach('#vm-'+uuid, opts);
|
||||
}
|
||||
|
||||
|
||||
$(function() {
|
||||
$('.text').click(showInput);
|
||||
$('.input').blur(hideInput);
|
||||
$('#btnAddVM').click(function AddVMEvent() {
|
||||
window.location = '/VMs/AddVM';
|
||||
});
|
||||
|
||||
$('.autostart').switchButton({
|
||||
labels_placement: "right"
|
||||
});
|
||||
$('.autostart').change(function () {
|
||||
$.post( "/plugins/dynamix.vm.manager/VMajax.php", { action: "domain-autostart", uuid: $(this).attr('uuid'), autostart: $(this).prop('checked'), response: "json" }, function( data ) {
|
||||
$(this).prop('checked', data.autostart );
|
||||
}, "json");
|
||||
});
|
||||
|
||||
context.init({ preventDoubleContext: false });
|
||||
<?=implode("\n\t", $contextMenus);?>
|
||||
});
|
||||
</script>
|
||||
|
||||
183
plugins/dynamix.vm.manager/VMSettings.page
Normal file
@@ -0,0 +1,183 @@
|
||||
Menu="OtherSettings"
|
||||
Title="VM Manager"
|
||||
Icon="dynamix.vm.manager.png"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
if ($var['fsState'] != "Started") {
|
||||
echo "<p class='notice'>Array must be Started to manage Virtual Machines.</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt.php');
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt_helpers.php');
|
||||
|
||||
|
||||
// Check for Intel VT-x (vmx) or AMD-V (svm) cpu virtualzation support
|
||||
// Attempt to load either of the kvm modules to see if virtualzation hw is supported
|
||||
exec('modprobe -a kvm_intel kvm_amd 2>/dev/null');
|
||||
|
||||
// If either kvm_intel or kvm_amd are loaded then Intel VT-x (vmx) or AMD-V (svm) cpu virtualzation support was found
|
||||
$strLoadedModules = shell_exec("lsmod | grep '^kvm_\(amd\|intel\)'");
|
||||
|
||||
if (empty($strLoadedModules)) {
|
||||
?><p class="notice">Your hardware does not have Intel VT-x or AMD-V capability. This is required to create VMs in KVM. <a href="http://lime-technology.com/wiki/index.php/UnRAID_Manual_6#Hardware-Assisted_Virtualization_.28HVM.29" target="_blank">Click here to see the unRAID Wiki for more information</a></p><?php
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
$arrValidBridges = getNetworkBridges();
|
||||
|
||||
|
||||
// Check for PCIE ACS capabilities
|
||||
$boolACSEnabled = (strpos(file_get_contents('/proc/cmdline'), 'pcie_acs_override=') !== false);
|
||||
|
||||
// Check the /boot/syslinux/syslinux.cfg for the existance of pcie_acs_override=
|
||||
$arrSyslinuxCfg = file('/boot/syslinux/syslinux.cfg');
|
||||
$strCurrentLabel = '';
|
||||
$boolACSInSyslinux = false;
|
||||
|
||||
foreach ($arrSyslinuxCfg as &$strSyslinuxCfg) {
|
||||
if (stripos(trim($strSyslinuxCfg), 'label ') === 0) {
|
||||
$strCurrentLabel = trim(str_ireplace('label ', '', $strSyslinuxCfg));
|
||||
}
|
||||
if (stripos($strSyslinuxCfg, 'append ') !== false) {
|
||||
if (stripos($strSyslinuxCfg, 'pcie_acs_override=') !== false) {
|
||||
// pcie_acs_override= was found
|
||||
$boolACSInSyslinux = true;
|
||||
}
|
||||
|
||||
// We just examine the first append line
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($boolACSEnabled != $boolACSInSyslinux) {
|
||||
?><p class="notice">You must reboot for changes to take effect</p><?php
|
||||
}
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.switchbutton.css">
|
||||
<style>
|
||||
body { -webkit-overflow-scrolling: touch;}
|
||||
.fileTree {
|
||||
width: 305px;
|
||||
max-height: 150px;
|
||||
overflow: scroll;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<form id="settingsForm" markdown="1" method="POST" action="/update.php" target="progressFrame">
|
||||
<input type="hidden" name="#file" value="<?=$domain_cfgfile;?>" />
|
||||
<input type="hidden" id="command" name="#command" value="" />
|
||||
|
||||
Enable VMs:
|
||||
: <select id="SERVICE" name="SERVICE" class="narrow">
|
||||
<?= mk_option($libvirt_service, 'disable', 'No'); ?>
|
||||
<?= mk_option($libvirt_service, 'enable', 'Yes'); ?>
|
||||
</select>
|
||||
|
||||
> Stopping the VM Manager will first attempt to shutdown all running VMs. After 40 seconds, any remaining VM instances will be terminated.
|
||||
|
||||
<? if ($libvirt_service == 'enable') {
|
||||
$libvirt_info = libvirt_version('libvirt');
|
||||
$qemu_info = $lv->get_connect_information(); ?>
|
||||
Libvirt Version:
|
||||
: <?= $libvirt_info['libvirt.major'] . '.' . $libvirt_info['libvirt.minor'] . '.' . $libvirt_info['libvirt.release']; ?>
|
||||
|
||||
QEMU Version:
|
||||
: <?= $qemu_info['hypervisor_major'] . '.' . $qemu_info['hypervisor_minor'] . '.' . $qemu_info['hypervisor_release']; ?>
|
||||
<? } ?>
|
||||
|
||||
ISO Library Share <span style="font-weight: normal">(optional)</span>:
|
||||
: <input type="text" data-pickfolders="true" data-pickfilter="NO_FILES_FILTER" data-pickroot="<?= (is_dir('/mnt/user/') ? '/mnt/user/' : '/mnt/') ?>" name="MEDIADIR" value="<?=$domain_cfg['MEDIADIR']?>" placeholder="Click to Select">
|
||||
|
||||
> Specify a user share that contains all your installation media for operating systems
|
||||
|
||||
VirtIO Windows Drivers ISO <span style="font-weight: normal">(optional)</span>:
|
||||
: <input type="text" data-pickfilter="iso" data-pickcloseonfile="true" data-pickroot="<?= (is_dir('/mnt/user/') ? '/mnt/user/' : '/mnt/') ?>" name="VIRTIOISO" value="<?=$domain_cfg['VIRTIOISO']?>" placeholder="Click to Select">
|
||||
|
||||
> Specify the virtual CD-ROM (ISO) that contains the VirtIO Windows drivers as provided by the Fedora Project.
|
||||
> Download the latest ISO from here: <a href="https://fedoraproject.org/wiki/Windows_Virtio_Drivers#Direct_download" target="_blank">https://fedoraproject.org/wiki/Windows_Virtio_Drivers#Direct_download</a>
|
||||
>
|
||||
> When installing Windows, you will reach a step where no disk devices will be found.
|
||||
> There is an option to browse for drivers on that screen. Click browse and locate the additional CD-ROM in the menu.
|
||||
> Inside there will be various folders for the different versions of Windows. Open the folder for the version of Windows
|
||||
> you are installing and then select the AMD64 subfolder inside (even if you are on an Intel system, select AMD64).
|
||||
> Three drivers will be found. Select them all, click next, and the vDisks you have assigned will appear.
|
||||
|
||||
<!--
|
||||
vDisk Share <span style="font-weight: normal">(optional)</span>:
|
||||
: <input type="text" data-pickfolders="true" data-pickfilter="NO_FILES_FILTER" data-pickroot="/mnt/" name="DISKDIR" value="<?=$domain_cfg['DISKDIR']?>" placeholder="Click to Select (ie. /mnt/cache/images)" title="Click to Select (ie. /mnt/cache/images)">
|
||||
-->
|
||||
|
||||
Default Network Bridge:
|
||||
: <select id="bridge" name="BRNAME">
|
||||
<?php
|
||||
foreach ($arrValidBridges as $strBridge) {
|
||||
echo mk_option($domain_cfg['BRNAME'], $strBridge, $strBridge);
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
|
||||
> Enter the name of the network bridge you wish to use for your VMs here, otherwise leave the field blank and
|
||||
> libvirt will create a bridge that will utilize NAT (network address translation) and act as a DHCP server to hand out
|
||||
> IP addresses to virtual machines directly.
|
||||
>
|
||||
> NOTE: You can also specify an network bridge on a per-VM basis.
|
||||
|
||||
PCIe ACS Override:
|
||||
: <select id="pcie_acs_override" class="narrow">
|
||||
<?= mk_option(($boolACSInSyslinux ? '1' : '0'), '0', 'No'); ?>
|
||||
<?= mk_option(($boolACSInSyslinux ? '1' : '0'), '1', 'Yes'); ?>
|
||||
</select>
|
||||
|
||||
> Warning: Use of this setting could cause possible data corruption with certain hardware configurations. Please visit the Lime Technology forums for more information.
|
||||
>
|
||||
> A reboot will be required for changes to this setting to take affect.
|
||||
|
||||
|
||||
: <input type="button" id="applyBtn" value="Apply"/><input type="button" value="Done" onclick="done()">
|
||||
</form>
|
||||
|
||||
> View the log for libvirt: <a id="openlog" title="/var/log/libvirt/libvirtd.log" href="#" onclick="openWindow('/webGui/scripts/tail_log&arg1=libvirt/libvirtd.log','Log Information',600,900);">/var/log/libvirt/libvirtd.log</a>
|
||||
|
||||
|
||||
<script src="/webGui/javascript/jquery.filetree.js"></script>
|
||||
<script src="/webGui/javascript/jquery.switchbutton.js"></script>
|
||||
<script src="/plugins/dynamix.vm.manager/scripts/dynamix.vm.manager.js"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
$("#applyBtn").click(function(){
|
||||
$("#command").val("/plugins/dynamix.vm.manager/event/" + ($("#SERVICE").val()=="enable" ? "started" : "stopping_svcs"));
|
||||
|
||||
if ($('#pcie_acs_override').val() == '1') {
|
||||
$.get("/plugins/dynamix.vm.manager/VMajax.php?action=acs-override-enable", function( data ) {
|
||||
$("#settingsForm").submit();
|
||||
}, "json");
|
||||
} else {
|
||||
$.get("/plugins/dynamix.vm.manager/VMajax.php?action=acs-override-disable", function( data ) {
|
||||
$("#settingsForm").submit();
|
||||
}, "json");
|
||||
}
|
||||
});
|
||||
|
||||
$("input[data-pickroot]").fileTreeAttach();
|
||||
});
|
||||
</script>
|
||||
372
plugins/dynamix.vm.manager/VMajax.php
Normal file
@@ -0,0 +1,372 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
|
||||
require_once('/usr/local/emhttp/webGui/include/Helpers.php');
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt.php');
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt_helpers.php');
|
||||
|
||||
$arrSizePrefix = [
|
||||
0 => '',
|
||||
1 => 'K',
|
||||
2 => 'M',
|
||||
3 => 'G',
|
||||
4 => 'T',
|
||||
5 => 'P'
|
||||
];
|
||||
|
||||
$_REQUEST = array_merge($_GET, $_POST);
|
||||
|
||||
$action = array_key_exists('action', $_REQUEST) ? $_REQUEST['action'] : '';
|
||||
$uuid = array_key_exists('uuid', $_REQUEST) ? $_REQUEST['uuid'] : '';
|
||||
|
||||
// Make sure libvirt is connected to qemu
|
||||
if (!isset($lv) || !$lv->enabled()) {
|
||||
header('Content-Type: application/json');
|
||||
die(json_encode(['error' => 'failed to connect to the hypervisor']));
|
||||
}
|
||||
|
||||
if ($uuid) {
|
||||
$domName = $lv->domain_get_name_by_uuid($uuid);
|
||||
if (!$domName) {
|
||||
header('Content-Type: application/json');
|
||||
die(json_encode(['error' => $lv->get_last_error()]));
|
||||
}
|
||||
}
|
||||
|
||||
$arrResponse = [];
|
||||
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'domain-autostart':
|
||||
$arrResponse = $lv->domain_set_autostart($domName, ($_REQUEST['autostart'] != "false")) ?
|
||||
['success' => true, 'autostart' => (bool)$lv->domain_get_autostart($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-start':
|
||||
$arrResponse = $lv->domain_start($domName) ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-pause':
|
||||
$arrResponse = $lv->domain_suspend($domName) ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-resume':
|
||||
$arrResponse = $lv->domain_resume($domName) ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-pmwakeup':
|
||||
// No support in libvirt-php to do a dompmwakeup, use virsh tool instead
|
||||
exec("virsh dompmwakeup " . escapeshellarg($uuid) . " 2>&1", $arrOutput, $intReturnCode);
|
||||
$arrResponse = ($intReturnCode == 0) ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => str_replace('error: ', '', implode('. ', $arrOutput))];
|
||||
break;
|
||||
|
||||
case 'domain-restart':
|
||||
$arrResponse = $lv->domain_reboot($domName) ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-save':
|
||||
$arrResponse = $lv->domain_save($domName) ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-stop':
|
||||
$arrResponse = $lv->domain_shutdown($domName) ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-destroy':
|
||||
$arrResponse = $lv->domain_destroy($domName) ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-delete':
|
||||
$arrResponse = $lv->domain_delete($domName) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-undefine':
|
||||
$arrResponse = $lv->domain_undefine($domName) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-define':
|
||||
$domName = $lv->domain_define($_REQUEST['xml']);
|
||||
$arrResponse = $domName ?
|
||||
['success' => true, 'state' => $lv->domain_get_state($domName)] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-state':
|
||||
$state = $lv->domain_get_state($domName);
|
||||
$arrResponse = ($state) ?
|
||||
['success' => true, 'state' => $state] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-diskdev':
|
||||
$arrResponse = ($lv->domain_set_disk_dev($domName, $_REQUEST['olddev'], $_REQUEST['diskdev'])) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'cdrom-change':
|
||||
$arrResponse = ($lv->domain_change_cdrom($domName, $_REQUEST['cdrom'], $_REQUEST['dev'], $_REQUEST['bus'])) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'memory-change':
|
||||
$arrResponse = ($lv->domain_set_memory($domName, $_REQUEST['memory']*1024)) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'vcpu-change':
|
||||
$arrResponse = ($lv->domain_set_vcpu($domName, $_REQUEST['vcpu'])) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'bootdev-change':
|
||||
$arrResponse = ($lv->domain_set_boot_device($domName, $_REQUEST['bootdev'])) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'disk-remove':
|
||||
$arrResponse = ($lv->domain_disk_remove($domName, $_REQUEST['dev'])) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'snap-create':
|
||||
$arrResponse = ($lv->domain_snapshot_create($domName)) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'snap-delete':
|
||||
$arrResponse = ($lv->domain_snapshot_delete($domName, $_REQUEST['snap'])) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'snap-revert':
|
||||
$arrResponse = ($lv->domain_snapshot_revert($domName, $_REQUEST['snap'])) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'snap-desc':
|
||||
$arrResponse = ($lv->snapshot_set_metadata($domName, $_REQUEST['snap'], $_REQUEST['snapdesc'])) ?
|
||||
['success' => true] :
|
||||
['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'disk-create':
|
||||
$disk = $_REQUEST['disk'];
|
||||
$driver = $_REQUEST['driver'];
|
||||
$size = str_replace(array("KB","MB","GB","TB","PB", " ", ","), array("K","M","G","T","P", "", ""), strtoupper($_REQUEST['size']));
|
||||
|
||||
$dir = dirname($disk);
|
||||
|
||||
if (!is_dir($dir))
|
||||
mkdir($dir);
|
||||
|
||||
// determine the actual disk if user share is being used
|
||||
if (strpos($dir, '/mnt/user/') === 0) {
|
||||
$tmp = parse_ini_string(shell_exec("getfattr -n user.LOCATION " . escapeshellarg($dir) . " | grep user.LOCATION"));
|
||||
$dir = str_replace('/mnt/user', '/mnt/' . $tmp['user.LOCATION'], $dir); // replace 'user' with say 'cache' or 'disk1' etc
|
||||
}
|
||||
|
||||
@exec("chattr +C -R " . escapeshellarg($dir) . " >/dev/null");
|
||||
|
||||
$strLastLine = exec("qemu-img create -q -f " . escapeshellarg($driver) . " " . escapeshellarg($disk) . " " . escapeshellarg($size) . " 2>&1", $out, $status);
|
||||
|
||||
if (empty($status)) {
|
||||
$arrResponse = ['success' => true];
|
||||
} else {
|
||||
$arrResponse = ['error' => $strLastLine];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'disk-resize':
|
||||
$disk = $_REQUEST['disk'];
|
||||
$capacity = str_replace(array("KB","MB","GB","TB","PB", " ", ","), array("K","M","G","T","P", "", ""), strtoupper($_REQUEST['cap']));
|
||||
$old_capacity = str_replace(array("KB","MB","GB","TB","PB", " ", ","), array("K","M","G","T","P", "", ""), strtoupper($_REQUEST['oldcap']));
|
||||
|
||||
if (substr($old_capacity,0,-1) < substr($capacity,0,-1)){
|
||||
$strLastLine = exec("qemu-img resize -q " . escapeshellarg($disk) . " " . escapeshellarg($capacity) . " 2>&1", $out, $status);
|
||||
if (empty($status)) {
|
||||
$arrResponse = ['success' => true];
|
||||
} else {
|
||||
$arrResponse = ['error' => $strLastLine];
|
||||
}
|
||||
} else {
|
||||
$arrResponse = ['error' => "Disk capacity has to be greater than " . $old_capacity];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'file-info':
|
||||
$file = $_REQUEST['file'];
|
||||
|
||||
$arrResponse = [
|
||||
'isfile' => (!empty($file) ? is_file($file) : false),
|
||||
'isdir' => (!empty($file) ? is_dir($file) : false),
|
||||
'isblock' => (!empty($file) ? is_block($file) : false),
|
||||
'resizable' => false
|
||||
];
|
||||
|
||||
// if file, get size and format info
|
||||
if (is_file($file)) {
|
||||
$json_info = json_decode(shell_exec("qemu-img info --output json " . escapeshellarg($file)), true);
|
||||
if (!empty($json_info)) {
|
||||
$intDisplaySize = (int)$json_info['virtual-size'];
|
||||
$intShifts = 0;
|
||||
while (!empty($intDisplaySize) &&
|
||||
(floor($intDisplaySize) == $intDisplaySize) &&
|
||||
isset($arrSizePrefix[$intShifts])) {
|
||||
|
||||
$arrResponse['display-size'] = $intDisplaySize . $arrSizePrefix[$intShifts];
|
||||
|
||||
$intDisplaySize /= 1024;
|
||||
$intShifts++;
|
||||
}
|
||||
|
||||
$arrResponse['virtual-size'] = $json_info['virtual-size'];
|
||||
$arrResponse['actual-size'] = $json_info['actual-size'];
|
||||
$arrResponse['format'] = $json_info['format'];
|
||||
$arrResponse['dirty-flag'] = $json_info['dirty-flag'];
|
||||
$arrResponse['resizable'] = true;
|
||||
}
|
||||
} else if (is_block($file)) {
|
||||
$strDevSize = trim(shell_exec("blockdev --getsize64 " . escapeshellarg($file)));
|
||||
if (!empty($strDevSize) && is_numeric($strDevSize)) {
|
||||
$arrResponse['actual-size'] = (int)$strDevSize;
|
||||
$arrResponse['format'] = 'raw';
|
||||
|
||||
$intDisplaySize = (int)$strDevSize;
|
||||
$intShifts = 0;
|
||||
while (!empty($intDisplaySize) &&
|
||||
($intDisplaySize >= 2) &&
|
||||
isset($arrSizePrefix[$intShifts])) {
|
||||
|
||||
$arrResponse['display-size'] = round($intDisplaySize, 0) . $arrSizePrefix[$intShifts];
|
||||
|
||||
$intDisplaySize /= 1000; // 1000 looks better than 1024 for block devs
|
||||
$intShifts++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'generate-mac':
|
||||
$arrResponse = [
|
||||
'mac' => $lv->generate_random_mac_addr()
|
||||
];
|
||||
break;
|
||||
|
||||
case 'acs-override-enable':
|
||||
// Check the /boot/syslinux/syslinux.cfg for the existance of pcie_acs_override=downstream, add it in if not found
|
||||
$arrSyslinuxCfg = file('/boot/syslinux/syslinux.cfg');
|
||||
$strCurrentLabel = '';
|
||||
$boolModded = false;
|
||||
foreach ($arrSyslinuxCfg as &$strSyslinuxCfg) {
|
||||
if (stripos(trim($strSyslinuxCfg), 'label ') === 0) {
|
||||
$strCurrentLabel = trim(str_ireplace('label ', '', $strSyslinuxCfg));
|
||||
}
|
||||
if (stripos($strSyslinuxCfg, 'append ') !== false) {
|
||||
if (stripos($strSyslinuxCfg, 'pcie_acs_override=') === false) {
|
||||
// pcie_acs_override=downstream was not found so append it in
|
||||
$strSyslinuxCfg = str_ireplace('append ', 'append pcie_acs_override=downstream ', $strSyslinuxCfg);
|
||||
$boolModded = true;
|
||||
}
|
||||
|
||||
// We just modify the first append line, other boot menu items are untouched
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($boolModded) {
|
||||
// Backup syslinux.cfg
|
||||
copy('/boot/syslinux/syslinux.cfg', '/boot/syslinux/syslinux.cfg-');
|
||||
|
||||
// Write Changes to syslinux.cfg
|
||||
file_put_contents('/boot/syslinux/syslinux.cfg', implode('', $arrSyslinuxCfg));
|
||||
}
|
||||
|
||||
$arrResponse = ['success' => true, 'label' => $strCurrentLabel];
|
||||
break;
|
||||
|
||||
case 'acs-override-disable':
|
||||
// Check the /boot/syslinux/syslinux.cfg for the existance of pcie_acs_override=, remove it if found
|
||||
$arrSyslinuxCfg = file('/boot/syslinux/syslinux.cfg');
|
||||
$strCurrentLabel = '';
|
||||
$boolModded = false;
|
||||
foreach ($arrSyslinuxCfg as &$strSyslinuxCfg) {
|
||||
if (stripos(trim($strSyslinuxCfg), 'label ') === 0) {
|
||||
$strCurrentLabel = trim(str_ireplace('label ', '', $strSyslinuxCfg));
|
||||
}
|
||||
if (stripos($strSyslinuxCfg, 'append ') !== false) {
|
||||
if (stripos($strSyslinuxCfg, 'pcie_acs_override=') !== false) {
|
||||
// pcie_acs_override= was found so remove the two variations
|
||||
$strSyslinuxCfg = str_ireplace('pcie_acs_override=downstream ', '', $strSyslinuxCfg);
|
||||
$strSyslinuxCfg = str_ireplace('pcie_acs_override=multifunction ', '', $strSyslinuxCfg);
|
||||
$boolModded = true;
|
||||
}
|
||||
|
||||
// We just modify the first append line, other boot menu items are untouched
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($boolModded) {
|
||||
// Backup syslinux.cfg
|
||||
copy('/boot/syslinux/syslinux.cfg', '/boot/syslinux/syslinux.cfg-');
|
||||
|
||||
// Write Changes to syslinux.cfg
|
||||
file_put_contents('/boot/syslinux/syslinux.cfg', implode('', $arrSyslinuxCfg));
|
||||
}
|
||||
|
||||
$arrResponse = ['success' => true, 'label' => $strCurrentLabel];
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
$arrResponse = ['error' => 'Unknown action \'' . $action . '\''];
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
die(json_encode($arrResponse));
|
||||
|
||||
329
plugins/dynamix.vm.manager/VMedit.php
Normal file
@@ -0,0 +1,329 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
require_once('/usr/local/emhttp/webGui/include/Helpers.php');
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt.php');
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt_helpers.php');
|
||||
|
||||
$arrLoad = [
|
||||
'name' => '',
|
||||
'icon' => 'default.png',
|
||||
'desc' => '',
|
||||
'autostart' => false
|
||||
];
|
||||
|
||||
$strSelectedTemplate = 'Custom';
|
||||
|
||||
if (!empty($_GET['uuid'])) {
|
||||
// Edit VM mode
|
||||
$res = $lv->domain_get_domain_by_uuid($_GET['uuid']);
|
||||
|
||||
$strIcon = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@icon');
|
||||
|
||||
if (!empty($strIcon)) {
|
||||
if (file_exists($strIcon)) {
|
||||
$strIconURL = $strIcon;
|
||||
} else if (file_exists('/usr/local/emhttp/plugins/dynamix.vm.manager/templates/images/' . $strIcon)) {
|
||||
$strIconURL = '/plugins/dynamix.vm.manager/templates/images/' . $strIcon;
|
||||
}
|
||||
} else {
|
||||
$strIcon = ($lv->domain_get_clock_offset($res) == 'localtime' ? 'windows.png' : 'linux.png');
|
||||
$strIconURL = '/plugins/dynamix.vm.manager/templates/images/' . $strIcon;
|
||||
}
|
||||
|
||||
$arrLoad = [
|
||||
'name' => $lv->domain_get_name($res),
|
||||
'icon' => $strIcon,
|
||||
'desc' => $lv->domain_get_description($res),
|
||||
'autostart' => $lv->domain_get_autostart($res)
|
||||
];
|
||||
|
||||
if (!empty($_GET['template'])) {
|
||||
$strSelectedTemplate = $_GET['template'];
|
||||
} else {
|
||||
$strTemplate = $lv->_get_single_xpath_result($res, '//domain/metadata/vmtemplate/@name');
|
||||
if (!empty($strTemplate)) {
|
||||
$strSelectedTemplate = $strTemplate;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// New VM mode
|
||||
$strIcon = 'windows.png';
|
||||
$strIconURL = '/plugins/dynamix.vm.manager/templates/images/windows.png';
|
||||
|
||||
$arrLoad['icon'] = $strIcon;
|
||||
|
||||
if (!empty($_GET['template'])) {
|
||||
$strSelectedTemplate = $_GET['template'];
|
||||
}
|
||||
}
|
||||
|
||||
$arrTemplates = array();
|
||||
|
||||
// Read files from the templates folder
|
||||
foreach (glob('plugins/dynamix.vm.manager/templates/*.form.php') as $template) {
|
||||
$arrTemplates[] = basename($template, '.form.php');
|
||||
}
|
||||
|
||||
if (!empty($arrTemplates) && !in_array($strSelectedTemplate, $arrTemplates)) {
|
||||
$strSelectedTemplate = $arrTemplates[0];
|
||||
}
|
||||
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.switchbutton.css">
|
||||
<style type="text/css">
|
||||
body { -webkit-overflow-scrolling: touch;}
|
||||
.fileTree {
|
||||
width: 305px;
|
||||
max-height: 150px;
|
||||
overflow: scroll;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
}
|
||||
#createform table {
|
||||
margin-top: 0;
|
||||
}
|
||||
#createform div#title + table {
|
||||
margin-top: -21px;
|
||||
}
|
||||
#createform table tr {
|
||||
vertical-align: top;
|
||||
line-height: 24px;
|
||||
}
|
||||
#createform table tr td:nth-child(odd) {
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
#createform table tr td:nth-child(even) {
|
||||
width: 80px;
|
||||
}
|
||||
#createform table tr td:last-child {
|
||||
width: inherit;
|
||||
}
|
||||
#createform .multiple {
|
||||
position: relative;
|
||||
}
|
||||
#createform .sectionbutton {
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
cursor: pointer;
|
||||
opacity: 0.4;
|
||||
font-size: 15px;
|
||||
line-height: 17px;
|
||||
z-index: 10;
|
||||
transition-property: opacity, left;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
#createform .sectionbutton.remove { top: 0; opacity: 0.3; }
|
||||
#createform .sectionbutton.add { bottom: 0; }
|
||||
#createform .sectionbutton:hover { opacity: 1.0; }
|
||||
#createform .sectiontab {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
left: 0;
|
||||
width: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: #DDDDDD;
|
||||
transition-property: background, width;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
#createform .multiple:hover .sectionbutton {
|
||||
opacity: 0.7;
|
||||
left: 4px;
|
||||
}
|
||||
#createform .multiple:hover .sectionbutton.remove {
|
||||
opacity: 0.6;
|
||||
}
|
||||
#createform .multiple:hover .sectiontab {
|
||||
background-color: #CCCCCC;
|
||||
width: 8px;
|
||||
}
|
||||
#form_content {
|
||||
margin-top: -21px;
|
||||
}
|
||||
span.advancedview_panel {
|
||||
display: none;
|
||||
line-height: 16px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
.basic {
|
||||
/*Empty placeholder*/
|
||||
}
|
||||
.advanced {
|
||||
display: none;
|
||||
}
|
||||
.switch-button-label.off {
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="content" style="margin-top:-21px;margin-left:0px">
|
||||
<form id="createform" method="POST">
|
||||
<input type="hidden" name="domain[type]" value="kvm" />
|
||||
|
||||
<table>
|
||||
<tr <? if (!empty($arrLoad['name'])) echo 'style="display: none"'; ?>>
|
||||
<td>Template:</td>
|
||||
<td>
|
||||
<select id="domain_template" name="template[name]" class="narrow" title="Choose a preconfigured template or select Custom to create your own from scratch">
|
||||
<?php
|
||||
foreach ($arrTemplates as $strTemplate) {
|
||||
echo mk_option($strSelectedTemplate, $strTemplate, str_replace('_', ' ', $strTemplate));
|
||||
}
|
||||
?>
|
||||
</select><img src="/webGui/images/spinner.gif" style="display: none">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div <? if (!empty($arrLoad['name'])) echo 'style="display: none"'; ?>>
|
||||
<blockquote class="inline_help">
|
||||
<p>Choose a preconfigured template or select Custom to create your own from scratch.</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Icon:</td>
|
||||
<td><input type="hidden" name="template[icon]" id="template_icon" value="<?=$arrLoad['icon']?>" /><img id="template_img" src="<?=htmlentities($strIconURL)?>" width="48" height="48" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr class="non_expert_xml">
|
||||
<td>Name:</td>
|
||||
<td><input type="text" name="domain[name]" class="textTemplate" title="Name of virtual machine" placeholder="e.g. My Workstation" value="<?=htmlentities($arrLoad['name'])?>" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="non_expert_xml">
|
||||
<blockquote class="inline_help">
|
||||
<p>Give the VM a name (e.g. Work, Gaming, Media Player, Firewall, Bitcoin Miner)</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr class="non_expert_xml">
|
||||
<td>Description:</td>
|
||||
<td><input type="text" name="domain[desc]" title="description of virtual machine" placeholder="description of virtual machine (optional)" value="<?=htmlentities($arrLoad['desc'])?>" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="non_expert_xml">
|
||||
<blockquote class="inline_help">
|
||||
<p>Give the VM a brief description (optional field).</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr style="line-height: 15px; vertical-align: middle;">
|
||||
<td>Autostart:</td>
|
||||
<td><div style="margin-left: -10px"><input type="checkbox" id="domain_autostart" name="domain[autostart]" style="display: none" class="autostart" value="1" <? if ($arrLoad['autostart']) echo 'checked'; ?>></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
<blockquote class="inline_help">
|
||||
<p>If you want this VM to start with the array, set this to yes.</p>
|
||||
</blockquote>
|
||||
|
||||
<div id="title">
|
||||
<span class="left"><img src="/plugins/dynamix.docker.manager/icons/preferences.png" class="icon">Template Settings</span>
|
||||
<span class="status advancedview_panel"><input type="checkbox" class="advancedview"></span>
|
||||
</div>
|
||||
|
||||
<div id="form_content"></div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/webGui/javascript/jquery.filetree.js"></script>
|
||||
<script src="/webGui/javascript/jquery.switchbutton.js"></script>
|
||||
<script src="/plugins/dynamix.vm.manager/scripts/dynamix.vm.manager.js"></script>
|
||||
<script>
|
||||
function isVMAdvancedMode() {
|
||||
return ($.cookie('vmmanager_listview_mode') == 'advanced');
|
||||
}
|
||||
function checkForAdvancedMode() {
|
||||
var $el = $('#form_content');
|
||||
|
||||
var $advanced = $el.find('.advanced');
|
||||
var $basic = $el.find('.basic');
|
||||
|
||||
if ($advanced.length || $basic.length) {
|
||||
$('.advancedview_panel').fadeIn('fast');
|
||||
if (isVMAdvancedMode()) {
|
||||
$('.basic').hide();
|
||||
$('.advanced').filter(function() {
|
||||
return (($(this).prop('style').display + '') === '');
|
||||
}).show();
|
||||
} else {
|
||||
$('.advanced').hide();
|
||||
$('.basic').filter(function() {
|
||||
return (($(this).prop('style').display + '') === '');
|
||||
}).show();
|
||||
}
|
||||
} else {
|
||||
$('.advancedview_panel').fadeOut('fast');
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$('.autostart').switchButton({
|
||||
on_label: 'Yes',
|
||||
off_label: 'No',
|
||||
labels_placement: "right"
|
||||
});
|
||||
$('.autostart').change(function () {
|
||||
$('#domain_autostart').prop('checked', $(this).is(':checked'));
|
||||
});
|
||||
|
||||
$('.advancedview').switchButton({
|
||||
labels_placement: "left",
|
||||
on_label: 'Advanced View',
|
||||
off_label: 'Basic View',
|
||||
checked: isVMAdvancedMode()
|
||||
});
|
||||
$('.advancedview').change(function () {
|
||||
toggleRows('advanced', $(this).is(':checked'), 'basic');
|
||||
$.cookie('vmmanager_listview_mode', $(this).is(':checked') ? 'advanced' : 'basic', { expires: 3650 });
|
||||
});
|
||||
|
||||
$('#domain_template').change(function loadDomainTemplate(){
|
||||
var $el = $('#form_content');
|
||||
var templateName = $(this).val();
|
||||
|
||||
toggleRows('non_expert_xml', (templateName != 'XML_Expert'));
|
||||
|
||||
$el.fadeOut(100, function(){
|
||||
$el.html('<div style="padding-left: 80px; padding-top: 20px; font-size: 1.3em"><img src="/webGui/images/spinner.gif"> Loading...</div>').fadeIn('fast');
|
||||
});
|
||||
|
||||
$.get('/plugins/dynamix.vm.manager/templates/' + templateName + '.form.php' + location.search, function(data) {
|
||||
$el.stop(true, false).fadeOut(100, function() {
|
||||
$el.html(data);
|
||||
|
||||
checkForAdvancedMode();
|
||||
|
||||
if ($.cookie('help')=='help') {
|
||||
$('.inline_help').show();
|
||||
}
|
||||
|
||||
$el.fadeIn('fast');
|
||||
});
|
||||
});
|
||||
}).change(); // Fire now too
|
||||
});
|
||||
</script>
|
||||
40
plugins/dynamix.vm.manager/VMs.page
Normal file
@@ -0,0 +1,40 @@
|
||||
Menu="Tasks:70"
|
||||
Type="xmenu"
|
||||
Cond="(pgrep('libvirtd')!==false)"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.filetree.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/jquery.switchbutton.css">
|
||||
<link type="text/css" rel="stylesheet" href="/webGui/styles/context.standalone.css">
|
||||
<style type="text/css">
|
||||
body{-webkit-overflow-scrolling:touch;}
|
||||
.fileTree{width:305px;max-height:150px;overflow:scroll;position:absolute;z-index:100;display:none;}
|
||||
</style>
|
||||
<script src="/webGui/javascript/jquery.filetree.js"></script>
|
||||
<script src="/webGui/javascript/jquery.switchbutton.js"></script>
|
||||
<script src="/webGui/javascript/context.js"></script>
|
||||
<script src="/plugins/dynamix.vm.manager/scripts/dynamix.vm.manager.js"></script>
|
||||
|
||||
<?
|
||||
if ($var['fsState'] != "Started") {
|
||||
echo "<p class='notice'>Array must be Started to manage Virtual Machines.</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt.php');
|
||||
require_once('/usr/local/emhttp/plugins/dynamix.vm.manager/classes/libvirt_helpers.php');
|
||||
|
||||
if (count($pages)==2) $tabbed = false;
|
||||
?>
|
||||
2232
plugins/dynamix.vm.manager/classes/libvirt.php
Normal file
542
plugins/dynamix.vm.manager/classes/libvirt_helpers.php
Normal file
@@ -0,0 +1,542 @@
|
||||
<?PHP
|
||||
/* Copyright 2015, Lime Technology
|
||||
* Copyright 2015, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
|
||||
// Load emhttp variables if needed.
|
||||
if (! isset($var)){
|
||||
if (! is_file("/usr/local/emhttp/state/var.ini")) shell_exec("wget -qO /dev/null localhost:$(lsof -nPc emhttp | grep -Po 'TCP[^\d]*\K\d+')");
|
||||
$var = @parse_ini_file("/usr/local/emhttp/state/var.ini");
|
||||
}
|
||||
|
||||
|
||||
// Check if program is running and
|
||||
$libvirt_running = trim(shell_exec( "[ -f /proc/`cat /var/run/libvirt/libvirtd.pid 2> /dev/null`/exe ] && echo 'yes' || echo 'no' 2> /dev/null" ));
|
||||
|
||||
// Create domain config if needed
|
||||
$domain_cfgfile = "/boot/config/domain.cfg";
|
||||
if (!file_exists($domain_cfgfile)) {
|
||||
file_put_contents($domain_cfgfile, 'SERVICE="disable"'."\n".'DEBUG="no"'."\n".'MEDIADIR="/mnt/"'."\n".'VIRTIOISO=""'."\n".'DISKDIR="/mnt/"'."\n".'BRNAME=""'."\n");
|
||||
} else {
|
||||
// This will clean any ^M characters (\r) caused by windows from the config file
|
||||
shell_exec("sed -i 's!\r!!g' '$domain_cfgfile'");
|
||||
}
|
||||
|
||||
$domain_cfg = parse_ini_file($domain_cfgfile);
|
||||
|
||||
if (!isset($domain_cfg['VIRTIOISO'])) {
|
||||
$domain_cfg['VIRTIOISO'] = "";
|
||||
}
|
||||
|
||||
$domain_debug = isset($domain_cfg['DEBUG']) ? $domain_cfg['DEBUG'] : "no";
|
||||
if ($domain_debug != "yes") {
|
||||
error_reporting(0);
|
||||
}
|
||||
|
||||
$domain_bridge = (!($domain_cfg['BRNAME'])) ? 'virbr0' : $domain_cfg['BRNAME'];
|
||||
$msg = (empty($domain_bridge)) ? "Error: Setup Bridge in Settings/Network Settings" : false;
|
||||
$libvirt_service = isset($domain_cfg['SERVICE']) ? $domain_cfg['SERVICE'] : "disable";
|
||||
|
||||
if ($libvirt_running == "yes"){
|
||||
$lv = new Libvirt('qemu:///system', null, null, false);
|
||||
$arrHostInfo = $lv->host_get_node_info();
|
||||
$maxcpu = (int)$arrHostInfo['cpus'];
|
||||
$maxmem = number_format(($arrHostInfo['memory'] / 1048576), 1, '.', ' ');
|
||||
}
|
||||
|
||||
$theme = $display['theme'];
|
||||
//set color on even rows for white or black theme
|
||||
function bcolor($row, $color) {
|
||||
if ($color == "white")
|
||||
$color = ($row % 2 == 0) ? "transparent" : "#F8F8F8";
|
||||
else
|
||||
$color = ($row % 2 == 0) ? "transparent" : "#0C0C0C";
|
||||
return $color;
|
||||
}
|
||||
|
||||
//create checkboxes for usb devices
|
||||
function usb_checkbox($usb, $key) {
|
||||
$deviceid = substr(strstr($usb, 'ID'),3,9);
|
||||
echo '<input class="checkbox" type="checkbox" value="'.$deviceid.'" name="usb['.$key.']" />';
|
||||
echo "<label>$usb</label><br />";
|
||||
}
|
||||
|
||||
//create memory drop down option based on max memory
|
||||
function memOption($maxmem) {
|
||||
for ($i = 1; $i <= ($maxmem*2); $i++) {
|
||||
$mem = ($i*512);
|
||||
echo "<option value='$mem'>$mem</option>";
|
||||
}
|
||||
}
|
||||
|
||||
//create drop down options from arrays
|
||||
function arrayOptions($ValueArray, $DisplayArray, $value) {
|
||||
for ($i = 0; $i < sizeof($ValueArray); $i++) {
|
||||
echo "<option value='$ValueArray[$i]'";
|
||||
if ($ValueArray[$i] == $value)
|
||||
echo " selected='selected'>$DisplayArray[$i] *</option>";
|
||||
else
|
||||
echo ">$DisplayArray[$i]</option>";
|
||||
}
|
||||
}
|
||||
|
||||
//create memory drop down options
|
||||
function memOptions($maxmem, $mem) {
|
||||
for ($i = 1; $i <= ($maxmem*2); $i++) {
|
||||
$mem2 = ($i*512);
|
||||
echo "<option value=".$mem2*1024;
|
||||
if ((int)$mem == $mem2*1024)
|
||||
echo " selected='selected'>$mem2 *</option>";
|
||||
else
|
||||
echo ">$mem2</option>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mk_dropdown_options($arrOptions, $strSelected) {
|
||||
foreach ($arrOptions as $key => $label) {
|
||||
echo mk_option($strSelected, $key, $label);
|
||||
}
|
||||
}
|
||||
|
||||
function appendOrdinalSuffix($number) {
|
||||
$ends = array('th','st','nd','rd','th','th','th','th','th','th');
|
||||
|
||||
if (($number % 100) >= 11 && ($number % 100) <= 13) {
|
||||
$abbreviation = $number . 'th';
|
||||
} else {
|
||||
$abbreviation = $number . $ends[$number % 10];
|
||||
}
|
||||
|
||||
return $abbreviation;
|
||||
}
|
||||
|
||||
|
||||
$cacheValidPCIDevices = null;
|
||||
function getValidPCIDevices() {
|
||||
global $cacheValidPCIDevices;
|
||||
|
||||
if (!is_null($cacheValidPCIDevices)) {
|
||||
return $cacheValidPCIDevices;
|
||||
}
|
||||
|
||||
$strOSUSBController = trim(shell_exec("udevadm info -q path -n /dev/disk/by-label/UNRAID 2>/dev/null | grep -Po '0000:\K\w{2}:\w{2}\.\w{1}'"));
|
||||
$strOSNetworkDevice = trim(shell_exec("udevadm info -q path -p /sys/class/net/eth0 2>/dev/null | grep -Po '0000:\K\w{2}:\w{2}\.\w{1}'"));
|
||||
|
||||
//TODO: add any drive controllers currently being used by unraid to the blacklist
|
||||
|
||||
$arrBlacklistIDs = array($strOSUSBController, $strOSNetworkDevice);
|
||||
$arrBlacklistClassIDregex = '/^(05|06|08|0a|0b|0c05)/';
|
||||
// Got Class IDs at the bottom of /usr/share/hwdata/pci.ids
|
||||
$arrWhitelistGPUClassIDregex = '/^(0001|03)/';
|
||||
$arrWhitelistAudioClassIDregex = '/^(0403)/';
|
||||
|
||||
$arrValidPCIDevices = array();
|
||||
|
||||
exec("lspci -m -nn 2>/dev/null", $arrAllPCIDevices);
|
||||
|
||||
foreach ($arrAllPCIDevices as $strPCIDevice) {
|
||||
// Example: 00:1f.0 "ISA bridge [0601]" "Intel Corporation [8086]" "Z77 Express Chipset LPC Controller [1e44]" -r04 "Micro-Star International Co., Ltd. [MSI] [1462]" "Device [7759]"
|
||||
if (preg_match('/^(?P<id>\S+) \"(?P<type>[^"]+) \[(?P<typeid>[a-f0-9]{4})\]\" \"(?P<vendorname>[^"]+) \[(?P<vendorid>[a-f0-9]{4})\]\" \"(?P<productname>[^"]+) \[(?P<productid>[a-f0-9]{4})\]\"/', $strPCIDevice, $arrMatch)) {
|
||||
if (in_array($arrMatch['id'], $arrBlacklistIDs) || preg_match($arrBlacklistClassIDregex, $arrMatch['typeid'])) {
|
||||
// Device blacklisted, skip device
|
||||
continue;
|
||||
}
|
||||
|
||||
$strClass = 'other';
|
||||
if (preg_match($arrWhitelistGPUClassIDregex, $arrMatch['typeid'])) {
|
||||
$strClass = 'vga';
|
||||
// Specialized product name cleanup for GPU
|
||||
// GF116 [GeForce GTX 550 Ti] --> GeForce GTX 550 Ti
|
||||
if (preg_match('/.+\[(?P<gpuname>.+)\]/', $arrMatch['productname'], $arrGPUMatch)) {
|
||||
$arrMatch['productname'] = $arrGPUMatch['gpuname'];
|
||||
}
|
||||
} else if (preg_match($arrWhitelistAudioClassIDregex, $arrMatch['typeid'])) {
|
||||
$strClass = 'audio';
|
||||
}
|
||||
|
||||
if ($strClass == 'vga' &&
|
||||
strpos($arrMatch['id'], '00:') === 0 &&
|
||||
(stripos($arrMatch['productname'], 'integrated') !== false || strpos($arrMatch['vendorname'], 'Intel ') !== false)) {
|
||||
// Our sorry attempt to detect a integrated gpu
|
||||
// Integrated gpus dont work for passthrough, skip device
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!file_exists('/sys/bus/pci/devices/0000:' . $arrMatch['id'] . '/iommu_group/')) {
|
||||
// No IOMMU support for device, skip device
|
||||
continue;
|
||||
}
|
||||
|
||||
// Specialized vendor name cleanup
|
||||
// e.g.: Advanced Micro Devices, Inc. [AMD/ATI] --> Advanced Micro Devices, Inc.
|
||||
if (preg_match('/(?P<gpuvendor>.+) \[.+\]/', $arrMatch['vendorname'], $arrGPUMatch)) {
|
||||
$arrMatch['vendorname'] = $arrGPUMatch['gpuvendor'];
|
||||
}
|
||||
|
||||
// Clean up the vendor and product name
|
||||
$arrMatch['vendorname'] = str_replace(['Advanced Micro Devices, Inc.'], 'AMD', $arrMatch['vendorname']);
|
||||
$arrMatch['vendorname'] = str_replace([' Corporation', ' Semiconductor Co., Ltd.', ' Technology Group Ltd.', ' Electronics Systems Ltd.', ' Systems, Inc.'], '', $arrMatch['vendorname']);
|
||||
$arrMatch['productname'] = str_replace([' PCI Express'], [' PCIe'], $arrMatch['productname']);
|
||||
|
||||
$arrValidPCIDevices[] = array(
|
||||
'id' => $arrMatch['id'],
|
||||
'type' => $arrMatch['type'],
|
||||
'typeid' => $arrMatch['typeid'],
|
||||
'vendorid' => $arrMatch['vendorid'],
|
||||
'vendorname' => $arrMatch['vendorname'],
|
||||
'productid' => $arrMatch['productid'],
|
||||
'productname' => $arrMatch['productname'],
|
||||
'class' => $strClass,
|
||||
'name' => $arrMatch['vendorname'] . ' ' . $arrMatch['productname']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$cacheValidPCIDevices = $arrValidPCIDevices;
|
||||
|
||||
return $arrValidPCIDevices;
|
||||
}
|
||||
|
||||
|
||||
function getValidGPUDevices() {
|
||||
$arrValidPCIDevices = getValidPCIDevices();
|
||||
|
||||
$arrValidGPUDevices = array_filter($arrValidPCIDevices, function($arrDev) {
|
||||
return ($arrDev['class'] == 'vga');
|
||||
});
|
||||
|
||||
return $arrValidGPUDevices;
|
||||
}
|
||||
|
||||
|
||||
function getValidAudioDevices() {
|
||||
$arrValidPCIDevices = getValidPCIDevices();
|
||||
|
||||
$arrValidAudioDevices = array_filter($arrValidPCIDevices, function($arrDev) {
|
||||
return ($arrDev['class'] == 'audio');
|
||||
});
|
||||
|
||||
return $arrValidAudioDevices;
|
||||
}
|
||||
|
||||
|
||||
function getValidOtherDevices() {
|
||||
$arrValidPCIDevices = getValidPCIDevices();
|
||||
|
||||
$arrValidOtherDevices = array_filter($arrValidPCIDevices, function($arrDev) {
|
||||
return ($arrDev['class'] == 'other');
|
||||
});
|
||||
|
||||
return $arrValidOtherDevices;
|
||||
}
|
||||
|
||||
|
||||
$cacheValidUSBDevices = null;
|
||||
function getValidUSBDevices() {
|
||||
global $cacheValidUSBDevices;
|
||||
|
||||
if (!is_null($cacheValidUSBDevices)) {
|
||||
return $cacheValidUSBDevices;
|
||||
}
|
||||
|
||||
$arrValidUSBDevices = array();
|
||||
|
||||
// Get a list of all usb hubs so we can blacklist them
|
||||
exec("cat /sys/bus/usb/drivers/hub/*/modalias | grep -Po 'usb:v\K\w{9}' | tr 'p' ':'", $arrAllUSBHubs);
|
||||
|
||||
exec("lsusb 2>/dev/null", $arrAllUSBDevices);
|
||||
|
||||
foreach ($arrAllUSBDevices as $strUSBDevice) {
|
||||
if (preg_match('/^.+ID (?P<id>\S+) (?P<name>.+)$/', $strUSBDevice, $arrMatch)) {
|
||||
$arrMatch['name'] = trim($arrMatch['name']);
|
||||
|
||||
if (empty($arrMatch['name'])) {
|
||||
// Device name is blank, replace using fallback default
|
||||
$arrMatch['name'] = 'unnamed device ('.$arrMatch['id'].')';
|
||||
}
|
||||
|
||||
if (stripos($GLOBALS['var']['flashGUID'], str_replace(':', '-', $arrMatch['id'])) === 0) {
|
||||
// Device id matches the unraid boot device, skip device
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array(strtoupper($arrMatch['id']), $arrAllUSBHubs)) {
|
||||
// Device class is a Hub, skip device
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrValidUSBDevices[] = array(
|
||||
'id' => $arrMatch['id'],
|
||||
'name' => $arrMatch['name'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$cacheValidUSBDevices = $arrValidUSBDevices;
|
||||
|
||||
return $arrValidUSBDevices;
|
||||
}
|
||||
|
||||
|
||||
function getValidMachineTypes() {
|
||||
global $lv;
|
||||
|
||||
$arrValidMachineTypes = [];
|
||||
|
||||
$arrQEMUInfo = $lv->get_connect_information();
|
||||
$arrMachineTypes = $lv->get_machine_types('x86_64');
|
||||
|
||||
$strQEMUVersion = $arrQEMUInfo['hypervisor_major'] . '.' . $arrQEMUInfo['hypervisor_minor'];
|
||||
|
||||
foreach ($arrMachineTypes as $arrMachine) {
|
||||
if ($arrMachine['name'] == 'q35') {
|
||||
// Latest Q35
|
||||
$arrValidMachineTypes['pc-q35-' . $strQEMUVersion] = 'Q35-' . $strQEMUVersion;
|
||||
}
|
||||
if (strpos($arrMachine['name'], 'q35-') !== false) {
|
||||
// Prior releases of Q35
|
||||
$arrValidMachineTypes[$arrMachine['name']] = str_replace(['q35', 'pc-'], ['Q35', ''], $arrMachine['name']);
|
||||
}
|
||||
if ($arrMachine['name'] == 'pc') {
|
||||
// Latest i440fx
|
||||
$arrValidMachineTypes['pc-i440fx-' . $strQEMUVersion] = 'i440fx-' . $strQEMUVersion;
|
||||
}
|
||||
if (strpos($arrMachine['name'], 'i440fx-') !== false) {
|
||||
// Prior releases of i440fx
|
||||
$arrValidMachineTypes[$arrMachine['name']] = str_replace('pc-', '', $arrMachine['name']);
|
||||
}
|
||||
}
|
||||
|
||||
arsort($arrValidMachineTypes);
|
||||
|
||||
return $arrValidMachineTypes;
|
||||
}
|
||||
|
||||
|
||||
function getLatestMachineType($strType = 'i440fx') {
|
||||
$arrMachineTypes = getValidMachineTypes();
|
||||
|
||||
foreach ($arrMachineTypes as $key => $value) {
|
||||
if (stripos($key, $strType) !== false) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
return array_shift(array_keys($arrMachineTypes));
|
||||
}
|
||||
|
||||
|
||||
function getValidDiskDrivers() {
|
||||
$arrValidDiskDrivers = [
|
||||
'raw' => 'raw',
|
||||
'qcow2' => 'qcow2'
|
||||
];
|
||||
|
||||
return $arrValidDiskDrivers;
|
||||
}
|
||||
|
||||
|
||||
function getValidKeyMaps() {
|
||||
$arrValidKeyMaps = [
|
||||
'ar' => 'Arabic (ar)',
|
||||
'hr' => 'Croatian (hr)',
|
||||
'cz' => 'Czech (cz)',
|
||||
'da' => 'Danish (da)',
|
||||
'nl' => 'Dutch (nl)',
|
||||
'nl-be' => 'Dutch-Belgium (nl-be)',
|
||||
'en-gb' => 'English-United Kingdom (en-gb)',
|
||||
'en-us' => 'English-United States (en-us)',
|
||||
'es' => 'Español (es)',
|
||||
'et' => 'Estonian (et)',
|
||||
'fo' => 'Faroese (fo)',
|
||||
'fi' => 'Finnish (fi)',
|
||||
'fr' => 'French (fr)',
|
||||
'bepo' => 'French-Bépo (bepo)',
|
||||
'fr-be' => 'French-Belgium (fr-be)',
|
||||
'fr-ca' => 'French-Canadian (fr-ca)',
|
||||
'fr-ch' => 'French-Switzerland (fr-ch)',
|
||||
'de-ch' => 'German-Switzerland (de-ch)',
|
||||
'hu' => 'Hungarian (hu)',
|
||||
'is' => 'Icelandic (is)',
|
||||
'it' => 'Italian (it)',
|
||||
'ja' => 'Japanese (ja)',
|
||||
'lv' => 'Latvian (lv)',
|
||||
'lt' => 'Lithuanian (lt)',
|
||||
'mk' => 'Macedonian (mk)',
|
||||
'no' => 'Norwegian (no)',
|
||||
'pl' => 'Polish (pl)',
|
||||
'pt-br' => 'Portuguese-Brazil (pt-br)',
|
||||
'ru' => 'Russian (ru)',
|
||||
'sl' => 'Slovene (sl)',
|
||||
'sv' => 'Swedish (sv)',
|
||||
'th' => 'Thailand (th)',
|
||||
'tr' => 'Turkish (tr)'
|
||||
];
|
||||
|
||||
return $arrValidKeyMaps;
|
||||
}
|
||||
|
||||
|
||||
function getHostCPUModel() {
|
||||
$cpu = explode('#', exec("dmidecode -q -t 4|awk -F: '{if(/Version:/) v=$2; else if(/Current Speed:/) s=$2} END{print v\"#\"s}'"));
|
||||
list($strCPUModel) = explode('@', str_replace(array("Processor","CPU","(C)","(R)","(TM)"), array("","","©","®","™"), $cpu[0]) . '@');
|
||||
return trim($strCPUModel);
|
||||
}
|
||||
|
||||
|
||||
function getNetworkBridges() {
|
||||
exec("brctl show | awk -F'\t' 'FNR > 1 {print \$1}' | awk 'NF > 0'", $arrValidBridges);
|
||||
|
||||
if (!is_array($arrValidBridges)) {
|
||||
$arrValidBridges = [];
|
||||
}
|
||||
|
||||
// Make sure the default libvirt bridge is first in the list
|
||||
if (($key = array_search('virbr0', $arrValidBridges)) !== false) {
|
||||
unset($arrValidBridges[$key]);
|
||||
}
|
||||
// We always list virbr0 because libvirt might not be started yet (thus the bridge doesn't exists)
|
||||
array_unshift($arrValidBridges, 'virbr0');
|
||||
|
||||
return array_values($arrValidBridges);
|
||||
}
|
||||
|
||||
|
||||
function domain_to_config($uuid) {
|
||||
global $lv;
|
||||
|
||||
$arrValidGPUDevices = getValidGPUDevices();
|
||||
$arrValidAudioDevices = getValidAudioDevices();
|
||||
$arrValidOtherDevices = getValidOtherDevices();
|
||||
$arrValidUSBDevices = getValidUSBDevices();
|
||||
$arrValidDiskDrivers = getValidDiskDrivers();
|
||||
|
||||
$res = $lv->domain_get_domain_by_uuid($uuid);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$medias = $lv->get_cdrom_stats($res);
|
||||
$disks = $lv->get_disk_stats($res, false);
|
||||
$arrNICs = $lv->get_nic_info($res);
|
||||
$arrHostDevs = $lv->domain_get_host_devices_pci($res);
|
||||
$arrUSBDevs = $lv->domain_get_host_devices_usb($res);
|
||||
|
||||
|
||||
// Metadata Parsing
|
||||
// libvirt xpath parser sucks, use php's xpath parser instead
|
||||
$strDOMXML = $lv->domain_get_xml($res);
|
||||
$xmldoc = new DOMDocument();
|
||||
$xmldoc->loadXML($strDOMXML);
|
||||
$xpath = new DOMXPath($xmldoc);
|
||||
$objNodes = $xpath->query('//domain/metadata/vmtemplate/@*');
|
||||
|
||||
$arrTemplateValues = [];
|
||||
if ($objNodes->length > 0) {
|
||||
foreach ($objNodes as $objNode) {
|
||||
$arrTemplateValues[$objNode->nodeName] = $objNode->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($arrTemplateValues['name'])) {
|
||||
$arrTemplateValues['name'] = 'Custom';
|
||||
}
|
||||
|
||||
|
||||
$arrGPUDevices = [];
|
||||
$arrAudioDevices = [];
|
||||
$arrOtherDevices = [];
|
||||
|
||||
// check for vnc; add to arrGPUDevices
|
||||
$intVNCPort = $lv->domain_get_vnc_port($res);
|
||||
if (!empty($intVNCPort)) {
|
||||
$arrGPUDevices[] = [
|
||||
'id' => 'vnc',
|
||||
'keymap' => $lv->domain_get_vnc_keymap($res)
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($arrHostDevs as $arrHostDev) {
|
||||
$arrFoundGPUDevices = array_filter($arrValidGPUDevices, function($arrDev) use ($arrHostDev) { return ($arrDev['id'] == $arrHostDev['id']); });
|
||||
if (!empty($arrFoundGPUDevices)) {
|
||||
$arrGPUDevices[] = ['id' => $arrHostDev['id']];
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrFoundAudioDevices = array_filter($arrValidAudioDevices, function($arrDev) use ($arrHostDev) { return ($arrDev['id'] == $arrHostDev['id']); });
|
||||
if (!empty($arrFoundAudioDevices)) {
|
||||
$arrAudioDevices[] = ['id' => $arrHostDev['id']];
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrFoundOtherDevices = array_filter($arrValidOtherDevices, function($arrDev) use ($arrHostDev) { return ($arrDev['id'] == $arrHostDev['id']); });
|
||||
if (!empty($arrFoundOtherDevices)) {
|
||||
$arrOtherDevices[] = ['id' => $arrHostDev['id']];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add claimed USB devices by this VM to the available USB devices
|
||||
/*
|
||||
foreach($arrUSBDevs as $arrUSB) {
|
||||
$arrValidUSBDevices[] = array(
|
||||
'id' => $arrUSB['id'],
|
||||
'name' => $arrUSB['product'],
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
$arrDisks = [];
|
||||
foreach ($disks as $disk) {
|
||||
$arrDisks[] = [
|
||||
'new' => (empty($disk['file']) ? $disk['partition'] : $disk['file']),
|
||||
'size' => '',
|
||||
'driver' => 'raw',
|
||||
'dev' => $disk['device'],
|
||||
'bus' => $disk['bus']
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'template' => $arrTemplateValues,
|
||||
'domain' => [
|
||||
'name' => $lv->domain_get_name($res),
|
||||
'desc' => $lv->domain_get_description($res),
|
||||
'persistent' => 1,
|
||||
'uuid' => $lv->domain_get_uuid($res),
|
||||
'clock' => $lv->domain_get_clock_offset($res),
|
||||
'arch' => $lv->domain_get_arch($res),
|
||||
'machine' => $lv->domain_get_machine($res),
|
||||
'mem' => $lv->domain_get_current_memory($res),
|
||||
'maxmem' => $lv->domain_get_memory($res),
|
||||
'password' => '', //TODO?
|
||||
'cpumode' => $lv->domain_get_cpu_type($res),
|
||||
'vcpus' => $dom['nrVirtCpu'],
|
||||
'vcpu' => $lv->domain_get_vcpu_pins($res),
|
||||
'hyperv' => ($lv->domain_get_feature($res, 'hyperv') ? 1 : 0),
|
||||
'autostart' => ($lv->domain_get_autostart($res) ? 1 : 0),
|
||||
'state' => $lv->domain_state_translate($dom['state']),
|
||||
'ovmf' => ($lv->domain_get_ovmf($res) ? 1 : 0)
|
||||
],
|
||||
'media' => [
|
||||
'cdrom' => (!empty($medias) && !empty($medias[0]) && array_key_exists('file', $medias[0])) ? $medias[0]['file'] : '',
|
||||
'drivers' => (!empty($medias) && !empty($medias[1]) && array_key_exists('file', $medias[1])) ? $medias[1]['file'] : ''
|
||||
],
|
||||
'disk' => $arrDisks,
|
||||
'gpu' => $arrGPUDevices,
|
||||
'audio' => $arrAudioDevices,
|
||||
'pci' => $arrOtherDevices,
|
||||
'nic' => $arrNICs,
|
||||
'usb' => $arrUSBDevs,
|
||||
'shares' => $lv->domain_get_mount_filesystems($res)
|
||||
];
|
||||
}
|
||||
|
||||
?>
|
||||
BIN
plugins/dynamix.vm.manager/dynamix.kvm.manager/domain.tar.xz
Normal file
74
plugins/dynamix.vm.manager/dynamix.kvm.manager/qemu
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env php
|
||||
|
||||
<?php
|
||||
if (!isset($argv[2]) || $argv[2] != 'start') {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$strXML = file_get_contents('php://stdin');
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadXML($strXML);
|
||||
|
||||
$xpath = new DOMXpath($doc);
|
||||
|
||||
$args = $xpath->evaluate("//domain/*[name()='qemu:commandline']/*[name()='qemu:arg']/@value");
|
||||
|
||||
for ($i = 0; $i < $args->length; $i++){
|
||||
$arg_list = explode(',', $args->item($i)->nodeValue);
|
||||
|
||||
if ($arg_list[0] !== 'vfio-pci') {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($arg_list as $arg) {
|
||||
$keypair = explode('=', $arg);
|
||||
|
||||
if ($keypair[0] == 'host' && !empty($keypair[1])) {
|
||||
vfio_bind($keypair[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit(0); // end of script
|
||||
|
||||
|
||||
|
||||
function vfio_bind($strPassthruDevice) {
|
||||
// Ensure we have leading 0000:
|
||||
$strPassthruDeviceShort = str_replace('0000:', '', $strPassthruDevice);
|
||||
$strPassthruDeviceLong = '0000:' . $strPassthruDeviceShort;
|
||||
|
||||
// Determine the driver currently assigned to the device
|
||||
$strDriverSymlink = @readlink('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/driver');
|
||||
|
||||
if ($strDriverSymlink !== false) {
|
||||
// Device is bound to a Driver already
|
||||
|
||||
if (strpos($strDriverSymlink, 'vfio-pci') !== false) {
|
||||
// Driver bound to vfio-pci already - nothing left to do for this device now regarding vfio
|
||||
return true;
|
||||
}
|
||||
|
||||
// Driver bound to some other driver - attempt to unbind driver
|
||||
if (file_put_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/driver/unbind', $strPassthruDeviceLong) === false) {
|
||||
file_put_contents('php://stderr', 'Failed to unbind device ' . $strPassthruDeviceShort . ' from current driver');
|
||||
exit(1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get Vendor and Device IDs for the passthru device
|
||||
$strVendor = file_get_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/vendor');
|
||||
$strDevice = file_get_contents('/sys/bus/pci/devices/' . $strPassthruDeviceLong . '/device');
|
||||
|
||||
// Attempt to bind driver to vfio-pci
|
||||
if (file_put_contents('/sys/bus/pci/drivers/vfio-pci/new_id', $strVendor . ' ' . $strDevice) === false) {
|
||||
file_put_contents('php://stderr', 'Failed to bind device ' . $strPassthruDeviceShort . ' to vfio-pci driver');
|
||||
exit(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
43
plugins/dynamix.vm.manager/event/started
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Only start if array has started in Normal operation mode
|
||||
if grep -q 'fsState="Started"' /var/local/emhttp/var.ini && grep -q 'startMode="Normal"' /var/local/emhttp/var.ini; then
|
||||
|
||||
SERVICE="disable"
|
||||
if [ -f /boot/config/domain.cfg ]; then
|
||||
source /boot/config/domain.cfg
|
||||
fi
|
||||
|
||||
#copy old image to new
|
||||
if [ -f /boot/config/plugins/virtMan/virtMan.img ]; then
|
||||
if [ "$(mount | grep virtMan.img)" ]; then
|
||||
umount /etc/libvirt
|
||||
fi
|
||||
if [ ! -f /boot/config/plugins/dynamix.kvm.manager/domain.img ]; then
|
||||
mkdir -p /boot/config/plugins/dynamix.kvm.manager
|
||||
cp /boot/config/plugins/virtMan/virtMan.img /boot/config/plugins/dynamix.kvm.manager/domain.img
|
||||
fi
|
||||
fi
|
||||
|
||||
#copy seed loopback image and qemu hook, if needed, to flash drive
|
||||
if [ -d /usr/local/emhttp/plugins/dynamix.vm.manager/dynamix.kvm.manager ]; then
|
||||
mkdir -p /boot/config/plugins/dynamix.kvm.manager
|
||||
tar --no-same-owner -xkf /usr/local/emhttp/plugins/dynamix.vm.manager/dynamix.kvm.manager/domain.tar.xz -C /boot/config/plugins/dynamix.kvm.manager/
|
||||
cp -n /usr/local/emhttp/plugins/dynamix.vm.manager/dynamix.kvm.manager/qemu /boot/config/plugins/dynamix.kvm.manager/
|
||||
fi
|
||||
|
||||
if [ "$SERVICE" = "enable" ]; then
|
||||
# mount xml/conf image if not already mounted
|
||||
if [ ! "$(mount | grep domain.img)" ]; then
|
||||
mount -t ext4 /boot/config/plugins/dynamix.kvm.manager/domain.img /etc/libvirt
|
||||
mkdir -p /etc/libvirt/hooks
|
||||
cp /boot/config/plugins/dynamix.kvm.manager/qemu /etc/libvirt/hooks/
|
||||
fi
|
||||
|
||||
# Start libvirt
|
||||
if [ -x /etc/rc.d/rc.libvirt ]; then
|
||||
echo "Starting libvirt..."
|
||||
/etc/rc.d/rc.libvirt start |& logger
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
11
plugins/dynamix.vm.manager/event/stopping_svcs
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Shutdown libvirt
|
||||
if [ -x /etc/rc.d/rc.libvirt ]; then
|
||||
echo "Stopping libvirt..."
|
||||
/etc/rc.d/rc.libvirt stop |& logger
|
||||
fi
|
||||
|
||||
if [ "$(mount | grep domain.img)" ]; then
|
||||
umount /etc/libvirt
|
||||
fi
|
||||
BIN
plugins/dynamix.vm.manager/icons/addvm.png
Normal file
|
After Width: | Height: | Size: 875 B |
BIN
plugins/dynamix.vm.manager/icons/addxml.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/dynamix.vm.manager/icons/devices.png
Normal file
|
After Width: | Height: | Size: 812 B |
BIN
plugins/dynamix.vm.manager/icons/storage.png
Normal file
|
After Width: | Height: | Size: 693 B |
BIN
plugins/dynamix.vm.manager/icons/updatevm.png
Normal file
|
After Width: | Height: | Size: 628 B |
BIN
plugins/dynamix.vm.manager/icons/virtualmachines.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/dynamix.vm.manager/icons/vmmanager.png
Normal file
|
After Width: | Height: | Size: 816 B |
BIN
plugins/dynamix.vm.manager/images/alt.png
Normal file
|
After Width: | Height: | Size: 339 B |
BIN
plugins/dynamix.vm.manager/images/cdrom.png
Normal file
|
After Width: | Height: | Size: 842 B |
BIN
plugins/dynamix.vm.manager/images/clipboard.png
Normal file
|
After Width: | Height: | Size: 501 B |
BIN
plugins/dynamix.vm.manager/images/connect.png
Normal file
|
After Width: | Height: | Size: 404 B |
BIN
plugins/dynamix.vm.manager/images/ctrl.png
Normal file
|
After Width: | Height: | Size: 354 B |
BIN
plugins/dynamix.vm.manager/images/ctrlaltdel.png
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
plugins/dynamix.vm.manager/images/db.png
Normal file
|
After Width: | Height: | Size: 579 B |
BIN
plugins/dynamix.vm.manager/images/disconnect.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
plugins/dynamix.vm.manager/images/drag.png
Normal file
|
After Width: | Height: | Size: 963 B |
BIN
plugins/dynamix.vm.manager/images/dynamix.vm.manager.png
Normal file
|
After Width: | Height: | Size: 11 KiB |