mirror of
https://github.com/caprover/caprover
synced 2026-05-06 03:30:29 +00:00
Image cleanup added to settings
This commit is contained in:
Vendored
+75
-1
@@ -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() {
|
||||
|
||||
+68
-1
@@ -90,7 +90,7 @@
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="col-lg-6">
|
||||
|
||||
<rd-widget>
|
||||
|
||||
@@ -154,4 +154,71 @@
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
|
||||
<rd-widget>
|
||||
|
||||
<rd-widget-header icon="fa-cloud-download" title="Disk Cleanup">
|
||||
</rd-widget-header>
|
||||
|
||||
<rd-widget-body loading="loadingState.cleanUp.enabled">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="input-group" uib-tooltip="for example, enter 2 in order to exclude 2 most recent builds during clean-up">
|
||||
<span class="input-group-addon">Keep Most Recent:</span>
|
||||
<input type="number" class="form-control" ng-model="cleanUp.mostRecentLimit">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-default" ng-click="onGetOldImagesClicked()">
|
||||
<span>
|
||||
<i class="fa fa-refresh"></i>
|
||||
</span> Fetch Old Images</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row" ng-show="cleanUp.unusedImages">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="padding-left: 35px">Remove?</th>
|
||||
<th class="text-center">Image Name</th>
|
||||
<th class="text-center">ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="img in cleanUp.unusedImages">
|
||||
<td class="text-center">
|
||||
<input type="checkbox" ng-model="img.remove">
|
||||
</td>
|
||||
<td class="text-center">{{img.description}}</td>
|
||||
<td class="text-center">{{img.id}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="cleanUp.unusedImages">
|
||||
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-primary" ng-click="onRemoveImagesClicked()">
|
||||
<span>
|
||||
<i class="fa fa-trash"></i>
|
||||
</span> Remove Images</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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', {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
<hr/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div class="col-lg-6">
|
||||
|
||||
<rd-widget>
|
||||
|
||||
@@ -158,4 +158,75 @@
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
|
||||
<rd-widget>
|
||||
|
||||
<rd-widget-header icon="fa-cloud-download" title="Disk Cleanup">
|
||||
</rd-widget-header>
|
||||
|
||||
<rd-widget-body loading="loadingState.cleanUp.enabled">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="input-group"
|
||||
uib-tooltip="for example, enter 2 in order to exclude 2 most recent builds during clean-up">
|
||||
<span class="input-group-addon">Keep Most Recent:</span>
|
||||
<input type="number" class="form-control"
|
||||
ng-model="cleanUp.mostRecentLimit">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-default"
|
||||
ng-click="onGetOldImagesClicked()">
|
||||
<span>
|
||||
<i class="fa fa-refresh"></i>
|
||||
</span> Fetch Old Images</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row" ng-show="cleanUp.unusedImages">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="padding-left: 35px;">Remove?</th>
|
||||
<th class="text-center">Image Name</th>
|
||||
<th class="text-center">ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="img in cleanUp.unusedImages">
|
||||
<td class="text-center">
|
||||
<input type="checkbox" ng-model="img.remove">
|
||||
</td>
|
||||
<td class="text-center">{{img.description}}</td>
|
||||
<td class="text-center">{{img.id}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="cleanUp.unusedImages">
|
||||
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-primary"
|
||||
ng-click="onRemoveImagesClicked()">
|
||||
<span>
|
||||
<i class="fa fa-trash"></i>
|
||||
</span> Remove Images</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user