Modernise the appearance of the plugin manager (#5916)

Co-authored-by: Joseph Petersen <josephp90@gmail.com>
Co-authored-by: Tim Jacomb <timjacomb1+github@gmail.com>
Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2021-12-14 08:27:21 +00:00
1 parent 165d559469
commit 3dcabc4b02
34 files changed
+845 -688

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
root = true
[*.{jelly, js, less, css, hbs}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
@@ -1,74 +0,0 @@
#filter-box {
padding-left: 35px;
width: 15em;
}
time {
white-space: nowrap;
}
#filter-container {
margin: 1em 0;
font-size: 1em;
}
.plugin-manager__categories {
margin-top: 0.25em;
}
.plugin-manager__search-input {
position: static;
padding: 0.25rem 0.1rem 0.25rem 2.5rem;
margin: 0;
font-size: 1rem;
line-height: 1.5;
color: #333;
color: var(--text-color);
border-radius: 4px;
border: 1px solid grey;
outline: none;
}
.plugin-manager__search {
position: relative;
}
.plugin-manager__icon-leading {
position: absolute;
left: -2px;
top: -3px;
font-size: 15px;
padding: 0.5rem;
}
.main-search__icon-leading {
left: 0;
pointer-events: none;
}
.hidden-by-default {
}
.hidden {
display: none;
}
#plugins tr.unavailable {
background-color: #f4f4f4;
background-color: var(--plugin-manager-unavailable-bg-color);
}
tr.unavailable span.unavailable-label {
display: inline-block;
border: 1px solid #666;
color: #333;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
padding: 0 0.5rem;
margin: 0.25rem 0.5rem;
text-decoration: none;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
}
@@ -16,13 +16,14 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) {
var anyVisible = false;
for (var i=0; i<items.length; i++) {
if ((filterParts.length < 1 || filter.length < 2) && items[i].hasClassName("hidden-by-default")) {
items[i].addClassName("hidden");
items[i].addClassName("jenkins-hidden");
continue;
}
var makeVisible = true;
var pluginId = items[i].getAttribute('data-plugin-id');
var content = (items[i].querySelector('.details').innerText + " " + pluginId).toLowerCase();
for (var j = 0; j < filterParts.length; j++) {
var part = filterParts[j];
if (content.indexOf(part) < 0) {
@@ -30,11 +31,12 @@ Behaviour.specify("#filter-box", '_table', 0, function(e) {
break;
}
}
if (makeVisible) {
items[i].removeClassName("hidden");
items[i].classList.remove("jenkins-hidden");
anyVisible = true;
} else {
items[i].addClassName("hidden");
items[i].classList.add("jenkins-hidden");
}
}
var instructions = document.getElementById("hidden-by-default-instructions")
@@ -23,92 +23,90 @@ THE SOFTWARE.
-->
<!--
List of available new plugins
Advanced settings for plugin manager
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout title="${%Update Center}" permission="${app.SYSTEM_READ}">
<j:set var="readOnlyMode" value="${!app.hasPermission(app.ADMINISTER)}" />
<l:layout title="${%Advanced Settings} - ${%Plugin Manager}" permission="${app.SYSTEM_READ}">
<j:set var="readOnlyMode" value="${!app.hasPermission(app.ADMINISTER)}"/>
<l:app-bar title="${%Plugin Manager}"/>
<st:include page="sidepanel.jelly"/>
<l:main-panel>
<local:tabBar page="advanced" xmlns:local="/hudson/PluginManager"/>
<div class="pane-frame">
<div id="pluginsAdv" class="pane" style="margin-top:0; border-top:none">
<section>
<h2>${%HTTP Proxy Configuration}</h2>
<f:form method="post" action="proxyConfigure" name="proxyConfigure">
<j:scope>
<j:set var="instance" value="${app.proxy}"/>
<j:set var="descriptor" value="${it.proxyDescriptor}"/>
<st:include from="${descriptor}" page="${descriptor.configPage}"/>
</j:scope>
<l:isAdmin>
<f:block>
<f:submit/>
</f:block>
</l:isAdmin>
</f:form>
</section>
<l:isAdmin>
<section>
<h2>${%Deploy Plugin}</h2>
<f:form method="post" action="uploadPlugin" name="uploadPlugin" enctype="multipart/form-data">
<div style="margin-bottom: 1em;">
${%deploytext}
</div>
<f:entry title="${%File}">
<input type="file" name="name" class="setting-input" accept=".hpi,.jpi"/>
</f:entry>
<p>${%Or}</p>
<f:entry title="${%URL}">
<f:textbox name="pluginUrl"
class="setting-input validated" checkUrl="'checkPluginUrl?value='+this.value" />
</f:entry>
<f:submit value="${%Deploy}"/>
</f:form>
</section>
</l:isAdmin>
<section class="jenkins-section">
<h2 class="jenkins-section__title">${%HTTP Proxy Configuration}</h2>
<f:form method="post" action="proxyConfigure" name="proxyConfigure">
<j:scope>
<j:set var="instance" value="${app.proxy}"/>
<j:set var="descriptor" value="${it.proxyDescriptor}"/>
<st:include from="${descriptor}" page="${descriptor.configPage}"/>
</j:scope>
<l:isAdmin>
<f:block>
<f:submit/>
</f:block>
</l:isAdmin>
</f:form>
</section>
<section>
<h2>${%Update Site}</h2>
<f:form method="post" action="siteConfigure" name="siteConfigure">
<f:entry title="${%URL}">
<f:textbox name="site"
value="${app.updateCenter.getSite(app.updateCenter.ID_DEFAULT).url}"/>
</f:entry>
<l:isAdmin>
<f:block>
<f:submit/>
</f:block>
</l:isAdmin>
</f:form>
<j:set var="hasNonDefault" value="${false}"/>
<l:isAdmin>
<section class="jenkins-section">
<h2 class="jenkins-section__title">${%Deploy Plugin}</h2>
<f:form method="post" action="uploadPlugin" name="uploadPlugin" enctype="multipart/form-data">
<div style="margin-bottom: 1em;">
${%deploytext}
</div>
<f:entry title="${%File}">
<input type="file" name="name" class="setting-input" accept=".hpi,.jpi"/>
</f:entry>
<p>${%Or}</p>
<f:entry title="${%URL}">
<f:textbox name="pluginUrl"
class="setting-input validated" checkUrl="'checkPluginUrl?value='+this.value"/>
</f:entry>
<f:submit value="${%Deploy}"/>
</f:form>
</section>
</l:isAdmin>
<section class="jenkins-section">
<h2 class="jenkins-section__title">${%Update Site}</h2>
<f:form method="post" action="siteConfigure" name="siteConfigure">
<f:entry title="${%URL}">
<f:textbox name="site"
value="${app.updateCenter.getSite(app.updateCenter.ID_DEFAULT).url}"/>
</f:entry>
<l:isAdmin>
<f:block>
<f:submit/>
</f:block>
</l:isAdmin>
</f:form>
<j:set var="hasNonDefault" value="${false}"/>
<j:forEach var="site" items="${app.updateCenter.sites}">
<j:if test="${site.id != app.updateCenter.ID_DEFAULT}">
<j:set var="hasNonDefault" value="${true}"/>
</j:if>
</j:forEach>
</section>
<j:if test="${hasNonDefault}">
<section class="jenkins-section">
<h2 class="jenkins-section__title">${%Other Sites}</h2>
<ul>
<j:forEach var="site" items="${app.updateCenter.sites}">
<j:if test="${site.id != app.updateCenter.ID_DEFAULT}">
<j:set var="hasNonDefault" value="${true}"/>
<li>${site.url}</li>
</j:if>
</j:forEach>
</section>
<j:if test="${hasNonDefault}">
<section>
<h2 class="h4">${%Other Sites}</h2>
<ul>
<j:forEach var="site" items="${app.updateCenter.sites}">
<j:if test="${site.id != app.updateCenter.ID_DEFAULT}">
<li>${site.url}</li>
</j:if>
</j:forEach>
</ul>
</section>
</j:if>
</div>
</div>
<div align="right" style="margin-top:1em">
<st:include page="check.jelly"/>
</div>
</ul>
</section>
</j:if>
</l:main-panel>
</l:layout>
</j:jelly>
@@ -23,47 +23,42 @@ THE SOFTWARE.
-->
<!--
List of available new plugins
List of available new plugins
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:f="/lib/form">
<l:layout title="${%Available Plugins}" permission="${app.SYSTEM_READ}">
<l:layout title="${%Available Plugins} - ${%Plugin Manager}" permission="${app.SYSTEM_READ}">
<j:set var="page" value="available"/>
<st:include page="sidepanel.jelly"/>
<l:main-panel>
<script src="${resURL}/jsbundles/plugin-manager-ui.js" type="text/javascript"/>
<link rel="stylesheet" href="${resURL}/jsbundles/plugin-manager-ui.css" type="text/css"/>
<div id="filter-container">
<div class="plugin-manager__search">
<input
role="searchbox"
class="plugin-manager__search-input"
type="text"
id="filter-box"
value="${request.getParameter('filter')}"
placeholder="${%search}"/>
<span class="plugin-manager__icon-leading">
<l:svgIcon href="${resURL}/images/material-icons/svg-sprite-action-symbol.svg#ic_search_24px"/>
</span>
<l:app-bar title="${%Plugin Manager}">
<div class="jenkins-search">
<input
class="jenkins-search__input"
type="search"
id="filter-box"
value="${request.getParameter('filter')}"
placeholder="${%Search}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
</div>
</div>
</l:app-bar>
<st:include page="sidepanel.jelly"/>
<l:main-panel>
<script src="${resURL}/jsbundles/plugin-manager-ui.js" type="text/javascript"/>
<form method="post" action="install">
<local:tabBar page="${page}" xmlns:local="/hudson/PluginManager"/>
<table id="plugins" class="sortable pane bigtable stripped-odd"
<table id="plugins" class="jenkins-table sortable"
data-hasAdmin="${h.hasPermission(app.ADMINISTER)}">
<thead>
<tr>
<l:isAdmin>
<th initialSortDir="${isUpdates?null:'up'}"
tooltip="${%Check to install the plugin}"
width="32">${%Install}
</th>
<th width="32">${%Install}</th>
</l:isAdmin>
<th initialSortDir="${isUpdates?'down':null}">${%Name}</th>
<th>${%Version}</th>
<th initialSortDir="down">${%Name}</th>
<th>${%Released}</th>
<j:if test="${isUpdates}">
<th>${%Installed}</th>
@@ -23,29 +23,31 @@ THE SOFTWARE.
-->
<!--
Config page
Installed plugins
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:s="/lib/form">
<l:layout title="${%Update Center}" permission="${app.SYSTEM_READ}">
<j:set var="readOnlyMode" value="${!app.hasPermission(app.ADMINISTER)}" />
<st:include page="sidepanel.jelly"/>
<l:main-panel>
<st:adjunct includes="hudson.PluginManager._table"/>
<div id="filter-container">
<div class="plugin-manager__search">
<input
role="searchbox"
class="plugin-manager__search-input"
type="text"
id="filter-box"
value="${request.getParameter('filter')}"
placeholder="${filtered == 'true' ? '%search' : '%filter'}" />
<span class="plugin-manager__icon-leading">
<l:svgIcon href="${resURL}/images/material-icons/svg-sprite-action-symbol.svg#ic_search_24px" />
</span>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:s="/lib/form">
<l:layout title="${%Installed Plugins} - ${%Plugin Manager}" permission="${app.SYSTEM_READ}">
<j:set var="readOnlyMode" value="${!app.hasPermission(app.ADMINISTER)}"/>
<l:app-bar title="${%Plugin Manager}">
<div class="jenkins-search">
<input
class="jenkins-search__input"
type="search"
id="filter-box"
value="${request.getParameter('filter')}"
placeholder="${filtered == 'true' ? '%Search' : '%Filter'}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
</div>
</div>
</l:app-bar>
<st:include page="sidepanel.jelly"/>
<l:main-panel>
<st:adjunct includes="hudson.PluginManager._table"/>
<j:if test="${app.updateCenter.isRestartRequiredForCompletion()}">
<div class="alert alert-warning"><strong>${%Warning}</strong>: ${%requires.restart}</div>
@@ -64,171 +66,202 @@ THE SOFTWARE.
/>
<local:tabBar page="installed" xmlns:local="/hudson/PluginManager" />
<table id="plugins" class="pane bigtable sortable stripped-odd">
<table id="plugins" class="jenkins-table sortable">
<j:choose>
<j:when test="${empty(app.pluginManager.plugins) and empty(app.pluginManager.failedPlugins)}">
<tr>
<th>${%Installed plugins}</th>
</tr>
<tr>
<td align="center">${%No plugins installed.}</td>
</tr>
<thead>
<tr>
<th>${%Installed plugins}</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">${%No plugins installed.}</td>
</tr>
</tbody>
</j:when>
<j:otherwise>
<tr>
<th width="32" tooltip="${%Uncheck to disable the plugin}">${%Enabled}</th>
<th initialSortDir="down">${%Name}</th>
<th width="1">${%Version}</th>
<l:isAdmin>
<th width="1">${%Previously installed version}</th>
<th width="1">${%Uninstall}</th>
</l:isAdmin>
</tr>
<j:forEach var="p" items="${app.pluginManager.plugins}">
<tr class="plugin ${p.hasMandatoryDependents()?'has-dependents':''} ${(p.hasMandatoryDependents() and !p.enabled)?'has-dependents-but-disabled':''} ${p.isDeleted()?'deleted':''} ${p.hasImpliedDependents() and p.enabled ? 'possibly-has-implied-dependents' : ''}" data-plugin-id="${p.shortName}" data-plugin-name="${p.displayName}">
<j:set var="state" value="${p.enabled?'true':null}"/>
<td class="center pane enable" data="${state}">
<input type="checkbox" checked="${state}" onclick="flip(event)"
url="plugin/${p.shortName}"
disabled="${readOnlyMode ? 'true' : null}"
original="${p.active?'true':'false'}"/>
</td>
<td class="pane details">
<div class="plugin-name success-plugin">
<a href="${p.url}" target="_blank" rel="noopener noreferrer" class="display-name">
${p.updateInfo.displayName?:p.displayName}
</a>
</div>
<div class="excerpt">
<j:set var="indexPage" value="${p.indexPage.toString()}" />
<j:set var="excerpt" value="${app.updateCenter.getPlugin(p.shortName).excerpt}"/>
<j:set var="description" value="${p.manifest.mainAttributes.getValue('Specification-Title')}"/>
<j:choose>
<j:when test="${!empty(indexPage)}"> <!-- src/main/resources/index.jelly, preferred -->
<j:include uri="${indexPage}"/>
</j:when>
<j:when test="${excerpt != null}"> <!-- HTML from {excerpt} in wiki page -->
<j:out value="${excerpt}"/>
</j:when>
<j:when test="${description != null}"> <!-- last resort, from POM -->
${description}
</j:when>
<j:otherwise>
${%No description available.}
</j:otherwise>
</j:choose>
</div>
<j:if test="${!p.getActiveWarnings().isEmpty()}">
<div class="alert alert-danger">${%securityWarning}
<ul>
<j:forEach var="warning" items="${p.getActiveWarnings()}">
<li><a href="${warning.url}" rel="noopener noreferrer" target="_blank">${warning.message}</a></li>
</j:forEach>
</ul>
</div>
</j:if>
<j:if test="${p.isDeprecated()}">
<div class="alert alert-warning">
<!-- figure out a way how to present deprecations by multiple update sites instead of just the first one -->
${%deprecationWarning(p.getDeprecations().get(0).url)}
</div>
</j:if>
<j:if test="${it.hasAdoptThisPluginLabel(p)}">
<div class="alert alert-warning">
${%adoptThisPlugin}
</div>
</j:if>
</td>
<td class="pane" style="white-space:normal">
<a href="plugin/${p.shortName}/thirdPartyLicenses">
${p.version}
</a>
</td>
<thead>
<tr>
<th initialSortDir="down">${%Name}</th>
<th class="jenkins-table__cell--tight">${%Enabled}</th>
<l:isAdmin>
<td class="pane">
<j:if test="${p.downgradable}">
<form method="post" action="${rootURL}/updateCenter/plugin/${p.shortName}/downgrade">
<s:submit value="${%downgradeTo(p.backupVersion)}"/>
</form>
</j:if>
</td>
<j:choose>
<j:when test="${p.hasMandatoryDependents()}">
<j:set var="uninstallPossible" value="true"/>
</j:when>
<j:otherwise>
<j:set var="uninstallPossible" value="false"/>
</j:otherwise>
</j:choose>
<td class="pane uninstall" data="${uninstallPossible}">
<j:choose>
<j:when test="${p.isDeleted()}">
<p>${%Uninstallation pending}</p>
</j:when>
<j:otherwise>
<j:if test="${p.hasMandatoryDependents() or p.hasImpliedDependents()}">
<div class="dependent-list">
<j:forEach var="dependent" items="${p.mandatoryDependents}">
<span data-plugin-id="${dependent}"></span>
</j:forEach>
<j:set var="impliedDependents" value="${p.impliedDependents}" />
<j:choose>
<j:when test="${impliedDependents.size() lt 15}">
<j:forEach var="dependent" items="${impliedDependents}">
<span data-plugin-id="${dependent}"></span>
</j:forEach>
</j:when>
<j:otherwise>
<p>${%detached-many-dependents(impliedDependents.size())}</p>
</j:otherwise>
</j:choose>
</div>
</j:if>
<j:if test="${p.hasMandatoryDependencies()}">
<div class="dependency-list">
<j:forEach var="dependency" items="${p.mandatoryDependencies}">
<span data-plugin-id="${dependency.shortName}"></span>
</j:forEach>
</div>
</j:if>
<form method="post" action="plugin/${p.shortName}/uninstall">
<input class="uninstall" type="submit" value="${%Uninstall}"/>
</form>
</j:otherwise>
</j:choose>
</td>
<th data-sort-disable="true"/>
</l:isAdmin>
</tr>
</j:forEach>
<!-- failed ones -->
<j:forEach var="p" items="${it.failedPlugins}">
<tr class="hoverback">
<td class="center pane enable">
<input type="checkbox" disabled="disabled"/>
</td>
<td class="pane">
<j:if test="${p.pluginWrapper != null}">
<div class="plugin-name failed-plugin with-plugin-wrapper">
<a href="${p.pluginWrapper.url}" target="_blank" rel="noopener noreferrer" class="display-name">
${p.pluginWrapper.updateInfo.displayName?:p.pluginWrapper.displayName}
</thead>
<tbody>
<j:forEach var="p" items="${app.pluginManager.plugins}">
<tr
class="plugin ${p.hasMandatoryDependents()?'has-dependents':''} ${(p.hasMandatoryDependents() and !p.enabled)?'has-dependents-but-disabled':''} ${p.isDeleted()?'deleted':''} ${p.hasImpliedDependents() and p.enabled ? 'possibly-has-implied-dependents' : ''}"
data-plugin-id="${p.shortName}" data-plugin-name="${p.displayName}">
<td class="details">
<div class="plugin-name success-plugin">
<a href="${p.url}" class="jenkins-table__link" target="_blank" rel="noopener noreferrer">
${p.updateInfo.displayName?:p.displayName}
<span class="jenkins-label jenkins-label--tertiary" style="margin-left: 1ch;">
<span class="jenkins-visually-hidden">${%Version}</span>
${p.version}
</span>
</a>
</div>
</j:if>
<j:if test="${p.pluginWrapper == null}">
<div class="plugin-name failed-plugin no-plugin-wrapper">
${p.name}
<div class="jenkins-!-margin-top-1">
<j:set var="indexPage" value="${p.indexPage.toString()}"/>
<j:set var="excerpt"
value="${app.updateCenter.getPlugin(p.shortName).excerpt}"/>
<j:set var="description"
value="${p.manifest.mainAttributes.getValue('Specification-Title')}"/>
<j:choose>
<j:when
test="${!empty(indexPage)}"> <!-- src/main/resources/index.jelly, preferred -->
<j:include uri="${indexPage}"/>
</j:when>
<j:when test="${excerpt != null}"> <!-- HTML from {excerpt} in wiki page -->
<j:out value="${excerpt}"/>
</j:when>
<j:when test="${description != null}"> <!-- last resort, from POM -->
${description}
</j:when>
<j:otherwise>
${%No description available.}
</j:otherwise>
</j:choose>
</div>
</j:if>
<div class="alert alert-danger">
<pre>${p.cause.message}</pre>
</div>
</td>
<td class="pane" />
<td class="pane" />
<td class="pane" />
</tr>
</j:forEach>
<j:if test="${!p.getActiveWarnings().isEmpty()}">
<div class="alert alert-danger">${%securityWarning}
<ul>
<j:forEach var="warning" items="${p.getActiveWarnings()}">
<li>
<a href="${warning.url}" rel="noopener noreferrer"
target="_blank">${warning.message}
</a>
</li>
</j:forEach>
</ul>
</div>
</j:if>
<j:if test="${p.isDeprecated()}">
<div class="alert alert-warning">
<!-- figure out a way how to present deprecations by multiple update sites instead of just the first one -->
${%deprecationWarning(p.getDeprecations().get(0).url)}
</div>
</j:if>
<j:if test="${it.hasAdoptThisPluginLabel(p)}">
<div class="alert alert-warning jenkins-!-margin-top-1 jenkins-!-margin-bottom-0">
${%adoptThisPlugin}
</div>
</j:if>
</td>
<j:set var="state" value="${p.enabled?'true':null}"/>
<td class="jenkins-table__cell--tight enable" data="${state}">
<div class="jenkins-table__cell__button-wrapper">
<span class="jenkins-toggle-switch">
<input type="checkbox" checked="${state}" onclick="flip(event)"
url="plugin/${p.shortName}"
disabled="${readOnlyMode ? 'true' : null}"
original="${p.active?'true':'false'}"/>
<label class="attach-previous"></label>
</span>
</div>
</td>
<l:isAdmin>
<td class="jenkins-table__cell--tight">
<div class="jenkins-table__cell__button-wrapper">
<j:if test="${p.downgradable}">
<form class="jenkins-buttons-row" method="post"
action="${rootURL}/updateCenter/plugin/${p.shortName}/downgrade">
<button class="jenkins-table__button jenkins-table__button--orange" tooltip="${%downgradeTo(p.backupVersion)}">
<l:ionicon name="downgrade-circle-outline"/>
<span class="jenkins-!-margin-left-1"
style="font-size: 0.75rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 50px;">
${p.backupVersion}
</span>
</button>
</form>
</j:if>
</div>
</td>
<td class="jenkins-table__cell--tight uninstall">
<div class="jenkins-table__cell__button-wrapper">
<j:choose>
<j:when test="${p.isDeleted()}">
<p>${%Uninstallation pending}</p>
</j:when>
<j:otherwise>
<j:if test="${p.hasMandatoryDependents() or p.hasImpliedDependents()}">
<div class="dependent-list">
<j:forEach var="dependent" items="${p.mandatoryDependents}">
<span data-plugin-id="${dependent}"></span>
</j:forEach>
<j:set var="impliedDependents" value="${p.impliedDependents}"/>
<j:choose>
<j:when test="${impliedDependents.size() lt 15}">
<j:forEach var="dependent" items="${impliedDependents}">
<span data-plugin-id="${dependent}"></span>
</j:forEach>
</j:when>
<j:otherwise>
<p>
${%detached-many-dependents(impliedDependents.size())}
</p>
</j:otherwise>
</j:choose>
</div>
</j:if>
<j:if test="${p.hasMandatoryDependencies()}">
<div class="dependency-list">
<j:forEach var="dependency" items="${p.mandatoryDependencies}">
<span data-plugin-id="${dependency.shortName}"></span>
</j:forEach>
</div>
</j:if>
<form class="jenkins-buttons-row" method="post" action="plugin/${p.shortName}/uninstall">
<button class="jenkins-table__button jenkins-table__button--red uninstall" tooltip="${%Uninstall} ${p.updateInfo.displayName?:p.displayName}">
<l:ionicon name="close-circle-outline"/>
</button>
</form>
</j:otherwise>
</j:choose>
</div>
</td>
</l:isAdmin>
</tr>
</j:forEach>
<!-- failed ones -->
<j:forEach var="p" items="${it.failedPlugins}">
<tr class="hoverback">
<td>
<j:if test="${p.pluginWrapper != null}">
<div class="plugin-name failed-plugin with-plugin-wrapper">
<a href="${p.pluginWrapper.url}" target="_blank" rel="noopener noreferrer"
class="display-name">
${p.pluginWrapper.updateInfo.displayName?:p.pluginWrapper.displayName}
</a>
</div>
</j:if>
<j:if test="${p.pluginWrapper == null}">
<div class="plugin-name failed-plugin no-plugin-wrapper">
${p.name}
</div>
</j:if>
<div class="alert alert-danger jenkins-!-margin-top-1 jenkins-!-margin-bottom-0">
<pre>${p.cause.message}</pre>
</div>
</td>
<td class="jenkins-table__cell--tight">
<div class="jenkins-table__cell__button-wrapper">
<span class="jenkins-toggle-switch">
<input type="checkbox" disabled="disabled"/>
<label class="attach-previous"></label>
</span>
</div>
</td>
<td/>
<td/>
</tr>
</j:forEach>
</tbody>
</j:otherwise>
</j:choose>
</table>
@@ -237,7 +270,7 @@ THE SOFTWARE.
<form method="post" action="${rootURL}/safeRestart">
${%Changes will take effect when you restart Jenkins}
<j:if test="${app.lifecycle.canRestart()}">
<s:submit value="${%Restart Once No Jobs Are Running}" />
<s:submit value="${%Restart Once No Jobs Are Running}"/>
</j:if>
</form>
</div>
@@ -252,7 +285,7 @@ THE SOFTWARE.
new Ajax.Request(btn.getAttribute('url')+"/make"+(btn.checked?"Enable":"Disable")+"d", {
method: "POST",
onFailure : function(req,o) {
$('needRestart').innerHTML = req.responseText;
$('needRestart').innerHTML = req.responseText;
}
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

@@ -36,75 +36,89 @@ THE SOFTWARE.
If true, will hide all rows by default, and only the filter field will show them.
</st:attribute>
</st:documentation>
<l:layout title="${%Update Center}" permission="${app.SYSTEM_READ}">
<l:layout title="${%Update Center} - ${%Plugin Manager}" permission="${app.SYSTEM_READ}">
<l:app-bar title="${%Plugin Manager}">
<div class="jenkins-search">
<input
class="jenkins-search__input"
type="search"
id="filter-box"
value="${request.getParameter('filter')}"
placeholder="${filtered == 'true' ? '%Search' : '%Filter'}"/>
<div class="jenkins-search__icon">
<l:ionicon name="search-outline"/>
</div>
</div>
</l:app-bar>
<st:include page="sidepanel.jelly"/>
<l:main-panel>
<st:adjunct includes="hudson.PluginManager._table"/>
<div id="filter-container">
<div class="plugin-manager__search">
<input
role="searchbox"
class="plugin-manager__search-input"
type="text"
id="filter-box"
value="${request.getParameter('filter')}"
placeholder="${filtered == 'true' ? '%search' : '%filter'}" />
<span class="plugin-manager__icon-leading">
<l:svgIcon href="${resURL}/images/material-icons/svg-sprite-action-symbol.svg#ic_search_24px" />
</span>
</div>
</div>
<form method="post" action="install">
<j:set var="isUpdates" value="${attrs.page=='updates'}" />
<local:tabBar page="${attrs.page}" xmlns:local="/hudson/PluginManager" />
<table id="plugins" class="sortable pane bigtable stripped-odd">
<tr>
<l:isAdmin>
<th initialSortDir="${isUpdates?null:'up'}"
tooltip="${%Check to install the plugin}"
width="32">${%Install}</th>
</l:isAdmin>
<th initialSortDir="${isUpdates?'down':null}">${%Name}</th>
<th>${%Version}</th>
<th>${%Released}</th>
<j:if test="${isUpdates}"><th>${%Installed}</th></j:if>
</tr>
<j:set var="isUpdates" value="${attrs.page=='updates'}"/>
<local:tabBar xmlns:local="/hudson/PluginManager" page="${attrs.page}"/>
<table id="plugins" class="jenkins-table sortable">
<thead>
<tr>
<l:isAdmin>
<th initialSortDir="${isUpdates?null:'up'}"
tooltip="${%Check to install the plugin}"
width="32">${%Install}
</th>
</l:isAdmin>
<th initialSortDir="${isUpdates?'down':null}">${%Name}</th>
<th>${%Released}</th>
<j:if test="${isUpdates}">
<th>${%Installed}</th>
</j:if>
</tr>
</thead>
<j:set var="unavailableList" value="${app.updateCenter.pluginsWithUnavailableUpdates}"/>
<j:choose>
<j:when test="${!empty(list) or (isUpdates and !empty(unavailableList))}">
<j:set var="cache" value="${it.createCache()}"/>
<j:if test="${filtered == 'true'}">
<tr id="hidden-by-default-instructions">
<td class="pane" colspan="5" align="center" id="hidden-by-default-instructions-td" data-loaded-text="${%instructions}">
<td colspan="5" align="center" id="hidden-by-default-instructions-td"
data-loaded-text="${%instructions}">
${%loading}
</td>
</tr>
</j:if>
<j:forEach var="p" items="${list}">
<j:set var="installJob" value="${isUpdates ? app.updateCenter.getJob(p) : null}" />
<j:set var="installJob" value="${isUpdates ? app.updateCenter.getJob(p) : null}"/>
<j:set var="installedOk"
value="${installJob.status.success and installJob.plugin.version==p.version}" />
<tr class="${installJob!=null ? 'already-upgraded' : ''} plugin ${filtered == 'true' ? 'hidden hidden-by-default' : ''}" name="${p.displayName}">
value="${installJob.status.success and installJob.plugin.version==p.version}"/>
<tr
class="${installJob!=null ? 'already-upgraded' : ''} plugin ${filtered == 'true' ? 'hidden hidden-by-default' : ''}"
name="${p.displayName}">
<l:isAdmin>
<td class="pane" align="center" data="${isUpdates ? null : it.unscientific(p.popularity)}">
<input type="checkbox" name="plugin.${p.name}.${p.sourceId}"
checked="${installedOk ? 'checked' : null}"
disabled="${installedOk ? 'disabled' : null}"
data-compat-warning="${!p.isCompatible(cache)}" />
<td align="center" data="${isUpdates ? null : it.unscientific(p.popularity)}">
<input type="checkbox" name="plugin.${p.name}.${p.sourceId}"
checked="${installedOk ? 'checked' : null}"
disabled="${installedOk ? 'disabled' : null}"
data-compat-warning="${!p.isCompatible(cache)}"/>
</td>
</l:isAdmin>
<td class="pane details" data="${h.xmlEscape(p.displayName)}" data-id="${h.xmlEscape(p.name+':'+p.version)}">
<td class="details"
data="${h.xmlEscape(p.displayName)}"
data-id="${h.xmlEscape(p.name+':'+p.version)}">
<div>
<a href="${p.wiki}" rel="noopener noreferrer" target="_blank"><st:out value="${p.displayName}"/></a>
<a href="${p.wiki}" class="jenkins-table__link" rel="noopener noreferrer" target="_blank">
<st:out value="${p.displayName}"/>
<span class="jenkins-label jenkins-label--tertiary" style="margin-left: 1ch;">
<span class="jenkins-visually-hidden">${%Version}</span>
${p.version}
</span>
</a>
</div>
<j:if test="${!p.categories.isEmpty()}">
<div class="plugin-manager__categories">
<div class="app-plugin-manager__categories">
<j:forEach var="label" items="${p.categories}">
<j:if test="${!it.isMetaLabel(label)}">
<a href="?filter=${app.updateCenter.getCategoryDisplayName(label)}"
class="plugin-manager__category-label">
class="jenkins-table__link jenkins-table__badge">
${app.updateCenter.getCategoryDisplayName(label)}
</a>
</j:if>
@@ -112,7 +126,9 @@ THE SOFTWARE.
</div>
</j:if>
<j:if test="${p.excerpt!=null}">
<div class="excerpt"><j:out value="${p.excerpt}" /></div>
<div>
<j:out value="${p.excerpt}"/>
</div>
</j:if>
<j:if test="${!p.isCompatibleWithInstalledVersion()}">
<div class="alert alert-danger">${%compatWarning}
@@ -120,7 +136,12 @@ THE SOFTWARE.
<br/>${%parentCompatWarning}
<ul>
<j:forEach var="i" items="${p.getIncompatibleParentPlugins()}">
<li><a href="${i.wiki}"><st:out value="${i.displayName} (${i.installed.version})"/></a></li>
<li>
<a href="${i.wiki}">
<st:out
value="${i.displayName} (${i.installed.version})"/>
</a>
</li>
</j:forEach>
</ul>
</j:if>
@@ -133,29 +154,45 @@ THE SOFTWARE.
<div class="alert alert-danger">${%javaWarning(p.minimumJavaVersion)}</div>
</j:if>
<j:if test="${p.fixesSecurityVulnerabilities()}">
<div class="alert alert-warning">${%Applying this update will address security vulnerabilities in the currently installed version.}</div>
<div class="alert alert-warning">
${%Applying this update will address security vulnerabilities in the currently installed version.}
</div>
</j:if>
<j:if test="${!p.isNeededDependenciesCompatibleWithInstalledVersion(cache)}">
<div class="alert alert-danger">${%depCompatWarning}
<br/>${%parentDepCompatWarning}
<ul>
<j:forEach var="d" items="${p.getDependenciesIncompatibleWithInstalledVersion(cache)}">
<li><a href="${d.wiki}"><st:out value="${d.displayName} (${d.installed.version})"/></a></li>
<j:forEach var="d"
items="${p.getDependenciesIncompatibleWithInstalledVersion(cache)}">
<li>
<a href="${d.wiki}">
<st:out
value="${d.displayName} (${d.installed.version})"/>
</a>
</li>
</j:forEach>
</ul>
</div>
</j:if>
<j:if test="${p.isNeededDependenciesForNewerJenkins(cache)}">
<div class="alert alert-danger">${%depCoreWarning(p.getNeededDependenciesRequiredCore().toString())}</div>
<div class="alert alert-danger">
${%depCoreWarning(p.getNeededDependenciesRequiredCore().toString())}
</div>
</j:if>
<j:if test="${p.isNeededDependenciesForNewerJava()}">
<div class="alert alert-danger">${%depJavaWarning(p.getNeededDependenciesMinimumJavaVersion().toString())}</div>
<div class="alert alert-danger">
${%depJavaWarning(p.getNeededDependenciesMinimumJavaVersion().toString())}
</div>
</j:if>
<j:if test="${p.hasWarnings()}">
<div class="alert alert-danger">${%securityWarning}
<ul>
<j:forEach var="warning" items="${p.getWarnings()}">
<li><a href="${warning.url}" rel="noopener noreferrer" target="_blank">${warning.message}</a></li>
<li>
<a href="${warning.url}" rel="noopener noreferrer"
target="_blank">${warning.message}
</a>
</li>
</j:forEach>
</ul>
</div>
@@ -176,23 +213,26 @@ THE SOFTWARE.
</div>
</j:if>
</td>
<td class="pane"><st:out value="${p.version}" /></td>
<td class="pane" data="${p.releaseTimestamp.time}">
<td data="${p.releaseTimestamp.time}" class="jenkins-table__cell--no-wrap">
<j:if test="${p.releaseTimestamp != null}">
<time datetime="${h.iso8601DateTime(p.releaseTimestamp)}" tooltip="${h.localDate(p.releaseTimestamp)}">
<time datetime="${h.iso8601DateTime(p.releaseTimestamp)}"
tooltip="${h.localDate(p.releaseTimestamp)}">
${%ago(h.getTimeSpanString(p.releaseTimestamp))}
</time>
</j:if>
</td>
<j:if test="${isUpdates}">
<td class="pane">
<j:choose><j:when test="${p.installed.active}">
<st:out value="${p.installed.version}" />
</j:when><j:otherwise>
<span title="${%Inactive}">
(<st:out value="${p.installed.version}" />)
</span>
</j:otherwise></j:choose>
<td>
<j:choose>
<j:when test="${p.installed.active}">
<st:out value="${p.installed.version}"/>
</j:when>
<j:otherwise>
<span title="${%Inactive}">
(<st:out value="${p.installed.version}"/>)
</span>
</j:otherwise>
</j:choose>
</td>
</j:if>
</tr>
@@ -200,22 +240,31 @@ THE SOFTWARE.
<j:if test="${isUpdates}">
<!-- TODO this might be nicer by going through the root list element and populating it in index.jelly -->
<j:forEach var="p" items="${unavailableList}">
<j:if test="${!list.contains(p)}"><!-- only show entries for plugins not already in the real updates list -->
<j:if
test="${!list.contains(p)}"><!-- only show entries for plugins not already in the real updates list -->
<tr class="unavailable">
<l:isAdmin>
<td><!-- install --></td>
</l:isAdmin>
<td class="pane" data="${h.xmlEscape(p.displayName)}" data-id="${h.xmlEscape(p.name+':'+p.version)}">
<td class="details"
data="${h.xmlEscape(p.displayName)}"
data-id="${h.xmlEscape(p.name+':'+p.version)}">
<div>
<a href="${p.wiki}" rel="noopener noreferrer" target="_blank"><st:out value="${p.displayName}"/></a>
<a href="${p.wiki}" rel="noopener noreferrer" target="_blank">
<st:out value="${p.displayName}"/>
<span class="jenkins-label jenkins-label--tertiary" style="margin-left: 1ch;">
<span class="jenkins-visually-hidden">${%Version}</span>
${p.latest}
</span>
</a>
<span class="unavailable-label">${%unavailable}</span>
</div>
<j:if test="${!p.categories.isEmpty()}">
<div class="plugin-manager__categories">
<div class="app-plugin-manager__categories">
<j:forEach var="label" items="${p.categories}">
<j:if test="${!it.isMetaLabel(label)}">
<a href="?filter=${app.updateCenter.getCategoryDisplayName(label)}"
class="plugin-manager__category-label">
class="jenkins-table__link jenkins-table__badge">
${app.updateCenter.getCategoryDisplayName(label)}
</a>
</j:if>
@@ -223,7 +272,9 @@ THE SOFTWARE.
</div>
</j:if>
<j:if test="${p.excerpt!=null}">
<div class="excerpt"><j:out value="${p.excerpt}" /></div>
<div>
<j:out value="${p.excerpt}"/>
</div>
</j:if>
<j:if test="${it.hasLatestVersionNewerThanOffered(p)}">
<div class="alert alert-info">
@@ -231,16 +282,18 @@ THE SOFTWARE.
</div>
</j:if>
</td>
<td>${p.latest}</td>
<td><!-- timestamp --></td>
<td class="pane">
<j:choose><j:when test="${p.installed.active}">
<st:out value="${p.installed.version}" />
</j:when><j:otherwise>
<span title="${%Inactive}">
(<st:out value="${p.installed.version}" />)
</span>
</j:otherwise></j:choose>
<td>
<j:choose>
<j:when test="${p.installed.active}">
<st:out value="${p.installed.version}"/>
</j:when>
<j:otherwise>
<span title="${%Inactive}">
(<st:out value="${p.installed.version}"/>)
</span>
</j:otherwise>
</j:choose>
</td>
</tr>
</j:if>
@@ -259,21 +312,21 @@ THE SOFTWARE.
</j:choose>
</table>
<div id="bottom-sticker" >
<div id="bottom-sticker">
<div class="bottom-sticker-inner">
<l:isAdmin>
<j:if test="${!empty(list)}">
<j:if test="${!isUpdates}">
<f:submit value="${%Install without restart}" name="dynamicLoad" />
<f:submit value="${%Install without restart}" name="dynamicLoad"/>
<span style="margin-left:2em;"></span>
</j:if>
<f:submit value="${%Download now and install after restart}" />
<f:submit value="${%Download now and install after restart}"/>
</j:if>
</l:isAdmin>
<st:include page="check.jelly"/>
</div>
</div>
<d:invokeBody />
</div>
<d:invokeBody/>
</form>
</l:main-panel>
<div class="grey-anime-preload" style="display:none">
@@ -77,7 +77,9 @@ THE SOFTWARE.
<td>
<div class="jenkins-search">
<input type="search" placeholder="${%find}" class="jenkins-search__input" />
<l:svgIcon href="${resURL}/images/material-icons/svg-sprite-action-symbol.svg#ic_search_24px" class="jenkins-search__icon" />
<div class="jenkins-search__icon">
<l:ionicon name="search-outline" />
</div>
</div>
<div id="no-builds" class="jenkins-pane__information" style="${page.runs.isEmpty() ? '' : 'display: none;'}">
${%No builds}
@@ -27,10 +27,10 @@
<input name="q" placeholder="${searchPlaceholder}" id="search-box" class="main-search__input" value="${request.getParameter('q')}" role="searchbox" />
<span class="main-search__icon-leading">
<l:svgIcon href="${resURL}/images/material-icons/svg-sprite-action-symbol.svg#ic_search_24px" />
<l:ionicon name="search-outline"/>
</span>
<a href="${searchHelpUrl}" class="main-search__icon-trailing">
<l:svgIcon href="${resURL}/images/material-icons/svg-sprite-action-symbol.svg#ic_help_outline_24px" />
<l:ionicon name="help-circle-outline"/>
</a>
<div id="search-box-completion" />
@@ -93,7 +93,9 @@ THE SOFTWARE.
<j:set var="descriptor" value="${it.descriptor}" />
<div class="jenkins-search">
<s:textbox placeholder="${%Type for suggestions}" clazz="jenkins-search__input" name="from" field="copyNewItemFrom" onfocus="$('copy').click()" />
<l:svgIcon href="${resURL}/images/material-icons/svg-sprite-action-symbol.svg#ic_search_24px" class="jenkins-search__icon" />
<div class="jenkins-search__icon">
<l:ionicon name="search-outline" />
</div>
</div>
</div>
</div>
@@ -113,4 +115,4 @@ THE SOFTWARE.
<st:adjunct includes="lib.hudson.newFromList.validation" />
</s:form>
</j:jelly>
</j:jelly>
@@ -0,0 +1,44 @@
<!--
The MIT License
Copyright (c) 2021, Jenkins Contributors
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.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define">
<st:documentation>
<st:attribute name="title" use="required">
The title for the application bar
</st:attribute>
Generates a row containing the page title and an optional set of controls
</st:documentation>
<j:if test="${mode=='app-bar'}">
<div class="jenkins-app-bar">
<div class="jenkins-app-bar__content">
<h1>${title}</h1>
</div>
<div class="jenkins-app-bar__controls">
<d:invokeBody/>
</div>
</div>
</j:if>
</j:jelly>
@@ -220,8 +220,10 @@ THE SOFTWARE.
</div>
</j:if>
<div id="main-panel">
<j:set var="mode" value="main-panel" />
<d:invokeBody/>
<j:set var="mode" value="app-bar" />
<d:invokeBody/>
<j:set var="mode" value="main-panel" />
<d:invokeBody/>
</div>
</div>
@@ -191,7 +191,7 @@ public class PluginManagerInstalledGUITest {
}
private HtmlInput getEnableWidget() {
HtmlElement input = pluginRow.getCells().get(0).getElementsByTagName("input").get(0);
HtmlElement input = pluginRow.getCells().get(1).getElementsByTagName("input").get(0);
return (HtmlInput) input;
}
+10 -6
View File
@@ -4,13 +4,17 @@ import requestAnimationFrame from 'raf';
import pluginManagerAvailable from './templates/plugin-manager/available.hbs'
import pluginManager from './api/pluginManager';
var filterInput = document.getElementById('filter-box');
function applyFilter(searchQuery) {
// debounce reduces number of server side calls while typing
pluginManager.availablePluginsSearch(searchQuery.toLowerCase().trim(), 50, function (plugins) {
var pluginsTable = document.getElementById('plugins');
var tbody = pluginsTable.querySelector('tbody');
var selectedPlugins = []
var admin = pluginsTable.dataset.hasadmin === 'true';
var selectedPlugins = [];
filterInput.parentElement.classList.remove("jenkins-search--loading");
function clearOldResults() {
if (!admin) {
@@ -22,7 +26,7 @@ function applyFilter(searchQuery) {
rows.forEach(function (row) {
var input = row.querySelector('input');
if (input.checked === true) {
var pluginName = input.name.split('.')[1]
var pluginName = input.name.split('.')[1];
selectedPlugins.push(pluginName)
} else {
row.remove();
@@ -54,14 +58,14 @@ var handleFilter = function (e) {
var debouncedFilter = debounce(handleFilter, 150);
document.addEventListener("DOMContentLoaded", function () {
var filterInput = document.getElementById('filter-box');
filterInput.addEventListener('input', debouncedFilter);
filterInput.addEventListener('input', function (e) {
debouncedFilter(e);
filterInput.parentElement.classList.add("jenkins-search--loading");
});
applyFilter(filterInput.value);
setTimeout(function () {
layoutUpdateCallback.call();
}, 350)
});
@@ -1,20 +1,22 @@
{{#each plugins}}
<tr data-plugin-id="{{ this.name }}" data-plugin-version="{{ this.version }}">
{{#if @root/admin }}
<td class="pane">
<td align="center">
<input type="checkbox" name="plugin.{{ this.name }}.{{ this.sourceId }}">
</td>
{{/if}}
<td class="pane">
<td>
<div>
<a href="{{ this.wiki }}" target="_blank" rel="noopener noreferrer">
<a href="{{ this.wiki }}" class="jenkins-table__link" target="_blank" rel="noopener noreferrer">
{{ this.displayName }}
<span class="jenkins-visually-hidden">Version</span>
<span class="jenkins-label jenkins-label--tertiary" style="margin-left: 1ch;">{{ this.version }}</span>
</a>
</div>
{{#if this.categories }}
<div class="plugin-manager__categories">
<div class="app-plugin-manager__categories">
{{#each this.categories }}
<a href="?filter={{ this }}" class="plugin-manager__category-label">
<a href="?filter={{ this }}" class="jenkins-table__link jenkins-table__badge">
{{ this }}
</a>
{{/each}}
@@ -65,10 +67,7 @@
</div>
{{/if}}
</td>
<td class="pane">
{{ this.version }}
</td>
<td class="pane">
<td width="25%">
{{#if this.releaseTimestamp }}
<time datetime="{{ this.releaseTimestamp.iso8601 }}">
{{ this.releaseTimestamp.displayValue }}
+1
View File
@@ -323,5 +323,6 @@
// Colors
--green: #1c9146;
--orange: #fe820a;
--red: #df001c;
}
+3
View File
@@ -20,6 +20,7 @@ html {
@import './abstracts/mixins';
@import './base/layout-commons';
@import './base/spacing';
@import './base/style';
@import './base/typography';
@import './base/visibility-utils';
@@ -44,3 +45,5 @@ html {
@import './modules/table';
@import './modules/tabs';
@import './modules/tooltips';
@import './pages/plugin-manager';
+45
View File
@@ -0,0 +1,45 @@
@unit: 0.4rem;
each(range(0, 5), {
.jenkins-\!-margin-@{value} {
margin: @value * @unit !important;
}
.jenkins-\!-margin-top-@{value} {
margin-top: @value * @unit !important;
}
.jenkins-\!-margin-left-@{value} {
margin-left: @value * @unit !important;
}
.jenkins-\!-margin-bottom-@{value} {
margin-bottom: @value * @unit !important;
}
.jenkins-\!-margin-right-@{value} {
margin-right: @value * @unit !important;
}
});
each(range(0, 5), {
.jenkins-\!-padding-@{value} {
padding: @value * @unit !important;
}
.jenkins-\!-padding-top-@{value} {
padding-top: @value * @unit !important;
}
.jenkins-\!-padding-left-@{value} {
padding-left: @value * @unit !important;
}
.jenkins-\!-padding-bottom-@{value} {
padding-bottom: @value * @unit !important;
}
.jenkins-\!-padding-right-@{value} {
padding-right: @value * @unit !important;
}
});
+1 -121
View File
@@ -1008,103 +1008,6 @@ table.fingerprint-in-build td {
padding-right: 1em;
}
/* ========================= plugin manager ========================= */
#plugins {
margin-top: 0;
border-top: none;
}
#plugins.bigtable {
// Add this fallback because tables with hidden rows will
// not have a bottom border
border-bottom: 1px solid var(--bigtable-row-border-color);
}
#pluginsAdv tr:hover {
background-color: inherit;
}
#plugins tr.already-upgraded {
background-color: var(--plugin-manager-bg-color-already-upgraded);
}
#plugins tr.all-dependents-disabled .enable input {
pointer-events: auto;
opacity: 1;
visibility: visible;
}
#plugins tr.has-dependents input,
#plugins tr.has-disabled-dependency .enable input {
pointer-events: none;
opacity: 0.5;
}
#plugins tr.has-dependents-but-disabled .enable input {
pointer-events: auto;
opacity: 1;
visibility: visible;
}
#plugins tr.has-disabled-dependency .enable input {
opacity: 0.4;
}
#plugins tr.deleted input {
visibility: hidden !important;
}
#plugins .dependent-list,
#plugins .dependency-list {
display: none;
}
#plugins .enable-state-info,
#plugins .uninstall-state-info {
padding: 5px 20px;
max-width: 70%;
border: solid 1px var(--warning);
border-radius: 3px;
text-align: center;
}
#plugins .enable-state-info {
float: left;
}
#plugins .uninstall-state-info {
float: right;
}
#plugins .plugin-dependency-info .title,
#plugins .plugin-dependency-info .subtitle {
opacity: 0.7;
}
#plugins .plugin-dependency-info .title {
margin-bottom: 5px;
font-size: larger;
font-weight: bolder;
}
#plugins .plugin-dependency-info span {
margin: 5px 5px 0 0;
background-color: var(--primary);
display: inline-block;
padding: 0.2em 0.6em 0.3em;
font-size: var(--font-size-xs);
font-weight: 700;
line-height: 1;
color: var(--white);
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 0.25em;
}
/* ========================= repeatable elements ========================= */
.repeated-chunk {
@@ -1238,19 +1141,6 @@ textarea.rich-editor {
}
}
/* ========================= plugin update center ========================= */
#plugins .excerpt {
white-space: normal;
margin-top: 0.5em;
padding-left: 2em;
color: var(--text-color-secondary);
}
#plugins .excerpt a {
text-decoration: underline;
}
/*
* TODO(andipabst): Not used after https://github.com/jenkinsci/jenkins/pull/4299,
* remove once there are no more dependencies
@@ -1269,16 +1159,6 @@ textarea.rich-editor {
color: #f00;
}
#pluginsAdv {
padding: 0.5rem;
}
#pluginsAdv section {
&:not(:last-child) {
margin-bottom: 1.5rem;
}
}
/* ========================= progress bar ========================= */
table.progress-bar {
@@ -1538,7 +1418,7 @@ select.select-ajax-pending {
padding: 15px;
margin-bottom: 20px;
border: 1px solid transparent;
border-radius: 4px;
border-radius: var(--form-input-border-radius);
}
.alert a {
+8 -1
View File
@@ -90,4 +90,11 @@ h6,
line-height: 1.66;
margin-top: 0;
margin-bottom: 2rem;
}
}
.jenkins-label {
&--tertiary {
color: var(--text-color-secondary);
opacity: 0.7;
}
}
@@ -142,6 +142,10 @@
}
}
.jenkins-hidden {
display: none;
}
/// Hide an element visually, but have it available for screen readers
.jenkins-visually-hidden {
position: absolute;
+6
View File
@@ -9,6 +9,7 @@
justify-content: center;
flex-direction: column;
width: 100%;
min-height: 36px;
}
.jenkins-app-bar__controls {
@@ -16,6 +17,11 @@
align-items: center;
justify-content: center;
margin-left: 2rem;
min-height: 36px;
.jenkins-search {
min-width: 260px;
}
}
&--border {
+11
View File
@@ -307,11 +307,17 @@ a.yui-button.icon-button.large-button {
.jenkins-buttons-row {
display: flex;
align-items: center;
.yui-button {
margin-left: 0!important;
margin-right: 0.5rem!important;
}
.jenkins-table__button {
margin-left: 0!important;
margin-right: 1.5rem!important;
}
&--invert {
justify-content: flex-end;
@@ -320,5 +326,10 @@ a.yui-button.icon-button.large-button {
margin-left: 0.5rem!important;
margin-right: 0!important;
}
.jenkins-table__button {
margin-left: 1.5rem!important;
margin-right: 0!important;
}
}
}
+105
View File
@@ -356,3 +356,108 @@
}
}
}
.jenkins-toggle-switch {
position: relative;
display: inline-block;
}
.jenkins-toggle-switch input {
position: absolute;
width: 1px;
height: 1px;
// If margin is set to a negative value it can cause text to be announced in
// the wrong order in VoiceOver for OSX
margin: 0;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
&:disabled {
& + label {
cursor: default;
&::before {
opacity: 0.25 !important;
background-color: var(--input-border) !important;
box-shadow: none !important;
}
&::after {
opacity: 0.5 !important;
}
}
}
&:checked + label::before {
background-color: var(--focus-input-border);
}
&:checked + label::after {
left: 25px;
color: var(--focus-input-border);
transform: rotate(0deg);
}
&:active + label::before, &:focus + label::before {
box-shadow: 0 0 0 5px var(--focus-input-glow);
}
}
.jenkins-toggle-switch label {
position: relative;
display: flex;
align-items: flex-start;
justify-content: flex-start;
margin: 0;
cursor: pointer;
line-height: 30px;
font-weight: bold;
&::before {
display: inline-block;
content: "";
position: relative;
min-width: 50px;
min-height: 30px;
background: var(--input-border);
border-radius: 19px;
transition: 0.2s ease;
margin-right: 15px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05), 0 0 0 10px transparent;
}
&::after {
content: "✓";
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 5px;
left: 5px;
width: 20px;
height: 20px;
background: var(--background);
border-radius: 19px;
color: transparent;
transition: 0.2s ease;
transform: rotate(-90deg);
font-size: 12px;
padding-top: 2px;
box-shadow: -1px 0 0 rgba(0, 0, 0, 0.1);
}
&:hover::before {
background-color: var(--input-border-hover);
}
&:active, &:focus {
&::before {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05), 0 0 0 5px var(--focus-input-glow);
}
}
}
+12 -3
View File
@@ -111,8 +111,8 @@ a.page-header__brand-link {
line-height: var(--line-height-base);
font-weight: bold;
color: var(--search-input-color);
border-radius: var(--header-item-border-radius);
border: 3px solid var(--header-search-border);
border-radius: var(--form-input-border-radius);
border: 2px solid var(--header-search-border);
outline: none;
&::placeholder {
@@ -130,7 +130,6 @@ a.page-header__brand-link {
display: inline-flex;
height: 2.5rem;
width: 2.5rem;
padding: 0.5rem;
justify-content: center;
align-items: center;
background: transparent;
@@ -139,6 +138,11 @@ a.page-header__brand-link {
.main-search__icon-leading {
left: 0;
pointer-events: none;
svg {
width: 16px;
height: 16px;
}
}
.main-search__icon-trailing {
@@ -158,6 +162,11 @@ a.page-header__brand-link {
&:active {
fill: var(--header-link-bg-classic-active);
}
svg {
width: 20px;
height: 20px;
}
}
#search-box-completion {
+17
View File
@@ -65,6 +65,7 @@
&:first-of-type {
border-radius: var(--table-body-radius) 0 0 var(--table-body-radius);
padding-left: calc(var(--table-padding) * 2);
padding-right: var(--table-padding);
}
&:last-of-type {
@@ -110,6 +111,10 @@
width: 60px;
}
&__cell--no-wrap {
white-space: nowrap;
}
&__button {
display: inline-flex;
}
@@ -155,6 +160,10 @@
}
}
&__button, &__link {
color: var(--link-color)!important;
}
&__button, .sortheader, &__link {
align-items: center;
justify-content: center;
@@ -206,6 +215,14 @@
&--green {
color: var(--green)!important;
}
&--orange {
color: var(--orange)!important;
}
&--red {
color: var(--red)!important;
}
}
&__badge {
+100
View File
@@ -0,0 +1,100 @@
#plugins {
.app-plugin-manager__categories {
margin: 0.5rem 0;
a {
margin: 0 1.25rem 0 0 !important;
font-size: 0.75rem;
}
}
tr.already-upgraded {
background-color: var(--plugin-manager-bg-color-already-upgraded);
}
tr.all-dependents-disabled .enable input,
tr.all-dependents-disabled .enable button,
tr.all-dependents-disabled .enable .jenkins-toggle-switch label {
pointer-events: auto;
opacity: 1;
visibility: visible;
}
tr.has-dependents input,
tr.has-disabled-dependency .enable input,
tr.has-disabled-dependency .enable .jenkins-toggle-switch label,
tr.has-dependents button,
tr.has-dependents .enable button,
tr.has-dependents .enable .jenkins-toggle-switch label {
pointer-events: none;
opacity: 0.25;
}
tr.has-dependents-but-disabled .enable input,
tr.has-dependents-but-disabled .enable button,
tr.has-dependents-but-disabled .enable .jenkins-toggle-switch label {
pointer-events: auto;
opacity: 1;
visibility: visible;
}
tr.has-disabled-dependency .enable input,
tr.has-disabled-dependency .enable button,
tr.has-disabled-dependency .enable .jenkins-toggle-switch label {
opacity: 0.4;
}
tr.deleted input,
tr.deleted button,
tr.deleted .jenkins-toggle-switch label {
visibility: hidden !important;
}
.dependent-list,
.dependency-list {
display: none;
}
.enable-state-info,
.uninstall-state-info {
padding: 5px 20px;
max-width: 70%;
border: solid 1px var(--warning);
border-radius: 3px;
text-align: center;
}
.enable-state-info {
float: left;
}
.uninstall-state-info {
float: right;
}
.plugin-dependency-info .title,
.plugin-dependency-info .subtitle {
opacity: 0.7;
}
.plugin-dependency-info .title {
margin-bottom: 5px;
font-size: larger;
font-weight: bolder;
}
.plugin-dependency-info span {
margin: 5px 5px 0 0;
background-color: var(--primary);
display: inline-block;
padding: 0.2em 0.6em 0.3em;
font-size: var(--font-size-xs);
font-weight: 700;
line-height: 1;
color: var(--white);
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 0.25em;
}
}
-105
View File
@@ -1,105 +0,0 @@
#filter-box {
padding-left: 35px;
width: 15em;
}
time {
white-space: nowrap;
}
#filter-container {
margin: 1em 0;
font-size: 1em;
}
.plugin-manager__categories {
margin-top: 0.25em;
}
.plugin-manager__category-label:link,
.plugin-manager__category-label:visited {
display: inline-block;
border: 1px solid #ccc;
border: 1px solid var(--plugin-manager-category-link-border-color);
background-color: #fbfbfb;
background-color: var(--plugin-manager-category-link-bg-color);
color: #333;
color: var(--plugin-manager-category-text-color);
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
padding: 0 0.5rem;
margin: 0 0.25rem 0.25rem 0;
text-decoration: none;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
transition: all 0.15s ease-in-out;
}
.plugin-manager__category-label:hover,
.plugin-manager__category-label:focus,
.plugin-manager__category-label:active {
background-color: #f2f2f2;
background-color: var(--plugin-manager-category-link-bg-color--hover);
border-color: #333;
border-color: var(--plugin-manager-category-link-border-color--hover);
color: #333;
color: var(--plugin-manager-category-link-color--hover);
}
.plugin-manager__search-input {
position: static;
padding: 0.25rem 0.1rem 0.25rem 2.5rem;
margin: 0;
font-size: 1rem;
line-height: 1.5;
color: #333;
color: var(--text-color);
border-radius: 4px;
border: 1px solid grey;
outline: none;
}
.plugin-manager__search {
position: relative;
}
.plugin-manager__icon-leading {
position: absolute;
left: -2px;
top: -3px;
font-size: 15px;
padding: 0.5rem;
}
.main-search__icon-leading {
left: 0;
pointer-events: none;
}
.hidden-by-default {
}
.hidden {
display: none;
}
#plugins tr.unavailable {
background-color: #f4f4f4;
background-color: var(--plugin-manager-unavailable-bg-color);
}
tr.unavailable span.unavailable-label {
display: inline-block;
border: 1px solid #666;
color: #333;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
padding: 0 0.5rem;
margin: 0.25rem 0.5rem;
text-decoration: none;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Close Circle</title><path d="M448 256c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M320 320L192 192M192 320l128-128"/></svg>

After

Width:  |  Height:  |  Size: 415 B

@@ -0,0 +1,5 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M64 256C64 150 150 64 256 64C362 64 448 150 448 256C448 362 362 448 256 448C150 448 64 362 64 256Z" stroke="currentColor" stroke-width="32" stroke-miterlimit="10"/>
<path d="M170.46 197.85L181.83 184.62C197.379 168.34 217.817 157.581 240.039 153.978C262.261 150.376 285.052 154.126 304.948 164.659C324.844 175.192 340.758 191.933 350.271 212.336C359.783 232.74 362.375 255.691 357.652 277.702C352.929 299.714 341.15 319.581 324.104 334.286C307.058 348.991 285.677 357.73 263.211 359.173C240.746 360.617 218.423 354.687 199.635 342.285C180.847 329.882 166.621 311.686 159.12 290.46" stroke="currentColor" stroke-width="32" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M144.68 162C145.799 162.005 146.905 162.232 147.935 162.668C148.965 163.104 149.898 163.74 150.68 164.54L210.22 224.08C211.428 225.284 212.252 226.819 212.586 228.491C212.921 230.164 212.751 231.898 212.099 233.473C211.446 235.049 210.341 236.396 208.922 237.342C207.503 238.289 205.835 238.793 204.13 238.79H144.59C142.304 238.79 140.111 237.882 138.495 236.265C136.878 234.649 135.97 232.456 135.97 230.17V170.61C135.967 169.472 136.19 168.344 136.626 167.293C137.062 166.241 137.702 165.287 138.509 164.484C139.317 163.681 140.275 163.047 141.329 162.617C142.383 162.187 143.512 161.971 144.65 161.98L144.68 162Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Help Circle</title><path d="M256 80a176 176 0 10176 176A176 176 0 00256 80z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path d="M200 202.29s.84-17.5 19.57-32.57C230.68 160.77 244 158.18 256 158c10.93-.14 20.69 1.67 26.53 4.45 10 4.76 29.47 16.38 29.47 41.09 0 26-17 37.81-36.37 50.8S251 281.43 251 296" fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="28"/><circle cx="250" cy="348" r="20" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 581 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Search</title><path d="M221.09 64a157.09 157.09 0 10157.09 157.09A157.1 157.1 0 00221.09 64z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M338.29 338.29L448 448"/></svg>

After

Width:  |  Height:  |  Size: 393 B

-1
View File
@@ -16,7 +16,6 @@ module.exports = (env, argv) => ({
],
"plugin-manager-ui": [
path.join(__dirname, "src/main/js/plugin-manager-ui.js"),
path.join(__dirname, "src/main/less/plugin-manager-ui.less"),
],
"add-item": [
path.join(__dirname, "src/main/js/add-item.js"),