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: + +
+
+ +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + +
Remove?Image NameID
+ + {{img.description}}{{img.id}}
+
+
+ +
+ +
+ +
+ +
+ + +
+
+
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: + +
+
+ +
+ +
+ +
+ + +
+ +
+ + + + + + + + + + + + + + + +
Remove?Image NameID
+ + {{img.description}}{{img.id}}
+
+
+ +
+ +
+ +
+ +
+ + +
+
+