diff --git a/src/main/java/io/linuxserver/fleet/core/FleetAppController.java b/src/main/java/io/linuxserver/fleet/core/FleetAppController.java
index 6f523f9..bba930a 100644
--- a/src/main/java/io/linuxserver/fleet/core/FleetAppController.java
+++ b/src/main/java/io/linuxserver/fleet/core/FleetAppController.java
@@ -161,4 +161,10 @@ public class FleetAppController extends AbstractAppController implements Service
public final AuthenticationResult authenticateUser(final String username, final String password) {
return authenticationDelegate.authenticate(username, password);
}
+
+ public final void trackBranch(final ImageKey imageKey, final String branchName) {
+
+ getRepositoryService().trackBranchOnImage(imageKey, branchName);
+ synchroniseImage(imageKey);
+ }
}
diff --git a/src/main/java/io/linuxserver/fleet/v2/Utils.java b/src/main/java/io/linuxserver/fleet/v2/Utils.java
new file mode 100644
index 0000000..1bf1958
--- /dev/null
+++ b/src/main/java/io/linuxserver/fleet/v2/Utils.java
@@ -0,0 +1,30 @@
+/*
+ * 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 .
+ */
+
+package io.linuxserver.fleet.v2;
+
+public final class Utils {
+
+ public static T ensureNotNull(final T obj) {
+
+ if (null == obj) {
+ throw new IllegalArgumentException("Parameter null");
+ }
+
+ return obj;
+ }
+}
diff --git a/src/main/java/io/linuxserver/fleet/v2/db/DefaultImageDAO.java b/src/main/java/io/linuxserver/fleet/v2/db/DefaultImageDAO.java
index 38cd898..fc31391 100644
--- a/src/main/java/io/linuxserver/fleet/v2/db/DefaultImageDAO.java
+++ b/src/main/java/io/linuxserver/fleet/v2/db/DefaultImageDAO.java
@@ -33,6 +33,7 @@ import io.linuxserver.fleet.v2.types.meta.history.ImagePullHistory;
import io.linuxserver.fleet.v2.types.meta.history.ImagePullStatistic;
import java.sql.*;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
@@ -58,6 +59,7 @@ public class DefaultImageDAO extends AbstractDAO implements ImageDAO {
private static final String GetImage = "{CALL Image_Get(?)}";
private static final String DeleteImage = "{CALL Image_Delete(?)}";
private static final String GetImageStats = "{CALL Image_GetStats(?)}";
+ private static final String DeleteStats = "{CALL Image_ClearStatsBefore(?)}";
public DefaultImageDAO(final DatabaseProvider databaseConnection) {
super(databaseConnection);
diff --git a/src/main/java/io/linuxserver/fleet/v2/db/ImageDAO.java b/src/main/java/io/linuxserver/fleet/v2/db/ImageDAO.java
index f75fe5d..5d00a43 100644
--- a/src/main/java/io/linuxserver/fleet/v2/db/ImageDAO.java
+++ b/src/main/java/io/linuxserver/fleet/v2/db/ImageDAO.java
@@ -27,6 +27,7 @@ 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 java.time.LocalDate;
import java.util.List;
public interface ImageDAO {
diff --git a/src/main/java/io/linuxserver/fleet/v2/key/AbstractHasKey.java b/src/main/java/io/linuxserver/fleet/v2/key/AbstractHasKey.java
index 3294df2..5dbea86 100644
--- a/src/main/java/io/linuxserver/fleet/v2/key/AbstractHasKey.java
+++ b/src/main/java/io/linuxserver/fleet/v2/key/AbstractHasKey.java
@@ -17,12 +17,14 @@
package io.linuxserver.fleet.v2.key;
+import io.linuxserver.fleet.v2.Utils;
+
public abstract class AbstractHasKey implements HasKey {
private final KEY key;
public AbstractHasKey(final KEY key) {
- this.key = key;
+ this.key = Utils.ensureNotNull(key);
}
@Override
diff --git a/src/main/java/io/linuxserver/fleet/v2/service/RepositoryService.java b/src/main/java/io/linuxserver/fleet/v2/service/RepositoryService.java
index bb58226..67e8e66 100644
--- a/src/main/java/io/linuxserver/fleet/v2/service/RepositoryService.java
+++ b/src/main/java/io/linuxserver/fleet/v2/service/RepositoryService.java
@@ -29,6 +29,7 @@ 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.RepositoryOutlineRequest;
+import io.linuxserver.fleet.v2.types.internal.TagBranchOutlineRequest;
import io.linuxserver.fleet.v2.types.meta.ItemSyncSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -232,6 +233,27 @@ public class RepositoryService {
}
}
+ public void trackBranchOnImage(final ImageKey imageKey, final String branchName) {
+
+ final Image image = repositoryCache.findImage(imageKey);
+ if (null == image) {
+ throw new IllegalArgumentException("Could not find image with key " + imageKey);
+ }
+
+ if (image.findTagBranchByName(branchName) != null) {
+ throw new IllegalArgumentException("Image is already tracking branch " + branchName);
+ }
+
+ final InsertUpdateResult outlineResult = imageDAO.createTagBranchOutline(new TagBranchOutlineRequest(imageKey, branchName));
+ if (outlineResult.isError()) {
+ throw new RuntimeException(outlineResult.getStatusMessage());
+ }
+
+ final Image updatableClone = image.cloneForUpdate();
+ updatableClone.addTagBranch(outlineResult.getResult());
+ storeImage(updatableClone);
+ }
+
private void updateCache(final Image storedImage) {
final Repository imageParentRepository = repositoryCache.findItem(storedImage.getRepositoryKey());
diff --git a/src/main/java/io/linuxserver/fleet/v2/thread/schedule/CheckAppVersionSchedule.java b/src/main/java/io/linuxserver/fleet/v2/thread/schedule/CheckAppVersionSchedule.java
index f787b83..8ca6634 100644
--- a/src/main/java/io/linuxserver/fleet/v2/thread/schedule/CheckAppVersionSchedule.java
+++ b/src/main/java/io/linuxserver/fleet/v2/thread/schedule/CheckAppVersionSchedule.java
@@ -28,6 +28,6 @@ public class CheckAppVersionSchedule extends AbstractAppSchedule {
@Override
public void executeSchedule() {
-
+ getLogger().info("Currently not implemented. This is a placeholder schedule");
}
}
diff --git a/src/main/java/io/linuxserver/fleet/v2/thread/schedule/TidyHistoricDataSchedule.java b/src/main/java/io/linuxserver/fleet/v2/thread/schedule/TidyHistoricDataSchedule.java
new file mode 100644
index 0000000..d9f5890
--- /dev/null
+++ b/src/main/java/io/linuxserver/fleet/v2/thread/schedule/TidyHistoricDataSchedule.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2019 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 .
+ */
+
+package io.linuxserver.fleet.v2.thread.schedule;
+
+import io.linuxserver.fleet.core.FleetAppController;
+
+public class TidyHistoricDataSchedule extends AbstractAppSchedule {
+
+ public TidyHistoricDataSchedule(final ScheduleSpec spec,
+ final FleetAppController controller) {
+ super(spec, controller);
+ }
+
+ @Override
+ public void executeSchedule() {
+ getLogger().info("Currently not implemented. This is a placeholder schedule");
+ }
+}
diff --git a/src/main/java/io/linuxserver/fleet/v2/types/Image.java b/src/main/java/io/linuxserver/fleet/v2/types/Image.java
index fd1963e..e5320f6 100644
--- a/src/main/java/io/linuxserver/fleet/v2/types/Image.java
+++ b/src/main/java/io/linuxserver/fleet/v2/types/Image.java
@@ -67,6 +67,10 @@ public class Image extends AbstractSyncItem {
return cloned;
}
+ public final Image cloneForUpdate() {
+ return cloneWithSyncSpec(getSpec());
+ }
+
@Override
public final Image cloneWithSyncSpec(final ItemSyncSpec syncSpec) {
diff --git a/src/main/java/io/linuxserver/fleet/v2/types/TagBranch.java b/src/main/java/io/linuxserver/fleet/v2/types/TagBranch.java
index 63fedab..e3a282b 100644
--- a/src/main/java/io/linuxserver/fleet/v2/types/TagBranch.java
+++ b/src/main/java/io/linuxserver/fleet/v2/types/TagBranch.java
@@ -18,6 +18,8 @@
package io.linuxserver.fleet.v2.types;
import io.linuxserver.fleet.v2.key.AbstractHasKey;
+import io.linuxserver.fleet.v2.key.HasKey;
+import io.linuxserver.fleet.v2.key.ImageKey;
import io.linuxserver.fleet.v2.key.TagBranchKey;
import java.util.concurrent.atomic.AtomicReference;
diff --git a/src/main/java/io/linuxserver/fleet/v2/types/api/ApiImagePullHistoryWrapper.java b/src/main/java/io/linuxserver/fleet/v2/types/api/ApiImagePullHistoryWrapper.java
index e480261..144bc43 100644
--- a/src/main/java/io/linuxserver/fleet/v2/types/api/ApiImagePullHistoryWrapper.java
+++ b/src/main/java/io/linuxserver/fleet/v2/types/api/ApiImagePullHistoryWrapper.java
@@ -24,8 +24,16 @@ import java.util.stream.Collectors;
public class ApiImagePullHistoryWrapper extends AbstractApiWrapper> {
- public ApiImagePullHistoryWrapper(final List originalObject) {
+ private final ImagePullStatistic.StatGroupMode groupMode;
+
+ public ApiImagePullHistoryWrapper(final List originalObject,
+ final ImagePullStatistic.StatGroupMode groupMode) {
super(originalObject);
+ this.groupMode = groupMode;
+ }
+
+ public final String getGroupModeDataPoint() {
+ return groupMode.getDataPoint();
}
public final List getLabels() {
@@ -36,6 +44,10 @@ public class ApiImagePullHistoryWrapper extends AbstractApiWrapper.
+ */
+
+package io.linuxserver.fleet.v2.types.docker;
+
+public enum DockerCapability {
+
+ AUDIT_CONTROL,
+ AUDIT_WRITE,
+ BLOCK_SUSPEND,
+ CHOWN,
+ DAC_OVERRIDE,
+ DAC_READ_SEARCH,
+ FOWNER,
+ FSETID,
+ IPC_LOCK,
+ IPC_OWNER,
+ KILL,
+ LEASE,
+ LINUX_IMMUTABLE,
+ MAC_ADMIN,
+ MAC_OVERRIDE,
+ MKNOD,
+ NET_ADMIN,
+ NET_BIND_SERVICE,
+ NET_BROADCAST,
+ NET_RAW,
+ SETFCAP,
+ SETGID,
+ SETPCAP,
+ SETUID,
+ SYSLOG,
+ SYS_ADMIN,
+ SYS_BOOT,
+ SYS_CHROOT,
+ SYS_MODULE,
+ SYS_NICE,
+ SYS_PACCT,
+ SYS_PTRACE,
+ SYS_RAWIO,
+ SYS_RESOURCE,
+ SYS_TIME,
+ SYS_TTY_CONFIG,
+ WAKE_ALARM;
+}
diff --git a/src/main/java/io/linuxserver/fleet/v2/types/meta/history/ImagePullStatistic.java b/src/main/java/io/linuxserver/fleet/v2/types/meta/history/ImagePullStatistic.java
index 4681151..729e47b 100644
--- a/src/main/java/io/linuxserver/fleet/v2/types/meta/history/ImagePullStatistic.java
+++ b/src/main/java/io/linuxserver/fleet/v2/types/meta/history/ImagePullStatistic.java
@@ -76,6 +76,19 @@ public class ImagePullStatistic implements Comparable {
}
public enum StatGroupMode {
- Day, Week, Month;
+
+ Day("hour"),
+ Week("day"),
+ Month("day");
+
+ private final String dataPoints;
+
+ StatGroupMode(final String dataPoints) {
+ this.dataPoints = dataPoints;
+ }
+
+ public final String getDataPoint() {
+ return dataPoints;
+ }
}
}
diff --git a/src/main/java/io/linuxserver/fleet/v2/web/Locations.java b/src/main/java/io/linuxserver/fleet/v2/web/Locations.java
index 8fc653d..3ba5dde 100644
--- a/src/main/java/io/linuxserver/fleet/v2/web/Locations.java
+++ b/src/main/java/io/linuxserver/fleet/v2/web/Locations.java
@@ -45,12 +45,14 @@ public interface Locations {
String Schedule = "schedule";
String Sync = "sync";
String Stats = "stats";
+ String Track = "track";
}
interface Admin {
String Repositories = "/admin/repositories";
String Images = "/admin/images";
+ String ImageEdit = "/admin/image";
String Schedules = "/admin/schedules";
String Users = "/admin/users";
}
diff --git a/src/main/java/io/linuxserver/fleet/v2/web/WebRouteController.java b/src/main/java/io/linuxserver/fleet/v2/web/WebRouteController.java
index 20665e8..f31967b 100644
--- a/src/main/java/io/linuxserver/fleet/v2/web/WebRouteController.java
+++ b/src/main/java/io/linuxserver/fleet/v2/web/WebRouteController.java
@@ -64,6 +64,7 @@ public class WebRouteController {
get(Locations.Admin.Repositories, new AdminRepositoryController(app), roles(AppRole.Anyone));
get(Locations.Admin.Images, new AdminImageController( app), roles(AppRole.Anyone));
+ get(Locations.Admin.ImageEdit, new AdminImageEditController( app), roles(AppRole.Anyone));
get(Locations.Admin.Schedules, new AdminScheduleController( app), roles(AppRole.Anyone));
path(Locations.Internal.Api, () -> {
@@ -90,6 +91,10 @@ public class WebRouteController {
path(Locations.Internal.Stats, () -> {
get(apiController::getImagePullHistory, roles(AppRole.Anyone));
});
+
+ path(Locations.Internal.Track, () -> {
+ put(apiController::trackNewBranch, roles(AppRole.Anyone));
+ });
});
path(Locations.Internal.Schedule, () -> {
diff --git a/src/main/java/io/linuxserver/fleet/v2/web/routes/AdminImageEditController.java b/src/main/java/io/linuxserver/fleet/v2/web/routes/AdminImageEditController.java
new file mode 100644
index 0000000..1efa409
--- /dev/null
+++ b/src/main/java/io/linuxserver/fleet/v2/web/routes/AdminImageEditController.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2019 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 .
+ */
+
+package io.linuxserver.fleet.v2.web.routes;
+
+import io.javalin.http.Context;
+import io.linuxserver.fleet.core.FleetAppController;
+import io.linuxserver.fleet.v2.key.ImageKey;
+import io.linuxserver.fleet.v2.service.RepositoryService;
+import io.linuxserver.fleet.v2.types.docker.DockerCapability;
+import io.linuxserver.fleet.v2.web.PageModelSpec;
+
+public class AdminImageEditController extends AbstractPageHandler {
+
+ private RepositoryService repositoryService;
+
+ public AdminImageEditController(final FleetAppController controller) {
+ super(controller);
+ repositoryService = controller.getRepositoryService();
+ }
+
+ @Override
+ protected PageModelSpec handlePageLoad(final Context ctx) {
+
+ final String imageKeyParam = ctx.queryParam("imageKey");
+ if (null != imageKeyParam) {
+
+ final PageModelSpec modelSpec = new PageModelSpec("views/pages/admin/image-edit.ftl");
+ modelSpec.addModelAttribute("image", repositoryService.getImage(ImageKey.parse(imageKeyParam)));
+ modelSpec.addModelAttribute("containerCapabilities", DockerCapability.values());
+ return modelSpec;
+
+ } else {
+ return new PageModelSpec("views/pages/not-found.ftl");
+ }
+ }
+
+ @Override
+ protected PageModelSpec handleFormSubmission(final Context ctx) {
+ return null;
+ }
+}
diff --git a/src/main/java/io/linuxserver/fleet/v2/web/routes/InternalApiController.java b/src/main/java/io/linuxserver/fleet/v2/web/routes/InternalApiController.java
index 1a32968..2535b97 100644
--- a/src/main/java/io/linuxserver/fleet/v2/web/routes/InternalApiController.java
+++ b/src/main/java/io/linuxserver/fleet/v2/web/routes/InternalApiController.java
@@ -172,7 +172,21 @@ public class InternalApiController extends AbstractAppService {
final ImagePullStatistic.StatGroupMode groupMode = ctx.queryParam("groupMode", ImagePullStatistic.StatGroupMode.class).get();
final Image cachedImage = getController().getRepositoryService().getImage(ImageKey.parse(imageKeyParam));
- ctx.json(new ApiImagePullHistoryWrapper(cachedImage.getMetaData().getHistoryFor(groupMode)));
+ ctx.json(new ApiImagePullHistoryWrapper(cachedImage.getMetaData().getHistoryFor(groupMode), groupMode));
+
+ } catch (IllegalArgumentException e) {
+ throw new ApiException(e.getMessage(), e);
+ }
+ }
+
+ public void trackNewBranch(final Context ctx) {
+
+ try {
+
+ final String imageKeyParam = ctx.formParam("imageKey", String.class).get();
+ final String branchName = ctx.formParam("branchName", String.class).get();
+
+ getController().trackBranch(ImageKey.parse(imageKeyParam), branchName);
} catch (IllegalArgumentException e) {
throw new ApiException(e.getMessage(), e);
diff --git a/src/main/resources/db/migration/V2.1__MigrateToNewTables.sql b/src/main/resources/db/migration/V2.1__MigrateToNewTables.sql
index 445f057..73fcc2b 100644
--- a/src/main/resources/db/migration/V2.1__MigrateToNewTables.sql
+++ b/src/main/resources/db/migration/V2.1__MigrateToNewTables.sql
@@ -24,4 +24,5 @@ VALUES
('SyncAllCachedImages', '1:hours', '0:minutes', 'io.linuxserver.fleet.v2.thread.schedule.sync.AllImagesSyncSchedule'),
('GetMissingImages', '30:minutes', '0:minutes', 'io.linuxserver.fleet.v2.thread.schedule.sync.GetMissingImagesSchedule'),
('RefreshCache', '1:days', '15:minutes', 'io.linuxserver.fleet.v2.thread.schedule.cache.RefreshCacheSchedule'),
+ ('TidyHistoricData', '1:days', '0:minutes', 'io.linuxserver.fleet.v2.thread.schedule.TidyHistoricDataSchedule'),
('CheckAppVersion', '1:days', '0:minutes', 'io.linuxserver.fleet.v2.thread.schedule.CheckAppVersionSchedule');
diff --git a/src/main/resources/static/assets/js/admin.js b/src/main/resources/static/assets/js/admin.js
index 508665a..ef283ca 100644
--- a/src/main/resources/static/assets/js/admin.js
+++ b/src/main/resources/static/assets/js/admin.js
@@ -187,6 +187,23 @@ var adminManager = (function($) {
ajaxManager.call(request, function() {});
};
+ var trackNewBranch = function(branchName, imageKey) {
+
+ var request = {
+
+ url: '/internalapi/image/track',
+ method: 'put',
+ data: {
+ 'imageKey': imageKey,
+ 'branchName': branchName
+ }
+ };
+
+ ajaxManager.call(request, function() {
+ window.location.reload();
+ });
+ };
+
var cleanEmpty = function(val) {
return (typeof val === 'undefined' || $.trim(val).length === 0) ? null : val;
};
@@ -233,6 +250,14 @@ var adminManager = (function($) {
$('.sync-image').on('click', function() {
syncImage($(this));
});
+
+ $('#TrackNewBranch').on('click', function() {
+
+ var branchName = $.trim($('#NewTrackedBranch').val());
+ if (branchName.length > 0) {
+ trackNewBranch(branchName, $('#ImageKey').val());
+ }
+ });
};
return {
diff --git a/src/main/resources/static/assets/js/app.js b/src/main/resources/static/assets/js/app.js
index 621cac1..1a19116 100644
--- a/src/main/resources/static/assets/js/app.js
+++ b/src/main/resources/static/assets/js/app.js
@@ -268,6 +268,19 @@ var imageSearchManager = (function($) {
var chartManager = (function($) {
+ var formatNumber = function(num) {
+
+ var array = num.toString().split('');
+ var index = -3;
+
+ while (array.length + index > 0) {
+ array.splice(index, 0, ',');
+ index -= 4;
+ }
+
+ return array.join('');
+ };
+
var populateChart = function(imageKey, groupMode) {
var request = {
@@ -278,16 +291,29 @@ var chartManager = (function($) {
ajaxManager.call(request, function(history) {
- var ctx = document.getElementById('ImagePullHistory').getContext('2d');
+ $('#PullActivityDataPoint').text(history.groupModeDataPoint);
+ $('#PullActivityRate').text(formatNumber(history.mean));
+
+ var ctx = document.getElementById('ImagePullHistory').getContext('2d');
+ var gradient = ctx.createLinearGradient(0, 0, 0, 400);
+ gradient.addColorStop(0, 'rgba(0, 209, 178, 0.5)');
+ gradient.addColorStop(0.3, 'rgba(0, 209, 178, 0)');
+
new Chart(ctx, {
type: 'line',
data: {
labels: history.pullDifferential.labels,
- datasets: [{
- data: history.pullDifferential.pulls,
- borderColor: 'rgba(0, 209, 178, 1)',
- backgroundColor: 'rgba(0, 209, 178, 0.3)'
- }]
+ datasets: [
+ {
+ lineTension: 0,
+ data: history.pullDifferential.pulls,
+ pointRadius: 0,
+ pointHitRadius: 2,
+ borderWidth: 2,
+ borderColor: 'rgba(0, 209, 178, 1)',
+ backgroundColor : gradient
+ }
+ ]
},
options: {
responsive: true,
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
index 9a8156c..845ce7f 100644
--- a/src/main/resources/version.properties
+++ b/src/main/resources/version.properties
@@ -1,5 +1,5 @@
-#Fri Jan 03 17:22:49 GMT 2020
-app.build.date=2020-01-03T17\:22\:49
+#Sat Jan 04 18:27:43 GMT 2020
+app.build.date=2020-01-04T18\:27\:43
app.build.os=Linux
app.build.user=josh
app.version=2.0.0
diff --git a/src/main/resources/views/pages/admin/image-edit.ftl b/src/main/resources/views/pages/admin/image-edit.ftl
new file mode 100644
index 0000000..7bf959a
--- /dev/null
+++ b/src/main/resources/views/pages/admin/image-edit.ftl
@@ -0,0 +1,270 @@
+<#--
+ Copyright (c) 2019 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 .
+-->
+<#import "../../prebuilt/base.ftl" as base />
+<#import "../../prebuilt/fleet-title.ftl" as title />
+
+<#import "../../ui/components/dropdown.ftl" as dropdown />
+<#import "../../ui/layout/section.ftl" as section />
+<#import "../../ui/layout/container.ftl" as container />
+<#import "../../ui/form/input.ftl" as input />
+<#import "../../ui/elements/button.ftl" as button />
+<#import "../../ui/elements/table.ftl" as table />
+
+<@base.base title='Edit ${image.name} | Admin' context="admin_image_edit">
+
+ <#if image?has_content>
+
+
+
+ <@section.section id="ManageImage">
+ <@container.container>
+
+