MDL-82283 admin: js filtering of the plugins on plugins overview page

This commit is contained in:
Marina Glancy 2024-06-24 17:44:03 +01:00
parent 1938c438ea
commit f8e7bbe776
9 changed files with 160 additions and 56 deletions

11
admin/amd/build/plugins_overview.min.js vendored Normal file
View File

@ -0,0 +1,11 @@
define("core_admin/plugins_overview",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){const filters=document.querySelectorAll(SELECTORS_PLUGIN_FILTERS),pluginRows=document.querySelectorAll(SELECTORS_PLUGIN_ROWS),pluginTypeRows=document.querySelectorAll(SELECTORS_PLUGIN_TYPE_ROWS),filterPlugins=target=>{const filterBy=target.getAttribute("data-filterby"),headerVisibility={};for(const row of pluginRows){const type=[...row.classList].find((s=>s.startsWith("type-"))),visible="all"===filterBy||row.classList.contains(filterBy);row.style.display=visible?null:"none",visible&&type&&(headerVisibility[type]=!0)}for(const row of pluginTypeRows){const type=[...row.classList].find((s=>s.startsWith("type-")));if(type){const visible="all"===filterBy||headerVisibility[type];row.style.display=visible?null:"none"}}filters.forEach((el=>el.classList.remove("active"))),target.classList.add("active")};if(filters.forEach((target=>target.addEventListener("click",(e=>{e.preventDefault(),window.history.replaceState({},null,e.target.href),filterPlugins(target)})))),window.location.hash.length>1){const anchor=window.location.hash.substring(1),target=[...filters].find((t=>t.getAttribute("data-filterby")===anchor));target&&filterPlugins(target)}};
/**
* Allows to filter the plugin list on plugins overview page
*
* @module core_admin/plugins_overview
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const SELECTORS_PLUGIN_FILTERS="#plugins-overview-panel [data-filterby]",SELECTORS_PLUGIN_ROWS="table#plugins-control-panel tbody tr:not(.plugintypeheader)",SELECTORS_PLUGIN_TYPE_ROWS="table#plugins-control-panel tbody tr.plugintypeheader"}));
//# sourceMappingURL=plugins_overview.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"plugins_overview.min.js","sources":["../src/plugins_overview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Allows to filter the plugin list on plugins overview page\n *\n * @module core_admin/plugins_overview\n * @copyright 2024 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst SELECTORS = {\n PLUGIN_FILTERS: '#plugins-overview-panel [data-filterby]',\n PLUGIN_ROWS: 'table#plugins-control-panel tbody tr:not(.plugintypeheader)',\n PLUGIN_TYPE_ROWS: 'table#plugins-control-panel tbody tr.plugintypeheader',\n};\n\n/**\n * Initialise filters for the \"Plugins overview\" page\n */\nexport function init() {\n const filters = document.querySelectorAll(SELECTORS.PLUGIN_FILTERS);\n const pluginRows = document.querySelectorAll(SELECTORS.PLUGIN_ROWS);\n const pluginTypeRows = document.querySelectorAll(SELECTORS.PLUGIN_TYPE_ROWS);\n\n const filterPlugins = (target) => {\n const filterBy = target.getAttribute('data-filterby');\n const headerVisibility = {};\n\n // Hide all plugin rows in the plugin table that do not match the filter and show all others.\n for (const row of pluginRows) {\n const type = [...row.classList].find(s => s.startsWith('type-'));\n const visible = filterBy === 'all' ? true : row.classList.contains(filterBy);\n row.style.display = visible ? null : 'none';\n if (visible && type) {\n headerVisibility[type] = true;\n }\n }\n\n // Hide all the plugin type headers that do not have any visible plugins and show all others.\n for (const row of pluginTypeRows) {\n const type = [...row.classList].find(s => s.startsWith('type-'));\n if (type) {\n const visible = filterBy === 'all' || headerVisibility[type];\n row.style.display = visible ? null : 'none';\n }\n }\n\n // Toggle 'active' class for the selected filter.\n filters.forEach(el => el.classList.remove('active'));\n target.classList.add('active');\n };\n\n // Add event listeners for the links changing plugins filters.\n filters\n .forEach(target => target.addEventListener('click', (e) => {\n e.preventDefault();\n window.history.replaceState({}, null, e.target.href);\n filterPlugins(target);\n }));\n\n // Pre-filter plugins based on the current url anchor.\n if (window.location.hash.length > 1) {\n const anchor = window.location.hash.substring(1);\n const target = [...filters].find(t => t.getAttribute('data-filterby') === anchor);\n if (target) {\n filterPlugins(target);\n }\n }\n}\n"],"names":["filters","document","querySelectorAll","SELECTORS","pluginRows","pluginTypeRows","filterPlugins","target","filterBy","getAttribute","headerVisibility","row","type","classList","find","s","startsWith","visible","contains","style","display","forEach","el","remove","add","addEventListener","e","preventDefault","window","history","replaceState","href","location","hash","length","anchor","substring","t"],"mappings":"4JAiCUA,QAAUC,SAASC,iBAAiBC,0BACpCC,WAAaH,SAASC,iBAAiBC,uBACvCE,eAAiBJ,SAASC,iBAAiBC,4BAE3CG,cAAiBC,eACbC,SAAWD,OAAOE,aAAa,iBAC/BC,iBAAmB,OAGpB,MAAMC,OAAOP,WAAY,OACpBQ,KAAO,IAAID,IAAIE,WAAWC,MAAKC,GAAKA,EAAEC,WAAW,WACjDC,QAAuB,QAAbT,UAA4BG,IAAIE,UAAUK,SAASV,UACnEG,IAAIQ,MAAMC,QAAUH,QAAU,KAAO,OACjCA,SAAWL,OACXF,iBAAiBE,OAAQ,OAK5B,MAAMD,OAAON,eAAgB,OACxBO,KAAO,IAAID,IAAIE,WAAWC,MAAKC,GAAKA,EAAEC,WAAW,cACnDJ,KAAM,OACAK,QAAuB,QAAbT,UAAsBE,iBAAiBE,MACvDD,IAAIQ,MAAMC,QAAUH,QAAU,KAAO,QAK7CjB,QAAQqB,SAAQC,IAAMA,GAAGT,UAAUU,OAAO,YAC1ChB,OAAOM,UAAUW,IAAI,cAIzBxB,QACCqB,SAAQd,QAAUA,OAAOkB,iBAAiB,SAAUC,IACjDA,EAAEC,iBACFC,OAAOC,QAAQC,aAAa,GAAI,KAAMJ,EAAEnB,OAAOwB,MAC/CzB,cAAcC,aAIdqB,OAAOI,SAASC,KAAKC,OAAS,EAAG,OAC3BC,OAASP,OAAOI,SAASC,KAAKG,UAAU,GACxC7B,OAAS,IAAIP,SAASc,MAAKuB,GAAKA,EAAE5B,aAAa,mBAAqB0B,SACtE5B,QACAD,cAAcC;;;;;;;;MAvDpBJ,yBACc,0CADdA,sBAEW,8DAFXA,2BAGgB"}

View File

@ -0,0 +1,82 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Allows to filter the plugin list on plugins overview page
*
* @module core_admin/plugins_overview
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const SELECTORS = {
PLUGIN_FILTERS: '#plugins-overview-panel [data-filterby]',
PLUGIN_ROWS: 'table#plugins-control-panel tbody tr:not(.plugintypeheader)',
PLUGIN_TYPE_ROWS: 'table#plugins-control-panel tbody tr.plugintypeheader',
};
/**
* Initialise filters for the "Plugins overview" page
*/
export function init() {
const filters = document.querySelectorAll(SELECTORS.PLUGIN_FILTERS);
const pluginRows = document.querySelectorAll(SELECTORS.PLUGIN_ROWS);
const pluginTypeRows = document.querySelectorAll(SELECTORS.PLUGIN_TYPE_ROWS);
const filterPlugins = (target) => {
const filterBy = target.getAttribute('data-filterby');
const headerVisibility = {};
// Hide all plugin rows in the plugin table that do not match the filter and show all others.
for (const row of pluginRows) {
const type = [...row.classList].find(s => s.startsWith('type-'));
const visible = filterBy === 'all' ? true : row.classList.contains(filterBy);
row.style.display = visible ? null : 'none';
if (visible && type) {
headerVisibility[type] = true;
}
}
// Hide all the plugin type headers that do not have any visible plugins and show all others.
for (const row of pluginTypeRows) {
const type = [...row.classList].find(s => s.startsWith('type-'));
if (type) {
const visible = filterBy === 'all' || headerVisibility[type];
row.style.display = visible ? null : 'none';
}
}
// Toggle 'active' class for the selected filter.
filters.forEach(el => el.classList.remove('active'));
target.classList.add('active');
};
// Add event listeners for the links changing plugins filters.
filters
.forEach(target => target.addEventListener('click', (e) => {
e.preventDefault();
window.history.replaceState({}, null, e.target.href);
filterPlugins(target);
}));
// Pre-filter plugins based on the current url anchor.
if (window.location.hash.length > 1) {
const anchor = window.location.hash.substring(1);
const target = [...filters].find(t => t.getAttribute('data-filterby') === anchor);
if (target) {
filterPlugins(target);
}
}
}

View File

@ -29,8 +29,6 @@ require_once($CFG->libdir . '/adminlib.php');
require_once($CFG->libdir . '/filelib.php');
$fetchupdates = optional_param('fetchupdates', false, PARAM_BOOL); // Check for available plugins updates.
$updatesonly = optional_param('updatesonly', false, PARAM_BOOL); // Show updateable plugins only.
$contribonly = optional_param('contribonly', false, PARAM_BOOL); // Show additional plugins only.
$uninstall = optional_param('uninstall', '', PARAM_COMPONENT); // Uninstall the plugin.
$delete = optional_param('delete', '', PARAM_COMPONENT); // Delete the plugin folder after it is uninstalled.
$confirmed = optional_param('confirm', false, PARAM_BOOL); // Confirm the uninstall/delete action.
@ -47,8 +45,7 @@ require_admin();
$syscontext = context_system::instance();
// URL params we want to maintain on redirects.
$pageparams = array('updatesonly' => $updatesonly, 'contribonly' => $contribonly);
$pageurl = new moodle_url('/admin/plugins.php', $pageparams);
$pageurl = new moodle_url('/admin/plugins.php');
$pluginman = core_plugin_manager::instance();
@ -57,7 +54,7 @@ $PAGE->set_primary_active_tab('siteadminnode');
if ($uninstall) {
if (!$confirmed) {
admin_externalpage_setup('pluginsoverview', '', $pageparams);
admin_externalpage_setup('pluginsoverview');
} else {
$PAGE->set_url($pageurl);
$PAGE->set_context($syscontext);
@ -201,7 +198,7 @@ if ($installupdate and $installupdateversion) {
}
}
admin_externalpage_setup('pluginsoverview', '', $pageparams);
admin_externalpage_setup('pluginsoverview');
/** @var core_admin_renderer $output */
$output = $PAGE->get_renderer('core', 'admin');
@ -214,4 +211,4 @@ if ($fetchupdates) {
redirect($PAGE->url);
}
echo $output->plugin_management_page($pluginman, $checker, $pageparams);
echo $output->plugin_management_page($pluginman, $checker);

View File

@ -1673,8 +1673,9 @@ class core_admin_renderer extends plugin_renderer_base {
public function plugins_overview_panel(core_plugin_manager $pluginman, array $options = array()) {
$plugininfo = $pluginman->get_plugins();
$this->page->requires->js_call_amd('core_admin/plugins_overview', 'init');
$numtotal = $numextension = $numupdatable = $numinstallable = 0;
$numtotal = $numextension = $numupdatable = $numinstallable = $nummissing = $numnew = 0;
foreach ($plugininfo as $type => $plugins) {
foreach ($plugins as $name => $plugin) {
@ -1688,8 +1689,12 @@ class core_admin_renderer extends plugin_renderer_base {
}
}
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_MISSING) {
$nummissing++;
continue;
}
if ($plugin->get_status() === core_plugin_manager::PLUGIN_STATUS_NEW) {
$numnew++;
}
$numtotal++;
if (!$plugin->is_standard()) {
$numextension++;
@ -1698,36 +1703,50 @@ class core_admin_renderer extends plugin_renderer_base {
}
$infoall = html_writer::link(
new moodle_url($this->page->url, array('contribonly' => 0, 'updatesonly' => 0)),
$this->page->url,
get_string('overviewall', 'core_plugin'),
array('title' => get_string('filterall', 'core_plugin'))
['title' => get_string('filterall', 'core_plugin'), 'data-filterby' => 'all', 'class' => 'active']
).' '.html_writer::span($numtotal, 'badge number number-all');
$infoext = html_writer::link(
new moodle_url($this->page->url, array('contribonly' => 1, 'updatesonly' => 0)),
new moodle_url($this->page->url, [], 'additional'),
get_string('overviewext', 'core_plugin'),
array('title' => get_string('filtercontribonly', 'core_plugin'))
['title' => get_string('filtercontribonly', 'core_plugin'), 'data-filterby' => 'additional']
).' '.html_writer::span($numextension, 'badge number number-additional');
if ($numupdatable) {
$infoupdatable = html_writer::link(
new moodle_url($this->page->url, array('contribonly' => 0, 'updatesonly' => 1)),
new moodle_url($this->page->url, [], 'updatable'),
get_string('overviewupdatable', 'core_plugin'),
array('title' => get_string('filterupdatesonly', 'core_plugin'))
['title' => get_string('filterupdatesonly', 'core_plugin'), 'data-filterby' => 'updatable']
).' '.html_writer::span($numupdatable, 'badge bg-info text-white number number-updatable');
} else {
// No updates, or the notifications disabled.
$infoupdatable = '';
}
$out = html_writer::start_div('', array('id' => 'plugins-overview-panel'));
if (!empty($options['updatesonly'])) {
$out .= $this->output->heading(get_string('overviewupdatable', 'core_plugin'), 3);
} else if (!empty($options['contribonly'])) {
$out .= $this->output->heading(get_string('overviewext', 'core_plugin'), 3);
if ($numnew) {
$infonew = html_writer::link(
new moodle_url($this->page->url, [], 'newplugin'),
get_string('status_new', 'plugin'),
['title' => get_string('filternewpluginsonly', 'core_plugin'), 'data-filterby' => 'newplugin']
).' '.html_writer::span($numnew, 'badge bg-success text-white number number-newplugin');
} else {
$infonew = '';
}
if ($nummissing) {
$infomissing = html_writer::link(
new moodle_url($this->page->url, [], 'missing'),
get_string('status_missing', 'plugin'),
['title' => get_string('filtermissingonly', 'core_plugin'), 'data-filterby' => 'missing']
).' '.html_writer::span($nummissing, 'badge bg-danger text-white number number-missing');
} else {
$infomissing = '';
}
$out = html_writer::start_div('', ['id' => 'plugins-overview-panel']);
if ($numinstallable) {
$out .= $this->output->single_button(
new moodle_url($this->page->url, array('installupdatex' => 1)),
@ -1738,8 +1757,10 @@ class core_admin_renderer extends plugin_renderer_base {
}
$out .= html_writer::div($infoall, 'info info-all').
html_writer::div($infoext, 'info info-ext').
html_writer::div($infoupdatable, 'info info-updatable');
html_writer::div($infoext, 'info info-additional').
html_writer::div($infoupdatable, 'info info-updatable').
html_writer::div($infonew, 'info info-newplugin').
html_writer::div($infomissing, 'info info-missing');
$out .= html_writer::end_div(); // End of #plugins-overview-panel block.
@ -1759,38 +1780,6 @@ class core_admin_renderer extends plugin_renderer_base {
$plugininfo = $pluginman->get_plugins();
// Filter the list of plugins according the options.
if (!empty($options['updatesonly'])) {
$updateable = array();
foreach ($plugininfo as $plugintype => $pluginnames) {
foreach ($pluginnames as $pluginname => $pluginfo) {
$pluginavailableupdates = $pluginfo->available_updates();
if (!empty($pluginavailableupdates)) {
foreach ($pluginavailableupdates as $pluginavailableupdate) {
$updateable[$plugintype][$pluginname] = $pluginfo;
}
}
}
}
$plugininfo = $updateable;
}
if (!empty($options['contribonly'])) {
$contribs = array();
foreach ($plugininfo as $plugintype => $pluginnames) {
foreach ($pluginnames as $pluginname => $pluginfo) {
if (!$pluginfo->is_standard()) {
$contribs[$plugintype][$pluginname] = $pluginfo;
}
}
}
$plugininfo = $contribs;
}
if (empty($plugininfo)) {
return '';
}
$table = new html_table();
$table->id = 'plugins-control-panel';
$table->head = array(
@ -1888,13 +1877,15 @@ class core_admin_renderer extends plugin_renderer_base {
$row->attributes['class'] .= ' standard';
$source = '';
} else {
$row->attributes['class'] .= ' extension';
$source = html_writer::div(get_string('sourceext', 'core_plugin'), 'source badge bg-info text-white');
$row->attributes['class'] .= ' additional';
$source = html_writer::div(get_string('sourceext', 'core_plugin'), 'source badge mr-1 bg-info text-white');
}
if ($status === core_plugin_manager::PLUGIN_STATUS_MISSING) {
$row->attributes['class'] .= ' missing';
$msg = html_writer::div(get_string('status_missing', 'core_plugin'), 'statusmsg badge bg-danger text-white');
} else if ($status === core_plugin_manager::PLUGIN_STATUS_NEW) {
$row->attributes['class'] .= ' newplugin';
$msg = html_writer::div(get_string('status_new', 'core_plugin'), 'statusmsg badge bg-success text-white');
} else {
$msg = '';
@ -1910,6 +1901,7 @@ class core_admin_renderer extends plugin_renderer_base {
$updateinfo = '';
if (is_array($plugin->available_updates())) {
$row->attributes['class'] .= ' updatable';
foreach ($plugin->available_updates() as $availableupdate) {
$updateinfo .= $this->plugin_available_update_info($pluginman, $availableupdate);
}

View File

@ -53,6 +53,8 @@ $string['err_response_format_version'] = 'Unexpected version of the response for
$string['err_response_http_code'] = 'Unable to fetch available updates data - unexpected HTTP response code.';
$string['filterall'] = 'Show all';
$string['filtercontribonly'] = 'Show additional plugins only';
$string['filtermissingonly'] = 'Show missing from disk only';
$string['filternewpluginsonly'] = 'Show only plugins to be installed';
$string['filterupdatesonly'] = 'Show updateable only';
$string['incompatibleversion'] = 'Incompatible Moodle version: {$a}';
$string['isenabled'] = 'Enabled?';

View File

@ -438,6 +438,13 @@
.info {
display: inline-block;
margin-right: 1em;
[data-filterby].active {
font-weight: bold;
}
}
.updateavailableinstallall {
margin-right: 1em;
}
}

View File

@ -26876,6 +26876,12 @@ img.icon {
display: inline-block;
margin-right: 1em;
}
#page-admin-plugins #plugins-overview-panel .info [data-filterby].active {
font-weight: bold;
}
#page-admin-plugins #plugins-overview-panel .updateavailableinstallall {
margin-right: 1em;
}
#page-admin-plugins .checkforupdates {
margin: 10px 0;
}

View File

@ -26876,6 +26876,12 @@ img.icon {
display: inline-block;
margin-right: 1em;
}
#page-admin-plugins #plugins-overview-panel .info [data-filterby].active {
font-weight: bold;
}
#page-admin-plugins #plugins-overview-panel .updateavailableinstallall {
margin-right: 1em;
}
#page-admin-plugins .checkforupdates {
margin: 10px 0;
}