mirror of
https://github.com/linuxserver/fleet.git
synced 2026-02-20 05:11:08 +08:00
Core Meta
- Store core meta as part of standard image save - Display core meta on main image display - TODO: upload image and save.
This commit is contained in:
parent
cd0f025d55
commit
b8f64bd4f2
@ -41,7 +41,7 @@ public class DockerImageUpdateResponse implements AsyncDockerApiResponse {
|
||||
|
||||
@Override
|
||||
public void handleDockerApiResponse() {
|
||||
controller.getImageService().applyImageUpdate(imageKey, latestImage);
|
||||
controller.getImageService().applyImageUpstreamUpdate(imageKey, latestImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -28,12 +28,14 @@ import io.linuxserver.fleet.v2.types.*;
|
||||
import io.linuxserver.fleet.v2.types.internal.ImageOutlineRequest;
|
||||
import io.linuxserver.fleet.v2.types.internal.RepositoryOutlineRequest;
|
||||
import io.linuxserver.fleet.v2.types.internal.TagBranchOutlineRequest;
|
||||
import io.linuxserver.fleet.v2.types.meta.ImageCoreMeta;
|
||||
import io.linuxserver.fleet.v2.types.meta.ImageMetaData;
|
||||
import io.linuxserver.fleet.v2.types.meta.ItemSyncSpec;
|
||||
import io.linuxserver.fleet.v2.types.meta.history.ImagePullHistory;
|
||||
import io.linuxserver.fleet.v2.types.meta.history.ImagePullStatistic;
|
||||
import io.linuxserver.fleet.v2.types.meta.template.ImageTemplateHolder;
|
||||
|
||||
import java.net.URL;
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
@ -50,7 +52,7 @@ public class DefaultImageDAO extends AbstractDAO implements ImageDAO {
|
||||
private static final String CreateRepositoryOutline = "{CALL Repository_CreateOutline(?,?,?,?,?,?,?,?)}";
|
||||
private static final String StoreRepository = "{CALL Repository_Store(?,?,?,?)}";
|
||||
|
||||
private static final String StoreImage = "{CALL Image_Store(?,?,?,?,?,?,?,?,?,?,?)}";
|
||||
private static final String StoreImage = "{CALL Image_Store(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)}";
|
||||
private static final String CreateTagBranchOutline = "{CALL Image_CreateTagBranchOutline(?,?)}";
|
||||
private static final String StoreTagBranch = "{CALL Image_StoreTagBranch(?,?,?,?)}";
|
||||
private static final String StoreTagDigest = "{CALL Image_StoreTagDigest(?,?,?,?,?)}";
|
||||
@ -103,6 +105,12 @@ public class DefaultImageDAO extends AbstractDAO implements ImageDAO {
|
||||
call.setBoolean(i++, image.isSyncEnabled());
|
||||
Utils.setNullableString(call, i++, image.getVersionMask());
|
||||
|
||||
Utils.setNullableString(call, i++, image.getMetaData().getCategory());
|
||||
Utils.setNullableString(call, i++, image.getMetaData().getSupportUrl());
|
||||
Utils.setNullableString(call, i++, image.getMetaData().getAppUrl());
|
||||
Utils.setNullableString(call, i++, image.getMetaData().getBaseImage());
|
||||
Utils.setNullableString(call, i++, image.getMetaData().getAppImagePath());
|
||||
|
||||
call.registerOutParameter(i, Types.VARCHAR);
|
||||
|
||||
final ResultSet results = call.executeQuery();
|
||||
@ -486,7 +494,7 @@ public class DefaultImageDAO extends AbstractDAO implements ImageDAO {
|
||||
|
||||
final Image image = new Image(imageKey,
|
||||
makeSyncSpec(results),
|
||||
makeImageMetaData(connection, imageKey),
|
||||
makeImageMetaData(connection, imageKey, results),
|
||||
makeCountData(results),
|
||||
results.getString("Description"),
|
||||
results.getTimestamp("LastUpdated").toLocalDateTime());
|
||||
@ -499,12 +507,22 @@ public class DefaultImageDAO extends AbstractDAO implements ImageDAO {
|
||||
return null;
|
||||
}
|
||||
|
||||
private ImageMetaData makeImageMetaData(final Connection connection, final ImageKey imageKey) throws SQLException {
|
||||
private ImageMetaData makeImageMetaData(final Connection connection, final ImageKey imageKey, final ResultSet mainImageResults) throws SQLException {
|
||||
|
||||
return new ImageMetaData(makePullHistory(connection, imageKey),
|
||||
return new ImageMetaData(makeCoreMeta(mainImageResults),
|
||||
makePullHistory(connection, imageKey),
|
||||
templateFactory.makeTemplateHolder(connection, imageKey));
|
||||
}
|
||||
|
||||
private ImageCoreMeta makeCoreMeta(final ResultSet mainImageResults) throws SQLException {
|
||||
|
||||
return new ImageCoreMeta(mainImageResults.getString("CoreMetaImagePath"),
|
||||
mainImageResults.getString("CoreMetaBaseImage"),
|
||||
mainImageResults.getString("CoreMetaCategory"),
|
||||
mainImageResults.getString("CoreMetaSupportUrl"),
|
||||
mainImageResults.getString("CoreMetaAppUrl"));
|
||||
}
|
||||
|
||||
private ImagePullHistory makePullHistory(final Connection connection, final ImageKey imageKey) throws SQLException {
|
||||
|
||||
final ImagePullHistory pullHistory = new ImagePullHistory();
|
||||
|
||||
@ -28,14 +28,14 @@ import io.linuxserver.fleet.v2.service.util.TemplateMerger;
|
||||
import io.linuxserver.fleet.v2.types.*;
|
||||
import io.linuxserver.fleet.v2.types.docker.DockerImage;
|
||||
import io.linuxserver.fleet.v2.types.docker.DockerTag;
|
||||
import io.linuxserver.fleet.v2.types.internal.ImageOutlineRequest;
|
||||
import io.linuxserver.fleet.v2.types.internal.ImageTemplateRequest;
|
||||
import io.linuxserver.fleet.v2.types.internal.RepositoryOutlineRequest;
|
||||
import io.linuxserver.fleet.v2.types.internal.TagBranchOutlineRequest;
|
||||
import io.linuxserver.fleet.v2.types.internal.*;
|
||||
import io.linuxserver.fleet.v2.types.meta.ImageCoreMeta;
|
||||
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;
|
||||
@ -192,7 +192,7 @@ public class ImageService {
|
||||
return getAllRepositories().stream().filter(r -> !r.isHidden()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Image applyImageUpdate(final ImageKey imageKey, final DockerImage latestImage) {
|
||||
public Image applyImageUpstreamUpdate(final ImageKey imageKey, final DockerImage latestImage) {
|
||||
|
||||
final Image cachedImage = findImage(imageKey);
|
||||
final Image cloned = cachedImage.cloneForUpdate(latestImage.getPullCount(),
|
||||
@ -240,6 +240,25 @@ public class ImageService {
|
||||
storeImage(updatableClone);
|
||||
}
|
||||
|
||||
public void updateImageGeneralInfo(final ImageKey imageKey, final ImageGeneralInfoUpdateRequest generalInfoUpdateRequest) {
|
||||
|
||||
final Image image = findImage(imageKey);
|
||||
|
||||
String appLogoPath = image.getMetaData().getAppImagePath();
|
||||
if (null != generalInfoUpdateRequest.getImageAppLogo()) {
|
||||
// TODO: Write FileManager
|
||||
}
|
||||
|
||||
final ImageCoreMeta coreMeta = new ImageCoreMeta(appLogoPath,
|
||||
generalInfoUpdateRequest.getBaseImage(),
|
||||
generalInfoUpdateRequest.getCategory(),
|
||||
generalInfoUpdateRequest.getSupportUrl(),
|
||||
generalInfoUpdateRequest.getApplicationUrl());
|
||||
|
||||
final Image cloned = image.cloneWithMetaData(image.getMetaData().cloneWithCoreMeta(coreMeta));
|
||||
storeImage(cloned);
|
||||
}
|
||||
|
||||
public void updateImageTemplate(final ImageKey imageKey, final ImageTemplateRequest imageTemplateUpdateFields) {
|
||||
|
||||
final Image image = findImage(imageKey);
|
||||
|
||||
@ -48,6 +48,8 @@ public class SynchronisationService extends AbstractAppService {
|
||||
|
||||
if (repository.isSyncEnabled()) {
|
||||
|
||||
getLogger().info("synchroniseUpstreamRepository checking {} for new images since last sync", repository);
|
||||
|
||||
final List<DockerImage> apiImages = getController().getConfiguredDockerDelegate().getImagesForRepository(repository.getKey());
|
||||
for (DockerImage apiImage : apiImages) {
|
||||
|
||||
|
||||
@ -31,11 +31,11 @@ public class TemplateMerger {
|
||||
public final Image mergeTemplateRequestIntoImage(final Image image, final ImageTemplateRequest templateRequest) {
|
||||
|
||||
final ImageTemplateHolder templateHolder = makeTemplateHolder(templateRequest);
|
||||
addMisc(templateRequest, templateHolder);
|
||||
addPorts(templateRequest, templateHolder);
|
||||
addVolumes(templateRequest, templateHolder);
|
||||
addMisc( templateRequest, templateHolder);
|
||||
addPorts( templateRequest, templateHolder);
|
||||
addVolumes( templateRequest, templateHolder);
|
||||
addEnvironment(templateRequest, templateHolder);
|
||||
addDevices(templateRequest, templateHolder);
|
||||
addDevices( templateRequest, templateHolder);
|
||||
|
||||
final Image cloned = image.cloneWithMetaData(image.getMetaData().cloneWithTemplate(templateHolder));
|
||||
return cloned;
|
||||
|
||||
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.internal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AbstractParamRequest {
|
||||
|
||||
private final Map<String, List<String>> params;
|
||||
|
||||
public AbstractParamRequest(final Map<String, List<String>> params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
protected final List<String> getParams(final String key) {
|
||||
return params.get(key);
|
||||
}
|
||||
|
||||
protected final String getOrNull(final String value) {
|
||||
return "".equalsIgnoreCase(value.trim()) ? null : value;
|
||||
}
|
||||
|
||||
protected final String getFirstOrNull(final String key) {
|
||||
|
||||
final List<String> strings = params.get(key);
|
||||
if (null == strings || strings.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return strings.get(0);
|
||||
}
|
||||
|
||||
protected final boolean getAsBoolean(final String value) {
|
||||
return "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
protected final void checkLists(final List<String>... lists) {
|
||||
|
||||
boolean containsDifferent = false;
|
||||
|
||||
boolean allNull = Stream.of(lists).allMatch(Objects::isNull);
|
||||
boolean noneNull = Stream.of(lists).allMatch(Objects::nonNull);
|
||||
|
||||
if (allNull || noneNull) {
|
||||
|
||||
if (allNull) {
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
containsDifferent = true;
|
||||
}
|
||||
|
||||
if (!containsDifferent) {
|
||||
|
||||
int prevSize = -1;
|
||||
for (List<String> list : lists) {
|
||||
|
||||
if (prevSize != -1 && list.size() != prevSize) {
|
||||
|
||||
containsDifferent = true;
|
||||
break;
|
||||
|
||||
} else {
|
||||
prevSize = list.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (containsDifferent) {
|
||||
throw new IllegalArgumentException("One or more values are null when others are not, or sizes mismatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.internal;
|
||||
|
||||
import io.linuxserver.fleet.v2.key.ImageKey;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ImageAppLogo {
|
||||
|
||||
private final ImageKey imageKey;
|
||||
private final InputStream rawDataStream;
|
||||
private final String logoName;
|
||||
private final long logoSize;
|
||||
private final String fileExtension;
|
||||
|
||||
public ImageAppLogo(final ImageKey imageKey,
|
||||
final InputStream rawDataStream,
|
||||
final String logoName,
|
||||
final long logoSize,
|
||||
final String fileExtension) {
|
||||
|
||||
this.imageKey = imageKey;
|
||||
this.rawDataStream = rawDataStream;
|
||||
this.logoName = logoName;
|
||||
this.logoSize = logoSize;
|
||||
this.fileExtension = fileExtension;
|
||||
}
|
||||
|
||||
public final ImageKey getImageKey() {
|
||||
return imageKey;
|
||||
}
|
||||
|
||||
public final InputStream getRawDataStream() {
|
||||
return rawDataStream;
|
||||
}
|
||||
|
||||
public final String getLogoName() {
|
||||
return logoName;
|
||||
}
|
||||
|
||||
public final long getLogoSize() {
|
||||
return logoSize;
|
||||
}
|
||||
|
||||
public final String getFileExtension() {
|
||||
return fileExtension;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.internal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ImageGeneralInfoUpdateRequest extends AbstractParamRequest {
|
||||
|
||||
private final ImageAppLogo imageAppLogo;
|
||||
|
||||
public ImageGeneralInfoUpdateRequest(final Map<String, List<String>> params,
|
||||
final ImageAppLogo imageAppLogo) {
|
||||
super(params);
|
||||
this.imageAppLogo = imageAppLogo;
|
||||
}
|
||||
|
||||
public final ImageAppLogo getImageAppLogo() {
|
||||
return imageAppLogo;
|
||||
}
|
||||
|
||||
public final String getBaseImage() {
|
||||
return getFirstOrNull("ImageBase");
|
||||
}
|
||||
|
||||
public final String getCategory() {
|
||||
return getFirstOrNull("ImageCategory");
|
||||
}
|
||||
|
||||
public final String getSupportUrl() {
|
||||
return getFirstOrNull("ImageSupportUrl");
|
||||
}
|
||||
|
||||
public final String getApplicationUrl() {
|
||||
return getFirstOrNull("ImageApplicationUrl");
|
||||
}
|
||||
}
|
||||
@ -20,15 +20,11 @@ package io.linuxserver.fleet.v2.types.internal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ImageTemplateRequest {
|
||||
|
||||
private final Map<String, List<String>> rawTemplateParams;
|
||||
public class ImageTemplateRequest extends AbstractParamRequest {
|
||||
|
||||
public ImageTemplateRequest(final Map<String, List<String>> rawTemplateParams) {
|
||||
this.rawTemplateParams = rawTemplateParams;
|
||||
super(rawTemplateParams);
|
||||
}
|
||||
|
||||
public final String getRegistryUrl() {
|
||||
@ -48,14 +44,14 @@ public class ImageTemplateRequest {
|
||||
}
|
||||
|
||||
public final List<String> getCapabilities() {
|
||||
return rawTemplateParams.get("ImageTemplateCapabilities");
|
||||
return getParams("ImageTemplateCapabilities");
|
||||
}
|
||||
|
||||
public final List<TemplateItem<String>> getPorts() {
|
||||
|
||||
final List<String> portNumbers = rawTemplateParams.get("imageTemplatePort");
|
||||
final List<String> portProtocols = rawTemplateParams.get("imageTemplatePortProtocol");
|
||||
final List<String> portDescriptions = rawTemplateParams.get("imageTemplatePortDescription");
|
||||
final List<String> portNumbers = getParams("imageTemplatePort");
|
||||
final List<String> portProtocols = getParams("imageTemplatePortProtocol");
|
||||
final List<String> portDescriptions = getParams("imageTemplatePortDescription");
|
||||
|
||||
checkLists(portNumbers, portProtocols, portDescriptions);
|
||||
|
||||
@ -77,9 +73,9 @@ public class ImageTemplateRequest {
|
||||
|
||||
public final List<TemplateItem<Boolean>> getVolumes() {
|
||||
|
||||
final List<String> volumeNames = rawTemplateParams.get("imageTemplateVolume");
|
||||
final List<String> volumeReadOnlys = rawTemplateParams.get("imageTemplateVolumeReadonly");
|
||||
final List<String> volumeDescriptions = rawTemplateParams.get("imageTemplateVolumeDescription");
|
||||
final List<String> volumeNames = getParams("imageTemplateVolume");
|
||||
final List<String> volumeReadOnlys = getParams("imageTemplateVolumeReadonly");
|
||||
final List<String> volumeDescriptions = getParams("imageTemplateVolumeDescription");
|
||||
|
||||
checkLists(volumeNames, volumeReadOnlys, volumeDescriptions);
|
||||
|
||||
@ -101,8 +97,8 @@ public class ImageTemplateRequest {
|
||||
|
||||
public final List<TemplateItem<Void>> getEnvironment() {
|
||||
|
||||
final List<String> envNames = rawTemplateParams.get("imageTemplateEnv");
|
||||
final List<String> envDescriptions = rawTemplateParams.get("imageTemplateEnvDescription");
|
||||
final List<String> envNames = getParams("imageTemplateEnv");
|
||||
final List<String> envDescriptions = getParams("imageTemplateEnvDescription");
|
||||
|
||||
checkLists(envNames, envDescriptions);
|
||||
|
||||
@ -121,8 +117,8 @@ public class ImageTemplateRequest {
|
||||
|
||||
public final List<TemplateItem<Void>> getDevices() {
|
||||
|
||||
final List<String> deviceNames = rawTemplateParams.get("imageTemplateDevice");
|
||||
final List<String> deviceDescriptions = rawTemplateParams.get("imageTemplateDeviceDescription");
|
||||
final List<String> deviceNames = getParams("imageTemplateDevice");
|
||||
final List<String> deviceDescriptions = getParams("imageTemplateDeviceDescription");
|
||||
|
||||
checkLists(deviceNames, deviceDescriptions);
|
||||
|
||||
@ -139,62 +135,6 @@ public class ImageTemplateRequest {
|
||||
return env;
|
||||
}
|
||||
|
||||
private String getOrNull(final String value) {
|
||||
return "".equalsIgnoreCase(value.trim()) ? null : value;
|
||||
}
|
||||
|
||||
private String getFirstOrNull(final String key) {
|
||||
|
||||
final List<String> strings = rawTemplateParams.get(key);
|
||||
if (null == strings || strings.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return strings.get(0);
|
||||
}
|
||||
|
||||
private boolean getAsBoolean(final String value) {
|
||||
return "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private final void checkLists(final List<String>... lists) {
|
||||
|
||||
boolean containsDifferent = false;
|
||||
|
||||
boolean allNull = Stream.of(lists).allMatch(Objects::isNull);
|
||||
boolean noneNull = Stream.of(lists).allMatch(Objects::nonNull);
|
||||
|
||||
if (allNull || noneNull) {
|
||||
|
||||
if (allNull) {
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
containsDifferent = true;
|
||||
}
|
||||
|
||||
if (!containsDifferent) {
|
||||
|
||||
int prevSize = -1;
|
||||
for (List<String> list : lists) {
|
||||
|
||||
if (prevSize != -1 && list.size() != prevSize) {
|
||||
|
||||
containsDifferent = true;
|
||||
break;
|
||||
|
||||
} else {
|
||||
prevSize = list.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (containsDifferent) {
|
||||
throw new IllegalArgumentException("One or more values are null when others are not, or sizes mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
public static class TemplateItem<T> {
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.meta;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class ImageCoreMeta {
|
||||
|
||||
private final String appImagePath;
|
||||
private final String baseImage;
|
||||
private final String category;
|
||||
private final String supportUrl;
|
||||
private final String appUrl;
|
||||
|
||||
public ImageCoreMeta(final String appImagePath,
|
||||
final String baseImage,
|
||||
final String category,
|
||||
final String supportUrl,
|
||||
final String appUrl) {
|
||||
|
||||
this.appImagePath = appImagePath;
|
||||
this.baseImage = baseImage;
|
||||
this.category = category;
|
||||
this.supportUrl = supportUrl;
|
||||
this.appUrl = appUrl;
|
||||
}
|
||||
|
||||
public final String getAppImagePath() {
|
||||
return appImagePath;
|
||||
}
|
||||
|
||||
public final String getBaseImage() {
|
||||
return baseImage;
|
||||
}
|
||||
|
||||
public final String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public final String getSupportUrl() {
|
||||
return supportUrl;
|
||||
}
|
||||
|
||||
public final String getAppUrl() {
|
||||
return appUrl;
|
||||
}
|
||||
}
|
||||
@ -27,14 +27,22 @@ public class ImageMetaData {
|
||||
|
||||
private final ImagePullHistory pullHistory;
|
||||
private final ImageTemplateHolder templateHolder;
|
||||
private final ImageCoreMeta coreMeta;
|
||||
|
||||
public ImageMetaData(final ImagePullHistory pullHistory, final ImageTemplateHolder templateHolder) {
|
||||
public ImageMetaData(final ImageCoreMeta coreMeta,
|
||||
final ImagePullHistory pullHistory,
|
||||
final ImageTemplateHolder templateHolder) {
|
||||
this.coreMeta = coreMeta;
|
||||
this.pullHistory = pullHistory;
|
||||
this.templateHolder = templateHolder;
|
||||
}
|
||||
|
||||
public final ImageMetaData cloneWithTemplate(final ImageTemplateHolder templateHolder) {
|
||||
return new ImageMetaData(pullHistory, templateHolder);
|
||||
return new ImageMetaData(getCoreMeta(), this.pullHistory, templateHolder);
|
||||
}
|
||||
|
||||
public final ImageMetaData cloneWithCoreMeta(final ImageCoreMeta coreMeta) {
|
||||
return new ImageMetaData(coreMeta, this.pullHistory, getTemplates());
|
||||
}
|
||||
|
||||
public final List<ImagePullStatistic> getHistoryFor(final ImagePullStatistic.StatGroupMode groupMode) {
|
||||
@ -44,4 +52,37 @@ public class ImageMetaData {
|
||||
public final ImageTemplateHolder getTemplates() {
|
||||
return templateHolder;
|
||||
}
|
||||
|
||||
public final ImageCoreMeta getCoreMeta() {
|
||||
return coreMeta;
|
||||
}
|
||||
|
||||
public final String getAppImagePath() {
|
||||
return getCoreMeta().getAppImagePath();
|
||||
}
|
||||
|
||||
public final String getBaseImage() {
|
||||
return getCoreMeta().getBaseImage();
|
||||
}
|
||||
|
||||
public final String getCategory() {
|
||||
return getCoreMeta().getCategory();
|
||||
}
|
||||
|
||||
public final String getSupportUrl() {
|
||||
return getCoreMeta().getSupportUrl();
|
||||
}
|
||||
|
||||
public final String getAppUrl() {
|
||||
return getCoreMeta().getAppUrl();
|
||||
}
|
||||
|
||||
public final boolean isPopulated() {
|
||||
|
||||
return (
|
||||
null != getCategory() ||
|
||||
null != getSupportUrl() ||
|
||||
null != getAppUrl()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,10 +18,13 @@
|
||||
package io.linuxserver.fleet.v2.web.routes;
|
||||
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.http.UploadedFile;
|
||||
import io.linuxserver.fleet.core.FleetAppController;
|
||||
import io.linuxserver.fleet.v2.key.ImageKey;
|
||||
import io.linuxserver.fleet.v2.service.ImageService;
|
||||
import io.linuxserver.fleet.v2.types.docker.DockerCapability;
|
||||
import io.linuxserver.fleet.v2.types.internal.ImageAppLogo;
|
||||
import io.linuxserver.fleet.v2.types.internal.ImageGeneralInfoUpdateRequest;
|
||||
import io.linuxserver.fleet.v2.types.internal.ImageTemplateRequest;
|
||||
import io.linuxserver.fleet.v2.web.PageModelSpec;
|
||||
|
||||
@ -78,9 +81,31 @@ public class AdminImageEditController extends AbstractPageHandler {
|
||||
if (!ctx.isMultipartFormData()) {
|
||||
throw new IllegalArgumentException("Form submission must be form/multipart");
|
||||
}
|
||||
|
||||
imageService.updateImageGeneralInfo(imageKey, makeInfoRequest(imageKey, ctx));
|
||||
}
|
||||
|
||||
private void handleTemplateUpdate(final Context ctx, final ImageKey imageKey) {
|
||||
imageService.updateImageTemplate(imageKey, new ImageTemplateRequest(ctx.formParamMap()));
|
||||
}
|
||||
|
||||
private ImageGeneralInfoUpdateRequest makeInfoRequest(final ImageKey imageKey, final Context ctx) {
|
||||
|
||||
return new ImageGeneralInfoUpdateRequest(ctx.formParamMap(),
|
||||
makeImageLogoIfPresent(imageKey, ctx.uploadedFile("ImageAppLogo")));
|
||||
}
|
||||
|
||||
private ImageAppLogo makeImageLogoIfPresent(final ImageKey imageKey, final UploadedFile uploadedFile) {
|
||||
|
||||
if (null != uploadedFile) {
|
||||
|
||||
return new ImageAppLogo(imageKey,
|
||||
uploadedFile.getContent(),
|
||||
uploadedFile.getFilename(),
|
||||
uploadedFile.getSize(),
|
||||
uploadedFile.getExtension());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,145 @@
|
||||
DELIMITER //
|
||||
|
||||
CREATE OR REPLACE VIEW `Image_View` AS (
|
||||
|
||||
SELECT
|
||||
|
||||
-- Key
|
||||
images.`id` AS `ImageId`,
|
||||
images.`name` AS `ImageName`,
|
||||
images.`repository` AS `RepositoryId`,
|
||||
repositories.`name` AS `RepositoryName`,
|
||||
|
||||
-- Counts
|
||||
images.`pulls` AS `LatestPullCount`,
|
||||
images.`stars` AS `LatestStarCount`,
|
||||
|
||||
-- Spec
|
||||
images.`sync_enabled` AS `SyncEnabled`,
|
||||
images.`version_mask` AS `VersionMask`,
|
||||
images.`hidden` AS `Hidden`,
|
||||
images.`stable` AS `Stable`,
|
||||
images.`deprecated` AS `Deprecated`,
|
||||
|
||||
-- General
|
||||
images.`description` AS `Description`,
|
||||
images.`modified` AS `LastUpdated`,
|
||||
|
||||
-- Core Meta
|
||||
meta.icon_url AS `CoreMetaImagePath`,
|
||||
meta.base_image AS `CoreMetaBaseImage`,
|
||||
meta.category AS `CoreMetaCategory`,
|
||||
meta.support AS `CoreMetaSupportUrl`,
|
||||
meta.app_url AS `CoreMetaAppUrl`
|
||||
|
||||
FROM
|
||||
Image images
|
||||
JOIN
|
||||
Repository repositories ON repositories.`id` = images.`repository`
|
||||
LEFT JOIN
|
||||
ImageMetadata meta on meta.`image_id` = images.`id`
|
||||
);
|
||||
//
|
||||
|
||||
CREATE OR REPLACE PROCEDURE `Image_Store`
|
||||
(
|
||||
in_id INT,
|
||||
in_pulls BIGINT,
|
||||
in_stars INT,
|
||||
in_description TEXT,
|
||||
in_modified TIMESTAMP,
|
||||
in_deprecated TINYINT,
|
||||
in_hidden TINYINT,
|
||||
in_stable TINYINT,
|
||||
in_synchronised TINYINT,
|
||||
in_version_mask VARCHAR(255),
|
||||
in_category VARCHAR(255),
|
||||
in_support VARCHAR(500),
|
||||
in_app_url VARCHAR(500),
|
||||
in_base_image VARCHAR(255),
|
||||
in_icon_url VARCHAR(1000),
|
||||
|
||||
OUT out_status enum('Updated', 'NoChange')
|
||||
)
|
||||
BEGIN
|
||||
|
||||
IF NOT EXISTS(SELECT `id` FROM Image WHERE `id` = in_id) THEN
|
||||
SET out_status = 'NoChange';
|
||||
ELSE
|
||||
|
||||
UPDATE
|
||||
Image
|
||||
SET
|
||||
`pulls` = in_pulls,
|
||||
`stars` = in_stars,
|
||||
`description` = in_description,
|
||||
`modified` = in_modified,
|
||||
`deprecated` = in_deprecated,
|
||||
`hidden` = in_hidden,
|
||||
`stable` = in_stable,
|
||||
`sync_enabled` = in_synchronised,
|
||||
`version_mask` = in_version_mask
|
||||
WHERE
|
||||
`id` = in_id;
|
||||
|
||||
-- Only add core metadata if it has been provided with at least one value
|
||||
IF
|
||||
(
|
||||
in_category IS NOT NULL OR
|
||||
in_support IS NOT NULL OR
|
||||
in_app_url IS NOT NULL OR
|
||||
in_base_image IS NOT NULL OR
|
||||
in_icon_url IS NOT NULL
|
||||
)
|
||||
THEN
|
||||
|
||||
IF NOT EXISTS(SELECT 1 FROM ImageMetadata WHERE `image_id` = in_id) THEN
|
||||
|
||||
INSERT INTO ImageMetadata
|
||||
(
|
||||
`image_id`,
|
||||
`category`,
|
||||
`support`,
|
||||
`app_url`,
|
||||
`base_image`,
|
||||
`icon_url`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
in_id,
|
||||
in_category,
|
||||
in_support,
|
||||
in_app_url,
|
||||
in_base_image,
|
||||
in_icon_url
|
||||
);
|
||||
|
||||
ELSE
|
||||
|
||||
UPDATE ImageMetadata
|
||||
SET
|
||||
`category` = in_category,
|
||||
`support` = in_support,
|
||||
`app_url` = in_app_url,
|
||||
`base_image` = in_base_image,
|
||||
`icon_url` = in_icon_url
|
||||
WHERE
|
||||
`image_id` = in_id;
|
||||
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
|
||||
IF ROW_COUNT() <> 1 THEN
|
||||
SET out_status = 'NoChange';
|
||||
ELSE
|
||||
SET out_status = 'Updated';
|
||||
END IF;
|
||||
|
||||
CALL Image_StorePullHistory(in_id, in_pulls, out_status);
|
||||
|
||||
SELECT * FROM ImageKey_View WHERE `ImageId` = in_id;
|
||||
|
||||
END IF;
|
||||
|
||||
END //
|
||||
@ -1,5 +1,5 @@
|
||||
#Sat Jan 18 12:56:52 GMT 2020
|
||||
app.build.date=2020-01-18T12\:56\:52
|
||||
#Sat Jan 18 17:28:00 GMT 2020
|
||||
app.build.date=2020-01-18T17\:28\:00
|
||||
app.build.os=Linux
|
||||
app.build.user=josh
|
||||
app.version=2.0.0
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
<#import "../../ui/form/input.ftl" as input />
|
||||
<#import "../../ui/elements/button.ftl" as button />
|
||||
<#import "../../ui/elements/table.ftl" as table />
|
||||
<#import "../../ui/elements/tag.ftl" as tag />
|
||||
<#import "../../ui/components/message.ftl" as message />
|
||||
|
||||
<#import "template-components/image-template-ports.ftl" as templatePorts />
|
||||
@ -71,21 +72,33 @@
|
||||
<label class="label" for="ImageAppLogo">App Logo</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<input type="file" name="imageAppLogo" id="ImageAppLogo" />
|
||||
<input type="file" name="ImageAppLogo" id="ImageAppLogo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-full">
|
||||
<@input.text id="ImageBase" label="Base Image" isInline=true />
|
||||
|
||||
<@input.text id="ImageBase" label="Base Image" isInline=true value=image.metaData.baseImage
|
||||
infoText="The name of the base image this image pulls from." />
|
||||
|
||||
</div>
|
||||
<div class="column is-full">
|
||||
<@input.text id="ImageCategory" label="Category" isInline=true />
|
||||
|
||||
<@input.text id="ImageCategory" label="Category" isInline=true value=image.metaData.category
|
||||
infoText="The application category for this image (e.g Home Automation)." />
|
||||
|
||||
</div>
|
||||
<div class="column is-full">
|
||||
<@input.text id="ImageSupportUrl" label="Support Url" isInline=true />
|
||||
|
||||
<@input.text id="ImageSupportUrl" label="Support Url" isInline=true value=image.metaData.supportUrl
|
||||
infoText="A link to the primary source of support for this image, such as a forum thread or documentation site." />
|
||||
|
||||
</div>
|
||||
<div class="column is-full">
|
||||
<@input.text id="ImageAppUrl" label="Application Url" isInline=true />
|
||||
|
||||
<@input.text id="ImageApplicationUrl" label="Application Url" isInline=true value=image.metaData.appUrl
|
||||
infoText="The primary URL for the application encapsulated by this image." />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
@ -132,16 +145,14 @@
|
||||
<td>
|
||||
<#if !tagBranch.branchProtected>
|
||||
<@button.buttons isRightAligned=true>
|
||||
<@button.button extraClasses="remove-tag-branch" colour="danger" size="small">
|
||||
<i class="fas fa-trash"></i> Remove
|
||||
<@button.button extraClasses="remove-tag-branch" colour="white" size="small" title="Stop tracking this branch.">
|
||||
<i class="fas fa-trash has-text-danger is-marginless"></i>
|
||||
</@button.button>
|
||||
</@button.buttons>
|
||||
<#else>
|
||||
<@button.buttons isRightAligned=true>
|
||||
<@button.button colour="light" size="small" isDisabled=true>
|
||||
Protected
|
||||
</@button.button>
|
||||
</@button.buttons>
|
||||
<div class="tags is-right">
|
||||
<@tag.tag colour="light" value="Protected" extraAttributes='title="This branch can\'t be removed."' />
|
||||
</div>
|
||||
</#if>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -95,7 +95,7 @@
|
||||
|
||||
<div class="columns is-multiline">
|
||||
|
||||
<div class="column is-full">
|
||||
<div class="column is-full has-margin-bottom">
|
||||
|
||||
<h2 class="title is-5">Build Information</h2>
|
||||
<h3 class="subtitle is-6">General build information for this image</h3>
|
||||
@ -109,6 +109,9 @@
|
||||
<tbody>
|
||||
<@table.halfDisplayRow title="Repository" value=image.repositoryName link="/?key=${image.repositoryKey}" />
|
||||
<@table.halfDisplayRow title="Build Time" value=image.lastUpdatedAsString />
|
||||
<#if image.metaData.baseImage?has_content>
|
||||
<@table.halfDisplayRow title="Base Image" value=image.metaData.baseImage?html />
|
||||
</#if>
|
||||
<@table.halfDisplayRow title="Synchronised" value=image.syncEnabled?string("Yes", "No") />
|
||||
<@table.halfDisplayRow title="Stable" value=image.stable?string("Yes", "No") />
|
||||
<@table.halfDisplayRow title="Deprecated" value=image.deprecated?string("Yes", "No") />
|
||||
@ -117,7 +120,35 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="column is-full has-margin-bottom">
|
||||
<#if image.metaData.populated>
|
||||
<div class="column is-full has-margin-bottom">
|
||||
|
||||
<h2 class="title is-5">Support Information</h2>
|
||||
<h3 class="subtitle is-6">External links and support</h3>
|
||||
|
||||
<@table.table isFullWidth=true isNarrow=false isStriped=true isScrollable=true>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="row" colspan="2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<#if image.metaData.category?has_content>
|
||||
<@table.halfDisplayRow title="Category" value=image.metaData.category />
|
||||
</#if>
|
||||
<#if image.metaData.appUrl?has_content>
|
||||
<@table.halfDisplayRow title="Application Home" value=image.metaData.appUrl?html link=image.metaData.appUrl />
|
||||
</#if>
|
||||
<#if image.metaData.supportUrl?has_content>
|
||||
<@table.halfDisplayRow title="Support" value=image.metaData.supportUrl?html link=image.metaData.supportUrl />
|
||||
</#if>
|
||||
</tbody>
|
||||
</@table.table>
|
||||
|
||||
</div>
|
||||
</#if>
|
||||
|
||||
<div class="column is-full">
|
||||
|
||||
<h2 class="title is-5">Tracked Tags</h2>
|
||||
<h3 class="subtitle is-6">Known tags which link to a specific branched app version.</h3>
|
||||
|
||||
@ -22,7 +22,10 @@ version: "2"
|
||||
services:
|
||||
${containerName}:
|
||||
image: ${fullName}<#if latest?has_content>:${latest}</#if>
|
||||
container_name: ${containerName}<#if templates.restartPolicy?has_content>
|
||||
container_name: ${containerName}
|
||||
<#if templates.hostNetworkingEnabled> network_mode: host
|
||||
</#if>
|
||||
<#if templates.restartPolicy?has_content>
|
||||
restart: ${templates.restartPolicy}</#if>
|
||||
<#if templates.capabilities?has_content> cap_add:
|
||||
<#list templates.capabilities as cap>
|
||||
@ -39,7 +42,7 @@ services:
|
||||
- /host/path/to${volume.name}:${volume.name}<#if volume.readonly>:ro</#if> <#if volume.description?has_content># ${volume.description}</#if>
|
||||
</#list>
|
||||
</#if>
|
||||
<#if templates.ports?has_content> ports:
|
||||
<#if templates.ports?has_content && !templates.hostNetworkingEnabled> ports:
|
||||
<#list templates.ports as port>
|
||||
- ${port.name?string["##0"]}:${port.name?string["##0"]}/${port.protocol} <#if port.description?has_content># ${port.description}</#if>
|
||||
</#list>
|
||||
@ -57,7 +60,8 @@ services:
|
||||
|
||||
<div class="content">
|
||||
<pre><code class="language-bash">docker create \
|
||||
--name=${containerName} \<#if templates.env?has_content>
|
||||
--name=${containerName} \<#if templates.hostNetworkingEnabled>
|
||||
--net=host \</#if><#if templates.env?has_content>
|
||||
<#list templates.env as env>
|
||||
-e ${env.name}=<#if env.description?has_content> `# ${env.description}`</#if> \
|
||||
</#list>
|
||||
@ -67,7 +71,7 @@ services:
|
||||
-v /host/path/to${volume.name}:${volume.name}<#if volume.readonly>:ro</#if><#if volume.description?has_content> `# ${volume.description}`</#if> \
|
||||
</#list>
|
||||
</#if>
|
||||
<#if templates.ports?has_content>
|
||||
<#if templates.ports?has_content && !templates.hostNetworkingEnabled>
|
||||
<#list templates.ports as port>
|
||||
-p ${port.name?string["##0"]}:${port.name?string["##0"]}/${port.protocol}<#if port.description?has_content> `# ${port.description}`</#if> \
|
||||
</#list>
|
||||
|
||||
@ -15,7 +15,7 @@ Reference: https://bulma.io/documentation/form/input/
|
||||
requiredHelp:String - If "required" is true, some help text to be displayed
|
||||
size:enum(small, normal, large) - The size of the input
|
||||
-->
|
||||
<#macro input type id value="" label="" title="" placeholder="" extraClasses="" extraAttributes="" icon="" isRequired=false isReadonly=false isDisabled=false isInvalid=false isInline=false requiredHelp="" size="normal">
|
||||
<#macro input type id value="" label="" title="" placeholder="" extraClasses="" extraAttributes="" icon="" isRequired=false isReadonly=false isDisabled=false isInvalid=false isInline=false requiredHelp="" infoText="" size="normal">
|
||||
|
||||
<div class="field<#if isInline> is-horizontal</#if>">
|
||||
<#if isInline>
|
||||
@ -38,6 +38,9 @@ Reference: https://bulma.io/documentation/form/input/
|
||||
<#if isRequired && requiredHelp?has_content>
|
||||
<p class="help invalid-feedback is-danger">${requiredHelp}</p>
|
||||
</#if>
|
||||
<#if infoText?has_content>
|
||||
<p class="help">${infoText}</p>
|
||||
</#if>
|
||||
</div>
|
||||
<#if isInline>
|
||||
</div>
|
||||
@ -64,8 +67,8 @@ Convenience macro to generate a text input
|
||||
requiredHelp:String - If "required" is true, some help text to be displayed
|
||||
size:enum(small, normal, large) - The size of the input
|
||||
-->
|
||||
<#macro text id value="" label="" title="" placeholder="" extraClasses="" extraAttributes="" icon="" isRequired=false isReadonly=false isDisabled=false isInvalid=false isInline=false requiredHelp="" size="normal">
|
||||
<@input type="text" id=id value=value title=title label=label placeholder=placeholder extraAttributes=extraAttributes extraClasses=extraClasses icon=icon isRequired=isRequired isReadonly=isReadonly isDisabled=isDisabled isInvalid=isInvalid isInline=isInline requiredHelp=requiredHelp size=size />
|
||||
<#macro text id value="" label="" title="" placeholder="" extraClasses="" extraAttributes="" icon="" isRequired=false isReadonly=false isDisabled=false isInvalid=false isInline=false requiredHelp="" infoText="" size="normal">
|
||||
<@input type="text" id=id value=value title=title label=label placeholder=placeholder extraAttributes=extraAttributes extraClasses=extraClasses icon=icon isRequired=isRequired isReadonly=isReadonly isDisabled=isDisabled isInvalid=isInvalid isInline=isInline requiredHelp=requiredHelp infoText=infoText size=size />
|
||||
</#macro>
|
||||
|
||||
<#--
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user