Merge remote-tracking branch 'upstream/master'
@@ -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.
|
||||
@@ -0,0 +1,33 @@
|
||||
Menu="UPSsettings"
|
||||
Title="UPS Details"
|
||||
Tag="battery-3"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2022, Lime Technology
|
||||
* Copyright 2012-2022, Bergware International.
|
||||
* Copyright 2015, Dan Landon.
|
||||
*
|
||||
* 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() {
|
||||
$.post('/plugins/dynamix.apcupsd/include/UPSstatus.php',{level:<?=$cfg['BATTERYLEVEL']?>,runtime:<?=$cfg['MINUTES']?>},function(data) {
|
||||
data = data.split('\n');
|
||||
$('#ups_summary').html(data[0]);
|
||||
$('#ups_status').html(data[1]);
|
||||
setTimeout(getUPSstatus,3000);
|
||||
});
|
||||
}
|
||||
$(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"><div class="spinner"></div></td></tr></tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,128 @@
|
||||
Menu="OtherSettings"
|
||||
Type="xmenu"
|
||||
Title="UPS Settings"
|
||||
Icon="icon-ups"
|
||||
Tag="battery-3"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2022, Lime Technology
|
||||
* Copyright 2012-2022, Bergware International.
|
||||
* Copyright 2015, Dan Landon.
|
||||
*
|
||||
* 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}
|
||||
tr.ups{height:3rem;line-height:3rem}
|
||||
i.ups{margin-right:8px}
|
||||
</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><i class='ups fa fa-shield'></i>_(UPS Model)_</th><th><i class='ups fa fa-plug'></i>_(UPS Status)_</th><th><i class='ups fa fa-battery'></i>_(Battery Charge)_</th><th><i class='ups fa fa-clock-o'></i>_(Runtime Left)_</th><th><i class='ups fa fa-bolt'></i>_(Nominal Power)_</th><th><i class='ups fa fa-bars'></i>_(UPS Load)_</th><th><i class='ups fa fa-ellipsis-h'></i>_(Output Voltage)_</th></tr></thead>
|
||||
<tbody id="ups_summary"><tr class="ups"><td colspan="7"> </td></tr></tbody>
|
||||
</table>
|
||||
|
||||
<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>
|
||||
<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">
|
||||
|
||||
_(Start APC UPS daemon)_:
|
||||
: <select name="SERVICE">
|
||||
<?=mk_option($cfg['SERVICE'], "disable", _("No"))?>
|
||||
<?=mk_option($cfg['SERVICE'], "enable", _("Yes"))?>
|
||||
</select>
|
||||
|
||||
:apc_ups_daemon_help:
|
||||
|
||||
_(UPS cable)_:
|
||||
: <select name="UPSCABLE" 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>
|
||||
|
||||
:apc_ups_cable_help:
|
||||
|
||||
_(Custom UPS cable)_:
|
||||
: <input type="text" name="CUSTOMUPSCABLE" class="narrow" maxlength="40" value="<?=htmlspecialchars($cfg['CUSTOMUPSCABLE']);?>">
|
||||
|
||||
:apc_ups_custom_cable_help:
|
||||
|
||||
_(UPS type)_:
|
||||
: <select name="UPSTYPE" 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>
|
||||
|
||||
:apc_ups_type_help:
|
||||
|
||||
_(Device)_:
|
||||
: <input type="text" name="DEVICE" maxlength="200" class="narrow" value="<?=htmlspecialchars($cfg['DEVICE']);?>">
|
||||
|
||||
:apc_ups_device_help:
|
||||
|
||||
_(Battery level to initiate shutdown)_ (%):
|
||||
: <input type="text" name="BATTERYLEVEL" class="narrow" maxlength="3" value="<?=htmlspecialchars($cfg['BATTERYLEVEL']);?>">
|
||||
|
||||
:apc_battery_level_help:
|
||||
|
||||
_(Runtime left to initiate shutdown)_ (_(minutes)_):
|
||||
: <input type="text" name="MINUTES" class="narrow" maxlength="3" value="<?=htmlspecialchars($cfg['MINUTES']);?>">
|
||||
|
||||
:apc_runtime_left_help:
|
||||
|
||||
_(Time on battery before shutdown)_ (_(seconds)_):
|
||||
: <input type="text" name="TIMEOUT" class="narrow" maxlength="4" value="<?=htmlspecialchars($cfg['TIMEOUT']);?>">
|
||||
|
||||
:apc_battery_time_help:
|
||||
<div></div>
|
||||
:apc_note_help:
|
||||
|
||||
_(Turn off UPS after shutdown)_:
|
||||
: <select name="KILLUPS">
|
||||
<?=mk_option($cfg['KILLUPS'], "no", _("No"))?>
|
||||
<?=mk_option($cfg['KILLUPS'], "yes", _("Yes"))?>
|
||||
</select>
|
||||
|
||||
:apc_killups_help:
|
||||
|
||||
<input type="submit" name="#default" value="_(Default)_">
|
||||
: <input type="submit" name="#apply" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
|
||||
</form>
|
||||
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# Send system notify message from apcupsd
|
||||
#
|
||||
read MESSAGE
|
||||
[[ "$MESSAGE" == *restored* || "$MESSAGE" == *returned* ]] && LEVEL=normal || LEVEL=alert
|
||||
/usr/local/emhttp/webGui/scripts/notify -l "/Settings/UPSsettings" -e "Unraid Server Alert" -s "UPS Alert" -d "$MESSAGE" -i $LEVEL
|
||||
@@ -0,0 +1,9 @@
|
||||
SERVICE="disable"
|
||||
UPSCABLE="usb"
|
||||
CUSTOMUPSCABLE=""
|
||||
UPSTYPE="usb"
|
||||
DEVICE=""
|
||||
BATTERYLEVEL="10"
|
||||
MINUTES="10"
|
||||
TIMEOUT="0"
|
||||
KILLUPS="no"
|
||||
@@ -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
|
||||
@@ -0,0 +1,95 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, Bergware International.
|
||||
* Copyright 2015, Dan Landon.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'settings';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$state = [
|
||||
'ONLINE' => _('Online'),
|
||||
'SLAVE' => '('._('slave').')',
|
||||
'TRIM' => '('._('trim').')',
|
||||
'BOOST' => '('._('boost').')',
|
||||
'COMMLOST' => _('Lost communication'),
|
||||
'ONBATT' => _('On battery'),
|
||||
'NOBATT' => _('No battery detected'),
|
||||
'LOWBATT' => _('Low on battery'),
|
||||
'OVERLOAD' => _('UPS overloaded'),
|
||||
'SHUTTING DOWN' => _('System goes down')
|
||||
];
|
||||
|
||||
$red = "class='red-text'";
|
||||
$green = "class='green-text'";
|
||||
$orange = "class='orange-text'";
|
||||
$status = array_fill(0,7,"<td>-</td>");
|
||||
$result = [];
|
||||
$level = $_POST['level'] ?: 10;
|
||||
$runtime = $_POST['runtime'] ?: 5;
|
||||
|
||||
if (file_exists("/var/run/apcupsd.pid")) {
|
||||
exec("/sbin/apcaccess 2>/dev/null", $rows);
|
||||
for ($i=0; $i<count($rows); $i++) {
|
||||
[$key,$val] = array_map('trim',array_pad(explode(':',$rows[$i],2),2,''));
|
||||
switch ($key) {
|
||||
case 'MODEL':
|
||||
$status[0] = "<td $green>$val</td>";
|
||||
break;
|
||||
case 'STATUS':
|
||||
$text = strtr($val, $state);
|
||||
$status[1] = $val ? (strpos($val,'ONLINE')!==false ? "<td $green>$text</td>" : "<td $red>$text</td>") : "<td $orange>"._('Refreshing')."...</td>";
|
||||
break;
|
||||
case 'BCHARGE':
|
||||
$charge = round(strtok($val,' '));
|
||||
$status[2] = $charge>$level ? "<td $green>$charge %</td>" : "<td $red>$charge %</td>";
|
||||
break;
|
||||
case 'TIMELEFT':
|
||||
$time = round(strtok($val,' '));
|
||||
$unit = _('minutes');
|
||||
$status[3] = $time>$runtime ? "<td $green>$time $unit</td>" : "<td $red>$time $unit</td>";
|
||||
break;
|
||||
case 'NOMPOWER':
|
||||
$power = strtok($val,' ');
|
||||
$status[4] = $power>0 ? "<td $green>$power W</td>" : "<td $red>$power W</td>";
|
||||
break;
|
||||
case 'LOADPCT':
|
||||
$load = strtok($val,' ');
|
||||
$status[5] = round($load)." %";
|
||||
break;
|
||||
case 'OUTPUTV':
|
||||
$output = round(strtok($val,' '));
|
||||
$status[6] = "$output V";
|
||||
break;
|
||||
case 'NOMINV':
|
||||
$volt = strtok($val,' ');
|
||||
$minv = floor($volt / 1.1); // +/- 10% tolerance
|
||||
$maxv = ceil($volt * 1.1);
|
||||
break;
|
||||
case 'LINEFREQ':
|
||||
$freq = round(strtok($val,' '));
|
||||
break;
|
||||
}
|
||||
if ($i%2==0) $result[] = "<tr>";
|
||||
$result[]= "<td><strong>$key</strong></td><td>$val</td>";
|
||||
if ($i%2==1) $result[] = "</tr>";
|
||||
}
|
||||
if (count($rows)%2==1) $result[] = "<td></td><td></td></tr>";
|
||||
if ($power && isset($load)) $status[5] = ($load<90 ? "<td $green>" : "<td $red>").round($power*$load/100)." W (".$status[5].")</td>";
|
||||
elseif (isset($load)) $status[5] = ($load<90 ? "<td $green>" : "<td $red>").$status[5]."</td>";
|
||||
$status[6] = $output ? ((!$volt || ($minv<$output && $output<$maxv) ? "<td $green>" : "<td $red>").$status[6].($freq ? " ~ $freq Hz" : "")."</td>") : $status[6];
|
||||
}
|
||||
if (empty($rows)) $result[] = "<tr><td colspan='4' style='text-align:center'>"._('No information available')."</td></tr>";
|
||||
|
||||
echo "<tr class='ups'>",implode($status),"</tr>\n",implode($result);
|
||||
?>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2018, Lime Technology
|
||||
* Copyright 2012-2020, Bergware International.
|
||||
* Copyright 2015, Dan Landon.
|
||||
*
|
||||
* 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 '".str_replace("'","\\'",$new['UPSTYPE'])."'' $conf");
|
||||
exec("sed -i -e '/^DEVICE/c\\DEVICE '".str_replace("'","\\'",$new['DEVICE'])."'' $conf");
|
||||
exec("sed -i -e '/^BATTERYLEVEL/c\\BATTERYLEVEL '".intval($new['BATTERYLEVEL'])."'' $conf");
|
||||
exec("sed -i -e '/^MINUTES/c\\MINUTES '".intval($new['MINUTES'])."'' $conf");
|
||||
exec("sed -i -e '/^TIMEOUT/c\\TIMEOUT '".intval($new['TIMEOUT'])."'' $conf");
|
||||
exec("sed -i -e '/^UPSCABLE/c\\UPSCABLE '".str_replace("'","\\'",$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");
|
||||
?>
|
||||
@@ -0,0 +1,25 @@
|
||||
Title="Add Container"
|
||||
Cond="(pgrep('dockerd')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2020, Lime Technology
|
||||
* Copyright 2014-2020, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2020, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
// add docker translations (if needed)
|
||||
if (substr($_SERVER['REQUEST_URI'],0,7) != '/Docker') {
|
||||
$docker = "$docroot/languages/$locale/docker.dot";
|
||||
if (file_exists($docker)) $language = array_merge($language,unserialize(file_get_contents($docker)));
|
||||
}
|
||||
eval('?>'.parse_file("$docroot/plugins/dynamix.docker.manager/include/CreateDocker.php"));
|
||||
?>
|
||||
@@ -0,0 +1,25 @@
|
||||
Menu="Tasks:60"
|
||||
Type="xmenu"
|
||||
Code="e90b"
|
||||
Lock="true"
|
||||
Cond="exec(\"grep -o '^DOCKER_ENABLED=.yes' /boot/config/docker.cfg 2>/dev/null\")"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2021, Lime Technology
|
||||
* Copyright 2014-2021, 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 "<div class='notice shift'>_(Array must be **Started** to view Docker containers)_.</div>";
|
||||
} elseif (!is_file('/var/run/dockerd.pid') || (!is_dir('/proc/'.@file_get_contents('/var/run/dockerd.pid')))) {
|
||||
echo "<div class='notice shift'>_(Docker Service failed to start)_.</div>";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,193 @@
|
||||
Menu="Docker:1"
|
||||
Title="Docker Containers"
|
||||
Tag="cubes"
|
||||
Cond="is_file('/var/run/dockerd.pid')"
|
||||
Markdown="false"
|
||||
Nchan="docker_load:stop"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2014-2023, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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 "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$width = in_array($theme,['white','black']) ? -58: -44;
|
||||
$top = in_array($theme,['white','black']) ? 40 : 20;
|
||||
$busy = "<i class='fa fa-spin fa-circle-o-notch'></i> "._('Please wait')."... "._('starting up containers');
|
||||
$cpus = cpu_list();
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.ui.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/plugins/dynamix.docker.manager/styles/style-$theme.css")?>">
|
||||
<style>
|
||||
.basic{display:block}
|
||||
.advanced{display:none;white-space:nowrap}
|
||||
.log{cursor:zoom-in}
|
||||
.exec{cursor:pointer}
|
||||
table#docker_containers{text-align:left}
|
||||
th.five{width:5%}
|
||||
th.nine{width:9%}
|
||||
th.load{width:140px}
|
||||
input.wait{width:24px;margin:0 4px;padding:0 5px;border:none;box-shadow:none;background-color:transparent}
|
||||
table tbody td{line-height:normal}
|
||||
i.mover{margin-right:8px;display:none}
|
||||
#resetsort{margin-left:12px;display:inline-block;width:32px}
|
||||
</style>
|
||||
<table id="docker_containers" class="tablesorter shift">
|
||||
<thead><tr><th><a id="resetsort" class="nohand" onclick="resetSorting()" title="_(Reset sorting)_"><i class="fa fa-th-list"></i></a>_(Application)_</th><th>_(Version)_</th><th>_(Network)_</th><th>_(Port Mappings)_ <small>(_(App to Host)_)</small></th><th>_(Volume Mappings)_ <small>(_(App to Host)_)</small></th><th class="load advanced">_(CPU & Memory load)_</th><th class="nine">_(Autostart)_</th><th class="five">_(Uptime)_</th></tr></thead>
|
||||
<tbody id="docker_list"><tr><td colspan='8'></td></tr></tbody>
|
||||
</table>
|
||||
<input type="button" onclick="addContainer()" value="_(Add Container)_" style="display:none">
|
||||
<input type="button" onclick="startAll()" value="_(Start All)_" style="display:none">
|
||||
<input type="button" onclick="stopAll()" value="_(Stop All)_" style="display:none">
|
||||
<input type="button" onclick="pauseAll()" value="_(Pause All)_" style="display:none">
|
||||
<input type="button" onclick="resumeAll()" value="_(Resume All)_" style="display:none">
|
||||
<input type="button" onclick="checkAll()" value="_(Check for Updates)_" id="checkAll" style="display:none">
|
||||
<input type="button" onclick="updateAll()" value="_(Update All)_" id="updateAll" style="display:none">
|
||||
<input type="button" onclick="contSizes()" value="_(Container Size)_" style="display:none">
|
||||
<div id="iframe-popup" style="display:none;-webkit-overflow-scrolling:touch;"></div>
|
||||
|
||||
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.docker.manager/javascript/docker.js')?>"></script>
|
||||
<script>
|
||||
var docker = [];
|
||||
<?if (!$tabbed):?>
|
||||
$('.title').append("<span id='busy' class='red-text strong' style='display:none;margin-left:40px'><?=$busy?></span>");
|
||||
<?else:?>
|
||||
$('.tabs').append("<span id='busy' class='red-text strong' style='display:none;position:relative;top:<?=$top?>px;left:40px;font-size:1.4rem;letter-spacing:2px'><?=$busy?></span>");
|
||||
<?endif;?>
|
||||
<?if (_var($display,'resize')):?>
|
||||
function resize() {
|
||||
$('#docker_list').height(Math.max(window.innerHeight-340,330));
|
||||
$('#docker_containers thead,#docker_containers tbody').removeClass('fixed');
|
||||
$('#docker_containers thead tr th').each(function(){$(this).width($(this).width());});
|
||||
$('#docker_containers tbody tr td').each(function(){$(this).width($(this).width());});
|
||||
$('#docker_containers thead,#docker_containers tbody').addClass('fixed');
|
||||
}
|
||||
<?endif;?>
|
||||
function resetSorting() {
|
||||
if ($.cookie('lockbutton')==null) return;
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
$.post('/plugins/dynamix.docker.manager/include/UserPrefs.php',{reset:true},function(){loadlist();});
|
||||
}
|
||||
function listview() {
|
||||
var more = $.cookie('docker_listview_mode')=='advanced';
|
||||
<?if(($dockercfg['DOCKER_READMORE']??'yes') === 'yes'):?>
|
||||
$('.docker_readmore').readmore({maxHeight:32,moreLink:"<a href='#' style='text-align:center'><i class='fa fa-chevron-down'></i></a>",lessLink:"<a href='#' style='text-align:center'><i class='fa fa-chevron-up'></i></a>"});
|
||||
<?endif;?>
|
||||
$('input.autostart').each(function(){
|
||||
var wait = $('#'+$(this).prop('id').replace('auto','wait'));
|
||||
var auto = $(this).prop('checked');
|
||||
if (auto && more) wait.show(); else wait.hide();
|
||||
});
|
||||
}
|
||||
function LockButton() {
|
||||
if ($.cookie('lockbutton')==null) {
|
||||
$.cookie('lockbutton','lockbutton');
|
||||
$('#resetsort').removeClass('nohand').addClass('hand');
|
||||
$('i.mover').show();
|
||||
$('#docker_list .sortable').css({'cursor':'move'});
|
||||
<?if ($themes1):?>
|
||||
$('div.nav-item.LockButton').find('a').prop('title',"_(Lock sortable items)_");
|
||||
$('div.nav-item.LockButton').find('b').removeClass('icon-u-lock green-text').addClass('icon-u-lock-open red-text');
|
||||
<?endif;?>
|
||||
$('div.nav-item.LockButton').find('span').text("_(Lock sortable items)_");
|
||||
$('#docker_list').sortable({helper:'clone',items:'.sortable',cursor:'grab',axis:'y',containment:'parent',cancel:'span.docker_readmore,input',delay:100,opacity:0.5,zIndex:9999,forcePlaceholderSize:true,
|
||||
update:function(e,ui){
|
||||
var row = $('#docker_list').find('tr:first');
|
||||
var names = ''; var index = '';
|
||||
row.parent().children().find('td.ct-name').each(function(){names+=$(this).find('.appname').text()+';';index+=$(this).parent().parent().children().index($(this).parent())+';';});
|
||||
$.post('/plugins/dynamix.docker.manager/include/UserPrefs.php',{names:names,index:index});
|
||||
}});
|
||||
} else {
|
||||
$.removeCookie('lockbutton');
|
||||
$('#resetsort').removeClass('hand').addClass('nohand');
|
||||
$('i.mover').hide();
|
||||
$('#docker_list .sortable').css({'cursor':'default'});
|
||||
<?if ($themes1):?>
|
||||
$('div.nav-item.LockButton').find('a').prop('title',"_(Unlock sortable items)_");
|
||||
$('div.nav-item.LockButton').find('b').removeClass('icon-u-lock-open red-text').addClass('icon-u-lock green-text');
|
||||
<?endif;?>
|
||||
$('div.nav-item.LockButton').find('span').text("_(Unlock sortable items)_");
|
||||
$('#docker_list').sortable('destroy');
|
||||
}
|
||||
}
|
||||
function loadlist(init) {
|
||||
timers.docker = setTimeout(function(){$('div.spinner.fixed').show('slow');},500);
|
||||
$.get('/plugins/dynamix.docker.manager/include/DockerContainers.php',function(d) {
|
||||
clearTimeout(timers.docker);
|
||||
var data = d.split(/\0/);
|
||||
$('#docker_list').html(data[0]);
|
||||
$('head').append('<script>'+data[1]+'<\/script>');
|
||||
<?if (_var($display,'resize')):?>
|
||||
resize();
|
||||
if (init) $(window).bind('resize',function(){resize();});
|
||||
<?endif;?>
|
||||
$('.iconstatus').each(function(){
|
||||
if ($(this).hasClass('stopped')) $('div.'+$(this).prop('id')).hide();
|
||||
});
|
||||
$('.autostart').switchButton({labels_placement:'right', on_label:"_(On)_", off_label:"_(Off)_"});
|
||||
$('.autostart').change(function(){
|
||||
var more = $.cookie('docker_listview_mode')=='advanced';
|
||||
var wait = $('#'+$(this).prop('id').replace('auto','wait'));
|
||||
var auto = $(this).prop('checked');
|
||||
if (auto && more) wait.show(); else wait.hide();
|
||||
$.post('/plugins/dynamix.docker.manager/include/UpdateConfig.php',{action:'autostart',container:$(this).attr('container'),auto:auto,wait:wait.find('input.wait').val()});
|
||||
});
|
||||
$('input.wait').change(function(){
|
||||
$.post('/plugins/dynamix.docker.manager/include/UpdateConfig.php',{action:'wait',container:$(this).attr('container'),wait:$(this).val()});
|
||||
});
|
||||
if ($.cookie('docker_listview_mode')=='advanced') {$('.advanced').show(); $('.basic').hide();}
|
||||
$('input[type=button]').prop('disabled',false).show('slow');
|
||||
var update = false, rebuild = false;
|
||||
for (var i=0,ct; ct=docker[i]; i++) {
|
||||
if (ct.update==1) update = true;
|
||||
if (ct.update==2) rebuild = true;
|
||||
}
|
||||
listview();
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
if (data[2]==1) {$('#busy').show(); setTimeout(loadlist,5000);} else if ($('#busy').is(':visible')) {$('#busy').hide(); setTimeout(loadlist,3000);}
|
||||
if (!update) $('input#updateAll').prop('disabled',true);
|
||||
if (rebuild) rebuildAll();
|
||||
});
|
||||
}
|
||||
function contSizes() {
|
||||
// show spinner over window
|
||||
$('div.spinner.fixed').css({'z-index':'100000'}).show();
|
||||
openPlugin('container_size', "_(Container Size)_");
|
||||
}
|
||||
var dockerload = new NchanSubscriber('/sub/dockerload',{subscriber:'websocket'});
|
||||
dockerload.on('message', function(msg){
|
||||
var data = msg.split('\n');
|
||||
for (var i=0,row; row=data[i]; i++) {
|
||||
var id = row.split(';');
|
||||
var w1 = Math.round(Math.min(id[1].slice(0,-1)/<?=count($cpus)*count(preg_split('/[,-]/',$cpus[0]))?>,100)*100)/100+'%';
|
||||
$('.cpu-'+id[0]).text(w1.replace('.','<?=_var($display,'number','.,')[0]?>'));
|
||||
$('.mem-'+id[0]).text(id[2]);
|
||||
$('#cpu-'+id[0]).css('width',w1);
|
||||
}
|
||||
});
|
||||
$(function() {
|
||||
$(".tabs").append('<span class="status"><span><input type="checkbox" class="advancedview"></span></span>');
|
||||
$('.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});
|
||||
listview();
|
||||
});
|
||||
$.removeCookie('lockbutton');
|
||||
loadlist(true);
|
||||
dockerload.start();
|
||||
});
|
||||
</script>
|
||||
@@ -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, Inc.](http://lime-technology.com).
|
||||
|
||||
This file shall be included in all copies or substantial portions of the Software.
|
||||
@@ -0,0 +1,25 @@
|
||||
Title="Update Container"
|
||||
Cond="(pgrep('dockerd')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2020, Lime Technology
|
||||
* Copyright 2014-2020, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2020, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
// add docker translations (if needed)
|
||||
if (substr($_SERVER['REQUEST_URI'],0,7) != '/Docker') {
|
||||
$docker = "$docroot/languages/$locale/docker.dot";
|
||||
if (file_exists($docker)) $language = array_merge($language,unserialize(file_get_contents($docker)));
|
||||
}
|
||||
eval('?>'.parse_file("$docroot/plugins/dynamix.docker.manager/include/CreateDocker.php"));
|
||||
?>
|
||||
@@ -0,0 +1,10 @@
|
||||
DOCKER_ENABLED="no"
|
||||
DOCKER_IMAGE_SIZE="20"
|
||||
DOCKER_LOG_ROTATION="yes"
|
||||
DOCKER_LOG_SIZE="50m"
|
||||
DOCKER_LOG_FILES="1"
|
||||
DOCKER_AUTHORING_MODE="no"
|
||||
DOCKER_USER_NETWORKS="remove"
|
||||
DOCKER_ALLOW_ACCESS=""
|
||||
DOCKER_TIMEOUT=10
|
||||
DOCKER_READMORE="yes"
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,33 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2022, Lime Technology
|
||||
* Copyright 2014-2022, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2022, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$user_prefs = $dockerManPaths['user-prefs'];
|
||||
$action = $_POST['action'];
|
||||
$status = $action=='start' ? 'exited' : ($action=='unpause' ? 'paused' : 'running');
|
||||
$containers = DockerUtil::docker("ps -a --filter status='$status' --format='{{.Names}}'",true);
|
||||
|
||||
if (file_exists($user_prefs)) {
|
||||
$prefs = parse_ini_file($user_prefs); $sort = [];
|
||||
foreach ($containers as $ct) $sort[] = array_search($ct,$prefs) ?? 999;
|
||||
array_multisort($sort, ($action=='start'?SORT_ASC:SORT_DESC), SORT_NUMERIC, $containers);
|
||||
}
|
||||
|
||||
foreach ($containers as $ct) {
|
||||
DockerUtil::docker("$action $ct >/dev/null");
|
||||
addRoute($ct);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,160 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2014-2023, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'docker';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
$containers = $DockerClient->getDockerContainers();
|
||||
$images = $DockerClient->getDockerImages();
|
||||
$user_prefs = $dockerManPaths['user-prefs'];
|
||||
$autostart_file = $dockerManPaths['autostart-file'];
|
||||
|
||||
if (!$containers && !$images) {
|
||||
echo "<tr><td colspan='7' style='text-align:center;padding-top:12px'>"._('No Docker containers installed')."</td></tr>";
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists($user_prefs)) {
|
||||
$prefs = @parse_ini_file($user_prefs) ?: [];
|
||||
$sort = [];
|
||||
foreach ($containers as $ct) $sort[] = array_search($ct['Name'],$prefs);
|
||||
array_multisort($sort,SORT_NUMERIC,$containers);
|
||||
unset($sort);
|
||||
}
|
||||
|
||||
// Read container info
|
||||
$allInfo = $DockerTemplates->getAllInfo();
|
||||
$docker = [];
|
||||
$null = '0.0.0.0';
|
||||
|
||||
$autostart = @file($autostart_file,FILE_IGNORE_NEW_LINES) ?: [];
|
||||
$names = array_map('var_split',$autostart);
|
||||
|
||||
function my_lang_time($text) {
|
||||
[$number, $text] = my_explode(' ',$text,2);
|
||||
return sprintf(_("%s $text"),$number);
|
||||
}
|
||||
function my_lang_log($text) {
|
||||
global $language;
|
||||
if (isset($language['healthy'])) $text = str_replace('healthy',$language['healthy'],$text);
|
||||
if (isset($language['Exited'])) $text = str_replace('Exited',$language['Exited'],$text);
|
||||
if (strpos($text,'ago')!==false) {
|
||||
[$t1,$t2] = my_explode(') ',$text);
|
||||
return $t1.'): '.my_lang_time($t2);
|
||||
}
|
||||
return _(_($text),2);
|
||||
}
|
||||
foreach ($containers as $ct) {
|
||||
$name = $ct['Name'];
|
||||
$id = $ct['Id'];
|
||||
$info = &$allInfo[$name];
|
||||
$running = $info['running'] ? 1 : 0;
|
||||
$paused = $info['paused'] ? 1 : 0;
|
||||
$is_autostart = $info['autostart'] ? 'true':'false';
|
||||
$updateStatus = substr($ct['NetworkMode'],-4)==':???' ? 2 : ($info['updated']=='true' ? 0 : ($info['updated']=='false' ? 1 : 3));
|
||||
$template = $info['template']??'';
|
||||
$shell = $info['shell']??'';
|
||||
$webGui = html_entity_decode($info['url']??'');
|
||||
$support = html_entity_decode($info['Support']??'');
|
||||
$project = html_entity_decode($info['Project']??'');
|
||||
$registry = html_entity_decode($info['registry']??'');
|
||||
$donateLink = html_entity_decode($info['DonateLink']??'');
|
||||
$readme = html_entity_decode($info['ReadMe']??'');
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s', '%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), $shell, $id, addslashes($support), addslashes($project),addslashes($registry),addslashes($donateLink),addslashes($readme));
|
||||
$docker[] = "docker.push({name:'$name',id:'$id',state:$running,pause:$paused,update:$updateStatus});";
|
||||
$shape = $running ? ($paused ? 'pause' : 'play') : 'square';
|
||||
$status = $running ? ($paused ? 'paused' : 'started') : 'stopped';
|
||||
$color = $status=='started' ? 'green-text' : ($status=='paused' ? 'orange-text' : 'red-text');
|
||||
$update = $updateStatus==1 ? 'blue-text' : '';
|
||||
$icon = $info['icon'] ?: '/plugins/dynamix.docker.manager/images/question.png';
|
||||
$image = substr($icon,-4)=='.png' ? "<img src='$icon?".filemtime("$docroot{$info['icon']}")."' class='img' onerror=this.src='/plugins/dynamix.docker.manager/images/question.png';>" : (substr($icon,0,5)=='icon-' ? "<i class='$icon img'></i>" : "<i class='fa fa-$icon img'></i>");
|
||||
$wait = var_split($autostart[array_search($name,$names)]??'',1);
|
||||
$ports = [];
|
||||
foreach ($ct['Ports'] as $port) {
|
||||
$intern = $running ? ($ct['NetworkMode']=='host' ? $host : _var($port,'IP')) : $null;
|
||||
$extern = $running ? (_var($port,'NAT') ? $host : $intern) : $null;
|
||||
$ports[] = sprintf('%s:%s/%s<i class="fa fa-arrows-h" style="margin:0 6px"></i>%s:%s', $intern, _var($port,'PrivatePort'), strtoupper(_var($port,'Type')), $extern, _var($port,'PublicPort'));
|
||||
}
|
||||
$paths = [];
|
||||
$ct['Volumes'] = is_array($ct['Volumes']) ? $ct['Volumes'] : [];
|
||||
foreach ($ct['Volumes'] as $mount) {
|
||||
[$host_path,$container_path,$access_mode] = my_explode(':',$mount,3);
|
||||
$paths[] = sprintf('%s<i class="fa fa-%s" style="margin:0 6px"></i>%s', htmlspecialchars($container_path), $access_mode=='ro'?'long-arrow-left':'arrows-h', htmlspecialchars($host_path));
|
||||
}
|
||||
echo "<tr class='sortable'><td class='ct-name' style='width:220px;padding:8px'><i class='fa fa-arrows-v mover orange-text'></i>";
|
||||
if ($template) {
|
||||
$appname = "<a class='exec' onclick=\"editContainer('".addslashes(htmlspecialchars($name))."','".addslashes(htmlspecialchars($template))."')\">".htmlspecialchars($name)."</a>";
|
||||
} else {
|
||||
$appname = htmlspecialchars($name);
|
||||
}
|
||||
echo "<span class='outer'><span id='$id' $menu class='hand'>$image</span><span class='inner'><span class='appname $update'>$appname</span><br><i id='load-$id' class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span></span>";
|
||||
echo "<div class='advanced' style='margin-top:8px'>"._('Container ID').": $id<br>";
|
||||
if ($ct['BaseImage']) echo "<i class='fa fa-cubes' style='margin-right:5px'></i>".htmlspecialchars(${ct['BaseImage']})."<br>";
|
||||
echo _('By').": ";
|
||||
$registry = $info['registry'];
|
||||
[$author,$version] = my_explode(':',$ct['Image']);
|
||||
if ($registry) {
|
||||
echo "<a href='".htmlspecialchars($registry)."' target='_blank'>".htmlspecialchars(compress($author,24))."</a>";
|
||||
} else {
|
||||
echo htmlspecialchars(compress($author,24));
|
||||
}
|
||||
echo "</div></td><td class='updatecolumn'>";
|
||||
switch ($updateStatus) {
|
||||
case 0:
|
||||
echo "<span class='green-text' style='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
|
||||
break;
|
||||
case 1:
|
||||
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('update ready')."</span></div>";
|
||||
echo "<a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('apply update')."</span></a>";
|
||||
break;
|
||||
case 2:
|
||||
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('rebuild ready')."</span></div>";
|
||||
echo "<a class='exec'><span style='white-space:nowrap;'><i class='fa fa-recycle fa-fw'></i> "._('rebuilding')."</span></a>";
|
||||
break;
|
||||
default:
|
||||
echo "<span class='orange-text' style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
|
||||
break;
|
||||
}
|
||||
echo "<div class='advanced'><i class='fa fa-info-circle fa-fw'></i> ".compress(_($version),12,0)."</div></td>";
|
||||
echo "<td>{$ct['NetworkMode']}</td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'>".implode('<br>',$ports)."</span></td>";
|
||||
echo "<td style='word-break:break-all'><span class='docker_readmore'>".implode('<br>',$paths)."</span></td>";
|
||||
echo "<td class='advanced'><span class='cpu-$id'>0%</span><div class='usage-disk mm'><span id='cpu-$id' style='width:0'></span><span></span></div>";
|
||||
echo "<br><span class='mem-$id'>0 / 0</span></td>";
|
||||
echo "<td><input type='checkbox' id='$id-auto' class='autostart' container='".htmlspecialchars($name)."'".($info['autostart'] ? ' checked':'').">";
|
||||
echo "<span id='$id-wait' style='float:right;display:none'>"._('wait')."<input class='wait' container='".htmlspecialchars($name)."' type='number' value='$wait' placeholder='0' title=\""._('seconds')."\"></span></td>";
|
||||
echo "<td><div style='white-space:nowrap'>".htmlspecialchars(str_replace('Up',_('Uptime').':',my_lang_log($ct['Status'])))."<div style='margin-top:4px'>"._('Created').": ".htmlspecialchars(my_lang_time($ct['Created']))."</div></div></td></tr>";
|
||||
}
|
||||
foreach ($images as $image) {
|
||||
if (count($image['usedBy'])) continue;
|
||||
$id = $image['Id'];
|
||||
$menu = sprintf("onclick=\"addDockerImageContext('%s','%s')\"", $id, implode(',',$image['Tags']));
|
||||
echo "<tr class='advanced'><td style='width:220px;padding:8px'>";
|
||||
echo "<span class='outer apps'><span id='$id' $menu class='hand'><img src='/webGui/images/disk.png' class='img'></span><span class='inner'>("._('orphan image').")<br><i class='fa fa-square stopped grey-text'></i><span class='state'>"._('stopped')."</span></span></span>";
|
||||
echo "</td><td colspan='6'>"._('Image ID').": $id<br>";
|
||||
echo implode(', ',$image['Tags']);
|
||||
echo "</td><td>"._('Created')." ".htmlspecialchars(_($image['Created'],0))."</td></tr>";
|
||||
}
|
||||
echo "\0".implode($docker)."\0".(pgrep('rc.docker')!==false ? 1:0);
|
||||
?>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2022, Lime Technology
|
||||
* Copyright 2014-2022, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2022, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$ncsi = exec("wget --spider --no-check-certificate -nv -T10 -t1 https://www.msftncsi.com/ncsi.txt 2>&1|grep -o 'OK'")=='OK';
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
if ($ncsi) $DockerTemplates->downloadTemplates();
|
||||
$DockerTemplates->getAllInfo($ncsi,$ncsi);
|
||||
?>
|
||||
@@ -0,0 +1,73 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2014-2023, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Secure.php";
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'docker';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$DockerClient = new DockerClient();
|
||||
$action = unscript(_var($_REQUEST,'action'));
|
||||
$container = unbundle(_var($_REQUEST,'container'));
|
||||
$name = unscript(_var($_REQUEST,'name'));
|
||||
$image = unscript(_var($_REQUEST,'image'));
|
||||
$arrResponse = ['error' => _('Missing parameters')];
|
||||
|
||||
switch ($action) {
|
||||
case 'start':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->startContainer($container)];
|
||||
break;
|
||||
case 'pause':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->pauseContainer($container)];
|
||||
break;
|
||||
case 'stop':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->stopContainer($container)];
|
||||
break;
|
||||
case 'resume':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->resumeContainer($container)];
|
||||
break;
|
||||
case 'restart':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->restartContainer($container)];
|
||||
break;
|
||||
case 'remove_container':
|
||||
if ($container) $arrResponse = ['success' => $DockerClient->removeContainer($name, $container, 1)];
|
||||
break;
|
||||
case 'remove_image':
|
||||
if ($image) $arrResponse = ['success' => $DockerClient->removeImage($image)];
|
||||
break;
|
||||
case 'remove_all':
|
||||
if ($container && $image) {
|
||||
// first: try to remove container
|
||||
$ret = $DockerClient->removeContainer($name, $container, 2);
|
||||
if ($ret === true) {
|
||||
// next: try to remove image
|
||||
$arrResponse = ['success' => $DockerClient->removeImage($image)];
|
||||
} else {
|
||||
// error: container failed to remove
|
||||
$arrResponse = ['success' => $ret];
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$arrResponse = ['error' => _('Unknown action')." '$action'"];
|
||||
break;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
die(json_encode($arrResponse));
|
||||
@@ -0,0 +1,533 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2014-2023, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
function addRoute($ct) {
|
||||
// add static route(s) for remote WireGuard access
|
||||
[$pid,$net] = array_pad(explode(' ',exec("docker inspect --format='{{.State.Pid}} {{.NetworkSettings.Networks}}' $ct")),2,'');
|
||||
$net = substr($net,4,strpos($net,':')-4);
|
||||
if (!$pid || $net != 'br0') return;
|
||||
$thisip = ipaddr();
|
||||
foreach (glob('/etc/wireguard/wg*.cfg') as $cfg) {
|
||||
$network = exec("grep -Pom1 '^Network:0=\"\\K[^\"]+' $cfg");
|
||||
if ($network) exec("nsenter -n -t $pid ip -4 route add $network via $thisip 2>/dev/null");
|
||||
}
|
||||
}
|
||||
|
||||
function xml_encode($string) {
|
||||
return htmlspecialchars($string, ENT_XML1, 'UTF-8');
|
||||
}
|
||||
|
||||
function xml_decode($string) {
|
||||
return strval(html_entity_decode($string, ENT_XML1, 'UTF-8'));
|
||||
}
|
||||
|
||||
function postToXML($post, $setOwnership=false) {
|
||||
$dom = new domDocument;
|
||||
$dom->appendChild($dom->createElement("Container"));
|
||||
$xml = simplexml_import_dom($dom);
|
||||
$xml['version'] = 2;
|
||||
$xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName']));
|
||||
$xml->Repository = xml_encode(trim($post['contRepository']));
|
||||
$xml->Registry = xml_encode(trim($post['contRegistry']));
|
||||
$xml->Network = xml_encode($post['contNetwork']);
|
||||
$xml->MyIP = xml_encode($post['contMyIP']);
|
||||
$xml->Shell = xml_encode($post['contShell']);
|
||||
$xml->Privileged = strtolower($post['contPrivileged']??'')=='on' ? 'true' : 'false';
|
||||
$xml->Support = xml_encode($post['contSupport']);
|
||||
$xml->Project = xml_encode($post['contProject']);
|
||||
$xml->Overview = xml_encode($post['contOverview']);
|
||||
$xml->Category = xml_encode($post['contCategory']);
|
||||
$xml->WebUI = xml_encode(trim($post['contWebUI']));
|
||||
$xml->TemplateURL = xml_encode($post['contTemplateURL']);
|
||||
$xml->Icon = xml_encode(trim($post['contIcon']));
|
||||
$xml->ExtraParams = xml_encode($post['contExtraParams']);
|
||||
$xml->PostArgs = xml_encode($post['contPostArgs']);
|
||||
$xml->CPUset = xml_encode($post['contCPUset']);
|
||||
$xml->DateInstalled = xml_encode(time());
|
||||
$xml->DonateText = xml_encode($post['contDonateText']);
|
||||
$xml->DonateLink = xml_encode($post['contDonateLink']);
|
||||
$xml->Requires = xml_encode($post['contRequires']);
|
||||
|
||||
$size = is_array($post['confName']) ? count($post['confName']) : 0;
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$Type = $post['confType'][$i];
|
||||
$config = $xml->addChild('Config', xml_encode($post['confValue'][$i]));
|
||||
$config['Name'] = xml_encode($post['confName'][$i]);
|
||||
$config['Target'] = xml_encode($post['confTarget'][$i]);
|
||||
$config['Default'] = xml_encode($post['confDefault'][$i]);
|
||||
$config['Mode'] = xml_encode($post['confMode'][$i]);
|
||||
$config['Description'] = xml_encode($post['confDescription'][$i]);
|
||||
$config['Type'] = xml_encode($post['confType'][$i]);
|
||||
$config['Display'] = xml_encode($post['confDisplay'][$i]);
|
||||
$config['Required'] = xml_encode($post['confRequired'][$i]);
|
||||
$config['Mask'] = xml_encode($post['confMask'][$i]);
|
||||
}
|
||||
$dom = new DOMDocument('1.0');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$dom->loadXML($xml->asXML());
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
function xmlToVar($xml) {
|
||||
global $subnet;
|
||||
$xml = is_file($xml) ? simplexml_load_file($xml) : simplexml_load_string($xml);
|
||||
$out = [];
|
||||
$out['Name'] = preg_replace('/\s+/', '', xml_decode($xml->Name));
|
||||
$out['Repository'] = xml_decode($xml->Repository);
|
||||
$out['Registry'] = xml_decode($xml->Registry);
|
||||
$out['Network'] = xml_decode($xml->Network);
|
||||
$out['MyIP'] = xml_decode($xml->MyIP ?? '');
|
||||
$out['Shell'] = xml_decode($xml->Shell ?? 'sh');
|
||||
$out['Privileged'] = xml_decode($xml->Privileged);
|
||||
$out['Support'] = xml_decode($xml->Support);
|
||||
$out['Project'] = xml_decode($xml->Project);
|
||||
$out['Overview'] = stripslashes(xml_decode($xml->Overview));
|
||||
$out['Category'] = xml_decode($xml->Category);
|
||||
$out['WebUI'] = xml_decode($xml->WebUI);
|
||||
$out['TemplateURL'] = xml_decode($xml->TemplateURL);
|
||||
$out['Icon'] = xml_decode($xml->Icon);
|
||||
$out['ExtraParams'] = xml_decode($xml->ExtraParams);
|
||||
$out['PostArgs'] = xml_decode($xml->PostArgs);
|
||||
$out['CPUset'] = xml_decode($xml->CPUset);
|
||||
$out['DonateText'] = xml_decode($xml->DonateText);
|
||||
$out['DonateLink'] = xml_decode($xml->DonateLink);
|
||||
$out['Requires'] = xml_decode($xml->Requires);
|
||||
$out['Config'] = [];
|
||||
if (isset($xml->Config)) {
|
||||
foreach ($xml->Config as $config) {
|
||||
$c = [];
|
||||
$c['Value'] = strlen(xml_decode($config)) ? xml_decode($config) : xml_decode($config['Default']);
|
||||
foreach ($config->attributes() as $key => $value) {
|
||||
$value = xml_decode($value);
|
||||
if ($key == 'Mode') {
|
||||
switch (xml_decode($config['Type'])) {
|
||||
case 'Path':
|
||||
$value = in_array(strtolower($value),['rw','rw,slave','rw,shared','ro','ro,slave','ro,shared']) ? $value : "rw";
|
||||
break;
|
||||
case 'Port':
|
||||
$value = in_array(strtolower($value),['tcp','udp']) ? $value : "tcp";
|
||||
break;
|
||||
}
|
||||
}
|
||||
$c[$key] = strip_tags(html_entity_decode($value));
|
||||
}
|
||||
$out['Config'][] = $c;
|
||||
}
|
||||
}
|
||||
// some xml templates advertise as V2 but omit the new <Network> element
|
||||
// check for and use the V1 <Networking> element when this occurs
|
||||
if (empty($out['Network']) && isset($xml->Networking->Mode)) {
|
||||
$out['Network'] = xml_decode($xml->Networking->Mode);
|
||||
}
|
||||
// check if network exists
|
||||
if (!key_exists($out['Network'],$subnet)) $out['Network'] = 'none';
|
||||
// V1 compatibility
|
||||
if ($xml['version'] != '2') {
|
||||
if (isset($xml->Description)) {
|
||||
$out['Overview'] = stripslashes(xml_decode($xml->Description));
|
||||
}
|
||||
if (isset($xml->Networking->Publish->Port)) {
|
||||
$portNum = 0;
|
||||
foreach ($xml->Networking->Publish->Port as $port) {
|
||||
if (empty(xml_decode($port->ContainerPort))) continue;
|
||||
$portNum += 1;
|
||||
$out['Config'][] = [
|
||||
'Name' => "Host Port {$portNum}",
|
||||
'Target' => xml_decode($port->ContainerPort),
|
||||
'Default' => xml_decode($port->HostPort),
|
||||
'Value' => xml_decode($port->HostPort),
|
||||
'Mode' => xml_decode($port->Protocol) ? xml_decode($port->Protocol) : "tcp",
|
||||
'Description' => '',
|
||||
'Type' => 'Port',
|
||||
'Display' => 'always',
|
||||
'Required' => 'true',
|
||||
'Mask' => 'false'
|
||||
];
|
||||
}
|
||||
}
|
||||
if (isset($xml->Data->Volume)) {
|
||||
$volNum = 0;
|
||||
foreach ($xml->Data->Volume as $vol) {
|
||||
if (empty(xml_decode($vol->ContainerDir))) continue;
|
||||
$volNum += 1;
|
||||
$out['Config'][] = [
|
||||
'Name' => "Host Path {$volNum}",
|
||||
'Target' => xml_decode($vol->ContainerDir),
|
||||
'Default' => xml_decode($vol->HostDir),
|
||||
'Value' => xml_decode($vol->HostDir),
|
||||
'Mode' => xml_decode($vol->Mode) ? xml_decode($vol->Mode) : "rw",
|
||||
'Description' => '',
|
||||
'Type' => 'Path',
|
||||
'Display' => 'always',
|
||||
'Required' => 'true',
|
||||
'Mask' => 'false'
|
||||
];
|
||||
}
|
||||
}
|
||||
if (isset($xml->Environment->Variable)) {
|
||||
$varNum = 0;
|
||||
foreach ($xml->Environment->Variable as $varitem) {
|
||||
if (empty(xml_decode($varitem->Name))) continue;
|
||||
$varNum += 1;
|
||||
$out['Config'][] = [
|
||||
'Name' => "Key {$varNum}",
|
||||
'Target' => xml_decode($varitem->Name),
|
||||
'Default' => xml_decode($varitem->Value),
|
||||
'Value' => xml_decode($varitem->Value),
|
||||
'Mode' => '',
|
||||
'Description' => '',
|
||||
'Type' => 'Variable',
|
||||
'Display' => 'always',
|
||||
'Required' => 'false',
|
||||
'Mask' => 'false'
|
||||
];
|
||||
}
|
||||
}
|
||||
if (isset($xml->Labels->Variable)) {
|
||||
$varNum = 0;
|
||||
foreach ($xml->Labels->Variable as $varitem) {
|
||||
if (empty(xml_decode($varitem->Name))) continue;
|
||||
$varNum += 1;
|
||||
$out['Config'][] = [
|
||||
'Name' => "Label {$varNum}",
|
||||
'Target' => xml_decode($varitem->Name),
|
||||
'Default' => xml_decode($varitem->Value),
|
||||
'Value' => xml_decode($varitem->Value),
|
||||
'Mode' => '',
|
||||
'Description' => '',
|
||||
'Type' => 'Label',
|
||||
'Display' => 'always',
|
||||
'Required' => 'false',
|
||||
'Mask' => 'false'
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
xmlSecurity($out);
|
||||
return $out;
|
||||
}
|
||||
|
||||
function xmlSecurity(&$template) {
|
||||
foreach ($template as &$element) {
|
||||
if (is_array($element)) {
|
||||
xmlSecurity($element);
|
||||
} else {
|
||||
if (is_string($element)) {
|
||||
$tempElement = htmlspecialchars_decode($element);
|
||||
$tempElement = str_replace("[","<",$tempElement);
|
||||
$tempElement = str_replace("]",">",$tempElement);
|
||||
if (preg_match('#<script(.*?)>(.*?)</script>#is',$tempElement) || preg_match('#<iframe(.*?)>(.*?)</iframe>#is',$tempElement) || (stripos($tempElement,"<link") !== false) ) {
|
||||
$element = "REMOVED";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function xmlToCommand($xml, $create_paths=false) {
|
||||
global $docroot, $var, $cfg, $driver;
|
||||
$xml = xmlToVar($xml);
|
||||
$cmdName = strlen($xml['Name']) ? '--name='.escapeshellarg($xml['Name']) : '';
|
||||
$cmdPrivileged = strtolower($xml['Privileged'])=='true' ? '--privileged=true' : '';
|
||||
$cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg(strtolower($xml['Network']));
|
||||
$cmdMyIP = '';
|
||||
foreach (explode(' ',str_replace(',',' ',$xml['MyIP'])) as $myIP) if ($myIP) $cmdMyIP .= (strpos($myIP,':')?'--ip6=':'--ip=').escapeshellarg($myIP).' ';
|
||||
$cmdCPUset = strlen($xml['CPUset']) ? '--cpuset-cpus='.escapeshellarg($xml['CPUset']) : '';
|
||||
$Volumes = [''];
|
||||
$Ports = [''];
|
||||
$Variables = [''];
|
||||
$Labels = [''];
|
||||
$Devices = [''];
|
||||
// Bind Time
|
||||
$Variables[] = 'TZ="'.$var['timeZone'].'"';
|
||||
// Add HOST_OS variable
|
||||
$Variables[] = 'HOST_OS="Unraid"';
|
||||
// Add HOST_HOSTNAME variable
|
||||
$Variables[] = 'HOST_HOSTNAME="'.$var['NAME'].'"';
|
||||
// Add HOST_CONTAINERNAME variable
|
||||
$Variables[] = 'HOST_CONTAINERNAME="'.$xml['Name'].'"';
|
||||
// Docker labels for WebUI and Icon
|
||||
$Labels[] = 'net.unraid.docker.managed=dockerman';
|
||||
if (strlen($xml['WebUI'])) $Labels[] = 'net.unraid.docker.webui='.escapeshellarg($xml['WebUI']);
|
||||
if (strlen($xml['Icon'])) $Labels[] = 'net.unraid.docker.icon='.escapeshellarg($xml['Icon']);
|
||||
|
||||
foreach ($xml['Config'] as $key => $config) {
|
||||
$confType = strtolower(strval($config['Type']));
|
||||
$hostConfig = strlen($config['Value']) ? $config['Value'] : $config['Default'];
|
||||
$containerConfig = strval($config['Target']);
|
||||
$Mode = strval($config['Mode']);
|
||||
if ($confType != "device" && !strlen($containerConfig)) continue;
|
||||
if ($confType == "path") {
|
||||
$Volumes[] = escapeshellarg($hostConfig).':'.escapeshellarg($containerConfig).':'.escapeshellarg($Mode);
|
||||
if (!file_exists($hostConfig) && $create_paths) {
|
||||
@mkdir($hostConfig, 0777, true);
|
||||
@chown($hostConfig, 99);
|
||||
@chgrp($hostConfig, 100);
|
||||
}
|
||||
} elseif ($confType == 'port') {
|
||||
switch ($driver[$xml['Network']]) {
|
||||
case 'host':
|
||||
case 'macvlan':
|
||||
case 'ipvlan':
|
||||
// Export ports as variable if network is set to host or macvlan or ipvlan
|
||||
$Variables[] = strtoupper(escapeshellarg($Mode.'_PORT_'.$containerConfig).'='.escapeshellarg($hostConfig));
|
||||
break;
|
||||
case 'bridge':
|
||||
// Export ports as port if network is set to (custom) bridge
|
||||
$Ports[] = escapeshellarg($hostConfig.':'.$containerConfig.'/'.$Mode);
|
||||
break;
|
||||
case 'none':
|
||||
// No export of ports if network is set to none
|
||||
}
|
||||
} elseif ($confType == "label") {
|
||||
$Labels[] = escapeshellarg($containerConfig).'='.escapeshellarg($hostConfig);
|
||||
} elseif ($confType == "variable") {
|
||||
$Variables[] = escapeshellarg($containerConfig).'='.escapeshellarg($hostConfig);
|
||||
} elseif ($confType == "device") {
|
||||
$Devices[] = escapeshellarg($hostConfig);
|
||||
}
|
||||
}
|
||||
$logSize = $logFile = '';
|
||||
if (($cfg['DOCKER_LOG_ROTATION']??'')=='yes') {
|
||||
$logSize = $cfg['DOCKER_LOG_SIZE'] ?? '10m';
|
||||
$logSize = "--log-opt max-size='$logSize'";
|
||||
$logFile = $cfg['DOCKER_LOG_FILES'] ?? '1';
|
||||
$logFile = "--log-opt max-file='$logFile'";
|
||||
}
|
||||
$cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s',
|
||||
$cmdName, $cmdNetwork, $cmdMyIP, $cmdCPUset, $logSize, $logFile, $cmdPrivileged, implode(' -e ', $Variables), implode(' -l ', $Labels), implode(' -p ', $Ports), implode(' -v ', $Volumes), implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $xml['PostArgs']);
|
||||
return [preg_replace('/\s\s+/', ' ', $cmd), $xml['Name'], $xml['Repository']];
|
||||
}
|
||||
function stopContainer($name, $t=false, $echo=true) {
|
||||
global $DockerClient;
|
||||
$waitID = mt_rand();
|
||||
if ($echo) {
|
||||
echo "<p class=\"logLine\" id=\"logBody\"></p>";
|
||||
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>"._('Stopping container').": ",addslashes(htmlspecialchars($name)),"</legend><p class=\"logLine\" id=\"logBody\"></p><span id=\"wait{$waitID}\">"._('Please wait')." </span></fieldset>');show_Wait($waitID);</script>\n";
|
||||
@flush();
|
||||
}
|
||||
$retval = $DockerClient->stopContainer($name, $t);
|
||||
$out = ($retval === true) ? _('Successfully stopped container')." '$name'" : _('Error').": ".$retval;
|
||||
if ($echo) {
|
||||
echo "<script>stop_Wait($waitID);addLog('<b>",addslashes(htmlspecialchars($out)),"</b>');</script>\n";
|
||||
@flush();
|
||||
}
|
||||
}
|
||||
|
||||
function removeContainer($name, $cache=false, $echo=true) {
|
||||
global $DockerClient;
|
||||
$waitID = mt_rand();
|
||||
if ($echo) {
|
||||
echo "<p class=\"logLine\" id=\"logBody\"></p>";
|
||||
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>"._('Removing container').": ",addslashes(htmlspecialchars($name)),"</legend><p class=\"logLine\" id=\"logBody\"></p><span id=\"wait{$waitID}\">"._('Please wait')." </span></fieldset>');show_Wait($waitID);</script>\n";
|
||||
@flush();
|
||||
}
|
||||
$retval = $DockerClient->removeContainer($name, false, $cache);
|
||||
$out = ($retval === true) ? _('Successfully removed container')." '$name'" : _('Error').": ".$retval;
|
||||
if ($echo) {
|
||||
echo "<script>stop_Wait($waitID);addLog('<b>",addslashes(htmlspecialchars($out)),"</b>');</script>\n";
|
||||
@flush();
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage($image, $echo=true) {
|
||||
global $DockerClient;
|
||||
$waitID = mt_rand();
|
||||
if ($echo) {
|
||||
echo "<p class=\"logLine\" id=\"logBody\"></p>";
|
||||
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>"._('Removing orphan image').": ",addslashes(htmlspecialchars($image)),"</legend><p class=\"logLine\" id=\"logBody\"></p><span id=\"wait{$waitID}\">"._('Please wait')." </span></fieldset>');show_Wait($waitID);</script>\n";
|
||||
@flush();
|
||||
}
|
||||
$retval = $DockerClient->removeImage($image);
|
||||
$out = ($retval === true) ? _('Successfully removed orphan image')." '$image'" : _('Error').": ".$retval;
|
||||
if ($echo) {
|
||||
echo "<script>stop_Wait($waitID);addLog('<b>",addslashes(htmlspecialchars($out))."</b>');</script>\n";
|
||||
@flush();
|
||||
}
|
||||
}
|
||||
|
||||
function pullImage($name, $image, $echo=true) {
|
||||
global $DockerClient, $DockerTemplates, $DockerUpdate;
|
||||
$waitID = mt_rand();
|
||||
if (!preg_match("/:\S+$/", $image)) $image .= ":latest";
|
||||
if ($echo) {
|
||||
echo "<p class=\"logLine\" id=\"logBody\"></p>";
|
||||
echo "<script>addLog('<fieldset style=\"margin-top:1px;\" class=\"CMD\"><legend>"._('Pulling image').": ",addslashes(htmlspecialchars($image)),"</legend><p class=\"logLine\" id=\"logBody\"></p><span id=\"wait{$waitID}\">"._('Please wait')." </span></fieldset>');show_Wait($waitID);</script>\n";
|
||||
@flush();
|
||||
}
|
||||
$alltotals = [];
|
||||
$laststatus = [];
|
||||
$strError = '';
|
||||
$DockerClient->pullImage($image, function ($line) use (&$alltotals, &$laststatus, &$waitID, &$strError, $image, $DockerClient, $DockerUpdate, $echo) {
|
||||
$cnt = json_decode($line, true);
|
||||
$id = $cnt['id'] ?? '';
|
||||
$status = $cnt['status'] ?? '';
|
||||
if (isset($cnt['error'])) $strError = $cnt['error'];
|
||||
if ($waitID !== false) {
|
||||
if ($echo) {
|
||||
echo "<script>stop_Wait($waitID);</script>\n";
|
||||
@flush();
|
||||
}
|
||||
$waitID = false;
|
||||
}
|
||||
if (empty($status)) return;
|
||||
if (!empty($id)) {
|
||||
if (!empty($cnt['progressDetail']) && !empty($cnt['progressDetail']['total'])) {
|
||||
$alltotals[$id] = $cnt['progressDetail']['total'];
|
||||
}
|
||||
if (empty($laststatus[$id])) {
|
||||
$laststatus[$id] = '';
|
||||
}
|
||||
switch ($status) {
|
||||
case 'Waiting':
|
||||
// Omit
|
||||
break;
|
||||
case 'Downloading':
|
||||
if ($laststatus[$id] != $status) {
|
||||
if ($echo) echo "<script>addToID('$id','",addslashes(htmlspecialchars($status)),"');</script>\n";
|
||||
}
|
||||
$total = $cnt['progressDetail']['total'];
|
||||
$current = $cnt['progressDetail']['current'];
|
||||
if ($total > 0) {
|
||||
$percentage = round(($current / $total) * 100);
|
||||
if ($echo) echo "<script>progress('$id',' ",$percentage,"% ",_('of')," ",$DockerClient->formatBytes($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
|
||||
$alltotals[$id] = $current;
|
||||
if ($echo) echo "<script>progress('$id',' ",$DockerClient->formatBytes($current),"');</script>\n";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if ($laststatus[$id] == "Downloading") {
|
||||
if ($echo) echo "<script>progress('$id',' 100% ",_('of')," ",$DockerClient->formatBytes($alltotals[$id]),"');</script>\n";
|
||||
}
|
||||
if ($laststatus[$id] != $status) {
|
||||
if ($echo) echo "<script>addToID('",($id=='latest'?mt_rand():$id),"','",addslashes(htmlspecialchars($status)),"');</script>\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
$laststatus[$id] = $status;
|
||||
} else {
|
||||
if (strpos($status, 'Status: ') === 0) {
|
||||
if ($echo) echo "<script>addLog('",addslashes(htmlspecialchars($status)),"');</script>\n";
|
||||
}
|
||||
if (strpos($status, 'Digest: ') === 0) {
|
||||
$DockerUpdate->setUpdateStatus($image, substr($status, 8));
|
||||
}
|
||||
}
|
||||
if ($echo) @flush();
|
||||
});
|
||||
if ($echo) {
|
||||
echo "<script>addLog('<br><b>",_('TOTAL DATA PULLED'),":</b> ",$DockerClient->formatBytes(array_sum($alltotals)),"');</script>\n";
|
||||
@flush();
|
||||
}
|
||||
if (!empty($strError)) {
|
||||
if ($echo) {
|
||||
echo "<script>addLog('<br><span class=\"error\"><b>",_('Error'),":</b> ",addslashes(htmlspecialchars($strError)),"</span>');</script>\n";
|
||||
@flush();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function execCommand($command, $echo=true) {
|
||||
$waitID = mt_rand();
|
||||
if ($echo) {
|
||||
[$cmd,$args] = explode(' ',$command,2);
|
||||
echo '<p class="logLine" id="logBody"></p>';
|
||||
echo '<script>addLog(\'<fieldset style="margin-top:1px;" class="CMD"><legend>',_('Command execution'),'</legend>';
|
||||
echo basename($cmd),' ',str_replace(" -","<br> -",addslashes(htmlspecialchars($args))),'<br>';
|
||||
echo '<span id="wait'.$waitID.'">',_('Please wait').' </span>';
|
||||
echo '<p class="logLine" id="logBody"></p></fieldset>\');show_Wait('.$waitID.');</script>';
|
||||
@flush();
|
||||
}
|
||||
$proc = popen("$command 2>&1",'r');
|
||||
while ($out = fgets($proc)) {
|
||||
$out = preg_replace("%[\t\n\x0B\f\r]+%", '', $out);
|
||||
if ($echo) {
|
||||
echo '<script>addLog("',htmlspecialchars($out),'");</script>';
|
||||
@flush();
|
||||
}
|
||||
}
|
||||
$retval = pclose($proc);
|
||||
if ($echo) echo '<script>stop_Wait('.$waitID.');</script>';
|
||||
$out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!';
|
||||
if ($echo) echo '<script>addLog(\'<br><b>',$out,'</b>\');</script>';
|
||||
return $retval===0;
|
||||
}
|
||||
|
||||
function getXmlVal($xml, $element, $attr=null, $pos=0) {
|
||||
$xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml);
|
||||
$element = $xml->xpath("//$element")[$pos];
|
||||
return isset($element) ? (isset($element[$attr]) ? strval($element[$attr]) : strval($element)) : "";
|
||||
}
|
||||
|
||||
function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) {
|
||||
$xml = (is_file($xml)) ? simplexml_load_file($xml) : simplexml_load_string($xml);
|
||||
$element = $xml->xpath("//$el")[$pos];
|
||||
if (!isset($element)) $element = $xml->addChild($el);
|
||||
if ($attr) {
|
||||
$element[$attr] = $value;
|
||||
} else {
|
||||
$element->{0} = $value;
|
||||
}
|
||||
$dom = new DOMDocument('1.0');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$dom->loadXML($xml->asXML());
|
||||
$xml = $dom->saveXML();
|
||||
}
|
||||
|
||||
function getAllocations() {
|
||||
global $DockerClient, $host;
|
||||
|
||||
$ports = [];
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
$list = $port = [];
|
||||
$nat = $ip = false;
|
||||
$list['Name'] = $ct['Name'];
|
||||
foreach ($ct['Ports'] as $tmp) {
|
||||
$nat = $tmp['NAT'];
|
||||
$ip = $tmp['IP'];
|
||||
$port[] = $tmp['PublicPort'];
|
||||
}
|
||||
sort($port);
|
||||
$ip = $ct['NetworkMode']=='host'||$nat ? $host : ($ip ?: DockerUtil::myIP($ct['Name']) ?: '0.0.0.0');
|
||||
$list['Port'] = "<span class='net'>{$ct['NetworkMode']}</span><span class='ip'>$ip</span>".(implode(', ',array_unique($port)) ?: '???');
|
||||
$ports[] = $list;
|
||||
}
|
||||
return $ports;
|
||||
}
|
||||
|
||||
function getCurlHandle($url, $method='GET') {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 45);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "");
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_REFERER, "");
|
||||
if ($method === 'HEAD') {
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD');
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_NOBODY, true);
|
||||
}
|
||||
return $ch;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2018, Lime Technology
|
||||
* Copyright 2012-2018, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
function ports_only($key) {
|
||||
return preg_match('/_(ETH|BR|BOND)[0-9]/',$key);
|
||||
}
|
||||
$ports = array_filter($_POST,'ports_only',ARRAY_FILTER_USE_KEY);
|
||||
$purge = [];
|
||||
foreach ($ports as $port => $val) {
|
||||
$port = explode('_',$port,3)[2];
|
||||
if (!in_array($port,$purge)) $purge[] = $port;
|
||||
}
|
||||
foreach ($purge as $port) {
|
||||
switch (substr($port,0,2)) {
|
||||
case 'ET':
|
||||
$A1 = str_replace('ETH','BR',$port);
|
||||
$A2 = str_replace('ETH','BOND',$port);
|
||||
break;
|
||||
case 'BR':
|
||||
$A1 = str_replace('BR','ETH',$port);
|
||||
$A2 = str_replace('BR','BOND',$port);
|
||||
break;
|
||||
case 'BO':
|
||||
$A1 = str_replace('BOND','BR',$port);
|
||||
$A2 = str_replace('BOND','ETH',$port);
|
||||
break;
|
||||
}
|
||||
unset($keys["DOCKER_AUTO_$A1"], $keys["DOCKER_AUTO_$A2"]);
|
||||
unset($keys["DOCKER_DHCP_$A1"], $keys["DOCKER_DHCP6_$A1"], $keys["DOCKER_DHCP_$A2"], $keys["DOCKER_DHCP6_$A2"]);
|
||||
unset($keys["DOCKER_SUBNET_$A1"], $keys["DOCKER_SUBNET6_$A1"], $keys["DOCKER_SUBNET_$A2"], $keys["DOCKER_SUBNET6_$A2"]);
|
||||
unset($keys["DOCKER_GATEWAY_$A1"], $keys["DOCKER_GATEWAY6_$A1"], $keys["DOCKER_GATEWAY_$A2"], $keys["DOCKER_GATEWAY6_$A2"]);
|
||||
unset($keys["DOCKER_RANGE_$A1"], $keys["DOCKER_RANGE6_$A1"], $keys["DOCKER_RANGE_$A2"], $keys["DOCKER_RANGE6_$A2"]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2021, Lime Technology
|
||||
* Copyright 2014-2021, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2021, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$autostart_file = $dockerManPaths['autostart-file'];
|
||||
$template_repos = $dockerManPaths['template-repos'];
|
||||
$user_prefs = $dockerManPaths['user-prefs'];
|
||||
|
||||
switch ($_POST['action']) {
|
||||
case 'autostart':
|
||||
// update container autostart setting
|
||||
$container = urldecode(($_POST['container']));
|
||||
$wait = $_POST['wait'];
|
||||
$item = rtrim("$container $wait");
|
||||
$autostart = @file($autostart_file, FILE_IGNORE_NEW_LINES) ?: [];
|
||||
$key = array_search($item, $autostart);
|
||||
if ($_POST['auto']=='true') {
|
||||
if ($key===false) $autostart[] = $item;
|
||||
} else {
|
||||
unset($autostart[$key]);
|
||||
}
|
||||
if ($autostart) {
|
||||
if (file_exists($user_prefs)) {
|
||||
$prefs = parse_ini_file($user_prefs); $sort = [];
|
||||
foreach ($autostart as $ct) $sort[] = array_search(var_split($ct),$prefs) ?? 999;
|
||||
array_multisort($sort,$autostart);
|
||||
} else {
|
||||
natcasesort($autostart);
|
||||
}
|
||||
file_put_contents($autostart_file, implode("\n", $autostart)."\n");
|
||||
} else @unlink($autostart_file);
|
||||
break;
|
||||
case 'wait':
|
||||
// update wait period used after container autostart
|
||||
$container = urldecode(($_POST['container']));
|
||||
$wait = $_POST['wait'];
|
||||
$item = rtrim("$container $wait");
|
||||
$autostart = file($autostart_file, FILE_IGNORE_NEW_LINES) ?: [];
|
||||
$names = array_map('var_split', $autostart);
|
||||
$autostart[array_search($container,$names)] = $item;
|
||||
file_put_contents($autostart_file, implode("\n", $autostart)."\n");
|
||||
break;
|
||||
case 'templates':
|
||||
// update template
|
||||
readfile("$docroot/update.htm");
|
||||
file_put_contents($template_repos, $_POST['template_repos']);
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
$DockerTemplates->downloadTemplates();
|
||||
break;
|
||||
case 'exist':
|
||||
// docker file or folder exists?
|
||||
$file = $_POST['name'];
|
||||
if (substr($file,0,5)=='/mnt/') echo file_exists($file) ? 0 : 1;
|
||||
break;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2018, Lime Technology
|
||||
* Copyright 2015-2018, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2018, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$autostart_file = $dockerManPaths['autostart-file'];
|
||||
$user_prefs = $dockerManPaths['user-prefs'];
|
||||
|
||||
if (isset($_POST['reset'])) {
|
||||
@unlink($user_prefs);
|
||||
if (file_exists($autostart_file)) {
|
||||
$allAutoStart = file($autostart_file, FILE_IGNORE_NEW_LINES);
|
||||
natcasesort($allAutoStart);
|
||||
file_put_contents($autostart_file, implode(PHP_EOL, $allAutoStart).PHP_EOL);
|
||||
}
|
||||
} else {
|
||||
$names = explode(';',$_POST['names']);
|
||||
$index = explode(';',$_POST['index']);
|
||||
$save = []; $i = 0;
|
||||
|
||||
foreach ($names as $name) if ($name) $save[] = $index[$i++]."=\"".$name."\""; else $i++;
|
||||
file_put_contents($user_prefs, implode("\n",$save)."\n");
|
||||
|
||||
// sort containers for start-up
|
||||
if (file_exists($autostart_file)) {
|
||||
$prefs = parse_ini_file($user_prefs); $sort = [];
|
||||
$allAutoStart = file($autostart_file, FILE_IGNORE_NEW_LINES);
|
||||
foreach ($allAutoStart as $ct) $sort[] = array_search(explode(' ',$ct)[0],$prefs) ?? 999;
|
||||
array_multisort($sort,SORT_NUMERIC,$allAutoStart);
|
||||
file_put_contents($autostart_file, implode(PHP_EOL, $allAutoStart).PHP_EOL);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -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",html:true,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();
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
var eventURL = '/plugins/dynamix.docker.manager/include/Events.php';
|
||||
|
||||
function addDockerContainerContext(container, image, template, started, paused, update, autostart, webui, shell, id, Support, Project, Registry, donateLink, ReadMe) {
|
||||
var opts = [];
|
||||
context.settings({right:false,above:false});
|
||||
if (started && !paused) {
|
||||
if (webui !== '' && webui != '#') opts.push({text:_('WebUI'), icon:'fa-globe', href:webui, target:'_blank'});
|
||||
opts.push({text:_('Console'), icon:'fa-terminal', action:function(e){e.preventDefault(); openTerminal('docker',container,shell);}});
|
||||
opts.push({divider:true});
|
||||
}
|
||||
if (update==1) {
|
||||
opts.push({text:_('Update'), icon:'fa-cloud-download', action:function(e){e.preventDefault(); updateContainer(container);}});
|
||||
opts.push({divider:true});
|
||||
}
|
||||
if (started) {
|
||||
if (paused) {
|
||||
opts.push({text:_('Resume'), icon:'fa-play', action:function(e){e.preventDefault(); eventControl({action:'resume', container:id}, 'loadlist');}});
|
||||
} else {
|
||||
opts.push({text:_('Stop'), icon:'fa-stop', action:function(e){e.preventDefault(); eventControl({action:'stop', container:id}, 'loadlist');}});
|
||||
opts.push({text:_('Pause'), icon:'fa-pause', action:function(e){e.preventDefault(); eventControl({action:'pause', container:id}, 'loadlist');}});
|
||||
}
|
||||
opts.push({text:_('Restart'), icon:'fa-refresh', action:function(e){e.preventDefault(); eventControl({action:'restart', container:id}, 'loadlist');}});
|
||||
} else {
|
||||
opts.push({text:_('Start'), icon:'fa-play', action:function(e){e.preventDefault(); eventControl({action:'start', container:id}, 'loadlist');}});
|
||||
}
|
||||
opts.push({divider:true});
|
||||
opts.push({text:_('Logs'), icon:'fa-navicon', action:function(e){e.preventDefault(); openTerminal('docker',container,'.log');}});
|
||||
if (template) {
|
||||
opts.push({text:_('Edit'), icon:'fa-wrench', action:function(e){e.preventDefault(); editContainer(container, template);}});
|
||||
}
|
||||
opts.push({text:_('Remove'), icon:'fa-trash', action:function(e){e.preventDefault(); rmContainer(container, image, id);}});
|
||||
if (ReadMe||Project||Support||Registry) {
|
||||
opts.push({divider:true});
|
||||
}
|
||||
if (ReadMe) {
|
||||
opts.push({text:_('Read Me First'), icon:'fa-book', href:ReadMe, target:'_blank'});
|
||||
}
|
||||
if (Project) {
|
||||
opts.push({text:_('Project Page'), icon:'fa-life-ring', href:Project, target:'_blank'});
|
||||
}
|
||||
if (Support) {
|
||||
opts.push({text:_('Support'), icon:'fa-question', href:Support, target:'_blank'});
|
||||
}
|
||||
if (Registry) {
|
||||
opts.push({text:_('More Info'),icon:'fa-info-circle', href:Registry, target:'_blank'});
|
||||
}
|
||||
if (donateLink) {
|
||||
opts.push({divider:true});
|
||||
opts.push({text:_('Donate'),icon:'fa-external-link', href:donateLink,target:'_blank'});
|
||||
}
|
||||
context.attach('#'+id, opts);
|
||||
}
|
||||
function addDockerImageContext(image, imageTag) {
|
||||
var opts = [];
|
||||
opts.push({text:_('Remove'), icon:'fa-trash', action:function(e){e.preventDefault(); rmImage(image, imageTag);}});
|
||||
context.attach('#'+image, opts);
|
||||
}
|
||||
function popupWithIframe(title, cmd, reload, func) {
|
||||
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: Math.min(Math.max(window.innerWidth/2,900),1600),
|
||||
height: Math.max(window.innerHeight*3/5,600),
|
||||
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) {
|
||||
if (func) setTimeout(func+'()',0); else location = window.location.href;
|
||||
} else {
|
||||
resumeEvents();
|
||||
}
|
||||
}
|
||||
});
|
||||
$(".ui-dialog .ui-dialog-titlebar").addClass('menu');
|
||||
$('.ui-dialog .ui-dialog-titlebar-close').text('X').prop('title',_('Close'));
|
||||
$(".ui-dialog .ui-dialog-title").css({'text-align':'center','width':'100%'});
|
||||
$(".ui-dialog .ui-dialog-content").css({'padding-top':'15px','vertical-align':'bottom'});
|
||||
}
|
||||
function execUpContainer(container) {
|
||||
var title = _('Updating the container')+': '+container;
|
||||
var cmd = '/plugins/dynamix.docker.manager/include/CreateDocker.php?updateContainer=true&ct[]='+encodeURIComponent(container);
|
||||
popupWithIframe(title, cmd, true, 'loadlist');
|
||||
}
|
||||
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 updateContainer(container) {
|
||||
swal({
|
||||
title:_('Are you sure?'),text:_('Update container')+': '+container, type:'warning',html:true,showCancelButton:true,closeOnConfirm:false,confirmButtonText:_('Yes, update it!'),cancelButtonText:_('Cancel')
|
||||
},function(){
|
||||
openDocker('update_container '+encodeURIComponent(container),_('Updating the container'),'','loadlist');
|
||||
});
|
||||
}
|
||||
function rmContainer(container, image, id) {
|
||||
var body = _('Remove container')+': '+container+'<br><br><label><input id="removeimagechk" type="checkbox" checked style="display:inline;width:unset;height:unset;margin-top:unset;margin-bottom:unset">'+_('also remove image')+'</label>';
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
swal({
|
||||
title:_('Are you sure?'),text:body,type:'warning',html:true,showCancelButton:true,confirmButtonText:_('Yes, delete it!'),cancelButtonText:_('Cancel'),showLoaderOnConfirm:true
|
||||
},function(c){
|
||||
if (!c) {setTimeout(loadlist); return;}
|
||||
$('div.spinner.fixed').show('slow');
|
||||
if ($('#removeimagechk').prop('checked')) {
|
||||
eventControl({action:'remove_all', container:id, name:container, image:image},'loadlist');
|
||||
} else {
|
||||
eventControl({action:'remove_container', container:id, name:container},'loadlist');
|
||||
}
|
||||
});
|
||||
}
|
||||
function rmImage(image, imageName) {
|
||||
var body = _('Remove image')+': '+$('<textarea />').html(imageName).text();
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
swal({
|
||||
title:_('Are you sure?'),text:body,type:'warning',html:true,showCancelButton:true,confirmButtonText:_('Yes, delete it!'),cancelButtonText:_('Cancel'),showLoaderOnConfirm:true
|
||||
},function(c){
|
||||
if (!c) {setTimeout(loadlist,0); return;}
|
||||
$('div.spinner.fixed').show('slow');
|
||||
eventControl({action:'remove_image', image:image},'loadlist');
|
||||
});
|
||||
}
|
||||
function eventControl(params, spin) {
|
||||
if (spin) $('#'+params['container']).parent().find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
$.post(eventURL, params, function(data) {
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
if (data.success === true) {
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
} else {
|
||||
setTimeout(function(){
|
||||
swal({
|
||||
title:_('Execution error'),text:data.success,type:'error',html:true,confirmButtonText:_('Ok')
|
||||
},function(){
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
});
|
||||
},100);
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
function startAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,ct; ct=docker[i]; i++) if (ct.state==0) $('#'+ct.id).parent().find('i').removeClass('fa-square').addClass('fa-refresh fa-spin');
|
||||
$.post('/plugins/dynamix.docker.manager/include/ContainerManager.php',{action:'start'},function(){loadlist();});
|
||||
}
|
||||
function stopAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,ct; ct=docker[i]; i++) if (ct.state==1) $('#'+ct.id).parent().find('i').removeClass('fa-play fa-pause').addClass('fa-refresh fa-spin');
|
||||
$.post('/plugins/dynamix.docker.manager/include/ContainerManager.php',{action:'stop'},function(){loadlist();});
|
||||
}
|
||||
function pauseAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,ct; ct=docker[i]; i++) if (ct.state==1 && ct.pause==0) $('#'+ct.id).parent().find('i').removeClass('fa-play').addClass('fa-refresh fa-spin');
|
||||
$.post('/plugins/dynamix.docker.manager/include/ContainerManager.php',{action:'pause'},function(){loadlist();});
|
||||
}
|
||||
function resumeAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,ct; ct=docker[i]; i++) if (ct.state==1 && ct.pause==1) $('#'+ct.id).parent().find('i').removeClass('fa-pause').addClass('fa-refresh fa-spin');
|
||||
$.post('/plugins/dynamix.docker.manager/include/ContainerManager.php',{action:'unpause'},function(){loadlist();});
|
||||
}
|
||||
function checkAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
$('.updatecolumn').html('<span style="color:#267CA8"><i class="fa fa-refresh fa-spin"></i> '+_('checking')+'...</span>');
|
||||
$.post('/plugins/dynamix.docker.manager/include/DockerUpdate.php',{},function(){loadlist();});
|
||||
}
|
||||
function updateAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
var ct = [];
|
||||
for (var i=0,d; d=docker[i]; i++) if (d.update==1) ct.push(encodeURIComponent(d.name));
|
||||
openDocker('update_container '+ct.join('*'),_('Updating all Containers'),'','loadlist');
|
||||
}
|
||||
function rebuildAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
$('div.spinner.fixed').show('slow');
|
||||
var ct = [];
|
||||
for (var i=0,d; d=docker[i]; i++) if (d.update==2) ct.push(encodeURIComponent(d.name));
|
||||
$.get('/plugins/dynamix.docker.manager/include/CreateDocker.php',{updateContainer:true,mute:true,ct},function(){loadlist();});
|
||||
}
|
||||
@@ -0,0 +1,548 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width=1300">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="same-origin">
|
||||
<style>
|
||||
/************************
|
||||
/
|
||||
/ Fonts
|
||||
/
|
||||
/************************/
|
||||
@font-face{font-family:clear-sans;font-weight:normal;font-style:normal; src:url('/webGui/styles/clear-sans.woff?v=20220513') format('woff')}
|
||||
@font-face{font-family:clear-sans;font-weight:bold;font-style:normal; src:url('/webGui/styles/clear-sans-bold.woff?v=20220513') format('woff')}
|
||||
@font-face{font-family:clear-sans;font-weight:normal;font-style:italic; src:url('/webGui/styles/clear-sans-italic.woff?v=20220513') format('woff')}
|
||||
@font-face{font-family:clear-sans;font-weight:bold;font-style:italic; src:url('/webGui/styles/clear-sans-bold-italic.woff?v=20220513') format('woff')}
|
||||
@font-face{font-family:bitstream;font-weight:normal;font-style:normal; src:url('/webGui/styles/bitstream.woff?v=20220513') format('woff')}
|
||||
@font-face{font-family:bitstream;font-weight:bold;font-style:normal; src:url('/webGui/styles/bitstream-bold.woff?v=20220513') format('woff')}
|
||||
@font-face{font-family:bitstream;font-weight:normal;font-style:italic; src:url('/webGui/styles/bitstream-italic.woff?v=20220513') format('woff')}
|
||||
@font-face{font-family:bitstream;font-weight:bold;font-style:italic; src:url('/webGui/styles/bitstream-bold-italic.woff?v=20220513') format('woff')}
|
||||
|
||||
html{font-family:clear-sans;height:100%}
|
||||
body{font-size:1.3rem;padding:0;margin:0;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
|
||||
.logLine{color:#1c1c1c;font-family:bitstream;font-size:1.2rem;line-height:1.6rem;margin:0 8px;padding:0;text-align:left}
|
||||
.logLine.spacing{margin:10px}
|
||||
input[type=button],input[type=reset],input[type=submit],button,button[type=button],a.button{font-family:clear-sans;font-size:1.1rem;text-transform:uppercase;margin:0;padding:9px 18px;text-decoration:none;white-space:nowrap;cursor:pointer;outline:none;border-radius:4px;border:0;color:#f15a2c;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#e22828),to(#e22828)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#ff8c2f),to(#ff8c2f)) 100% 100% no-repeat;background:linear-gradient(90deg,#e22828 0,#ff8c2f) 0 0 no-repeat,linear-gradient(90deg,#e22828 0,#ff8c2f) 0 100% no-repeat,linear-gradient(0deg,#e22828 0,#e22828) 0 100% no-repeat,linear-gradient(0deg,#ff8c2f 0,#ff8c2f) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%}
|
||||
input:hover[type=button],input:hover[type=reset],input:hover[type=submit],button:hover,button:hover[type=button],a.button:hover{color:#f2f2f2;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));background:linear-gradient(90deg,#e22828 0,#ff8c2f)}
|
||||
input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled],button[disabled],button[type=button][disabled],a.button[disabled]
|
||||
input:hover[type=button][disabled],input:hover[type=reset][disabled],input:hover[type=submit][disabled],button:hover[disabled],button:hover[type=button][disabled],a.button:hover[disabled]
|
||||
input:active[type=button][disabled],input:active[type=reset][disabled],input:active[type=submit][disabled],button:active[disabled],button:active[type=button][disabled],a.button:active[disabled]
|
||||
{cursor:default;color:#808080;background:-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#404040),to(#404040)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#808080),to(#808080)) 100% 100% no-repeat;background:linear-gradient(90deg,#404040 0,#808080) 0 0 no-repeat,linear-gradient(90deg,#404040 0,#808080) 0 100% no-repeat,linear-gradient(0deg,#404040 0,#404040) 0 100% no-repeat,linear-gradient(0deg,#808080 0,#808080) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%}
|
||||
p.centered{text-align:center}
|
||||
span.error{color:#D8000C;background-color:#FFBABA;display:block;width:100%}
|
||||
span.warn{color:#9F6000;background-color:#FEEFB3;display:block;width:100%}
|
||||
span.system{color:#00529B;background-color:#BDE5F8;display:block;width:100%}
|
||||
span.array{color:#4F8A10;background-color:#DFF2BF;display:block;width:100%}
|
||||
span.login{color:#D63301;background-color:#FFCCBA;display:block;width:100%}
|
||||
span.label{padding:4px 8px;margin-right:10px;border-radius:4px;display:inline;width:auto}
|
||||
legend{font-size:1.1rem;font-weight:bold}
|
||||
#content{margin:10;padding:0}
|
||||
</style>
|
||||
<script>
|
||||
/*
|
||||
This script block has the following license.
|
||||
Original: https://github.com/drudru/ansi_up Version: 5.0.1
|
||||
Modified by: https://github.com/nysos3 for use in unRaid
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2011 Dru Nelson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
|
||||
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
|
||||
return cooked;
|
||||
};
|
||||
|
||||
var PacketKind;
|
||||
(function (PacketKind) {
|
||||
PacketKind[PacketKind["EOS"] = 0] = "EOS";
|
||||
PacketKind[PacketKind["Text"] = 1] = "Text";
|
||||
PacketKind[PacketKind["Incomplete"] = 2] = "Incomplete";
|
||||
PacketKind[PacketKind["ESC"] = 3] = "ESC";
|
||||
PacketKind[PacketKind["Unknown"] = 4] = "Unknown";
|
||||
PacketKind[PacketKind["SGR"] = 5] = "SGR";
|
||||
PacketKind[PacketKind["OSCURL"] = 6] = "OSCURL";
|
||||
})(PacketKind || (PacketKind = {}));
|
||||
|
||||
var AnsiUp = (function() {
|
||||
function AnsiUp() {
|
||||
this.VERSION = "5.0.1";
|
||||
this.setup_palettes();
|
||||
this._use_classes = false;
|
||||
this.bold = false;
|
||||
this.fg = this.bg = null;
|
||||
this._buffer = '';
|
||||
this._url_whitelist = { 'http': 1, 'https': 1 };
|
||||
}
|
||||
Object.defineProperty(AnsiUp.prototype, "use_classes", {
|
||||
get: function() {
|
||||
return this._use_classes;
|
||||
},
|
||||
set: function(arg) {
|
||||
this._use_classes = arg;
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
Object.defineProperty(AnsiUp.prototype, "url_whitelist", {
|
||||
get: function() {
|
||||
return this._url_whitelist;
|
||||
},
|
||||
set: function(arg) {
|
||||
this._url_whitelist = arg;
|
||||
},
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
AnsiUp.prototype.setup_palettes = function() {
|
||||
var _this = this;
|
||||
this.ansi_colors = [
|
||||
[
|
||||
{ rgb: [0, 0, 0], class_name: "ansi-black" },
|
||||
{ rgb: [187, 0, 0], class_name: "ansi-red" },
|
||||
{ rgb: [0, 187, 0], class_name: "ansi-green" },
|
||||
{ rgb: [187, 187, 0], class_name: "ansi-yellow" },
|
||||
{ rgb: [0, 0, 187], class_name: "ansi-blue" },
|
||||
{ rgb: [187, 0, 187], class_name: "ansi-magenta" },
|
||||
{ rgb: [0, 187, 187], class_name: "ansi-cyan" },
|
||||
{ rgb: [255, 255, 255], class_name: "ansi-white" }
|
||||
],
|
||||
[
|
||||
{ rgb: [85, 85, 85], class_name: "ansi-bright-black" },
|
||||
{ rgb: [255, 85, 85], class_name: "ansi-bright-red" },
|
||||
{ rgb: [0, 255, 0], class_name: "ansi-bright-green" },
|
||||
{ rgb: [255, 255, 85], class_name: "ansi-bright-yellow" },
|
||||
{ rgb: [85, 85, 255], class_name: "ansi-bright-blue" },
|
||||
{ rgb: [255, 85, 255], class_name: "ansi-bright-magenta" },
|
||||
{ rgb: [85, 255, 255], class_name: "ansi-bright-cyan" },
|
||||
{ rgb: [255, 255, 255], class_name: "ansi-bright-white" }
|
||||
]
|
||||
];
|
||||
this.palette_256 = [];
|
||||
this.ansi_colors.forEach(function(palette) {
|
||||
palette.forEach(function(rec) {
|
||||
_this.palette_256.push(rec);
|
||||
});
|
||||
});
|
||||
var levels = [0, 95, 135, 175, 215, 255];
|
||||
for (var r = 0; r < 6; ++r) {
|
||||
for (var g = 0; g < 6; ++g) {
|
||||
for (var b = 0; b < 6; ++b) {
|
||||
var col = { rgb: [levels[r], levels[g], levels[b]], class_name: 'truecolor' };
|
||||
this.palette_256.push(col);
|
||||
}
|
||||
}
|
||||
}
|
||||
var grey_level = 8;
|
||||
for (var i = 0; i < 24; ++i, grey_level += 10) {
|
||||
var gry = { rgb: [grey_level, grey_level, grey_level], class_name: 'truecolor' };
|
||||
this.palette_256.push(gry);
|
||||
}
|
||||
};
|
||||
AnsiUp.prototype.escape_txt_for_html = function(txt) {
|
||||
return txt.replace(/[&<>"']/gm, function(str) {
|
||||
if (str === "&")
|
||||
return "&";
|
||||
if (str === "<")
|
||||
return "<";
|
||||
if (str === ">")
|
||||
return ">";
|
||||
if (str === "\"")
|
||||
return """;
|
||||
if (str === "'")
|
||||
return "'";
|
||||
});
|
||||
};
|
||||
AnsiUp.prototype.append_buffer = function(txt) {
|
||||
var str = this._buffer + txt;
|
||||
this._buffer = str;
|
||||
};
|
||||
AnsiUp.prototype.get_next_packet = function() {
|
||||
var pkt = {
|
||||
kind: PacketKind.EOS,
|
||||
text: '',
|
||||
url: ''
|
||||
};
|
||||
var len = this._buffer.length;
|
||||
if (len == 0)
|
||||
return pkt;
|
||||
var pos = this._buffer.indexOf("\x1B");
|
||||
if (pos == -1) {
|
||||
pkt.kind = PacketKind.Text;
|
||||
pkt.text = this._buffer;
|
||||
this._buffer = '';
|
||||
return pkt;
|
||||
}
|
||||
if (pos > 0) {
|
||||
pkt.kind = PacketKind.Text;
|
||||
pkt.text = this._buffer.slice(0, pos);
|
||||
this._buffer = this._buffer.slice(pos);
|
||||
return pkt;
|
||||
}
|
||||
if (pos == 0) {
|
||||
if (len == 1) {
|
||||
pkt.kind = PacketKind.Incomplete;
|
||||
return pkt;
|
||||
}
|
||||
var next_char = this._buffer.charAt(1);
|
||||
if ((next_char != '[') && (next_char != ']')) {
|
||||
pkt.kind = PacketKind.ESC;
|
||||
pkt.text = this._buffer.slice(0, 1);
|
||||
this._buffer = this._buffer.slice(1);
|
||||
return pkt;
|
||||
}
|
||||
if (next_char == '[') {
|
||||
if (!this._csi_regex) {
|
||||
this._csi_regex = rgx(__makeTemplateObject(["\n ^ # beginning of line\n #\n # First attempt\n (?: # legal sequence\n \u001B[ # CSI\n ([<-?]?) # private-mode char\n ([d;]*) # any digits or semicolons\n ([ -/]? # an intermediate modifier\n [@-~]) # the command\n )\n | # alternate (second attempt)\n (?: # illegal sequence\n \u001B[ # CSI\n [ -~]* # anything legal\n ([\0-\u001F:]) # anything illegal\n )\n "], ["\n ^ # beginning of line\n #\n # First attempt\n (?: # legal sequence\n \\x1b\\[ # CSI\n ([\\x3c-\\x3f]?) # private-mode char\n ([\\d;]*) # any digits or semicolons\n ([\\x20-\\x2f]? # an intermediate modifier\n [\\x40-\\x7e]) # the command\n )\n | # alternate (second attempt)\n (?: # illegal sequence\n \\x1b\\[ # CSI\n [\\x20-\\x7e]* # anything legal\n ([\\x00-\\x1f:]) # anything illegal\n )\n "]));
|
||||
}
|
||||
var match = this._buffer.match(this._csi_regex);
|
||||
if (match === null) {
|
||||
pkt.kind = PacketKind.Incomplete;
|
||||
return pkt;
|
||||
}
|
||||
if (match[4]) {
|
||||
pkt.kind = PacketKind.ESC;
|
||||
pkt.text = this._buffer.slice(0, 1);
|
||||
this._buffer = this._buffer.slice(1);
|
||||
return pkt;
|
||||
}
|
||||
if ((match[1] != '') || (match[3] != 'm'))
|
||||
pkt.kind = PacketKind.Unknown;
|
||||
else
|
||||
pkt.kind = PacketKind.SGR;
|
||||
pkt.text = match[2];
|
||||
var rpos = match[0].length;
|
||||
this._buffer = this._buffer.slice(rpos);
|
||||
return pkt;
|
||||
}
|
||||
if (next_char == ']') {
|
||||
if (len < 4) {
|
||||
pkt.kind = PacketKind.Incomplete;
|
||||
return pkt;
|
||||
}
|
||||
if ((this._buffer.charAt(2) != '8')
|
||||
|| (this._buffer.charAt(3) != ';')) {
|
||||
pkt.kind = PacketKind.ESC;
|
||||
pkt.text = this._buffer.slice(0, 1);
|
||||
this._buffer = this._buffer.slice(1);
|
||||
return pkt;
|
||||
}
|
||||
if (!this._osc_st) {
|
||||
this._osc_st = rgxG(__makeTemplateObject(["\n (?: # legal sequence\n (\u001B\\) # ESC | # alternate\n (\u0007) # BEL (what xterm did)\n )\n | # alternate (second attempt)\n ( # illegal sequence\n [\0-\u0006] # anything illegal\n | # alternate\n [\b-\u001A] # anything illegal\n | # alternate\n [\u001C-\u001F] # anything illegal\n )\n "], ["\n (?: # legal sequence\n (\\x1b\\\\) # ESC \\\n | # alternate\n (\\x07) # BEL (what xterm did)\n )\n | # alternate (second attempt)\n ( # illegal sequence\n [\\x00-\\x06] # anything illegal\n | # alternate\n [\\x08-\\x1a] # anything illegal\n | # alternate\n [\\x1c-\\x1f] # anything illegal\n )\n "]));
|
||||
}
|
||||
this._osc_st.lastIndex = 0;
|
||||
{
|
||||
var match_1 = this._osc_st.exec(this._buffer);
|
||||
if (match_1 === null) {
|
||||
pkt.kind = PacketKind.Incomplete;
|
||||
return pkt;
|
||||
}
|
||||
if (match_1[3]) {
|
||||
pkt.kind = PacketKind.ESC;
|
||||
pkt.text = this._buffer.slice(0, 1);
|
||||
this._buffer = this._buffer.slice(1);
|
||||
return pkt;
|
||||
}
|
||||
}
|
||||
{
|
||||
var match_2 = this._osc_st.exec(this._buffer);
|
||||
if (match_2 === null) {
|
||||
pkt.kind = PacketKind.Incomplete;
|
||||
return pkt;
|
||||
}
|
||||
if (match_2[3]) {
|
||||
pkt.kind = PacketKind.ESC;
|
||||
pkt.text = this._buffer.slice(0, 1);
|
||||
this._buffer = this._buffer.slice(1);
|
||||
return pkt;
|
||||
}
|
||||
}
|
||||
if (!this._osc_regex) {
|
||||
this._osc_regex = rgx(__makeTemplateObject(["\n ^ # beginning of line\n #\n \u001B]8; # OSC Hyperlink\n [ -:<-~]* # params (excluding ;)\n ; # end of params\n ([!-~]{0,512}) # URL capture\n (?: # ST\n (?:\u001B\\) # ESC | # alternate\n (?:\u0007) # BEL (what xterm did)\n )\n ([ -~]+) # TEXT capture\n \u001B]8;; # OSC Hyperlink End\n (?: # ST\n (?:\u001B\\) # ESC | # alternate\n (?:\u0007) # BEL (what xterm did)\n )\n "], ["\n ^ # beginning of line\n #\n \\x1b\\]8; # OSC Hyperlink\n [\\x20-\\x3a\\x3c-\\x7e]* # params (excluding ;)\n ; # end of params\n ([\\x21-\\x7e]{0,512}) # URL capture\n (?: # ST\n (?:\\x1b\\\\) # ESC \\\n | # alternate\n (?:\\x07) # BEL (what xterm did)\n )\n ([\\x20-\\x7e]+) # TEXT capture\n \\x1b\\]8;; # OSC Hyperlink End\n (?: # ST\n (?:\\x1b\\\\) # ESC \\\n | # alternate\n (?:\\x07) # BEL (what xterm did)\n )\n "]));
|
||||
}
|
||||
var match = this._buffer.match(this._osc_regex);
|
||||
if (match === null) {
|
||||
pkt.kind = PacketKind.ESC;
|
||||
pkt.text = this._buffer.slice(0, 1);
|
||||
this._buffer = this._buffer.slice(1);
|
||||
return pkt;
|
||||
}
|
||||
pkt.kind = PacketKind.OSCURL;
|
||||
pkt.url = match[1];
|
||||
pkt.text = match[2];
|
||||
var rpos = match[0].length;
|
||||
this._buffer = this._buffer.slice(rpos);
|
||||
return pkt;
|
||||
}
|
||||
}
|
||||
};
|
||||
AnsiUp.prototype.ansi_to_html = function(txt) {
|
||||
this.append_buffer(txt);
|
||||
var blocks = [];
|
||||
while (true) {
|
||||
var packet = this.get_next_packet();
|
||||
if ((packet.kind == PacketKind.EOS)
|
||||
|| (packet.kind == PacketKind.Incomplete))
|
||||
break;
|
||||
if ((packet.kind == PacketKind.ESC)
|
||||
|| (packet.kind == PacketKind.Unknown))
|
||||
continue;
|
||||
if (packet.kind == PacketKind.Text)
|
||||
blocks.push(this.transform_to_html(this.with_state(packet)));
|
||||
else if (packet.kind == PacketKind.SGR)
|
||||
this.process_ansi(packet);
|
||||
else if (packet.kind == PacketKind.OSCURL)
|
||||
blocks.push(this.process_hyperlink(packet));
|
||||
}
|
||||
return blocks.join("");
|
||||
};
|
||||
AnsiUp.prototype.with_state = function(pkt) {
|
||||
return { bold: this.bold, fg: this.fg, bg: this.bg, text: pkt.text };
|
||||
};
|
||||
AnsiUp.prototype.process_ansi = function(pkt) {
|
||||
var sgr_cmds = pkt.text.split(';');
|
||||
while (sgr_cmds.length > 0) {
|
||||
var sgr_cmd_str = sgr_cmds.shift();
|
||||
var num = parseInt(sgr_cmd_str, 10);
|
||||
if (isNaN(num) || num === 0) {
|
||||
this.fg = this.bg = null;
|
||||
this.bold = false;
|
||||
}
|
||||
else if (num === 1) {
|
||||
this.bold = true;
|
||||
}
|
||||
else if (num === 22) {
|
||||
this.bold = false;
|
||||
}
|
||||
else if (num === 39) {
|
||||
this.fg = null;
|
||||
}
|
||||
else if (num === 49) {
|
||||
this.bg = null;
|
||||
}
|
||||
else if ((num >= 30) && (num < 38)) {
|
||||
this.fg = this.ansi_colors[0][(num - 30)];
|
||||
}
|
||||
else if ((num >= 40) && (num < 48)) {
|
||||
this.bg = this.ansi_colors[0][(num - 40)];
|
||||
}
|
||||
else if ((num >= 90) && (num < 98)) {
|
||||
this.fg = this.ansi_colors[1][(num - 90)];
|
||||
}
|
||||
else if ((num >= 100) && (num < 108)) {
|
||||
this.bg = this.ansi_colors[1][(num - 100)];
|
||||
}
|
||||
else if (num === 38 || num === 48) {
|
||||
if (sgr_cmds.length > 0) {
|
||||
var is_foreground = (num === 38);
|
||||
var mode_cmd = sgr_cmds.shift();
|
||||
if (mode_cmd === '5' && sgr_cmds.length > 0) {
|
||||
var palette_index = parseInt(sgr_cmds.shift(), 10);
|
||||
if (palette_index >= 0 && palette_index <= 255) {
|
||||
if (is_foreground)
|
||||
this.fg = this.palette_256[palette_index];
|
||||
else
|
||||
this.bg = this.palette_256[palette_index];
|
||||
}
|
||||
}
|
||||
if (mode_cmd === '2' && sgr_cmds.length > 2) {
|
||||
var r = parseInt(sgr_cmds.shift(), 10);
|
||||
var g = parseInt(sgr_cmds.shift(), 10);
|
||||
var b = parseInt(sgr_cmds.shift(), 10);
|
||||
if ((r >= 0 && r <= 255) && (g >= 0 && g <= 255) && (b >= 0 && b <= 255)) {
|
||||
var c = { rgb: [r, g, b], class_name: 'truecolor' };
|
||||
if (is_foreground)
|
||||
this.fg = c;
|
||||
else
|
||||
this.bg = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
AnsiUp.prototype.transform_to_html = function(fragment) {
|
||||
var txt = fragment.text;
|
||||
if (txt.length === 0)
|
||||
return txt;
|
||||
// txt = this.escape_txt_for_html(txt);
|
||||
if (!fragment.bold && fragment.fg === null && fragment.bg === null)
|
||||
return txt;
|
||||
var styles = [];
|
||||
var classes = [];
|
||||
var fg = fragment.fg;
|
||||
var bg = fragment.bg;
|
||||
if (fragment.bold)
|
||||
styles.push('font-weight:bold');
|
||||
if (!this._use_classes) {
|
||||
if (fg)
|
||||
styles.push("color:rgb(" + fg.rgb.join(',') + ")");
|
||||
if (bg)
|
||||
styles.push("background-color:rgb(" + bg.rgb + ")");
|
||||
}
|
||||
else {
|
||||
if (fg) {
|
||||
if (fg.class_name !== 'truecolor') {
|
||||
classes.push(fg.class_name + "-fg");
|
||||
}
|
||||
else {
|
||||
styles.push("color:rgb(" + fg.rgb.join(',') + ")");
|
||||
}
|
||||
}
|
||||
if (bg) {
|
||||
if (bg.class_name !== 'truecolor') {
|
||||
classes.push(bg.class_name + "-bg");
|
||||
}
|
||||
else {
|
||||
styles.push("background-color:rgb(" + bg.rgb.join(',') + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
var class_string = '';
|
||||
var style_string = '';
|
||||
if (classes.length)
|
||||
class_string = " class=\"" + classes.join(' ') + "\"";
|
||||
if (styles.length)
|
||||
style_string = " style=\"" + styles.join(';') + "\"";
|
||||
return "<span" + style_string + class_string + ">" + txt + "</span>";
|
||||
};
|
||||
;
|
||||
AnsiUp.prototype.process_hyperlink = function(pkt) {
|
||||
var parts = pkt.url.split(':');
|
||||
if (parts.length < 1)
|
||||
return '';
|
||||
if (!this._url_whitelist[parts[0]])
|
||||
return '';
|
||||
// var result = "<a href=\"" + this.escape_txt_for_html(pkt.url) + "\">" + this.escape_txt_for_html(pkt.text) + "</a>";
|
||||
var result = "<a href=\"" + this.escape_txt_for_html(pkt.url) + "\">" + pkt.text + "</a>";
|
||||
return result;
|
||||
};
|
||||
return AnsiUp;
|
||||
}());
|
||||
function rgx(tmplObj) {
|
||||
var subst = [];
|
||||
for (var _i = 1; _i < arguments.length; _i++) {
|
||||
subst[_i - 1] = arguments[_i];
|
||||
}
|
||||
var regexText = tmplObj.raw[0];
|
||||
var wsrgx = /^\s+|\s+\n|\s*#[\s\S]*?\n|\n/gm;
|
||||
var txt2 = regexText.replace(wsrgx, '');
|
||||
return new RegExp(txt2);
|
||||
}
|
||||
function rgxG(tmplObj) {
|
||||
var subst = [];
|
||||
for (var _i = 1; _i < arguments.length; _i++) {
|
||||
subst[_i - 1] = arguments[_i];
|
||||
}
|
||||
var regexText = tmplObj.raw[0];
|
||||
var wsrgx = /^\s+|\s+\n|\s*#[\s\S]*?\n|\n/gm;
|
||||
var txt2 = regexText.replace(wsrgx, '');
|
||||
return new RegExp(txt2, 'g');
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
var dots = [];
|
||||
var span = [];
|
||||
var ansi_up = new AnsiUp;
|
||||
|
||||
function show_Wait(id) {
|
||||
span[id] = document.getElementById("wait" + id);
|
||||
dots[id] = setInterval(function() {
|
||||
if (((span[id].innerHTML+='.').match(/\./g)||[]).length > 9) {
|
||||
span[id].innerHTML = span[id].innerHTML.replace(/\.+$/,'');
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function stop_Wait(id) {
|
||||
span[id].innerHTML = '';
|
||||
clearInterval( dots[id] );
|
||||
}
|
||||
|
||||
function addLog(logLine) {
|
||||
var elms = document.getElementsByClassName('logLine');
|
||||
logLine = ansi_up.ansi_to_html(logLine);
|
||||
if (elms.length) {
|
||||
var el = elms[elms.length-1];
|
||||
el.innerHTML += logLine + "<br>";
|
||||
}
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
|
||||
function loadLog(container, since) {
|
||||
var httpRequest = new XMLHttpRequest();
|
||||
httpRequest.onreadystatechange = function() {
|
||||
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
|
||||
parseScript(httpRequest.responseText);
|
||||
}
|
||||
};
|
||||
httpRequest.open('GET', location.protocol+'//'+location.host+location.pathname+'?action=log&container='+encodeURIComponent(container)+'&since='+encodeURIComponent(since));
|
||||
httpRequest.send();
|
||||
}
|
||||
|
||||
function parseScript(_source) {
|
||||
var source = _source;
|
||||
var scripts = [];
|
||||
while(source.indexOf("<script") > -1 || source.indexOf("</script") > -1) {
|
||||
var s = source.indexOf("<script");
|
||||
var s_e = source.indexOf(">", s);
|
||||
var e = source.indexOf("</script", s);
|
||||
var e_e = source.indexOf(">", e);
|
||||
scripts.push(source.substring(s_e+1, e));
|
||||
source = source.substring(0, s) + source.substring(e_e+1);
|
||||
}
|
||||
for (var i=0; i<scripts.length; i++) {
|
||||
try {
|
||||
eval(scripts[i]);
|
||||
} catch(ex) {}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
function progress(id, prog) {
|
||||
var elms = document.getElementsByClassName(id+'_progress');
|
||||
if (elms.length) {
|
||||
elms[elms.length-1].textContent = prog;
|
||||
}
|
||||
}
|
||||
|
||||
function addToID(id, m) {
|
||||
var elms = document.getElementById(id);
|
||||
if (elms === null) {
|
||||
addLog('<span id=\"'+id+'\">IMAGE ID ['+id+']: <span class=\"content\">'+m+'</span><span class=\"'+id+'_progress\"></span>. </span>');
|
||||
} else {
|
||||
var elms_content = elms.getElementsByClassName("content");
|
||||
if (!elms_content.length || elms_content[elms_content.length-1].textContent != m) {
|
||||
elms.innerHTML += '<span class=\"content\">'+m+'</span><span class=\"'+id+'_progress\"></span>. ';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"><p class="logLine" id="logBody"></p></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
while :; do
|
||||
curl -sfd "$(docker stats --no-stream --format='{{.ID}};{{.CPUPerc}};{{.MemUsage}}')" --unix-socket /var/run/nginx.socket http://localhost/pub/dockerload?buffer_length=0 >/dev/null 2>&1
|
||||
done
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'docker';
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$unit = ['B','kB','MB','GB','TB','PB','EB','ZB','YB'];
|
||||
$list = [];
|
||||
|
||||
function write(...$messages){
|
||||
$com = curl_init();
|
||||
curl_setopt_array($com,[
|
||||
CURLOPT_URL => 'http://localhost/pub/plugins?buffer_length=1',
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
]);
|
||||
foreach ($messages as $message) {
|
||||
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
|
||||
curl_exec($com);
|
||||
}
|
||||
curl_close($com);
|
||||
}
|
||||
function autoscale($value) {
|
||||
global $unit;
|
||||
$size = count($unit);
|
||||
$base = $value ? floor(log($value, 1000)) : 0;
|
||||
if ($base>=$size) $base = $size-1;
|
||||
$value /= pow(1000, $base);
|
||||
$decimals = $base ? ($value>=100 ? 0 : ($value>=10 ? 1 : (round($value*100)%100===0 ? 0 : 2))) : 0;
|
||||
return number_format($value, $decimals, '.', $value>9999 ? ',' : '').' '.$unit[$base];
|
||||
}
|
||||
function align($text, $w=25) {
|
||||
return $text.str_repeat(' ',$w-min(strlen($text),$w-1));
|
||||
}
|
||||
function gap($text) {
|
||||
return preg_replace('/([kMGTPEZY]?B)$/'," $1",$text);
|
||||
}
|
||||
function byteval($data) {
|
||||
global $unit;
|
||||
[$value,$base] = explode(' ',gap($data));
|
||||
return $value*pow(1000,array_search($base,$unit));
|
||||
}
|
||||
|
||||
exec("docker ps -sa --format='{{.Names}}|{{.Size}}'",$container);
|
||||
foreach ($container as $ct) {
|
||||
[$name,$size] = explode('|',$ct);
|
||||
[$writable,$dummy,$total] = explode(' ',str_replace(['(',')'],'',$size));
|
||||
$list[] = ['name' => $name, 'total' => byteval($total), 'writable' => byteval($writable), 'log' => (exec("docker inspect --format='{{.LogPath}}' $name|xargs du -b 2>/dev/null |cut -f1")) ?: "0"];
|
||||
}
|
||||
$sum = ['total' => 0, 'writable' => 0, 'log' => 0];
|
||||
array_multisort(array_column($list,'total'),SORT_DESC,$list); // sort on container size
|
||||
$i = 0;
|
||||
write(align(_('Name'),48).align(_('Container')).align(_('Writable'))._('Log')."\n");
|
||||
foreach ($list as $ct) {
|
||||
write(($i++==0 ? '<hr>':'').align($ct['name'],48).align(autoscale($ct['total'])).align(autoscale($ct['writable'])).autoscale($ct['log'])."\n");
|
||||
$sum['total'] += $ct['total'];
|
||||
$sum['writable'] += $ct['writable'];
|
||||
$sum['log'] += $ct['log'];
|
||||
}
|
||||
write("<hr>".align(_('Total size'),48).align(autoscale($sum['total'])).align(autoscale($sum['writable'])).autoscale($sum['log'])."\n",'_DONE_','');
|
||||
?>
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
# Just a wrapper.
|
||||
exec /usr/bin/docker "$@"
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
# Invoked after docker image loopback mounted but before docker is started
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
# delete the docker image file or folder
|
||||
if [[ -f /boot/config/docker.cfg ]]; then
|
||||
rm -f /var/local/emhttp/plugins/dynamix.docker.manager/docker.json
|
||||
. /boot/config/docker.cfg
|
||||
if [[ -f $DOCKER_IMAGE_FILE ]]; then
|
||||
echo "Deleting $DOCKER_IMAGE_FILE ..."
|
||||
rm -f "$DOCKER_IMAGE_FILE"
|
||||
elif [[ -d $DOCKER_IMAGE_FILE ]]; then
|
||||
echo "Deleting $DOCKER_IMAGE_FILE ..."
|
||||
rm -rf "$DOCKER_IMAGE_FILE"
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2015-2023, Lime Technology
|
||||
* Copyright 2015-2016, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$cfgfile = "/boot/config/docker.cfg";
|
||||
$cfg_defaults = [
|
||||
"DOCKER_ENABLED" => "no",
|
||||
"DOCKER_IMAGE_FILE" => "/mnt/user/system/docker/docker.img",
|
||||
"DOCKER_IMAGE_SIZE" => "20",
|
||||
"DOCKER_APP_CONFIG_PATH" => "/mnt/user/appdata/",
|
||||
"DOCKER_APP_UNRAID_PATH" => "",
|
||||
"DOCKER_READMORE" => "yes"
|
||||
];
|
||||
|
||||
$cfg_new = $cfg_defaults;
|
||||
if (file_exists($cfgfile)) {
|
||||
$cfg_old = parse_ini_file($cfgfile);
|
||||
if (!empty($cfg_old)) {
|
||||
$cfg_new = array_merge($cfg_defaults, $cfg_old);
|
||||
if (empty(array_diff($cfg_new, $cfg_old))) unset($cfg_new);
|
||||
}
|
||||
}
|
||||
if (isset($cfg_new)) {
|
||||
$tmp = '';
|
||||
foreach ($cfg_new as $key => $value) $tmp .= "$key=\"$value\"\n";
|
||||
file_put_contents($cfgfile, $tmp);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2014-2023, Guilherme Jardim, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
extract(parse_plugin_cfg('dynamix', true));
|
||||
|
||||
// Multi-language support
|
||||
$_SERVER['REQUEST_URI'] = "scripts";
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
exec("pgrep docker", $pid);
|
||||
if (count($pid) == 1) exit(0);
|
||||
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
|
||||
foreach ($argv as $arg) {
|
||||
switch ($arg) {
|
||||
case '-v' : $DockerTemplates->verbose = true; break;
|
||||
case 'check': $check = true; break;
|
||||
case 'nonotify': $nonotify = true; break;}
|
||||
}
|
||||
|
||||
if (!isset($check)) {
|
||||
echo " Updating templates... ";
|
||||
$DockerTemplates->downloadTemplates();
|
||||
echo " Updating info... ";
|
||||
$DockerTemplates->getAllInfo(true);
|
||||
echo " Done.";
|
||||
} else {
|
||||
$notify = "$docroot/webGui/scripts/notify";
|
||||
$var = @parse_ini_file("/var/local/emhttp/var.ini") ?: [];
|
||||
$server = strtoupper(_var($var,'NAME','tower'));
|
||||
$output = _var($notify,'docker_notify');
|
||||
$info = $DockerTemplates->getAllInfo(true);
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
$name = $ct['Name'];
|
||||
$image = $ct['Image'];
|
||||
if ($info[$name]['updated'] == "false") {
|
||||
$updateStatus = is_file($dockerManPaths['update-status']) ? json_decode(file_get_contents($dockerManPaths['update-status']),true) : [];
|
||||
$new = str_replace('sha256:', '', $updateStatus[$image]['remote']);
|
||||
$new = substr($new, 0, 4).'..'.substr($new, -4, 4);
|
||||
if ( ! isset($nonotify) ) {
|
||||
$event = str_replace("'","'",_("Docker")." - $name [$new]");
|
||||
$subject = str_replace("'","'",sprintf(_("Notice [%s] - Version update %s"),$server,$new));
|
||||
$description = str_replace("'","'",sprintf(_("A new version of %s is available"),$name));
|
||||
exec("$notify -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i ".escapeshellarg("normal $output")." -l '/Docker' -x");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
?>
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
function startsWith($haystack, $needle) {
|
||||
if (!is_string($haystack) || !is_string($needle)) return false;
|
||||
return $needle === "" || strripos($haystack, $needle, -strlen($haystack)) !== false;
|
||||
}
|
||||
|
||||
$xmlFiles = glob("/boot/config/plugins/dockerMan/templates-user/*.xml");
|
||||
|
||||
foreach ($xmlFiles as $file) {
|
||||
unset($changeFlag);
|
||||
$xml = @simplexml_load_file($file);
|
||||
if (!$xml) continue;
|
||||
|
||||
foreach ($xml->Config as $id => $config) {
|
||||
if (startsWith((string)$config->attributes()->Description,"Container ".(string)$config->attributes()->Type)) {
|
||||
$config->attributes()->Description = "";
|
||||
$changeFlag = true;
|
||||
}
|
||||
}
|
||||
if (isset($changeFlag)) {
|
||||
copy($file,"$file.bak");
|
||||
$dom = new DOMDocument('1.0');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
$dom->loadXML($xml->asXML());
|
||||
file_put_contents($file,$dom->saveXML());
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = '';
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/plugins/dynamix.docker.manager/include/DockerClient.php";
|
||||
|
||||
$var = parse_ini_file('/var/local/emhttp/var.ini');
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
$DockerTemplates = new DockerTemplates();
|
||||
|
||||
$custom = DockerUtil::custom();
|
||||
$subnet = DockerUtil::network($custom);
|
||||
$cpus = DockerUtil::cpus();
|
||||
|
||||
function write(...$messages){
|
||||
$com = curl_init();
|
||||
curl_setopt_array($com,[
|
||||
CURLOPT_URL => 'http://localhost/pub/docker?buffer_length=1',
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
]);
|
||||
foreach ($messages as $message) {
|
||||
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
|
||||
curl_exec($com);
|
||||
}
|
||||
curl_close($com);
|
||||
}
|
||||
function stopContainer_nchan($name) {
|
||||
global $DockerClient;
|
||||
$waitID = mt_rand();
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Stopping container').": ".htmlspecialchars($name)."</legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>","show_Wait\0$waitID");
|
||||
$retval = $DockerClient->stopContainer($name);
|
||||
$out = ($retval === true) ? _('Successfully stopped container').": $name" : _('Error').": ".$retval;
|
||||
write("stop_Wait\0$waitID","addLog\0<b>".htmlspecialchars($out)."</b>");
|
||||
}
|
||||
function removeContainer_nchan($name) {
|
||||
global $DockerClient;
|
||||
$waitID = mt_rand();
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Removing container').": ".htmlspecialchars($name)."</legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>","show_Wait\0$waitID");
|
||||
$retval = $DockerClient->removeContainer($name);
|
||||
$out = ($retval === true) ? _('Successfully removed container').": $name" : _('Error').": ".$retval;
|
||||
write("stop_Wait\0$waitID","addLog\0<b>".htmlspecialchars($out)."</b>");
|
||||
}
|
||||
function removeImage_nchan($image) {
|
||||
global $DockerClient;
|
||||
$waitID = mt_rand();
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Removing orphan image').": ".htmlspecialchars($image)."</legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>","show_Wait\0$waitID");
|
||||
$retval = $DockerClient->removeImage($image);
|
||||
$out = ($retval === true) ? _('Successfully removed orphan image').": $image" : _('Error').": ".$retval;
|
||||
write("stop_Wait\0$waitID","addLog\0<b>".htmlspecialchars($out)."</b>");
|
||||
}
|
||||
function pullImage_nchan($name, $image) {
|
||||
global $DockerClient, $DockerTemplates, $DockerUpdate;
|
||||
$waitID = mt_rand();
|
||||
if (!preg_match("/:\S+$/", $image)) $image .= ":latest";
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Pulling image').": ".htmlspecialchars($image)."</legend><p class='logLine'></p><span id='wait-$waitID'>"._('Please wait')." </span></fieldset>","show_Wait\0$waitID");
|
||||
$alltotals = [];
|
||||
$laststatus = [];
|
||||
$strError = '';
|
||||
$DockerClient->pullImage($image, function ($line) use (&$alltotals, &$laststatus, &$waitID, &$strError, $image, $DockerClient, $DockerUpdate) {
|
||||
$cnt = json_decode($line, true);
|
||||
$id = $cnt['id'] ?? '';
|
||||
$status = $cnt['status'] ?? '';
|
||||
if (isset($cnt['error'])) $strError = $cnt['error'];
|
||||
if ($waitID !== false) {
|
||||
write("stop_Wait\0$waitID");
|
||||
$waitID = false;
|
||||
}
|
||||
if (empty($status)) return;
|
||||
if (!empty($id)) {
|
||||
if (!empty($cnt['progressDetail']) && !empty($cnt['progressDetail']['total'])) {
|
||||
$alltotals[$id] = $cnt['progressDetail']['total'];
|
||||
}
|
||||
if (empty($laststatus[$id])) {
|
||||
$laststatus[$id] = '';
|
||||
}
|
||||
switch ($status) {
|
||||
case 'Waiting':
|
||||
// Omit
|
||||
break;
|
||||
case 'Downloading':
|
||||
if ($laststatus[$id] != $status) {
|
||||
write("addToID\0$id\0".htmlspecialchars($status));
|
||||
}
|
||||
$total = $cnt['progressDetail']['total'];
|
||||
$current = $cnt['progressDetail']['current'];
|
||||
if ($total > 0) {
|
||||
$percentage = round(($current / $total) * 100);
|
||||
write("progress\0$id\0 ".$percentage."% "._('of')." ".$DockerClient->formatBytes($total));
|
||||
} else {
|
||||
// Docker must not know the total download size (http-chunked or something?)
|
||||
// just show the current download progress without the percentage
|
||||
$alltotals[$id] = $current;
|
||||
write("progress\0$id\0".$DockerClient->formatBytes($current));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if ($laststatus[$id] == "Downloading") {
|
||||
write("progress\0$id\0 100% "._('of')." ".$DockerClient->formatBytes($alltotals[$id]));
|
||||
}
|
||||
if ($laststatus[$id] != $status) {
|
||||
write("addToID\0".($id=='latest'?mt_rand():$id)."\0".htmlspecialchars($status));
|
||||
}
|
||||
break;
|
||||
}
|
||||
$laststatus[$id] = $status;
|
||||
} else {
|
||||
if (strpos($status, 'Status: ') === 0) {
|
||||
write("addLog\0".htmlspecialchars($status));
|
||||
}
|
||||
if (strpos($status, 'Digest: ') === 0) {
|
||||
$DockerUpdate->setUpdateStatus($image, substr($status,8));
|
||||
}
|
||||
}
|
||||
});
|
||||
write("addLog\0<br><b>"._('TOTAL DATA PULLED').":</b> ".$DockerClient->formatBytes(array_sum($alltotals)));
|
||||
if (!empty($strError)) {
|
||||
write("addLog\0<br><span class='error'><b>"._('Error').":</b> ".htmlspecialchars($strError)."</span>");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function execCommand_nchan($command) {
|
||||
$waitID = mt_rand();
|
||||
[$cmd,$args] = explode(' ',$command,2);
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._('Command execution')."</legend>".basename($cmd).' '.str_replace(" -","<br> -",htmlspecialchars($args))."<br><span id='wait-$waitID'>"._('Please wait')." </span><p class='logLine'></p></fieldset>","show_Wait\0$waitID");
|
||||
$proc = popen("$command 2>&1",'r');
|
||||
while ($out = fgets($proc)) {
|
||||
$out = preg_replace("%[\t\n\x0B\f\r]+%", '',$out);
|
||||
write("addLog\0".htmlspecialchars($out));
|
||||
}
|
||||
$retval = pclose($proc);
|
||||
$out = $retval ? _('The command failed').'.' : _('The command finished successfully').'!';
|
||||
write("stop_Wait\0$waitID","addLog\0<br><b>$out</b>");
|
||||
return $retval===0;
|
||||
}
|
||||
|
||||
$style = ["<style>"];
|
||||
$style[] = ".logLine{font-family:bitstream!important;font-size:1.2rem!important;margin:0;padding:0}";
|
||||
$style[] = "fieldset.docker{border:solid thin;margin-top:8px}";
|
||||
$style[] = "legend{font-size:1.1rem!important;font-weight:bold}";
|
||||
$style[] = "</style>";
|
||||
|
||||
write(implode($style)."<p class='logLine'></p>");
|
||||
foreach (explode('*',rawurldecode($argv[1])) as $value) {
|
||||
$tmpl = $DockerTemplates->getUserTemplate($value);
|
||||
if (!$tmpl) {
|
||||
write("addLog\0"._('Configuration not found').". "._('Was this container created using this plugin')."?");
|
||||
continue;
|
||||
}
|
||||
$xml = file_get_contents($tmpl);
|
||||
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
|
||||
$Registry = getXmlVal($xml, "Registry");
|
||||
$oldImageID = $DockerClient->getImageID($Repository);
|
||||
// pull image
|
||||
if (!pullImage_nchan($Name, $Repository)) continue;
|
||||
$oldContainerInfo = $DockerClient->getContainerDetails($Name);
|
||||
// determine if the container is still running
|
||||
$startContainer = false;
|
||||
if (!empty($oldContainerInfo) && !empty($oldContainerInfo['State']) && !empty($oldContainerInfo['State']['Running'])) {
|
||||
// since container was already running, put it back it to a running state after update
|
||||
$cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
|
||||
$startContainer = true;
|
||||
// attempt graceful stop of container first
|
||||
stopContainer_nchan($Name);
|
||||
}
|
||||
// force kill container if still running after 10 seconds
|
||||
if (empty($_GET['communityApplications'])) removeContainer_nchan($Name);
|
||||
execCommand_nchan($cmd);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
$DockerClient->flushCaches();
|
||||
$newImageID = $DockerClient->getImageID($Repository);
|
||||
// remove old orphan image since it's no longer used by this container
|
||||
if ($oldImageID && $oldImageID != $newImageID) removeImage_nchan($oldImageID);
|
||||
}
|
||||
write('_DONE_','');
|
||||
?>
|
||||
@@ -0,0 +1,62 @@
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button{
|
||||
font-family:clear-sans;font-size:1.1rem;font-weight:bold;letter-spacing:2px;text-transform:uppercase;margin:10px 12px 10px 0;padding:9px 18px;text-decoration:none;white-space:nowrap;cursor:pointer;outline:none;border-radius:4px;border:0;color:#ff8c2f;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#e22828),to(#e22828)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#ff8c2f),to(#ff8c2f)) 100% 100% no-repeat;background:linear-gradient(90deg,#e22828 0,#ff8c2f) 0 0 no-repeat,linear-gradient(90deg,#e22828 0,#ff8c2f) 0 100% no-repeat,linear-gradient(0deg,#e22828 0,#e22828) 0 100% no-repeat,linear-gradient(0deg,#ff8c2f 0,#ff8c2f) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button:hover{
|
||||
color:#f2f2f2;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));background:linear-gradient(90deg,#e22828 0,#ff8c2f)
|
||||
}
|
||||
.ui-dropdownchecklist .ui-state-default{
|
||||
background:#f2f2f2;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
outline:none;
|
||||
cursor:pointer;
|
||||
height:2.2rem;
|
||||
line-height:2.2rem;
|
||||
}
|
||||
.ui-dropdownchecklist-group{
|
||||
font-weight:normal;
|
||||
font-style:italic;
|
||||
padding:1px 9px 1px 8px;
|
||||
}
|
||||
.ui-dropdownchecklist-selector{
|
||||
border:1px solid #1c1c1c;
|
||||
display:inline-block;
|
||||
cursor:pointer;
|
||||
padding:1px 9px 1px 8px;
|
||||
}
|
||||
.ui-dropdownchecklist-selector-wrapper{
|
||||
vertical-align:middle;
|
||||
font-size:0;
|
||||
}
|
||||
.ui-widget-header{
|
||||
border:none;
|
||||
background:#e3e3e3;
|
||||
color:#1c1c1c;
|
||||
font-weight:bold;
|
||||
}
|
||||
.ui-widget-content{
|
||||
border:1px solid #1c1c1c;
|
||||
background:#f2f2f2;
|
||||
color:#1c1c1c;
|
||||
|
||||
.ui-state-active{
|
||||
background:#e8e8e8;
|
||||
}
|
||||
.ui-dropdownchecklist-dropcontainer{
|
||||
background:#f2f2f2;
|
||||
border:1px solid #1c1c1c;
|
||||
}
|
||||
.ui-state-disabled{
|
||||
color:#1c1c1c;border-color:#a2a2a2;background:#e8e8e8;opacity:0.5;
|
||||
}
|
||||
.ui-dropdownchecklist-indent{
|
||||
padding-left:7px;
|
||||
}
|
||||
.ui-dropdownchecklist-text{
|
||||
color:#1c1c1c;
|
||||
font-size:1.3rem;
|
||||
}
|
||||
.ui-dropdownchecklist .ui-widget-content .ui-state-default{
|
||||
background:#f2f2f2;
|
||||
border:0px;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button{
|
||||
font-family:clear-sans;font-size:1.1rem;font-weight:bold;letter-spacing:2px;text-transform:uppercase;margin:10px 12px 10px 0;padding:9px 18px;text-decoration:none;white-space:nowrap;cursor:pointer;outline:none;border-radius:4px;border:0;color:#ff8c2f;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#e22828),to(#e22828)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#ff8c2f),to(#ff8c2f)) 100% 100% no-repeat;background:linear-gradient(90deg,#e22828 0,#ff8c2f) 0 0 no-repeat,linear-gradient(90deg,#e22828 0,#ff8c2f) 0 100% no-repeat,linear-gradient(0deg,#e22828 0,#e22828) 0 100% no-repeat,linear-gradient(0deg,#ff8c2f 0,#ff8c2f) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button:hover{
|
||||
color:#f2f2f2;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));background:linear-gradient(90deg,#e22828 0,#ff8c2f)
|
||||
}
|
||||
.ui-dropdownchecklist .ui-state-default{
|
||||
background:#1c1c1c;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
outline:none;
|
||||
cursor:pointer;
|
||||
height:2.2rem;
|
||||
line-height:2.2rem;
|
||||
}
|
||||
.ui-dropdownchecklist-group{
|
||||
font-weight:normal;
|
||||
font-style:italic;
|
||||
padding:1px 9px 1px 8px;
|
||||
}
|
||||
.ui-dropdownchecklist-selector{
|
||||
border:1px solid #e5e5e5;
|
||||
display:inline-block;
|
||||
cursor:pointer;
|
||||
padding:1px 9px 1px 8px;
|
||||
}
|
||||
.ui-dropdownchecklist-selector-wrapper{
|
||||
vertical-align:middle;
|
||||
font-size:0;
|
||||
}
|
||||
.ui-widget-header{
|
||||
border:none;
|
||||
background:#2b2b2b;
|
||||
color:#f2f2f2;
|
||||
font-weight:bold;
|
||||
}
|
||||
.ui-widget-content{
|
||||
border:1px solid #e5e5e5;
|
||||
background:#1c1c1c;
|
||||
color:#f2f2f2;
|
||||
|
||||
.ui-state-active{
|
||||
background:#262626;
|
||||
}
|
||||
.ui-dropdownchecklist-dropcontainer{
|
||||
background:#1c1c1c;
|
||||
border:1px solid #e5e5e5;
|
||||
}
|
||||
.ui-state-disabled{
|
||||
color:#f2f2f2;border-color:#6c6c6c;background:#262626;opacity:0.5;
|
||||
}
|
||||
.ui-dropdownchecklist-indent{
|
||||
padding-left:7px;
|
||||
}
|
||||
.ui-dropdownchecklist-text{
|
||||
color:#f2f2f2;
|
||||
font-size:1.3rem;
|
||||
}
|
||||
.ui-dropdownchecklist .ui-widget-content .ui-state-default{
|
||||
background:#1c1c1c;
|
||||
border:0px;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button{
|
||||
font-family:clear-sans;font-size:1.1rem;font-weight:bold;letter-spacing:2px;text-transform:uppercase;margin:10px 12px 10px 0;padding:9px 18px;text-decoration:none;white-space:nowrap;cursor:pointer;outline:none;border-radius:4px;border:0;color:#ff8c2f;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#e22828),to(#e22828)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#ff8c2f),to(#ff8c2f)) 100% 100% no-repeat;background:linear-gradient(90deg,#e22828 0,#ff8c2f) 0 0 no-repeat,linear-gradient(90deg,#e22828 0,#ff8c2f) 0 100% no-repeat,linear-gradient(0deg,#e22828 0,#e22828) 0 100% no-repeat,linear-gradient(0deg,#ff8c2f 0,#ff8c2f) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button:hover{
|
||||
color:#f2f2f2;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));background:linear-gradient(90deg,#e22828 0,#ff8c2f)
|
||||
}
|
||||
.ui-dropdownchecklist .ui-state-default{
|
||||
background:#1c1c1c;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
outline:none;
|
||||
cursor:pointer;
|
||||
height:2.2rem;
|
||||
line-height:2.2rem;
|
||||
}
|
||||
.ui-dropdownchecklist-group{
|
||||
font-weight:normal;
|
||||
font-style:italic;
|
||||
padding:1px 9px 1px 8px;
|
||||
}
|
||||
.ui-dropdownchecklist-selector{
|
||||
border:1px solid #e5e5e5;
|
||||
display:inline-block;
|
||||
cursor:pointer;
|
||||
padding:1px 9px 1px 8px;
|
||||
}
|
||||
.ui-dropdownchecklist-selector-wrapper{
|
||||
vertical-align:middle;
|
||||
font-size:0;
|
||||
}
|
||||
.ui-widget-header{
|
||||
border:none;
|
||||
background:#2b2b2b;
|
||||
color:#f2f2f2;
|
||||
font-weight:bold;
|
||||
}
|
||||
.ui-widget-content{
|
||||
border:1px solid #e5e5e5;
|
||||
background:#1c1c1c;
|
||||
color:#f2f2f2;
|
||||
|
||||
.ui-state-active{
|
||||
background:#262626;
|
||||
}
|
||||
.ui-dropdownchecklist-dropcontainer{
|
||||
background:#1c1c1c;
|
||||
border:1px solid #e5e5e5;
|
||||
}
|
||||
.ui-state-disabled{
|
||||
color:#f2f2f2;border-color:#6c6c6c;background:#262626;opacity:0.5;
|
||||
}
|
||||
.ui-dropdownchecklist-indent{
|
||||
padding-left:7px;
|
||||
}
|
||||
.ui-dropdownchecklist-text{
|
||||
color:#f2f2f2;
|
||||
font-size:1.3rem;
|
||||
}
|
||||
.ui-dropdownchecklist .ui-widget-content .ui-state-default{
|
||||
background:#1c1c1c;
|
||||
border:0px;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button{
|
||||
font-family:clear-sans;font-size:1.1rem;font-weight:bold;letter-spacing:2px;text-transform:uppercase;margin:10px 12px 10px 0;padding:9px 18px;text-decoration:none;white-space:nowrap;cursor:pointer;outline:none;border-radius:4px;border:0;color:#ff8c2f;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#e22828),to(#e22828)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#ff8c2f),to(#ff8c2f)) 100% 100% no-repeat;background:linear-gradient(90deg,#e22828 0,#ff8c2f) 0 0 no-repeat,linear-gradient(90deg,#e22828 0,#ff8c2f) 0 100% no-repeat,linear-gradient(0deg,#e22828 0,#e22828) 0 100% no-repeat,linear-gradient(0deg,#ff8c2f 0,#ff8c2f) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%
|
||||
}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button:hover{
|
||||
color:#f2f2f2;background:-webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));background:linear-gradient(90deg,#e22828 0,#ff8c2f)
|
||||
}
|
||||
.ui-dropdownchecklist .ui-state-default{
|
||||
background:#f2f2f2;
|
||||
border:none;
|
||||
box-shadow:none;
|
||||
outline:none;
|
||||
cursor:pointer;
|
||||
height:2.2rem;
|
||||
line-height:2.2rem;
|
||||
}
|
||||
.ui-dropdownchecklist-group{
|
||||
font-weight:normal;
|
||||
font-style:italic;
|
||||
padding:1px 9px 1px 8px;
|
||||
}
|
||||
.ui-dropdownchecklist-selector{
|
||||
border:1px solid #1c1c1c;
|
||||
display:inline-block;
|
||||
cursor:pointer;
|
||||
padding:1px 9px 1px 8px;
|
||||
}
|
||||
.ui-dropdownchecklist-selector-wrapper{
|
||||
vertical-align:middle;
|
||||
font-size:0;
|
||||
}
|
||||
.ui-widget-header{
|
||||
border:none;
|
||||
background:#e3e3e3;
|
||||
color:#1c1c1c;
|
||||
font-weight:bold;
|
||||
}
|
||||
.ui-widget-content{
|
||||
border:1px solid #1c1c1c;
|
||||
background:#f2f2f2;
|
||||
color:#1c1c1c;
|
||||
|
||||
.ui-state-active{
|
||||
background:#e8e8e8;
|
||||
}
|
||||
.ui-dropdownchecklist-dropcontainer{
|
||||
background:#f2f2f2;
|
||||
border:1px solid #1c1c1c;
|
||||
}
|
||||
.ui-state-disabled{
|
||||
color:#1c1c1c;border-color:#a2a2a2;background:#e8e8e8;opacity:0.5;
|
||||
}
|
||||
.ui-dropdownchecklist-indent{
|
||||
padding-left:7px;
|
||||
}
|
||||
.ui-dropdownchecklist-text{
|
||||
color:#1c1c1c;
|
||||
font-size:1.3rem;
|
||||
}
|
||||
.ui-dropdownchecklist .ui-widget-content .ui-state-default{
|
||||
background:#f2f2f2;
|
||||
border:0px;
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
Menu="About"
|
||||
Type="xmenu"
|
||||
Title="Registration"
|
||||
Icon="icon-registration"
|
||||
Tag="pencil"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
function my_time_any($time) {
|
||||
return $time ? _(my_time($time),0) : _('Anytime');
|
||||
}
|
||||
function my_time_now($time) {
|
||||
return $time ? _(my_time($time),0) : _('Unknown');
|
||||
}
|
||||
?>
|
||||
<style>
|
||||
span.thanks{padding-left:12px;color:#6FA239;font-weight:bold;}
|
||||
span.thanks.red{color:#F0000C;}
|
||||
div.device{padding:0 12px;font-weight:normal;font-style:italic;}
|
||||
div.remark{padding:0 12px;text-align:justify;}
|
||||
</style>
|
||||
|
||||
<?if ( (strstr($var['regTy'], "unregistered")) or ($var['regTy']=="Trial") or (strstr($var['regTy'], "no connection")) or (strstr($var['regTy'], "withdrawn")) or (strstr($var['regTy'], "expired")) ):?>
|
||||
|
||||
<span class="thanks">_(Thank you for trying Unraid OS)_!</span>
|
||||
|
||||
<?elseif ( ($var['regTy']=="Basic") or ($var['regTy']=="Plus") or ($var['regTy']=="Pro") ):?>
|
||||
|
||||
<span class="thanks">_(Thank you for choosing Unraid OS)_!</span>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "unregistered")):?>
|
||||
|
||||
<div markdown="1" class="remark">
|
||||
:registration_1_plug:
|
||||
Your server will not be usable until you purchase a Registration key or install a free 30-day *Trial* key. A *Trial*
|
||||
key provides all the functionality of a *Pro* Registration key.
|
||||
|
||||
Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device
|
||||
at least 1GB in size (min 4GB recommended).
|
||||
|
||||
Note: USB memory card readers are generally **not** supported because most do not present unique serial numbers.
|
||||
:end
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ($var['regTy']=="Trial"):?>
|
||||
|
||||
<div markdown="1" class="remark">
|
||||
:registration_3_plug:
|
||||
Your *Trial* key includes all the functionality and device support of a *Pro* Registration key.
|
||||
|
||||
After your *Trial* key has reached expiration, your server **still functions normally** until the next time you **Stop** the array.
|
||||
|
||||
At that point, you may either purchase a Registration key, or request a *Trial* extension.
|
||||
:end
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "no connection")):?>
|
||||
|
||||
<div markdown="1" class="remark">
|
||||
<span class='red-text'>_(Cannot connect to key-server)_!</span>
|
||||
|
||||
_(Your *Trial* key requires an internet connection)_. _(Please check your)_ [_(Network Settings)_](NetworkSettings).
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "withdrawn")):?>
|
||||
|
||||
<div markdown="1" class="remark">
|
||||
<span class='red-text'>_(Release has been withdrawn)_!</span>
|
||||
|
||||
_(This release has been withdrawn for use with *Trial* keys)_.
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "expired")):?>
|
||||
|
||||
<div markdown="1" class="remark">
|
||||
<span class='red-text'>_(Your *Trial* key has expired)_.</span>
|
||||
:registration_4_plug:
|
||||
To continue using Unraid OS you may purchase a Registration key. Alternately, you may request a *Trial* extension key.
|
||||
|
||||
Most *Trial* extension requests are processed immediately but please allow up to one business day to receive your *Trial* extension key.
|
||||
:end
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "invalid installation")):?>
|
||||
|
||||
<span class='thanks red'>_(Invalid *Trial* Installation)_</span>
|
||||
<div markdown="1" class="remark">
|
||||
:registration_5_plug:
|
||||
It is not possible to use a *Trial* key with an existing Unraid OS installation.
|
||||
|
||||
You may purchase a Registration key corresponding to this USB Flash device to continue using this installation.
|
||||
|
||||
For more information, please [Contact Support](https://lime-technology.com/contact).
|
||||
:end
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "missing")):?>
|
||||
|
||||
<span class='thanks red'>_(Missing Key File)_</span>
|
||||
<div markdown="1" class="remark">
|
||||
:registration_6_plug:
|
||||
It appears that your Registration key file is corrupted or missing. The key file should be located in the
|
||||
[config](/Registration/Browse?dir=/boot/config) directory on your USB Flash boot device.
|
||||
|
||||
If you do not have a backup copy of your Registration key file, [Contact Support](https://lime-technology.com/contact).
|
||||
|
||||
If this was a *Trial* installation, you may purchase a Registration key.
|
||||
:end
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "invalid key")):?>
|
||||
|
||||
<span class='thanks red'>_(The registered GUID does not match the USB Flash boot device GUID)_</span>
|
||||
|
||||
<?if (strstr($var['regTy'], "Trial")):?>
|
||||
|
||||
<div markdown="1" class="remark">
|
||||
:registration_7_plug:
|
||||
*Trial* installations are only valid with the originally registered USB Flash device.
|
||||
|
||||
To continue using this installation with this USB Flash device, you may purchase a Registration key.
|
||||
:end
|
||||
</div>
|
||||
|
||||
<?else:?>
|
||||
|
||||
<div markdown="1" class="remark">
|
||||
:registration_8_plug:
|
||||
The Registration key file does not correspond to the USB Flash boot device.
|
||||
Please copy the correct key file to the [config](/Registration/Browse?dir=/boot/config) directory
|
||||
on your USB Flash boot device. If you do not have a backup copy of your key file, [Contact Support](https://lime-technology.com/contact).
|
||||
|
||||
If you want to replace your Registration key with a new key bound to this USB Flash device, click Replace Key below. An original key may be
|
||||
replaced anytime. Thereafter, a replacement key may be replaced again after one year has passed. If you require
|
||||
another replacement key sooner, [Contact Support](https://lime-technology.com/contact).
|
||||
|
||||
**Note:** Replacing a Registration key results in permanently *blacklisting* the previous USB Flash GUID.
|
||||
:end
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "blacklisted")):?>
|
||||
|
||||
<span class='thanks red'>_(Blacklisted USB Flash GUID)_</span>
|
||||
<div markdown="1" class="remark">
|
||||
:registration_9_plug:
|
||||
This USB Flash boot device has been *blacklisted*. This can occur as a result of transfering your Registration key to
|
||||
a replacement USB Flash device, and you are currently booted from your old USB Flash device.
|
||||
|
||||
A USB Flash device may also be *blacklisted* if there is no serial number, or if we discover the serial number
|
||||
is not unique (this is common with USB card readers).
|
||||
|
||||
For more information, please [Contact Support](https://lime-technology.com/contact).
|
||||
:end
|
||||
</div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ( ( !(strstr($var['regTy'], "invalid key")) and ((strstr($var['regTy'], "Trial"))) ) || (strstr($var['regTy'], "no connection")) || (strstr($var['regTy'], "withdrawn")) ):?>
|
||||
|
||||
_(***Trial*** key expires on)_:
|
||||
: <?=my_time_now($var['regTm2'])?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ( strstr($var['regTy'], "invalid installation") || ( (strstr($var['regTy'], "invalid key")) && (strstr($var['regTy'], "Trial")) )):?>
|
||||
|
||||
_(Expiration)_:
|
||||
: <?=my_time_now($var['regTm2'])?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ( (strstr($var['regTy'], "invalid installation")) || (strstr($var['regTy'], "invalid key")) || ($var['regTy']=="Basic") || ($var['regTy']=="Plus") || ($var['regTy']=="Pro") ):?>
|
||||
|
||||
_(Registered to)_:
|
||||
: <?=htmlspecialchars($var['regTo'])?>
|
||||
|
||||
_(Registered on)_:
|
||||
: <?=my_time_now($var['regTm'])?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ( (strstr($var['regTy'], "invalid installation")) or ( (strstr($var['regTy'], "invalid key")) and (!(strstr($var['regTy'], "Trial")))) ):?>
|
||||
|
||||
_(Registered GUID)_:
|
||||
: <?=$var['regGUID']?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (strstr($var['regTy'], "flash device error")):?>
|
||||
|
||||
<span class='thanks red'>_(Error accessing your physical USB Flash boot device)_</span>
|
||||
<div markdown="1" class="remark">
|
||||
_(There is a physical problem accessing your USB Flash boot device)_. _(Please)_ [Contact Support](https://lime-technology.com/contact).
|
||||
|
||||
_(Flash GUID)_:
|
||||
: _(Error code)_: <?=$var['regCheck']?>
|
||||
|
||||
<?else:?>
|
||||
|
||||
_(Flash GUID)_:
|
||||
: <?=$var['flashGUID']?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
_(Flash Vendor)_:
|
||||
: <?=$var['flashVendor']?>
|
||||
|
||||
_(Flash Product)_:
|
||||
: <?=$var['flashProduct']?>
|
||||
|
||||
<?if ( ((strstr($var['regTy'], "invalid key")) and !(strstr($var['regTy'], "Trial"))) || ($var['regTy']=="Basic") || ($var['regTy']=="Plus") || ($var['regTy']=="Pro") ):?>
|
||||
|
||||
_(Replaceable)_:
|
||||
: <?=my_time_any($var['regTm2'])?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ( !(strstr($var['regTy'], "flash device error")) || !(strstr($var['regTy'], "blacklisted")) ):?>
|
||||
|
||||
<div class="device"><?=sprintf(_("This server has %s attached storage device".($var['deviceCount']==1?'.':'s.')),$var['deviceCount'])?></div>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<unraid-upc-trigger></unraid-upc-trigger>
|
||||
@@ -0,0 +1,129 @@
|
||||
<!-- myservers1 -->
|
||||
<style>
|
||||
#header {
|
||||
z-index: 102 !important;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
vue-userprofile,
|
||||
unraid-user-profile {
|
||||
font-size: 16px;
|
||||
margin-left: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
unraid-launchpad,
|
||||
unraid-promo {
|
||||
position: relative;
|
||||
z-index: 10001;
|
||||
}
|
||||
</style>
|
||||
<?
|
||||
$myservers_flash_cfg_path='/boot/config/plugins/dynamix.my.servers/myservers.cfg';
|
||||
$myservers = file_exists($myservers_flash_cfg_path) ? @parse_ini_file($myservers_flash_cfg_path,true) : [];
|
||||
|
||||
$ALLOWED_UPC_ENV_VALS = [
|
||||
'production',
|
||||
'staging',
|
||||
'stagingLogs',
|
||||
'development',
|
||||
'local',
|
||||
'preview',
|
||||
];
|
||||
$ALLOWED_UPC_ENV_PREVIEW_CNAME = '.d1eohvtyc6gnee.amplifyapp.com/';
|
||||
|
||||
// defaults
|
||||
$computedCookieValue = $_COOKIE['UPC_ENV'] ?? '';
|
||||
$previewUrl = '';
|
||||
$isPreview = strpos($computedCookieValue, 'preview::');
|
||||
// extract preview src url
|
||||
if ($isPreview !== false) {
|
||||
list($computedCookieValue, $previewUrl) = explode('::', $computedCookieValue);
|
||||
// prevent unauthoraized URLs for previews
|
||||
$isPreviewAllowed = strpos($previewUrl, $ALLOWED_UPC_ENV_PREVIEW_CNAME);
|
||||
if (!$isPreviewAllowed) {
|
||||
$computedCookieValue = '';
|
||||
$previewUrl = '';
|
||||
}
|
||||
}
|
||||
// finalize cookie value
|
||||
$UPC_ENV_CK = in_array($computedCookieValue, $ALLOWED_UPC_ENV_VALS)
|
||||
? $computedCookieValue
|
||||
: null;
|
||||
// Determine what source we should use for web components
|
||||
if (!file_exists('/usr/local/sbin/unraid-api')) { // When NOT using the plugin we should load the UPC from the file system unless $UPC_ENV_CK exists.
|
||||
$UPC_ENV = $UPC_ENV_CK ?? 'local';
|
||||
} else {
|
||||
$UPC_ENV = $UPC_ENV_CK ?? 'production';
|
||||
}
|
||||
$upcLocalSrc = autov('/plugins/dynamix.my.servers/webComps/unraid.min.js', true);
|
||||
switch ($UPC_ENV) {
|
||||
case 'production':
|
||||
$upcSrc = 'https://registration.unraid.net/webComps/unraid.min.js';
|
||||
break;
|
||||
case 'staging':
|
||||
$upcSrc = 'https://registration-dev.unraid.net/webComps/unraid.min.js';
|
||||
break;
|
||||
case 'stagingLogs':
|
||||
$upcSrc = 'https://registration-dev-logs.unraid.net/webComps/unraid.min.js';
|
||||
break;
|
||||
case 'development':
|
||||
$upcSrc = 'https://launchpad.unraid.test:6969/webComps/unraid.js?t=' . time();
|
||||
break;
|
||||
case 'preview':
|
||||
$upcSrc = $previewUrl . 'webComps/unraid.min.js';
|
||||
break;
|
||||
default: // load from webGUI filesystem.
|
||||
$upcSrc = $upcLocalSrc;
|
||||
break;
|
||||
}
|
||||
// add the intended web component source to the DOM
|
||||
echo '<script id="unraid-wc" defer src="' . $upcSrc . '"></script>';
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
const upcEnvCookie = "<?=$UPC_ENV_CK??''?>";
|
||||
if (upcEnvCookie) console.debug('[UPC_ENV] ✨', upcEnvCookie);
|
||||
|
||||
// If the UPC isn't defined after 3secs inject UPC via
|
||||
setTimeout(() => {
|
||||
// UPC exists do nothing
|
||||
if (window.customElements.get('unraid-user-profile')) return;
|
||||
|
||||
console.log('[UPC] Fallback to filesystem src 😖');
|
||||
const el = document.createElement('script');
|
||||
el.type = 'text/javascript';
|
||||
el.src = '<?=$upcLocalSrc?>';
|
||||
document.head.appendChild(el);
|
||||
return upcEnv('local', false, true); // set session cookie to prevent delayed loads of UPC
|
||||
}, 3000);
|
||||
|
||||
function upcEnv(str, reload = true, session = false) { // overwrite upc src
|
||||
const ckName = 'UPC_ENV';
|
||||
const ckDate = new Date();
|
||||
const ckDays = 30;
|
||||
ckDate.setTime(ckDate.getTime()+(ckDays*24*60*60*1000));
|
||||
const ckExpire = `expires=${session ? 0 : ckDate.toGMTString()};`;
|
||||
if (!str) { // if no str param provided we remove the cookie to fallback to the enviroment's default JS source
|
||||
console.log(`✨ ${ckName} removed…reloading ♻️ `);
|
||||
document.cookie = `${ckName}=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT`;
|
||||
return window.location.reload();
|
||||
}
|
||||
if (reload) {
|
||||
console.log(`✨ ${ckName} set…reloading ✨ `);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
console.log(`✨ ${ckName}=${str} for session ✨ `);
|
||||
}
|
||||
return document.cookie = `${ckName}=${str}; path=/; ${ckExpire}`;
|
||||
};
|
||||
</script>
|
||||
<!-- /myservers1 -->
|
||||
@@ -0,0 +1,648 @@
|
||||
<!-- myservers2 -->
|
||||
<?
|
||||
// add 'ipaddr' function for 6.9 backwards compatibility
|
||||
if (!function_exists('ipaddr')) {
|
||||
function ipaddr($ethX='eth0', $prot=4) {
|
||||
global $$ethX;
|
||||
switch ($$ethX['PROTOCOL:0']) {
|
||||
case 'ipv4':
|
||||
return $$ethX['IPADDR:0'];
|
||||
case 'ipv6':
|
||||
return $$ethX['IPADDR6:0'];
|
||||
case 'ipv4+ipv6':
|
||||
switch ($prot) {
|
||||
case 4: return $$ethX['IPADDR:0'];
|
||||
case 6: return $$ethX['IPADDR6:0'];
|
||||
default:return [$$ethX['IPADDR:0'],$$ethX['IPADDR6:0']];}
|
||||
default:
|
||||
return $$ethX['IPADDR:0'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$upc_translations = [
|
||||
($_SESSION['locale']) ? $_SESSION['locale'] : 'en_US' => [
|
||||
'getStarted' => _('Get Started'),
|
||||
'signIn' => _('Sign In'),
|
||||
'signUp' => _('Sign Up'),
|
||||
'signInUp' => _('Sign In / Up'),
|
||||
'signInUnraidNetAccount' => _('Sign In with Unraid.net Account'),
|
||||
'signOut' => _('Sign Out'),
|
||||
'error' => _('Error'),
|
||||
'fixError' => _('Fix Error'),
|
||||
'closeLaunchpad' => _('Close Launchpad and continue to webGUI'),
|
||||
'installPlugin' => _('Install Plugin'),
|
||||
'noThanks' => _('No thanks'),
|
||||
'closePromo' => _('Close Connect details and continue to webGUI'),
|
||||
'promoHeading' => _('Enhance your Unraid experience with these<br> Connect (BETA) features'),
|
||||
'learnMore' => _('Learn more'),
|
||||
'checkoutTheMyServersDocs' => _('Checkout the Connect docs'),
|
||||
'popUp' => _('Pop-up'),
|
||||
'close' => _('Close'),
|
||||
'backToPopUp' => sprintf(_('Back to %s'), _('Pop-up')),
|
||||
'closePopUp' => sprintf(_('Close %s'), _('Pop-up')),
|
||||
'contactSupport' => _('Contact Support'),
|
||||
'lanIp' => sprintf(_('Click to copy LAN IP %s'), '{0}'),
|
||||
'lanIpCopied' => _('LAN IP Copied'),
|
||||
'continueToUnraid' => _('Continue to Unraid'),
|
||||
'description' => _('Description'),
|
||||
'year' => _('year'),
|
||||
'years' => _('years'),
|
||||
'month' => _('month'),
|
||||
'months' => _('months'),
|
||||
'day' => _('day'),
|
||||
'days' => _('days'),
|
||||
'hour' => _('hour'),
|
||||
'hours' => _('hours'),
|
||||
'minute' => _('minute'),
|
||||
'minutes' => _('minutes'),
|
||||
'second' => _('second'),
|
||||
'seconds' => _('seconds'),
|
||||
'ago' => _('ago'),
|
||||
'basicPlusPro' => [
|
||||
'heading' => _('Thank you for choosing Unraid OS and Connect').'!',
|
||||
'message' => [
|
||||
'registered' => _('Register for Connect by signing in to Unraid.net'),
|
||||
'upgradeEligible' => _('To support more storage devices as your server grows click Upgrade Key'),
|
||||
],
|
||||
],
|
||||
'actions' => [
|
||||
'purchase' => _('Purchase Key'),
|
||||
'upgrade' => _('Upgrade Key'),
|
||||
'recover' => _('Recover Key'),
|
||||
'replace' => _('Replace Key'),
|
||||
'replaceIneligible' => _('Replace Key Ineligible'),
|
||||
'extend' => _('Extend Trial'),
|
||||
'startTrial' => _('Start Trial'),
|
||||
'signOutUnraidNet' => _('Sign Out of Unraid.net'),
|
||||
'redeemActivationCode' => _('Redeem Activation Code'),
|
||||
],
|
||||
'upc' => [
|
||||
'avatarAlt' => '{0} '._('Avatar'),
|
||||
'confirmClosure' => _('Confirm closure then continue to webGUI'),
|
||||
'closeDropdown' => _('Close dropdown'),
|
||||
'openDropdown' => _('Open dropdown'),
|
||||
'pleaseConfirmClosureYouHaveOpenPopUp' => _('Please confirm closure').'. '._('You have an open pop-up').'.',
|
||||
'trialHasExpiredSeeOptions' => _('Trial has expired see options below'),
|
||||
'errorCertRequiresSignIn' => _('Sign In before your Unraid.net SSL certificate expires'),
|
||||
'removeMyServersPlugin' => _('Remove Connect plugin'),
|
||||
'continueUsingMyServers' => _('Continue using Connect'),
|
||||
'confirmMyServersPluginRemoval' => _('Confirm Connect plugin removal'),
|
||||
'removingMyServersPlugin' => _('Removing Connect plugin…'),
|
||||
'enhanceYourExperienceWithMyServers' => _('Enhance your experience with Connect'),
|
||||
'lanIpCopied' => _('LAN IP Copied'),
|
||||
'installingMyServers' => _('Installing Connect (beta)'),
|
||||
"updatePlugin" => _('Update Plugin'),
|
||||
"updatingMyServers" => _('Updating Connect (beta)'),
|
||||
'thankYouForInstallingMyServers' => _('Thank you installing Connect') . '!',
|
||||
'connectYourUnraidnetAccountToGetStarted' => _('Connect your Unraid.net account to get started'),
|
||||
'noRemoteApikeyRegisteredWithPlg' => [
|
||||
'heading' => _('Connect Error'),
|
||||
'msg' => _('Unraid.net re-authentication required'),
|
||||
],
|
||||
'errorTooManyDisks' => [
|
||||
'heading' => 'Too many devices',
|
||||
'msg' => [
|
||||
'base' => 'You must upgrade your key to support more devices.',
|
||||
'basic' => 'Your Basic key supports 6 devices.',
|
||||
'plus' => 'Your Plus key supports 12 devices.',
|
||||
],
|
||||
],
|
||||
'extraLinks' => [
|
||||
'newTab' => sprintf(_('Opens %s in new tab'), '{0}'),
|
||||
'myServers' => _('Go to Connect'),
|
||||
'forums' => _('Unraid Forums'),
|
||||
'settings' => [
|
||||
'text' => _('Settings'),
|
||||
'title' => _('Settings > Management Access • Unraid.net'),
|
||||
],
|
||||
],
|
||||
'meta' => [
|
||||
'trial' => [
|
||||
'active' => [
|
||||
'date' => sprintf(_('Trial key expires at %s'), '{date}'),
|
||||
'timeDiff' => sprintf(_('Trial expires in %s'), '{timeDiff}'),
|
||||
],
|
||||
'expired' => [
|
||||
'date' => sprintf(_('Trial key expired at %s'), '{date}'),
|
||||
'timeDiff' => sprintf(_('Trial expired %s'), '{timeDiff}'),
|
||||
],
|
||||
],
|
||||
'uptime' => [
|
||||
'date' => sprintf(_('Server up since %s'), '{date}'),
|
||||
'readable' => sprintf(_('Uptime %s'), '{timeDiff}'),
|
||||
],
|
||||
],
|
||||
'myServers' => [
|
||||
'heading' => _('Connect'),
|
||||
'beta' => _('beta'),
|
||||
'restarting' => _('Restarting…'),
|
||||
'errors' => [
|
||||
'unraidApi' => [
|
||||
'heading' => _('Unraid API Error'),
|
||||
'message' => _('Failed to connect to Unraid API'),
|
||||
],
|
||||
'myServers' => [
|
||||
'heading' => _('Connect Error'),
|
||||
'message' => _('Please wait a moment and reload the page'),
|
||||
],
|
||||
],
|
||||
'closeDetails' => _('Close Details'),
|
||||
'loading' => _('Loading Connect data'),
|
||||
'displayingLastKnown' => _('Displaying last known server data'),
|
||||
'mothership' => [
|
||||
'connected' => _('Connected'),
|
||||
'notConnected' => _('Disconnected'),
|
||||
],
|
||||
'accessLabels' => [
|
||||
'current' => _('Current server'),
|
||||
'local' => _('Local access'),
|
||||
'offline' => _('Server Offline'),
|
||||
'remote' => _('Remote access'),
|
||||
'unavailable' => _('Access unavailable'),
|
||||
],
|
||||
'api' => [
|
||||
'start' => _('Restart unraid-api'),
|
||||
'startTitle' => _('Executes `unraid-api start`; no terminal needed'),
|
||||
'stop' => _('Stop unraid-api'),
|
||||
],
|
||||
],
|
||||
'opensNewHttpsWindow' => [
|
||||
'base' => sprintf(_('Opens new HTTPS window to %s'), '{0}'),
|
||||
'signIn' => sprintf(_('Opens new HTTPS window to %s'), _('Sign In')),
|
||||
'signOut' => sprintf(_('Opens new HTTPS window to %s'), _('Sign Out')),
|
||||
'purchase' => sprintf(_('Opens new HTTPS window to %s'), _('Purchase Key')),
|
||||
'upgrade' => sprintf(_('Opens new HTTPS window to %s'), _('Upgrade Key')),
|
||||
],
|
||||
'signInActions' => [
|
||||
'resolve' => _('Sign In to resolve'),
|
||||
'purchaseKey' => _('Sign In to Purchase Key'),
|
||||
'purchaseKeyOrExtendTrial' => '@:upc.signInActions.purchaseKey or @:actions.extend',
|
||||
],
|
||||
],
|
||||
'stateData' => [
|
||||
'ENOKEYFILE' => [
|
||||
'humanReadable' => _('No Keyfile'),
|
||||
'heading' => _("Let's unleash your hardware").'!',
|
||||
'message' => '<p>'._('Your server will not be usable until you purchase a Registration key or install a free 30-day Trial key').'. '._('A Trial key provides all the functionality of a Pro Registration key').'.</p><p>'._('Registration keys are bound to your USB Flash boot device serial number (GUID)').'. '._('Please use a high quality name brand device at least 1GB in size (min 4GB recommended)').'.</p><p>'._('Note: USB memory card readers are generally not supported because most do not present unique serial numbers').'.</p>',
|
||||
],
|
||||
'TRIAL' => [
|
||||
'humanReadable' => _('Trial'),
|
||||
'heading' => _('Thank you for choosing Unraid OS').'!',
|
||||
'message' => _('Your Trial key includes all the functionality and device support of a Pro key').'. '._('After your Trial has reached expiration your server still functions normally until the next time you Stop the array or reboot your server').'. '._('At that point you may either purchase a license key or request a Trial extension').'.',
|
||||
'_extraMsg' => sprintf(_('You have %s remaining on your Trial key'), '{parsedExpireTime}'),
|
||||
],
|
||||
'EEXPIRED' => [
|
||||
'humanReadable' => _('Trial Expired'),
|
||||
'heading' => _('Your Trial has expired'),
|
||||
'message' => [
|
||||
'base' => _('To continue using Unraid OS you may purchase a license key').'. ',
|
||||
'extensionNotEligible' => _('You have used all your Trial extensions').'. @:stateData.EEXPIRED.message.base',
|
||||
'extensionEligible' => '@:stateData.EEXPIRED.message.base '._('Alternately, you may request a Trial extension').'.',
|
||||
],
|
||||
],
|
||||
'BASIC' => [
|
||||
'humanReadable' => _('Basic'),
|
||||
],
|
||||
'PLUS' => [
|
||||
'humanReadable' => _('Plus'),
|
||||
],
|
||||
'PRO' => [
|
||||
'humanReadable' => _('Pro'),
|
||||
],
|
||||
'EGUID' => [
|
||||
'humanReadable' => _('GUID Error'),
|
||||
'error' => [
|
||||
'heading' => _('Registration key / GUID mismatch'),
|
||||
'message' => [
|
||||
'default' => _('The license key file does not correspond to the USB Flash boot device').'. '._('Please copy the correct key file to the */config* directory on your USB Flash boot device or choose Purchase Key').'.',
|
||||
'replacementIneligible' => _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months').'.',
|
||||
'replacementEligible' => _('The license key file does not correspond to the USB Flash boot device').'. '._('Please copy the correct key file to the */config* directory on your USB Flash boot device or choose Purchase Key or Replace Key').'.',
|
||||
'blacklisted' => _('Your Unraid registration key is ineligible for replacement as it is blacklisted') . '.',
|
||||
],
|
||||
],
|
||||
],
|
||||
'ENOKEYFILE2' => [
|
||||
'humanReadable' => _('Missing key file'),
|
||||
'error' => [
|
||||
'heading' => '@:stateData.ENOKEYFILE2.humanReadable',
|
||||
'message' => _('It appears that your license key file is corrupted or missing').'. '._('The key file should be located in the */config* directory on your USB Flash boot device').'. '._('If you do not have a backup copy of your license key file you may install the Connect (beta) plugin to attempt to recover your key').'. '._('If this was an expired Trial installation, you may purchase a license key').'.',
|
||||
],
|
||||
],
|
||||
'ETRIAL' => [
|
||||
'humanReadable' => _('Invalid installation'),
|
||||
'error' => [
|
||||
'heading' => '@:stateData.ETRIAL.humanReadable',
|
||||
'message' => _('It is not possible to use a Trial key with an existing Unraid OS installation').'. '._('You may purchase a license key corresponding to this USB Flash device to continue using this installation').'.',
|
||||
],
|
||||
],
|
||||
'ENOKEYFILE1' => [
|
||||
'humanReadable' => _('No Keyfile'),
|
||||
'error' => [
|
||||
'heading' => _('No USB flash configuration data'),
|
||||
'message' => _('There is a problem with your USB Flash device'),
|
||||
],
|
||||
],
|
||||
'ENOFLASH' => [
|
||||
'humanReadable' => _('No Flash'),
|
||||
'error' => [
|
||||
'heading' => _('Cannot access your USB Flash boot device'),
|
||||
'message' => _('There is a physical problem accessing your USB Flash boot device'),
|
||||
],
|
||||
],
|
||||
'EGUID1' => [
|
||||
'humanReadable' => _('Multiple License Keys Present'),
|
||||
'error' => [
|
||||
'heading' => '@:stateData.EGUID1.humanReadable',
|
||||
'message' => _('There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device').'. '._('Please remove all key files except the one you want to replace from the */config* directory on your USB Flash boot device').'. '._('Alternately you may purchase a license key for this USB flash device').'. '._('If you want to replace one of your license keys with a new key bound to this USB Flash device please first remove all other key files first').'.',
|
||||
],
|
||||
],
|
||||
'EBLACKLISTED' => [
|
||||
'humanReadable' => _('BLACKLISTED'),
|
||||
'error' => [
|
||||
'heading' => _('Blacklisted USB Flash GUID'),
|
||||
'message' => _('This USB Flash boot device has been blacklisted').'. '._('This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device').'. '._('A USB Flash device may also be blacklisted if we discover the serial number is not unique – this is common with USB card readers').'.',
|
||||
],
|
||||
],
|
||||
'EBLACKLISTED1' => [
|
||||
'humanReadable' =>'@:stateData.EBLACKLISTED.humanReadable',
|
||||
'error' => [
|
||||
'heading' => _('USB Flash device error'),
|
||||
'message' => _('This USB Flash device has an invalid GUID').'. '._('Please try a different USB Flash device').'.',
|
||||
],
|
||||
],
|
||||
'EBLACKLISTED2' => [
|
||||
'humanReadable' => '@:stateData.EBLACKLISTED.humanReadable',
|
||||
'error' => [
|
||||
'heading' => _('USB Flash has no serial number'),
|
||||
'message' => '@:stateData.EBLACKLISTED.error.message',
|
||||
],
|
||||
],
|
||||
'ENOCONN' => [
|
||||
'humanReadable' => _('Trial Requires Internet Connection'),
|
||||
'error' => [
|
||||
'heading' => _('Cannot validate Unraid Trial key'),
|
||||
'message' => _('Your Trial key requires an internet connection').'. '._('Please check Settings > Network').'.',
|
||||
],
|
||||
],
|
||||
'STALE' => [
|
||||
'humanReadable' => _('Stale'),
|
||||
'error' => [
|
||||
'heading' => _('Stale Server'),
|
||||
'message' => _('Please refresh the page to ensure you load your latest configuration'),
|
||||
],
|
||||
],
|
||||
],
|
||||
'regWizPopUp' => [
|
||||
'regWiz' => _('Registration Wizard'),
|
||||
'toHome' => _('To Registration Wizard Home'),
|
||||
'continueTrial' => _('Continue Trial'),
|
||||
'serverInfoToggle' => _('Toggle server info visibility'),
|
||||
'youCanSafelyCloseThisWindow' => _('You can safely close this window'),
|
||||
'automaticallyClosingIn' => sprintf(_('Auto closing in %s'), '{0}'),
|
||||
'byeBye' => _('bye bye 👋'),
|
||||
'browserWillSelfDestructIn' => sprintf(_('Browser will self destruct in %s'), '{0}'),
|
||||
'closingPopUpMayLeadToErrors' => _('Closing this pop-up window while actions are being preformed may lead to unintended errors'),
|
||||
'goBack' => _('Go Back'),
|
||||
'shutDown' => _('Shut Down'),
|
||||
'haveAccountSignIn' => _('Already have an account').'? '._('Sign In'),
|
||||
'noAccountSignUp' => _('Do not have an account').'? '._('Sign Up'),
|
||||
'willConnectYourServerToMyServers' => _('This will register your server with Connect <sup>BETA</sup>'),
|
||||
'serverInfo' => [
|
||||
'flash' => _('Flash'),
|
||||
'product' => _('Product'),
|
||||
'GUID' => _('GUID'),
|
||||
'name' => _('Name'),
|
||||
'ip' => _('IP'),
|
||||
],
|
||||
'forms' => [
|
||||
'displayName' => _('Display Name'),
|
||||
'emailAddress' => _('Email Address'),
|
||||
'displayNameOrEmailAddress' => _('Display Name or Email Address'),
|
||||
'displayNameRootMessage' => _('Use your Unraid.net credentials, not your local server credentials'),
|
||||
'honeyPotCopy' => _('If you fill this field out then your email will not be sent'),
|
||||
'fieldRequired' => _('This field is required'),
|
||||
'submit' => _('Submit'),
|
||||
'submitting' => _('Submitting'),
|
||||
'notValid' => _('Form not valid'),
|
||||
'cancel' => _('Cancel'),
|
||||
'confirm' => _('Confirm'),
|
||||
'createMyAccount' => _('Create My Account'),
|
||||
'subject' => _('Subject'),
|
||||
'password' => _('Password'),
|
||||
'togglePasswordVisibility' => _('Toggle Password Visibility'),
|
||||
'message' => _('Message'),
|
||||
'confirmPassword' => _('Confirm Password'),
|
||||
'passwordMustMatch' => _('Password confirmation must match'),
|
||||
'passwordMinimum' => _('8 or more characters'),
|
||||
'comments' => _('comments'),
|
||||
'newsletterCopy' => _('Sign me up for the monthly Unraid newsletter').': '._('a digest of recent blog posts, community videos, popular forum threads, product announcements, and more'),
|
||||
'terms' => [
|
||||
'iAgree' => _('I agree to the'),
|
||||
'text' => _('Terms of Use'),
|
||||
],
|
||||
],
|
||||
'routes' => [
|
||||
'extendTrial' => [
|
||||
'heading' => [
|
||||
'loading' => _('Extending Trial'),
|
||||
'error' => _('Trial Extension Failed'),
|
||||
],
|
||||
'message' => _('Not ready to purchase?').'<br>'._('Receive an additional 15 days for your trial').'.',
|
||||
],
|
||||
'forgotPassword' => [
|
||||
'heading' => _('Forgot Password'),
|
||||
'subheading' => _("After resetting your password come back to the Registration Wizard pop-up window to Sign In and complete your server's registration"),
|
||||
'resetPasswordNow' => _('Reset Password Now'),
|
||||
'backToSignIn' => _('Back to Sign In'),
|
||||
],
|
||||
'signIn' => [
|
||||
'heading' => [
|
||||
'signIn' => _('Unraid.net Sign In'),
|
||||
'recover' => _('Unraid.net Sign In to Recover Key'),
|
||||
'replace' => _('Unraid.net Sign In to Replace Key'),
|
||||
],
|
||||
'form' => [
|
||||
'replacementConditions' => [
|
||||
'name' => _('Acknowledge Replacement Conditions'),
|
||||
'label' => _('I acknowledge that replacing a license key results in permanently blacklisting the previous USB Flash GUID'),
|
||||
],
|
||||
'label' => [
|
||||
'password' => [
|
||||
'replace' => _('Unraid.net account password'),
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'signUp' => [
|
||||
'heading' => _('Create Unraid.net Account'),
|
||||
],
|
||||
'signOut' => [
|
||||
'heading' => _('Unraid.net Sign Out'),
|
||||
'warnings' => [
|
||||
'remoteAccessDisabled' => _('Remote access will be disabled'),
|
||||
'remoteAccessInaccessible' => sprintf(_('You will no longer have access to this server using <abbr title="%s" class="italic">this url</abbr>'), '{0}'),
|
||||
'disablingFlashBackup' => _('Automated flash backups will be disabled until you sign in again'),
|
||||
'downloadFlashBackup' => _('Download latest backup from Connect Dashboard before signing out'),
|
||||
],
|
||||
],
|
||||
'success' => [
|
||||
'heading' => [
|
||||
'username' => sprintf(_('Hi %s'), '{0}'),
|
||||
'default' => _('Success'),
|
||||
],
|
||||
'subheading' => [
|
||||
'extention' => _('Your trial will expire in 15 days'),
|
||||
'newTrial' => _('Your trial will expire in 30 days'),
|
||||
],
|
||||
'signIn' => [
|
||||
'tileTitle' => [
|
||||
'actionFail' => sprintf(_('%s was not signed in to your Unraid.net account'), '{0}'),
|
||||
'actionSuccess' => sprintf(_('%s is signed in to your Unraid.net account'), '{0}'),
|
||||
'loading' => sprintf(_('Signing in %s to Unraid.net account'), '{0}'),
|
||||
],
|
||||
],
|
||||
'signOut' => [
|
||||
'tileTitle' => [
|
||||
'actionFail' => sprintf(_('%s was not signed out of your Unraid.net account'), '{0}'),
|
||||
'actionSuccess' => sprintf(_('%s was signed out of your Unraid.net account'), '{0}'),
|
||||
'loading' => sprintf(_('Signing out %s from Unraid.net account'), '{0}'),
|
||||
],
|
||||
],
|
||||
'keys' => [
|
||||
'trial' => _('Trial'),
|
||||
'basic' => _('Basic'),
|
||||
'plus' => _('Plus'),
|
||||
'pro' => _('Pro'),
|
||||
],
|
||||
'extended' => sprintf(_('%s Key Extended'), '{0}'),
|
||||
'recovered' => sprintf(_('%s Key Recovered'), '{0}'),
|
||||
'replaced' => sprintf(_('%s Key Replaced'), '{0}'),
|
||||
'created' => sprintf(_('%s Key Created'), '{0}'),
|
||||
'install' => [
|
||||
'loading' => sprintf(_('Installing %s Key'), '{0}'),
|
||||
'error' => sprintf(_('%s Key Install Error'), '{0}'),
|
||||
'success' => sprintf(_('Installed %s Key'), '{0}'),
|
||||
'manualInstructions' => _('To manually install the key paste the key file url into the Key file URL field on the webGUI Tools > Registration page and then click Install Key') . '.',
|
||||
'copyFail' => _('Unable to copy'),
|
||||
'copySuccess' => _('Copied key url') . '!',
|
||||
'copyButton' => _('Copy Key URL'),
|
||||
'copyBeforeClose' => _('Please copy the Key URL before closing this window'),
|
||||
],
|
||||
'timeout' => sprintf(_('Communication with %s has timed out'), '{0}'),
|
||||
'loading1' => _('Please keep this window open'),
|
||||
'loading2' => _('Still working our magic'),
|
||||
'countdown' => [
|
||||
'success' => [
|
||||
'prefix' => sprintf(_('Auto closing in %s'), '{0}'),
|
||||
'text' => _('You can safely close this window'),
|
||||
],
|
||||
'error' => [
|
||||
'prefix' => sprintf(_('Auto redirecting in %s'), '{0}'),
|
||||
'text' => _('Back to Registration Home'),
|
||||
'complete' => _('Back in a flash ⚡️'),
|
||||
],
|
||||
],
|
||||
],
|
||||
'troubleshoot' => [
|
||||
'heading' => [
|
||||
'default' => _('Troubleshoot'),
|
||||
'success' => _('Thank you for contacting Unraid'),
|
||||
],
|
||||
'subheading' => [
|
||||
'default' => _("Forgot what Unraid.net account you used").'? '._("Have a USB flash device that already has an account associated with it").'? '._("Just give us the details about what happened and we'll do our best to get you up and running again").'.',
|
||||
'success' => _('We have received your e-mail and will respond in the order it was received').'. '._('While we strive to respond to all requests as quickly as possible please allow for up to 3 business days for a response').'.',
|
||||
],
|
||||
'relevantServerData' => _('Your USB Flash GUID and other relevant server data will also be sent'),
|
||||
],
|
||||
'verifyEmail' => [
|
||||
'heading' => _('Verify Email'),
|
||||
'subheading' => sprintf(_('We have sent a verifcation email to %s'), '{0}'),
|
||||
'form' => [
|
||||
'verificationCode' => _('verification code'),
|
||||
'verifyCode' => _('Paste or Enter code'),
|
||||
],
|
||||
'noCode' => _("Didn't get code?"),
|
||||
],
|
||||
'verifyEmailResend' => [
|
||||
'heading' => _('Resend Email Verification Code'),
|
||||
'goBack' => _("Have the code now? Go Back"),
|
||||
'resend' => _("Resend Code"),
|
||||
],
|
||||
'whatIsMyServers' => [
|
||||
'heading' => _('What is Unraid.net?'),
|
||||
'subheading' => _('Expand your servers capabilities'),
|
||||
'copy' => _('With an Unraid.net account you can start using Connect (beta) which gives you access to the following features:'),
|
||||
'features' => [
|
||||
"dynamicRemoteAccess" => [
|
||||
'heading' => _('Dynamic Remote Access'),
|
||||
'copy' => _('Toggle on/off server accessibility with dynamic remote access').'. '._('Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds').'.',
|
||||
],
|
||||
"manageWithinConnect" => [
|
||||
'heading' => _('Manage Your Server Within Connect'),
|
||||
'copy' => _('Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI').'. '._('Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window').'.',
|
||||
],
|
||||
"deepLinking" => [
|
||||
'heading' => _('Deep Linking'),
|
||||
'copy' => _('The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections').'.',
|
||||
],
|
||||
"onlineFlashBackup" => [
|
||||
'heading' => _('Online Flash Backup'),
|
||||
'copy' => _('Never ever be left without a backup of your config').'. '._('If you need to change flash drives, generate a backup from Connect and be up and running in minutes').'.',
|
||||
],
|
||||
"realTimeMonitoring" => [
|
||||
'heading' => _('Real-time Monitoring'),
|
||||
'copy' => _("Get an overview of your server's state, storage space, apps and VMs status, and more").'.',
|
||||
],
|
||||
"customizableDashboardTitles" => [
|
||||
'heading' => _('Customizable Dashboard Tiles'),
|
||||
'copy' => _("Set custom server tiles how you like and automatically display your server's banner image on your Connect Dashboard").'.',
|
||||
],
|
||||
"licenseManagement" => [
|
||||
'heading' => _('License Management'),
|
||||
'copy' => _('Manage your license keys at any time via the My Keys section').'.',
|
||||
],
|
||||
'plusMore' => [
|
||||
'heading' => _('Plus more on the way'),
|
||||
'copy' => _('All you need is an active internet connection, an Unraid.net account, and the Connect plugin').'. '._('Get started by installing the plugin') . '.',
|
||||
],
|
||||
],
|
||||
],
|
||||
'replaceKey' => [
|
||||
'subheading' => [
|
||||
'registered' => 'A record of your replacement will be sent to your Unraid.net account email address',
|
||||
'notRegistered' => 'A record of your replacement will be sent to this email',
|
||||
],
|
||||
],
|
||||
'notFound' => [
|
||||
'subheading' => _('Page Not Found'),
|
||||
],
|
||||
'notAllowed' => [
|
||||
'subheading' => _('Page Not Allowed'),
|
||||
],
|
||||
],
|
||||
],
|
||||
'wanIpCheck' => [
|
||||
'checking' => _('Checking Wan IPs'),
|
||||
'match' => sprintf(_('Remark: your WAN IPv4 is **%s**'), '{0}'),
|
||||
'mismatch' => sprintf(_("Remark: Unraid's WAN IPv4 **%1s** does not match your client's WAN IPv4 **%2s**"), '{0}', '{1}').'. '._('This may indicate a complex network that will not work with this Remote Access solution').'. '._('Ignore this message if you are currently connected via Remote Access or VPN').'.',
|
||||
'resolveError' => _('DNS issue, unable to resolve wanip4.unraid.net'),
|
||||
],
|
||||
'upcTrigger' => [
|
||||
'upgrade' => _('To support more storage devices as your server grows click the *Open Dropdown* button').'.',
|
||||
'default' => _('Key management is done via the dropdown in the top right of the webGUI on every page').'.',
|
||||
'open' => _('Open Dropdown'),
|
||||
],
|
||||
'yargYePirate' => _('Oh no! Are you pirating Unraid OS?<br>Are you ready to buy a real license?'),
|
||||
'keyFileNotValid' => _('Key file not valid'),
|
||||
'installFailed' => [
|
||||
'heading' => _('Connect plugin install failed'),
|
||||
'message' => _('The Connect plugin install is incomplete').'. '._('Please uninstall and reinstall the Connect plugin').'. '._('Be sure to let the install complete before you close the window').'.',
|
||||
],
|
||||
"downloadUnraidApiLogs" => _('Download unraid-api Logs'),
|
||||
"download" => _('Download'),
|
||||
"pleaseWait" => _('Please wait…')
|
||||
],
|
||||
];
|
||||
// note: $myservers variable defined in myservers1.php, by parsing myservers.cfg
|
||||
|
||||
$configErrorEnum = [ // used to map $var['configValid'] value to mimic unraid-api's `configError` ENUM
|
||||
"error" => 'UNKNOWN_ERROR',
|
||||
"invalid" => 'INVALID',
|
||||
"nokeyserver" => 'NO_KEY_SERVER',
|
||||
"withdrawn" => 'WITHDRAWN',
|
||||
];
|
||||
$nginx = parse_ini_file('/var/local/emhttp/nginx.ini');
|
||||
|
||||
$plgInstalled = '';
|
||||
if (!file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net') && !file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net.staging')) {
|
||||
$plgInstalled = ''; // base OS only, plugin not installed • show ad for plugin
|
||||
} else {
|
||||
// plugin is installed but if the unraid-api file doesn't fully install it's a failed install
|
||||
if (file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net')) $plgInstalled = 'dynamix.unraid.net.plg';
|
||||
if (file_exists('/var/lib/pkgtools/packages/dynamix.unraid.net.staging')) $plgInstalled = 'dynamix.unraid.net.staging.plg';
|
||||
// plugin install failed • append failure detected so we can show warning about failed install via UPC
|
||||
if (!file_exists('/usr/local/sbin/unraid-api')) $plgInstalled = $plgInstalled . '_installFailed';
|
||||
}
|
||||
|
||||
// read flashbackup ini file
|
||||
$flashbackup_ini = '/var/local/emhttp/flashbackup.ini';
|
||||
$flashbackup_status = (file_exists($flashbackup_ini)) ? @parse_ini_file($flashbackup_ini) : [];
|
||||
|
||||
$serverstate = [ // feeds server vars to Vuex store in a slightly different array than state.php
|
||||
"avatar" => (!empty($myservers['remote']['avatar']) && $plgInstalled) ? $myservers['remote']['avatar'] : '',
|
||||
"config" => [
|
||||
'valid' => $var['configValid'] === 'yes',
|
||||
'error' => $var['configValid'] !== 'yes'
|
||||
? (array_key_exists($var['configValid'], $configErrorEnum) ? $configErrorEnum[$var['configValid']] : 'UNKNOWN_ERROR')
|
||||
: null,
|
||||
],
|
||||
"deviceCount" => $var['deviceCount'],
|
||||
"email" => $myservers['remote']['email'] ?? '',
|
||||
"extraOrigins" => explode(',', $myservers['api']['extraOrigins']??''),
|
||||
"flashproduct" => $var['flashProduct'],
|
||||
"flashvendor" => $var['flashVendor'],
|
||||
"flashBackupActivated" => empty($flashbackup_status['activated']) ? '' : 'true',
|
||||
"guid" => $var['flashGUID'],
|
||||
"hasRemoteApikey" => !empty($myservers['remote']['apikey']),
|
||||
"internalip" => ipaddr(),
|
||||
"internalport" => $_SERVER['SERVER_PORT'],
|
||||
"keyfile" => empty($var['regFILE'])? "" : str_replace(['+','/','='], ['-','_',''], trim(base64_encode(@file_get_contents($var['regFILE'])))),
|
||||
"osVersion" => $var['version'],
|
||||
"plgVersion" => $plgversion = file_exists('/var/log/plugins/dynamix.unraid.net.plg')
|
||||
? trim(@exec('/usr/local/sbin/plugin version /var/log/plugins/dynamix.unraid.net.plg 2>/dev/null'))
|
||||
: ( file_exists('/var/log/plugins/dynamix.unraid.net.staging.plg')
|
||||
? trim(@exec('/usr/local/sbin/plugin version /var/log/plugins/dynamix.unraid.net.staging.plg 2>/dev/null'))
|
||||
: 'base-'.$var['version'] ),
|
||||
"plgInstalled" => $plgInstalled,
|
||||
"protocol" => $_SERVER['REQUEST_SCHEME'],
|
||||
"reggen" => (int)$var['regGen'],
|
||||
"regGuid" => $var['regGUID'],
|
||||
"registered" => (!empty($myservers['remote']['username']) && $plgInstalled),
|
||||
"servername" => $var['NAME'],
|
||||
"site" => $_SERVER['REQUEST_SCHEME']."://".$_SERVER['HTTP_HOST'],
|
||||
"state" => strtoupper(empty($var['regCheck']) ? $var['regTy'] : $var['regCheck']),
|
||||
"ts" => time(),
|
||||
"username" => (!empty($myservers['remote']['username']) && $plgInstalled) ? $myservers['remote']['username'] : '',
|
||||
"wanFQDN" => $nginx['NGINX_WANFQDN'] ?? '',
|
||||
];
|
||||
/** @TODO - prop refactor needed. The issue is because the prop names share the same name as the vuex store variables
|
||||
* if we remove the props and deployed a UPC that doesn't rely on props anymore uses that don't have an updated version
|
||||
* of this file will have a non-working UPC.
|
||||
* apikey
|
||||
* apiVersion
|
||||
* csrf
|
||||
* expiretime
|
||||
* hideMyServers
|
||||
* plgPath
|
||||
* regWizTime
|
||||
* sendCrashInfo
|
||||
* serverdesc
|
||||
* servermodel
|
||||
* serverupdate
|
||||
* uptime
|
||||
*/
|
||||
?>
|
||||
<unraid-user-profile
|
||||
apikey="<?=$myservers['upc']['apikey'] ?? ''?>"
|
||||
api-version="<?=$myservers['api']['version'] ?? ''?>"
|
||||
banner="<?=$display['banner'] ?? ''?>"
|
||||
bgcolor="<?=($backgnd) ? '#'.$backgnd : ''?>"
|
||||
csrf="<?=$var['csrf_token']?>"
|
||||
displaydesc="<?=($display['headerdescription']??''!='no') ? 'true' : ''?>"
|
||||
expiretime="<?=1000*($var['regTy']=='Trial'||strstr($var['regTy'],'expired')?$var['regTm2']:0)?>"
|
||||
hide-my-servers="<?=$plgInstalled ? '' : 'yes' ?>"
|
||||
locale="<?=($_SESSION['locale']) ? $_SESSION['locale'] : 'en_US'?>"
|
||||
locale-messages="<?=rawurlencode(json_encode($upc_translations, JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE))?>"
|
||||
metacolor="<?=($display['headermetacolor']??'') ? '#'.$display['headermetacolor'] : ''?>"
|
||||
plg-path="dynamix.my.servers"
|
||||
reg-wiz-time="<?=$myservers['remote']['regWizTime'] ?? ''?>"
|
||||
serverdesc="<?=$var['COMMENT']?>"
|
||||
servermodel="<?=$var['SYS_MODEL']?>"
|
||||
serverstate="<?=rawurlencode(json_encode($serverstate, JSON_UNESCAPED_SLASHES))?>"
|
||||
show-banner-gradient="<?=$display['showBannerGradient'] ?? 'yes'?>"
|
||||
textcolor="<?=($header) ? '#'.$header : ''?>"
|
||||
theme="<?=$display['theme']?>"
|
||||
uptime="<?=1000*(time() - round(strtok(exec("cat /proc/uptime"),' ')))?>"
|
||||
></unraid-user-profile>
|
||||
<!-- /myservers2 -->
|
||||
@@ -0,0 +1,73 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
/* add translations */
|
||||
$_SERVER['REQUEST_URI'] = '';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$var = (array)parse_ini_file('state/var.ini');
|
||||
$license_state = strtoupper(empty($var['regCheck']) ? $var['regTy'] : $var['regCheck']);
|
||||
$key_contents = empty($var['regFILE'])? "" : str_replace(['+','/','='], ['-','_',''], trim(base64_encode(@file_get_contents($var['regFILE']))));
|
||||
$myservers_flash_cfg_path='/boot/config/plugins/dynamix.my.servers/myservers.cfg';
|
||||
$myservers = file_exists($myservers_flash_cfg_path) ? @parse_ini_file($myservers_flash_cfg_path,true) : [];
|
||||
|
||||
$configErrorEnum = [ // used to map $var['configValid'] value to mimic unraid-api's `configError` ENUM
|
||||
"error" => 'UNKNOWN_ERROR',
|
||||
"invalid" => 'INVALID',
|
||||
"nokeyserver" => 'NO_KEY_SERVER',
|
||||
"withdrawn" => 'WITHDRAWN',
|
||||
];
|
||||
|
||||
$arr = [];
|
||||
if (empty($myservers['remote']['username'])) {
|
||||
$arr['registered'] = 0;
|
||||
$arr['username'] = '';
|
||||
$arr['avatar'] = '';
|
||||
} else {
|
||||
$arr['registered'] = 1;
|
||||
$arr['username'] = $myservers['remote']['username'];
|
||||
$arr['avatar'] = $myservers['remote']['avatar']??'';
|
||||
}
|
||||
$arr['event'] = 'STATE';
|
||||
$arr['ts'] = time();
|
||||
$arr['deviceCount'] = $var['deviceCount'];
|
||||
$arr['guid'] = $var['flashGUID'];
|
||||
$arr['regGuid'] = $var['regGUID'];
|
||||
$arr['state'] = $license_state;
|
||||
$arr['keyfile'] = $key_contents;
|
||||
$arr['reggen'] = $var['regGen'];
|
||||
$arr['flashproduct'] = $var['flashProduct'];
|
||||
$arr['flashvendor'] = $var['flashVendor'];
|
||||
$arr['servername'] = $var['NAME'];
|
||||
$arr['serverdesc'] = $var['COMMENT'];
|
||||
$arr['internalip'] = $_SERVER['SERVER_ADDR'];
|
||||
$arr['internalport'] = $_SERVER['SERVER_PORT'];
|
||||
$arr['plgVersion'] = 'base-'.$var['version'];
|
||||
$arr['protocol'] = $_SERVER['REQUEST_SCHEME'];
|
||||
$arr['locale'] = $_SESSION['locale'] ?? 'en_US';
|
||||
$arr['expiretime']=1000*($var['regTy']=='Trial'||strstr($var['regTy'],'expired')?$var['regTm2']:0);
|
||||
$arr['uptime']=1000*(time() - round(strtok(exec("cat /proc/uptime"),' ')));
|
||||
$arr['hasRemoteApikey'] = empty($myservers['remote']['apikey']) ? 0 : 1;
|
||||
$arr['hideMyServers'] = (file_exists('/usr/local/sbin/unraid-api')) ? '' : 'yes';
|
||||
$arr['config'] = [
|
||||
'valid' => $var['configValid'] === 'yes',
|
||||
'error' => $var['configValid'] !== 'yes'
|
||||
? (array_key_exists($var['configValid'], $configErrorEnum) ? $configErrorEnum[$var['configValid']] : 'UNKNOWN_ERROR')
|
||||
: null,
|
||||
];
|
||||
|
||||
echo json_encode($arr);
|
||||
?>
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/php
|
||||
<?PHP
|
||||
/* Copyright 2005-2021, Lime Technology
|
||||
* Copyright 2012-2021, 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.
|
||||
*/
|
||||
function update($url, $payload) {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch,CURLOPT_POST, true);
|
||||
curl_setopt($ch,CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);
|
||||
$result = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
$dyn_cfg = '/boot/config/plugins/dynamix/dynamix.cfg';
|
||||
$mys_cfg = '/boot/config/plugins/dynamix.my.servers/myservers.cfg';
|
||||
|
||||
if (file_exists($dyn_cfg) && !file_exists($mys_cfg)) {
|
||||
$orig = @parse_ini_file($dyn_cfg,true);
|
||||
$var = @(array)parse_ini_file('/usr/local/emhttp/state/var.ini',true);
|
||||
$url = "http://localhost:".$var['PORT']."/update.php";
|
||||
|
||||
// write [remote] section to myservers.cfg
|
||||
if(!empty($orig['remote'])) {
|
||||
$fields_mys_remote = [
|
||||
'csrf_token' => $var['csrf_token'],
|
||||
'#file' => $mys_cfg,
|
||||
'#section' => 'remote'
|
||||
];
|
||||
foreach($orig['remote'] as $key => $value) {
|
||||
$fields_mys_remote[$key] = $value;
|
||||
}
|
||||
update($url, http_build_query($fields_mys_remote));
|
||||
}
|
||||
/*
|
||||
// write [wizard] section to myservers.cfg
|
||||
if(!empty($orig['wizard'])) {
|
||||
$fields_mys_wizard = [
|
||||
'csrf_token' => $var['csrf_token'],
|
||||
'#file' => $mys_cfg,
|
||||
'#section' => 'wizard'
|
||||
];
|
||||
foreach($orig['wizard'] as $key => $value) {
|
||||
$fields_mys_wizard[$key] = $value;
|
||||
}
|
||||
update($url, http_build_query($fields_mys_wizard));
|
||||
}
|
||||
*/
|
||||
// remove [remote] section from dynamix.cfg
|
||||
if(!empty($orig['remote'])) {
|
||||
$fields_dyn_remote = [
|
||||
'csrf_token' => $var['csrf_token'],
|
||||
'#file' => $dyn_cfg,
|
||||
'#section' => 'remote',
|
||||
'#cleanup' => 'true'
|
||||
];
|
||||
foreach($orig['remote'] as $key => $value) {
|
||||
$fields_dyn_remote[$key] = '';
|
||||
}
|
||||
update($url, http_build_query($fields_dyn_remote));
|
||||
}
|
||||
// remove [wizard] section from dynamix.cfg
|
||||
if(!empty($orig['wizard'])) {
|
||||
$fields_dyn_wizard = [
|
||||
'csrf_token' => $var['csrf_token'],
|
||||
'#file' => $dyn_cfg,
|
||||
'#section' => 'wizard',
|
||||
'#cleanup' => 'true'
|
||||
];
|
||||
foreach($orig['wizard'] as $key => $value) {
|
||||
$fields_dyn_wizard[$key] = '';
|
||||
}
|
||||
update($url, http_build_query($fields_dyn_wizard));
|
||||
}
|
||||
// remove [remote] and [wizard] section headings from dyn_cfg file
|
||||
$oldtext = file_get_contents($dyn_cfg);
|
||||
$newtext = preg_replace ('/\[(remote|wizard)\]\n/', '', $oldtext);
|
||||
if (strcmp($oldtext, $newtext) !== 0) {
|
||||
file_put_contents($dyn_cfg, $newtext);
|
||||
}
|
||||
}
|
||||
if (!file_exists($mys_cfg)) touch($mys_cfg);
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,98 @@
|
||||
Menu='Buttons'
|
||||
Link='nav-user'
|
||||
---
|
||||
<?
|
||||
/* Copyright 2023, Lime Technology
|
||||
* Copyright 2023, Andrew Zawadzki.
|
||||
*
|
||||
* 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>
|
||||
/* Additional CSS for when user supplies element */
|
||||
.ca_element_notice{padding-right:20px;width:100%;height:40px;line-height:40px;color:#e68a00;background:#feefb3;border-bottom:#e68a00 1px solid;text-align:center;font-size:1.4rem;z-index:900;}
|
||||
a.ca_PluginUpdateInstall{cursor:pointer;}
|
||||
span.ca_PluginUpdateDismiss{float:right;margin-right:20px;cursor:pointer;}
|
||||
span.bannerInfo {cursor:pointer;text-decoration:none;margin:0 12px 0 6px;}
|
||||
span.bannerInfo::before {content:"\f05a";font-family:fontAwesome;color:#e68a00;}
|
||||
</style>
|
||||
<script>
|
||||
const ca_args = {};
|
||||
|
||||
function ca_refresh() {
|
||||
<?if ($task == 'Plugins'):?>
|
||||
loadlist();
|
||||
<?else:?>
|
||||
refresh();
|
||||
<?endif;?>
|
||||
}
|
||||
|
||||
function ca_hidePluginUpdate(plugin,version,element) {
|
||||
$.cookie(plugin,version);
|
||||
$(element).hide();
|
||||
}
|
||||
|
||||
function ca_pluginUpdateInstall(plugin) {
|
||||
if (plugin == null) {
|
||||
openPlugin(ca_args.cmd,ca_args.title,'','ca_refresh');
|
||||
return;
|
||||
}
|
||||
ca_args.cmd = 'plugin update '+plugin;
|
||||
ca_args.title = "_(Installing Update)_";
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{cmd:'alert'},function(data) {
|
||||
if (data==0) {
|
||||
// no alert message - proceed with update
|
||||
setTimeout(ca_pluginUpdateInstall);
|
||||
} else {
|
||||
// show alert message and ask for confirmation
|
||||
openAlert("showchanges <?=$alerts?>","_(Alert Message)_","ca_pluginUpdateInstall");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function caPluginUpdateCheck(plugin,options=[],callback) {
|
||||
var pluginFilename = plugin.substr(0, plugin.lastIndexOf("."));
|
||||
console.time("checkPlugin "+plugin);
|
||||
console.log("checkPlugin "+plugin);
|
||||
$.post("/plugins/dynamix.plugin.manager/scripts/PluginAPI.php",{action:'checkPlugin',options:{plugin:plugin,name:options.name}},function(caAPIresult) {
|
||||
console.groupCollapsed("Result checkPlugin "+plugin);
|
||||
console.log(caAPIresult);
|
||||
console.timeEnd("checkPlugin "+plugin);
|
||||
console.groupEnd();
|
||||
var result = JSON.parse(caAPIresult);
|
||||
|
||||
if ( options.debug == true ) result.updateAvailable = true;
|
||||
if ( ! options.element && ! options.dontShow ) {
|
||||
if ( result.updateAvailable ) {
|
||||
var HTML = result.updateMessage+"<span class='bannerInfo fa fa-info-circle big' title=\"_(View Release Notes)_\" onclick='openChanges(\"showchanges /tmp/plugins/"+pluginFilename+".txt\",\"_(Release Notes)_\")'></span><a class='ca_PluginUpdateInstall' onclick='ca_pluginUpdateInstall(\""+plugin+"\")'>"+result.linkMessage+"</a>";
|
||||
addBannerWarning(HTML,false,options.noDismiss);
|
||||
}
|
||||
} else {
|
||||
if ( $.cookie(plugin) != result.version ) {
|
||||
if ( result.updateAvailable ) {
|
||||
var HTML = result.updateMessage+"<span class='bannerInfo fa fa-info-circle big' title=\"_(View Release Notes)_\" onclick='openChanges(\"showchanges /tmp/plugins/"+pluginFilename+".txt\",\"_(Release Notes)_\")'></span><a class='ca_PluginUpdateInstall' onclick='ca_pluginUpdateInstall(\""+plugin+"\")'>"+result.linkMessage+"</a>";
|
||||
if ( ! options.noDismiss ) {
|
||||
HTML = HTML.concat("<span class='ca_PluginUpdateDismiss'><i class='fa fa-close' onclick='ca_hidePluginUpdate(\""+plugin+"\",\""+result.version+"\",\""+options.element+"\")'></i></span>");
|
||||
}
|
||||
result.HTML = HTML;
|
||||
if ( ! options.dontShow ) {
|
||||
$(options.element).html(HTML);
|
||||
$(options.element).addClass("ca_element_notice");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( typeof options === "function" ) {
|
||||
callback = options;
|
||||
}
|
||||
if ( typeof callback === "function" ) {
|
||||
callback(JSON.stringify(result));
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,53 @@
|
||||
Menu="Plugins"
|
||||
Title="Install Plugin"
|
||||
Tag="download"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2022, Lime Technology
|
||||
* Copyright 2012-2022, 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>
|
||||
const my = {};
|
||||
|
||||
function installPlugin(file) {
|
||||
if (file == null) {
|
||||
$('#plugin_file').val('');
|
||||
openPlugin(my.cmd,my.title,my.plg);
|
||||
return;
|
||||
}
|
||||
file = file.trim();
|
||||
if (!file) return;
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{cmd:'alert'},function(data) {
|
||||
my.cmd = 'plugin install '+file;
|
||||
my.title = "_(Install Plugin)_";
|
||||
my.plg = file.replace(/^.*(\\|\/|\:)/,'').replace('.plg','')+':install';
|
||||
if (data==0) {
|
||||
// no alert message - proceed with install
|
||||
setTimeout(installPlugin);
|
||||
} else {
|
||||
// show alert message and ask for confirmation
|
||||
openAlert("showchanges <?=$alerts?>","<?=_('Alert Message')?>",'installPlugin');
|
||||
}
|
||||
});
|
||||
}
|
||||
</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>
|
||||
|
||||
:plugin_install_help:
|
||||
|
||||
**_(Select local plugin file)_**
|
||||
<div id="plugin_tree" class="textarea"></div>
|
||||
@@ -0,0 +1,161 @@
|
||||
Menu="Tasks:50"
|
||||
Type="xmenu"
|
||||
Title="Installed Plugins"
|
||||
Tag="icon-plugins"
|
||||
Tabs="true"
|
||||
Code="e944"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2022, Lime Technology
|
||||
* Copyright 2012-2022, 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 (check that script 'plugin' is not running to avoid clashes)
|
||||
if (!exec("pgrep -f $docroot/plugins/dynamix.plugin.manager/scripts/plugin")) {
|
||||
foreach (glob("/tmp/plugins/*.{plg,txt}", GLOB_NOSORT+GLOB_BRACE) as $entry) if (!file_exists("/var/log/plugins/".basename($entry))) @unlink($entry);
|
||||
}
|
||||
$check = $notify['version'] ? 0 : 1;
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.filetree.css')?>">
|
||||
<style>
|
||||
#plugin_tree{width:33%;height:200px;overflow-y:scroll}
|
||||
table tbody td{line-height:normal}
|
||||
</style>
|
||||
|
||||
<script src="<?autov('/webGui/javascript/jquery.filetree.js')?>" charset="utf-8"></script>
|
||||
<script>
|
||||
const args = {};
|
||||
|
||||
function openInstall(cmd,title,plg,func) {
|
||||
if (cmd == null) {
|
||||
openPlugin(args.cmd,args.title,args.plg,args.func,1);
|
||||
return;
|
||||
}
|
||||
args.cmd = cmd;
|
||||
args.title = title;
|
||||
args.plg = plg;
|
||||
args.func = func;
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{cmd:'alert'},function(data) {
|
||||
if (data==0) {
|
||||
// no alert message - proceed with update
|
||||
setTimeout(openInstall);
|
||||
} else {
|
||||
// show alert message and ask for confirmation
|
||||
openAlert("showchanges <?=$alerts?>","_(Alert Message)_",'openInstall');
|
||||
}
|
||||
});
|
||||
}
|
||||
<?if ($display['resize']):?>
|
||||
function resize(bind) {
|
||||
var width = [];
|
||||
var h = $('#plugin_list').height();
|
||||
var s = Math.max(window.innerHeight-340,330);
|
||||
if (h>s || bind) {
|
||||
$('#plugin_list').height(s);
|
||||
$('#plugin_table tbody tr:first-child td').each(function(){width.push($(this).width());});
|
||||
$('#plugin_table thead tr th').each(function(i){$(this).width(width[i]);});
|
||||
if (!bind) $('#plugin_table thead,#plugin_table tbody').addClass('fixed');
|
||||
}
|
||||
}
|
||||
<?endif;?>
|
||||
function multiRemove() {
|
||||
if ($('input.remove:checked').length > 1) $('#removeall').show(); else $('#removeall').hide();
|
||||
}
|
||||
function updateList() {
|
||||
var plugin = [];
|
||||
$('input.update').each(function(){plugin.push($(this).attr('data'));});
|
||||
var plugins = plugin.join('*');
|
||||
$('#updateall').hide();
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{cmd:'pending',plugin:plugins},function() {
|
||||
openPlugin("multiplugin update "+plugins,"_(Update All Plugins)_",":return","loadlist",1);
|
||||
});
|
||||
}
|
||||
function removeList() {
|
||||
var plugin = [];
|
||||
$('input.remove:checked').each(function(){plugin.push($(this).attr('data'));});
|
||||
var plugins = plugin.join('*');
|
||||
$('#removeall').hide();
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{cmd:'pending',plugin:plugins},function() {
|
||||
openPlugin("multiplugin remove "+plugins,"_(Remove Selected Plugins)_","","refresh",1);
|
||||
});
|
||||
}
|
||||
function updateInfo(data) {
|
||||
var updates = data.split('\n');
|
||||
for (var n=0,update; update=updates[n]; n++) {
|
||||
var fields = update.split('\r');
|
||||
for (var i=0,field; field=fields[i]; i++) {
|
||||
var row = field.split('::');
|
||||
$('#'+row[0]).attr('data',row[1]).html(row[2]);
|
||||
var removeButton = $('input[data="'+row[0].substr(4).replace(/-/g,'.')+'.plg'+'"]');
|
||||
if (row[2].indexOf('hourglass') >= 0) removeButton.hide(); else removeButton.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
function initlist() {
|
||||
timers.plugins = setTimeout(function(){$('div.spinner.fixed').show('slow');},500);
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{init:true,check:<?=$check?>},function(data) {
|
||||
clearTimeout(timers.plugins);
|
||||
$('#plugin_list').html(data);
|
||||
<?if ($display['resize']):?>
|
||||
resize();
|
||||
$(window).bind('resize',function(){resize(true);});
|
||||
<?endif;?>
|
||||
if (data.search("colspan='6'")==-1) {
|
||||
$('#plugin_table').tablesorter({sortList:[[4,0],[1,0]],sortAppend:[[1,0]],headers:{0:{sorter:false},5:{sorter:false}},textAttribute:'data'});
|
||||
$('.desc_readmore').readmore({maxHeight:80,moreLink:"<a href='#'><i class='fa fa-chevron-down'></i></a>",lessLink:"<a href='#'><i class='fa fa-chevron-up'></i></a>"});
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
loadlist();
|
||||
}
|
||||
});
|
||||
}
|
||||
function loadlist(id,check) {
|
||||
if (id) timers.plugins = setTimeout(function(){$('div.spinner.fixed').show('slow');},500);
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{audit:id,check:check||<?=$check?>},function(data) {
|
||||
data = data.split('\0');
|
||||
var list = $('#plugin_list');
|
||||
if (id) {
|
||||
clearTimeout(timers.plugins);
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
var cmd = id.split(':');
|
||||
var tr = 'tr#'+cmd[0].replace(/[\. _]/g,'');
|
||||
switch (cmd[1]) {
|
||||
case 'update':
|
||||
case 'return':
|
||||
updateInfo(data[0]);
|
||||
if (data[1] > 1) $('#updateall').show(); else $('#updateall').hide();
|
||||
break;
|
||||
case 'remove':
|
||||
list.find(tr).remove();
|
||||
break;
|
||||
case 'install':
|
||||
if (!list.find(tr).length) list.append(data[0]); loadlist(null,1);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
updateInfo(data[0]);
|
||||
if (data[1] > 1) $('#updateall').show(); else $('#updateall').hide();
|
||||
}
|
||||
$('#plugin_table').trigger('update');
|
||||
$('#checkall').find('input').prop('disabled',false);
|
||||
});
|
||||
}
|
||||
$(function() {
|
||||
initlist();
|
||||
$('#plugin_tree').fileTree({root:'/boot/',top:'/boot/',filter:'plg'}, function(file) {$('#plugin_file').val(file);});
|
||||
$('.tabs').append("<span id='checkall' class='status vhshift'><input type='button' value=\"_(Check For Updates)_\" onclick='openPlugin(\"checkall\",\"_(Plugin Update Check)_\",\":return\")' disabled></span>");
|
||||
$('.tabs').append("<span id='updateall' class='status vhshift' style='display:none;margin-left:12px'><input type='button' value=\"_(Update All Plugins)_\" onclick='updateList()'></span>");
|
||||
$('.tabs').append("<span id='removeall' class='status vhshift' style='display:none;margin-left:12px'><input type='button' value=\"_(Remove Selected Plugins)_\" onclick='removeList()'></span>");
|
||||
});
|
||||
</script>
|
||||
<table class='tablesorter plugins shift' id='plugin_table'>
|
||||
<thead><tr><th></th><th>_(Plugin)_</th><th>_(Author)_</th><th>_(Version)_</th><th>_(Status)_</th><th>_(Uninstall)_</th></tr></thead>
|
||||
<tbody id="plugin_list"><tr><td colspan="6"></td><tr></tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,33 @@
|
||||
Menu="Plugins"
|
||||
Title="Plugin File Install Errors"
|
||||
Tag="puzzle-piece"
|
||||
Cond="glob('/boot/config/plugins-error/*.plg')"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2021, Lime Technology
|
||||
* Copyright 2012-2021, 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 "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
|
||||
|
||||
echo "<table class='tablesorter' id='plugin_table'><thead>";
|
||||
echo "<tr><th>"._('Plugin File')."</th><th>"._('Status')."</th><th>",_('Uninstall')."</th></tr>";
|
||||
echo "</thead><tbody>";
|
||||
|
||||
foreach (glob("/boot/config/plugins-error/*.plg", GLOB_NOSORT) as $plugin_file) {
|
||||
$status = "<span class='orange-text'><i class='fa fa-times-circle'></i> "._('ERROR')."</span>";
|
||||
$action = make_link("delete", $plugin_file);
|
||||
echo "<tr><td><b>$plugin_file</b></td><td>$status</td><td>$action</td></tr>";
|
||||
}
|
||||
|
||||
echo "</tbody></table>";
|
||||
?>
|
||||
:plugin_error_help:
|
||||
@@ -0,0 +1,60 @@
|
||||
Menu="Plugins"
|
||||
Title="Plugin History"
|
||||
Tag="puzzle-piece"
|
||||
Cond="glob('/boot/config/plugins-stale/*.plg')"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2021, Lime Technology
|
||||
* Copyright 2012-2021, 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 "$docroot/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");
|
||||
|
||||
// status info
|
||||
$status = "<span class='orange-text'><i class='fa fa-unlink'></i> "._('STALE')."</span>";
|
||||
|
||||
// action
|
||||
$action = make_link("delete", $plugin_file);
|
||||
|
||||
// echo our plugin information
|
||||
echo "<tr><td><img src='$icon'></td><td>$desc</td><td>$author</td><td>$version</td><td>$status</td><td>$action</td></tr>";
|
||||
}
|
||||
|
||||
echo "</tbody></table>";
|
||||
?>
|
||||
:plugin_stale_help:
|
||||
@@ -0,0 +1,199 @@
|
||||
Menu="About"
|
||||
Title="Update OS"
|
||||
Icon="icon-update"
|
||||
Tag="upload"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2022, Lime Technology
|
||||
* Copyright 2012-2022, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$version = $branch = $date = _('unknown');
|
||||
$bzroot = file_exists('/boot/previous/bzroot');
|
||||
$check = $notify['unraidos'] ? 0 : 1;
|
||||
$changes = '/boot/previous/changes.txt';
|
||||
$zip = htmlspecialchars(str_replace(' ','_',strtolower($var['NAME'])));
|
||||
|
||||
if (file_exists($changes)) {
|
||||
exec("head -n4 $changes",$rows);
|
||||
foreach ($rows as $row) {
|
||||
$i = stripos($row,'version');
|
||||
if ($i !== false) {
|
||||
[$version,$date] = explode(' ',trim(substr($row,$i+7)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
$branch = strpos($version,'rc')!==false ? _('Next') : (strpos($version,'beta')!==false ? _('Beta') : _('Stable'));
|
||||
}
|
||||
$reboot = preg_match("/^\*\*(REBOOT REQUIRED|DOWNGRADE)/",@file_get_contents("$docroot/plugins/unRAIDServer/README.md",false,null,0,20));
|
||||
?>
|
||||
<style>
|
||||
ul,li{margin:0;padding-top:0;padding-bottom:0}
|
||||
pre.pre{margin:30px 0}
|
||||
pre>p{margin:0;padding:0}
|
||||
pre#swaltext{height:600px!important}
|
||||
@media (max-width:960px){pre#swaltext{height:400px!important}}
|
||||
@media (max-width:960px){.sweet-alert.nchan{height:600px;width:900px;margin-left:-470px}}
|
||||
@media (max-height:768px){pre#swaltext{height:400px!important}}
|
||||
@media (max-height:768px){.sweet-alert.nchan{height:600px;width:900px;margin-left:-470px}}
|
||||
input[value="_(Install)_"],input[value="_(Update)_"],input[value="_(Restore)_"]{margin:0}
|
||||
<?if ($themes1):?>
|
||||
span.vhshift{margin-top:13px!important}
|
||||
<?endif;?>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var diagnosticsFile = "";
|
||||
var nchan_diagnostics = new NchanSubscriber('/sub/diagnostics',{subscriber:'websocket'});
|
||||
var original = null;
|
||||
const args = {};
|
||||
|
||||
nchan_diagnostics.on('message', function(data) {
|
||||
if (data == '_DONE_') {
|
||||
nchan_diagnostics.stop();
|
||||
$('.sweet-alert').hide('fast').removeClass('nchan');
|
||||
swal.close();
|
||||
location = diagnosticsFile;
|
||||
setTimeout(cleanUp,4000);
|
||||
} else if (data) {
|
||||
let box = $('pre#swaltext');
|
||||
box.html(box.html()+'<br>'+data).scrollTop(box[0].scrollHeight);
|
||||
}
|
||||
});
|
||||
|
||||
function openInstall(cmd,title,plg) {
|
||||
if (cmd == null) {
|
||||
openPlugin(args.cmd,args.title,args.plg);
|
||||
return;
|
||||
}
|
||||
args.cmd = cmd;
|
||||
args.title = title;
|
||||
args.plg = plg;
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{cmd:'alert'},function(data) {
|
||||
if (data==0) {
|
||||
// no alert message - proceed with update
|
||||
setTimeout(openInstall);
|
||||
} else {
|
||||
// show alert message and ask for confirmation
|
||||
openAlert("showchanges <?=$alerts?>","_(Alert Message)_",'openInstall');
|
||||
}
|
||||
});
|
||||
}
|
||||
function update_table(branch) {
|
||||
if (original) {
|
||||
if (branch != original) branch = '';
|
||||
} else {
|
||||
if (branch) original = branch;
|
||||
}
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{system:true,branch:branch},function(data) {
|
||||
data = data.split('\0');
|
||||
updateInfo(data[0]);
|
||||
$('#os_table').trigger('update');
|
||||
});
|
||||
}
|
||||
function downgrade() {
|
||||
swal({title:"_(Diagnostics)_",text:"_(Please provide diagnostics when experiencing problems)_<br>_(Post these in the forums)_",html:true,type:'warning',showCancelButton:true,confirmButtonText:"<?=_('Diagnostics')?>",cancelButtonText:"<?=_('Restore')?>"},function(diag){
|
||||
if (diag) {
|
||||
// get diagnostics and then downgrade
|
||||
setTimeout(function(){diagnostics(zipfile());},250);
|
||||
} else {
|
||||
// downgrade immediately
|
||||
$.get('/plugins/dynamix.plugin.manager/include/Downgrade.php',{version:'<?=$version?>'},function(){refresh();});
|
||||
}
|
||||
});
|
||||
}
|
||||
function updateInfo(data) {
|
||||
var updates = data.split('\n');
|
||||
for (var n=0,update; update=updates[n]; n++) {
|
||||
var fields = update.split('\r');
|
||||
for (var i=0,field; field=fields[i]; i++) {
|
||||
var row = field.split('::');
|
||||
$('#'+row[0]).attr('data',row[1]).html(row[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
function initlist() {
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{init:true,system:true,check:<?=$check?>},function(data) {
|
||||
$('#os_list').html(data);
|
||||
<?if ($bzroot):?>
|
||||
$('#previous').show();
|
||||
<?endif;?>
|
||||
loadlist();
|
||||
});
|
||||
}
|
||||
function warning (data) {
|
||||
$('#change_branch').prop('disabled',true);
|
||||
return data.replace('green','orange').replace("<?=_('up-to-date')?>","<?=_('Reboot')?>").replace('checking','warning');
|
||||
}
|
||||
function loadlist(id,check) {
|
||||
$.get('/plugins/dynamix.plugin.manager/include/ShowPlugins.php',{system:true,audit:id,check:check||<?=$check?>},function(data) {
|
||||
var list = $('#os_list');
|
||||
if (id) {
|
||||
var cmd = id.split(':');
|
||||
var tr = 'tr#'+cmd[0].replace(/[\. _]/g,'');
|
||||
switch (cmd[1]) {
|
||||
case 'update' : data = warning(data);
|
||||
case 'return' : updateInfo(data); break;
|
||||
case 'install': list.find(tr).remove(); list.append(warning(data)); break;
|
||||
}
|
||||
} else {
|
||||
<?if (!$reboot):?>
|
||||
updateInfo(data);
|
||||
<?else:?>
|
||||
updateInfo(warning(data));
|
||||
<?endif;?>
|
||||
}
|
||||
$('#os_table').trigger('update');
|
||||
$('#checkos').prop('disabled',false);
|
||||
<?if ($reboot):?>
|
||||
$('#change_branch').prop('disabled',true);
|
||||
<?endif;?>
|
||||
});
|
||||
}
|
||||
function cleanUp() {
|
||||
if (document.hasFocus()) {
|
||||
$.post('/webGui/include/Download.php',{cmd:'delete',file:diagnosticsFile},function(){
|
||||
$.get('/plugins/dynamix.plugin.manager/include/Downgrade.php',{version:'<?=$version?>'},function(){refresh();});
|
||||
});
|
||||
} else {
|
||||
setTimeout(cleanUp,2000);
|
||||
}
|
||||
}
|
||||
function zipfile(){
|
||||
var tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds
|
||||
var localISOTime = (new Date(Date.now() - tzoffset)).toISOString().slice(0,-1);
|
||||
return '<?=$zip?>-diagnostics-'+localISOTime.substr(0,16).replace(/[-:]/g,'').replace('T','-')+'.zip';
|
||||
}
|
||||
function diagnostics(file) {
|
||||
nchan_diagnostics.start();
|
||||
$.post('/webGui/include/Download.php',{cmd:'diag',file:file,anonymize:''},function(zip) {
|
||||
if (zip) {
|
||||
diagnosticsFile = zip;
|
||||
swal({title:"_(Downloading)_...",text:"/boot/logs"+zip+"<hr><pre id='swaltext'></pre>",html:true,animation:'none',showConfirmButton:false});
|
||||
$('.sweet-alert').addClass('nchan');
|
||||
$('button.confirm').prop('disabled',true);
|
||||
} else {
|
||||
nchan_diagnostics.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
$(function() {
|
||||
initlist();
|
||||
$('.tabs').append("<span class='status vhshift'><input type='button' id='checkos' value=\"_(Check for Updates)_\" onclick='openPlugin(\"plugin checkos\",\"_(System Update Check)_\",\":return\")' disabled></span>");
|
||||
});
|
||||
</script>
|
||||
<table class='tablesorter plugins shift' id='os_table'>
|
||||
<thead><tr><th></th><th>_(Component)_</th><th>_(Author)_</th><th>_(Version)_</th><th>_(Status)_</th><th>_(Branch)_</th></tr></thead>
|
||||
<tbody id="os_list"><tr><td colspan="6"></td></tr></tbody>
|
||||
<?if ($bzroot):?>
|
||||
<tbody id="previous" style="display:none"><tr><td><img src="/plugins/unRAIDServer/images/unRAIDServer.png" class="list"></td><td><b>_(Unraid OS)_ (_(previous)_)</b></td><td>LimeTech</td><td><?=$version?></td><td><input type="button" value="_(Restore)_" onclick="downgrade()"></td><td><?=$branch?></td></tbody>
|
||||
<?endif;?>
|
||||
</table>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/webGui/include/Secure.php";
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'plugins';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
exec("mv -f /boot/previous/* /boot");
|
||||
$version = unscript(_var($_GET,'version'));
|
||||
file_put_contents("$docroot/plugins/unRAIDServer/README.md","**"._('DOWNGRADE TO VERSION')." $version**");
|
||||
?>
|
||||
@@ -0,0 +1,73 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2022, Lime Technology
|
||||
* Copyright 2012-2022, 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 = '') {
|
||||
global $docroot;
|
||||
exec("$docroot/plugins/dynamix.plugin.manager/scripts/plugin ".escapeshellarg($method)." ".escapeshellarg($arg), $output, $retval);
|
||||
return $retval==0 ? implode("\n", $output) : false;
|
||||
}
|
||||
|
||||
// Invoke the language command with indicated method
|
||||
function language($method, $arg = '') {
|
||||
global $docroot;
|
||||
exec("$docroot/plugins/dynamix.plugin.manager/scripts/language ".escapeshellarg($method)." ".escapeshellarg($arg), $output, $retval);
|
||||
return $retval==0 ? implode("\n", $output) : false;
|
||||
}
|
||||
|
||||
function check_plugin($arg, &$ncsi) {
|
||||
// Get network connection status indicator (NCSI)
|
||||
if ($ncsi===null) $ncsi = exec("wget --spider --no-check-certificate -nv -T10 -t1 https://www.msftncsi.com/ncsi.txt 2>&1|grep -o 'OK'");
|
||||
return $ncsi ? plugin('check',$arg) : false;
|
||||
}
|
||||
|
||||
function make_link($method, $arg, $extra='') {
|
||||
$plg = basename($arg,'.plg').':'.$method;
|
||||
$id = str_replace(['.',' ','_'],'',$plg);
|
||||
$check = $method=='remove' ? "<input type='checkbox' data='$arg' class='remove' onClick='document.getElementById(\"$id\").disabled=!this.checked;multiRemove()'>" : "";
|
||||
$disabled = $check ? ' disabled' : '';
|
||||
if ($method == 'delete') {
|
||||
$cmd = "plugin_rm $arg";
|
||||
$func = "refresh";
|
||||
$plg = "";
|
||||
} else {
|
||||
$cmd = "plugin $method $arg".($extra?" $extra":"");
|
||||
$func = "loadlist";
|
||||
}
|
||||
if (is_file("/tmp/plugins/pluginPending/$arg") && !$check) {
|
||||
return "<span class='orange-text'><i class='fa fa-hourglass-o fa-fw'></i> "._('pending')."</span>";
|
||||
} else {
|
||||
return "$check<input type='button' id='$id' data='$arg' class='$method' value=\""._(ucfirst($method))."\" onclick='openInstall(\"$cmd\",\""._(ucwords($method)." Plugin")."\",\"$plg\",\"$func\");'$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
|
||||
$icon = "plugins/$name/$name.png";
|
||||
if (file_exists($icon)) return $icon;
|
||||
$image = @preg_split('/[\._- ]/',$name)[0];
|
||||
$icon = "plugins/$name/images/$image.png";
|
||||
if (file_exists($icon)) return $icon;
|
||||
$icon = "plugins/$name/$image.png";
|
||||
if (file_exists($icon)) return $icon;
|
||||
// last resort - default plugin icon
|
||||
return "webGui/images/plg.png";
|
||||
}
|
||||
function mk_options($select,$value) {
|
||||
return "<option value='$value'".($select==$value?" selected":"").">"._(ucfirst($value))."</option>";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'plugins';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$valid = ['/var/tmp/','/tmp/plugins/'];
|
||||
$good = false;
|
||||
?>
|
||||
<body style="margin:14px 10px">
|
||||
<?
|
||||
if ($file = realpath(unscript(_var($_GET,'file')))) {
|
||||
foreach ($valid as $check) if (strncmp($file,$check,strlen($check))===0) $good = true;
|
||||
if ($good && pathinfo($file,PATHINFO_EXTENSION)=='txt') echo Markdown(file_get_contents($file));
|
||||
} else {
|
||||
echo Markdown("*"._('No release notes available')."!*");
|
||||
}
|
||||
?>
|
||||
<br><div style="text-align:center"><input type="button" value="<?=_('Done')?>" onclick="top.Shadowbox.close()"></div>
|
||||
</body>
|
||||
@@ -0,0 +1,206 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'plugins';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$system = unscript(_var($_GET,'system'));
|
||||
$branch = unscript(_var($_GET,'branch'));
|
||||
$audit = unscript(_var($_GET,'audit'));
|
||||
$check = unscript(_var($_GET,'check'));
|
||||
$cmd = unscript(_var($_GET,'cmd'));
|
||||
$init = unscript(_var($_GET,'init'));
|
||||
$empty = true;
|
||||
$install = false;
|
||||
$updates = 0;
|
||||
$alerts = '/tmp/plugins/my_alerts.txt';
|
||||
$builtin = ['unRAIDServer'];
|
||||
$plugins = "/var/log/plugins/*.plg";
|
||||
$ncsi = null; // network connection status indicator
|
||||
$Unraid = parse_ini_file("/etc/unraid-version");
|
||||
|
||||
if ($cmd=='alert') {
|
||||
// signal alert message yer or no
|
||||
echo is_file($alerts) ? 1 : 0;
|
||||
die();
|
||||
}
|
||||
|
||||
if ($cmd=='pending') {
|
||||
// prepare pending status for multi operations
|
||||
foreach (explode('*',_var($_GET,'plugin')) as $plugin) file_put_contents("/tmp/plugins/pluginPending/$plugin",'multi');
|
||||
die();
|
||||
}
|
||||
|
||||
if ($audit) {
|
||||
[$plg,$action] = my_explode(':',$audit);
|
||||
switch ($action) {
|
||||
case 'return' : $check = true; break;
|
||||
case 'remove' : return;
|
||||
case 'install': $install = true;
|
||||
case 'update' : $plugins = "/var/log/plugins/$plg.plg"; break;
|
||||
}
|
||||
}
|
||||
|
||||
delete_file($alerts);
|
||||
foreach (glob($plugins,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) ?: basename($plugin_file,".plg");
|
||||
$user = in_array($name,$builtin);
|
||||
//switch between system and user plugins
|
||||
if (($system && !$user) || (!$system && $user)) continue;
|
||||
//OS update?
|
||||
$os = $system && $name==$builtin[0];
|
||||
if ($init || $install) {
|
||||
//icon + link
|
||||
$launch = plugin('launch',$plugin_file);
|
||||
if ($icon = plugin('icon',$plugin_file)) {
|
||||
if (substr($icon,-4)=='.png') {
|
||||
if (file_exists("plugins/$name/images/$icon")) {
|
||||
$icon = "plugins/$name/images/$icon";
|
||||
} elseif (file_exists("plugins/$name/$icon")) {
|
||||
$icon = "plugins/$name/$icon";
|
||||
} else {
|
||||
$icon = "plugins/dynamix.plugin.manager/images/dynamix.plugin.manager.png";
|
||||
}
|
||||
$icon = "<img src='/$icon' class='list'>";
|
||||
} elseif (substr($icon,0,5)=='icon-') {
|
||||
$icon = "<i class='$icon list'></i>";
|
||||
} else {
|
||||
if (substr($icon,0,3)!='fa-') $icon = "fa-$icon";
|
||||
$icon = "<i class='fa $icon list'></i>";
|
||||
}
|
||||
$link = $launch ? "<a href='/$launch' class='list'>$icon</a>" : $icon;
|
||||
} else {
|
||||
$icon = icon($name);
|
||||
$link = $launch ? "<a href='/$launch' class='list'><img src='/$icon' class='list'></a>" : "<img src='/$icon' class='list'>";
|
||||
}
|
||||
//description
|
||||
$readme = "plugins/{$name}/README.md";
|
||||
$desc = file_exists($readme) ? Markdown(file_get_contents($readme)) : Markdown("**{$name}**");
|
||||
//support
|
||||
$support = plugin('support',$plugin_file) ?: "";
|
||||
$support = $support ? "<a href='$support' target='_blank'>"._('Support Thread')."</a>" : "";
|
||||
//author
|
||||
$author = plugin('author',$plugin_file) ?: _('anonymous');
|
||||
//version
|
||||
$version = plugin('version',$plugin_file) ?: _('unknown');
|
||||
$date = str_replace('.','',$version);
|
||||
//category
|
||||
$category = plugin('category',$plugin_file) ?: (strpos($version,'-')!==false ? 'next' : 'stable');
|
||||
//status
|
||||
$status = $check ? _('unknown') : _('checking').'...';
|
||||
$id = str_replace('.','-',$name);
|
||||
$empty = false;
|
||||
echo "<tr id=\"".str_replace(['.',' ','_'],'',basename($plugin_file,'.plg'))."\">";
|
||||
echo "<td>$link</td>";
|
||||
echo "<td><span class='desc_readmore' style='display:block'>$desc</span> $support</td>";
|
||||
echo "<td>$author</td>";
|
||||
echo "<td id='vid-$id' data='$date'>$version <span class='fa fa-info-circle fa-fw big blue-text'></span></td>";
|
||||
echo "<td id='sid-$id' data='0'><span style='color:#267CA8'><i class='fa fa-refresh fa-spin fa-fw'></i> $status</span></td>";
|
||||
echo "<td>";
|
||||
if ($os) {
|
||||
$regular = ['stable','next'];
|
||||
echo "<select id='change_branch' class='auto' onchange='update_table(this.value)'>";
|
||||
foreach ($regular as $choice) echo mk_options($category,$choice);
|
||||
if (!in_array($category,$regular)) echo mk_options($category,$category);
|
||||
echo "</select>";
|
||||
} else {
|
||||
echo make_link('remove',basename($plugin_file));
|
||||
}
|
||||
echo "</td>";
|
||||
echo "</tr>";
|
||||
} else {
|
||||
//forced plugin check?
|
||||
$checked = (!$audit && !$check) ? check_plugin(basename($plugin_file),$ncsi) : true;
|
||||
$past = false;
|
||||
//toggle stable/next release?
|
||||
if ($os && $branch) {
|
||||
$past = plugin('version',$plugin_file);
|
||||
$tmp_plg = "$name-.plg";
|
||||
$tmp_file = "/var/tmp/$name.plg";
|
||||
copy($plugin_file,$tmp_file);
|
||||
exec("sed -ri 's|^(<!ENTITY category).*|\\1 \"{$branch}\">|' $tmp_file");
|
||||
symlink($tmp_file,"/var/log/plugins/$tmp_plg");
|
||||
$next = array_filter(explode("\n",check_plugin($tmp_plg,$ncsi)),function($row){return is_numeric($row[0]);});
|
||||
$next = end($next);
|
||||
if (version_compare($next,$past,'>')) {
|
||||
copy("/tmp/plugins/$tmp_plg",$tmp_file);
|
||||
$plugin_file = $tmp_file;
|
||||
}
|
||||
}
|
||||
//version
|
||||
$version = plugin('version',$plugin_file) ?: _('unknown');
|
||||
$date = str_replace('.','',$version);
|
||||
//status
|
||||
$status = "<span class='orange-text'><i class='fa fa-unlink fa-fw'></i> "._('not available')."</span>";
|
||||
//compare
|
||||
$changes_file = $plugin_file;
|
||||
$url = plugin('pluginURL',$plugin_file);
|
||||
if ($url !== false) {
|
||||
$filename = "/tmp/plugins/".(($os && $branch) ? $tmp_plg : basename($url));
|
||||
if ($checked && file_exists($filename)) {
|
||||
if ($past && $past != $version) {
|
||||
$status = make_link('install',$plugin_file,'forced');
|
||||
} else {
|
||||
$latest = plugin('version',$filename);
|
||||
if ($os ? version_compare($latest,$version,'>') : strcmp($latest,$version) > 0) {
|
||||
$version .= "<br><span class='red-text'>$latest</span>";
|
||||
$error = null;
|
||||
if ( ! $os && (version_compare(plugin("min",$filename,$error) ?: "1.0",$Unraid['version'],">") || version_compare(plugin("max",$filename,$error) ?: "999.9.9",$Unraid['version'],"<") ) ) {
|
||||
$status = "<span class='warning'><i class='fa fa-exclamation-triangle' aria-hidden='true'></i> "._("Update Incompatible")."</span>";
|
||||
} else {
|
||||
$status = make_link("update",basename($plugin_file));
|
||||
}
|
||||
$changes_file = $filename;
|
||||
if (!$os) $updates++;
|
||||
} else {
|
||||
//status is considered outdated when older than 1 day
|
||||
$status = filectime($filename) > (time()-86400) ? "<span class='green-text'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>" : "<span class='orange-text'><i class='fa fa-flash fa-fw'></i> "._('need check')."</span>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (strpos($status,'update')!==false) $rank = '0';
|
||||
elseif (strpos($status,'install')!==false) $rank = '1';
|
||||
elseif ($status=='need check') $rank = '2';
|
||||
elseif ($status=='up-to-date') $rank = '3';
|
||||
else $rank = '4';
|
||||
if (($changes = plugin('changes',$changes_file)) !== false) {
|
||||
$txtfile = "/tmp/plugins/".basename($plugin_file,'.plg').".txt";
|
||||
file_put_contents($txtfile,$changes);
|
||||
$version .= " <span class='fa fa-info-circle fa-fw big blue-text' title='"._('View Release Notes')."' onclick=\"openChanges('showchanges $txtfile','"._('Release Notes')."')\"></span>";
|
||||
}
|
||||
if ($rank < 2 && ($alert = plugin('alert',$changes_file)) !== false) {
|
||||
// generate alert message (if existing) when newer version is available
|
||||
file_put_contents($alerts,($os ? "" : "## $name\n\n").$alert."\n\n",FILE_APPEND);
|
||||
}
|
||||
//write plugin information
|
||||
$empty = false;
|
||||
$id = str_replace('.','-',$name);
|
||||
echo "vid-$id::$date::$version\rsid-$id::$rank::$status\n";
|
||||
//remove temporary symlink
|
||||
@unlink("/var/log/plugins/$tmp_plg");
|
||||
}
|
||||
}
|
||||
if ($empty) echo "<tr><td colspan='6' style='text-align:center;padding-top:12px'><i class='fa fa-check-square-o icon'></i> "._('No plugins installed')."</td><tr>";
|
||||
if (!$init && !($os??false)) echo "\0".$updates;
|
||||
?>
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: "/usr/local/emhttp";
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
|
||||
|
||||
function readJson($file) {
|
||||
return is_file($file) ? json_decode(file_get_contents($file),true) : [];
|
||||
}
|
||||
function newurl($url) {
|
||||
$oldURL = 'https://raw.github.com/';
|
||||
$newURL = 'https://raw.githubusercontent.com/';
|
||||
return str_replace($oldURL,$newURL,$url);
|
||||
}
|
||||
function searchLink(&$db,$url) {
|
||||
if ($url) for ($i = 0; $i < count($db); $i++) if ($db[$i]['PluginURL']==$url) return $db[$i]['Support'];
|
||||
}
|
||||
|
||||
$type = $argv[1]??''; // plugin or language
|
||||
$method = $argv[2]??''; // install, update, remove, check
|
||||
$name = $argv[3]??''; // plugin name (*.plg) or language name (*.xml)
|
||||
$error = $argv[4]??''; // error code (empty if none)
|
||||
|
||||
$plugin = "/boot/config/plugins/$name";
|
||||
$pending = "/tmp/plugins/pluginPending";
|
||||
|
||||
switch ($type) {
|
||||
case 'plugin':
|
||||
switch ($method) {
|
||||
case 'install':
|
||||
case 'update':
|
||||
// abort if method was unsuccessful
|
||||
if ($error) break;
|
||||
// update support link in plugin file
|
||||
$info = readJson('/tmp/community.applications/tempFiles/templates.json');
|
||||
// find matching support link
|
||||
$url = plugin('pluginURL', $plugin);
|
||||
if ($support = searchLink($info, $url) ?: searchLink($info, newurl($url))) {
|
||||
// update incorrect or missing support links
|
||||
if (plugin('support', $plugin) != $support) {
|
||||
$xml = @simplexml_load_file($plugin);
|
||||
if ($xml->xpath('//PLUGIN/@support')[0]??false) {
|
||||
// support link exists, update it
|
||||
$xml->xpath('//PLUGIN/@support')[0] = $support;
|
||||
} else {
|
||||
// support link is missing, add it
|
||||
$xml->addAttribute('support', $support);
|
||||
}
|
||||
echo "Updating support link\n";
|
||||
file_put_contents($plugin, $xml->saveXML());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'language':
|
||||
// nothing defined
|
||||
break;
|
||||
}
|
||||
|
||||
// unset pending status
|
||||
if ($method != 'check') @unlink("$pending/$name");
|
||||
?>
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: "/usr/local/emhttp";
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
|
||||
|
||||
$type = $argv[1]??''; // plugin or language
|
||||
$method = $argv[2]??''; // install, update, remove, check
|
||||
$name = $argv[3]??''; // plugin name (*.plg) or language name (*.xml)
|
||||
|
||||
$plugin = "/boot/config/plugins/$name";
|
||||
$pending = "/tmp/plugins/pluginPending";
|
||||
$unraid = ['unRAIDServer.plg','unRAIDServer-.plg'];
|
||||
|
||||
// set pending status
|
||||
if (!is_dir($pending)) mkdir($pending);
|
||||
if ($method != 'check') file_put_contents("$pending/$name",$method);
|
||||
|
||||
switch ($type) {
|
||||
case 'plugin':
|
||||
switch ($method) {
|
||||
case 'update':
|
||||
// implicit validation on plugin update (not applicable to OS updates)
|
||||
if (in_array($name, $unraid)) break;
|
||||
if (@readlink("/var/log/plugins/$name")) plugin('check', $name);
|
||||
break;
|
||||
case 'check':
|
||||
// validate plugin update (not applicable to OS updates)
|
||||
if (in_array($name, $unraid)) break;
|
||||
$new_plugin = "/tmp/plugins/$name";
|
||||
if (plugin('version', $new_plugin) > plugin('version', $plugin)) {
|
||||
echo "Validating $name update\n";
|
||||
if (($status = plugin('validate', $new_plugin)) != 'valid') {
|
||||
echo "$status\n";
|
||||
// restore original plugin and undo update
|
||||
copy($plugin, $new_plugin);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'language':
|
||||
// nothing defined
|
||||
break;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2019-2023, Andrew Zawadzki.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
//add translations
|
||||
$_SERVER['REQUEST_URI'] = "plugins";
|
||||
require_once "$docroot/plugins/dynamix/include/Translations.php";
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
|
||||
require_once "$docroot/plugins/dynamix/include/Secure.php";
|
||||
|
||||
function download_url($url, $path = "") {
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch,[
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_FRESH_CONNECT => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 15,
|
||||
CURLOPT_TIMEOUT => 45,
|
||||
CURLOPT_ENCODING => "",
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true
|
||||
]);
|
||||
$out = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
if ( $path ) file_put_contents($path,$out);
|
||||
return $out ?: false;
|
||||
}
|
||||
|
||||
switch ($_POST['action']) {
|
||||
case 'checkPlugin':
|
||||
$options = $_POST['options'] ?? '';
|
||||
$plugin = $options['plugin'] ?? '';
|
||||
|
||||
$name = unbundle($options['name'] ?? $plugin);
|
||||
$file = "/boot/config/plugins/$plugin";
|
||||
$file = realpath($file)==$file ? $file : "";
|
||||
|
||||
if ( ! $plugin || ! file_exists($file) ) {
|
||||
echo json_encode(["updateAvailable"=>false]);
|
||||
break;
|
||||
}
|
||||
|
||||
exec("mkdir -p /tmp/plugins");
|
||||
@unlink("/tmp/plugins/$plugin");
|
||||
$url = plugin("pluginURL","/boot/config/plugins/$plugin");
|
||||
download_url($url,"/tmp/plugins/$plugin");
|
||||
|
||||
$changes = plugin("changes","/tmp/plugins/$plugin");
|
||||
$alerts = plugin("alert","/tmp/plugins/$plugin");
|
||||
$version = plugin("version","/tmp/plugins/$plugin");
|
||||
$installedVersion = plugin("version","/boot/config/plugins/$plugin");
|
||||
$min = plugin("min","/tmp/plugins/$plugin") ?: "6.4.0";
|
||||
if ( $changes ) {
|
||||
file_put_contents("/tmp/plugins/".pathinfo($plugin, PATHINFO_FILENAME).".txt",$changes);
|
||||
} else {
|
||||
@unlink("/tmp/plugins/".pathinfo($plugin, PATHINFO_FILENAME).".txt");
|
||||
}
|
||||
if ( $alerts ) {
|
||||
file_put_contents('/tmp/plugins/my_alerts.txt',$alerts);
|
||||
} else {
|
||||
@unlink('/tmp/plugins/my_alerts.txt');
|
||||
}
|
||||
|
||||
$update = false;
|
||||
if ( strcmp($version,$installedVersion) > 0 ) {
|
||||
$unraid = parse_ini_file("/etc/unraid-version");
|
||||
$update = version_compare($min,$unraid['version'],'<=');
|
||||
}
|
||||
$updateMessage = sprintf(_("%s: An update is available."),$name);
|
||||
$linkMessage = sprintf(_("Click here to install version %s"),$version);
|
||||
|
||||
echo json_encode(["updateAvailable"=>$update, "version"=>$version, "min"=>$min, "alert"=>$alerts, "changes"=>$changes, "installedVersion"=>$installedVersion, "updateMessage"=>$updateMessage, "linkMessage"=>$linkMessage]);
|
||||
break;
|
||||
|
||||
case 'addRebootNotice':
|
||||
$message = htmlspecialchars(trim($_POST['message']));
|
||||
if ( ! $message ) break;
|
||||
|
||||
$existing = @file("/tmp/reboot_notifications",FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
$existing[] = $message;
|
||||
|
||||
file_put_contents("/tmp/reboot_notifications",implode("\n",array_unique($existing)));
|
||||
break;
|
||||
|
||||
case 'removeRebootNotice':
|
||||
$message = htmlspecialchars(trim($_POST['message']));
|
||||
$existing = file_get_contents("/tmp/reboot_notifications");
|
||||
$newReboots = str_replace($message,"",$existing);
|
||||
file_put_contents("/tmp/reboot_notifications",$newReboots);
|
||||
break;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'plugins';
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$nchan = $argv[$argc-1] == 'nchan'; // console or nchan output
|
||||
|
||||
function write(...$messages){
|
||||
global $nchan;
|
||||
if ($nchan) {
|
||||
$com = curl_init();
|
||||
curl_setopt_array($com,[
|
||||
CURLOPT_URL => 'http://localhost/pub/plugins?buffer_length=1',
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
]);
|
||||
foreach ($messages as $message) {
|
||||
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
|
||||
curl_exec($com);
|
||||
}
|
||||
curl_close($com);
|
||||
} else {
|
||||
foreach ($messages as $message) echo $message;
|
||||
}
|
||||
}
|
||||
|
||||
write(_("Checking connectivity")." ...\n");
|
||||
if (exec("wget --spider --no-check-certificate -nv -T10 -t1 https://www.msftncsi.com/ncsi.txt 2>&1|grep -om1 'OK'")) {
|
||||
$check = popen('plugin checkall','r');
|
||||
while (!feof($check)) write(fgets($check));
|
||||
pclose($check);
|
||||
} else {
|
||||
write(_("No response, aborting")."!\n");
|
||||
}
|
||||
if ($nchan) write('_DONE_','');
|
||||
?>
|
||||
@@ -0,0 +1,442 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
// Copyright 2005-2023, Lime Technology
|
||||
// License: GPLv2 only
|
||||
//
|
||||
// Program updates made by Bergware International (April 2020)
|
||||
// Program updates made by Bergware International (June 2022)
|
||||
|
||||
$usage = <<<EOF
|
||||
Process language files.
|
||||
|
||||
Usage: language install LANGUAGE-FILE
|
||||
install a language
|
||||
|
||||
Usage: language [attribute name] LANGUAGE-FILE
|
||||
obtain an attribute value
|
||||
|
||||
Usage: language check LANGUAGE
|
||||
check and output the latest version of the language
|
||||
|
||||
Usage: language update LANGUAGE
|
||||
update the language
|
||||
|
||||
Usage: language remove LANGUAGE
|
||||
remove the language
|
||||
|
||||
Usage: language checkall
|
||||
check the latest version of all installed languages
|
||||
|
||||
Usage: language updateall
|
||||
update all installed languages
|
||||
|
||||
EOF;
|
||||
|
||||
// Error code to description (wget)
|
||||
// ref: https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html
|
||||
//
|
||||
function error_desc($code) {
|
||||
switch($code) {
|
||||
case 0: return 'No errors';
|
||||
case -1: return 'Generic error';
|
||||
case 1: return 'Generic error';
|
||||
case 2: return 'Parse error';
|
||||
case 3: return 'File I/O error';
|
||||
case 4: return 'Network failure';
|
||||
case 5: return 'SSL verification failure';
|
||||
case 6: return 'Username/password authentication failure';
|
||||
case 7: return 'Protocol errors';
|
||||
case 8: return 'Invalid URL / Server error response';
|
||||
default: return 'Error code '.$code;
|
||||
}
|
||||
}
|
||||
|
||||
// Signal DONE to caller
|
||||
//
|
||||
function done($code) {
|
||||
global $nchan;
|
||||
if ($nchan) write('_DONE_','');
|
||||
exit($code);
|
||||
}
|
||||
|
||||
// Function to write either to console (echo) or nchan (curl)
|
||||
// Default output is console, use optional parameter "nchan" to write to nchan instead
|
||||
//
|
||||
function write(...$messages){
|
||||
global $nchan;
|
||||
if ($nchan) {
|
||||
$com = curl_init();
|
||||
curl_setopt_array($com,[
|
||||
CURLOPT_URL => 'http://localhost/pub/plugins?buffer_length=1',
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
]);
|
||||
foreach ($messages as $message) {
|
||||
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
|
||||
curl_exec($com);
|
||||
}
|
||||
curl_close($com);
|
||||
} else {
|
||||
foreach ($messages as $message) echo $message;
|
||||
}
|
||||
}
|
||||
|
||||
// Run command and obtain output
|
||||
//
|
||||
function run($command) {
|
||||
$run = popen($command,'r');
|
||||
while (!feof($run)) write(fgets($run));
|
||||
return pclose($run);
|
||||
}
|
||||
|
||||
// Run hooked scripts before correct execution of "method"
|
||||
// method = install, update, remove, check
|
||||
// hook programs receives three parameters: type=language and method and language-name
|
||||
//
|
||||
function pre_hooks() {
|
||||
global $method, $name;
|
||||
$language = (pathinfo($name)['extension']??'')=='xml' ? $name : "lang-$name.xml";
|
||||
$hooks = "/usr/local/emhttp/plugins/dynamix.plugin.manager/pre-hooks";
|
||||
foreach (glob("$hooks/*") as $hook) if (is_executable($hook)) {
|
||||
write("Executing hook script: ".basename($hook)."\n");
|
||||
run("$hook language $method $language");
|
||||
}
|
||||
}
|
||||
|
||||
// Run hooked scripts after successful or failed completion of "method"
|
||||
// method = install, update, remove, check
|
||||
// hook programs receives four parameters: type=language and method and language-name and error (empty if none)
|
||||
//
|
||||
function post_hooks($error='') {
|
||||
global $method, $name;
|
||||
$language = (pathinfo($name)['extension']??'')=='xml' ? $name : "lang-$name.xml";
|
||||
$hooks = "/usr/local/emhttp/plugins/dynamix.plugin.manager/post-hooks";
|
||||
foreach (glob("$hooks/*") as $hook) if (is_executable($hook)) {
|
||||
write("Executing hook script: ".basename($hook)."\n");
|
||||
run("$hook language $method $language $error");
|
||||
}
|
||||
}
|
||||
|
||||
// Download a file from a URL.
|
||||
// Returns TRUE if success else FALSE and fills in error.
|
||||
//
|
||||
function download($url, $name, &$error) {
|
||||
$plg = basename($url);
|
||||
if ($file = popen("wget --compression=auto --no-cache --progress=dot -O $name $url 2>&1", 'r')) {
|
||||
write("language: downloading: $plg ...\r");
|
||||
$level = -1;
|
||||
while (!feof($file)) {
|
||||
if (preg_match('/\d+%/', fgets($file), $matches)) {
|
||||
$percentage = substr($matches[0],0,-1);
|
||||
if ($percentage > $level) {
|
||||
write("language: downloading: $plg ... $percentage%\r");
|
||||
$level = $percentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (($perror = pclose($file)) == 0) {
|
||||
write("language: downloading: $plg ... done\n");
|
||||
return true;
|
||||
} else {
|
||||
$error = "$plg download failure (".error_desc($perror).")";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$error = "$plg failed to open";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Deal with logging message.
|
||||
//
|
||||
function logger($message) {
|
||||
shell_exec("logger $message");
|
||||
}
|
||||
|
||||
// Interpret a language file
|
||||
// Returns TRUE if success, else FALSE and fills in error string.
|
||||
//
|
||||
function language($method, $xml_file, &$error) {
|
||||
global $docroot, $boot, $plugins, $tmp;
|
||||
|
||||
// parse language XML file
|
||||
$xml = file_exists($xml_file) ? @simplexml_load_file($xml_file,NULL,LIBXML_NOCDATA) : false;
|
||||
if ($xml === false) {
|
||||
$error = "XML file doesn't exist or xml parse error";
|
||||
return false;
|
||||
}
|
||||
switch ($method) {
|
||||
case 'install':
|
||||
$url = $xml->LanguageURL;
|
||||
$name = $xml->LanguagePack;
|
||||
$save = "$boot/dynamix/lang-$name.zip";
|
||||
if (!file_exists($save)) {
|
||||
if ($url) {
|
||||
if (!download($url, $save, $error)) {
|
||||
@unlink($save);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$error = "missing URL";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$path = "$docroot/languages/$name";
|
||||
exec("mkdir -p $path");
|
||||
@unlink("$docroot/webGui/javascript/translate.$name.js");
|
||||
foreach (glob("$path/*.dot",GLOB_NOSORT) as $dot_file) unlink($dot_file);
|
||||
exec("unzip -qqLjo -d $path $save", $dummy, $err);
|
||||
if ($err > 1) {
|
||||
@unlink($save);
|
||||
exec("rm -rf $path");
|
||||
$error = "unzip failed. Error code $err";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case 'remove':
|
||||
$name = $xml->LanguagePack;
|
||||
if ($name) {
|
||||
$path = "$docroot/languages/$name";
|
||||
exec("rm -rf $path");
|
||||
@unlink("$docroot/webGui/javascript/translate.$name.js");
|
||||
@unlink("$boot/lang-$name.xml");
|
||||
@unlink("$plugins/lang-$name.xml");
|
||||
@unlink("$tmp/lang-$name.xml");
|
||||
@unlink("$boot/dynamix/lang-$name.zip");
|
||||
return true;
|
||||
} else {
|
||||
$error = "missing language pack";
|
||||
return false;
|
||||
}
|
||||
case 'dump':
|
||||
// dump file: debugging
|
||||
write(print_r($xml,true));
|
||||
return true;
|
||||
default:
|
||||
// return single attribute
|
||||
$error = "$method attribute not present";
|
||||
return $xml->$method ?: false;
|
||||
}
|
||||
}
|
||||
|
||||
$docroot = '/usr/local/emhttp';
|
||||
$boot = '/boot/config/plugins';
|
||||
$plugins = '/var/log/plugins';
|
||||
$tmp = '/tmp/plugins';
|
||||
$method = $argv[1];
|
||||
$nchan = $argv[$argc-1] == 'nchan'; // console or nchan output
|
||||
|
||||
// MAIN - single argument
|
||||
if ($argc < 2) {
|
||||
write($usage);
|
||||
done(1);
|
||||
}
|
||||
|
||||
// language checkall
|
||||
// check all installed languages
|
||||
//
|
||||
if ($method == 'checkall') {
|
||||
write("language: checking all language packs\n");
|
||||
foreach (glob("$plugins/lang-*.xml", GLOB_NOSORT) as $link) {
|
||||
$lang_file = @readlink($link);
|
||||
if ($lang_file === false) continue;
|
||||
if (language('LanguageURL', $lang_file, $error) === false) continue;
|
||||
$name = str_replace('lang-', '', basename($lang_file, '.xml'));
|
||||
$lang = language('Language', $lang_file, $error) ?: $name;
|
||||
write("language: checking $lang language pack ...\n");
|
||||
exec(realpath($argv[0])." check $name >/dev/null");
|
||||
}
|
||||
write("language: checking finished.\n");
|
||||
done(0);
|
||||
}
|
||||
|
||||
// language updateall
|
||||
// update all installed languages
|
||||
//
|
||||
if ($method == 'updateall') {
|
||||
write("language: updating all language packs\n");
|
||||
foreach (glob("$plugins/lang-*.xml", GLOB_NOSORT) as $link) {
|
||||
$lang_file = @readlink($link);
|
||||
if ($lang_file === false) continue;
|
||||
if (language('LanguageURL', $lang_file, $error) === false) continue;
|
||||
$version = language('Version', $lang_file, $error);
|
||||
$name = str_replace('lang-', '', basename($lang_file, '.xml'));
|
||||
$lang = language('Language', $lang_file, $error) ?: $name;
|
||||
$latest = language('Version', "$tmp/lang-$name.xml", $error);
|
||||
// update only when newer
|
||||
if (strcmp($latest, $version) > 0) {
|
||||
write("language: updating $lang language pack ...\n");
|
||||
exec(realpath($argv[0])." update $name >/dev/null");
|
||||
}
|
||||
}
|
||||
write("language: updating finished.\n");
|
||||
done(0);
|
||||
}
|
||||
|
||||
// MAIN - two arguments
|
||||
if ($argc < 3) {
|
||||
write($usage);
|
||||
done(1);
|
||||
}
|
||||
|
||||
// language install [language_file]
|
||||
//
|
||||
if ($method == 'install') {
|
||||
$argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]);
|
||||
$name = basename($argv[2]);
|
||||
write("language: installing language pack\n");
|
||||
// check for URL
|
||||
if (preg_match('#^https?://#',$argv[2])) {
|
||||
$langURL = $argv[2];
|
||||
$xml_file = "$tmp/$name";
|
||||
write("language: downloading: $name\n");
|
||||
if (!download($langURL, $xml_file, $error)) {
|
||||
write("language: $error\n");
|
||||
@unlink($xml_file);
|
||||
done(1);
|
||||
}
|
||||
} else {
|
||||
$xml_file = realpath($argv[2]);
|
||||
}
|
||||
$link_file = "$plugins/$name";
|
||||
$lang_file = "$boot/$name";
|
||||
@unlink($link_file);
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
if (language('install', $xml_file, $error) === false) {
|
||||
write("language: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
$lang = language('Language', $xml_file, $error) ?: substr($name,0,-4);
|
||||
copy($xml_file, $lang_file);
|
||||
symlink($lang_file, $link_file);
|
||||
write("language: $lang language pack installed\n");
|
||||
logger("language: $lang language pack installed");
|
||||
// run hook scripts for post processing
|
||||
post_hooks();
|
||||
done(0);
|
||||
}
|
||||
|
||||
// language check [language]
|
||||
//
|
||||
if ($method == 'check') {
|
||||
$name = $argv[2];
|
||||
write("language: checking language pack\n");
|
||||
$link_file = "$plugins/lang-$name.xml";
|
||||
$lang_file = @readlink($link_file);
|
||||
if ($lang_file === false) {
|
||||
write("language: $name language pack not installed\n");
|
||||
done(1);
|
||||
}
|
||||
$templateURL = language('TemplateURL', $lang_file, $error);
|
||||
if ($templateURL === false) {
|
||||
write("language: $error\n");
|
||||
done(1);
|
||||
}
|
||||
$xml_file = "$tmp/lang-$name.xml";
|
||||
if (!download($templateURL, $xml_file, $error)) {
|
||||
write("language: $error\n");
|
||||
@unlink($xml_file);
|
||||
done(1);
|
||||
}
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
$version = language('Version', $xml_file, $error);
|
||||
if ($version === false) {
|
||||
write("language: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
write("$version\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks();
|
||||
done(0);
|
||||
}
|
||||
|
||||
// language update [language]
|
||||
//
|
||||
if ($method == 'update') {
|
||||
$name = $argv[2];
|
||||
write("language: updating language pack\n");
|
||||
$link_file = "$plugins/lang-$name.xml";
|
||||
$lang_file = @readlink($link_file);
|
||||
if ($lang_file === false) {
|
||||
write("language: $name language pack not installed\n");
|
||||
done(1);
|
||||
}
|
||||
// verify previous check has been done
|
||||
$xml_file = "$tmp/lang-$name.xml";
|
||||
if (!file_exists($xml_file)) {
|
||||
write("language: update does not exist, perform a check first\n");
|
||||
exit (1);
|
||||
}
|
||||
$lang = language('Language', $xml_file, $error) ?: $name;
|
||||
// check for a reinstall of same version
|
||||
$old_version = language('Version', $lang_file, $error);
|
||||
$new_version = language('Version', $xml_file, $error);
|
||||
if ($new_version == $old_version) {
|
||||
write("language: $lang language pack not reinstalling same version\n");
|
||||
done(1);
|
||||
}
|
||||
// install the updated plugin
|
||||
@unlink("$boot/dynamix/lang-$name.zip");
|
||||
@unlink($link_file);
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
if (language('install', $xml_file, $error) === false) {
|
||||
write("language: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
copy($xml_file, $lang_file);
|
||||
symlink($lang_file, $link_file);
|
||||
write("language: $lang language pack updated\n");
|
||||
logger("language: $lang language pack updated");
|
||||
// run hook scripts for post processing
|
||||
post_hooks();
|
||||
done(0);
|
||||
}
|
||||
|
||||
// language remove [language]
|
||||
//
|
||||
if ($method == 'remove') {
|
||||
$name = $argv[2];
|
||||
write("language: removing language pack: $name\n");
|
||||
$link_file = "$plugins/lang-$name.xml";
|
||||
$lang_file = @readlink($link_file);
|
||||
if ($lang_file === false) {
|
||||
write("language: $name language pack not installed\n");
|
||||
done(1);
|
||||
}
|
||||
$lang = language('Language', $lang_file, $error) ?: $name;
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
if (language('remove', $lang_file, $error) === false) {
|
||||
write("language: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
write("language: $lang language pack removed\n");
|
||||
logger("language: $lang language pack removed");
|
||||
// run hook scripts for post processing
|
||||
post_hooks();
|
||||
done(0);
|
||||
}
|
||||
|
||||
// return attribute
|
||||
//
|
||||
$xml_file = $argv[2];
|
||||
$value = language($method, $xml_file, $error);
|
||||
if ($value === false) {
|
||||
write("language: $error\n");
|
||||
done(1);
|
||||
}
|
||||
write("$value\n");
|
||||
done(0);
|
||||
?>
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
|
||||
extract(parse_plugin_cfg('dynamix', true));
|
||||
|
||||
// Multi-language support
|
||||
$_SERVER['REQUEST_URI'] = "scripts";
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$var = @parse_ini_file('/var/local/emhttp/var.ini') ?: [];
|
||||
|
||||
function apos($text) {
|
||||
// So that "'" doesn't show up in email
|
||||
return str_replace("'","'",$text);
|
||||
}
|
||||
|
||||
$server = strtoupper($var['NAME']);
|
||||
$output = _var($notify,'language_notify');
|
||||
$script = "$docroot/webGui/scripts/notify";
|
||||
$tmp = '/tmp/plugins';
|
||||
$plugins = '/var/log/plugins';
|
||||
|
||||
language('checkall');
|
||||
foreach (glob("/$tmp/lang-*.xml", GLOB_NOSORT) as $file) {
|
||||
$name = basename($file);
|
||||
$lang = language('LanguageLocal', $file);
|
||||
$new = language('Version', $file);
|
||||
$old = language('Version', "$plugins/$name");
|
||||
|
||||
// silently suppress bad download of XML file
|
||||
if (strcmp($new, $old) > 0) {
|
||||
$event = apos(_("Language")." - $lang [$new]");
|
||||
$subject = apos(sprintf(_("Notice [%s] - Version update %s"),$server,$new));
|
||||
$description = apos(sprintf(_("A new version of %s is available"),$lang));
|
||||
exec("$script -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i ".escapeshellarg("normal $output")." -l '/Apps' -x");
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
?>
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$method = $argv[1]??'';
|
||||
$plugins = explode('*',$argv[2]??'');
|
||||
$nchan = $argv[$argc-1] == 'nchan'; // console or nchan output
|
||||
$call = ['plg' => 'plugin', 'xml' => 'language', '' => 'language'];
|
||||
|
||||
function write(...$messages){
|
||||
global $nchan;
|
||||
if ($nchan) {
|
||||
$com = curl_init();
|
||||
curl_setopt_array($com,[
|
||||
CURLOPT_URL => 'http://localhost/pub/plugins?buffer_length=1',
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
]);
|
||||
foreach ($messages as $message) {
|
||||
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
|
||||
curl_exec($com);
|
||||
}
|
||||
curl_close($com);
|
||||
} else {
|
||||
foreach ($messages as $message) echo $message;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($plugins as $plugin) {
|
||||
if (!$plugin || (!$cmd = $call[pathinfo($plugin,PATHINFO_EXTENSION)])) continue;
|
||||
$line = '';
|
||||
$pluginArg = $method == "update" ? basename($plugin) : $plugin;
|
||||
$run = popen("$cmd $method $pluginArg",'r');
|
||||
while (!feof($run)) {
|
||||
$line .= fgetc($run);
|
||||
if (in_array($line[-1],["\r","\n"])) {write($line); $line = '';}
|
||||
}
|
||||
pclose($run);
|
||||
write("\n");
|
||||
}
|
||||
if ($nchan) write('_DONE_','');
|
||||
?>
|
||||
@@ -0,0 +1,887 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
// Copyright 2005-2022, Lime Technology
|
||||
// License: GPLv2 only
|
||||
//
|
||||
// Program updates made by Bergware International (April 2020)
|
||||
// Program updates made by Bergware International (June 2022)
|
||||
|
||||
$usage = <<<EOF
|
||||
Process plugin files.
|
||||
|
||||
Usage: plugin install PLUGIN-FILE [forced]
|
||||
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.
|
||||
|
||||
forced is optional and can be used to install a lower version than currently running.
|
||||
|
||||
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 extract 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/log/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-FILE
|
||||
|
||||
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.
|
||||
|
||||
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;
|
||||
|
||||
// Error code to description (wget)
|
||||
// ref: https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html
|
||||
//
|
||||
function error_desc($code) {
|
||||
switch($code) {
|
||||
case 0: return 'No errors';
|
||||
case -1: return 'Generic error';
|
||||
case 1: return 'Generic error';
|
||||
case 2: return 'Parse error';
|
||||
case 3: return 'File I/O error';
|
||||
case 4: return 'Network failure';
|
||||
case 5: return 'SSL verification failure';
|
||||
case 6: return 'Username/password authentication failure';
|
||||
case 7: return 'Protocol errors';
|
||||
case 8: return 'Invalid URL / Server error response';
|
||||
default: return 'Error code '.$code;
|
||||
}
|
||||
}
|
||||
|
||||
// Signal DONE to caller
|
||||
//
|
||||
function done($code) {
|
||||
global $nchan;
|
||||
if ($nchan) write('_DONE_','');
|
||||
exit($code);
|
||||
}
|
||||
|
||||
// Function to write either to console (echo) or nchan (curl)
|
||||
// Default output is console, use optional parameter "nchan" to write to nchan instead
|
||||
//
|
||||
function write(...$messages){
|
||||
global $nchan;
|
||||
if ($nchan) {
|
||||
$com = curl_init();
|
||||
curl_setopt_array($com,[
|
||||
CURLOPT_URL => 'http://localhost/pub/plugins?buffer_length=1',
|
||||
CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
]);
|
||||
foreach ($messages as $message) {
|
||||
curl_setopt($com, CURLOPT_POSTFIELDS, $message);
|
||||
curl_exec($com);
|
||||
}
|
||||
curl_close($com);
|
||||
} else {
|
||||
foreach ($messages as $message) echo $message;
|
||||
}
|
||||
}
|
||||
|
||||
// Run command and obtain output
|
||||
//
|
||||
function run($command) {
|
||||
$run = popen($command,'r');
|
||||
while (!feof($run)) write(fgets($run));
|
||||
return pclose($run);
|
||||
}
|
||||
|
||||
// Run hooked scripts before correct execution of "method"
|
||||
// method = install, update, remove, check
|
||||
// hook programs receives three parameters: type=plugin and method and plugin-name
|
||||
//
|
||||
function pre_hooks() {
|
||||
global $method, $plugin;
|
||||
$hooks = "/usr/local/emhttp/plugins/dynamix.plugin.manager/pre-hooks";
|
||||
foreach (glob("$hooks/*") as $hook) if (is_executable($hook)) {
|
||||
write("Executing hook script: ".basename($hook)."\n");
|
||||
run("$hook plugin $method $plugin");
|
||||
}
|
||||
}
|
||||
|
||||
// Run hooked scripts after successful or failed completion of "method"
|
||||
// method = install, update, remove, check
|
||||
// hook programs receives four parameters: type=plugin and method and plugin-name and error (empty if none)
|
||||
//
|
||||
function post_hooks($error='') {
|
||||
global $method, $plugin;
|
||||
$hooks = "/usr/local/emhttp/plugins/dynamix.plugin.manager/post-hooks";
|
||||
foreach (glob("$hooks/*") as $hook) if (is_executable($hook)) {
|
||||
write("Executing hook script: ".basename($hook)."\n");
|
||||
run("$hook plugin $method $plugin $error");
|
||||
}
|
||||
}
|
||||
|
||||
// Download a file from a URL.
|
||||
// Returns TRUE if success else FALSE and fills in error.
|
||||
//
|
||||
function download($url, $name, &$error, $write=true) {
|
||||
if ($url) {
|
||||
$plg = basename($url);
|
||||
$plg = str_replace('"', '', $plg);
|
||||
$tries = (strpos($url, '.cdn') !== false) ? "1" : "3";
|
||||
if ($file = popen("wget --compression=auto --no-cache --progress=dot --retry-connrefused --prefer-family=IPv4 --timeout=10 --tries=$tries --waitretry=$tries -O $name $url 2>&1", 'r')) {
|
||||
if ($write) write("plugin: downloading: $plg ...\r");
|
||||
$level = -1;
|
||||
while (($line = fgets($file)) !== false) {
|
||||
if (preg_match('/ \d+% /', $line, $matches)) {
|
||||
$percentage = substr(trim($matches[0]),0,-1);
|
||||
if ($percentage > $level) {
|
||||
if ($write) write("plugin: downloading: $plg ... $percentage%\r");
|
||||
$level = $percentage;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (($perror = pclose($file)) != 0) {
|
||||
$error = "$plg download failure: ".error_desc($perror);
|
||||
return false;
|
||||
} elseif (filesize($name) == 0) {
|
||||
if ($write) write("plugin: download failure: zero-length file\r","\n");
|
||||
return false;
|
||||
} else {
|
||||
if ($write) write("plugin: downloading: $plg ... done\r","\n");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$error = "$plg failed to open";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// The url is empty
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter URL.
|
||||
// If URL contains '.cdn' remove the '.cdn' from the URL.
|
||||
// Returns URL without '.cdn' if found.
|
||||
//
|
||||
function filter_url($url) {
|
||||
if (strpos($url, '.cdn') !== false) {
|
||||
$new_url = str_replace('"', '', $url);
|
||||
$new_url = str_replace('.cdn', '', $new_url);
|
||||
} else {
|
||||
$new_url = "";
|
||||
}
|
||||
return($new_url);
|
||||
}
|
||||
|
||||
// Deal with logging message.
|
||||
//
|
||||
function logger($message) {
|
||||
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) {
|
||||
global $unraid;
|
||||
$methods = ['install', 'remove'];
|
||||
|
||||
// parse plugin definition XML file
|
||||
$xml = file_exists($plugin_file) ? @simplexml_load_file($plugin_file, NULL, LIBXML_NOCDATA) : false;
|
||||
if ($xml === false) {
|
||||
$error = "XML file doesn't exist or xml parse error";
|
||||
return false;
|
||||
}
|
||||
|
||||
// dump
|
||||
if ($method == 'dump') {
|
||||
// dump file: debugging
|
||||
write(print_r($xml,true));
|
||||
return true;
|
||||
}
|
||||
|
||||
// release notes
|
||||
if ($method == 'changes') {
|
||||
return $xml->CHANGES ? trim($xml->CHANGES) : false;
|
||||
}
|
||||
|
||||
// alert message
|
||||
if ($method == 'alert') {
|
||||
return $xml->ALERT ? trim($xml->ALERT) : false;
|
||||
}
|
||||
|
||||
// validate plugin download without installation
|
||||
if ($method == 'validate') {
|
||||
$name = '/tmp/validate-plugin.tmp';
|
||||
foreach ($xml->FILE as $file) if ($file->URL) {
|
||||
if (!$file->SHA256 and !$file->MD5) continue;
|
||||
if ( (download($file->URL, $name, $error, false) === false) && (download(filter_url($file->URL), $name, $error, false) === false) ) {
|
||||
@unlink($name);
|
||||
return false;
|
||||
}
|
||||
if (($file->SHA256 && hash_file('sha256',$name) != $file->SHA256) or ($file->MD5 && md5_file($name) != $file->MD5)) {
|
||||
$error = "bad hash value";
|
||||
@unlink($name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@unlink($name);
|
||||
return "valid";
|
||||
}
|
||||
|
||||
// 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;
|
||||
} elseif ($method != 'install') continue;
|
||||
$name = $file->attributes()->Name;
|
||||
// bergware - check Unraid version dependency (if present)
|
||||
$min = $file->attributes()->Min;
|
||||
if ($min && version_compare($unraid['version'],$min,'<')) {
|
||||
write("plugin: skipping: ".basename($name)." - Unraid version too low, requires at least version $min\n");
|
||||
continue;
|
||||
}
|
||||
$max = $file->attributes()->Max;
|
||||
if ($max && version_compare($unraid['version'],$max,'>')) {
|
||||
write("plugin: skipping: ".basename($name)." - Unraid version too high, requires at most version $max\n");
|
||||
continue;
|
||||
}
|
||||
// Name can be missing but only makes sense if Run attribute is present
|
||||
if ($name) {
|
||||
// Ensure parent directory exists
|
||||
//
|
||||
if (!file_exists(dirname($name))) {
|
||||
if (!mkdir(dirname($name), 0770, true)) {
|
||||
$error = "unable to create parent directory for $name";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If file already exists, check the SHA256/MD5 (if supplied)
|
||||
if (file_exists($name)) {
|
||||
if ($file->SHA256) {
|
||||
logger("plugin: checking: $name - SHA256");
|
||||
if (hash_file('sha256', $name) != $file->SHA256) {
|
||||
unlink($name);
|
||||
}
|
||||
} elseif ($file->MD5) {
|
||||
logger("plugin: checking: $name - MD5");
|
||||
if (md5_file($name) != $file->MD5) {
|
||||
unlink($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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) && (download(filter_url($file->URL), $name, $error) === false) ) {
|
||||
@unlink($name);
|
||||
return false;
|
||||
}
|
||||
if ($file->SHA256) {
|
||||
logger("plugin: checking: $name - SHA256");
|
||||
if (hash_file('sha256', $name) != $file->SHA256) {
|
||||
$error = "bad file SHA256: $name";
|
||||
unlink($name);
|
||||
return false;
|
||||
}
|
||||
} elseif ($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(escapeshellarg("plugin: running: $command $name"));
|
||||
$retval = run("$command $name");
|
||||
} elseif ($file->LOCAL) {
|
||||
logger(escapeshellarg("plugin: running: $command $file->LOCAL"));
|
||||
$retval = run("$command $file->LOCAL");
|
||||
} elseif ($file->INLINE) {
|
||||
logger("plugin: running: 'anonymous'");
|
||||
$name = '/tmp/inline.sh';
|
||||
file_put_contents($name, $file->INLINE);
|
||||
$retval = run("$command $name");
|
||||
unlink($name);
|
||||
}
|
||||
if ($retval != 0) {
|
||||
$error = "run failed: '$command' returned $retval";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function move($src_file, $tar_dir) {
|
||||
@mkdir($tar_dir);
|
||||
return rename($src_file, $tar_dir."/".basename($src_file));
|
||||
}
|
||||
|
||||
$notify = '/usr/local/emhttp/webGui/scripts/notify';
|
||||
$boot = '/boot/config/plugins';
|
||||
$plugins = '/var/log/plugins';
|
||||
$tmp = '/tmp/plugins';
|
||||
$method = $argv[1];
|
||||
$builtin = ['unRAIDServer','unRAIDServer-'];
|
||||
$nchan = $argv[$argc-1] == 'nchan'; // console or nchan output
|
||||
|
||||
// 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"
|
||||
//
|
||||
// MAIN - single argument
|
||||
if ($argc < 2) {
|
||||
write($usage);
|
||||
done(1);
|
||||
}
|
||||
|
||||
// plugin checkall
|
||||
// check all installed plugins, except built-in
|
||||
//
|
||||
if ($method == 'checkall') {
|
||||
if (!$cmd = realpath($argv[0])) {
|
||||
write("Unknown command: {$argv[0]}\n");
|
||||
done(1);
|
||||
}
|
||||
foreach (glob("$plugins/*.plg", GLOB_NOSORT) as $link) {
|
||||
// skip OS related plugins
|
||||
if (in_array(basename($link,'.plg'),$builtin)) continue;
|
||||
// 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);
|
||||
write("plugin: checking $plugin ...\n");
|
||||
exec("$cmd check $plugin >/dev/null");
|
||||
}
|
||||
write("plugin: checking finished.\n");
|
||||
done(0);
|
||||
}
|
||||
|
||||
// plugin updateall
|
||||
// update all installed plugins, which have a update available
|
||||
//
|
||||
if ($method == 'updateall') {
|
||||
if (!$cmd = realpath($argv[0])) {
|
||||
write("Unknown command: {$argv[0]}\n");
|
||||
done(1);
|
||||
}
|
||||
foreach (glob("$plugins/*.plg", GLOB_NOSORT) as $link) {
|
||||
// skip OS related plugins
|
||||
if (in_array(basename($link,'.plg'),$builtin)) continue;
|
||||
// only consider symlinks
|
||||
$installed_plugin_file = @readlink($link);
|
||||
if ($installed_plugin_file === false) continue;
|
||||
if (plugin('pluginURL', $installed_plugin_file, $error) === false) continue;
|
||||
$version = plugin('version', $installed_plugin_file, $error);
|
||||
$plugin = basename($installed_plugin_file);
|
||||
$latest = plugin('version', "$tmp/$plugin", $error);
|
||||
// update only when newer
|
||||
if (strcmp($latest,$version) > 0) {
|
||||
write("plugin: updating $plugin ...\n");
|
||||
exec("$cmd update $plugin >/dev/null");
|
||||
}
|
||||
}
|
||||
write("plugin: updating finished.\n");
|
||||
done(0);
|
||||
}
|
||||
|
||||
// plugin checkos
|
||||
// check built-in only
|
||||
//
|
||||
if ($method == 'checkos') {
|
||||
if (!$cmd = realpath($argv[0])) {
|
||||
write("Unknown command: {$argv[0]}\n");
|
||||
done(1);
|
||||
}
|
||||
foreach ($builtin as $link) {
|
||||
// only consider symlinks
|
||||
$installed_plugin_file = @readlink("$plugins/$link.plg");
|
||||
if ($installed_plugin_file === false) continue;
|
||||
if (plugin("pluginURL", $installed_plugin_file, $error) === false) continue;
|
||||
$plugin = basename($installed_plugin_file);
|
||||
write("plugin: checking $plugin ...\n");
|
||||
exec("$cmd check $plugin >/dev/null");
|
||||
}
|
||||
write("plugin: checking finished.\n");
|
||||
done(0);
|
||||
}
|
||||
|
||||
// MAIN - two or three arguments
|
||||
if ($argc < 3) {
|
||||
write($usage);
|
||||
done(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
|
||||
//
|
||||
$unraid = parse_ini_file('/etc/unraid-version');
|
||||
if ($method == 'install') {
|
||||
$argv[2] = preg_replace('#[\x00-\x1F\x80-\xFF]#', '', $argv[2]);
|
||||
$plugin = basename($argv[2]);
|
||||
if (pathinfo($plugin, PATHINFO_EXTENSION) != "plg") {
|
||||
write("plugin: $plugin is not a plg file\n");
|
||||
done(1);
|
||||
}
|
||||
write("plugin: installing: $plugin\n");
|
||||
// check for URL
|
||||
if (preg_match('#^https?://#',$argv[2])) {
|
||||
$pluginURL = $argv[2];
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
$plugin_file = "$tmp/$plugin";
|
||||
write("plugin: downloading: $plugin\n");
|
||||
if ( (download($pluginURL, $plugin_file, $error) === false) && (download(filter_url($pluginURL), $plugin_file, $error) === false) ) {
|
||||
write("plugin: $error\n");
|
||||
@unlink($plugin_file);
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
} else {
|
||||
$plugin_file = realpath($argv[2]);
|
||||
}
|
||||
// bergware - check Unraid version dependency (if present)
|
||||
$min = plugin('min', $plugin_file, $error);
|
||||
if ($min && version_compare($unraid['version'], $min, '<')) {
|
||||
write("plugin: installed Unraid version is too low, require at least version $min\n");
|
||||
if (dirname($plugin_file) == "$boot") {
|
||||
move($plugin_file, "$boot-error");
|
||||
}
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
$max = plugin('max', $plugin_file, $error) ?: plugin('Unraid', $plugin_file, $error);
|
||||
if ($max && version_compare($unraid['version'], $max, '>')) {
|
||||
write("plugin: installed Unraid version is too high, require at most version $max\n");
|
||||
if (dirname($plugin_file) == "$boot") {
|
||||
move($plugin_file, "$boot-error");
|
||||
}
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
$symlink = "$plugins/$plugin";
|
||||
// check for re-install
|
||||
$installed_plugin_file = @readlink($symlink);
|
||||
if ($installed_plugin_file !== false) {
|
||||
if ($plugin_file == $installed_plugin_file) {
|
||||
write("plugin: not re-installing same plugin\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
// must have version attributes for re-install
|
||||
$version = plugin('version', $plugin_file, $error);
|
||||
if ($version === false) {
|
||||
write("plugin: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
$installed_version = plugin('version', $installed_plugin_file, $error);
|
||||
if ($installed_version === false) {
|
||||
write("plugin: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
// check version installation?
|
||||
$forced = $nchan ? ($argc==5 ? $argv[4] : false) : ($argc==4 ? $argv[3] : false);
|
||||
if (!$forced) {
|
||||
// do not re-install if same plugin already installed or has higher version
|
||||
if (strcmp($version, $installed_version) < 0) {
|
||||
write("plugin: not installing older version\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
if (strcmp($version, $installed_version) === 0) {
|
||||
write("plugin: not reinstalling same version\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
}
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
if (plugin('install', $plugin_file, $error) === false) {
|
||||
write("plugin: $error\n");
|
||||
if (dirname($plugin_file) == "$boot") {
|
||||
move($plugin_file, "$boot-error");
|
||||
}
|
||||
$event = "Install error";
|
||||
$subject = "plugin: ".basename($plugin_file);
|
||||
$description = "Plugin failed to install";
|
||||
exec("$notify -e $event -s $subject -d $description) -i 2");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
// remove symlink for re-install
|
||||
unlink($symlink);
|
||||
} else {
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
// fresh install
|
||||
if (plugin('install', $plugin_file, $error) === false) {
|
||||
write("plugin: $error\n");
|
||||
if (dirname($plugin_file) == "$boot") {
|
||||
move($plugin_file, "$boot-error");
|
||||
}
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
}
|
||||
// register successful install
|
||||
$target = "$boot/$plugin";
|
||||
if (!plugin('noInstall', $plugin_file, $error)) {
|
||||
if ($target != $plugin_file) copy($plugin_file, $target);
|
||||
symlink($target, $symlink);
|
||||
write("plugin: $plugin installed\n");
|
||||
logger("plugin: $plugin installed");
|
||||
} else {
|
||||
write("script: $plugin executed\n");
|
||||
logger("script: $plugin executed");
|
||||
}
|
||||
// run hook scripts for post processing
|
||||
post_hooks();
|
||||
done(0);
|
||||
}
|
||||
|
||||
// plugin check [plugin]
|
||||
// We use the pluginURL attribute to download the latest plg file into the "/tmp/plugins/"
|
||||
// directory.
|
||||
//
|
||||
if ($method == 'check') {
|
||||
$plugin = $argv[2];
|
||||
$symlink = "$plugins/$plugin";
|
||||
write("plugin: checking: $plugin ...\n");
|
||||
$installed_plugin_file = @readlink($symlink);
|
||||
if ($installed_plugin_file === false) {
|
||||
write("plugin: not installed\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
$installed_pluginURL = plugin('pluginURL', $installed_plugin_file, $error);
|
||||
if ($installed_pluginURL === false) {
|
||||
write("plugin: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
$plugin_file = "$tmp/$plugin";
|
||||
if ( (download($installed_pluginURL, $plugin_file, $error) === false) && (download(filter_url($installed_pluginURL), $plugin_file, $error) === false) ) {
|
||||
write("plugin: $error\n");
|
||||
@unlink($plugin_file);
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
$version = plugin('version', $plugin_file, $error);
|
||||
if ($version === false) {
|
||||
write("plugin: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
write("$version\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks();
|
||||
done(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') {
|
||||
$plugin = $argv[2];
|
||||
$symlink = "$plugins/$plugin";
|
||||
write("plugin: updating: $plugin\n");
|
||||
$installed_plugin_file = @readlink($symlink);
|
||||
if ($installed_plugin_file === false) {
|
||||
write("plugin: $plugin not installed\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
// verify previous check has been done
|
||||
$plugin_file = "$tmp/$plugin";
|
||||
if (!file_exists($plugin_file)) {
|
||||
write("plugin: $plugin_file does not exist, check for updates first\n");
|
||||
exit (1);
|
||||
}
|
||||
// bergware - check Unraid version dependency (if present)
|
||||
$min = plugin('min', $plugin_file, $error);
|
||||
if ($min && version_compare($unraid['version'], $min, '<')) {
|
||||
write("plugin: installed Unraid version is too low, require at least version $min\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
$max = plugin('max', $plugin_file, $error) ?: plugin('Unraid', $plugin_file, $error);
|
||||
if ($max && version_compare($unraid['version'], $max, '>')) {
|
||||
write("plugin: installed Unraid version is too high, require at most version $max\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
// check for a reinstall of same version
|
||||
if (strcmp(plugin('version', $installed_plugin_file, $error), plugin('version', $plugin_file, $error)) === 0) {
|
||||
write("Not reinstalling same version\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
// install the updated plugin
|
||||
if (plugin('install', $plugin_file, $error) === false) {
|
||||
write("plugin: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
// install was successful, save the updated plugin so it installs again next boot
|
||||
unlink($symlink);
|
||||
$target = "$boot/$plugin";
|
||||
copy($plugin_file, $target);
|
||||
symlink($target, $symlink);
|
||||
write("plugin: $plugin updated\n");
|
||||
logger("plugin: $plugin updated");
|
||||
// run hook scripts for post processing
|
||||
post_hooks();
|
||||
done(0);
|
||||
}
|
||||
|
||||
// plugin remove [plugin]
|
||||
// only .plg files should have a remove method
|
||||
//
|
||||
if ($method == 'remove') {
|
||||
$plugin = $argv[2];
|
||||
$symlink = "$plugins/$plugin";
|
||||
write("plugin: removing: $plugin\n");
|
||||
$installed_plugin_file = @readlink($symlink);
|
||||
if ($installed_plugin_file !== false) {
|
||||
// remove the symlink
|
||||
unlink($symlink);
|
||||
@unlink("$tmp/$plugin");
|
||||
// run hook scripts for pre processing
|
||||
pre_hooks();
|
||||
if (plugin('remove', $installed_plugin_file, $error) === false) {
|
||||
// but if can't remove, restore the symlink
|
||||
if (is_file($installed_plugin_file)) symlink($installed_plugin_file, $symlink);
|
||||
write("plugin: $error\n");
|
||||
// run hook scripts for post processing
|
||||
post_hooks($error);
|
||||
done(1);
|
||||
}
|
||||
}
|
||||
// remove the plugin file
|
||||
move($installed_plugin_file, "$boot-removed");
|
||||
write("plugin: $plugin removed\n");
|
||||
logger("plugin: $plugin removed");
|
||||
exec("/usr/local/sbin/update_cron");
|
||||
// run hook scripts for post processing
|
||||
post_hooks();
|
||||
done(0);
|
||||
}
|
||||
|
||||
// return attribute
|
||||
//
|
||||
$plugin_file = $argv[2];
|
||||
$value = plugin($method, $plugin_file, $error);
|
||||
if ($value === false) {
|
||||
write("plugin: $error\n");
|
||||
done(1);
|
||||
}
|
||||
write("$value\n");
|
||||
done(0);
|
||||
?>
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
write(){
|
||||
for message in "$@"; do
|
||||
curl -sfd "$message" --unix-socket /var/run/nginx.socket http://localhost/pub/plugins?buffer_length=1 >/dev/null 2>&1
|
||||
done
|
||||
}
|
||||
|
||||
# put some restrictions on 'delete'
|
||||
if [[ $1 == /boot/config/plugins-error/* || $1 == /boot/config/plugins-stale/* ]]; then
|
||||
text="Deleting $1 ..."
|
||||
if [[ -z $2 ]]; then
|
||||
echo "$text"
|
||||
else
|
||||
write "$text " "_DONE_" ""
|
||||
fi
|
||||
rm $1
|
||||
fi
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
|
||||
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
|
||||
// Multi-language support
|
||||
$_SERVER['REQUEST_URI'] = "scripts";
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$var = @parse_ini_file('/var/local/emhttp/var.ini') ?: [];
|
||||
|
||||
function apos($text) {
|
||||
// So that "'" doesn't show up in email
|
||||
return str_replace("'","'",$text);
|
||||
}
|
||||
|
||||
$current = parse_ini_file('/etc/unraid-version');
|
||||
$server = strtoupper(_var($var,'NAME','tower'));
|
||||
$output = _var($notify,'plugin');
|
||||
$builtin = ['unRAIDServer','unRAIDServer-'];
|
||||
$script = "$docroot/webGui/scripts/notify";
|
||||
$tmp = '/tmp/plugins';
|
||||
$plugins = '/var/log/plugins';
|
||||
|
||||
plugin('checkall');
|
||||
foreach (glob("/$tmp/*.plg", GLOB_NOSORT) as $file) {
|
||||
$name = basename($file,'.plg');
|
||||
// skip OS related updates
|
||||
if (in_array($name,$builtin)) continue;
|
||||
$new = plugin('version', $file);
|
||||
$old = plugin('version', "$plugins/$name.plg");
|
||||
$min = plugin('min', $file) ?: $current['version'];
|
||||
// silently suppress bad download of PLG file
|
||||
if (strcmp($new, $old)>0 && !version_compare($min,$current['version'],">")) {
|
||||
$event = apos(_("Plugin")." - $name [$new]");
|
||||
$subject = apos(sprintf(_("Notice [%s] - Version update %s"),$server,$new));
|
||||
$description = apos(sprintf(_("A new version of %s is available"),$name));
|
||||
exec("$script -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i ".escapeshellarg("normal $output")." -l '/Plugins' -x");
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
?>
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'plugins';
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
$file = realpath($argv[1]??'');
|
||||
$valid = ['/var/tmp/','/tmp/plugins/'];
|
||||
$good = false;
|
||||
|
||||
foreach ($valid as $check) if (strncmp($file,$check,strlen($check))===0) $good = true;
|
||||
if ($file && $good && pathinfo($file)['extension']=='txt') {
|
||||
echo Markdown(file_get_contents($file));
|
||||
} else {
|
||||
echo Markdown("*"._('No release notes available')."!*");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/php -q
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
|
||||
|
||||
// Multi-language support
|
||||
if (!function_exists('_')) {
|
||||
function _($text) {return $text;}
|
||||
}
|
||||
|
||||
extract(parse_plugin_cfg('dynamix', true));
|
||||
|
||||
$var = @parse_ini_file('/var/local/emhttp/var.ini') ?: [];
|
||||
$script = "$docroot/webGui/scripts/notify";
|
||||
$server = strtoupper(_var($var,'NAME','server'));
|
||||
$output = _var($notify,'plugin');
|
||||
$builtin = ['unRAIDServer'];
|
||||
|
||||
foreach ($builtin as $name) {
|
||||
$plg = "$name.plg";
|
||||
plugin('check',$plg);
|
||||
$file = "/tmp/plugins/$plg";
|
||||
$old = plugin('version', "/var/log/plugins/$plg");
|
||||
$new = plugin('version', $file);
|
||||
|
||||
// silently suppress bad download of PLG file
|
||||
if (version_compare($new,$old,'>')) {
|
||||
exec("$script -e ".escapeshellarg("System - $name [$new]")." -s ".escapeshellarg("Notice [$server] - Version update $new")." -d ".escapeshellarg("A new version of $name is available")." -i ".escapeshellarg("normal $output")." -l '/Tools/Update' -x");
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
?>
|
||||
@@ -0,0 +1,26 @@
|
||||
Title="Add VM"
|
||||
Tag="clipboard"
|
||||
Cond="(pgrep('libvirtd')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2020, Lime Technology
|
||||
* Copyright 2015-2020, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2020, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
// add vm translations (if needed)
|
||||
if (substr($_SERVER['REQUEST_URI'],0,4) != '/VMs') {
|
||||
$vms = "$docroot/languages/$locale/vms.dot";
|
||||
if (file_exists($vms)) $language = array_merge($language,unserialize(file_get_contents($vms)));
|
||||
}
|
||||
eval('?>'.parse_file("$docroot/plugins/dynamix.vm.manager/include/VMedit.php",false));
|
||||
?>
|
||||
@@ -0,0 +1,26 @@
|
||||
Title="Update VM"
|
||||
Tag="clipboard"
|
||||
Cond="(pgrep('libvirtd')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2020, Lime Technology
|
||||
* Copyright 2015-2020, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2020, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
// add vm translations (if needed)
|
||||
if (substr($_SERVER['REQUEST_URI'],0,4) != '/VMs') {
|
||||
$vms = "$docroot/languages/$locale/vms.dot";
|
||||
if (file_exists($vms)) $language = array_merge($language,unserialize(file_get_contents($vms)));
|
||||
}
|
||||
eval('?>'.parse_file("$docroot/plugins/dynamix.vm.manager/include/VMedit.php",false));
|
||||
?>
|
||||
@@ -0,0 +1,308 @@
|
||||
Menu="VMs:1"
|
||||
Title="Virtual Machines"
|
||||
Tag="columns"
|
||||
Cond="is_file('/var/run/libvirt/libvirtd.pid')"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2015-2023, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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 "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
$cpus = cpu_list();
|
||||
$hover = in_array($theme,['white','azure']) ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.1)';
|
||||
$bgcolor = in_array($theme,['white','azure']) ? '#f2f2f2' : '#1c1c1c';
|
||||
$fgcolor = in_array($theme,['white','azure']) ? '#1c1c1c' : '#f2f2f2';
|
||||
$incolor = $theme!='gray' ? $bgcolor : '#121510';
|
||||
|
||||
function showCPUs($uuid) {
|
||||
global $cpus;
|
||||
$vm = domain_to_config($uuid);
|
||||
$vcpu = $vm['domain']['vcpu'];
|
||||
echo "<div class='four'>";
|
||||
foreach ($cpus as $pair) {
|
||||
unset($cpu1,$cpu2);
|
||||
[$cpu1, $cpu2] = my_preg_split('/[,-]/',$pair);
|
||||
$check = in_array($cpu1, $vcpu) ? 'fa-circle orange-text':'fa-circle-o';
|
||||
if (!$cpu2) {
|
||||
echo "<label><i class='fa fa-fw $check'></i> cpu $cpu1</label>";
|
||||
} else {
|
||||
echo "<label class='cpu1'><i class='fa fa-fw $check'></i> cpu $cpu1 / $cpu2</label>";
|
||||
$check = in_array($cpu2, $vcpu) ? 'fa-circle orange-text':'fa-circle-o';
|
||||
echo "<label class='cpu2'><i class='fa fa-fw $check'></i></label>";
|
||||
}
|
||||
}
|
||||
echo "</div>";
|
||||
}
|
||||
function vsize($size,$expand=true) {
|
||||
$units = ['','K','M','G','T','P','E','Z','Y'];
|
||||
if ($expand) {
|
||||
$size = str_replace(['B',' ',',', '.'],'',strtoupper($size));
|
||||
[$c1,$c2] = my_preg_split('/(?<=[0-9])(?=[A-Z])/',$size);
|
||||
return $c1 * pow(1024,array_search($c2,$units)?:0);
|
||||
} else {
|
||||
$base = $size ? floor(log($size,1024)) : 0;
|
||||
return $size/pow(1024,$base).$units[$base];
|
||||
}
|
||||
}
|
||||
|
||||
$uuid = unscript(_var($_GET,'uuid'));
|
||||
$subaction = _var($_GET,'subaction');
|
||||
if (isset($_GET['refresh'])) {
|
||||
$vm = unscript(_var($_GET,'name'));
|
||||
if ($lv->domain_is_active($vm)) {
|
||||
echo "<meta http-equiv='refresh' content='5; url=/VMs?name=$vm&refresh=true'>";
|
||||
$msg = "Waiting for $vm to shutdown...";
|
||||
} else {
|
||||
$msg = "$vm has been shutdown";
|
||||
}
|
||||
}
|
||||
if ($subaction) {
|
||||
$vm = $lv->domain_get_name_by_uuid($uuid);
|
||||
if ($subaction == 'disk-resize') {
|
||||
$capacity = vsize($_GET['cap']);
|
||||
if ($capacity > vsize($_GET['oldcap'])) {
|
||||
shell_exec("qemu-img resize -q ".escapeshellarg(unscript($_GET['disk']??''))." ".vsize($capacity,0));
|
||||
$msg = $vm." disk capacity has been changed to {$_GET['cap']}";
|
||||
} else {
|
||||
$msg = "Error: disk capacity has to be greater than {$_GET['oldcap']}";
|
||||
}
|
||||
} elseif ($subaction == 'disk-remove') {
|
||||
$msg = $lv->domain_disk_remove($vm,_var($_GET,'dev'))
|
||||
? "$vm disk has been removed"
|
||||
: "Error: ".$lv->get_last_error();
|
||||
} elseif ($subaction == 'snap-create') {
|
||||
$msg = $lv->domain_snapshot_create($vm)
|
||||
? "Snapshot for $vm has been created"
|
||||
: "Error: ".$lv->get_last_error();
|
||||
} elseif ($subaction == 'snap-delete') {
|
||||
$msg = $lv->domain_snapshot_delete($vm,_var($_GET,'snap'))
|
||||
? "Snapshot for $vm has been deleted"
|
||||
: "Error: ".$lv->get_last_error();
|
||||
} elseif ($subaction == 'snap-revert') {
|
||||
$msg = $lv->domain_snapshot_revert($vm,_var($_GET,'snap'))
|
||||
? "$vm has been reverted"
|
||||
: "Error: ".$lv->get_last_error();
|
||||
} elseif ($subaction == 'snap-desc') {
|
||||
$msg = $lv->snapshot_set_metadata($vm,_var($_GET,'snap'),_var($_POST,'snapdesc'))
|
||||
? "Snapshot description for $vm has been saved"
|
||||
: "Error: ".$lv->get_last_error();
|
||||
}
|
||||
}
|
||||
if ($libvirt_running=='yes') $vms = $lv->get_domains() ?: [];
|
||||
if (empty($vms)) {
|
||||
$msg = $libvirt_running=='yes'
|
||||
? 'No VMs defined. Create from template or add XML.'
|
||||
: 'Libvirt is not running. Goto Settings tab then click Start.';
|
||||
}
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.filetree.css")?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.ui.css")?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/plugins/dynamix.docker.manager/styles/style-$theme.css")?>">
|
||||
<style>
|
||||
th.th1{width:25%}
|
||||
th.th2{width:15%}
|
||||
th.th3{width:80px}
|
||||
div.four{font-size:1.1rem;width:260px}
|
||||
div.four label{float:left;display:table-cell;width:25%}
|
||||
div.four label:nth-child(4n+4){float:none;clear:both}
|
||||
div.four label.cpu1{width:32%}
|
||||
div.four label.cpu2{width:26%}
|
||||
div.template,div#dialogWindow,input#upload{display:none}
|
||||
table.domdisk thead tr th:nth-child(1){width:56%!important}
|
||||
table.domdisk thead tr th:nth-child(n+2){width:8%!important}
|
||||
table.domdisk thead tr th:nth-child(1){padding-left:72px}
|
||||
table.domdisk tbody tr td:nth-child(1){padding-left:72px}
|
||||
table.domdisk tbody tr:nth-child(even){background-color:transparent!important}
|
||||
table.domdisk tbody tr:nth-child(4n-1){background-color:transparent!important}
|
||||
i.mover{margin-right:8px;display:none}
|
||||
#resetsort{margin-left:12px;display:inline-block;width:32px}
|
||||
.fileTree{background:<?=$bgcolor?>;width:500px;max-height:320px;overflow-y:scroll;overflow-x:hidden;position:absolute;z-index:100;display:none}
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button[disabled]{cursor:default;color:#808080;background:-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#404040),to(#808080)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#404040),to(#404040)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#808080),to(#808080)) 100% 100% no-repeat;background:linear-gradient(90deg,#404040 0,#808080) 0 0 no-repeat,linear-gradient(90deg,#404040 0,#808080) 0 100% no-repeat,linear-gradient(0deg,#404040 0,#404040) 0 100% no-repeat,linear-gradient(0deg,#808080 0,#808080) 100% 100% no-repeat;background-size:100% 2px,100% 2px,2px 100%,2px 100%}
|
||||
.dropdown-menu{z-index:10001}
|
||||
</style>
|
||||
<table id="kvm_table" class="tablesorter four shift">
|
||||
<thead><tr><th class="th1"><a id="resetsort" class="nohand" onclick="resetSorting()" title="Reset sorting"><i class="fa fa-th-list"></i></a>_(Name)_</th><th class="th2">_(Description)_</th><th>_(CPUs)_</th><th>_(Memory)_</th><th>_(vDisks)_</th><th>_(Graphics)_</th><th class="th3">_(Autostart)_</th></tr></thead>
|
||||
<tbody id="kvm_list"><tr><td colspan='8'></td></tr></tbody>
|
||||
</table>
|
||||
<input type="button" onclick="addVM()" id="btnAddVM" value="_(Add VM)_" style="display:none">
|
||||
<input type="button" onclick="startAll()" value="_(Start All)_" style="display:none">
|
||||
<input type="button" onclick="stopAll()" value="_(Stop All)_" style="display:none">
|
||||
|
||||
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/javascript/dynamix.vm.manager.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/javascript/vmmanager.js')?>"></script>
|
||||
<script src="<?autov("/webGui/javascript/jquery.filetree.js")?>"></script>
|
||||
<script>
|
||||
<?if (_var($display,'resize')):?>
|
||||
function resize() {
|
||||
$('#kvm_list').height(Math.max(window.innerHeight-340,330));
|
||||
$('#kvm_table thead,#kvm_table tbody').removeClass('fixed');
|
||||
$('#kvm_table thead tr th').each(function(){$(this).width($(this).width());});
|
||||
$('#kvm_table tbody tr td').each(function(){$(this).width($(this).width());});
|
||||
$('#kvm_table thead,#kvm_table tbody').not('.child').addClass('fixed');
|
||||
}
|
||||
<?endif;?>
|
||||
function resetSorting() {
|
||||
if ($.cookie('lockbutton')==null) return;
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
$.post('/plugins/dynamix.vm.manager/include/UserPrefs.php',{reset:true},function(){loadlist();});
|
||||
}
|
||||
function changemedia(uuid,dev,bus,file) {
|
||||
if (file === "--select") getisoimage(uuid,dev,bus,file);
|
||||
if (file === "--eject") ajaxVMDispatch({action:"change-media", uuid:uuid , cdrom:"" , dev:dev , bus:bus , file:file}, "loadlist");
|
||||
|
||||
}
|
||||
function getisoimage(uuid,dev,bus,file){
|
||||
var root = <?= '"'.$domain_cfg["MEDIADIR"].'"';?>;
|
||||
var match= ".iso";
|
||||
var box = $("#dialogWindow");
|
||||
box.html($("#templateISO").html());
|
||||
|
||||
box.find('#target').attr('data-pickroot',root).attr('data-picktop',root).attr('data-pickmatch',match).fileTreeAttach(null,null,function(path){
|
||||
var bits = path.substr(1).split('/');
|
||||
var auto = bits.length>3 ? '' : share;
|
||||
box.find('#target').val(path+auto).change();
|
||||
});
|
||||
var height = 100;
|
||||
box.dialog({
|
||||
title: "Select ISO",
|
||||
resizable: false,
|
||||
width: 600,
|
||||
height: 300,
|
||||
modal: true,
|
||||
show: {effect:'fade', duration:250},
|
||||
hide: {effect:'fade', duration:250},
|
||||
buttons: {
|
||||
"_(Insert)_": function(){
|
||||
var target = box.find('#target');
|
||||
if (target.length) {
|
||||
target = target.val();
|
||||
if (!target ) {errorTarget(); return;}
|
||||
} else target = '';
|
||||
box.find('#target').prop('disabled',true);
|
||||
ajaxVMDispatch({action:"change-media", uuid:uuid , cdrom:"" , dev:dev , bus:bus , file:target}, "loadlist");
|
||||
box.dialog('close');
|
||||
},
|
||||
"_(Cancel)_": function(){
|
||||
box.dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
function dialogStyle() {
|
||||
$('.ui-dialog-titlebar-close').css({'background':'transparent','border':'none','font-size':'1.8rem','margin-top':'-14px','margin-right':'-18px'}).html('<i class="fa fa-times"></i>').prop('title',"_(Close)_").prop('onclick',null).off('click').click(function(){box.dialog('close');});
|
||||
$('.ui-dialog-title').css({'text-align':'center','width':'100%','font-size':'1.8rem'});
|
||||
$('.ui-dialog-content').css({'padding-top':'15px','vertical-align':'bottom'});
|
||||
$('.ui-button-text').css({'padding':'0px 5px'});
|
||||
}
|
||||
var sortableHelper = function(e,ui){
|
||||
var child = ui.next();
|
||||
if (child.is(':visible')) child.addClass('unhide').hide();
|
||||
ui.children().each(function(){$(this).width($(this).width());});
|
||||
return ui;
|
||||
};
|
||||
function LockButton() {
|
||||
if ($.cookie('lockbutton')==null) {
|
||||
$.cookie('lockbutton','lockbutton');
|
||||
$('#resetsort').removeClass('nohand').addClass('hand');
|
||||
$('i.mover').show();
|
||||
$('#kvm_list .sortable').css({'cursor':'move'});
|
||||
<?if ($themes1):?>
|
||||
$('div.nav-item.LockButton a').prop('title',"_(Lock sortable items)_");
|
||||
$('div.nav-item.LockButton b').removeClass('icon-u-lock green-text').addClass('icon-u-lock-open red-text');
|
||||
<?endif;?>
|
||||
$('div.nav-item.LockButton span').text("_(Lock sortable items)_");
|
||||
$('#kvm_list').sortable({helper:sortableHelper,items:'.sortable',cursor:'grab',axis:'y',containment:'parent',delay:100,opacity:0.5,zIndex:9999,forcePlaceholderSize:true,
|
||||
update:function(e,ui){
|
||||
$('#kvm_list .sortable').each(function(){
|
||||
var parent = $(this).attr('parent-id');
|
||||
var child = $('tr[child-id="'+parent+'"]');
|
||||
child.detach().insertAfter($(this));
|
||||
if (child.hasClass('unhide')) child.removeClass('unhide').show();
|
||||
});
|
||||
var row = $('#kvm_list tr:first');
|
||||
var names = '', index = '';
|
||||
row.parent().children().find('td.vm-name').each(function(){names+=$(this).find('a').text()+';';index+=$(this).parent().parent().children().index($(this).parent())+';';});
|
||||
$.post('/plugins/dynamix.vm.manager/include/UserPrefs.php',{names:names,index:index});
|
||||
}});
|
||||
} else {
|
||||
$.removeCookie('lockbutton');
|
||||
$('#resetsort').removeClass('hand').addClass('nohand');
|
||||
$('i.mover').hide();
|
||||
$('#kvm_list .sortable').css({'cursor':'default'});
|
||||
<?if ($themes1):?>
|
||||
$('div.nav-item.LockButton a').prop('title',"_(Unlock sortable items)_");
|
||||
$('div.nav-item.LockButton b').removeClass('icon-u-lock-open red-text').addClass('icon-u-lock green-text');
|
||||
<?endif;?>
|
||||
$('div.nav-item.LockButton span').text("_(Unlock sortable items)_");
|
||||
$('#kvm_list').sortable('destroy');
|
||||
}
|
||||
}
|
||||
function loadlist() {
|
||||
timers.vm = setTimeout(function(){$('div.spinner.fixed').show('slow');},500);
|
||||
$.get('/plugins/dynamix.vm.manager/include/VMMachines.php',{show:$.cookie('vmshow')},function(d) {
|
||||
clearTimeout(timers.vm);
|
||||
var data = d.split(/\0/);
|
||||
$('#kvm_list').html(data[0]);
|
||||
$('head').append('<script>'+data[1]+'<\/script>');
|
||||
<?if (_var($display,'resize')):?>
|
||||
resize();
|
||||
$(window).bind('resize',function(){resize();});
|
||||
<?endif;?>
|
||||
<?foreach ($vms as $vm) {
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$uuid = $lv->domain_get_uuid($res);
|
||||
?> $('.vcpu-<?=$uuid?>').tooltipster({
|
||||
trigger:'custom',
|
||||
triggerOpen:{mouseenter:true,click:true,touchstart:true},
|
||||
contentAsHTML:true,
|
||||
animation:'grow',
|
||||
triggerClose:{click:true,scroll:true,mouseleave:true,delay:1},
|
||||
interactive:true,
|
||||
viewportAware:true,
|
||||
functionBefore:function(instance,helper){instance.content("<?=showCPUs($uuid)?>");}
|
||||
});
|
||||
<?}?>
|
||||
$('.autostart').switchButton({labels_placement:'right', on_label:"_(On)_", off_label:"_(Off)_"});
|
||||
$('.autostart').change(function() {
|
||||
$.post('/plugins/dynamix.vm.manager/include/VMajax.php',{action:'domain-autostart',uuid:$(this).attr('uuid'),autostart:$(this).prop('checked'),response:'json'},function(data){
|
||||
$(this).prop('checked', data.autostart);
|
||||
},'json');
|
||||
});
|
||||
$('div.spinner.fixed').hide('slow');
|
||||
$('input[type=button]').prop('disabled',false).show('slow');
|
||||
$('.text').click(showInput);
|
||||
$('.input').blur(hideInput);
|
||||
});
|
||||
}
|
||||
$(function() {
|
||||
<?if ($msg):?>
|
||||
<?$color = strpos($msg, "rror:")!==false ? 'red-text':'green-text'?>
|
||||
$('#countdown').html("<span class='<?=$color?>'><?=_($msg)?></span>");
|
||||
<?endif;?>
|
||||
$('#btnAddVM').click(function AddVMEvent(){$('.tab>input#tab2').click();});
|
||||
$.removeCookie('lockbutton');
|
||||
loadlist();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="dialogWindow"></div>
|
||||
<div markdown="1" id="templateISO" class="template">
|
||||
_(ISO Image)_:
|
||||
: <input type="text" id="target" autocomplete="off" spellcheck="false" value="" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="" data-pickmatch="" data-pickroot="" data-picktop="">
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,465 @@
|
||||
Menu="OtherSettings"
|
||||
Title="VM Manager"
|
||||
Icon="icon-virtualization"
|
||||
Tag="columns"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2015-2023, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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 "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
// Check for Intel VT-x (vmx) or AMD-V (svm) cpu virtualization support
|
||||
// If either kvm_intel or kvm_amd are loaded then Intel VT-x (vmx) or AMD-V (svm) cpu virtualization support was found
|
||||
$hardware = !empty(shell_exec("/etc/rc.d/rc.libvirt test"));
|
||||
if (!$hardware) {
|
||||
echo "<p class='notice'>"._('Your hardware does not have Intel VT-x or AMD-V capability').". "._('This is required to create VMs in KVM').". "._('Please disable the VM function').". ";
|
||||
echo "<a href='https://docs.unraid.net/unraid-os/manual/vm-management#determining-hvmiommu-hardware-support' target='_blank'> "._('Click here to see the Unraid Wiki for more information')."</a></p>";
|
||||
}
|
||||
|
||||
function scan($area, $text) {
|
||||
return strpos($area,$text)!==false;
|
||||
}
|
||||
function detect(&$syslinux, $key) {
|
||||
$size = count($syslinux);
|
||||
$menu = $i = 0;
|
||||
$value = '';
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($syslinux[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'menu default')) $menu = 1;
|
||||
if (scan($syslinux[$n],'append')) foreach (explode(' ',$syslinux[$n]) as $cmd) if (scan($cmd,$key)) {$value = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
$syslinux = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$arrValidNetworks = getValidNetworks();
|
||||
$pcie_acs_override = detect($syslinux, 'pcie_acs_override');
|
||||
$vfio_allow_unsafe = detect($syslinux, 'allow_unsafe_interrupts');
|
||||
$bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
$started = $var['fsState']=='Started';
|
||||
$libvirt_up = $libvirt_running=='yes';
|
||||
$libvirt_log = file_exists("/var/log/libvirt/libvirtd.log");
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.filetree.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
|
||||
<style>
|
||||
body{-webkit-overflow-scrolling:touch}
|
||||
.errortext{color:#EF3D47;display:none}
|
||||
.fileTree{background:<?=$bgcolor?>;width:300px;max-height:150px;overflow-y:scroll;overflow-x:hidden;position:absolute;z-index:100;display:none}
|
||||
.basic{display:block}
|
||||
.advanced{display:none}
|
||||
#winvirtio{display:none}
|
||||
#download_status{margin-left:5px;color:#777;display:none}
|
||||
#download_button{cursor:pointer;margin-left:-2px;color:#08C;display:none;transform:translate(0px,2px)}
|
||||
#download_button.fa-spin{cursor:default;color:#777}
|
||||
#download_button span{font-family:clear-sans}
|
||||
#download_button.fa-spin span{font-family:clear-sans;display:none}
|
||||
#remove_button{cursor:pointer;margin-left:-2px;color:#EF3D47;display:none;transform:translate(0px,2px)}
|
||||
#remove_button span{font-family:clear-sans}
|
||||
</style>
|
||||
|
||||
<?if ($hardware):?>
|
||||
<span class="status vhshift"><input type="checkbox" class="advancedview"></span>
|
||||
<?endif;?>
|
||||
<form markdown="1" id="settingsForm" method="POST" action="/update.php" target="progressFrame">
|
||||
<input type="hidden" name="#file" value="<?=htmlspecialchars($domain_cfgfile)?>">
|
||||
<input type="hidden" name="#command" value="/plugins/dynamix/scripts/emcmd">
|
||||
<input type="hidden" name="#arg[1]" value="cmdStatus=Apply">
|
||||
_(Enable VMs)_:
|
||||
: <select id="SERVICE" name="SERVICE">
|
||||
<?= mk_option($libvirt_service, 'disable', _('No'))?>
|
||||
<?= mk_option($libvirt_service, 'enable', _('Yes'))?>
|
||||
</select>
|
||||
<?if ($hardware):?>
|
||||
<?if (!$started):?>
|
||||
<span id="arraystopped"><i class="fa fa-warning icon warning"></i> <?=($libvirt_service=='enable')?'_(VMs will be available after Array is Started)_':'_(Apply to activate VMs after Array is Started)_'?></span>
|
||||
<?elseif (!is_dir(dirname($domain_cfg['IMAGE_FILE'])) || !is_dir($domain_cfg['DOMAINDIR']) || !is_dir($domain_cfg['MEDIADIR'])):?>
|
||||
<span class="basic" style="display:inline"><i class="fa fa-warning icon warning"></i> _(One or more paths do not exist)_ (<a href="#" onclick="$('.advancedview').switchButton('option','checked',true); return false">_(view)_</a>)</span>
|
||||
<?endif;?>
|
||||
<?endif;?>
|
||||
|
||||
:vms_enable_help:
|
||||
|
||||
<?if ($hardware):?>
|
||||
<div class="advanced" markdown="1">
|
||||
<?if ($libvirt_up):?>
|
||||
<?$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']?>
|
||||
|
||||
_(Libvirt storage location)_:
|
||||
: <?=htmlspecialchars($domain_cfg['IMAGE_FILE'])?>
|
||||
|
||||
:vms_libvirt_volume_help:
|
||||
|
||||
<?else: /* Libvirt is stopped */ ?>
|
||||
_(Libvirt vdisk size)_:
|
||||
: <input type="number" id="IMAGE_SIZE" name="IMAGE_SIZE" min="1" value="<?=htmlspecialchars($domain_cfg['IMAGE_SIZE']);?>" style="width:50px;" required="required" />_(GB)_ <span id="SIZE_ERROR" class="errortext"></span>
|
||||
|
||||
:vms_libvirt_vdisk_size_help:
|
||||
|
||||
_(Libvirt storage location)_:
|
||||
: <input type="text" id="IMAGE_FILE" name="IMAGE_FILE" autocomplete="off" spellcheck="false" value="<?=htmlspecialchars($domain_cfg['IMAGE_FILE']);?>" placeholder="e.g. /mnt/user/system/libvirt/libvirt.img" data-pickcloseonfile="true" data-pickfilter="img" data-pickroot="/mnt" data-pickfolders="true" required pattern="^[^\\]*libvirt\.img$">
|
||||
<?if (file_exists($domain_cfg['IMAGE_FILE'])):?><span id="deletePanel"><label><input type="checkbox" id="deleteCheckbox" /> _(Delete Image File)_</label></span><?endif;?>
|
||||
<?if (!$started):?><span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
|
||||
<?elseif (!is_dir(dirname($domain_cfg['IMAGE_FILE']))):?><span><i class="fa fa-warning icon warning"></i> _(Path does not exist)_</span>
|
||||
<?endif;?><span id="IMAGE_ERROR" class="errortext"></span>
|
||||
|
||||
:vms_libvirt_location_help:
|
||||
|
||||
<?endif;?>
|
||||
_(Default VM storage path)_:
|
||||
: <input type="text" id="domaindir" name="DOMAINDIR" autocomplete="off" spellcheck="false" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="<?=is_dir('/mnt/user')?'/mnt/user':'/mnt'?>" value="<?=htmlspecialchars($domain_cfg['DOMAINDIR'])?>" placeholder="_(Click to Select)_" pattern="^[^\\]*/$">
|
||||
<?if (!$started):?><span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
|
||||
<?elseif (!is_dir($domain_cfg['DOMAINDIR'])):?><span><i class="fa fa-warning icon warning"></i> _(Path does not exist)_</span><?endif;?>
|
||||
|
||||
:vms_libvirt_storage_help:
|
||||
|
||||
_(Default ISO storage path)_:
|
||||
: <input type="text" id="mediadir" name="MEDIADIR" autocomplete="off" spellcheck="false" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="<?=is_dir('/mnt/user')?'/mnt/user':'/mnt'?>" value="<?=htmlspecialchars($domain_cfg['MEDIADIR'])?>" placeholder="_(Click to Select)_" pattern="^[^\\]*/$">
|
||||
<?if (!$started):?><span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
|
||||
<?elseif (!is_dir($domain_cfg['MEDIADIR'])):?><span><i class="fa fa-warning icon warning"></i> _(Path does not exist)_</span><?endif;?>
|
||||
|
||||
:vms_libvirt_iso_storage_help:
|
||||
|
||||
</div>
|
||||
_(Default Windows VirtIO driver ISO)_ (_(optional)_):
|
||||
: <select id="winvirtio_select" class="lock" disabled></select><input type="text" id="winvirtio" name="VIRTIOISO" data-pickfilter="iso" data-pickcloseonfile="true" data-pickroot="<?=is_dir('/mnt/user')?'/mnt/user':'/mnt'?>" value="<?=htmlspecialchars($domain_cfg['VIRTIOISO'])?>"<?if ($started):?> placeholder="_(Click to Select)_" pattern="^[^\\]*\.(iso|ISO)$"<?endif;?>>
|
||||
<?if ($started):?>
|
||||
<i class="fa fa-trash fa-fw" id="remove_button" title="_(Remove Windows VirtIO driver ISO)_"><span> _(Remove)_</span></i><i class="fa fa-download fa-fw" id="download_button" title="_(Download Windows VirtIO driver ISO)_"><span> _(Download)_</span></i><span id="download_status"></span>
|
||||
<?endif;?>
|
||||
|
||||
:vms_virtio_driver_help:
|
||||
|
||||
<div class="advanced" markdown="1">
|
||||
_(Default network source)_:
|
||||
: <select id="network" name="BRNAME">
|
||||
<?foreach (array_keys($arrValidNetworks) as $key) {
|
||||
|
||||
echo mk_option("", $key, "- "._($key)." -", "disabled");
|
||||
|
||||
foreach ($arrValidNetworks[$key] as $strNetwork) {
|
||||
echo mk_option($domain_cfg['BRNAME'], $strNetwork, $strNetwork);
|
||||
}
|
||||
}?>
|
||||
</select>
|
||||
|
||||
:vms_network_source_help:
|
||||
|
||||
_(Upon host shutdown)_:
|
||||
: <select id="hostshutdown" name="HOSTSHUTDOWN">
|
||||
<?=mk_option($domain_cfg['HOSTSHUTDOWN'], 'shutdown', _('Shutdown VMs'))?>
|
||||
<?=mk_option($domain_cfg['HOSTSHUTDOWN'], 'hibernate', _('Hibernate VMs'))?>
|
||||
</select>
|
||||
|
||||
:vms_host_shutdown_help:
|
||||
|
||||
_(VM shutdown time-out)_:
|
||||
: <input type="number" id="vm_shutdown_timeout" name="TIMEOUT" value="<?=htmlspecialchars($domain_cfg['TIMEOUT'])?>" class="narrow">
|
||||
<?if (!empty($var['shutdownTimeout']) && !empty($domain_cfg['TIMEOUT']) && (int)$domain_cfg['TIMEOUT'] > (int)$var['shutdownTimeout']):?>
|
||||
<span id="arraystopped"><i class="fa fa-warning icon warning"></i> _(exceeds Disk Shutdown)_ <?=htmlspecialchars($var['shutdownTimeout'])?>s _(time-out)_ (<a href="/Settings/DiskSettings">_(edit)_</a>)</span>
|
||||
<?endif;?>
|
||||
|
||||
:vms_shutdown_timeout_help:
|
||||
|
||||
_(Console Options)_:
|
||||
: <select id="vmsconsole" name="CONSOLE">
|
||||
<?=mk_option($domain_cfg['CONSOLE'], 'web', _('Web interface'))?>
|
||||
<?=mk_option($domain_cfg['CONSOLE'], 'remote', _('Virt-Manager Remote Viewer'))?>
|
||||
<?=mk_option($domain_cfg['CONSOLE'], 'both', _('Both Web & Virt-Manager Remote Viewer'))?>
|
||||
</select>
|
||||
|
||||
:vms_console_help:
|
||||
|
||||
_(PCIe ACS override)_:
|
||||
: <select id="pcie_acs_override"<?=$safemode?' disabled':''?>>
|
||||
<?= mk_option($pcie_acs_override, '', _('Disabled'))?>
|
||||
<?= mk_option($pcie_acs_override, 'downstream', _('Downstream'))?>
|
||||
<?= mk_option($pcie_acs_override, 'multifunction', _('Multi-function'))?>
|
||||
<?= mk_option($pcie_acs_override, 'downstream,multifunction', _('Both'))?>
|
||||
</select><?if($safemode):?><span>*_(Setting disabled in **safe mode**)_*</span><?endif;?>
|
||||
|
||||
:vms_acs_override_help:
|
||||
|
||||
_(VFIO allow unsafe interrupts)_:
|
||||
: <select id="vfio_allow_unsafe"<?=$safemode?' disabled':''?>>
|
||||
<?= mk_option($vfio_allow_unsafe, '', _('No'))?>
|
||||
<?= mk_option($vfio_allow_unsafe, '1', _('Yes'))?>
|
||||
</select><?if($safemode):?><span>*_(Setting disabled in **safe mode**)_*</span><?endif;?>
|
||||
|
||||
:vms_vfio_interupts_help:
|
||||
|
||||
</div>
|
||||
<?if ($libvirt_log):?>
|
||||
<input type="button" onclick="openTerminal('log','libvirt','libvirt/libvirtd.log');" value="<?=_('View libvirt log')?>"/>
|
||||
<?else:?>
|
||||
|
||||
<?endif;?>
|
||||
<?endif;?>
|
||||
: <input type="button" id="applyBtn" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
|
||||
</form>
|
||||
<?if ($libvirt_log):?>
|
||||
|
||||
:vms_libvirt_log_help:
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ($libvirt_up && trim(shell_exec('stat -c %T -f /etc/libvirt'))=='btrfs'):?>
|
||||
<div class="advanced" markdown="1">
|
||||
<div class="title"><span class="left"><i class="title fa fa-list"></i>_(Libvirt volume info)_</span></div>
|
||||
_(btrfs filesystem show)_:
|
||||
: <?="<pre id='btrfs-scrub'>".shell_exec("btrfs filesystem show /etc/libvirt")."</pre>"?>
|
||||
|
||||
<form markdown="1" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareFS(this,'btrfs-scrub-vm','/etc/libvirt'>
|
||||
<?exec("$docroot/webGui/scripts/btrfs_scrub status /etc/libvirt", $scrub_status, $retval);?>
|
||||
_(btrfs scrub status)_:
|
||||
: <?="<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="/etc/libvirt">
|
||||
<input type="hidden" name="#arg[3]" value="-r">
|
||||
|
||||
: <input type="submit" value="_(Scrub)_"><label><input type="checkbox" name="#arg[3]" value="">_(Correct file system errors)_</label>
|
||||
|
||||
:vms_scrub_help:
|
||||
|
||||
<?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="/etc/libvirt">
|
||||
|
||||
: <input type="submit" value="_(Cancel)_">
|
||||
|
||||
:vms_cancel_help:
|
||||
|
||||
</form>
|
||||
<?endif;?>
|
||||
</div>
|
||||
|
||||
<?elseif (!$libvirt_up):?>
|
||||
<form id="removeForm" method="POST" action="/update.php" target="progressFrame">
|
||||
<input type="hidden" name="#command" value="/plugins/dynamix.vm.manager/scripts/libvirt_rm">
|
||||
</form>
|
||||
<?endif;?>
|
||||
|
||||
<script src="<?autov('/webGui/javascript/jquery.filetree.js')?>" charset="utf-8"></script>
|
||||
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/javascript/dynamix.vm.manager.js')?>"></script>
|
||||
<script>
|
||||
function prepareFS(form,cookie,value) {
|
||||
if ($(form).find('input[type="submit"]').val()=="_(Cancel)_") $.removeCookie(cookie); else $.cookie(cookie,value);
|
||||
}
|
||||
function btrfsScrub(path) {
|
||||
$.post('/webGui/include/FileSystemStatus.php',{cmd:'scrub',path:path},function(data) {
|
||||
if (data.indexOf('running')>0) {
|
||||
$('#btrfs-scrub').text(data);
|
||||
setTimeout(function(){btrfsScrub(path);},1000);
|
||||
} else {
|
||||
$.removeCookie('btrfs-scrub-vm');
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
$(function(){
|
||||
$.post("/plugins/dynamix.vm.manager/include/Fedora-virtio-isos.php",{},function(isos) {
|
||||
$('#winvirtio_select').html(isos).prop('disabled',false).change().each(function(){$(this).on('change',function() {
|
||||
// attach button updates when select element changes
|
||||
var form = $(this).parentsUntil('form').parent();
|
||||
form.find('input[value="<?=_("Apply")?>"],input[value="Apply"],input[name="cmdEditShare"],input[name="cmdUserEdit"]').not('input.lock').prop('disabled',false);
|
||||
form.find('input[value="<?=_("Done")?>"],input[value="Done"]').not('input.lock').val("<?=_('Reset')?>").prop('onclick',null).off('click').click(function(){formHasUnsavedChanges=false;refresh(form.offset().top);});
|
||||
});});
|
||||
});
|
||||
$("#applyBtn").click(function(){
|
||||
if ($("#deleteCheckbox").length && $("#deleteCheckbox").is(":checked")) {
|
||||
$("#removeForm").submit();
|
||||
return;
|
||||
}
|
||||
var run = true;
|
||||
$("#settingsForm").find('input[type="text"]').each(function(){
|
||||
if ($(this).is(':visible') && $(this).attr('pattern')) {
|
||||
var pattern = new RegExp($(this).attr('pattern'));
|
||||
if (!pattern.test($(this).val())) {$(this).css('color','red'); run = false;}
|
||||
}
|
||||
});
|
||||
<?if ($safemode):?>
|
||||
if (run) $("#settingsForm").submit();
|
||||
<?else:?>
|
||||
if (run) $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:'syslinux',pcie:$('#pcie_acs_override').val(),vfio:$('#vfio_allow_unsafe').val()}, function(data){
|
||||
$("#settingsForm").submit();
|
||||
});
|
||||
<?endif;?>
|
||||
});
|
||||
|
||||
$("#mediadir").on("input change", function(){
|
||||
$("#winvirtio_select").change();
|
||||
});
|
||||
|
||||
var checkDownloadTimer = null;
|
||||
var checkOrInitDownload = function(checkonly){
|
||||
clearTimeout(checkDownloadTimer);
|
||||
var $button = $("#download_button");
|
||||
var $form = $button.closest('form');
|
||||
var postdata = {
|
||||
action: "virtio-win-iso-download",
|
||||
download_version: $('#winvirtio_select').val(),
|
||||
download_path: $('#mediadir').val(),
|
||||
checkonly: ((typeof checkonly === 'undefined') ? false : !!checkonly) ? 1 : 0
|
||||
};
|
||||
$button.removeClass('fa-download').addClass('fa-circle-o-notch fa-spin');
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", postdata, function(data){
|
||||
if (postdata.download_version != $('#winvirtio_select').val())
|
||||
return;
|
||||
if (data.error) {
|
||||
$("#download_status").html('<span style="color: red">' + data.error + '</span>');
|
||||
$button.removeClass('fa-circle-o-notch fa-spin').addClass('fa-download');
|
||||
} else {
|
||||
$("#download_status").html(data.status);
|
||||
if (data.pid) {
|
||||
checkDownloadTimer = setTimeout(checkOrInitDownload, 1000);
|
||||
return;
|
||||
}
|
||||
if (data.status == "_(Done)_") $("#winvirtio_select").change();
|
||||
}
|
||||
}, "json");
|
||||
};
|
||||
$("#SERVICE").change(function changeService(){
|
||||
if ($(this).val()=='enable') {
|
||||
$('#arraystopped').fadeIn('slow');
|
||||
} else {
|
||||
$('#arraystopped').fadeOut('fast');
|
||||
}
|
||||
});
|
||||
if ($("#SERVICE").val()!='enable') $('#arraystopped').hide();
|
||||
$("#download_button").click(function downloadVirtIOVersion(){
|
||||
if (!$(this).hasClass('fa-spin')) {
|
||||
checkOrInitDownload(false);
|
||||
}
|
||||
});
|
||||
$("#remove_button").click(function removeVirtIOVersion() {
|
||||
var postdata = {
|
||||
action: "virtio-win-iso-remove",
|
||||
path: $('#mediadir').val(),
|
||||
file: $('#winvirtio_select').val()
|
||||
};
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", postdata, function(data){
|
||||
if (postdata.file != $('#winvirtio_select').val()) return;
|
||||
if (data.success) {
|
||||
$("#winvirtio_select").change();
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
// Fire events below once upon showing page
|
||||
$("#winvirtio_select").change(function changeVirtIOVersion(){
|
||||
clearTimeout(checkDownloadTimer);
|
||||
if ($(this).val()=='manual') {
|
||||
$("#download_button,#download_status").hide('fast');
|
||||
$("#winvirtio").show('fast');
|
||||
return;
|
||||
}
|
||||
$("#winvirtio").hide('fast');
|
||||
var params = {
|
||||
action: "virtio-win-iso-info",
|
||||
path: $("#mediadir").val(),
|
||||
file: $(this).val()
|
||||
};
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", params, function(data){
|
||||
if (!data.exists || data.pid) {
|
||||
$("#remove_button").hide('fast');
|
||||
$("#download_button").removeClass('fa-circle-o-notch fa-spin').addClass('fa-download').show('fast');
|
||||
$("#download_status").html('').show('fast');
|
||||
if (data.pid) checkOrInitDownload(true);
|
||||
} else {
|
||||
$("#download_button,#download_status").hide('fast');
|
||||
$("#remove_button").show('fast');
|
||||
$("#winvirtio").val(data.path);
|
||||
}
|
||||
}, "json");
|
||||
});
|
||||
if ($.cookie('vmsettings_view_mode') == 'advanced') {
|
||||
$('.advanced').show();
|
||||
$('.basic').hide();
|
||||
}
|
||||
<?if ($hardware):?>
|
||||
$('.advancedview').switchButton({
|
||||
labels_placement: "left",
|
||||
on_label: "_(Advanced View)_",
|
||||
off_label: "_(Basic View)_",
|
||||
checked: $.cookie('vmsettings_view_mode') == 'advanced'
|
||||
});
|
||||
$('.advancedview').change(function(){
|
||||
$('.advanced').toggle('slow');
|
||||
$('.basic').toggle('slow');
|
||||
$.cookie('vmsettings_view_mode', $('.advancedview').is(':checked') ? 'advanced' : 'basic', {expires:3650});
|
||||
});
|
||||
<?endif;?>
|
||||
showStatus('pid','libvirt/libvirtd');
|
||||
<?if ($started):?>
|
||||
$("#IMAGE_FILE").fileTreeAttach(null, null, function(folder) {
|
||||
$("#IMAGE_FILE").val(folder + 'libvirt.img').change();
|
||||
});
|
||||
$('#domaindir').fileTreeAttach();
|
||||
$('#mediadir').fileTreeAttach();
|
||||
$('#winvirtio').fileTreeAttach();
|
||||
<?endif;?>
|
||||
if ($("#IMAGE_FILE").length) {
|
||||
$("#IMAGE_FILE").on("input change", function(){
|
||||
$("#IMAGE_ERROR").fadeOut();
|
||||
$("#applyBtn").prop("disabled", false);
|
||||
<? if (file_exists($domain_cfg['IMAGE_FILE'])) { ?>
|
||||
if ($(this).val() != "<?=$domain_cfg['IMAGE_FILE']?>") {
|
||||
$("#deleteCheckbox").prop("disabled", true).attr("checked", false);
|
||||
$("#deletePanel").fadeOut();
|
||||
} else {
|
||||
$("#deleteCheckbox").attr("checked", false).prop("disabled", false);
|
||||
$("#deletePanel").fadeIn();
|
||||
}
|
||||
<? } ?>
|
||||
});
|
||||
$("#deleteCheckbox").change(function(){
|
||||
var checked = $(this).is(":checked");
|
||||
$("#SERVICE").prop("disabled", checked).val('disable');
|
||||
$("#IMAGE_SIZE").prop("disabled", checked);
|
||||
$("#IMAGE_FILE").prop("disabled", checked).val("<?=$domain_cfg['IMAGE_FILE']?>");
|
||||
$("#domaindir").prop("disabled", checked);
|
||||
$("#mediadir").prop("disabled", checked);
|
||||
$("#winvirtio_select").prop("disabled", checked);
|
||||
$("#winvirtio").prop("disabled", checked);
|
||||
$("#network").prop("disabled", checked);
|
||||
$("#hostshutdown").prop("disabled", checked);
|
||||
$("#pcie_acs_override").prop("disabled", checked);
|
||||
$("#vm_shutdown_timeout").prop("disabled", checked);
|
||||
$("#applyBtn").val(checked ? "_(Delete)_" : "_(Apply)_").removeAttr('disabled');
|
||||
});
|
||||
}
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:'reboot'}, function(data){
|
||||
var rebootMessage = "_(VM Settings: A reboot is required to apply changes)_";
|
||||
if (data.modified) addRebootNotice(rebootMessage); else removeRebootNotice(rebootMessage);
|
||||
});
|
||||
if ($.cookie('btrfs-scrub-vm')) btrfsScrub($.cookie('btrfs-scrub-vm'));
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,44 @@
|
||||
Title="Add VM"
|
||||
Tag="clipboard"
|
||||
Cond="(pgrep('libvirtd')!==false)"
|
||||
Markdown="false"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2020, Lime Technology
|
||||
* Copyright 2015-2020, 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.
|
||||
*/
|
||||
?>
|
||||
<style type="text/css">
|
||||
.vmheader{padding:10px;font-size:1.4rem;text-align:left;color:#888}
|
||||
.vmtemplate{display:inline-block;width:80px;height:90px;margin-bottom:15px;margin-left:10px;text-align:center;vertical-align:top}
|
||||
.vmtemplate img{width:48px;height:48px}
|
||||
.vmtemplate p{text-align:center;margin-top:8px;line-height:12px}
|
||||
</style>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
foreach($arrAllTemplates as $strName => $arrTemplate):
|
||||
if (empty($arrTemplate)) {
|
||||
// render header
|
||||
echo '<div class="vmheader">'.$strName.'</div>';
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<div class="vmtemplate">
|
||||
<a href="/VMs/AddVM?template=<?=htmlspecialchars(urlencode($strName))?>">
|
||||
<img src="/plugins/dynamix.vm.manager/templates/images/<?=htmlspecialchars($arrTemplate['icon'])?>" title="<?=htmlspecialchars($strName)?>">
|
||||
<p><?=htmlspecialchars($strName)?></p>
|
||||
</a>
|
||||
</div>
|
||||
<? endforeach; ?>
|
||||
<br>
|
||||
<center><button type='button' onclick='done()'>_(Cancel)_</button></center>
|
||||
<br>
|
||||
@@ -0,0 +1,26 @@
|
||||
Menu="Tasks:70"
|
||||
Type="xmenu"
|
||||
Code="e918"
|
||||
Lock="true"
|
||||
Cond="exec(\"grep -o '^SERVICE=.enable' /boot/config/domain.cfg 2>/dev/null\")"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2020, Lime Technology
|
||||
* Copyright 2015-2020, 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 "<div class='notice shift'>"._('Array must be **started** to view Virtual Machines').".</div>";
|
||||
} elseif (!is_file('/var/run/libvirt/libvirtd.pid') || (!is_dir('/proc/'.@file_get_contents('/var/run/libvirt/libvirtd.pid')))) {
|
||||
echo "<div class='notice shift'>"._('Libvirt Service failed to start').".</div>";
|
||||
}
|
||||
if (count($pages)==2) $tabbed = false;
|
||||
?>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2021, Lime Technology
|
||||
* Copyright 2015-2021, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2021, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'vms';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
require_once "$docroot/plugins/dynamix/include/Helpers.php";
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
// get Fedora download archive
|
||||
$fedora = '/var/tmp/fedora-virtio-isos';
|
||||
$archive = 'https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio';
|
||||
exec("wget -T10 -qO- $archive|grep -Po '\"\\Kvirtio-win-[^/]+'",$isos,$code);
|
||||
|
||||
if ($code==0 && count($isos)>1) {
|
||||
// delete obsolete entries
|
||||
foreach ($virtio_isos as $iso => $data) if (!in_array($iso,$isos)) unset($virtio_isos[$iso]);
|
||||
// add new entries
|
||||
foreach ($isos as $iso) {
|
||||
if (isset($virtio_isos[$iso])) continue;
|
||||
$file = explode('-',$iso);
|
||||
if (count($file)==4) array_pop($file);
|
||||
$file = implode('-',$file);
|
||||
$virtio_isos[$iso]['name'] = "$iso.iso";
|
||||
$virtio_isos[$iso]['url'] = "$archive/$iso/$file.iso";
|
||||
$virtio_isos[$iso]['size'] = 600*1024*1024; // assume 600 MB - adjusted once file is downloaded
|
||||
$virtio_isos[$iso]['md5'] = ''; // unused md5 - created once file is downloaded
|
||||
}
|
||||
// sort with newest version first
|
||||
uksort($virtio_isos,function($a,$b){return strnatcmp($b,$a);});
|
||||
// save obtained information to keep '$virtio_isos' up-to-date
|
||||
file_put_contents($fedora,serialize($virtio_isos));
|
||||
} else @unlink($fedora);
|
||||
|
||||
$iso_dir = $domain_cfg['MEDIADIR'];
|
||||
if (empty($iso_dir) || !is_dir($iso_dir)) {
|
||||
$iso_dir = '/mnt/user/isos/';
|
||||
} else {
|
||||
$iso_dir = str_replace('//', '/', $iso_dir.'/');
|
||||
}
|
||||
$strMatch = '';
|
||||
if (!empty($domain_cfg['MEDIADIR']) && !empty($domain_cfg['VIRTIOISO']) && dirname($domain_cfg['VIRTIOISO'])!='.' && is_file($domain_cfg['VIRTIOISO'])) $strMatch = 'manual';
|
||||
foreach ($virtio_isos as $key => $value) {
|
||||
if (($domain_cfg['VIRTIOISO'] == $iso_dir.$value['name']) && is_file($iso_dir.$value['name'])) $strMatch = $value['name'];
|
||||
echo mk_option($strMatch, $value['name'], $value['name']);
|
||||
}
|
||||
echo mk_option($strMatch, 'manual', _('Manual'));
|
||||
?>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2020, Lime Technology
|
||||
* Copyright 2015-2020, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2020, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$user_prefs = '/boot/config/plugins/dynamix.vm.manager/userprefs.cfg';
|
||||
|
||||
if (isset($_POST['reset'])) {
|
||||
@unlink($user_prefs);
|
||||
} else {
|
||||
$names = explode(';',$_POST['names']);
|
||||
$index = explode(';',$_POST['index']);
|
||||
$save = []; $i = 0;
|
||||
|
||||
foreach ($names as $name) if ($name) $save[] = $index[$i++]."=\"".$name."\""; else $i++;
|
||||
if (!is_dir(dirname($user_prefs))) mkdir(dirname($user_prefs));
|
||||
file_put_contents($user_prefs, implode("\n",$save)."\n");
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,225 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2015-2023, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'vms';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
$user_prefs = '/boot/config/plugins/dynamix.vm.manager/userprefs.cfg';
|
||||
$vms = $lv->get_domains();
|
||||
if (empty($vms)) {
|
||||
echo '<tr><td colspan="8" style="text-align:center;padding-top:12px">'._('No Virtual Machines installed').'</td></tr>';
|
||||
return;
|
||||
}
|
||||
if (file_exists($user_prefs)) {
|
||||
$prefs = @parse_ini_file($user_prefs) ?: [];
|
||||
$sort = [];
|
||||
foreach ($vms as $vm) $sort[] = array_search($vm,$prefs);
|
||||
array_multisort($sort,SORT_NUMERIC,$vms);
|
||||
unset($sort);
|
||||
} else {
|
||||
natcasesort($vms);
|
||||
}
|
||||
$i = 0;
|
||||
$kvm = ['var kvm=[];'];
|
||||
$show = explode(',',unscript(_var($_GET,'show')));
|
||||
$path = _var($domain_cfg,'MEDIADIR');
|
||||
|
||||
foreach ($vms as $vm) {
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$desc = $lv->domain_get_description($res);
|
||||
$uuid = $lv->domain_get_uuid($res);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$id = $lv->domain_get_id($res) ?: '-';
|
||||
$autostart = $lv->domain_get_autostart($res) ? 'checked' : '';
|
||||
$state = $lv->domain_state_translate($dom['state']);
|
||||
$icon = $lv->domain_get_icon_url($res);
|
||||
$image = substr($icon,-4)=='.png' ? "<img src='$icon' class='img'>" : (substr($icon,0,5)=='icon-' ? "<i class='$icon img'></i>" : "<i class='fa fa-$icon img'></i>");
|
||||
$arrConfig = domain_to_config($uuid);
|
||||
if ($state == 'running') {
|
||||
$mem = $dom['memory']/1024;
|
||||
} else {
|
||||
$mem = $lv->domain_get_memory($res)/1024;
|
||||
}
|
||||
$mem = round($mem).'M';
|
||||
$vcpu = $dom['nrVirtCpu'];
|
||||
$template = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
|
||||
if (empty($template)) $template = 'Custom';
|
||||
$log = (is_file("/var/log/libvirt/qemu/$vm.log") ? "libvirt/qemu/$vm.log" : '');
|
||||
$disks = '-';
|
||||
$diskdesc = '';
|
||||
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);
|
||||
}
|
||||
$arrValidDiskBuses = getValidDiskBuses();
|
||||
$vmrcport = $lv->domain_get_vnc_port($res);
|
||||
$autoport = $lv->domain_get_vmrc_autoport($res);
|
||||
$vmrcurl = '';
|
||||
$graphics = '';
|
||||
if ($vmrcport > 0) {
|
||||
$wsport = $lv->domain_get_ws_port($res);
|
||||
$vmrcprotocol = $lv->domain_get_vmrc_protocol($res);
|
||||
$vmrcurl = autov('/plugins/dynamix.vm.manager/'.$vmrcprotocol.'.html',true).'&autoconnect=true&host='._var($_SERVER,'HTTP_HOST');
|
||||
if ($vmrcprotocol == "spice") $vmrcurl .= '&vmname='. urlencode($vm) .'&port=/wsproxy/'.$vmrcport.'/'; else $vmrcurl .= '&port=&path=/wsproxy/'.$wsport.'/';
|
||||
$graphics = strtoupper($vmrcprotocol).":".$vmrcport;
|
||||
} elseif ($vmrcport == -1 || $autoport) {
|
||||
$vmrcprotocol = $lv->domain_get_vmrc_protocol($res);
|
||||
if ($autoport == "yes") $auto = "auto"; else $auto="manual";
|
||||
$graphics = strtoupper($vmrcprotocol).':'._($auto);
|
||||
} elseif (!empty($arrConfig['gpu'])) {
|
||||
$arrValidGPUDevices = getValidGPUDevices();
|
||||
foreach ($arrConfig['gpu'] as $arrGPU) {
|
||||
foreach ($arrValidGPUDevices as $arrDev) {
|
||||
if ($arrGPU['id'] == $arrDev['id']) {
|
||||
if (count(array_filter($arrValidGPUDevices, function($v) use ($arrDev) { return $v['name'] == $arrDev['name']; })) > 1) {
|
||||
$graphics .= $arrDev['name'].' ('.$arrDev['id'].')'."\n";
|
||||
$vmrcprotocol = "VGA";
|
||||
} else {
|
||||
$graphics .= $arrDev['name']."\n";
|
||||
$vmrcprotocol = "VGA";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$graphics = str_replace("\n", "<br>", trim($graphics));
|
||||
}
|
||||
unset($dom);
|
||||
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ;
|
||||
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log), $vmrcconsole);
|
||||
$kvm[] = "kvm.push({id:'$uuid',state:'$state'});";
|
||||
switch ($state) {
|
||||
case 'running':
|
||||
$shape = 'play';
|
||||
$status = 'started';
|
||||
$color = 'green-text';
|
||||
break;
|
||||
case 'paused':
|
||||
case 'pmsuspended':
|
||||
$shape = 'pause';
|
||||
$status = 'paused';
|
||||
$color = 'orange-text';
|
||||
break;
|
||||
default:
|
||||
$shape = 'square';
|
||||
$status = 'stopped';
|
||||
$color = 'red-text';
|
||||
break;
|
||||
}
|
||||
|
||||
/* VM information */
|
||||
echo "<tr parent-id='$i' class='sortable'><td class='vm-name' style='width:220px;padding:8px'><i class='fa fa-arrows-v mover orange-text'></i>";
|
||||
echo "<span class='outer'><span id='vm-$uuid' $menu class='hand'>$image</span><span class='inner'><a href='#' onclick='return toggle_id(\"name-$i\")' title='click for more VM info'>$vm</a><br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span></span></td>";
|
||||
echo "<td>$desc</td>";
|
||||
echo "<td><a class='vcpu-$uuid' style='cursor:pointer'>$vcpu</a></td>";
|
||||
echo "<td>$mem</td>";
|
||||
echo "<td title='$diskdesc'>$disks</td>";
|
||||
echo "<td>$graphics</td>";
|
||||
echo "<td><input class='autostart' type='checkbox' name='auto_{$vm}' title=\""._('Toggle VM autostart')."\" uuid='$uuid' $autostart></td></tr>";
|
||||
|
||||
/* Disk device information */
|
||||
echo "<tr child-id='$i' id='name-$i".(in_array('name-'.$i++,$show) ? "'>" : "' style='display:none'>");
|
||||
echo "<td colspan='8' style='margin:0;padding:0'>";
|
||||
echo "<table class='tablesorter domdisk'>";
|
||||
echo "<thead class='child'><tr><th><i class='fa fa-hdd-o'></i> <b>"._('Disk devices')."</b></th><th>"._('Serial')."</b></th><th>"._('Bus')."</th><th>"._('Capacity')."</th><th>"._('Allocation')."</th><th>Boot Order</th</tr></thead>";
|
||||
echo "<tbody class='child'>";
|
||||
|
||||
/* Display VM disks */
|
||||
foreach ($lv->get_disk_stats($res) as $arrDisk) {
|
||||
$capacity = $lv->format_size($arrDisk['capacity'], 0);
|
||||
$allocation = $lv->format_size($arrDisk['allocation'], 0);
|
||||
$disk = $arrDisk['file'] ?? $arrDisk['partition'];
|
||||
$dev = $arrDisk['device'];
|
||||
$bus = $arrValidDiskBuses[$arrDisk['bus']] ?? 'VirtIO';
|
||||
$boot= $arrDisk["boot order"];
|
||||
$serial = $arrDisk["serial"];
|
||||
if ($boot < 1) $boot="Not set";
|
||||
echo "<tr><td>$disk</td><td>$serial</td><td>$bus</td>";
|
||||
if ($state == 'shutoff') {
|
||||
echo "<td title='Click to increase Disk Size'>";
|
||||
echo "<form method='get' action=''>";
|
||||
echo "<input type='hidden' name='subaction' value='disk-resize'>";
|
||||
echo "<input type='hidden' name='uuid' value='".$uuid."'>";
|
||||
echo "<input type='hidden' name='disk' value='".htmlspecialchars($disk)."'>";
|
||||
echo "<input type='hidden' name='oldcap' value='".$capacity."'>";
|
||||
echo "<span class='diskresize' style='width:30px'>";
|
||||
echo "<span class='text'><a href='#' onclick='return false'>$capacity</a></span>";
|
||||
echo "<input class='input' type='text' style='width:46px' name='cap' value='$capacity' val='diskresize' hidden>";
|
||||
echo "</span></form></td>";
|
||||
} else {
|
||||
echo "<td>$capacity</td>";
|
||||
}
|
||||
echo "<td>$allocation</td><td>$boot</td></tr>";
|
||||
}
|
||||
|
||||
/* Display VM cdroms */
|
||||
foreach ($lv->get_cdrom_stats($res) as $arrCD) {
|
||||
$capacity = $lv->format_size($arrCD['capacity'], 0);
|
||||
$allocation = $lv->format_size($arrCD['allocation'], 0);
|
||||
$disk = $arrCD['file'] ?? $arrCD['partition'];
|
||||
$dev = $arrCD['device'];
|
||||
$bus = $arrValidDiskBuses[$arrCD['bus']] ?? 'VirtIO';
|
||||
$boot= $arrCD["boot order"];
|
||||
if ($boot < 1) $boot="Not set";
|
||||
if ($disk != "" ) {
|
||||
$title = _("Eject CD Drive").".";
|
||||
$changemedia = "changemedia(\"{$uuid}\",\"{$dev}\",\"{$bus}\", \"--eject\")";
|
||||
echo "<tr><td>$disk <a title='$title' href='#' onclick='$changemedia'> <i class='fa fa-eject' aria-hidden=true></i></a></td><td></td><td>$bus</td><td>$capacity</td><td>$allocation</td><td>$boot</td></tr>";
|
||||
} else {
|
||||
$title = _("Insert CD").".";
|
||||
$changemedia = "changemedia(\"{$uuid}\",\"{$dev}\",\"{$bus}\",\"--select\")";
|
||||
$disk = _("No CD image inserted in to drive");
|
||||
echo "<tr><td>$disk<a title='$title' href='#' onclick='$changemedia'> <i class='fa fa-bullseye' aria-hidden=true></i></a> </td><td></td><td>$bus</td><td>$capacity</td><td>$allocation</td><td>$boot</td></tr>";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
echo "</tbody>";
|
||||
/* Display VM IP Addresses "execute":"guest-network-get-interfaces" --pretty */
|
||||
echo "<thead class='child'><tr><th><i class='fa fa-sitemap'></i> <b>"._('Interfaces')."</b></th><th></th><th></th><th>"._('Type')."</th><th>"._('IP Address')."</th><th>"._('Prefix')."</th></tr></thead>";
|
||||
echo "<tbody class='child'>";
|
||||
$gastate = getgastate($res);
|
||||
if ($gastate == "connected") {
|
||||
$ip = $lv->domain_interface_addresses($res, 1);
|
||||
if ($ip != false) {
|
||||
$duplicates = []; // hide duplicate interface names
|
||||
foreach ($ip as $arrIP) {
|
||||
$ipname = $arrIP["name"];
|
||||
if (preg_match('/^(lo|Loopback)/',$ipname)) continue; // omit loopback interface
|
||||
$iphdwadr = $arrIP["hwaddr"] == "" ? _("N/A") : $arrIP["hwaddr"];
|
||||
$iplist = $arrIP["addrs"];
|
||||
foreach ($iplist as $arraddr) {
|
||||
$ipaddrval = $arraddr["addr"];
|
||||
if (preg_match('/^f[c-f]/',$ipaddrval)) continue; // omit ipv6 private addresses
|
||||
$iptype = $arraddr["type"] ? "ipv6" : "ipv4";
|
||||
$ipprefix = $arraddr["prefix"];
|
||||
$ipnamemac = "$ipname ($iphdwadr)";
|
||||
if (!in_array($ipnamemac,$duplicates)) $duplicates[] = $ipnamemac; else $ipnamemac = "";
|
||||
echo "<tr><td>$ipnamemac</td><td></td><td></td><td>$iptype</td><td>$ipaddrval</td><td>$ipprefix</td></tr>";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($gastate == "disconnected") echo "<tr><td>"._('Guest agent not installed')."</td><td></td><td></td><td></td></tr>";
|
||||
else echo "<tr><td>"._('Guest not running')."</td><td></td><td></td><td></td><td></td></tr>";
|
||||
}
|
||||
echo "</tbody></table>";
|
||||
echo "</td></tr>";
|
||||
}
|
||||
echo "\0".implode($kvm);
|
||||
?>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2020, Lime Technology
|
||||
* Copyright 2015-2020, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2020, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
$user_prefs = '/boot/config/plugins/dynamix.vm.manager/userprefs.cfg';
|
||||
|
||||
$act = $_POST['action'];
|
||||
$vms = $lv->get_domains();
|
||||
|
||||
if (file_exists($user_prefs)) {
|
||||
$prefs = parse_ini_file($user_prefs); $sort = [];
|
||||
foreach ($vms as $vm) $sort[] = array_search($vm,$prefs) ?? 999;
|
||||
array_multisort($sort, ($act=='start'?SORT_ASC:SORT_DESC), SORT_NUMERIC, $vms);
|
||||
} else {
|
||||
array_multisort($vms, ($act=='start'?SORT_ASC:SORT_DESC), SORT_NATURAL|SORT_FLAG_CASE);
|
||||
}
|
||||
|
||||
foreach ($vms as $vm) {
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$uuid = $lv->domain_get_uuid($res);
|
||||
$domName = $lv->domain_get_name_by_uuid($uuid);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$state = $lv->domain_state_translate($dom['state']);
|
||||
switch ($act) {
|
||||
case 'stop':
|
||||
if ($state!='running') continue;
|
||||
$result = $lv->domain_shutdown($domName) ? ['success'=>true, 'state'=>$lv->domain_get_state($domName)] : ['error'=>$lv->get_last_error()];
|
||||
$n = 20; // wait for VM to die
|
||||
while ($result['success'] && $lv->domain_get_state($domName)=='running') {sleep(1); if(!--$n) break;}
|
||||
break;
|
||||
case 'start':
|
||||
if ($state=='running') continue;
|
||||
$result = $lv->domain_start($domName) ? ['success'=>true, 'state'=>$lv->domain_get_state($domName)] : ['error'=>$lv->get_last_error()];
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,665 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2015-2023, Derek Macias, Eric Schultz, Jon Panozzo.
|
||||
* Copyright 2012-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'vms';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
function requireLibvirt() {
|
||||
global $lv;
|
||||
// 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']));
|
||||
}
|
||||
}
|
||||
|
||||
function scan($line, $text) {
|
||||
return stripos($line,$text)!==false;
|
||||
}
|
||||
|
||||
function embed(&$syslinux, $key, $value) {
|
||||
$size = count($syslinux);
|
||||
$make = false;
|
||||
$new = strlen($value) ? "$key=$value" : false;
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
// find sections and exclude safemode
|
||||
if (scan($syslinux[$i],'label ') && !scan($syslinux[$i],'safe mode') && !scan($syslinux[$i],'safemode')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'append ')) {
|
||||
$cmd = preg_split('/\s+/',trim($syslinux[$n]));
|
||||
// replace the existing setting
|
||||
for ($c = 1; $c < count($cmd); $c++) if (scan($cmd[$c],$key)) {$make |= ($cmd[$c]!=$new); $cmd[$c] = $new; break;}
|
||||
// or insert the new setting
|
||||
if ($c==count($cmd) && $new) {array_splice($cmd,-1,0,$new); $make = true;}
|
||||
$syslinux[$n] = ' '.str_replace(' ',' ',implode(' ',$cmd));
|
||||
}
|
||||
$n++;
|
||||
}
|
||||
$i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return $make;
|
||||
}
|
||||
|
||||
$arrSizePrefix = [0 => '', 1 => 'K', 2 => 'M', 3 => 'G', 4 => 'T', 5 => 'P'];
|
||||
$action = unscript(_var($_REQUEST,'action'));
|
||||
$uuid = unscript(_var($_REQUEST,'uuid'));
|
||||
$arrResponse = [];
|
||||
|
||||
if ($uuid) {
|
||||
requireLibvirt();
|
||||
$domName = $lv->domain_get_name_by_uuid($uuid);
|
||||
if (!$domName) {
|
||||
header('Content-Type: application/json');
|
||||
die(json_encode(['error' => $lv->get_last_error()]));
|
||||
}
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'domain-autostart':
|
||||
requireLibvirt();
|
||||
$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':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_start($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-start-console':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_start($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
$dom = $lv->get_domain_by_name($domName);
|
||||
$vmrcport = $lv->domain_get_vnc_port($dom);
|
||||
$wsport = $lv->domain_get_ws_port($dom);
|
||||
$protocol = $lv->domain_get_vmrc_protocol($dom);
|
||||
if ($vmrcport > 0) {
|
||||
$vmrcurl = autov('/plugins/dynamix.vm.manager/'.$protocol.'.html',true).'&autoconnect=true&host='._var($_SERVER,'HTTP_HOST');
|
||||
if ($protocol == "spice") $vmrcurl .= '&vmname='. urlencode($domName) .'&port=/wsproxy/'.$vmrcport.'/'; else $vmrcurl .= '&port=&path=/wsproxy/'.$wsport.'/';
|
||||
}
|
||||
$arrResponse['vmrcurl'] = $vmrcurl;
|
||||
break;
|
||||
|
||||
case 'domain-start-consoleRV':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_start($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
$dom = $lv->get_domain_by_name($domName);
|
||||
$vmrcport = $lv->domain_get_vnc_port($dom);
|
||||
$wsport = $lv->domain_get_ws_port($dom);
|
||||
$protocol = $lv->domain_get_vmrc_protocol($dom);
|
||||
if ($protocol == "spice") $port= $vmrcport ; else $port=$vmrcport ;
|
||||
$vvarray = array() ;
|
||||
$vvarray[] = "[virt-viewer]\n";
|
||||
$vvarray[] = "type=$protocol\n";
|
||||
$vvarray[] = "host="._var($_SERVER,'HTTP_HOST')."\n" ;
|
||||
$vvarray[] = "port=$port\n" ;
|
||||
if (!is_dir("/mnt/user/system/remoteviewer")) mkdir("/mnt/user/system/remoteviewer") ;
|
||||
$vvfile = "/mnt/user/system/remoteviewer/rv"._var($_SERVER,'HTTP_HOST').".$port.vv" ;
|
||||
file_put_contents($vvfile,$vvarray) ;
|
||||
$arrResponse['vvfile'] = $vvfile;
|
||||
break;
|
||||
|
||||
case 'domain-consoleRV':
|
||||
requireLibvirt();
|
||||
$dom = $lv->get_domain_by_name($domName);
|
||||
$vmrcport = $lv->domain_get_vnc_port($dom);
|
||||
$wsport = $lv->domain_get_ws_port($dom);
|
||||
$protocol = $lv->domain_get_vmrc_protocol($dom);
|
||||
if ($protocol == "spice") $port= $vmrcport ; else $port=$vmrcport ;
|
||||
$vvarray = array() ;
|
||||
$vvarray[] = "[virt-viewer]\n";
|
||||
$vvarray[] = "type=$protocol\n";
|
||||
$vvarray[] = "host="._var($_SERVER,'HTTP_HOST')."\n" ;
|
||||
$vvarray[] = "port=$port\n" ;
|
||||
if (!is_dir("/mnt/user/system/remoteviewer")) mkdir("/mnt/user/system/remoteviewer") ;
|
||||
$vvfile = "/mnt/user/system/remoteviewer/rv"._var($_SERVER,'HTTP_HOST').".$port.vv" ;
|
||||
file_put_contents($vvfile,$vvarray) ;
|
||||
$arrResponse['vvfile'] = $vvfile;
|
||||
break;
|
||||
|
||||
case 'domain-pause':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_suspend($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-resume':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_resume($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-pmsuspend':
|
||||
requireLibvirt();
|
||||
// No support in libvirt-php to do a dompmsuspend, use virsh tool instead
|
||||
exec("virsh dompmsuspend ".escapeshellarg($uuid)." disk 2>&1", $arrOutput, $intReturnCode);
|
||||
$arrResponse = $intReturnCode==0
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => str_replace('error: ', '', implode('. ', $arrOutput))];
|
||||
break;
|
||||
|
||||
case 'domain-pmwakeup':
|
||||
requireLibvirt();
|
||||
// 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':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_reboot($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-save':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_save($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-stop':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_shutdown($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
$n = 30; // wait for VM to die
|
||||
while ($arrResponse['success'] && $lv->domain_get_state($domName)=='running') {
|
||||
sleep(1); if(!--$n) break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'domain-destroy':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_destroy($domName)
|
||||
? ['success' => true, 'state' => $lv->domain_get_state($domName)]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-delete':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_delete($domName)
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-undefine':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_undefine($domName)
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-define':
|
||||
requireLibvirt();
|
||||
$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':
|
||||
requireLibvirt();
|
||||
$state = $lv->domain_get_state($domName);
|
||||
$arrResponse = $state
|
||||
? ['success' => true, 'state' => $state]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'domain-diskdev':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_set_disk_dev($domName, $_REQUEST['olddev'], $_REQUEST['diskdev'])
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'cdrom-change':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_change_cdrom($domName, $_REQUEST['cdrom'], $_REQUEST['dev'], $_REQUEST['bus'])
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'change-media':
|
||||
requireLibvirt();
|
||||
$dev= $_REQUEST['dev'];
|
||||
$file= $_REQUEST['file'];
|
||||
$cmdstr = "virsh change-media '$domName' $dev $file";
|
||||
$rtn=shell_exec($cmdstr)
|
||||
? ['success' => true]
|
||||
: ['error' => "Change Media Failed"];
|
||||
break;
|
||||
|
||||
case 'memory-change':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_set_memory($domName, $_REQUEST['memory']*1024)
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'vcpu-change':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_set_vcpu($domName, $_REQUEST['vcpu'])
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'bootdev-change':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_set_boot_device($domName, $_REQUEST['bootdev'])
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'disk-remove':
|
||||
requireLibvirt();
|
||||
// libvirt-php has an issue with detaching a disk, use virsh tool instead
|
||||
exec("virsh detach-disk ".escapeshellarg($uuid)." ".escapeshellarg($_REQUEST['dev'])." 2>&1", $arrOutput, $intReturnCode);
|
||||
$arrResponse = $intReturnCode==0
|
||||
? ['success' => true]
|
||||
: ['error' => str_replace('error: ', '', implode('. ', $arrOutput))];
|
||||
break;
|
||||
|
||||
case 'snap-create':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_snapshot_create($domName)
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'snap-delete':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_snapshot_delete($domName, $_REQUEST['snap'])
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'snap-revert':
|
||||
requireLibvirt();
|
||||
$arrResponse = $lv->domain_snapshot_revert($domName, $_REQUEST['snap'])
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'snap-desc':
|
||||
requireLibvirt();
|
||||
$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(["KB","MB","GB","TB","PB", " ", ","], ["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
|
||||
$dir = transpose_user_path($dir);
|
||||
@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);
|
||||
$arrResponse = empty($status)
|
||||
? ['success' => true]
|
||||
: ['error' => $strLastLine];
|
||||
break;
|
||||
|
||||
case 'disk-resize':
|
||||
$disk = $_REQUEST['disk'];
|
||||
$capacity = str_replace(["KB","MB","GB","TB","PB", " ", ","], ["K","M","G","T","P", "", ""], strtoupper($_REQUEST['cap']));
|
||||
$old_capacity = str_replace(["KB","MB","GB","TB","PB", " ", ","], ["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);
|
||||
$arrResponse = empty($status)
|
||||
? ['success' => true]
|
||||
: ['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 = getDiskImageInfo($file);
|
||||
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;
|
||||
}
|
||||
} elseif (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':
|
||||
requireLibvirt();
|
||||
$arrResponse = ['mac' => $lv->generate_random_mac_addr()];
|
||||
break;
|
||||
|
||||
case 'get-vm-icons':
|
||||
$arrImages = [];
|
||||
foreach (glob("$docroot/plugins/dynamix.vm.manager/templates/images/*.png") as $png_file) {
|
||||
$arrImages[] = [
|
||||
'custom' => false,
|
||||
'basename' => basename($png_file),
|
||||
'url' => '/plugins/dynamix.vm.manager/templates/images/'.basename($png_file)
|
||||
];
|
||||
}
|
||||
$arrResponse = $arrImages;
|
||||
break;
|
||||
|
||||
case 'get-usb-devices':
|
||||
$arrValidUSBDevices = getValidUSBDevices();
|
||||
$arrResponse = $arrValidUSBDevices;
|
||||
break;
|
||||
|
||||
case 'hot-attach-usb':
|
||||
//TODO - If usb is a block device, then attach as a <disk type="usb"> otherwise <hostdev type="usb">
|
||||
/*
|
||||
<hostdev mode='subsystem' type='usb'>
|
||||
<source startupPolicy='optional'>
|
||||
<vendor id='0x1234'/>
|
||||
<product id='0xbeef'/>
|
||||
</source>
|
||||
</hostdev>
|
||||
<disk type='block' device='disk'>
|
||||
<driver name='qemu' type='raw'/>
|
||||
<source dev='/dev/sda'/>
|
||||
<target dev='hdX' bus='virtio'/>
|
||||
</disk>
|
||||
*/
|
||||
break;
|
||||
|
||||
case 'hot-detach-usb':
|
||||
//TODO
|
||||
break;
|
||||
|
||||
case 'syslinux':
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$syslinux = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$m1 = embed($syslinux, 'pcie_acs_override', $_REQUEST['pcie']);
|
||||
$m2 = embed($syslinux, 'vfio_iommu_type1.allow_unsafe_interrupts', $_REQUEST['vfio']);
|
||||
if ($m1||$m2) file_put_contents($cfg, implode("\n",$syslinux)."\n");
|
||||
$arrResponse = ['success' => true, 'modified' => $m1|$m2];
|
||||
break;
|
||||
|
||||
case 'reboot':
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$syslinux = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$cmdline = explode(' ',file_get_contents('/proc/cmdline'));
|
||||
$pcie = $vfio = '';
|
||||
foreach ($cmdline as $cmd) {
|
||||
if (scan($cmd,'pcie_acs_override')) $pcie = explode('=',$cmd)[1];
|
||||
if (scan($cmd,'allow_unsafe_interrupts')) $vfio = explode('=',$cmd)[1];
|
||||
}
|
||||
$m1 = embed($syslinux, 'pcie_acs_override', $pcie);
|
||||
$m2 = embed($syslinux, 'vfio_iommu_type1.allow_unsafe_interrupts', $vfio);
|
||||
$arrResponse = ['success' => true, 'modified' => $m1|$m2];
|
||||
break;
|
||||
|
||||
case 'virtio-win-iso-info':
|
||||
$path = $_REQUEST['path'];
|
||||
$file = $_REQUEST['file'];
|
||||
$pid = pgrep('-f "VirtIOWin_'.basename($file, '.iso').'_install.sh"', false);
|
||||
if (empty($file)) {
|
||||
$arrResponse = ['exists' => false, 'pid' => $pid];
|
||||
break;
|
||||
}
|
||||
if (is_file($file)) {
|
||||
$arrResponse = ['exists' => true, 'pid' => $pid, 'path' => $file];
|
||||
break;
|
||||
}
|
||||
if (empty($path) || !is_dir($path)) {
|
||||
$path = '/mnt/user/isos/';
|
||||
} else {
|
||||
$path = str_replace('//', '/', $path.'/');
|
||||
}
|
||||
$file = $path.$file;
|
||||
$arrResponse = is_file($file)
|
||||
? ['exists' => true, 'pid' => $pid, 'path' => $file]
|
||||
: ['exists' => false, 'pid' => $pid];
|
||||
break;
|
||||
|
||||
case 'virtio-win-iso-download':
|
||||
$arrDownloadVirtIO = [];
|
||||
$strKeyName = basename($_REQUEST['download_version'], '.iso');
|
||||
if (array_key_exists($strKeyName, $virtio_isos)) {
|
||||
$arrDownloadVirtIO = $virtio_isos[$strKeyName];
|
||||
}
|
||||
if (empty($arrDownloadVirtIO)) {
|
||||
$arrResponse = ['error' => _('Unknown version').': '.$_REQUEST['download_version']];
|
||||
} elseif (empty($_REQUEST['download_path'])) {
|
||||
$arrResponse = ['error' => _('Specify a ISO storage path first')];
|
||||
} elseif (!is_dir($_REQUEST['download_path'])) {
|
||||
$arrResponse = ['error' => _("ISO storage path doesn't exist, please create the user share (or empty folder) first")];
|
||||
} elseif (substr(realpath($_REQUEST['download_path'])?:'',0,5) != '/mnt/') {
|
||||
$arrResponse = ['error' => _('Invalid storage path')];
|
||||
} else {
|
||||
@mkdir($_REQUEST['download_path'], 0777, true);
|
||||
$_REQUEST['download_path'] = realpath($_REQUEST['download_path']).'/';
|
||||
// Check free space
|
||||
if (disk_free_space($_REQUEST['download_path']) < $arrDownloadVirtIO['size']+10000) {
|
||||
$arrResponse['error'] = _('Not enough free space, need at least').' '.ceil($arrDownloadVirtIO['size']/1000000).'MB';
|
||||
break;
|
||||
}
|
||||
$boolCheckOnly = !empty($_REQUEST['checkonly']);
|
||||
$strInstallScript = '/tmp/VirtIOWin_'.$strKeyName.'_install.sh';
|
||||
$strInstallScriptPgrep = '-f "VirtIOWin_'.$strKeyName.'_install.sh"';
|
||||
$strTargetFile = $_REQUEST['download_path'].$arrDownloadVirtIO['name'];
|
||||
$strLogFile = $strTargetFile.'.log';
|
||||
$strMD5File = $strTargetFile.'.md5';
|
||||
$strMD5StatusFile = $strTargetFile.'.md5status';
|
||||
// Save to /boot/config/domain.conf
|
||||
$domain_cfg['MEDIADIR'] = $_REQUEST['download_path'];
|
||||
$domain_cfg['VIRTIOISO'] = $strTargetFile;
|
||||
$tmp = ''; $monitor = '/tmp/wget.monitor'; $dots = '... ';
|
||||
foreach ($domain_cfg as $key => $value) $tmp .= "$key=\"$value\"\n";
|
||||
file_put_contents($domain_cfgfile, $tmp);
|
||||
$strDownloadCmd = 'wget -cO '.escapeshellarg($strTargetFile).' '.escapeshellarg($arrDownloadVirtIO['url']);
|
||||
$strDownloadPgrep = '-f "wget.*'.$strTargetFile.'.*'.$arrDownloadVirtIO['url'].'"';
|
||||
$strVerifyCmd = $arrDownloadVirtIO['md5'] ? 'md5sum -c '.escapeshellarg($strMD5File) : 'md5sum '.escapeshellarg($strTargetFile);
|
||||
$strVerifyPgrep = '-f "md5sum.*'.($arrDownloadVirtIO['md5'] ? $strMD5File : $strTargetFile).'"';
|
||||
$strCleanCmd = '(chmod 777 '.escapeshellarg($_REQUEST['download_path']).' '.escapeshellarg($strTargetFile).'; chown nobody:users '.escapeshellarg($_REQUEST['download_path']).' '.escapeshellarg($strTargetFile).'; rm -f '.escapeshellarg($strMD5File).' '.escapeshellarg($strMD5StatusFile).')';
|
||||
//$strCleanPgrep = '-f "chmod.*chown.*rm.*'.$strMD5StatusFile.'"';
|
||||
$strAllCmd = "#!/bin/bash\n\n";
|
||||
$strAllCmd .= $strDownloadCmd.' >>'.escapeshellarg($strLogFile)." 2>$monitor && sleep 1 && ";
|
||||
$strAllCmd .= 'echo "'.$arrDownloadVirtIO['md5'].' '.$strTargetFile.'" >'.escapeshellarg($strMD5File).' && sleep 3 && ';
|
||||
$strAllCmd .= $strVerifyCmd.' >'.escapeshellarg($strMD5StatusFile).' 2>/dev/null && sleep 3 && ';
|
||||
$strAllCmd .= $strCleanCmd.' >>'.escapeshellarg($strLogFile).' 2>&1 && ';
|
||||
$strAllCmd .= 'rm -f '.escapeshellarg($strLogFile).' '.escapeshellarg($strInstallScript).' '.escapeshellarg($monitor);
|
||||
$arrResponse = [];
|
||||
if (file_exists($strTargetFile)) {
|
||||
if (!file_exists($strLogFile)) {
|
||||
if (!pgrep($strDownloadPgrep, false)) {
|
||||
// Status = done
|
||||
$arrResponse['status'] = _('Done');
|
||||
$arrResponse['localpath'] = $strTargetFile;
|
||||
$arrResponse['localfolder'] = dirname($strTargetFile);
|
||||
} else {
|
||||
// Status = cleanup
|
||||
$arrResponse['status'] = _('Cleanup').$dots;
|
||||
}
|
||||
} else {
|
||||
if (pgrep($strDownloadPgrep, false)) {
|
||||
// Get Download progress and eta
|
||||
[$done,$eta] = my_explode(' ',exec("tail -2 $monitor|awk 'NF==9 {print \$7,\$9;exit}'"));
|
||||
$arrResponse['status'] = _('Downloading').$dots.$done.', '._('ETA').': '.$eta;
|
||||
} elseif (pgrep($strVerifyPgrep, false)) {
|
||||
// Status = running md5 check
|
||||
$arrResponse['status'] = _('Verifying').$dots;
|
||||
} elseif (file_exists($strMD5StatusFile)) {
|
||||
// Status = running extract
|
||||
$arrResponse['status'] = _('Cleanup').$dots;
|
||||
// Examine md5 status
|
||||
$strMD5StatusContents = file_get_contents($strMD5StatusFile);
|
||||
if (strpos($strMD5StatusContents, ': FAILED')!==false) {
|
||||
// ERROR: MD5 check failed
|
||||
unset($arrResponse['status']);
|
||||
$arrResponse['error'] = _('MD5 verification failed, your download is incomplete or corrupted');
|
||||
} elseif (!$arrDownloadVirtIO['md5']) {
|
||||
// MD5 checksum & size missing: add these
|
||||
$fedora = '/var/tmp/fedora-virtio-isos';
|
||||
if (file_exists($fedora)) {
|
||||
$virtio_isos = unserialize(file_get_contents($fedora));
|
||||
$iso = basename($arrDownloadVirtIO['name'], '.iso');
|
||||
$virtio_isos[$iso]['size'] = filesize($strTargetFile);
|
||||
$virtio_isos[$iso]['md5'] = explode(' ',$strMD5StatusContents)[0];
|
||||
file_put_contents($fedora,serialize($virtio_isos));
|
||||
}
|
||||
}
|
||||
} elseif (!file_exists($strMD5File)) {
|
||||
@unlink($monitor);
|
||||
// Status = running md5 check
|
||||
$arrResponse['status'] = _('Downloading').$dots.'100%';
|
||||
if (!pgrep($strInstallScriptPgrep, false) && !$boolCheckOnly) {
|
||||
// Run all commands
|
||||
file_put_contents($strInstallScript, $strAllCmd);
|
||||
chmod($strInstallScript, 0777);
|
||||
exec($strInstallScript.' >/dev/null 2>&1 &');
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (!$boolCheckOnly) {
|
||||
@unlink($monitor);
|
||||
if (!pgrep($strInstallScriptPgrep, false)) {
|
||||
// Run all commands
|
||||
file_put_contents($strInstallScript, $strAllCmd);
|
||||
chmod($strInstallScript, 0777);
|
||||
exec($strInstallScript.' >/dev/null 2>&1 &');
|
||||
}
|
||||
$arrResponse['status'] = _('Downloading').$dots.'0%';
|
||||
}
|
||||
$arrResponse['pid'] = pgrep($strInstallScriptPgrep, false);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'virtio-win-iso-cancel':
|
||||
$arrDownloadVirtIO = [];
|
||||
$strKeyName = basename($_REQUEST['download_version'], '.iso');
|
||||
if (array_key_exists($strKeyName, $virtio_isos)) {
|
||||
$arrDownloadVirtIO = $virtio_isos[$strKeyName];
|
||||
}
|
||||
if (empty($arrDownloadVirtIO)) {
|
||||
$arrResponse = ['error' => _('Unknown version').': '.$_REQUEST['download_version']];
|
||||
} elseif (empty($_REQUEST['download_path'])) {
|
||||
$arrResponse = ['error' => _('ISO storage path was empty')];
|
||||
} elseif (!is_dir($_REQUEST['download_path'])) {
|
||||
$arrResponse = ['error' => _("ISO storage path doesn't exist")];
|
||||
} else {
|
||||
$strInstallScriptPgrep = '-f "VirtIOWin_'.$strKeyName.'_install.sh"';
|
||||
$pid = pgrep($strInstallScriptPgrep, false);
|
||||
if (!$pid) {
|
||||
$arrResponse = ['error' => _('Not running')];
|
||||
} else {
|
||||
if (!posix_kill($pid, SIGTERM)) {
|
||||
$arrResponse = ['error' => _("Wasn't able to stop the process")];
|
||||
} else {
|
||||
$strTargetFile = $_REQUEST['download_path'].$arrDownloadVirtIO['name'];
|
||||
$strLogFile = $strTargetFile.'.log';
|
||||
$strMD5File = $strTargetFile.'.md5';
|
||||
$strMD5StatusFile = $strTargetFile.'.md5status';
|
||||
@unlink($strTargetFile);
|
||||
@unlink($strMD5File);
|
||||
@unlink($strMD5StatusFile);
|
||||
@unlink($strLogFile);
|
||||
$arrResponse['status'] = _('Done');
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'virtio-win-iso-remove':
|
||||
$path = $_REQUEST['path'];
|
||||
$file = $_REQUEST['file'];
|
||||
$pid = pgrep('-f "VirtIOWin_'.basename($file, '.iso').'_install.sh"', false);
|
||||
if (empty($file) || substr($file, -4) !== '.iso') {
|
||||
$arrResponse = ['success' => false];
|
||||
break;
|
||||
}
|
||||
if ($pid !== false) {
|
||||
$arrResponse = ['success' => false];
|
||||
break;
|
||||
}
|
||||
if (is_file($file)) {
|
||||
foreach (glob($file.'*') as $name) unlink($name);
|
||||
$arrResponse = ['success' => true];
|
||||
break;
|
||||
}
|
||||
if (empty($path) || !is_dir($path)) {
|
||||
$path = '/mnt/user/isos/';
|
||||
} else {
|
||||
$path = str_replace('//', '/', $path.'/');
|
||||
}
|
||||
if (is_file($path.$file)) {
|
||||
foreach (glob($path.$file.'*') as $name) unlink($name);
|
||||
$arrResponse = ['success' => true];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$arrResponse = ['error' => _('Unknown action')." '$action'"];
|
||||
break;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
die(json_encode($arrResponse));
|
||||
@@ -0,0 +1,272 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2015-2023, 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.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
// add translations
|
||||
if (substr($_SERVER['REQUEST_URI'],0,4) != '/VMs') {
|
||||
$_SERVER['REQUEST_URI'] = 'vms';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
}
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
|
||||
switch ($display['theme']) {
|
||||
case 'gray' : $bgcolor = '#121510'; $border = '#606e7f'; $top = -44; break;
|
||||
case 'azure': $bgcolor = '#edeaef'; $border = '#606e7f'; $top = -44; break;
|
||||
case 'black': $bgcolor = '#212121'; $border = '#2b2b2b'; $top = -58; break;
|
||||
default : $bgcolor = '#ededed'; $border = '#e3e3e3'; $top = -58; break;
|
||||
}
|
||||
|
||||
$strSelectedTemplate = array_keys($arrAllTemplates)[1];
|
||||
if (isset($_GET['template']) && isset($arrAllTemplates[unscript($_GET['template'])])) {
|
||||
$strSelectedTemplate = unscript($_GET['template']);
|
||||
}
|
||||
|
||||
$arrLoad = [
|
||||
'name' => '',
|
||||
'icon' => $arrAllTemplates[$strSelectedTemplate]['icon'],
|
||||
'autostart' => false,
|
||||
'form' => $arrAllTemplates[$strSelectedTemplate]['form'],
|
||||
'state' => 'shutoff'
|
||||
];
|
||||
$strIconURL = '/plugins/dynamix.vm.manager/templates/images/'.$arrLoad['icon'];
|
||||
|
||||
if (isset($_GET['uuid'])) {
|
||||
// Edit VM mode
|
||||
$res = $lv->domain_get_domain_by_uuid(unscript($_GET['uuid']));
|
||||
|
||||
if ($res === false) {
|
||||
echo "<p class='notice'>"._('Invalid VM to edit').".</p><input type='button' value=\""._('Done')."\" onclick='done()'>";
|
||||
return;
|
||||
}
|
||||
|
||||
$strIconURL = $lv->domain_get_icon_url($res);
|
||||
$arrLoad = [
|
||||
'name' => $lv->domain_get_name($res),
|
||||
'icon' => basename($strIconURL),
|
||||
'autostart' => $lv->domain_get_autostart($res),
|
||||
'form' => $arrAllTemplates[$strSelectedTemplate]['form'],
|
||||
'state' => $lv->domain_get_state($res)
|
||||
];
|
||||
|
||||
if (empty($_GET['template'])) {
|
||||
// read vm-template attribute
|
||||
$strTemplateOS = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@os');
|
||||
$strLibreELEC = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@libreelec');
|
||||
$strOpenELEC = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@openelec');
|
||||
if ($strLibreELEC) $strSelectedTemplate = 'LibreELEC';
|
||||
elseif ($strOpenELEC) $strSelectedTemplate = 'OpenELEC';
|
||||
elseif ($strTemplateOS) {
|
||||
$strSelectedTemplate = $lv->_get_single_xpath_result($res, '//domain/metadata/*[local-name()=\'vmtemplate\']/@name');
|
||||
} else {
|
||||
// legacy VM support for <6.2 but need it going forward too
|
||||
foreach ($arrAllTemplates as $strName => $arrTemplate) {
|
||||
if (!empty($arrTemplate) && !empty($arrTemplate['os']) && $arrTemplate['os'] == $strTemplateOS) {
|
||||
$strSelectedTemplate = $strName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($strSelectedTemplate) || empty($arrAllTemplates[$strSelectedTemplate])) {
|
||||
$strSelectedTemplate = 'Windows 10'; //default to Windows 10
|
||||
}
|
||||
}
|
||||
$arrLoad['form'] = $arrAllTemplates[$strSelectedTemplate]['form'];
|
||||
}
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.filetree.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
|
||||
|
||||
<style>
|
||||
body{-webkit-overflow-scrolling:touch}
|
||||
.fileTree{background:<?=$bgcolor?>;width:300px;max-height:150px;overflow-y:scroll;overflow-x:hidden;position:relative;z-index:100;display:none}
|
||||
#vmform table{margin-top:0}
|
||||
#vmform div.title + table{margin-top:0}
|
||||
#vmform table tr{vertical-align:top;line-height:40px}
|
||||
#vmform table tr td:nth-child(odd){width:300px;text-align:right;padding-right:10px}
|
||||
#vmform table tr td:nth-child(even){width:110px}
|
||||
#vmform table tr td:first-child{padding-right:30px}
|
||||
#vmform table tr td:last-child{width:inherit}
|
||||
#vmform .multiple{position:relative}
|
||||
#vmform .sectionbutton{position:absolute;left:2px;cursor:pointer;opacity:0.4;font-size:1.4rem;line-height:17px;z-index:10;transition-property:opacity,left;transition-duration:0.1s;transition-timing-function:linear}
|
||||
#vmform .sectionbutton.remove{top:0;opacity:0.3}
|
||||
#vmform .sectionbutton.add{bottom:0}
|
||||
#vmform .sectionbutton:hover{opacity:1.0}
|
||||
#vmform .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}
|
||||
#vmform .multiple:hover .sectionbutton{opacity:0.7;left:4px}
|
||||
#vmform .multiple:hover .sectionbutton.remove{opacity:0.6}
|
||||
#vmform .multiple:hover .sectiontab{background-color:#CCCCCC;width:8px}
|
||||
#vmform table.multiple{margin:10px 0;background:<?=$bgcolor?>;background-size:800px 100%;background-position:-800px;background-repeat:no-repeat;background-clip:content-box;transition:background 0.3s linear}
|
||||
#vmform table.multiple:hover{background-position:0 0;}
|
||||
#vmform table.multiple td{padding:5px 0}
|
||||
span.advancedview_panel{display:none;line-height:16px;margin-top:1px}
|
||||
.basic{display:none}
|
||||
.advanced{/*Empty placeholder*/}
|
||||
.switch-button-label.off{color:inherit}
|
||||
#template_img{cursor:pointer}
|
||||
#template_img:hover{opacity:0.5}
|
||||
#template_img:hover i{opacity:1.0}
|
||||
.template_img_chooser_inner{display:inline-block;width:80px;margin-bottom:15px;margin-right:10px;text-align:center;}
|
||||
.template_img_chooser_inner img{width:48px;height:48px}
|
||||
.template_img_chooser_inner p{text-align:center;line-height:8px;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative}
|
||||
#template_img_chooser div:hover{background-color:#eee;cursor:pointer;}
|
||||
#template_img_chooser_outer{position:absolute;display:none;border-radius:5px;border:1px solid <?=$border?>;background:<?=$bgcolor?>;z-index:10}
|
||||
#form_content{display:none}
|
||||
#vmform .four{overflow:hidden}
|
||||
#vmform .four label{float:left;display:table-cell;width:15%;}
|
||||
#vmform .four label:nth-child(4n+4){}
|
||||
#vmform .four label.cpu1{width:28%;height:16px;line-height:16px}
|
||||
#vmform .four label.cpu2{width:3%;height:16px;line-height:16px}
|
||||
#vmform .mac_generate{cursor:pointer;margin-left:-5px;color:#08C;font-size:1.3rem;transform:translate(0px, 2px)}
|
||||
#vmform .disk{display:none}
|
||||
#vmform .disk_preview{display:inline-block;color:#BBB;transform:translate(0px, 1px)}
|
||||
span#dropbox{border:1px solid <?=$border?>;background:<?=$bgcolor?>;padding:28px 12px;line-height:72px;margin-right:16px;}
|
||||
i.fa-plus-circle,i.fa-minus-circle{margin-left:8px}
|
||||
input[type=checkbox]{margin-left:0}
|
||||
</style>
|
||||
|
||||
<span class="status advancedview_panel" style="margin-top:<?=$top?>px"><input type="checkbox" class="advancedview"></span>
|
||||
<div class="domain">
|
||||
<form id="vmform" method="POST">
|
||||
<input type="hidden" name="domain[type]" value="kvm" />
|
||||
<input type="hidden" name="template[name]" value="<?=htmlspecialchars($strSelectedTemplate)?>" />
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>_(Icon)_:</td>
|
||||
<td>
|
||||
<input type="hidden" name="template[icon]" id="template_icon" value="<?=htmlspecialchars($arrLoad['icon'])?>" />
|
||||
<img id="template_img" src="<?=htmlspecialchars($strIconURL)?>" width="48" height="48" title="_(Change Icon)_..."/>
|
||||
<div id="template_img_chooser_outer">
|
||||
<div id="template_img_chooser">
|
||||
<?
|
||||
$arrImagePaths = [
|
||||
"$docroot/plugins/dynamix.vm.manager/templates/images/*.png" => '/plugins/dynamix.vm.manager/templates/images/',
|
||||
"$docroot/boot/config/plugins/dynamix.vm.manager/templates/images/*.png" => '/boot/config/plugins/dynamix.vm.manager/templates/images/'
|
||||
];
|
||||
foreach ($arrImagePaths as $strGlob => $strIconURLBase) {
|
||||
foreach (glob($strGlob) as $png_file) {
|
||||
echo '<div class="template_img_chooser_inner"><img src="'.$strIconURLBase.basename($png_file).'" basename="'.basename($png_file).'"><p>'.basename($png_file,'.png').'</p></div>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr style="line-height: 16px; vertical-align: middle;">
|
||||
<td>_(Autostart)_:</td>
|
||||
<td><div style="margin-left:-10px;padding-top:6px"><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="form_content"><?eval('?>'.parse_file("$docroot/plugins/dynamix.vm.manager/templates/{$arrLoad['form']}",false))?></div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="<?autov('/webGui/javascript/jquery.filedrop.js')?>"></script>
|
||||
<script src="<?autov('/webGui/javascript/jquery.filetree.js')?>" charset="utf-8"></script>
|
||||
<script src="<?autov('/webGui/javascript/jquery.switchbutton.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/javascript/dynamix.vm.manager.js')?>"></script>
|
||||
<script>
|
||||
function isVMAdvancedMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isVMXMLMode() {
|
||||
return ($.cookie('vmmanager_listview_mode') == 'xml');
|
||||
}
|
||||
|
||||
$(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: "_(XML View)_",
|
||||
off_label: "_(Form View)_",
|
||||
checked: isVMXMLMode()
|
||||
});
|
||||
$('.advancedview').change(function () {
|
||||
toggleRows('xmlview', $(this).is(':checked'), 'formview');
|
||||
$.cookie('vmmanager_listview_mode', $(this).is(':checked') ? 'xml' : 'form', { expires: 3650 });
|
||||
});
|
||||
|
||||
$('#template_img').click(function (){
|
||||
var p = $(this).position();
|
||||
p.left -= 4;
|
||||
p.top -= 4;
|
||||
$('#template_img_chooser_outer').css(p);
|
||||
$('#template_img_chooser_outer').slideDown();
|
||||
});
|
||||
$('#template_img_chooser').on('click', 'div', function (){
|
||||
$('#template_img').attr('src', $(this).find('img').attr('src'));
|
||||
$('#template_icon').val($(this).find('img').attr('basename'));
|
||||
$('#template_img_chooser_outer').slideUp();
|
||||
});
|
||||
$(document).keyup(function(e) {
|
||||
if (e.which == 27) $('#template_img_chooser_outer').slideUp();
|
||||
});
|
||||
|
||||
$("#vmform table[data-category]").each(function () {
|
||||
var category = $(this).data('category');
|
||||
|
||||
updatePrefixLabels(category);
|
||||
<?if ($arrLoad['state'] == 'shutoff'):?> bindSectionEvents(category); <?endif;?>
|
||||
});
|
||||
|
||||
$("#vmform input[data-pickroot]").fileTreeAttach();
|
||||
|
||||
var $el = $('#form_content');
|
||||
var $xmlview = $el.find('.xmlview');
|
||||
var $formview = $el.find('.formview');
|
||||
|
||||
if ($xmlview.length || $formview.length) {
|
||||
$('.advancedview_panel').fadeIn('fast');
|
||||
if (isVMXMLMode()) {
|
||||
$('.formview').hide();
|
||||
$('.xmlview').filter(function() {
|
||||
return (($(this).prop('style').display + '') === '');
|
||||
}).show();
|
||||
} else {
|
||||
$('.xmlview').hide();
|
||||
$('.formview').filter(function() {
|
||||
return (($(this).prop('style').display + '') === '');
|
||||
}).show();
|
||||
}
|
||||
} else {
|
||||
$('.advancedview_panel').fadeOut('fast');
|
||||
}
|
||||
|
||||
$("#vmform #btnCancel").click(function (){
|
||||
done();
|
||||
});
|
||||
|
||||
$('#form_content').fadeIn('fast');
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,229 @@
|
||||
function clearHistory(){
|
||||
window.history.pushState('VMs', 'Title', '/VMs');
|
||||
}
|
||||
|
||||
function ordinal_suffix_of(i) {
|
||||
var j = i % 10,
|
||||
k = i % 100;
|
||||
if (j == 1 && k != 11) {
|
||||
return i + "st";
|
||||
}
|
||||
if (j == 2 && k != 12) {
|
||||
return i + "nd";
|
||||
}
|
||||
if (j == 3 && k != 13) {
|
||||
return i + "rd";
|
||||
}
|
||||
return i + "th";
|
||||
}
|
||||
|
||||
function slideUpRows($tr, onComplete) {
|
||||
$tr.not('tr,table').finish().fadeOut('fast');
|
||||
|
||||
$tr.filter('tr').find('td').finish().each(function(){
|
||||
$(this)
|
||||
.data("paddingstate", $(this).css(["paddingTop", "paddingBottom"]))
|
||||
.animate({ paddingTop: 0, paddingBottom: 0 }, { duration: 'fast' })
|
||||
.wrapInner('<div />')
|
||||
.children()
|
||||
.slideUp("fast", function() {
|
||||
$(this).contents().unwrap();
|
||||
$tr.filter('tr').hide();
|
||||
if ($.isFunction(onComplete)) {
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$tr.filter('table').finish().each(function(){
|
||||
$(this)
|
||||
.wrap('<div style="overflow: hidden"></div>')
|
||||
.parent()
|
||||
.slideUp("fast", function() {
|
||||
$(this).contents().unwrap();
|
||||
$tr.filter('table').hide();
|
||||
if ($.isFunction(onComplete)) {
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return $tr;
|
||||
}
|
||||
|
||||
function slideDownRows($tr, onComplete) {
|
||||
$tr.filter(':hidden').not('tr,table').finish().fadeIn('fast');
|
||||
|
||||
$tr.filter('tr:hidden').find('td').finish().each(function(){
|
||||
$(this)
|
||||
.wrapInner('<div style="display: none"></div>')
|
||||
.animate($(this).data("paddingstate"), { duration: 'fast', start: function() { $tr.filter('tr:hidden').show(); } })
|
||||
.children()
|
||||
.slideDown("fast", function() {
|
||||
$(this).contents().unwrap();
|
||||
if ($.isFunction(onComplete)) {
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$tr.filter('table:hidden').finish().each(function(){
|
||||
$(this)
|
||||
.wrap('<div style="display: none; overflow: hidden"></div>')
|
||||
.show()
|
||||
.parent()
|
||||
.slideDown("fast", function() {
|
||||
$(this).contents().unwrap();
|
||||
if ($.isFunction(onComplete)) {
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return $tr;
|
||||
}
|
||||
|
||||
function toggleRows(what, val, what2, onComplete) {
|
||||
if (val == 1) {
|
||||
slideDownRows($('.'+what), onComplete);
|
||||
if (arguments.length > 2) {
|
||||
slideUpRows($('.'+what2), onComplete);
|
||||
}
|
||||
} else {
|
||||
slideUpRows($('.'+what), onComplete);
|
||||
if (arguments.length > 2) {
|
||||
slideDownRows($('.'+what2), onComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updatePrefixLabels(category) {
|
||||
$("#vmform table:data(category)").filter(function() {
|
||||
return $(this).data('category') == category;
|
||||
}).each(function (index) {
|
||||
var oldprefix = $(this).data('prefix');
|
||||
var newprefix = oldprefix;
|
||||
|
||||
if (index > 0) {
|
||||
newprefix = ordinal_suffix_of(index+1);
|
||||
}
|
||||
|
||||
$(this)
|
||||
.data('prefix', newprefix)
|
||||
.find('tr').each(function() {
|
||||
var $td = $(this).children('td').first();
|
||||
|
||||
var old = $td.text();
|
||||
if (oldprefix && old.indexOf(oldprefix) === 0) {
|
||||
old = old.replace(oldprefix + ' ', '');
|
||||
}
|
||||
|
||||
$td.text(newprefix + ' ' + old);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindSectionEvents(category) {
|
||||
var $Filtered = $("#vmform table:data(category)").filter(function(index) {
|
||||
return $(this).data('category') == category;
|
||||
});
|
||||
|
||||
var count = $Filtered.length;
|
||||
|
||||
$Filtered.each(function(index) {
|
||||
var $table = $(this);
|
||||
var config = $(this).data();
|
||||
var boolAdd = false;
|
||||
var boolDelete = false;
|
||||
|
||||
if (!config.hasOwnProperty('multiple')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean old sections
|
||||
var $first_td = $(this).find('td').first();
|
||||
|
||||
// delete section
|
||||
if (!config.hasOwnProperty('minimum') || config.minimum === undefined || parseInt(config.minimum) < (index+1)) {
|
||||
if ($first_td.children('.sectionbutton.remove').length === 0) {
|
||||
var $el_remove = $('<div class="sectionbutton remove" title="Remove ' + config.prefix + ' ' + category.replace('_', ' ') + '"><i class="fa fa-minus-circle red"></i></div>').one('click', clickRemoveSection);
|
||||
$first_td.append($el_remove);
|
||||
}
|
||||
boolDelete = true;
|
||||
} else {
|
||||
$first_td.children('.sectionbutton.remove').fadeOut('fast', function() { $(this).remove(); });
|
||||
}
|
||||
|
||||
// add section (can only add from the last section)
|
||||
if ((index+1) == count) {
|
||||
if (!config.hasOwnProperty('maximum') || config.maximum === undefined || parseInt(config.maximum) > (index+1)) {
|
||||
if ($first_td.children('.sectionbutton.add').length === 0) {
|
||||
var $el_add = $('<div class="sectionbutton add" title="Add another ' + category.replace('_', ' ') + '"><i class="fa fa-plus-circle green"></i></div>').one('click', clickAddSection);
|
||||
$first_td.append($el_add);
|
||||
}
|
||||
boolAdd = true;
|
||||
} else {
|
||||
$first_td.children('.sectionbutton.add').fadeOut('fast', function() { $(this).remove(); });
|
||||
}
|
||||
}
|
||||
|
||||
if (boolDelete || boolAdd) {
|
||||
$table.addClass("multiple");
|
||||
if ($first_td.children('.sectiontab').length === 0) {
|
||||
var $el_tab = $('<div class="sectiontab"></div>');
|
||||
$first_td.append($el_tab);
|
||||
}
|
||||
} else {
|
||||
$first_td.children('.sectionbutton, .sectiontab').fadeOut('fast', function() {
|
||||
$(this).remove();
|
||||
$table.removeClass("multiple");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clickAddSection() {
|
||||
var $table = $(this).closest('table');
|
||||
$(this).remove();
|
||||
var newindex = new Date().getTime();
|
||||
var config = $table.data();
|
||||
|
||||
var $template = $($('<div/>').loadTemplate($("#tmpl" + config.category)).html().replace(/{{INDEX}}/g, newindex));
|
||||
|
||||
$template
|
||||
.data({
|
||||
multiple: true,
|
||||
category: config.category,
|
||||
index: newindex,
|
||||
minimum: config.minimum,
|
||||
maximum: config.maximum
|
||||
})
|
||||
.find('tr').hide()
|
||||
.find("input[data-pickroot]").fileTreeAttach();
|
||||
|
||||
$table.after($template);
|
||||
|
||||
updatePrefixLabels(config.category);
|
||||
bindSectionEvents(config.category);
|
||||
|
||||
$el_showable = $template.find('tr').not("." + (isVMAdvancedMode() ? 'basic' : 'advanced'));
|
||||
|
||||
slideDownRows($el_showable);
|
||||
|
||||
$table.parent().trigger('spawn_section', [ $template, $template.data() ]);
|
||||
}
|
||||
|
||||
function clickRemoveSection() {
|
||||
var $table = $(this).closest('table');
|
||||
var $parent = $table.parent();
|
||||
var tabledata = $table.data();
|
||||
|
||||
slideUpRows($table, function() {
|
||||
$table.detach();
|
||||
$parent.trigger('destroy_section', [ $table, tabledata ]);
|
||||
$table.remove();
|
||||
|
||||
updatePrefixLabels(tabledata.category);
|
||||
bindSectionEvents(tabledata.category);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
function displayconsole(url) {
|
||||
window.open(url, '_blank', 'scrollbars=yes,resizable=yes');
|
||||
}
|
||||
|
||||
function downloadFile(source) {
|
||||
var a = document.createElement('a');
|
||||
a.setAttribute('href',source);
|
||||
a.setAttribute('download',source.split('/').pop());
|
||||
a.style.display = 'none';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
function ajaxVMDispatch(params, spin){
|
||||
if (spin) $('#vm-'+params['uuid']).parent().find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", params, function(data) {
|
||||
if (data.error) {
|
||||
swal({
|
||||
title:_("Execution error"), html:true,
|
||||
text:data.error, type:"error",
|
||||
confirmButtonText:_('Ok')
|
||||
},function(){
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
});
|
||||
} else {
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
function ajaxVMDispatchconsole(params, spin){
|
||||
if (spin) $('#vm-'+params['uuid']).parent().find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", params, function(data) {
|
||||
if (data.error) {
|
||||
swal({
|
||||
title:_("Execution error"), html:true,
|
||||
text:data.error, type:"error",
|
||||
confirmButtonText:_('Ok')
|
||||
},function(){
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
});
|
||||
} else {
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
setTimeout('displayconsole("'+data.vmrcurl+'")',500) ;
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
function ajaxVMDispatchconsoleRV(params, spin){
|
||||
if (spin) $('#vm-'+params['uuid']).parent().find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", params, function(data) {
|
||||
if (data.error) {
|
||||
swal({
|
||||
title:_("Execution error"), html:true,
|
||||
text:data.error, type:"error",
|
||||
confirmButtonText:_('Ok')
|
||||
},function(){
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
});
|
||||
} else {
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
downloadFile(data.vvfile) ;
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, console="web"){
|
||||
var opts = [];
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
if (vmrcurl !== "" && state == "running") {
|
||||
if (console == "web" || console == "both") {
|
||||
var vmrctext=_("VM Console") + "(" + vmrcprotocol + ")" ;
|
||||
opts.push({text:vmrctext, icon:"fa-desktop", action:function(e) {
|
||||
e.preventDefault();
|
||||
window.open(vmrcurl, '_blank', 'scrollbars=yes,resizable=yes');
|
||||
}});
|
||||
}
|
||||
if (console == "remote" || console == "both") {
|
||||
opts.push({text:_("VM remote-viewer")+ "(" + vmrcprotocol + ")" , icon:"fa-desktop", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatchconsoleRV({action:"domain-consoleRV", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
|
||||
}});
|
||||
}
|
||||
|
||||
opts.push({divider:true});
|
||||
}
|
||||
context.settings({right:false,above:false});
|
||||
if (state == "running") {
|
||||
opts.push({text:_("Stop"), icon:"fa-stop", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-stop", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
opts.push({text:_("Pause"), icon:"fa-pause", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-pause", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
opts.push({text:_("Restart"), icon:"fa-refresh", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-restart", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
opts.push({text:_("Hibernate"), icon:"fa-bed", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-pmsuspend", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
opts.push({text:_("Force Stop"), icon:"fa-bomb", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch( {action:"domain-destroy", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
} else if (state == "pmsuspended") {
|
||||
opts.push({text:_("Resume"), icon:"fa-play", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-pmwakeup", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
opts.push({text:_("Force Stop"), icon:"fa-bomb", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-destroy", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
} else if (state == "paused" || state == "unknown") {
|
||||
opts.push({text:_("Resume"), icon:"fa-play", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-resume", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
opts.push({text:_("Force Stop"), icon:"fa-bomb", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-destroy", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
} else {
|
||||
opts.push({text:_("Start"), icon:"fa-play", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatch({action:"domain-start", uuid:uuid}, "loadlist");
|
||||
}});
|
||||
if (vmrcprotocol == "VNC" || vmrcprotocol == "SPICE") {
|
||||
if (console == "web" || console == "both") {
|
||||
opts.push({text:_("Start with console")+ "(" + vmrcprotocol + ")" , icon:"fa-play", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatchconsole({action:"domain-start-console", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
|
||||
}});}
|
||||
if (console == "remote" || console == "both") {
|
||||
opts.push({text:_("Start with remote-viewer")+ "(" + vmrcprotocol + ")" , icon:"fa-play", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatchconsoleRV({action:"domain-start-consoleRV", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
|
||||
}});
|
||||
}
|
||||
}}
|
||||
opts.push({divider:true});
|
||||
if (log !== "") {
|
||||
opts.push({text:_("Logs"), icon:"fa-navicon", action:function(e){e.preventDefault(); openTerminal('log',name,log);}});
|
||||
}
|
||||
opts.push({text:_("Edit"), icon:"fa-pencil", href:path+'/UpdateVM?uuid='+uuid});
|
||||
if (state == "shutoff") {
|
||||
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,
|
||||
confirmButtonText:_('Proceed'),
|
||||
cancelButtonText:_('Cancel')
|
||||
},function(){
|
||||
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
ajaxVMDispatch({action:"domain-undefine",uuid:uuid}, "loadlist");
|
||||
});
|
||||
}});
|
||||
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,
|
||||
confirmButtonText:_('Proceed'),
|
||||
cancelButtonText:_('Cancel')
|
||||
},function(){
|
||||
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
ajaxVMDispatch({action:"domain-delete",uuid:uuid}, "loadlist");
|
||||
});
|
||||
}});
|
||||
}
|
||||
}
|
||||
context.attach('#vm-'+uuid, opts);
|
||||
}
|
||||
function startAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,vm; vm=kvm[i]; i++) if (vm.state!='running') $('#vm-'+vm.id).parent().find('i').removeClass('fa-square').addClass('fa-refresh fa-spin');
|
||||
$.post('/plugins/dynamix.vm.manager/include/VMManager.php',{action:'start'}, function(){loadlist();});
|
||||
}
|
||||
function stopAll() {
|
||||
$('input[type=button]').prop('disabled',true);
|
||||
for (var i=0,vm; vm=kvm[i]; i++) if (vm.state=='running') $('#vm-'+vm.id).parent().find('i').removeClass('fa-play').addClass('fa-refresh fa-spin');
|
||||
$.post('/plugins/dynamix.vm.manager/include/VMManager.php',{action:'stop'}, function(){loadlist();});
|
||||
}
|
||||
function vncOpen() {
|
||||
$.post('/plugins/dynamix.vm.manager/include/vnc.php',{cmd:'open',root:'<?=$docroot?>',file:'<?=$docroot?>/plugins/dynamix.vm.manager/vncconnect.vnc'},function(data) {
|
||||
window.location.href = data;
|
||||
});
|
||||
}
|
||||
function toggle_id(itemID){
|
||||
var cookie = $.cookie('vmshow')||'';
|
||||
if ((document.getElementById(itemID).style.display == 'none')) {
|
||||
slideDownRows($('#'+itemID));
|
||||
if (cookie.indexOf(itemID)<0) $.cookie('vmshow',cookie+itemID+',');
|
||||
} else {
|
||||
slideUpRows($('#'+itemID));
|
||||
if (cookie.indexOf(itemID)>=0) $.cookie('vmshow',cookie.replace(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 addVM() {
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
location = path+"/VMTemplates";
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2019 The noVNC Authors
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
// NB: this should *not* be included as a module until we have
|
||||
// native support in the browsers, so that our error handler
|
||||
// can catch script-loading errors.
|
||||
|
||||
// No ES6 can be used in this file since it's used for the translation
|
||||
/* eslint-disable prefer-arrow-callback */
|
||||
|
||||
(function _scope() {
|
||||
"use strict";
|
||||
|
||||
// Fallback for all uncought errors
|
||||
function handleError(event, err) {
|
||||
try {
|
||||
const msg = document.getElementById('noVNC_fallback_errormsg');
|
||||
|
||||
// Only show the initial error
|
||||
if (msg.hasChildNodes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let div = document.createElement("div");
|
||||
div.classList.add('noVNC_message');
|
||||
div.appendChild(document.createTextNode(event.message));
|
||||
msg.appendChild(div);
|
||||
|
||||
if (event.filename) {
|
||||
div = document.createElement("div");
|
||||
div.className = 'noVNC_location';
|
||||
let text = event.filename;
|
||||
if (event.lineno !== undefined) {
|
||||
text += ":" + event.lineno;
|
||||
if (event.colno !== undefined) {
|
||||
text += ":" + event.colno;
|
||||
}
|
||||
}
|
||||
div.appendChild(document.createTextNode(text));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
if (err && err.stack) {
|
||||
div = document.createElement("div");
|
||||
div.className = 'noVNC_stack';
|
||||
div.appendChild(document.createTextNode(err.stack));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
document.getElementById('noVNC_fallback_error')
|
||||
.classList.add("noVNC_open");
|
||||
} catch (exc) {
|
||||
document.write("noVNC encountered an error.");
|
||||
}
|
||||
// Don't return true since this would prevent the error
|
||||
// from being printed to the browser console.
|
||||
return false;
|
||||
}
|
||||
window.addEventListener('error', function onerror(evt) { handleError(evt, evt.error); });
|
||||
window.addEventListener('unhandledrejection', function onreject(evt) { handleError(evt.reason, evt.reason); });
|
||||
})();
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="alt.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="18.205425"
|
||||
inkscape:cy="17.531398"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<g
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text5290">
|
||||
<path
|
||||
d="m 9.9560547,1042.3329 -2.9394531,0 -0.4638672,1.3281 -1.8896485,0 2.7001953,-7.29 2.241211,0 2.7001958,7.29 -1.889649,0 -0.4589843,-1.3281 z m -2.4707031,-1.3526 1.9970703,0 -0.9960938,-2.9003 -1.0009765,2.9003 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5340" />
|
||||
<path
|
||||
d="m 13.188477,1036.0634 1.748046,0 0,7.5976 -1.748046,0 0,-7.5976 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5342" />
|
||||
<path
|
||||
d="m 18.535156,1036.6395 0,1.5528 1.801758,0 0,1.25 -1.801758,0 0,2.3193 q 0,0.3809 0.151367,0.5176 0.151368,0.1318 0.600586,0.1318 l 0.898438,0 0,1.25 -1.499024,0 q -1.035156,0 -1.469726,-0.4297 -0.429688,-0.4345 -0.429688,-1.4697 l 0,-2.3193 -0.86914,0 0,-1.25 0.86914,0 0,-1.5528 1.748047,0 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5344" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="clipboard.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="15.366606"
|
||||
inkscape:cy="16.42981"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<path
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 9,6 6,6 C 5.4459889,6 5,6.4459889 5,7 l 0,13 c 0,0.554011 0.4459889,1 1,1 l 13,0 c 0.554011,0 1,-0.445989 1,-1 L 20,7 C 20,6.4459889 19.554011,6 19,6 l -3,0"
|
||||
transform="translate(0,1027.3622)"
|
||||
id="rect6083"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cssssssssc" />
|
||||
<rect
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect6085"
|
||||
width="7"
|
||||
height="4"
|
||||
x="9"
|
||||
y="1031.3622"
|
||||
ry="1.00002" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
|
||||
d="m 8.5071212,1038.8622 7.9999998,0"
|
||||
id="path6087"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
|
||||
d="m 8.5071212,1041.8622 3.9999998,0"
|
||||
id="path6089"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196081"
|
||||
d="m 8.5071212,1044.8622 5.9999998,0"
|
||||
id="path6091"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="connect.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="37.14834"
|
||||
inkscape:cy="1.9525926"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<g
|
||||
id="g5103"
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,-729.15757,315.8823)">
|
||||
<path
|
||||
sodipodi:nodetypes="cssssc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="rect5096"
|
||||
d="m 11,1040.3622 -5,0 c -1.108,0 -2,-0.892 -2,-2 l 0,-4 c 0,-1.108 0.892,-2 2,-2 l 5,0"
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<path
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 14,1032.3622 5,0 c 1.108,0 2,0.892 2,2 l 0,4 c 0,1.108 -0.892,2 -2,2 l -5,0"
|
||||
id="path5099"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cssssc" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5101"
|
||||
d="m 9,1036.3622 7,0"
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ctrl.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="18.205425"
|
||||
inkscape:cy="17.531398"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<g
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text5290">
|
||||
<path
|
||||
d="m 9.1210938,1043.1898 q -0.5175782,0.2686 -1.0791016,0.4053 -0.5615235,0.1367 -1.171875,0.1367 -1.8212891,0 -2.8857422,-1.0156 -1.0644531,-1.0205 -1.0644531,-2.7637 0,-1.748 1.0644531,-2.7637 1.0644531,-1.0205 2.8857422,-1.0205 0.6103515,0 1.171875,0.1368 0.5615234,0.1367 1.0791016,0.4052 l 0,1.5088 q -0.522461,-0.3564 -1.0302735,-0.5224 -0.5078125,-0.1661 -1.0693359,-0.1661 -1.0058594,0 -1.5820313,0.6446 -0.5761719,0.6445 -0.5761719,1.7773 0,1.1279 0.5761719,1.7725 0.5761719,0.6445 1.5820313,0.6445 0.5615234,0 1.0693359,-0.166 0.5078125,-0.166 1.0302735,-0.5225 l 0,1.5088 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5370" />
|
||||
<path
|
||||
d="m 12.514648,1036.5687 0,1.5528 1.801758,0 0,1.25 -1.801758,0 0,2.3193 q 0,0.3809 0.151368,0.5176 0.151367,0.1318 0.600586,0.1318 l 0.898437,0 0,1.25 -1.499023,0 q -1.035157,0 -1.469727,-0.4297 -0.429687,-0.4345 -0.429687,-1.4697 l 0,-2.3193 -0.8691411,0 0,-1.25 0.8691411,0 0,-1.5528 1.748046,0 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5372" />
|
||||
<path
|
||||
d="m 19.453125,1039.6107 q -0.229492,-0.1074 -0.458984,-0.1562 -0.22461,-0.054 -0.454102,-0.054 -0.673828,0 -1.040039,0.4345 -0.361328,0.4297 -0.361328,1.2354 l 0,2.5195 -1.748047,0 0,-5.4687 1.748047,0 0,0.8984 q 0.336914,-0.5371 0.771484,-0.7813 0.439453,-0.249 1.049805,-0.249 0.08789,0 0.19043,0.01 0.102539,0 0.297851,0.029 l 0.0049,1.582 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5374" />
|
||||
<path
|
||||
d="m 20.332031,1035.9926 1.748047,0 0,7.5976 -1.748047,0 0,-7.5976 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5376" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="ctrlaltdel.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="11.135667"
|
||||
inkscape:cy="16.407428"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect5253"
|
||||
width="5"
|
||||
height="5.0000172"
|
||||
x="16"
|
||||
y="1031.3622"
|
||||
ry="1.0000174" />
|
||||
<rect
|
||||
y="1043.3622"
|
||||
x="4"
|
||||
height="5.0000172"
|
||||
width="5"
|
||||
id="rect5255"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
ry="1.0000174" />
|
||||
<rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect5257"
|
||||
width="5"
|
||||
height="5.0000172"
|
||||
x="13"
|
||||
y="1043.3622"
|
||||
ry="1.0000174" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="disconnect.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="25.05707"
|
||||
inkscape:cy="11.594858"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<g
|
||||
id="g5171"
|
||||
transform="translate(-24.062499,-6.15775e-4)">
|
||||
<path
|
||||
id="path5110"
|
||||
transform="translate(0,1027.3622)"
|
||||
d="m 39.744141,3.4960938 c -0.769923,0 -1.539607,0.2915468 -2.121094,0.8730468 l -2.566406,2.5664063 1.414062,1.4140625 2.566406,-2.5664063 c 0.403974,-0.404 1.010089,-0.404 1.414063,0 l 2.828125,2.828125 c 0.40398,0.4039 0.403907,1.0101621 0,1.4140629 l -2.566406,2.566406 1.414062,1.414062 2.566406,-2.566406 c 1.163041,-1.1629 1.162968,-3.0791874 0,-4.2421874 L 41.865234,4.3691406 C 41.283747,3.7876406 40.514063,3.4960937 39.744141,3.4960938 Z M 39.017578,9.015625 a 1.0001,1.0001 0 0 0 -0.6875,0.3027344 l -0.445312,0.4453125 1.414062,1.4140621 0.445313,-0.445312 A 1.0001,1.0001 0 0 0 39.017578,9.015625 Z m -6.363281,0.7070312 a 1.0001,1.0001 0 0 0 -0.6875,0.3027348 L 28.431641,13.5625 c -1.163042,1.163 -1.16297,3.079187 0,4.242188 l 2.828125,2.828124 c 1.162974,1.163101 3.079213,1.163101 4.242187,0 l 3.535156,-3.535156 a 1.0001,1.0001 0 1 0 -1.414062,-1.414062 l -3.535156,3.535156 c -0.403974,0.404 -1.010089,0.404 -1.414063,0 l -2.828125,-2.828125 c -0.403981,-0.404 -0.403908,-1.010162 0,-1.414063 l 3.535156,-3.537109 A 1.0001,1.0001 0 0 0 32.654297,9.7226562 Z m 3.109375,2.1621098 -2.382813,2.384765 a 1.0001,1.0001 0 1 0 1.414063,1.414063 l 2.382812,-2.384766 -1.414062,-1.414062 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,0,0)"
|
||||
y="752.29541"
|
||||
x="-712.31262"
|
||||
height="18.000017"
|
||||
width="3"
|
||||
id="rect5116"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="drag.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="9.8789407"
|
||||
inkscape:cy="9.5008608"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 7.039733,1049.3037 c -0.4309106,-0.1233 -0.7932634,-0.4631 -0.9705434,-0.9103 -0.04922,-0.1241 -0.057118,-0.2988 -0.071321,-1.5771 l -0.015972,-1.4375 -0.328125,-0.082 c -0.7668138,-0.1927 -1.1897046,-0.4275 -1.7031253,-0.9457 -0.4586773,-0.4629 -0.6804297,-0.8433 -0.867034,-1.4875 -0.067215,-0.232 -0.068001,-0.2642 -0.078682,-3.2188 -0.012078,-3.341 -0.020337,-3.2012 0.2099452,-3.5555 0.2246623,-0.3458 0.5798271,-0.5892 0.9667343,-0.6626 0.092506,-0.017 0.531898,-0.032 0.9764271,-0.032 l 0.8082347,0 1.157e-4,1.336 c 1.125e-4,1.2779 0.00281,1.3403 0.062214,1.4378 0.091785,0.1505 0.2357707,0.226 0.4314082,0.2261 0.285389,2e-4 0.454884,-0.1352 0.5058962,-0.4042 0.019355,-0.102 0.031616,-0.982 0.031616,-2.269 0,-1.9756 0.00357,-2.1138 0.059205,-2.2926 0.1645475,-0.5287 0.6307616,-0.9246 1.19078,-1.0113 0.8000572,-0.1238 1.5711277,0.4446 1.6860387,1.2429 0.01732,0.1203 0.03177,0.8248 0.03211,1.5657 6.19e-4,1.3449 7.22e-4,1.347 0.07093,1.4499 0.108355,0.1587 0.255268,0.2248 0.46917,0.2108 0.204069,-0.013 0.316116,-0.08 0.413642,-0.2453 0.06028,-0.1024 0.06307,-0.1778 0.07862,-2.1218 0.01462,-1.8283 0.02124,-2.0285 0.07121,-2.1549 0.260673,-0.659 0.934894,-1.0527 1.621129,-0.9465 0.640523,0.099 1.152269,0.6104 1.243187,1.2421 0.01827,0.1269 0.03175,0.9943 0.03211,2.0657 l 6.19e-4,1.8469 0.07031,0.103 c 0.108355,0.1587 0.255267,0.2248 0.46917,0.2108 0.204069,-0.013 0.316115,-0.08 0.413642,-0.2453 0.05951,-0.1011 0.06329,-0.1786 0.07907,-1.6218 0.01469,-1.3438 0.02277,-1.5314 0.07121,-1.6549 0.257975,-0.6576 0.934425,-1.0527 1.620676,-0.9465 0.640522,0.099 1.152269,0.6104 1.243186,1.2421 0.0186,0.1292 0.03179,1.0759 0.03222,2.3125 7.15e-4,2.0335 0.0025,2.0966 0.06283,2.1956 0.09178,0.1505 0.235771,0.226 0.431409,0.2261 0.285388,2e-4 0.454884,-0.1352 0.505897,-0.4042 0.01874,-0.099 0.03161,-0.8192 0.03161,-1.769 0,-1.4848 0.0043,-1.6163 0.0592,-1.7926 0.164548,-0.5287 0.630762,-0.9246 1.19078,-1.0113 0.800057,-0.1238 1.571128,0.4446 1.686039,1.2429 0.04318,0.2999 0.04372,9.1764 5.78e-4,9.4531 -0.04431,0.2841 -0.217814,0.6241 -0.420069,0.8232 -0.320102,0.315 -0.63307,0.4268 -1.194973,0.4268 l -0.35281,0 -2.51e-4,1.2734 c -1.25e-4,0.7046 -0.01439,1.3642 -0.03191,1.4766 -0.06665,0.4274 -0.372966,0.8704 -0.740031,1.0702 -0.349999,0.1905 0.01748,0.18 -6.242199,0.1776 -5.3622439,0 -5.7320152,-0.01 -5.9121592,-0.057 l 1.4e-5,0 z"
|
||||
id="path4379"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="error.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="14.00357"
|
||||
inkscape:cy="12.443398"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<path
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 7 3 C 4.7839905 3 3 4.7839905 3 7 L 3 18 C 3 20.21601 4.7839905 22 7 22 L 18 22 C 20.21601 22 22 20.21601 22 18 L 22 7 C 22 4.7839905 20.21601 3 18 3 L 7 3 z M 7.6992188 6 A 1.6916875 1.6924297 0 0 1 8.9121094 6.5117188 L 12.5 10.101562 L 16.087891 6.5117188 A 1.6916875 1.6924297 0 0 1 17.251953 6 A 1.6916875 1.6924297 0 0 1 18.480469 8.90625 L 14.892578 12.496094 L 18.480469 16.085938 A 1.6916875 1.6924297 0 1 1 16.087891 18.478516 L 12.5 14.888672 L 8.9121094 18.478516 A 1.6916875 1.6924297 0 1 1 6.5214844 16.085938 L 10.109375 12.496094 L 6.5214844 8.90625 A 1.6916875 1.6924297 0 0 1 7.6992188 6 z "
|
||||
transform="translate(0,1027.3622)"
|
||||
id="rect4135" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="esc.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="18.205425"
|
||||
inkscape:cy="17.531398"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="text5290"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="true"
|
||||
inkscape:snap-global="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<g
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:48px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="text5290">
|
||||
<path
|
||||
d="m 3.9331055,1036.1464 5.0732422,0 0,1.4209 -3.1933594,0 0,1.3574 3.0029297,0 0,1.4209 -3.0029297,0 0,1.6699 3.3007812,0 0,1.4209 -5.180664,0 0,-7.29 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5314" />
|
||||
<path
|
||||
d="m 14.963379,1038.1385 0,1.3282 q -0.561524,-0.2344 -1.083984,-0.3516 -0.522461,-0.1172 -0.986329,-0.1172 -0.498046,0 -0.742187,0.127 -0.239258,0.122 -0.239258,0.3808 0,0.21 0.180664,0.3223 0.185547,0.1123 0.65918,0.166 l 0.307617,0.044 q 1.342773,0.1709 1.806641,0.5615 0.463867,0.3906 0.463867,1.2256 0,0.874 -0.644531,1.3134 -0.644532,0.4395 -1.923829,0.4395 -0.541992,0 -1.123046,-0.088 -0.576172,-0.083 -1.186524,-0.2539 l 0,-1.3281 q 0.522461,0.2539 1.069336,0.3808 0.551758,0.127 1.118164,0.127 0.512695,0 0.771485,-0.1416 0.258789,-0.1416 0.258789,-0.4199 0,-0.2344 -0.180664,-0.3467 -0.175782,-0.1172 -0.708008,-0.1807 l -0.307617,-0.039 q -1.166993,-0.1465 -1.635743,-0.542 -0.46875,-0.3955 -0.46875,-1.2012 0,-0.8691 0.595703,-1.2891 0.595704,-0.4199 1.826172,-0.4199 0.483399,0 1.015625,0.073 0.532227,0.073 1.157227,0.2294 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5316" />
|
||||
<path
|
||||
d="m 21.066895,1038.1385 0,1.4258 q -0.356446,-0.2441 -0.717774,-0.3613 -0.356445,-0.1172 -0.742187,-0.1172 -0.732422,0 -1.142579,0.4297 -0.405273,0.4248 -0.405273,1.1914 0,0.7666 0.405273,1.1963 0.410157,0.4248 1.142579,0.4248 0.410156,0 0.776367,-0.1221 0.371094,-0.122 0.683594,-0.3613 l 0,1.4307 q -0.410157,0.1513 -0.834961,0.2246 -0.419922,0.078 -0.844727,0.078 -1.479492,0 -2.314453,-0.7568 -0.834961,-0.7618 -0.834961,-2.1143 0,-1.3525 0.834961,-2.1094 0.834961,-0.7617 2.314453,-0.7617 0.429688,0 0.844727,0.078 0.419921,0.073 0.834961,0.2246 z"
|
||||
style="font-size:10px;fill:#ffffff;fill-opacity:1"
|
||||
id="path5318" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="9"
|
||||
height="10"
|
||||
viewBox="0 0 9 10"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="expander.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="9.8737281"
|
||||
inkscape:cy="6.4583132"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:snap-object-midpoints="false"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1042.3622)">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 2.0800781,1042.3633 A 2.0002,2.0002 0 0 0 0,1044.3613 l 0,6 a 2.0002,2.0002 0 0 0 3.0292969,1.7168 l 5,-3 a 2.0002,2.0002 0 0 0 0,-3.4316 l -5,-3 a 2.0002,2.0002 0 0 0 -0.9492188,-0.2832 z"
|
||||
id="path4138"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="25"
|
||||
height="25"
|
||||
viewBox="0 0 25 25"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="fullscreen.svg"
|
||||
inkscape:export-filename="/home/ossman/devel/noVNC/images/drag.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#959595"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="16.400723"
|
||||
inkscape:cy="15.083758"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
showguides="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1136"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-nodes="false">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1027.3622)">
|
||||
<rect
|
||||
style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect5006"
|
||||
width="17"
|
||||
height="17.000017"
|
||||
x="4"
|
||||
y="1031.3622"
|
||||
ry="3.0000174" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="m 7.5,1044.8622 4,0 -1.5,-1.5 1.5,-1.5 -1,-1 -1.5,1.5 -1.5,-1.5 0,4 z"
|
||||
id="path5017"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5025"
|
||||
d="m 17.5,1034.8622 -4,0 1.5,1.5 -1.5,1.5 1,1 1.5,-1.5 1.5,1.5 0,-4 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |