mirror of
https://github.com/linuxserver/fleet.git
synced 2026-02-20 05:11:08 +08:00
Core Meta
- Added logo uploads.
This commit is contained in:
parent
b8f64bd4f2
commit
bd1946f84e
3
.gitignore
vendored
3
.gitignore
vendored
@ -28,6 +28,7 @@ build/
|
||||
out/
|
||||
*.iml
|
||||
config/fleet.properties
|
||||
/config/fleet_static/*
|
||||
src/main/resources/assets/js/all*.js
|
||||
src/main/resources/assets/css/all*.css
|
||||
src/main/resources/log4j2.xml
|
||||
@ -35,4 +36,4 @@ src/main/resources/log4j2.xml
|
||||
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
.settings/
|
||||
|
||||
@ -29,6 +29,7 @@ import io.linuxserver.fleet.v2.client.docker.queue.DockerApiDelegate;
|
||||
import io.linuxserver.fleet.v2.db.DefaultImageDAO;
|
||||
import io.linuxserver.fleet.v2.db.DefaultScheduleDAO;
|
||||
import io.linuxserver.fleet.v2.db.DefaultUserDAO;
|
||||
import io.linuxserver.fleet.v2.file.FileManager;
|
||||
import io.linuxserver.fleet.v2.key.ImageKey;
|
||||
import io.linuxserver.fleet.v2.service.ImageService;
|
||||
import io.linuxserver.fleet.v2.service.ScheduleService;
|
||||
@ -53,10 +54,12 @@ public class FleetAppController extends AbstractAppController implements Service
|
||||
private final SynchronisationService syncService;
|
||||
private final UserService userService;
|
||||
private final AuthenticationDelegate authenticationDelegate;
|
||||
private final FileManager fileManager;
|
||||
|
||||
public FleetAppController() {
|
||||
|
||||
imageService = new ImageService(new DefaultImageDAO(getDatabaseProvider()));
|
||||
fileManager = new FileManager(this);
|
||||
imageService = new ImageService(this, new DefaultImageDAO(getDatabaseProvider()));
|
||||
scheduleService = new ScheduleService(this, new DefaultScheduleDAO(getDatabaseProvider()));
|
||||
dockerApiDelegate = new DockerApiDelegate(this);
|
||||
syncService = new SynchronisationService(this);
|
||||
@ -156,6 +159,11 @@ public class FleetAppController extends AbstractAppController implements Service
|
||||
return userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileManager getFileManager() {
|
||||
return fileManager;
|
||||
}
|
||||
|
||||
public final AuthenticationResult authenticateUser(final String username, final String password) {
|
||||
return authenticationDelegate.authenticate(username, password);
|
||||
}
|
||||
|
||||
@ -19,6 +19,11 @@ package io.linuxserver.fleet.core;
|
||||
|
||||
public interface FleetRuntime {
|
||||
|
||||
/**
|
||||
* If set will switch specific properties to allow more streamlined development
|
||||
*/
|
||||
boolean DEV_MODE = System.getProperty("enable.dev") != null;
|
||||
|
||||
/**
|
||||
* Base directory for the config file.
|
||||
*/
|
||||
|
||||
@ -53,6 +53,7 @@ class PropertiesLoader extends BaseRuntimeLoader {
|
||||
Properties properties = new Properties();
|
||||
properties.load(new FileInputStream(FleetRuntime.CONFIG_BASE + "/fleet.properties"));
|
||||
properties.load(Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream("version.properties")));
|
||||
properties.setProperty("fleet.static.dirname", "fleet_static");
|
||||
|
||||
this.properties = new AppProperties(properties);
|
||||
|
||||
@ -88,7 +89,7 @@ class PropertiesLoader extends BaseRuntimeLoader {
|
||||
|
||||
private boolean createStaticFileDirectory() {
|
||||
|
||||
File staticFilesDir = new File(FleetRuntime.CONFIG_BASE + "/fleet_static");
|
||||
File staticFilesDir = new File(properties.getStaticFilesPath().toString());
|
||||
|
||||
if (staticFilesDir.exists()) {
|
||||
return true;
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package io.linuxserver.fleet.core;
|
||||
|
||||
import io.linuxserver.fleet.v2.file.FileManager;
|
||||
import io.linuxserver.fleet.v2.service.ImageService;
|
||||
import io.linuxserver.fleet.v2.service.ScheduleService;
|
||||
import io.linuxserver.fleet.v2.service.SynchronisationService;
|
||||
@ -31,4 +32,6 @@ public interface ServiceProvider {
|
||||
ScheduleService getScheduleService();
|
||||
|
||||
UserService getUserService();
|
||||
|
||||
FileManager getFileManager();
|
||||
}
|
||||
|
||||
@ -17,6 +17,10 @@
|
||||
|
||||
package io.linuxserver.fleet.core.config;
|
||||
|
||||
import io.linuxserver.fleet.core.FleetRuntime;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Properties;
|
||||
|
||||
public class AppProperties {
|
||||
@ -59,6 +63,10 @@ public class AppProperties {
|
||||
return getStringProperty("fleet.database.password");
|
||||
}
|
||||
|
||||
public final Path getStaticFilesPath() {
|
||||
return Paths.get(FleetRuntime.CONFIG_BASE, getStringProperty("fleet.static.dirname")).toAbsolutePath();
|
||||
}
|
||||
|
||||
public String getAppSecret() {
|
||||
|
||||
String secret = getStringProperty("fleet.admin.secret");
|
||||
|
||||
121
src/main/java/io/linuxserver/fleet/v2/file/FileManager.java
Normal file
121
src/main/java/io/linuxserver/fleet/v2/file/FileManager.java
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (c) 2020 LinuxServer.io
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.v2.file;
|
||||
|
||||
import io.linuxserver.fleet.core.FleetAppController;
|
||||
import io.linuxserver.fleet.v2.key.ImageKey;
|
||||
import io.linuxserver.fleet.v2.service.AbstractAppService;
|
||||
import io.linuxserver.fleet.v2.types.FilePathDetails;
|
||||
import io.linuxserver.fleet.v2.types.internal.ImageAppLogo;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class FileManager extends AbstractAppService {
|
||||
|
||||
private static final String imageDirName = "images";
|
||||
|
||||
private final String publicSafeImagesDir;
|
||||
private final Path staticImagesDir;
|
||||
|
||||
public FileManager(final FleetAppController controller) {
|
||||
super(controller);
|
||||
staticImagesDir = Paths.get(getProperties().getStaticFilesPath().toString(), imageDirName);
|
||||
publicSafeImagesDir = "/" + imageDirName;
|
||||
makeImageUploadDir();
|
||||
}
|
||||
|
||||
public final FilePathDetails saveImageLogo(final ImageAppLogo logo) {
|
||||
|
||||
if (logo.getMimeType().startsWith("image/")) {
|
||||
|
||||
try {
|
||||
|
||||
final FilePathDetails filePathDetails = makeFilePathDetails(logo);
|
||||
|
||||
final File logoFile = new File(filePathDetails.getFullAbsolutePathWithFileName());
|
||||
if (logoFile.exists()) {
|
||||
|
||||
final boolean deleted = logoFile.delete();
|
||||
if (!deleted) {
|
||||
|
||||
getLogger().warn("Unable to delete file: " + logoFile);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
boolean created = logoFile.createNewFile();
|
||||
if (created) {
|
||||
|
||||
writeDataToFile(logo, logoFile);
|
||||
return filePathDetails;
|
||||
|
||||
} else {
|
||||
getLogger().warn("Unable to delete file: " + logoFile);
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
getLogger().error("Unable to create logo file.", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Disallowed mimeType for file: " + logo.getMimeType());
|
||||
}
|
||||
}
|
||||
|
||||
private FilePathDetails makeFilePathDetails(final ImageAppLogo logo) {
|
||||
|
||||
return new FilePathDetails(makePathSafeFileName(logo.getImageKey()) + logo.getFileExtension(),
|
||||
staticImagesDir.toString(),
|
||||
publicSafeImagesDir);
|
||||
}
|
||||
|
||||
private String makePathSafeFileName(final ImageKey key) {
|
||||
return key.getAsRepositoryAndImageName().replace("/", "_");
|
||||
}
|
||||
|
||||
private void writeDataToFile(final ImageAppLogo logo, final File logoFile) throws IOException {
|
||||
|
||||
final byte[] buffer = new byte[logo.getRawDataStream().available()];
|
||||
int read = logo.getRawDataStream().read(buffer);
|
||||
if (read != -1) {
|
||||
getLogger().warn("Not all file content has been read! File may be corrupted once saved to disk");
|
||||
}
|
||||
|
||||
try (final OutputStream out = new FileOutputStream(logoFile)) {
|
||||
out.write(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeImageUploadDir() {
|
||||
|
||||
final File imageDir = new File(staticImagesDir.toString());
|
||||
if (!imageDir.exists()) {
|
||||
|
||||
getLogger().info("Creating new image directory for uploaded logos");
|
||||
final boolean created = imageDir.mkdir();
|
||||
if (!created) {
|
||||
throw new RuntimeException("Unable to create uploaded file dir. Check permissions");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@
|
||||
package io.linuxserver.fleet.v2.service;
|
||||
|
||||
import io.linuxserver.fleet.core.FleetAppController;
|
||||
import io.linuxserver.fleet.core.config.AppProperties;
|
||||
import io.linuxserver.fleet.v2.LoggerOwner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -36,6 +37,10 @@ public class AbstractAppService implements LoggerOwner {
|
||||
return controller;
|
||||
}
|
||||
|
||||
public final AppProperties getProperties() {
|
||||
return getController().getAppProperties();
|
||||
}
|
||||
|
||||
public final Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@ -17,10 +17,12 @@
|
||||
|
||||
package io.linuxserver.fleet.v2.service;
|
||||
|
||||
import io.linuxserver.fleet.core.FleetAppController;
|
||||
import io.linuxserver.fleet.db.query.InsertUpdateResult;
|
||||
import io.linuxserver.fleet.dockerhub.util.DockerTagFinder;
|
||||
import io.linuxserver.fleet.v2.cache.RepositoryCache;
|
||||
import io.linuxserver.fleet.v2.db.ImageDAO;
|
||||
import io.linuxserver.fleet.v2.file.FileManager;
|
||||
import io.linuxserver.fleet.v2.key.ImageKey;
|
||||
import io.linuxserver.fleet.v2.key.ImageLookupKey;
|
||||
import io.linuxserver.fleet.v2.key.RepositoryKey;
|
||||
@ -34,24 +36,25 @@ import io.linuxserver.fleet.v2.types.meta.ItemSyncSpec;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ImageService {
|
||||
public class ImageService extends AbstractAppService {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ImageService.class);
|
||||
|
||||
private final ImageDAO imageDAO;
|
||||
private final FileManager fileManager;
|
||||
private final RepositoryCache repositoryCache;
|
||||
private final TemplateMerger templateMerger;
|
||||
|
||||
public ImageService(final ImageDAO imageDAO) {
|
||||
public ImageService(final FleetAppController controller, final ImageDAO imageDAO) {
|
||||
super(controller);
|
||||
|
||||
this.imageDAO = imageDAO;
|
||||
this.fileManager = controller.getFileManager();
|
||||
this.repositoryCache = new RepositoryCache();
|
||||
this.templateMerger = new TemplateMerger();
|
||||
|
||||
@ -246,7 +249,11 @@ public class ImageService {
|
||||
|
||||
String appLogoPath = image.getMetaData().getAppImagePath();
|
||||
if (null != generalInfoUpdateRequest.getImageAppLogo()) {
|
||||
// TODO: Write FileManager
|
||||
|
||||
final FilePathDetails filePathDetails = fileManager.saveImageLogo(generalInfoUpdateRequest.getImageAppLogo());
|
||||
if (null != filePathDetails) {
|
||||
appLogoPath = filePathDetails.getPublicSafePathWithFileName();
|
||||
}
|
||||
}
|
||||
|
||||
final ImageCoreMeta coreMeta = new ImageCoreMeta(appLogoPath,
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2020 LinuxServer.io
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.v2.types;
|
||||
|
||||
public class FilePathDetails {
|
||||
|
||||
private final String fileNameWithExtension;
|
||||
private final String fullAbsolutePath;
|
||||
private final String publicSafePath;
|
||||
|
||||
public FilePathDetails(final String fileNameWithExtension,
|
||||
final String fullAbsolutePath,
|
||||
final String publicSafePath) {
|
||||
|
||||
this.fileNameWithExtension = fileNameWithExtension;
|
||||
this.fullAbsolutePath = fullAbsolutePath;
|
||||
this.publicSafePath = publicSafePath;
|
||||
}
|
||||
|
||||
public final String getFileNameWithExtension() {
|
||||
return fileNameWithExtension;
|
||||
}
|
||||
|
||||
public final String getFullAbsolutePath() {
|
||||
return fullAbsolutePath;
|
||||
}
|
||||
|
||||
public final String getPublicSafePathWithFileName() {
|
||||
return publicSafePath + "/" + getFileNameWithExtension();
|
||||
}
|
||||
|
||||
public final String getFullAbsolutePathWithFileName() {
|
||||
return getFullAbsolutePath() + "/" + getFileNameWithExtension();
|
||||
}
|
||||
}
|
||||
@ -25,18 +25,21 @@ public class ImageAppLogo {
|
||||
|
||||
private final ImageKey imageKey;
|
||||
private final InputStream rawDataStream;
|
||||
private final String mimeType;
|
||||
private final String logoName;
|
||||
private final long logoSize;
|
||||
private final String fileExtension;
|
||||
|
||||
public ImageAppLogo(final ImageKey imageKey,
|
||||
final InputStream rawDataStream,
|
||||
final String mimeType,
|
||||
final String logoName,
|
||||
final long logoSize,
|
||||
final String fileExtension) {
|
||||
|
||||
this.imageKey = imageKey;
|
||||
this.rawDataStream = rawDataStream;
|
||||
this.mimeType = mimeType;
|
||||
this.logoName = logoName;
|
||||
this.logoSize = logoSize;
|
||||
this.fileExtension = fileExtension;
|
||||
@ -50,6 +53,10 @@ public class ImageAppLogo {
|
||||
return rawDataStream;
|
||||
}
|
||||
|
||||
public final String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public final String getLogoName() {
|
||||
return logoName;
|
||||
}
|
||||
|
||||
@ -19,12 +19,13 @@ package io.linuxserver.fleet.v2.web;
|
||||
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.core.validation.JavalinValidation;
|
||||
import io.javalin.http.staticfiles.Location;
|
||||
import io.linuxserver.fleet.core.FleetAppController;
|
||||
import io.linuxserver.fleet.core.config.WebConfiguration;
|
||||
import io.linuxserver.fleet.v2.key.ImageKey;
|
||||
import io.linuxserver.fleet.v2.key.ImageLookupKey;
|
||||
import io.linuxserver.fleet.v2.key.RepositoryKey;
|
||||
import io.linuxserver.fleet.v2.types.meta.history.ImagePullStatistic;
|
||||
import io.linuxserver.fleet.v2.types.meta.history.ImagePullStatistic.StatGroupMode;
|
||||
import io.linuxserver.fleet.v2.web.routes.*;
|
||||
|
||||
import static io.javalin.apibuilder.ApiBuilder.*;
|
||||
@ -45,18 +46,19 @@ public class WebRouteController {
|
||||
|
||||
config.showJavalinBanner = false;
|
||||
config.addStaticFiles(Locations.Static.Static);
|
||||
config.addStaticFiles(app.getAppProperties().getStaticFilesPath().toString(), Location.EXTERNAL);
|
||||
config.accessManager(new DefaultAccessManager());
|
||||
|
||||
}).start(webConfiguration.getPort());
|
||||
|
||||
Javalin.log.info(printBanner());
|
||||
|
||||
JavalinValidation.register(ImagePullStatistic.StatGroupMode.class, ImagePullStatistic.StatGroupMode::valueOf);
|
||||
JavalinValidation.register(ImageKey.class, ImageKey::parse);
|
||||
JavalinValidation.register(StatGroupMode.class, StatGroupMode::valueOf);
|
||||
JavalinValidation.register(ImageKey.class, ImageKey::parse);
|
||||
JavalinValidation.register(ImageLookupKey.class, ImageLookupKey::new);
|
||||
JavalinValidation.register(RepositoryKey.class, RepositoryKey::parse);
|
||||
JavalinValidation.register(RepositoryKey.class, RepositoryKey::parse);
|
||||
|
||||
webInstance.exception(ApiException.class, (e, ctx) -> {
|
||||
webInstance.exception(Exception.class, (e, ctx) -> {
|
||||
|
||||
ctx.status(400);
|
||||
ctx.result(e.getMessage());
|
||||
|
||||
@ -71,7 +71,7 @@ public abstract class AbstractPageHandler extends AbstractAppService implements
|
||||
injectTopLevelModelAttributes(ctx, spec);
|
||||
checkViewForRedirect(ctx, spec);
|
||||
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
|
||||
LOGGER.error("Unexpected error occurred when loading page.", e);
|
||||
ctx.render("views/pages/error.ftl", model("error", "Something unexepected happened", "exception", e));
|
||||
|
||||
@ -101,6 +101,7 @@ public class AdminImageEditController extends AbstractPageHandler {
|
||||
|
||||
return new ImageAppLogo(imageKey,
|
||||
uploadedFile.getContent(),
|
||||
uploadedFile.getContentType(),
|
||||
uploadedFile.getFilename(),
|
||||
uploadedFile.getSize(),
|
||||
uploadedFile.getExtension());
|
||||
|
||||
@ -55,6 +55,10 @@ span.image-title {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.has-margin-right {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.has-margin-top {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
#Sat Jan 18 17:28:00 GMT 2020
|
||||
app.build.date=2020-01-18T17\:28\:00
|
||||
#Sun Jan 19 10:53:51 GMT 2020
|
||||
app.build.date=2020-01-19T10\:53\:51
|
||||
app.build.os=Linux
|
||||
app.build.user=josh
|
||||
app.version=2.0.0
|
||||
|
||||
@ -72,6 +72,15 @@
|
||||
<label class="label" for="ImageAppLogo">App Logo</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
|
||||
<#if image.metaData.appImagePath?has_content>
|
||||
<div class="is-fullwidth">
|
||||
<figure class="image is-128x128">
|
||||
<img src="${image.metaData.appImagePath}" alt="${image.name} logo" />
|
||||
</figure>
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<input type="file" name="ImageAppLogo" id="ImageAppLogo" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -66,6 +66,7 @@
|
||||
<@table.table id="ImageTable" isFullWidth=true isScrollable=true extraClasses="table--sortable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Name</th>
|
||||
<th></th>
|
||||
<th>Latest Version</th>
|
||||
|
||||
@ -19,11 +19,13 @@
|
||||
<#import "../prebuilt/fleet-title.ftl" as title />
|
||||
<#import "../prebuilt/docker-example.ftl" as dockerExample />
|
||||
|
||||
<#import "../ui/layout/section.ftl" as section />
|
||||
<#import "../ui/layout/container.ftl" as container />
|
||||
<#import "../ui/components/message.ftl" as message />
|
||||
<#import "../ui/elements/box.ftl" as box />
|
||||
<#import "../ui/elements/button.ftl" as button />
|
||||
<#import "../ui/elements/table.ftl" as table />
|
||||
<#import "../ui/elements/tag.ftl" as tag />
|
||||
<#import "../ui/layout/container.ftl" as container />
|
||||
<#import "../ui/layout/section.ftl" as section />
|
||||
|
||||
<@base.base title="${(image.fullName)!'Unknown Image'}" context="image" hasHero=false>
|
||||
|
||||
@ -37,7 +39,12 @@
|
||||
|
||||
<div class="column is-full">
|
||||
|
||||
<@title.title icon="cube" thinValue=image.repositoryName boldValue=image.name separator="/" subtitle=image.description />
|
||||
<@title.title
|
||||
icon="cube"
|
||||
imageIcon=image.metaData.appImagePath
|
||||
thinValue=image.repositoryName
|
||||
boldValue=image.name separator="/"
|
||||
subtitle=image.description />
|
||||
|
||||
<div class="tags">
|
||||
|
||||
@ -191,6 +198,20 @@
|
||||
<h3 class="subtitle is-6">
|
||||
Basic examples for getting this image running as a container
|
||||
</h3>
|
||||
|
||||
<@message.message colour="info">
|
||||
|
||||
These examples <strong>do not</strong> include the relevant values for volume mappings or environment variables. You will
|
||||
need to review these snippets and fill in the gaps based on your own needs. If you would like to generate a compose
|
||||
block or CLI run command with your mappings included, you can also use the template generator:
|
||||
|
||||
<div class="has-text-centered has-margin-top">
|
||||
<@button.link id="TemplateGeneratorLink" colour="info">
|
||||
<i class="fas fa-layer-group"></i> Template Generator
|
||||
</@button.link>
|
||||
</div>
|
||||
|
||||
</@message.message>
|
||||
</div>
|
||||
|
||||
<div class="column is-full has-margin-bottom">
|
||||
|
||||
@ -14,10 +14,17 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<#macro title thinValue="" boldValue="" separator="" icon="" subtitle="">
|
||||
<#macro title thinValue="" boldValue="" separator="" icon="" imageIcon="" subtitle="">
|
||||
|
||||
<h2 class="title is-size-3-desktop is-size-4-mobile">
|
||||
<#if icon?has_content><i class="fas fa-${icon}"></i> </#if><#if thinValue?has_content><span class="has-text-weight-light">${thinValue}</span>${separator}</#if>${boldValue}<span class="has-text-primary">.</span>
|
||||
<#if imageIcon?has_content>
|
||||
<figure class="image is-32x32 is-pulled-left has-margin-right">
|
||||
<img src="${imageIcon}" alt="Title logo" />
|
||||
</figure>
|
||||
<#elseif icon?has_content>
|
||||
<i class="fas fa-${icon}"></i>
|
||||
</#if>
|
||||
<#if thinValue?has_content><span class="has-text-weight-light">${thinValue}</span>${separator}</#if>${boldValue}<span class="has-text-primary">.</span>
|
||||
<#nested />
|
||||
</h2>
|
||||
<#if subtitle?has_content>
|
||||
|
||||
@ -22,10 +22,19 @@
|
||||
<#if !image.hidden>
|
||||
|
||||
<tr class="image-row" data-image-name="${image.name}">
|
||||
<td class="is-vcentered has-text-right is-paddingless" style="width: 16px">
|
||||
<#if image.metaData.appImagePath?has_content>
|
||||
<figure class="image is-16x16">
|
||||
<img src="${image.metaData.appImagePath}" alt="Title logo" />
|
||||
</figure>
|
||||
<#else>
|
||||
<i class="fas fa-cube"></i>
|
||||
</#if>
|
||||
</td>
|
||||
<td class="is-vcentered">
|
||||
<h4 class="title is-6">
|
||||
<a class="has-text-grey-dark" href="/image?name=${image.fullName}">
|
||||
<i class="fas fa-cube"></i> <span class="has-text-weight-light">${image.repositoryKey.name} / </span><span class="has-text-weight-bold">${image.name}</span>
|
||||
<span class="has-text-weight-light">${image.repositoryKey.name} / </span><span class="has-text-weight-bold">${image.name}</span>
|
||||
</a>
|
||||
</h4>
|
||||
</td>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user