diff --git a/app-backend/dist/js/dashboard.min.js b/app-backend/dist/js/dashboard.min.js
index 7435601..0c8fe5b 100644
--- a/app-backend/dist/js/dashboard.min.js
+++ b/app-backend/dist/js/dashboard.min.js
@@ -419,6 +419,30 @@ angular
callback(null);
});
},
+ getUnusedImages: function (mostRecentLimit, callback) {
+ $http
+ .get(BASE_API + 'user/appDefinitions/unusedImages/?mostRecentLimit=' + (mostRecentLimit || '0'), createConfig())
+ .then(
+ function (response) {
+ callback(response.data);
+ },
+ function () {
+ callback(null);
+ });
+ },
+ deleteImages: function (imageIds, callback) {
+ $http
+ .post(BASE_API + 'user/appDefinitions/deleteImages', {
+ imageIds: imageIds
+ }, createConfig())
+ .then(
+ function (response) {
+ callback(response.data);
+ },
+ function () {
+ callback(null);
+ });
+ },
deleteApp: function (appName, callback) {
$http
.post(BASE_API + 'user/appDefinitions/delete', {
@@ -1679,14 +1703,18 @@ angular.module('RDash')
'$uibModal', '$state', SettingsCtrl]);
function SettingsCtrl($scope, $cookieStore, $rootScope, pageDefinitions,
- apiManager, captainToast, $uibModal, $state) {
+ apiManager, captainToast, $uibModal, $state) {
$scope.loadingState = {
changePassword: {},
versionCheck: {},
+ cleanUp: {},
nginxConfig: {}
};
+ $scope.cleanUp = {};
+ $scope.cleanUp.mostRecentLimit = 2;
+
$scope.passwords = {};
(function ChangePasswordCtrl() {
@@ -1751,6 +1779,52 @@ function SettingsCtrl($scope, $cookieStore, $rootScope, pageDefinitions,
}());
+ (function cleanUpCtrl() {
+ $scope.onGetOldImagesClicked = function () {
+ $scope.loadingState.cleanUp.enabled = true;
+ apiManager.getUnusedImages($scope.cleanUp.mostRecentLimit, function (data) {
+
+ if (captainToast.showErrorToastIfNeeded(data)) {
+ return;
+ }
+
+ $scope.loadingState.cleanUp.enabled = false;
+ var unusedImages = data.data.unusedImages;
+ for (var i = 0; i < unusedImages.length; i++) {
+ unusedImages[i].remove = true;
+ }
+ $scope.cleanUp.unusedImages = unusedImages;
+ });
+ }
+
+ $scope.onRemoveImagesClicked = function () {
+
+ $scope.loadingState.cleanUp.enabled = true;
+
+ var imageIds = [];
+ for (var i = 0; i < $scope.cleanUp.unusedImages.length; i++) {
+ if ($scope.cleanUp.unusedImages[i].remove) {
+ imageIds.push($scope.cleanUp.unusedImages[i].id);
+ }
+ }
+
+ apiManager.deleteImages(imageIds, function (data) {
+
+ if (captainToast.showErrorToastIfNeeded(data)) {
+ return;
+ }
+
+ $scope.loadingState.cleanUp.enabled = false;
+
+ // forcing a refresh
+ $scope.onGetOldImagesClicked();
+
+ });
+ }
+
+
+ }());
+
(function nginxConfigCtrl() {
function getInitialValues() {
diff --git a/app-backend/dist/templates/settings.html b/app-backend/dist/templates/settings.html
index e879e99..74066e2 100644
--- a/app-backend/dist/templates/settings.html
+++ b/app-backend/dist/templates/settings.html
@@ -90,7 +90,7 @@
-
+
@@ -154,4 +154,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Keep Most Recent:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app-backend/src/datastore/DataStoreImpl.js b/app-backend/src/datastore/DataStoreImpl.js
index ec710e6..cc2bc63 100644
--- a/app-backend/src/datastore/DataStoreImpl.js
+++ b/app-backend/src/datastore/DataStoreImpl.js
@@ -130,17 +130,26 @@ class DataStore {
getImageName(authObj, appName, version) {
- if (version === 0) {
- version = '0';
- }
-
let authPrefix = '';
if (authObj) {
authPrefix = authObj.serveraddress + '/' + authObj.username + '/';
}
- return authPrefix + 'img-' + this.getNameSpace() + '--' + appName + (version ? (':' + version) : '');
+ return authPrefix + this.getImageNameWithoutAuthObj(appName, version);
+ }
+
+ getImageNameWithoutAuthObj(appName, version) {
+
+ if (version === 0) {
+ version = '0';
+ }
+
+ return this.getImageNameBase() + appName + (version ? (':' + version) : '');
+ }
+
+ getImageNameBase() {
+ return 'img-' + this.getNameSpace() + '--';
}
getRootDomain() {
diff --git a/app-backend/src/docker/DockerApi.js b/app-backend/src/docker/DockerApi.js
index 963a1f0..0e014a9 100644
--- a/app-backend/src/docker/DockerApi.js
+++ b/app-backend/src/docker/DockerApi.js
@@ -1318,6 +1318,37 @@ class DockerApi {
})
}
+ deleteImages(imageIds) {
+ const self = this;
+
+ return Promise.resolve()
+ .then(function () {
+
+ let promises = [];
+
+ for (let i = 0; i < imageIds.length; i++) {
+ const imageId = imageIds[i];
+ let p = self.dockerode.getImage(imageId).remove()
+ .catch(function (err) {
+ Logger.e(err);
+ });
+
+ promises.push(p);
+ }
+
+ return Promise.all(promises);
+ })
+ }
+
+ getImages() {
+ const self = this;
+
+ return Promise.resolve()
+ .then(function () {
+ return self.dockerode.listImages();
+ })
+ }
+
getNodeLables(nodeId) {
const self = this;
return self.dockerode
diff --git a/app-backend/src/routes/AppDefinitionRouter.js b/app-backend/src/routes/AppDefinitionRouter.js
index fae0f99..f4e53c2 100644
--- a/app-backend/src/routes/AppDefinitionRouter.js
+++ b/app-backend/src/routes/AppDefinitionRouter.js
@@ -32,6 +32,71 @@ router.get('/oneclickapps', function (req, res, next) {
});
});
+// unused iamges
+router.get('/unusedImages', function (req, res, next) {
+
+ let dataStore = res.locals.user.dataStore;
+ let serviceManager = res.locals.user.serviceManager;
+
+ Promise.resolve()
+ .then(function () {
+ let mostRecentLimit = Number(req.query.mostRecentLimit || '0');
+ return serviceManager.getUnusedImages(mostRecentLimit);
+ })
+ .then(function (unusedImages) {
+
+ let baseApi = new BaseApi(ApiStatusCodes.STATUS_OK, "Unused images retrieved.");
+ baseApi.data = {};
+ baseApi.data.unusedImages = unusedImages;
+
+ res.send(baseApi);
+
+ })
+ .catch(function (error) {
+
+ Logger.e(error);
+
+ if (error && error.captainErrorType) {
+ res.send(new BaseApi(error.captainErrorType, error.apiMessage));
+ return;
+ }
+
+ res.sendStatus(500);
+
+ });
+});
+
+// unused iamges
+router.post('/deleteImages', function (req, res, next) {
+
+ let dataStore = res.locals.user.dataStore;
+ let serviceManager = res.locals.user.serviceManager;
+ let imageIds = req.body.imageIds || [];
+
+ Promise.resolve()
+ .then(function () {
+ return serviceManager.deleteImages(imageIds);
+ })
+ .then(function () {
+
+ let baseApi = new BaseApi(ApiStatusCodes.STATUS_OK, "Images Deleted.");
+ res.send(baseApi);
+
+ })
+ .catch(function (error) {
+
+ Logger.e(error);
+
+ if (error && error.captainErrorType) {
+ res.send(new BaseApi(error.captainErrorType, error.apiMessage));
+ return;
+ }
+
+ res.sendStatus(500);
+
+ });
+});
+
// Get All App Definitions
router.get('/', function (req, res, next) {
diff --git a/app-backend/src/user/ServiceManager.js b/app-backend/src/user/ServiceManager.js
index b80e779..cdedbf4 100644
--- a/app-backend/src/user/ServiceManager.js
+++ b/app-backend/src/user/ServiceManager.js
@@ -670,6 +670,76 @@ class ServiceManager {
});
}
+ getUnusedImages(mostRecentLimit) {
+ Logger.d('Getting unused images, excluding most recent ones: ' + mostRecentLimit);
+ const self = this;
+
+ let dockerApi = this.dockerApi;
+ let dataStore = this.dataStore;
+ let allImages = null;
+
+ return Promise.resolve()
+ .then(function () {
+
+ return dockerApi
+ .getImages();
+ })
+ .then(function (images) {
+
+ allImages = images;
+
+ return dataStore.getAppDefinitions()
+ })
+ .then(function (apps) {
+
+ let unusedImages = [];
+
+ for (let i = 0; i < allImages.length; i++) {
+ const img = allImages[i];
+ let imageInUse = false;
+ for (let j = 0; j < img.RepoTags.length; j++) {
+ const repoTag = img.RepoTags[j];
+ Object.keys(apps).forEach(function (key, index) {
+ let app = apps[key];
+ app.appName = key;
+ for (let k = 0; k < (mostRecentLimit + 1); k++) {
+ if (repoTag.indexOf(dataStore.getImageNameWithoutAuthObj(app.appName, Number(app.deployedVersion) - k)) >= 0) {
+ imageInUse = true;
+ }
+ }
+ });
+ }
+
+ if (!imageInUse) {
+ unusedImages.push({
+ id: img.Id,
+ description: (img.RepoTags && img.RepoTags.length) ? img.RepoTags[0] : 'untagged'
+ })
+ }
+ }
+
+ return unusedImages;
+
+ });
+ }
+
+ deleteImages(imageIds){
+
+ Logger.d('Deleting images...');
+ const self = this;
+
+ let dockerApi = this.dockerApi;
+ let dataStore = this.dataStore;
+ let allImages = null;
+
+ return Promise.resolve()
+ .then(function () {
+
+ return dockerApi
+ .deleteImages(imageIds);
+ });
+ }
+
ensureServiceInitedAndUpdated(appName, version) {
Logger.d('Ensure service inited and Updated for: ' + appName);
diff --git a/app-frontend/src/js/captain/apiManager.js b/app-frontend/src/js/captain/apiManager.js
index c19338e..210ce09 100644
--- a/app-frontend/src/js/captain/apiManager.js
+++ b/app-frontend/src/js/captain/apiManager.js
@@ -331,6 +331,30 @@
callback(null);
});
},
+ getUnusedImages: function (mostRecentLimit, callback) {
+ $http
+ .get(BASE_API + 'user/appDefinitions/unusedImages/?mostRecentLimit=' + (mostRecentLimit || '0'), createConfig())
+ .then(
+ function (response) {
+ callback(response.data);
+ },
+ function () {
+ callback(null);
+ });
+ },
+ deleteImages: function (imageIds, callback) {
+ $http
+ .post(BASE_API + 'user/appDefinitions/deleteImages', {
+ imageIds: imageIds
+ }, createConfig())
+ .then(
+ function (response) {
+ callback(response.data);
+ },
+ function () {
+ callback(null);
+ });
+ },
deleteApp: function (appName, callback) {
$http
.post(BASE_API + 'user/appDefinitions/delete', {
diff --git a/app-frontend/src/js/controllers/settings-ctrl.js b/app-frontend/src/js/controllers/settings-ctrl.js
index 0ddd863..19cad5f 100644
--- a/app-frontend/src/js/controllers/settings-ctrl.js
+++ b/app-frontend/src/js/controllers/settings-ctrl.js
@@ -4,14 +4,18 @@ angular.module('RDash')
'$uibModal', '$state', SettingsCtrl]);
function SettingsCtrl($scope, $cookieStore, $rootScope, pageDefinitions,
- apiManager, captainToast, $uibModal, $state) {
+ apiManager, captainToast, $uibModal, $state) {
$scope.loadingState = {
changePassword: {},
versionCheck: {},
+ cleanUp: {},
nginxConfig: {}
};
+ $scope.cleanUp = {};
+ $scope.cleanUp.mostRecentLimit = 2;
+
$scope.passwords = {};
(function ChangePasswordCtrl() {
@@ -76,6 +80,52 @@ function SettingsCtrl($scope, $cookieStore, $rootScope, pageDefinitions,
}());
+ (function cleanUpCtrl() {
+ $scope.onGetOldImagesClicked = function () {
+ $scope.loadingState.cleanUp.enabled = true;
+ apiManager.getUnusedImages($scope.cleanUp.mostRecentLimit, function (data) {
+
+ if (captainToast.showErrorToastIfNeeded(data)) {
+ return;
+ }
+
+ $scope.loadingState.cleanUp.enabled = false;
+ var unusedImages = data.data.unusedImages;
+ for (var i = 0; i < unusedImages.length; i++) {
+ unusedImages[i].remove = true;
+ }
+ $scope.cleanUp.unusedImages = unusedImages;
+ });
+ }
+
+ $scope.onRemoveImagesClicked = function () {
+
+ $scope.loadingState.cleanUp.enabled = true;
+
+ var imageIds = [];
+ for (var i = 0; i < $scope.cleanUp.unusedImages.length; i++) {
+ if ($scope.cleanUp.unusedImages[i].remove) {
+ imageIds.push($scope.cleanUp.unusedImages[i].id);
+ }
+ }
+
+ apiManager.deleteImages(imageIds, function (data) {
+
+ if (captainToast.showErrorToastIfNeeded(data)) {
+ return;
+ }
+
+ $scope.loadingState.cleanUp.enabled = false;
+
+ // forcing a refresh
+ $scope.onGetOldImagesClicked();
+
+ });
+ }
+
+
+ }());
+
(function nginxConfigCtrl() {
function getInitialValues() {
diff --git a/app-frontend/src/templates/settings.html b/app-frontend/src/templates/settings.html
index e3f8f13..2a87df9 100644
--- a/app-frontend/src/templates/settings.html
+++ b/app-frontend/src/templates/settings.html
@@ -91,7 +91,7 @@
-
+
@@ -158,4 +158,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Keep Most Recent:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+