diff --git a/plugins/dynamix.plugin.manager/Plugins.page b/plugins/dynamix.plugin.manager/Plugins.page index 3f0717a0f..7e7c2f4c0 100644 --- a/plugins/dynamix.plugin.manager/Plugins.page +++ b/plugins/dynamix.plugin.manager/Plugins.page @@ -36,7 +36,7 @@ const args = {}; function openInstall(cmd,title,plg) { if (cmd == null) { - openPlugin(args.cmd,args.title,args.plg); + openPlugin(args.cmd,args.title,args.plg,null,1); return; } args.cmd = cmd; @@ -69,14 +69,22 @@ function multiRemove() { if ($('input.remove:checked').length > 1) $('#removeall').show(); else $('#removeall').hide(); } function updateList() { - let list = []; - $('input.update').each(function(){list.push($(this).attr('data'));}); - openPlugin("multiplugin update "+list.join('*'),"_(Update All Plugins)_",":return"); + 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() { - let list = []; - $('input.remove:checked').each(function(){list.push($(this).attr('data'));}); - openPlugin("multiplugin remove "+list.join('*'),"_(Remove Selected Plugins)_","","refresh"); + 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'); @@ -85,6 +93,8 @@ function updateInfo(data) { 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(); } } } @@ -123,10 +133,10 @@ function loadlist(id,check) { } } else { updateInfo(data[0]); + if (data[1] > 1) $('#updateall').show(); else $('#updateall').hide(); } $('#plugin_table').trigger('update'); $('#checkall').find('input').prop('disabled',false); - if (data[1] > 1) $('#updateall').show(); else $('#updateall').hide(); }); } $(function() { diff --git a/plugins/dynamix.plugin.manager/include/PluginHelpers.php b/plugins/dynamix.plugin.manager/include/PluginHelpers.php index 6c95d4b5d..be936e54e 100644 --- a/plugins/dynamix.plugin.manager/include/PluginHelpers.php +++ b/plugins/dynamix.plugin.manager/include/PluginHelpers.php @@ -41,7 +41,11 @@ function make_link($method, $arg, $extra='') { } else { $cmd = "plugin $method $arg".($extra?" $extra":""); } - return "$check"; + if (is_file("/tmp/plugins/pluginPending/$arg") && !$check) { + return " "._('pending').""; + } else { + return "$check"; + } } // trying our best to find an icon diff --git a/plugins/dynamix.plugin.manager/include/ShowPlugins.php b/plugins/dynamix.plugin.manager/include/ShowPlugins.php index 82f0b5a6a..9e4b941cf 100644 --- a/plugins/dynamix.plugin.manager/include/ShowPlugins.php +++ b/plugins/dynamix.plugin.manager/include/ShowPlugins.php @@ -39,6 +39,12 @@ if ($cmd=='alert') { die(); } +if ($cmd=='pending') { + // prepare pending status for multi operations + foreach (explode('*',$_GET['plugin']) as $plugin) file_put_contents("/tmp/plugins/pluginPending/$plugin",'multi'); + die(); +} + if ($audit) { [$plg,$action] = my_explode(':',$audit); switch ($action) { diff --git a/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks b/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks index 8a84b8e44..5b48c7b2b 100755 --- a/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks +++ b/plugins/dynamix.plugin.manager/post-hooks/post_plugin_checks @@ -27,32 +27,47 @@ function searchLink(&$db,$url) { if ($url) for ($i = 0; $i < count($db); $i++) if ($db[$i]['PluginURL']==$url) return $db[$i]['Support']; } -$method = $argv[1]; -$name = $argv[2]; -$error = $argv[3]; -$plugin = "/boot/config/plugins/$name"; +$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) -if ($method == 'install' or $method == 'update') { - // check if method successfully completed - if ($error) die; - // 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]) { - // support link exists, update it - $xml->xpath('//PLUGIN/@support')[0] = $support; - } else { - // support link is missing, add it - $xml->addAttribute('support', $support); +$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]) { + // 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()); } - 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"); ?> diff --git a/plugins/dynamix.plugin.manager/pre-hooks/pre_plugin_checks b/plugins/dynamix.plugin.manager/pre-hooks/pre_plugin_checks index 997e1d0e6..dc8ee0bec 100755 --- a/plugins/dynamix.plugin.manager/pre-hooks/pre_plugin_checks +++ b/plugins/dynamix.plugin.manager/pre-hooks/pre_plugin_checks @@ -15,20 +15,41 @@ $docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: "/usr/local/emhttp"; require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php"; -$method = $argv[1]; -$plugin = $argv[2]; +$type = $argv[1]; // plugin or language +$method = $argv[2]; // install, update, remove, check +$name = $argv[3]; // plugin name (*.plg) or language name (*.xml) -// validate plugin update (not applicable to OS updates) -if ($method == 'check' and $plugin != 'unRAIDServer.plg') { - $old_plugin = "/boot/config/plugins/$plugin"; - $new_plugin = "/tmp/plugins/$plugin"; - if (plugin('version', $new_plugin) > plugin('version', $old_plugin)) { - echo "Validating $plugin update\n"; - if (($status = plugin('validate', $new_plugin)) != 'valid') { - echo "$status\n"; - // restore original plugin and undo update - copy($old_plugin, $new_plugin); +$plugin = "/boot/config/plugins/$name"; +$pending = "/tmp/plugins/pluginPending"; + +// 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 + if (@readlink("/var/log/plugins/$name")) plugin('check', $name); + break; + case 'check': + // validate plugin update (not applicable to OS updates) + if ($name == 'unRAIDServer.plg') 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; } ?> diff --git a/plugins/dynamix.plugin.manager/scripts/language b/plugins/dynamix.plugin.manager/scripts/language index df6c7d5c2..a530caa06 100755 --- a/plugins/dynamix.plugin.manager/scripts/language +++ b/plugins/dynamix.plugin.manager/scripts/language @@ -79,6 +79,34 @@ function write($message) { } } +// 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. // @@ -262,8 +290,12 @@ if ($method == 'install') { $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); exit(1); } $lang = language('Language', $xml_file, $error) ?: substr($name,0,-4); @@ -271,6 +303,8 @@ if ($method == 'install') { 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(); } @@ -296,12 +330,18 @@ if ($method == 'check') { @unlink($xml_file); exit(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); exit(1); } write("$version\n"); + // run hook scripts for post processing + post_hooks(); exit(0); } @@ -333,14 +373,20 @@ if ($method == 'update') { // 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); exit(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(); } @@ -356,12 +402,18 @@ if ($method == 'remove') { exit(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); exit(1); } write("language: $lang language pack removed\n"); logger("language: $lang language pack removed"); + // run hook scripts for post processing + post_hooks(); done(); } diff --git a/plugins/dynamix.plugin.manager/scripts/plugin b/plugins/dynamix.plugin.manager/scripts/plugin index 1759f670b..120fe3f21 100755 --- a/plugins/dynamix.plugin.manager/scripts/plugin +++ b/plugins/dynamix.plugin.manager/scripts/plugin @@ -200,27 +200,27 @@ function run($command) { // Run hooked scripts before correct execution of "method" // method = install, update, remove, check -// hook programs receives two parameters: method and plugin-name +// 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 $method $plugin"); + run("$hook plugin $method $plugin"); } } // Run hooked scripts after successful or failed completion of "method" // method = install, update, remove, check -// hook programs receives three parameters: method and plugin-name and error (empty if none) +// 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 $method $plugin $error"); + run("$hook plugin $method $plugin $error"); } } diff --git a/plugins/dynamix/include/DefaultPageLayout.php b/plugins/dynamix/include/DefaultPageLayout.php index 43d0a474e..08eed2101 100644 --- a/plugins/dynamix/include/DefaultPageLayout.php +++ b/plugins/dynamix/include/DefaultPageLayout.php @@ -244,21 +244,29 @@ function openTerminal(tag,name,more) { var socket = ['ttyd','syslog'].includes(tag) ? '/webterminal/'+tag+'/' : '/logterminal/'+name+(more=='.log'?more:'')+'/'; $.get('/webGui/include/OpenTerminal.php',{tag:tag,name:name,more:more},function(){tty_window.location=socket; tty_window.focus();}); } -function bannerAlert(text,cmd,plg,func) { +function bannerAlert(text,cmd,plg,func,start) { $.post('/webGui/include/StartCommand.php',{cmd:cmd,pid:1},function(pid) { if (pid == 0) { - if ($(".upgrade_notice").hasClass('done') || timers.bannerAlert==null) { + if ($(".upgrade_notice").hasClass('done') || timers.bannerAlert == null) { forcedBanner = false; if ($.cookie('addAlert') != null) { removeBannerWarning($.cookie('addAlert')); $.removeCookie('addAlert'); } $(".upgrade_notice").removeClass('alert done'); - if (plg != null) setTimeout((func||'loadlist')+'("'+plg+'")',250); + timers.callback = null; + if (plg != null) { + if ($.cookie('addAlert-page') == null || $.cookie('addAlert-page') == '') { + setTimeout((func||'loadlist')+'("'+plg+'")',250); + } else if ('Plugins' == '') { + setTimeout(refresh); + } + $.removeCookie('addAlert-page'); + } } else { $(".upgrade_notice").removeClass('alert').addClass('done'); timers.bannerAlert = null; - setTimeout(function(){bannerAlert(text,cmd,plg,func);},1000); + setTimeout(function(){bannerAlert(text,cmd,plg,func,start);},1000); } } else { $.cookie('addAlert',addBannerWarning(text,true,true,true)); @@ -266,12 +274,14 @@ function bannerAlert(text,cmd,plg,func) { $.cookie('addAlert-cmd',cmd); $.cookie('addAlert-plg',plg); $.cookie('addAlert-func',func); - timers.bannerAlert = setTimeout(function(){bannerAlert(text,cmd,plg,func);},1000); + if ($.cookie('addAlert-page') == null) $.cookie('addAlert-page',''); + timers.bannerAlert = setTimeout(function(){bannerAlert(text,cmd,plg,func,start);},1000); + if (start == 1 && timers.callback == null && plg != null) timers.callback = setTimeout((func||'loadlist')+'("'+plg+'")',250); } }); } -function openPlugin(cmd,title,plg,func) { - $.post('/webGui/include/StartCommand.php',{cmd:cmd+' nchan'},function(pid) { +function openPlugin(cmd,title,plg,func,start=0) { + $.post('/webGui/include/StartCommand.php',{cmd:cmd+' nchan',start:start},function(pid) { if (pid==0) { $(".upgrade_notice").addClass('alert'); return; @@ -281,13 +291,13 @@ function openPlugin(cmd,title,plg,func) { nchan_plugins.stop(); $('div.spinner.fixed').hide(); $('.sweet-alert').hide('fast').removeClass('nchan'); - setTimeout(function(){bannerAlert(" ["+pid.toString().padStart(8,'0')+"]\" onclick='abortOperation("+pid+")'>",cmd,plg,func);}); + setTimeout(function(){bannerAlert(" ["+pid.toString().padStart(8,'0')+"]\" onclick='abortOperation("+pid+")'>",cmd,plg,func,start);}); }); $('.sweet-alert').addClass('nchan'); }); } function abortOperation(pid) { - swal({title:"",text:"!",html:true,type:'warning',showCancelButton:true,confirmButtonText:"",cancelButtonText:""},function(){ + swal({title:"",text:"",html:true,type:'warning',showCancelButton:true,confirmButtonText:"",cancelButtonText:""},function(){ $.post('/webGui/include/StartCommand.php',{kill:pid},function() { clearTimeout(timers.bannerAlert); timers.bannerAlert = null; @@ -886,7 +896,7 @@ $(function() { var top = ($.cookie('top')||0) - $('.tabs').offset().top - 75; if (top>0) {$('html,body').scrollTop(top);} $.removeCookie('top'); - if ($.cookie('addAlert')!=null) bannerAlert(addAlert.text,addAlert.cmd,addAlert.plg,addAlert.func); + if ($.cookie('addAlert') != null) bannerAlert(addAlert.text,addAlert.cmd,addAlert.plg,addAlert.func); showNotice(" "); diff --git a/plugins/dynamix/include/StartCommand.php b/plugins/dynamix/include/StartCommand.php index 430903f78..831c280db 100644 --- a/plugins/dynamix/include/StartCommand.php +++ b/plugins/dynamix/include/StartCommand.php @@ -18,11 +18,12 @@ function pgrep($proc) { return exec("pgrep -f $proc"); } -if (!empty($_POST['kill']) && $_POST['kill'] > 1) { +if (isset($_POST['kill']) && $_POST['kill'] > 1) { exec("kill ".$_POST['kill']); die; } +$start = isset($_POST['start']) && $_POST['start'] == 1; [$command,$args] = explode(' ',unscript($_POST['cmd']??''),2); // find absolute path of command @@ -35,11 +36,11 @@ if ($command && strncmp($name,$path,strlen($path))===0) { if (isset($_POST['pid'])) { // return running pid $pid = pgrep($name); - } elseif (!pgrep($name)) { - // only execute when command and valid path is given and command not already running + } elseif ($start or !pgrep($name)) { + // start command in background and return pid exec("echo \"$name $args\" | at -M now >/dev/null 2>&1"); usleep(5000); - $pid = pgrep($name); // started + $pid = pgrep($name); } } echo $pid;