mirror of
https://github.com/linuxserver/fleet.git
synced 2026-02-20 05:11:08 +08:00
commit
d1aeb6e7e9
@ -11,7 +11,7 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
version = '1.4.0'
|
||||
version = '1.4.1'
|
||||
|
||||
sourceSets {
|
||||
|
||||
|
||||
@ -18,20 +18,19 @@
|
||||
package io.linuxserver.fleet.cache;
|
||||
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ImageCache {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ImageCache.class);
|
||||
|
||||
private final Map<Integer, List<Image>> cachedImages;
|
||||
private final Map<RepositoryKey, List<Image>> cachedImages;
|
||||
|
||||
public ImageCache() {
|
||||
this.cachedImages = new HashMap<>();
|
||||
@ -41,19 +40,19 @@ public class ImageCache {
|
||||
|
||||
Image updatedImage = Image.copyOf(image);
|
||||
|
||||
if (cachedImages.containsKey(updatedImage.getRepositoryId())) {
|
||||
if (cachedImages.containsKey(updatedImage.getKey().getRepositoryKey())) {
|
||||
|
||||
List<Image> images = cachedImages.get(updatedImage.getRepositoryId());
|
||||
List<Image> images = cachedImages.get(updatedImage.getKey().getRepositoryKey());
|
||||
int cachedImageLocation = images.indexOf(updatedImage);
|
||||
|
||||
if (cachedImageLocation > -1) {
|
||||
|
||||
LOGGER.info("updateCache({}) Updating existing cached image.", updatedImage.getName());
|
||||
LOGGER.info("updateCache({}) Updating existing cached image.", updatedImage);
|
||||
images.set(cachedImageLocation, updatedImage);
|
||||
|
||||
} else {
|
||||
|
||||
LOGGER.info("updateCache({}) Adding image to cache", updatedImage.getName());
|
||||
LOGGER.info("updateCache({}) Adding image to cache", updatedImage);
|
||||
images.add(updatedImage);
|
||||
}
|
||||
|
||||
@ -62,33 +61,37 @@ public class ImageCache {
|
||||
List<Image> images = new ArrayList<>();
|
||||
images.add(updatedImage);
|
||||
|
||||
LOGGER.info("updateCache({}:{}) Creating new cache for repository {}", updatedImage.getName(), updatedImage.getRepositoryId());
|
||||
cachedImages.put(updatedImage.getRepositoryId(), images);
|
||||
LOGGER.info("updateCache({}) Creating new cache for repository {}", updatedImage, updatedImage.getRepositoryId());
|
||||
cachedImages.put(updatedImage.getKey().getRepositoryKey(), images);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Image> getAll(int repositoryId) {
|
||||
public List<Image> getAll(RepositoryKey repositoryKey) {
|
||||
|
||||
if (cachedImages.containsKey(repositoryId)) {
|
||||
return cachedImages.get(repositoryId).stream().map(Image::copyOf).collect(Collectors.toList());
|
||||
if (cachedImages.containsKey(repositoryKey)) {
|
||||
return cachedImages.get(repositoryKey).stream().map(Image::copyOf).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public Image get(int repositoryId, String imageName) {
|
||||
public Image get(ImageKey imageKey) {
|
||||
|
||||
if (cachedImages.containsKey(repositoryId)) {
|
||||
return cachedImages.get(repositoryId).stream().filter(i -> i.getName().equals(imageName)).findFirst().orElse(null);
|
||||
final RepositoryKey repositoryKey = imageKey.getRepositoryKey();
|
||||
|
||||
if (cachedImages.containsKey(repositoryKey)) {
|
||||
return cachedImages.get(repositoryKey).stream().filter(i -> i.getName().equals(imageKey.getName())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void remove(Image image) {
|
||||
public void remove(ImageKey imageKey) {
|
||||
|
||||
if (cachedImages.containsKey(image.getRepositoryId())) {
|
||||
cachedImages.get(image.getRepositoryId()).remove(image);
|
||||
final RepositoryKey repositoryKey = imageKey.getRepositoryKey();
|
||||
|
||||
if (cachedImages.containsKey(repositoryKey)) {
|
||||
cachedImages.get(repositoryKey).removeIf(image -> image.getKey().equals(imageKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,7 +131,8 @@ public class FleetApp {
|
||||
path("/api/v1", () -> {
|
||||
|
||||
get("/images", new AllImagesApi(beans.getRepositoryDelegate(), beans.getImageDelegate()), new JsonTransformer());
|
||||
get("/pullHistory", new GetImagePullHistoryApi(beans.getImageDelegate()), new JsonTransformer());
|
||||
get("/image", new GetImageApi(beans.getImageDelegate()), new JsonTransformer());
|
||||
get("/pullHistory", new GetImagePullHistoryApi(beans.getImageDelegate()), new JsonTransformer());
|
||||
|
||||
after("/*", (request, response) -> {
|
||||
|
||||
|
||||
@ -27,7 +27,11 @@ import io.linuxserver.fleet.db.dao.DefaultUserDAO;
|
||||
import io.linuxserver.fleet.db.migration.DatabaseVersion;
|
||||
import io.linuxserver.fleet.delegate.*;
|
||||
import io.linuxserver.fleet.dockerhub.DockerHubV2Client;
|
||||
import io.linuxserver.fleet.dockerhub.queue.DockerHubSyncConsumer;
|
||||
import io.linuxserver.fleet.dockerhub.queue.DockerHubSyncQueue;
|
||||
import io.linuxserver.fleet.thread.TaskManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -36,6 +40,8 @@ import io.linuxserver.fleet.thread.TaskManager;
|
||||
*/
|
||||
public class FleetBeans {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FleetBeans.class);
|
||||
|
||||
private final FleetProperties properties;
|
||||
private final ImageDelegate imageDelegate;
|
||||
private final RepositoryDelegate repositoryDelegate;
|
||||
@ -46,6 +52,7 @@ public class FleetBeans {
|
||||
private final TaskDelegate taskDelegate;
|
||||
private final UserDelegate userDelegate;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final DockerHubSyncQueue dockerHubSyncQueue;
|
||||
|
||||
/**
|
||||
* Ensures the database is kept up to date.
|
||||
@ -68,6 +75,14 @@ public class FleetBeans {
|
||||
userDelegate = new UserDelegate(passwordEncoder, new DefaultUserDAO(databaseConnection));
|
||||
taskDelegate = new TaskDelegate(this);
|
||||
authenticationDelegate = new DefaultAuthenticationDelegate(AuthenticatorFactory.getAuthenticator(this));
|
||||
dockerHubSyncQueue = new DockerHubSyncQueue();
|
||||
|
||||
final int consumerThreadCount = properties.getQueueThreadCount();
|
||||
for (int i = 0; i < consumerThreadCount; i++) {
|
||||
|
||||
LOGGER.info("Starting consumer thread " + i + "...");
|
||||
new DockerHubSyncConsumer(imageDelegate, dockerHubSyncQueue, "SyncThread-" + i).start();
|
||||
}
|
||||
}
|
||||
|
||||
public FleetProperties getProperties() {
|
||||
|
||||
@ -79,6 +79,10 @@ public class FleetProperties {
|
||||
return new DockerHubCredentials(username, password);
|
||||
}
|
||||
|
||||
public int getQueueThreadCount() {
|
||||
return Integer.parseInt(getStringProperty("fleet.queue.threads"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Obtains the property value from three separate sources: first from the config file. If not present, it will look
|
||||
|
||||
@ -24,6 +24,8 @@ import io.linuxserver.fleet.db.query.LimitedResult;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.model.internal.ImagePullStat;
|
||||
import io.linuxserver.fleet.model.internal.Tag;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -45,15 +47,15 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image findImageByRepositoryAndImageName(int repositoryId, String imageName) {
|
||||
public Image findImageByRepositoryAndImageName(ImageKey imageKey) {
|
||||
|
||||
CallableStatement call = null;
|
||||
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
call = connection.prepareCall("{CALL Image_GetByName(?,?)}");
|
||||
call.setInt(1, repositoryId);
|
||||
call.setString(2, imageName);
|
||||
call.setInt(1, imageKey.getRepositoryKey().getId());
|
||||
call.setString(2, imageKey.getName());
|
||||
|
||||
ResultSet results = call.executeQuery();
|
||||
if (results.next())
|
||||
@ -69,16 +71,16 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image fetchImage(Integer id) {
|
||||
public Image fetchImage(ImageKey imageKey) {
|
||||
|
||||
LOGGER.debug("Fetching image by ID: " + id);
|
||||
LOGGER.debug("Fetching image by ID: " + imageKey);
|
||||
|
||||
CallableStatement call = null;
|
||||
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
call = connection.prepareCall("{CALL Image_Get(?)}");
|
||||
call.setInt(1, id);
|
||||
call.setInt(1, imageKey.getId());
|
||||
|
||||
ResultSet results = call.executeQuery();
|
||||
if (results.next())
|
||||
@ -94,7 +96,7 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LimitedResult<Image> fetchImagesByRepository(int repositoryId) {
|
||||
public LimitedResult<Image> fetchImagesByRepository(final RepositoryKey repositoryKey) {
|
||||
|
||||
List<Image> images = new ArrayList<>();
|
||||
|
||||
@ -103,7 +105,7 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
call = connection.prepareCall("{CALL Image_GetAll(?,?)}");
|
||||
call.setInt(1, repositoryId);
|
||||
call.setInt(1, repositoryKey.getId());
|
||||
call.registerOutParameter(2, Types.INTEGER);
|
||||
|
||||
ResultSet results = call.executeQuery();
|
||||
@ -130,7 +132,7 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
call = connection.prepareCall("{CALL Image_Save(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
|
||||
setNullableInt(call, 1, image.getId());
|
||||
setNullableInt(call, 1, image.getKey().getId());
|
||||
call.setInt(2, image.getRepositoryId());
|
||||
call.setString(3, image.getName());
|
||||
setNullableLong(call, 4, image.getPullCount());
|
||||
@ -154,7 +156,7 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
String statusMessage = call.getString(15);
|
||||
|
||||
if (InsertUpdateStatus.OK == status)
|
||||
return new InsertUpdateResult<>(fetchImage(imageId), status, statusMessage);
|
||||
return new InsertUpdateResult<>(fetchImage(image.getKey().cloneWithId(imageId)), status, statusMessage);
|
||||
|
||||
return new InsertUpdateResult<>(status, statusMessage);
|
||||
|
||||
@ -169,14 +171,14 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeImage(Integer id) {
|
||||
public void removeImage(final ImageKey imageKey) {
|
||||
|
||||
CallableStatement call = null;
|
||||
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
call = connection.prepareCall("{CALL Image_Delete(?)}");
|
||||
call.setInt(1, id);
|
||||
call.setInt(1, imageKey.getId());
|
||||
|
||||
call.executeUpdate();
|
||||
call.close();
|
||||
@ -189,7 +191,7 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ImagePullStat> fetchImagePullHistory(Integer imageId, ImagePullStat.GroupMode groupMode) {
|
||||
public List<ImagePullStat> fetchImagePullHistory(final ImageKey imageKey, ImagePullStat.GroupMode groupMode) {
|
||||
|
||||
List<ImagePullStat> pullHistory = new ArrayList<>();
|
||||
|
||||
@ -198,7 +200,7 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
call = connection.prepareCall("CALL Image_GetPullHistory(?, ?)");
|
||||
call.setInt(1, imageId);
|
||||
call.setInt(1, imageKey.getId());
|
||||
call.setString(2, groupMode.toString());
|
||||
|
||||
ResultSet results = call.executeQuery();
|
||||
@ -229,9 +231,14 @@ public class DefaultImageDAO implements ImageDAO {
|
||||
private Image parseImageFromResultSet(ResultSet results) throws SQLException {
|
||||
|
||||
Image image = new Image(
|
||||
results.getInt("ImageId"),
|
||||
results.getInt("RepositoryId"),
|
||||
results.getString("ImageName"),
|
||||
new ImageKey(
|
||||
results.getInt("ImageId"),
|
||||
results.getString("ImageName"),
|
||||
new RepositoryKey(
|
||||
results.getInt("RepositoryId"),
|
||||
results.getString("RepositoryName")
|
||||
)
|
||||
),
|
||||
new Tag(
|
||||
results.getString("LatestTagVersion"),
|
||||
results.getString("LatestMaskedTagVersion"),
|
||||
|
||||
@ -21,6 +21,7 @@ import io.linuxserver.fleet.db.PoolingDatabaseConnection;
|
||||
import io.linuxserver.fleet.db.query.InsertUpdateResult;
|
||||
import io.linuxserver.fleet.db.query.InsertUpdateStatus;
|
||||
import io.linuxserver.fleet.model.internal.Repository;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -28,9 +29,7 @@ import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static io.linuxserver.fleet.db.dao.Utils.safeClose;
|
||||
import static io.linuxserver.fleet.db.dao.Utils.setNullableInt;
|
||||
import static io.linuxserver.fleet.db.dao.Utils.setNullableString;
|
||||
import static io.linuxserver.fleet.db.dao.Utils.*;
|
||||
|
||||
public class DefaultRepositoryDAO implements RepositoryDAO {
|
||||
|
||||
@ -43,14 +42,14 @@ public class DefaultRepositoryDAO implements RepositoryDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository fetchRepository(int id) {
|
||||
public Repository fetchRepository(RepositoryKey repositoryKey) {
|
||||
|
||||
CallableStatement call = null;
|
||||
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
call = connection.prepareCall("{CALL Repository_Get(?)}");
|
||||
call.setInt(1, id);
|
||||
call.setInt(1, repositoryKey.getId());
|
||||
|
||||
ResultSet results = call.executeQuery();
|
||||
if (results.next())
|
||||
@ -73,7 +72,7 @@ public class DefaultRepositoryDAO implements RepositoryDAO {
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
call = connection.prepareCall("{CALL Repository_Save(?,?,?,?,?,?,?)}");
|
||||
setNullableInt(call, 1, repository.getId());
|
||||
setNullableInt(call, 1, repository.getKey().getId());
|
||||
call.setString(2, repository.getName());
|
||||
setNullableString(call, 3, repository.getVersionMask());
|
||||
call.setBoolean(4, repository.isSyncEnabled());
|
||||
@ -89,7 +88,7 @@ public class DefaultRepositoryDAO implements RepositoryDAO {
|
||||
String statusMessage = call.getString(7);
|
||||
|
||||
if (InsertUpdateStatus.OK == status)
|
||||
return new InsertUpdateResult<>(fetchRepository(repositoryId), status, statusMessage);
|
||||
return new InsertUpdateResult<>(fetchRepository(repository.getKey().cloneWithId(repositoryId)), status, statusMessage);
|
||||
|
||||
return new InsertUpdateResult<>(status, statusMessage);
|
||||
|
||||
@ -171,7 +170,7 @@ public class DefaultRepositoryDAO implements RepositoryDAO {
|
||||
|
||||
private Repository parseRepositoryFromResultSet(ResultSet results) throws SQLException {
|
||||
|
||||
Repository repository = new Repository(results.getInt("RepositoryId"), results.getString("RepositoryName"))
|
||||
Repository repository = new Repository(new RepositoryKey(results.getInt("RepositoryId"), results.getString("RepositoryName")))
|
||||
.withSyncEnabled(results.getBoolean("SyncEnabled"))
|
||||
.withVersionMask(results.getString("RepositoryVersionMask"))
|
||||
.withModifiedTime(results.getTimestamp("ModifiedTime").toLocalDateTime());
|
||||
|
||||
@ -85,7 +85,7 @@ public class DefaultUserDAO implements UserDAO {
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
CallableStatement call = connection.prepareCall("{CALL User_Save(?,?,?,?,?,?)}");
|
||||
setNullableInt(call, 1, user.getId());
|
||||
setNullableInt(call, 1, user.getKey().getId());
|
||||
call.setString(2, user.getUsername());
|
||||
setNullableString(call, 3, user.getPassword());
|
||||
|
||||
@ -137,7 +137,7 @@ public class DefaultUserDAO implements UserDAO {
|
||||
try (Connection connection = databaseConnection.getConnection()) {
|
||||
|
||||
CallableStatement call = connection.prepareCall("{CALL User_Delete(?)}");
|
||||
call.setInt(1, user.getId());
|
||||
call.setInt(1, user.getKey().getId());
|
||||
|
||||
call.executeUpdate();
|
||||
|
||||
|
||||
@ -4,20 +4,22 @@ import io.linuxserver.fleet.db.query.InsertUpdateResult;
|
||||
import io.linuxserver.fleet.db.query.LimitedResult;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.model.internal.ImagePullStat;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ImageDAO {
|
||||
|
||||
Image findImageByRepositoryAndImageName(int repositoryId, String imageName);
|
||||
Image findImageByRepositoryAndImageName(ImageKey imageKey);
|
||||
|
||||
Image fetchImage(Integer id);
|
||||
Image fetchImage(ImageKey imageKey);
|
||||
|
||||
LimitedResult<Image> fetchImagesByRepository(int repositoryId);
|
||||
LimitedResult<Image> fetchImagesByRepository(RepositoryKey repositoryKey);
|
||||
|
||||
InsertUpdateResult<Image> saveImage(Image image);
|
||||
|
||||
void removeImage(Integer id);
|
||||
void removeImage(ImageKey imageKey);
|
||||
|
||||
List<ImagePullStat> fetchImagePullHistory(Integer imageId, ImagePullStat.GroupMode groupMode);
|
||||
List<ImagePullStat> fetchImagePullHistory(ImageKey imageKey, ImagePullStat.GroupMode groupMode);
|
||||
}
|
||||
|
||||
@ -2,12 +2,13 @@ package io.linuxserver.fleet.db.dao;
|
||||
|
||||
import io.linuxserver.fleet.db.query.InsertUpdateResult;
|
||||
import io.linuxserver.fleet.model.internal.Repository;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RepositoryDAO {
|
||||
|
||||
Repository fetchRepository(int id);
|
||||
Repository fetchRepository(RepositoryKey repositoryKey);
|
||||
|
||||
InsertUpdateResult<Repository> saveRepository(Repository repository);
|
||||
|
||||
|
||||
@ -63,6 +63,10 @@ public class DockerHubDelegate {
|
||||
return images;
|
||||
}
|
||||
|
||||
public DockerImage fetchImageFromRepository(String repositoryName, String imageName) {
|
||||
return convertImage(dockerHubClient.fetchImageFromRepository(repositoryName, imageName));
|
||||
}
|
||||
|
||||
public List<DockerTag> fetchAllTagsForImage(String repositoryName, String imageName) {
|
||||
return dockerHubClient.fetchAllTagsForImage(repositoryName, imageName).stream().map(this::convertTag).collect(Collectors.toList());
|
||||
}
|
||||
@ -80,6 +84,10 @@ public class DockerHubDelegate {
|
||||
|
||||
private DockerImage convertImage(DockerHubV2Image dockerHubV2Image) {
|
||||
|
||||
if (dockerHubV2Image == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DockerImage(
|
||||
dockerHubV2Image.getName(),
|
||||
dockerHubV2Image.getNamespace(),
|
||||
@ -92,6 +100,10 @@ public class DockerHubDelegate {
|
||||
|
||||
private DockerTag convertTag(DockerHubV2Tag dockerHubV2Tag) {
|
||||
|
||||
if (dockerHubV2Tag == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DockerTag(
|
||||
dockerHubV2Tag.getName(),
|
||||
dockerHubV2Tag.getFullSize(),
|
||||
|
||||
@ -24,6 +24,8 @@ import io.linuxserver.fleet.db.query.InsertUpdateStatus;
|
||||
import io.linuxserver.fleet.exception.SaveException;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.model.internal.ImagePullStat;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -45,13 +47,13 @@ public class ImageDelegate {
|
||||
* already has the image stored against the repository.
|
||||
* </p>
|
||||
*/
|
||||
public Image findImageByRepositoryAndImageName(int repositoryId, String imageName) {
|
||||
public Image findImageByRepositoryAndImageName(final ImageKey imageKey) {
|
||||
|
||||
Image cachedImage = imageCache.get(repositoryId, imageName);
|
||||
Image cachedImage = imageCache.get(imageKey);
|
||||
|
||||
if (null == cachedImage) {
|
||||
|
||||
Image image = imageDAO.findImageByRepositoryAndImageName(repositoryId, imageName);
|
||||
Image image = imageDAO.findImageByRepositoryAndImageName(imageKey);
|
||||
|
||||
if (null != image) {
|
||||
imageCache.updateCache(image);
|
||||
@ -63,16 +65,30 @@ public class ImageDelegate {
|
||||
return cachedImage;
|
||||
}
|
||||
|
||||
public Image fetchImage(int id) {
|
||||
return imageDAO.fetchImage(id);
|
||||
public Image fetchImage(ImageKey imageKey) {
|
||||
|
||||
Image cachedImage = imageCache.get(imageKey);
|
||||
|
||||
if (null == cachedImage) {
|
||||
|
||||
Image image = imageDAO.fetchImage(imageKey);
|
||||
|
||||
if (null != image) {
|
||||
imageCache.updateCache(image);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
return cachedImage;
|
||||
}
|
||||
|
||||
public List<Image> fetchImagesByRepository(int repositoryId) {
|
||||
public List<Image> fetchImagesByRepository(final RepositoryKey repositoryKey) {
|
||||
|
||||
List<Image> cachedImages = imageCache.getAll(repositoryId);
|
||||
List<Image> cachedImages = imageCache.getAll(repositoryKey);
|
||||
if (cachedImages.isEmpty()) {
|
||||
|
||||
List<Image> images = imageDAO.fetchImagesByRepository(repositoryId).getResults();
|
||||
List<Image> images = imageDAO.fetchImagesByRepository(repositoryKey).getResults();
|
||||
images.forEach(imageCache::updateCache);
|
||||
|
||||
return images;
|
||||
@ -81,13 +97,13 @@ public class ImageDelegate {
|
||||
return cachedImages;
|
||||
}
|
||||
|
||||
public void removeImage(Integer id) {
|
||||
public void removeImage(ImageKey imageKey) {
|
||||
|
||||
Image existingImage = imageDAO.fetchImage(id);
|
||||
Image existingImage = imageDAO.fetchImage(imageKey);
|
||||
if (null != existingImage) {
|
||||
|
||||
imageDAO.removeImage(id);
|
||||
imageCache.remove(existingImage);
|
||||
imageDAO.removeImage(imageKey);
|
||||
imageCache.remove(imageKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +122,7 @@ public class ImageDelegate {
|
||||
throw new SaveException(result.getStatusMessage());
|
||||
}
|
||||
|
||||
public List<ImagePullStat> fetchImagePullHistory(int id, ImagePullStat.GroupMode groupMode) {
|
||||
return imageDAO.fetchImagePullHistory(id, groupMode);
|
||||
public List<ImagePullStat> fetchImagePullHistory(ImageKey imageKey, ImagePullStat.GroupMode groupMode) {
|
||||
return imageDAO.fetchImagePullHistory(imageKey, groupMode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import io.linuxserver.fleet.db.query.InsertUpdateResult;
|
||||
import io.linuxserver.fleet.db.query.InsertUpdateStatus;
|
||||
import io.linuxserver.fleet.exception.SaveException;
|
||||
import io.linuxserver.fleet.model.internal.Repository;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -33,8 +34,8 @@ public class RepositoryDelegate {
|
||||
this.repositoryDAO = repositoryDAO;
|
||||
}
|
||||
|
||||
public Repository fetchRepository(int id) {
|
||||
return repositoryDAO.fetchRepository(id);
|
||||
public Repository fetchRepository(RepositoryKey repositoryKey) {
|
||||
return repositoryDAO.fetchRepository(repositoryKey);
|
||||
}
|
||||
|
||||
public Repository saveRepository(Repository repository) throws SaveException {
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.dockerhub.queue;
|
||||
|
||||
import io.linuxserver.fleet.delegate.ImageDelegate;
|
||||
import io.linuxserver.fleet.exception.SaveException;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.queue.AbstractQueueConsumer;
|
||||
import io.linuxserver.fleet.queue.RequestQueue;
|
||||
|
||||
public class DockerHubSyncConsumer extends AbstractQueueConsumer<DockerHubSyncResponse, DockerHubSyncRequest> {
|
||||
|
||||
private final ImageDelegate imageDelegate;
|
||||
|
||||
public DockerHubSyncConsumer(ImageDelegate imageDelegate, RequestQueue<DockerHubSyncRequest> requestQueue, String name) {
|
||||
super(requestQueue, name);
|
||||
this.imageDelegate = imageDelegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleResponse(DockerHubSyncResponse response) {
|
||||
|
||||
try {
|
||||
|
||||
final Image image = response.getImage();
|
||||
imageDelegate.saveImage(image);
|
||||
|
||||
} catch (SaveException e) {
|
||||
getLogger().error("handleResponse unable to save image: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.dockerhub.queue;
|
||||
|
||||
import io.linuxserver.fleet.queue.AbstractRequestQueue;
|
||||
|
||||
public class DockerHubSyncQueue extends AbstractRequestQueue<DockerHubSyncRequest> {
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.dockerhub.queue;
|
||||
|
||||
import io.linuxserver.fleet.delegate.DockerHubDelegate;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.queue.FleetRequest;
|
||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
|
||||
public class DockerHubSyncRequest implements FleetRequest<DockerHubSyncResponse> {
|
||||
|
||||
private final DockerHubDelegate dockerHubDelegate;
|
||||
private final Image image;
|
||||
|
||||
public DockerHubSyncRequest(DockerHubDelegate dockerHubDelegate, Image image) {
|
||||
|
||||
this.dockerHubDelegate = dockerHubDelegate;
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DockerHubSyncResponse execute() {
|
||||
return new DockerHubSyncResponse(dockerHubDelegate, image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (!(o instanceof DockerHubSyncRequest))
|
||||
return false;
|
||||
|
||||
return EqualsBuilder.reflectionEquals(this, "dockerHubDelegate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HashCodeBuilder.reflectionHashCode(this, "dockerHubDelegate");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.dockerhub.queue;
|
||||
|
||||
import io.linuxserver.fleet.delegate.DockerHubDelegate;
|
||||
import io.linuxserver.fleet.model.docker.DockerImage;
|
||||
import io.linuxserver.fleet.model.docker.DockerTag;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.model.internal.Tag;
|
||||
import io.linuxserver.fleet.queue.FleetResponse;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DockerHubSyncResponse implements FleetResponse {
|
||||
|
||||
private final DockerHubDelegate dockerHubDelegate;
|
||||
private final Image image;
|
||||
|
||||
public DockerHubSyncResponse(DockerHubDelegate dockerHubDelegate, Image image) {
|
||||
|
||||
this.dockerHubDelegate = dockerHubDelegate;
|
||||
this.image = Image.copyOf(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() {
|
||||
|
||||
DockerImage dockerImage = dockerHubDelegate.fetchImageFromRepository(image.getKey().getRepositoryKey().getName(), image.getName());
|
||||
|
||||
String versionMask = getVersionMask(null, image.getVersionMask());
|
||||
Tag maskedVersion = getLatestTagAndCreateMaskedVersion(versionMask);
|
||||
|
||||
image.withPullCount(dockerImage.getPullCount());
|
||||
image.updateTag(maskedVersion);
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
private String getVersionMask(String repositoryMask, String imageMask) {
|
||||
return imageMask == null ? repositoryMask : imageMask;
|
||||
}
|
||||
|
||||
private Tag getLatestTagAndCreateMaskedVersion(String versionMask) {
|
||||
|
||||
DockerTag tag = dockerHubDelegate.fetchLatestImageTag(image.getKey().getRepositoryKey().getName(), image.getName());
|
||||
|
||||
if (null == tag)
|
||||
return Tag.NONE;
|
||||
|
||||
if (isTagJustLatestAndNotAVersion(tag) || null == versionMask)
|
||||
return new Tag(tag.getName(), tag.getName(), tag.getBuildDate());
|
||||
|
||||
return new Tag(tag.getName(), extractMaskedVersion(tag.getName(), versionMask), tag.getBuildDate());
|
||||
}
|
||||
|
||||
private String extractMaskedVersion(String fullTag, String versionMask) {
|
||||
|
||||
Pattern pattern = Pattern.compile(versionMask);
|
||||
Matcher matcher = pattern.matcher(fullTag);
|
||||
|
||||
if (matcher.matches()) {
|
||||
|
||||
StringBuilder tagBuilder = new StringBuilder();
|
||||
|
||||
for (int groupNum = 1; groupNum <= matcher.groupCount(); groupNum++)
|
||||
tagBuilder.append(matcher.group(groupNum));
|
||||
|
||||
return tagBuilder.toString();
|
||||
}
|
||||
|
||||
return fullTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* If the top-level tag is not versioned, a mask can't be applied.
|
||||
* </p>
|
||||
*/
|
||||
private boolean isTagJustLatestAndNotAVersion(DockerTag tag) {
|
||||
return "latest".equals(tag.getName());
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@ import io.linuxserver.fleet.model.internal.Image;
|
||||
|
||||
public class ApiImage {
|
||||
|
||||
private String imageKey;
|
||||
private String name;
|
||||
private long pullCount;
|
||||
private String version;
|
||||
@ -28,6 +29,10 @@ public class ApiImage {
|
||||
private boolean deprecated;
|
||||
private String buildDate;
|
||||
|
||||
public String getImageKey() {
|
||||
return imageKey;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@ -56,6 +61,7 @@ public class ApiImage {
|
||||
|
||||
ApiImage apiImage = new ApiImage();
|
||||
|
||||
apiImage.imageKey = image.getKey().toString();
|
||||
apiImage.name = image.getName();
|
||||
apiImage.pullCount = image.getPullCount();
|
||||
apiImage.version = image.getMaskedVersion();
|
||||
|
||||
@ -49,7 +49,7 @@ public class ApiImagePullHistory {
|
||||
public static ApiImagePullHistory fromPullStats(Image image, List<ImagePullStat> stats) {
|
||||
|
||||
ApiImagePullHistory history = new ApiImagePullHistory();
|
||||
history.imageId = image.getId();
|
||||
history.imageId = image.getKey().getId();
|
||||
history.imageName = image.getName();
|
||||
history.groupMode = stats.get(0).getGroupMode().toString();
|
||||
|
||||
|
||||
@ -17,9 +17,11 @@
|
||||
|
||||
package io.linuxserver.fleet.model.internal;
|
||||
|
||||
import io.linuxserver.fleet.model.key.AbstractHasKey;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -27,42 +29,42 @@ import java.util.Objects;
|
||||
* specific information regarding its build status and pull count.
|
||||
* </p>
|
||||
*/
|
||||
public class Image extends PersistableItem<Image> {
|
||||
public class Image extends AbstractHasKey<ImageKey> {
|
||||
|
||||
private final int repositoryId;
|
||||
private final String name;
|
||||
private static final DateTimeFormatter DATE_PATTERN = DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss");
|
||||
|
||||
private Tag tag;
|
||||
private long pullCount;
|
||||
private String versionMask;
|
||||
private boolean unstable;
|
||||
private boolean hidden;
|
||||
private Tag tag;
|
||||
private long pullCount;
|
||||
private String versionMask;
|
||||
private boolean unstable;
|
||||
private boolean hidden;
|
||||
private LocalDateTime modifiedTime;
|
||||
private boolean deprecated;
|
||||
private String deprecationReason;
|
||||
|
||||
private boolean deprecated;
|
||||
private String deprecationReason;
|
||||
|
||||
public Image(Integer id, int repositoryId, String name, Tag latestVersion) {
|
||||
|
||||
super(id);
|
||||
|
||||
this.name = name;
|
||||
this.repositoryId = repositoryId;
|
||||
this.tag = new Tag(latestVersion.getVersion(), latestVersion.getMaskedVersion(), latestVersion.getBuildDate());
|
||||
public Image(final ImageKey imageKey, final Tag latestVersion) {
|
||||
super(imageKey);
|
||||
this.tag = new Tag(latestVersion.getVersion(), latestVersion.getMaskedVersion(), latestVersion.getBuildDate());
|
||||
}
|
||||
|
||||
public Image(int repositoryId, String name) {
|
||||
this(null, repositoryId, name, Tag.NONE);
|
||||
public Image(final ImageKey imageKey) {
|
||||
this(imageKey, Tag.NONE);
|
||||
}
|
||||
|
||||
public static Image makeFromKey(ImageKey imageKey) {
|
||||
return new Image(imageKey);
|
||||
}
|
||||
|
||||
public static Image copyOf(Image image) {
|
||||
|
||||
Image cloned = new Image(image.getId(), image.repositoryId, image.name, image.tag);
|
||||
Image cloned = new Image(image.getKey(), image.tag);
|
||||
cloned.pullCount = image.pullCount;
|
||||
cloned.versionMask = image.versionMask;
|
||||
cloned.unstable = image.unstable;
|
||||
cloned.hidden = image.hidden;
|
||||
cloned.deprecated = image.deprecated;
|
||||
cloned.deprecationReason = image.deprecationReason;
|
||||
cloned.modifiedTime = image.modifiedTime;
|
||||
|
||||
return cloned;
|
||||
}
|
||||
@ -103,16 +105,22 @@ public class Image extends PersistableItem<Image> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Image withModifiedTime(final LocalDateTime modifiedTime) {
|
||||
|
||||
this.modifiedTime = modifiedTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void updateTag(Tag maskedVersion) {
|
||||
this.tag = new Tag(maskedVersion.getVersion(), maskedVersion.getMaskedVersion(), maskedVersion.getBuildDate());
|
||||
}
|
||||
|
||||
public int getRepositoryId() {
|
||||
return repositoryId;
|
||||
return getKey().getRepositoryKey().getId();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
return getKey().getName();
|
||||
}
|
||||
|
||||
public long getPullCount() {
|
||||
@ -134,7 +142,7 @@ public class Image extends PersistableItem<Image> {
|
||||
public String getBuildDateAsString() {
|
||||
|
||||
if (getBuildDate() != null) {
|
||||
return getBuildDate().format(DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss"));
|
||||
return getBuildDate().format(DATE_PATTERN);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -160,17 +168,20 @@ public class Image extends PersistableItem<Image> {
|
||||
return deprecationReason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
public LocalDateTime getModifiedTime() {
|
||||
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Image image = (Image) o;
|
||||
return repositoryId == image.repositoryId && Objects.equals(name, image.name);
|
||||
}
|
||||
if (null != modifiedTime) {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(repositoryId, name);
|
||||
return LocalDateTime.of(
|
||||
modifiedTime.getYear(),
|
||||
modifiedTime.getMonth(),
|
||||
modifiedTime.getDayOfMonth(),
|
||||
modifiedTime.getHour(),
|
||||
modifiedTime.getMinute(),
|
||||
modifiedTime.getSecond()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,23 +17,23 @@
|
||||
|
||||
package io.linuxserver.fleet.model.internal;
|
||||
|
||||
public class Repository extends PersistableItem<Repository> {
|
||||
import io.linuxserver.fleet.model.key.AbstractHasKey;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
|
||||
private final String name;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
private String versionMask;
|
||||
private boolean syncEnabled;
|
||||
public class Repository extends AbstractHasKey<RepositoryKey> {
|
||||
|
||||
public Repository(Integer id, String name) {
|
||||
super(id);
|
||||
private String versionMask;
|
||||
private boolean syncEnabled;
|
||||
private LocalDateTime modifiedTime;
|
||||
|
||||
this.name = name;
|
||||
public Repository(final RepositoryKey repositoryKey) {
|
||||
super(repositoryKey);
|
||||
}
|
||||
|
||||
public Repository(String name) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
public static Repository copyOf(Repository repository) {
|
||||
return new Repository(repository.getKey()).withVersionMask(repository.versionMask).withSyncEnabled(repository.syncEnabled);
|
||||
}
|
||||
|
||||
public Repository withVersionMask(String versionMask) {
|
||||
@ -48,8 +48,14 @@ public class Repository extends PersistableItem<Repository> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Repository withModifiedTime(LocalDateTime modifiedTime) {
|
||||
|
||||
this.modifiedTime = modifiedTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
return getKey().getName();
|
||||
}
|
||||
|
||||
public String getVersionMask() {
|
||||
@ -59,4 +65,21 @@ public class Repository extends PersistableItem<Repository> {
|
||||
public boolean isSyncEnabled() {
|
||||
return syncEnabled;
|
||||
}
|
||||
|
||||
public LocalDateTime getModifiedTime() {
|
||||
|
||||
if (null != modifiedTime) {
|
||||
|
||||
return LocalDateTime.of(
|
||||
modifiedTime.getYear(),
|
||||
modifiedTime.getMonth(),
|
||||
modifiedTime.getDayOfMonth(),
|
||||
modifiedTime.getHour(),
|
||||
modifiedTime.getMinute(),
|
||||
modifiedTime.getSecond()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,20 +17,27 @@
|
||||
|
||||
package io.linuxserver.fleet.model.internal;
|
||||
|
||||
public class User extends PersistableItem<User> {
|
||||
import io.linuxserver.fleet.model.key.AbstractHasKey;
|
||||
import io.linuxserver.fleet.model.key.UserKey;
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class User extends AbstractHasKey<UserKey> {
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
|
||||
private LocalDateTime modifiedTime;
|
||||
|
||||
public User(String username, String password) {
|
||||
super();
|
||||
super(new UserKey());
|
||||
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public User(Integer id, String username, String password) {
|
||||
super(id);
|
||||
super(new UserKey(id));
|
||||
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
@ -43,4 +50,27 @@ public class User extends PersistableItem<User> {
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public User withModifiedTime(LocalDateTime modifiedTime) {
|
||||
|
||||
this.modifiedTime = modifiedTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LocalDateTime getModifiedTime() {
|
||||
|
||||
if (null != modifiedTime) {
|
||||
|
||||
return LocalDateTime.of(
|
||||
modifiedTime.getYear(),
|
||||
modifiedTime.getMonth(),
|
||||
modifiedTime.getDayOfMonth(),
|
||||
modifiedTime.getHour(),
|
||||
modifiedTime.getMinute(),
|
||||
modifiedTime.getSecond()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.model.key;
|
||||
|
||||
public abstract class AbstractHasKey<KEY extends Key> implements HasKey<KEY> {
|
||||
|
||||
private final KEY key;
|
||||
|
||||
public AbstractHasKey(final KEY key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final KEY getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return key.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (!(o instanceof HasKey<?>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return key.equals(((HasKey<?>) o).getKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return key.toString();
|
||||
}
|
||||
}
|
||||
@ -15,41 +15,47 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.model.internal;
|
||||
package io.linuxserver.fleet.model.key;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
public abstract class AbstractKey implements Key {
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
private final Integer id;
|
||||
|
||||
public abstract class PersistableItem<T extends PersistableItem> {
|
||||
|
||||
private Integer id;
|
||||
private LocalDateTime modifiedTime;
|
||||
|
||||
PersistableItem() { }
|
||||
|
||||
PersistableItem(Integer id) {
|
||||
public AbstractKey(final Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T withModifiedTime(LocalDateTime modifiedTime) {
|
||||
|
||||
this.modifiedTime = modifiedTime;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
@Override
|
||||
public final Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public LocalDateTime getModifiedTime() {
|
||||
return modifiedTime;
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (!(o instanceof Key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (null == id) {
|
||||
return ((Key) o).getId() == null;
|
||||
}
|
||||
|
||||
return ((Key) o).getId().equals(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
if (null == id) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||
return null == id ? "<NO_ID>" : String.valueOf(id);
|
||||
}
|
||||
}
|
||||
23
src/main/java/io/linuxserver/fleet/model/key/HasKey.java
Normal file
23
src/main/java/io/linuxserver/fleet/model/key/HasKey.java
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.model.key;
|
||||
|
||||
public interface HasKey<KEY extends Key> {
|
||||
|
||||
KEY getKey();
|
||||
}
|
||||
72
src/main/java/io/linuxserver/fleet/model/key/ImageKey.java
Normal file
72
src/main/java/io/linuxserver/fleet/model/key/ImageKey.java
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.model.key;
|
||||
|
||||
public class ImageKey extends AbstractKey {
|
||||
|
||||
private static final String KeyPattern = "^\\d+:\\d+:[^/]+/[^/]+$";
|
||||
|
||||
private final RepositoryKey repositoryKey;
|
||||
private final String name;
|
||||
|
||||
public ImageKey(final String name, final RepositoryKey repositoryKey) {
|
||||
this(null, name, repositoryKey);
|
||||
}
|
||||
|
||||
public ImageKey(final Integer id, final String name, final RepositoryKey repositoryKey) {
|
||||
super(id);
|
||||
|
||||
this.repositoryKey = repositoryKey;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static ImageKey parse(final String keyAsString) {
|
||||
|
||||
if (keyAsString.matches(KeyPattern)) {
|
||||
|
||||
final String[] keyParts = keyAsString.split(":");
|
||||
final String[] names = keyParts[2].split("/");
|
||||
final int repositoryId = Integer.parseInt(keyParts[0]);
|
||||
final int imageId = Integer.parseInt(keyParts[1]);
|
||||
final String repositoryName = names[0];
|
||||
final String imageName = names[1];
|
||||
|
||||
return new ImageKey(imageId, imageName, new RepositoryKey(repositoryId, repositoryName));
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Key pattern is malformed");
|
||||
}
|
||||
}
|
||||
|
||||
public ImageKey cloneWithId(int id) {
|
||||
return new ImageKey(id, name, repositoryKey);
|
||||
}
|
||||
|
||||
public final RepositoryKey getRepositoryKey() {
|
||||
return repositoryKey;
|
||||
}
|
||||
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return repositoryKey.getId() + ":" + super.toString() + ":" + repositoryKey.getName() + "/" + name;
|
||||
}
|
||||
}
|
||||
23
src/main/java/io/linuxserver/fleet/model/key/Key.java
Normal file
23
src/main/java/io/linuxserver/fleet/model/key/Key.java
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.model.key;
|
||||
|
||||
public interface Key {
|
||||
|
||||
Integer getId();
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.model.key;
|
||||
|
||||
public class RepositoryKey extends AbstractKey {
|
||||
|
||||
private final String name;
|
||||
|
||||
public RepositoryKey(final int id) {
|
||||
this(id, "<LookupKey>");
|
||||
}
|
||||
|
||||
public RepositoryKey(final String name) {
|
||||
this(null, name);
|
||||
}
|
||||
|
||||
public RepositoryKey(final Integer id, final String name) {
|
||||
super(id);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public RepositoryKey cloneWithId(int id) {
|
||||
return new RepositoryKey(id, name);
|
||||
}
|
||||
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ":" + name;
|
||||
}
|
||||
}
|
||||
29
src/main/java/io/linuxserver/fleet/model/key/UserKey.java
Normal file
29
src/main/java/io/linuxserver/fleet/model/key/UserKey.java
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.model.key;
|
||||
|
||||
public class UserKey extends AbstractKey {
|
||||
|
||||
public UserKey() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public UserKey(Integer id) {
|
||||
super(id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.queue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public abstract class AbstractQueueConsumer<R extends FleetResponse, T extends FleetRequest<R>> extends Thread implements QueueConsumer<T> {
|
||||
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(getClass().getName());
|
||||
|
||||
private final RequestQueue<T> requestQueue;
|
||||
private final AtomicReference<ConsumerState> state;
|
||||
|
||||
public AbstractQueueConsumer(final RequestQueue<T> requestQueue, final String name) {
|
||||
super(name + "-Consumer");
|
||||
this.requestQueue = requestQueue;
|
||||
this.state = new AtomicReference<>(ConsumerState.Stopped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
while (isRunning()) {
|
||||
consume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
getLogger().info("Starting...");
|
||||
super.start();
|
||||
state.set(ConsumerState.Running);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume() {
|
||||
|
||||
try {
|
||||
|
||||
T request = requestQueue.takeOneRequest();
|
||||
R response = request.execute();
|
||||
|
||||
handleResponse(response);
|
||||
|
||||
} catch (Exception e) {
|
||||
getLogger().error("consume caught unhandled exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return state.get().isRunning();
|
||||
}
|
||||
|
||||
protected abstract void handleResponse(R response);
|
||||
|
||||
public Logger getLogger() {
|
||||
return LOGGER;
|
||||
}
|
||||
|
||||
private enum ConsumerState {
|
||||
|
||||
Running, Stopped;
|
||||
|
||||
final boolean isRunning() {
|
||||
return this == Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.queue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
public class AbstractRequestQueue<T extends FleetRequest<? extends FleetResponse>> implements RequestQueue<T> {
|
||||
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(getClass().getName());
|
||||
|
||||
private final BlockingQueue<T> requestQueue;
|
||||
|
||||
public AbstractRequestQueue() {
|
||||
this.requestQueue = new LinkedBlockingDeque<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void enqueueRequest(final T request) {
|
||||
|
||||
try {
|
||||
|
||||
if (!requestQueue.contains(request)) {
|
||||
|
||||
final boolean added = requestQueue.add(request);
|
||||
if (!added) {
|
||||
LOGGER.warn("enqueueRequest unable to add request to queue");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("enqueueRequest caught unhandled exception {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized T takeOneRequest() {
|
||||
|
||||
try {
|
||||
return requestQueue.take();
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("takeOneRequest was interrupted: {}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/main/java/io/linuxserver/fleet/queue/FleetRequest.java
Normal file
23
src/main/java/io/linuxserver/fleet/queue/FleetRequest.java
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.queue;
|
||||
|
||||
public interface FleetRequest<T extends FleetResponse> {
|
||||
|
||||
T execute();
|
||||
}
|
||||
23
src/main/java/io/linuxserver/fleet/queue/FleetResponse.java
Normal file
23
src/main/java/io/linuxserver/fleet/queue/FleetResponse.java
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.queue;
|
||||
|
||||
public interface FleetResponse {
|
||||
|
||||
void handle();
|
||||
}
|
||||
23
src/main/java/io/linuxserver/fleet/queue/QueueConsumer.java
Normal file
23
src/main/java/io/linuxserver/fleet/queue/QueueConsumer.java
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.queue;
|
||||
|
||||
public interface QueueConsumer<T extends FleetRequest<?>> {
|
||||
|
||||
void consume();
|
||||
}
|
||||
25
src/main/java/io/linuxserver/fleet/queue/RequestQueue.java
Normal file
25
src/main/java/io/linuxserver/fleet/queue/RequestQueue.java
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.queue;
|
||||
|
||||
public interface RequestQueue<T extends FleetRequest> {
|
||||
|
||||
void enqueueRequest(T request);
|
||||
|
||||
T takeOneRequest();
|
||||
}
|
||||
@ -24,6 +24,8 @@ import io.linuxserver.fleet.model.docker.DockerTag;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.model.internal.Repository;
|
||||
import io.linuxserver.fleet.model.internal.Tag;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
import io.linuxserver.fleet.sync.event.ImageUpdateEvent;
|
||||
import io.linuxserver.fleet.sync.event.RepositoriesScannedEvent;
|
||||
import org.slf4j.Logger;
|
||||
@ -137,7 +139,7 @@ public class DefaultSynchronisationState implements SynchronisationState {
|
||||
try {
|
||||
|
||||
DockerImage dockerImage = images.get(i);
|
||||
Image image = configureImage(repository.getId(), dockerImage, context);
|
||||
Image image = configureImage(repository, dockerImage, context);
|
||||
|
||||
String versionMask = getVersionMask(repository.getVersionMask(), image.getVersionMask());
|
||||
Tag maskedVersion = getLatestTagAndCreateMaskedVersion(repository.getName(), image.getName(), versionMask, context);
|
||||
@ -167,7 +169,7 @@ public class DefaultSynchronisationState implements SynchronisationState {
|
||||
if (!repositories.contains(storedRepository.getName())) {
|
||||
|
||||
LOGGER.info("Found repository which no longer exists in Docker Hub. Removing {}", storedRepository.getName());
|
||||
context.getRepositoryDelegate().removeRepository(storedRepository.getId());
|
||||
context.getRepositoryDelegate().removeRepository(storedRepository.getKey().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,12 +179,12 @@ public class DefaultSynchronisationState implements SynchronisationState {
|
||||
List<String> dockerHubImageNames = images.stream().map(DockerImage::getName).collect(Collectors.toList());
|
||||
|
||||
LOGGER.info("Checking for any removed images under {}", repository.getName());
|
||||
for (Image storedImage : context.getImageDelegate().fetchImagesByRepository(repository.getId())) {
|
||||
for (Image storedImage : context.getImageDelegate().fetchImagesByRepository(repository.getKey())) {
|
||||
|
||||
if (!dockerHubImageNames.contains(storedImage.getName())) {
|
||||
|
||||
LOGGER.info("Found image which no longer exists in Docker Hub. Removing {}", storedImage.getName());
|
||||
context.getImageDelegate().removeImage(storedImage.getId());
|
||||
context.getImageDelegate().removeImage(storedImage.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,7 +218,7 @@ public class DefaultSynchronisationState implements SynchronisationState {
|
||||
|
||||
try {
|
||||
|
||||
return context.getRepositoryDelegate().saveRepository(new Repository(repositoryName));
|
||||
return context.getRepositoryDelegate().saveRepository(new Repository(new RepositoryKey(repositoryName)));
|
||||
|
||||
} catch (SaveException e) {
|
||||
LOGGER.error("Tried to save new repository during sync but failed", e);
|
||||
@ -230,15 +232,16 @@ public class DefaultSynchronisationState implements SynchronisationState {
|
||||
* Looks up the image in the database to see if it already exists. If it does, it gets returned, otherwise a base
|
||||
* image is created with just the top-level information, as the rest will get updated later.
|
||||
*/
|
||||
private Image configureImage(int repositoryId, DockerImage dockerHubImage, SynchronisationContext context) {
|
||||
private Image configureImage(Repository repository, DockerImage dockerHubImage, SynchronisationContext context) {
|
||||
|
||||
Image image = context.getImageDelegate().findImageByRepositoryAndImageName(repositoryId, dockerHubImage.getName());
|
||||
final ImageKey baseImageKey = new ImageKey(dockerHubImage.getName(), repository.getKey());
|
||||
final Image image = context.getImageDelegate().findImageByRepositoryAndImageName(baseImageKey);
|
||||
|
||||
if (isImageNew(image)) {
|
||||
|
||||
try {
|
||||
|
||||
return context.getImageDelegate().saveImage(new Image(repositoryId, dockerHubImage.getName()));
|
||||
return context.getImageDelegate().saveImage(Image.makeFromKey(baseImageKey));
|
||||
|
||||
} catch (SaveException e) {
|
||||
LOGGER.error("Tried to save new image during sync but failed", e);
|
||||
|
||||
@ -51,7 +51,7 @@ public class HomePage extends WebPage {
|
||||
for (Repository repository : repositories) {
|
||||
|
||||
if (repository.isSyncEnabled())
|
||||
populatedRepositories.add(new RepositoryWithImages(repository, imageDelegate.fetchImagesByRepository(repository.getId())));
|
||||
populatedRepositories.add(new RepositoryWithImages(repository, imageDelegate.fetchImagesByRepository(repository.getKey())));
|
||||
}
|
||||
|
||||
model.put("populatedRepositories", populatedRepositories);
|
||||
|
||||
@ -55,7 +55,7 @@ public class AllImagesApi implements Route {
|
||||
|
||||
if (repository.isSyncEnabled()) {
|
||||
|
||||
List<Image> savedImages = imageDelegate.fetchImagesByRepository(repository.getId());
|
||||
List<Image> savedImages = imageDelegate.fetchImagesByRepository(repository.getKey());
|
||||
List<ApiImage> apiImages = new ArrayList<>();
|
||||
|
||||
for (Image savedImage : savedImages) {
|
||||
|
||||
@ -18,9 +18,11 @@
|
||||
package io.linuxserver.fleet.web.routes;
|
||||
|
||||
import io.linuxserver.fleet.delegate.ImageDelegate;
|
||||
import io.linuxserver.fleet.model.api.ApiImage;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.model.api.ApiResponse;
|
||||
import io.linuxserver.fleet.model.api.FleetApiException;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
@ -36,16 +38,16 @@ public class GetImageApi implements Route {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
|
||||
String imageIdParam = request.queryParams("imageId");
|
||||
if (null == imageIdParam) {
|
||||
throw new FleetApiException(400, "Missing imageId param");
|
||||
String imageKeyParam = request.queryParams("imageKey");
|
||||
if (null == imageKeyParam) {
|
||||
throw new FleetApiException(400, "Missing imageKey param");
|
||||
}
|
||||
|
||||
Image image = imageDelegate.fetchImage(Integer.parseInt(imageIdParam));
|
||||
Image image = imageDelegate.fetchImage(ImageKey.parse(imageKeyParam));
|
||||
if (null == image) {
|
||||
throw new FleetApiException(404, "Image not found");
|
||||
}
|
||||
|
||||
return new ApiResponse<>("OK", image);
|
||||
return new ApiResponse<>("OK", ApiImage.fromImage(image));
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import io.linuxserver.fleet.model.internal.ImagePullStat;
|
||||
import io.linuxserver.fleet.model.api.ApiImagePullHistory;
|
||||
import io.linuxserver.fleet.model.api.ApiResponse;
|
||||
import io.linuxserver.fleet.model.api.FleetApiException;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
@ -40,15 +41,15 @@ public class GetImagePullHistoryApi implements Route {
|
||||
@Override
|
||||
public Object handle(Request request, Response response) {
|
||||
|
||||
String imageIdParam = request.queryParams("imageId");
|
||||
if (null == imageIdParam) {
|
||||
String imageKeyParam = request.queryParams("imageKey");
|
||||
if (null == imageKeyParam) {
|
||||
throw new FleetApiException(400, "Missing imageId param");
|
||||
}
|
||||
|
||||
int imageId = Integer.parseInt(imageIdParam);
|
||||
List<ImagePullStat> imagePullStats = imageDelegate.fetchImagePullHistory(imageId, getGroupMode(request));
|
||||
final ImageKey lookupKey = ImageKey.parse(imageKeyParam);
|
||||
List<ImagePullStat> imagePullStats = imageDelegate.fetchImagePullHistory(lookupKey, getGroupMode(request));
|
||||
|
||||
Image image = imageDelegate.fetchImage(imageId);
|
||||
Image image = imageDelegate.fetchImage(lookupKey);
|
||||
return new ApiResponse<>("OK", ApiImagePullHistory.fromPullStats(image, imagePullStats));
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ import io.linuxserver.fleet.delegate.ImageDelegate;
|
||||
import io.linuxserver.fleet.model.internal.Image;
|
||||
import io.linuxserver.fleet.model.api.ApiResponse;
|
||||
import io.linuxserver.fleet.model.api.FleetApiException;
|
||||
import io.linuxserver.fleet.model.key.ImageKey;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
@ -38,9 +39,9 @@ public class ManageImageApi implements Route {
|
||||
|
||||
try {
|
||||
|
||||
int imageId = Integer.parseInt(request.queryParams("imageId"));
|
||||
String imageKeyParam = request.queryParams("imageKey");
|
||||
|
||||
Image image = imageDelegate.fetchImage(imageId);
|
||||
Image image = imageDelegate.fetchImage(ImageKey.parse(imageKeyParam));
|
||||
if (null == image) {
|
||||
|
||||
response.status(404);
|
||||
|
||||
@ -21,6 +21,7 @@ import io.linuxserver.fleet.delegate.RepositoryDelegate;
|
||||
import io.linuxserver.fleet.model.internal.Repository;
|
||||
import io.linuxserver.fleet.model.api.ApiResponse;
|
||||
import io.linuxserver.fleet.model.api.FleetApiException;
|
||||
import io.linuxserver.fleet.model.key.RepositoryKey;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
@ -40,7 +41,7 @@ public class ManageRepositoryApi implements Route {
|
||||
|
||||
int repositoryId = Integer.parseInt(request.queryParams("repositoryId"));
|
||||
|
||||
Repository repository = repositoryDelegate.fetchRepository(repositoryId);
|
||||
Repository repository = repositoryDelegate.fetchRepository(new RepositoryKey(repositoryId));
|
||||
if (null == repository) {
|
||||
|
||||
response.status(404);
|
||||
|
||||
@ -78,6 +78,7 @@ input[type="text"], textarea {
|
||||
|
||||
.table tbody tr td {
|
||||
border-color: #eaeaea;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table tbody tr td a {
|
||||
|
||||
@ -111,10 +111,10 @@ var imageListManager = (function($) {
|
||||
|
||||
var showImage = function() {
|
||||
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageId = getImageId(row);
|
||||
var request = buildRequest('SHOW', imageId)
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageKey = getImageKey(row);
|
||||
var request = buildRequest('SHOW', imageKey)
|
||||
|
||||
ajaxManager.call(request, function(data) {
|
||||
|
||||
@ -125,10 +125,10 @@ var imageListManager = (function($) {
|
||||
|
||||
var hideImage = function() {
|
||||
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageId = getImageId(row);
|
||||
var request = buildRequest('HIDE', imageId)
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageKey = getImageKey(row);
|
||||
var request = buildRequest('HIDE', imageKey)
|
||||
|
||||
var call = ajaxManager.call(request, function(data) {
|
||||
|
||||
@ -139,10 +139,10 @@ var imageListManager = (function($) {
|
||||
|
||||
var markImageUnstable = function() {
|
||||
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageId = getImageId(row);
|
||||
var request = buildRequest('UNSTABLE', imageId)
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageKey = getImageKey(row);
|
||||
var request = buildRequest('UNSTABLE', imageKey)
|
||||
|
||||
var call = ajaxManager.call(request, function(data) {
|
||||
|
||||
@ -154,10 +154,10 @@ var imageListManager = (function($) {
|
||||
|
||||
var markImageStable = function() {
|
||||
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageId = getImageId(row);
|
||||
var request = buildRequest('STABLE', imageId)
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageKey = getImageKey(row);
|
||||
var request = buildRequest('STABLE', imageKey)
|
||||
|
||||
var call = ajaxManager.call(request, function(data) {
|
||||
|
||||
@ -168,31 +168,31 @@ var imageListManager = (function($) {
|
||||
|
||||
var removeDeprecationNotice = function() {
|
||||
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageId = getImageId(row);
|
||||
var request = buildRequest('RESTORE', imageId)
|
||||
var option = $(this);
|
||||
var row = getImageRow(option);
|
||||
var imageKey = getImageKey(row);
|
||||
var request = buildRequest('RESTORE', imageKey)
|
||||
|
||||
var call = ajaxManager.call(request, function(data) {
|
||||
|
||||
row.removeClass('deprecated-image');
|
||||
row.find('.deprecation-message').remove();
|
||||
|
||||
option.replaceWith(createDeprecationButton(imageId));
|
||||
option.replaceWith(createDeprecationButton(imageKey.split(':')[1]));
|
||||
});
|
||||
};
|
||||
|
||||
var showImageVersionMask = function(event) {
|
||||
|
||||
var modal = $(this);
|
||||
var option = $(event.relatedTarget);
|
||||
var row = getImageRow(option);
|
||||
var imageId = getImageId(row);
|
||||
var modal = $(this);
|
||||
var option = $(event.relatedTarget);
|
||||
var row = getImageRow(option);
|
||||
var imageKey = getImageKey(row);
|
||||
|
||||
modal.find('#selected-mask-image-name').text('Version mask for ' + getImageName(row));
|
||||
modal.find('#submit-version-mask-change').data('image-id', imageId);
|
||||
modal.find('#submit-version-mask-change').data('image-key', imageKey);
|
||||
|
||||
getImageMask(imageId, function(image) {
|
||||
getImageMask(imageKey, function(image) {
|
||||
modal.find('#image-version-mask').val(image.data.versionMask);
|
||||
});
|
||||
};
|
||||
@ -200,7 +200,7 @@ var imageListManager = (function($) {
|
||||
var submitVersionMaskChange = function() {
|
||||
|
||||
var button = $(this);
|
||||
var request = buildRequest('MASK', button.data('image-id'));
|
||||
var request = buildRequest('MASK', button.data('image-key'));
|
||||
request.data.versionMask = $('#image-version-mask').val();
|
||||
|
||||
ajaxManager.call(request, function() {
|
||||
@ -210,22 +210,22 @@ var imageListManager = (function($) {
|
||||
|
||||
var showImageDeprecationNotice = function(event) {
|
||||
|
||||
var modal = $(this);
|
||||
var option = $(event.relatedTarget);
|
||||
var row = getImageRow(option);
|
||||
var imageId = getImageId(row);
|
||||
var modal = $(this);
|
||||
var option = $(event.relatedTarget);
|
||||
var row = getImageRow(option);
|
||||
var imageKey = getImageKey(row);
|
||||
|
||||
modal.find('#image-deprecation-reason').val('');
|
||||
modal.find('#selected-deprecation-image-name').text('Deprecation notice for ' + getImageName(row));
|
||||
modal.find('#submit-deprecation-change').data('image-id', imageId);
|
||||
modal.find('#submit-deprecation-change').data('image-key', imageKey);
|
||||
modal.find('#submit-deprecation-change').data('trigger-option', option.attr('id'));
|
||||
};
|
||||
|
||||
var submitDeprecationNotice = function() {
|
||||
|
||||
var button = $(this);
|
||||
var imageId = button.data('image-id');
|
||||
var request = buildRequest('DEPRECATE', imageId);
|
||||
var button = $(this);
|
||||
var imageKey = button.data('image-key');
|
||||
var request = buildRequest('DEPRECATE', imageKey);
|
||||
|
||||
request.data.deprecationReason = $('#image-deprecation-reason').val();
|
||||
|
||||
@ -233,7 +233,6 @@ var imageListManager = (function($) {
|
||||
|
||||
var trigger = $('#' + button.data('trigger-option'));
|
||||
var row = getImageRow(trigger);
|
||||
var imageId = getImageId(row);
|
||||
|
||||
row.addClass('deprecated-image');
|
||||
row.find('.image-name').append(createRowDeprecationMessage(data.data.deprecationReason));
|
||||
@ -244,14 +243,14 @@ var imageListManager = (function($) {
|
||||
});
|
||||
};
|
||||
|
||||
var buildRequest = function(action, imageId) {
|
||||
var buildRequest = function(action, imageKey) {
|
||||
|
||||
return {
|
||||
url: '/admin/api/manageImage',
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: action,
|
||||
imageId: imageId
|
||||
imageKey: imageKey
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -272,8 +271,8 @@ var imageListManager = (function($) {
|
||||
return $('<button type="button" class="image--mark-unstable dropdown-item btn-clickable"><i class="fas fa-exclamation-triangle"></i> Mark as unstable</button>');
|
||||
};
|
||||
|
||||
var createDeprecationButton = function(imageId) {
|
||||
return $('<button id="deprecate-image_' + imageId + '" type="button" class="dropdown-item btn-clickable" data-toggle="modal" data-target="#update-image-deprecation"><i class="fas fa-exclamation-circle"></i> Mark as deprecated</button>');
|
||||
var createDeprecationButton = function(imageKey) {
|
||||
return $('<button id="deprecate-image_' + imageKey + '" type="button" class="dropdown-item btn-clickable" data-toggle="modal" data-target="#update-image-deprecation"><i class="fas fa-exclamation-circle"></i> Mark as deprecated</button>');
|
||||
};
|
||||
|
||||
var createRestoreButton = function() {
|
||||
@ -296,18 +295,18 @@ var imageListManager = (function($) {
|
||||
return item.parents('tr');
|
||||
};
|
||||
|
||||
var getImageId = function(row) {
|
||||
return parseInt(row.data('image-id'));
|
||||
var getImageKey = function(row) {
|
||||
return row.data('image-key');
|
||||
};
|
||||
|
||||
var getImageName = function(row) {
|
||||
return row.data('image-name');
|
||||
};
|
||||
|
||||
var getImageMask = function(imageId, callback) {
|
||||
var getImageMask = function(imageKey, callback) {
|
||||
|
||||
var request = {
|
||||
url: '/admin/api/getImage?imageId=' + imageId,
|
||||
url: '/admin/api/getImage?imageKey=' + imageKey,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
@ -340,14 +339,14 @@ var imageSearchManager = (function($) {
|
||||
var performSearch = function() {
|
||||
|
||||
var $searchBox = $(this);
|
||||
var currentSearch = $.trim($searchBox.val());
|
||||
var currentSearch = $.trim($searchBox.val()).toLowerCase();
|
||||
var activeTable = $('#' + $searchBox.data('repository-id') + '_images');
|
||||
var rows = activeTable.find('tbody tr');
|
||||
|
||||
rows.each(function(i, row) {
|
||||
|
||||
var $row = $(row);
|
||||
var imageName = $row.data('image-name');
|
||||
var imageName = $row.data('image-name').toLowerCase();
|
||||
|
||||
if (imageName.startsWith(currentSearch) || currentSearch.length === 0) {
|
||||
$row.show();
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
DELIMITER //
|
||||
|
||||
CREATE OR REPLACE VIEW `Image_View` AS (
|
||||
|
||||
SELECT
|
||||
images.`id` AS `ImageId`,
|
||||
images.`repository` AS `RepositoryId`,
|
||||
repositories.`name` AS `RepositoryName`,
|
||||
images.`name` AS `ImageName`,
|
||||
images.`pulls` AS `ImagePullCount`,
|
||||
images.`latest_version` AS `LatestTagVersion`,
|
||||
images.`latest_version_raw` AS `LatestMaskedTagVersion`,
|
||||
images.`latest_version_buildtime` AS `LatestTagBuildDate`,
|
||||
images.`version_mask` AS `ImageVersionMask`,
|
||||
images.`hidden` AS `ImageHidden`,
|
||||
images.`unstable` AS `ImageUnstable`,
|
||||
images.`deprecated` AS `ImageDeprecated`,
|
||||
images.`deprecation_reason` AS `ImageDeprecationReason`,
|
||||
images.`modified` AS `ModifiedTime`
|
||||
FROM
|
||||
Images images
|
||||
JOIN Repositories repositories ON repositories.`id` = images.`repository`
|
||||
);
|
||||
//
|
||||
@ -34,7 +34,7 @@
|
||||
<tbody>
|
||||
|
||||
<#list repositories as repository>
|
||||
<tr data-repository-id="#{repository.id}">
|
||||
<tr data-repository-id="#{repository.key.id}">
|
||||
<td>
|
||||
${repository.name}
|
||||
</td>
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
<ul class="nav nav--repositories" id="all-repositories-tablist" role="tablist">
|
||||
<#list populatedRepositories as populatedRepository>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <#if populatedRepository?index == 0>active</#if>" data-toggle="tab" role="tab" aria-controls="repository_#{populatedRepository.repository.id}" id="repository-tab_#{populatedRepository.repository.id}" href="#repository_#{populatedRepository.repository.id}">${populatedRepository.repository.name}</a>
|
||||
<a class="nav-link <#if populatedRepository?index == 0>active</#if>" data-toggle="tab" role="tab" aria-controls="repository_#{populatedRepository.repository.key.id}" id="repository-tab_#{populatedRepository.repository.key.id}" href="#repository_#{populatedRepository.repository.key.id}">${populatedRepository.repository.name}</a>
|
||||
</li>
|
||||
</#list>
|
||||
</ul>
|
||||
@ -21,7 +21,7 @@
|
||||
<div class="tab-content" id="repository-tab-content">
|
||||
<#list populatedRepositories as populatedRepository>
|
||||
|
||||
<div class="tab-pane<#if populatedRepository?index == 0> show active</#if>" id="repository_#{populatedRepository.repository.id}" role="tabpanel" aria-labelledby="repository-tab_#{populatedRepository.repository.id}">
|
||||
<div class="tab-pane<#if populatedRepository?index == 0> show active</#if>" id="repository_#{populatedRepository.repository.key.id}" role="tabpanel" aria-labelledby="repository-tab_#{populatedRepository.repository.key.id}">
|
||||
<div class="container">
|
||||
|
||||
<#if populatedRepository.everyImageStable>
|
||||
@ -48,15 +48,15 @@
|
||||
<div class="col-md-8">
|
||||
<div class="input-group input-group-sm mb-3 mt-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="searchLabel_#{populatedRepository.repository.id}"><i class="fas fa-search"></i> Search ${populatedRepository.repository.name}</span>
|
||||
<span class="input-group-text" id="searchLabel_#{populatedRepository.repository.key.id}"><i class="fas fa-search"></i> Search ${populatedRepository.repository.name}</span>
|
||||
</div>
|
||||
<input type="text" class="form-control image-search" id="search_#{populatedRepository.repository.id}" data-repository-id="#{populatedRepository.repository.id}" aria-describedby="searchLabel_#{populatedRepository.repository.id}">
|
||||
<input type="text" class="form-control image-search" id="search_#{populatedRepository.repository.key.id}" data-repository-id="#{populatedRepository.repository.key.id}" aria-describedby="searchLabel_#{populatedRepository.repository.key.id}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2"></div>
|
||||
<div class="col-12">
|
||||
|
||||
<div class="table-responsive" id="#{populatedRepository.repository.id}_images">
|
||||
<div class="table-responsive" id="#{populatedRepository.repository.key.id}_images">
|
||||
<table class="table table--sortable">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -73,7 +73,7 @@
|
||||
<tbody>
|
||||
<#list populatedRepository.images as image>
|
||||
<#if !image.hidden || __AUTHENTICATED_USER?has_content>
|
||||
<tr class="<#if image.hidden>hidden-image</#if><#if image.deprecated>deprecated-image</#if>" data-image-id="#{image.id}" data-image-name="${image.name}">
|
||||
<tr class="<#if image.hidden>hidden-image</#if><#if image.deprecated> deprecated-image</#if>" data-image-key="${image.key}" data-image-name="${image.name}">
|
||||
<td class="image-name">
|
||||
<span class="image-name--repository">${populatedRepository.repository.name} /</span> <a target="_blank" href="https://hub.docker.com/r/${populatedRepository.repository.name}/${image.name}"><span class="image-name__image">${image.name}</span></a>
|
||||
<#if image.deprecated>
|
||||
@ -105,10 +105,10 @@
|
||||
<#if __AUTHENTICATED_USER?has_content>
|
||||
<td class="admin-actions">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-info btn-xsm dropdown-toggle" type="button" id="admin-actions_#{populatedRepository.repository.id}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button class="btn btn-info btn-xsm dropdown-toggle" type="button" id="admin-actions_#{image.key.id}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Actions
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="admin-actions_#{populatedRepository.repository.id}">
|
||||
<div class="dropdown-menu" aria-labelledby="admin-actions_#{image.key.id}">
|
||||
|
||||
<#if image.hidden>
|
||||
<button type="button" class="image--show dropdown-item btn-clickable"><i class="fas fa-eye"></i> Show in list</button>
|
||||
@ -124,7 +124,7 @@
|
||||
<#if image.deprecated>
|
||||
<button type="button" class="image--remove-deprecation-notice dropdown-item btn-clickable"><i class="fas fa-thumbs-up"></i> Remove deprecation notice</button>
|
||||
<#else>
|
||||
<button id="deprecate-image_#{image.id}" type="button" class="dropdown-item btn-clickable" data-toggle="modal" data-target="#update-image-deprecation"><i class="fas fa-exclamation-circle"></i> Mark as deprecated</button>
|
||||
<button id="deprecate-image_#{image.key.id}" type="button" class="dropdown-item btn-clickable" data-toggle="modal" data-target="#update-image-deprecation"><i class="fas fa-exclamation-circle"></i> Mark as deprecated</button>
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user