Skip to content

Commit

Permalink
Move global configuration to Appearance category (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
strangelookingnerd committed Jan 3, 2024
1 parent 2625833 commit 924b26a
Show file tree
Hide file tree
Showing 23 changed files with 646 additions and 380 deletions.
1 change: 1 addition & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Release notes are recorded in https://github.com/jenkinsci/custom-folder-icon-pl

This version requires Jenkins 2.357 and above in order to support the transition to https://www.jenkins.io/blog/2022/06/28/require-java-11/[Java 11].

* Version 2.11 moved the <<Global Configuration>> to the _Appearance_ configuration
* Version 2.10 enables users to select the jobs to be considered for the combined build status in `BuildStatusFolderIcon`.

.Changes in previous versions ...
Expand Down
Binary file modified images/global-configuration.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 4 additions & 35 deletions src/main/java/jenkins/plugins/foldericon/CustomFolderIcon.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static jenkins.plugins.foldericon.CustomFolderIconConfiguration.PLUGIN_PATH;
import static jenkins.plugins.foldericon.CustomFolderIconConfiguration.USER_CONTENT_PATH;

/**
* A Custom Folder Icon.
*
Expand All @@ -57,8 +60,6 @@ public class CustomFolderIcon extends FolderIcon {

private static final Logger LOGGER = Logger.getLogger(CustomFolderIcon.class.getName());

private static final String PLUGIN_PATH = "customFolderIcons";
private static final String USER_CONTENT_PATH = "userContent";
private static final String DEFAULT_ICON_PATH = "plugin/custom-folder-icon/icons/default.png";

private final String foldericon;
Expand All @@ -80,6 +81,7 @@ public CustomFolderIcon(String foldericon) {
*
* @return all the icons that have been uploaded, sorted descending by {@link FilePath#lastModified()}.
*/
@NonNull
public static Set<String> getAvailableIcons() {
try {
FilePath iconDir = Jenkins.get().getRootPath().child(USER_CONTENT_PATH).child(PLUGIN_PATH);
Expand Down Expand Up @@ -190,39 +192,6 @@ public HttpResponse doUploadIcon(StaplerRequest req, @AncestorInPath Item item)
return HttpResponses.errorWithoutStack(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
}
}

/**
* Cleanup unused icons.
*
* @param req the request
* @return OK
*/
@RequirePOST
public HttpResponse doCleanup(@SuppressWarnings("unused") StaplerRequest req) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);

FilePath iconDir = Jenkins.get().getRootPath().child(USER_CONTENT_PATH).child(PLUGIN_PATH);

Set<String> existingIcons = getAvailableIcons();

Set<String> usedIcons = Jenkins.get().getAllItems(AbstractFolder.class).stream()
.filter(folder -> folder.getIcon() instanceof CustomFolderIcon)
.map(folder -> ((CustomFolderIcon) folder.getIcon()).getFoldericon())
.collect(Collectors.toSet());

if (usedIcons.isEmpty() || existingIcons.removeAll(usedIcons)) {
for (String icon : existingIcons) {
try {
if (!iconDir.child(icon).delete()) {
LOGGER.warning(() -> "Unable to delete unused Folder Icon '" + icon + "'!");
}
} catch (IOException | InterruptedException ex) {
LOGGER.log(Level.WARNING, ex, () -> "Unable to delete unused Folder Icon '" + icon + "'!");
}
}
}
return HttpResponses.ok();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* The MIT License
*
* Copyright (c) 2023 strangelookingnerd
*
* 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.
*/

package jenkins.plugins.foldericon;

import com.cloudbees.hudson.plugins.folder.AbstractFolder;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.PageDecorator;
import jenkins.appearance.AppearanceCategory;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.Jenkins;
import org.apache.commons.io.FileUtils;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.interceptor.RequirePOST;

import java.io.IOException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;


/**
* The global Custom Folder Icon configuration.
*
* @author strangelookingnerd
*/
@Extension
public class CustomFolderIconConfiguration extends PageDecorator {

private static final Logger LOGGER = Logger.getLogger(CustomFolderIconConfiguration.class.getName());

public static final String PLUGIN_PATH = "customFolderIcons";

public static final String USER_CONTENT_PATH = "userContent";

@NonNull
@Override
public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(AppearanceCategory.class);
}

/**
* Get human-readable disk-usage of all icons.
*
* @return OK
*/
@SuppressWarnings("unused")
@NonNull
public String getDiskUsage() {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);

FilePath iconDir = Jenkins.get().getRootPath().child(USER_CONTENT_PATH).child(PLUGIN_PATH);

Set<String> existingIcons = CustomFolderIcon.getAvailableIcons();

long total = 0L;

for (String icon : existingIcons) {
try {
total += iconDir.child(icon).length();
} catch (IOException | InterruptedException ex) {
LOGGER.log(Level.WARNING, ex, () -> "Unable to determine size for Folder Icon '" + icon + "'!");
}
}
return FileUtils.byteCountToDisplaySize(total);
}

/**
* Cleanup unused icons.
*
* @param req the request
* @return OK
*/
@RequirePOST
public HttpResponse doCleanup(@SuppressWarnings("unused") StaplerRequest req) {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);

FilePath iconDir = Jenkins.get().getRootPath().child(USER_CONTENT_PATH).child(PLUGIN_PATH);

Set<String> existingIcons = CustomFolderIcon.getAvailableIcons();

Set<String> usedIcons = Jenkins.get().getAllItems(AbstractFolder.class).stream()
.filter(folder -> folder.getIcon() instanceof CustomFolderIcon)
.map(folder -> ((CustomFolderIcon) folder.getIcon()).getFoldericon())
.collect(Collectors.toSet());

if (usedIcons.isEmpty() || existingIcons.removeAll(usedIcons)) {
for (String icon : existingIcons) {
try {
if (!iconDir.child(icon).delete()) {
LOGGER.warning(() -> "Unable to delete unused Folder Icon '" + icon + "'!");
}
} catch (IOException | InterruptedException ex) {
LOGGER.log(Level.WARNING, ex, () -> "Unable to delete unused Folder Icon '" + icon + "'!");
}
}
}
return HttpResponses.ok();
}
}
2 changes: 1 addition & 1 deletion src/main/resources/index.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ SOFTWARE.

<?jelly escape-by-default='true'?>
<div>
This plugin extends the <a href="https://github.com/jenkinsci/cloudbees-folder-plugin" target="_blank">Folders Plugin</a> to provide custom icons for folders.
This plugin extends the <a href="https://github.com/jenkinsci/cloudbees-folder-plugin" target="_blank">Folders Plugin</a> to provide custom icons for folders.<br/>
You can upload your own images, use predefined icons and emojis or the combined build status of the jobs within a folder as icon.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@
-->

<div>
You can select the jobs to be considered for the combined build status.</br>If you select none then all jobs are considered.
You can select the jobs to be considered for the combined build status.<br/>
If you select none then all jobs are considered.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ SOFTWARE.
</div>
</f:entry>
<j:if test="${not empty customicons}">
<f:advanced title="${%AvailableIcons}">
<f:advanced title="${%AvailableIcons}${not empty customicons ? ' (' + customicons.size() + ')' : ''}">
<j:forEach var="icon" items="${customicons}">
<a tooltip="${icon}">
<img class="custom-icon-selection" src="${rootURL}/userContent/customFolderIcons/${icon}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
-->

<div>
Upload an image to represent the folder.</br>
Use the "Browse..." button to select an image from disk.</br>
You can crop the image to the desired result and upload it using the "Apply" button.</br>
The file name will be randomized during upload.</br>
You can also select an image from the list of the already available icons.</br>
</br>
The recommended minimum size of the image is 128x128 pixels.</br>
Upload an image to represent the folder.<br/>
Use the "Browse..." button to select an image from disk.<br/>
You can crop the image to the desired result and upload it using the "Apply" button.<br/>
The file name will be randomized during upload.<br/>
You can also select an image from the list of the already available icons.<br/>
<br/>
The recommended minimum size of the image is 128x128 pixels.<br/>
The image will be deleted automatically if this folder is being deleted - unless of course it is still used by another folder.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,29 @@ SOFTWARE.
-->

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout" xmlns:st="jelly:stapler" xmlns:f="/lib/form">
<script src="${rootURL}/plugin/custom-folder-icon/js/custom-icon-global.js" type="text/javascript" />
<j:invokeStatic var="customicons" method="getAvailableIcons"
className="jenkins.plugins.foldericon.CustomFolderIcon" />
<f:section title="${%CustomFolderIcons}">
<f:entry title="${%CleanupIcons}" help="${descriptor.getHelpFile('global-cleanup')}">
<input id="custom-icon-cleanup" type="button" value="${%Cleanup}"
onclick="doCustomIconCleanup('${%CleanupSuccess}', '${%CleanupFailed}')" />
<f:entry>
${%DiskUsage}
<st:nbsp />
${instance.getDiskUsage()}
</f:entry>
<j:if test="${not empty customicons}">
<f:advanced title="${%AvailableIcons}${not empty customicons ? ' (' + customicons.size() + ')' : ''}">
<j:forEach var="icon" items="${customicons}">
<l:icon src="${rootURL}/userContent/customFolderIcons/${icon}" title="${icon}"
tooltip="${icon}"
class="icon-md" />
<st:nbsp />
</j:forEach>
</f:advanced>
<f:entry help="${descriptor.getHelpFile('cleanup')}">
<input id="custom-icon-cleanup" type="button" value="${%CleanupIcons}"
onclick="doCustomIconCleanup('${%CleanupSuccess}', '${%CleanupFailed}')" />
</f:entry>
</j:if>
</f:section>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@
# 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.

CustomFolderIcons=Custom Folder Icons
CleanupIcons=Cleanup Icons
Cleanup=Cleanup
AvailableIcons=Show all available icons
CleanupIcons=Cleanup unused icons
IconCount=Number of icons:
DiskUsage=Disk usage of icons:
CleanupSuccess=Successfully removed all unused icon images
CleanupFailed=Failed to removed unused icon images:
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ SOFTWARE.
<l:icon id="emoji-preview" src="${emojis.get(instance.emoji ?: 'sloth')}" title="${icon.key}"
class="icon-xlg" />
</f:entry>
<f:advanced title="${%AvailableIcons}">
<f:advanced title="${%AvailableIcons}${not empty emojis ? ' (' + emojis.size() + ')' : ''}">
<j:forEach var="icon" items="${emojis}">
<a id="select-emoji-${icon.key}" class="emoji-icon-selection" onclick="setEmojiIcon('${icon.key}')">
<l:icon id="emoji-icon-${icon.key}" src="${icon.value}" title="${icon.key}" tooltip="${icon.key}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ SOFTWARE.
title="${icon.key}"
class="icon-xlg" />
</f:entry>
<f:advanced title="${%AvailableIcons}">
<f:advanced title="${%AvailableIcons}${not empty fontawesomes ? ' (' + fontawesomes.size() + ')' : ''}">
<j:invokeStatic var="styles" method="values"
className="io.jenkins.plugins.fontawesome.SvgTag$FontAwesomeStyle" />
<j:forEach var="style" items="${styles}">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ SOFTWARE.
<l:icon id="ionicon-preview" src="${ionicons.get(instance.ionicon ?: 'jenkins')}" title="${icon.key}"
class="icon-xlg" />
</f:entry>
<f:advanced title="${%AvailableIcons}">
<f:advanced title="${%AvailableIcons}${not empty ionicons ? ' (' + ionicons.size() + ')' : ''}">
<j:forEach var="icon" items="${ionicons}">
<a id="select-ionicon-${icon.key}" class="ionicon-icon-selection" onclick="setIoniconIcon('${icon.key}')">
<l:icon id="ionicon-icon-${icon.key}" src="${icon.value}" title="${icon.key}" tooltip="${icon.key}"
Expand Down
6 changes: 3 additions & 3 deletions src/main/webapp/js/custom-icon-global.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@
* @param {string} errorMessage - The error message.
*/
function doCustomIconCleanup(successMessage, errorMessage) {
fetch(rootURL + "/descriptor/jenkins.plugins.foldericon.CustomFolderIcon/cleanup", {
fetch(rootURL + "/descriptor/jenkins.plugins.foldericon.CustomFolderIconConfiguration/cleanup", {
method: "post",
headers: crumb.wrap({}),
}).then(rsp => {
let button = document.getElementById("custom-icon-cleanup")
if (rsp.ok) {
hoverNotification(successMessage, button.parentNode);
hoverNotification(successMessage, button.parentNode.parentNode);
} else {
hoverNotification(errorMessage + " " + rsp.status + " - " + rsp.statusText, button.parentNode);
hoverNotification(errorMessage + " " + rsp.status + " - " + rsp.statusText, button.parentNode.parentNode);
}
}).catch(error => {
console.error(error);
Expand Down

0 comments on commit 924b26a

Please sign in to comment.