Files
cloudron-box/dashboard/public/views/apps.js
Girish Ramakrishnan 41bc08a07e backup: move appConfig to backups table
this is useful for clone also to copy notes, operators, checklist
of the time when the backup was made (as opposed to current)

at this point, it's not clear why we need a archives table. it's
an optimization to not have to store icon for every backup.
2024-12-10 21:04:37 +01:00

385 lines
15 KiB
JavaScript

'use strict';
/* global angular:false */
/* global $:false */
/* global APP_TYPES */
/* global onAppClick */
angular.module('Application').controller('AppsController', ['$scope', '$translate', '$interval', '$location', 'Client', function ($scope, $translate, $interval, $location, Client) {
var ALL_DOMAINS_DOMAIN = { _alldomains: true, domain: 'All Domains' }; // dummy record for the single select filter
var GROUP_ACCESS_UNSET = { _unset: true, name: 'Select Group' }; // dummy record for the single select filter
$scope.installedApps = Client.getInstalledApps();
$scope.tags = Client.getAppTags();
$scope.states = [
{ state: '', label: 'All States' },
{ state: 'running', label: 'Running' },
{ state: 'stopped', label: 'Stopped' },
{ state: 'update_available', label: 'Update Available' },
{ state: 'not_responding', label: 'Not Responding' }
];
$scope.selectedState = $scope.states[0];
$scope.selectedTags = [];
$scope.selectedGroup = GROUP_ACCESS_UNSET;
$scope.selectedDomain = ALL_DOMAINS_DOMAIN;
$scope.filterDomains = [ ALL_DOMAINS_DOMAIN ];
$scope.config = Client.getConfig();
$scope.user = Client.getUserInfo();
$scope.domains = [];
$scope.appSearch = '';
$scope.groups = [ GROUP_ACCESS_UNSET ];
$scope.APP_TYPES = APP_TYPES;
$scope.showFilter = false;
$scope.filterActive = false;
$scope.VIEWS = {
GRID: 'grid',
LIST: 'list'
};
$scope.view = $scope.VIEWS.GRID;
$scope.orderBy = 'location'; // or app, status, sso
$scope.orderByReverse = false;
$scope.allUsers = [];
$scope.allGroups = [];
$translate(['apps.stateFilterHeader', 'apps.domainsFilterHeader', 'apps.groupsFilterHeader', 'app.states.running', 'app.states.stopped', 'app.states.notResponding', 'app.states.updateAvailable']).then(function (tr) {
if (tr['apps.domainsFilterHeader']) ALL_DOMAINS_DOMAIN.domain = tr['apps.domainsFilterHeader'];
if (tr['apps.groupsFilterHeader']) GROUP_ACCESS_UNSET.name = tr['apps.groupsFilterHeader'];
if (tr['apps.stateFilterHeader']) $scope.states[0].label = tr['apps.stateFilterHeader'];
if (tr['app.states.running']) $scope.states[1].label = tr['app.states.running'];
if (tr['app.states.stopped']) $scope.states[2].label = tr['app.states.stopped'];
if (tr['app.states.notResponding']) $scope.states[4].label = tr['app.states.notResponding'];
if (tr['app.states.updateAvailable']) $scope.states[3].label = tr['app.states.updateAvailable'];
});
$scope.pendingChecklistItems = function (app) {
if (!app.checklist) return 0;
return Object.keys(app.checklist).filter(function (key) { return !app.checklist[key].acknowledged; }).length;
};
$scope.setOrderBy = function (by) {
if (by === $scope.orderBy) {
$scope.orderByReverse = !$scope.orderByReverse;
} else {
$scope.orderBy = by;
$scope.orderByReverse = false;
}
localStorage.appsOrderBy = by;
if ($scope.orderByReverse) localStorage.appsOrderByReverse = true;
else localStorage.removeItem('appsOrderByReverse');
};
// for sorting/grouping
$scope.orderByFilter = function (item) {
if ($scope.orderBy === 'app') return item.manifest.title || 'App Link';
if ($scope.orderBy === 'status') return item.installationState + '-' + item.runState;
if ($scope.orderBy === 'sso') return item.sso;
return item.label || item.fqdn;
};
$scope.setView = function (view) {
if (view !== $scope.VIEWS.LIST && view !== $scope.VIEWS.GRID) return;
$scope.view = view;
localStorage.appsView = view;
};
$scope.toggleView = function () {
$scope.view = $scope.view === $scope.VIEWS.GRID ? $scope.VIEWS.LIST : $scope.VIEWS.GRID;
localStorage.appsView = $scope.view;
};
$scope.toggleFilter = function () {
$scope.showFilter = !$scope.showFilter;
if ($scope.showFilter) localStorage.appsShowFilter = true;
else localStorage.removeItem('appsShowFilter');
// clear on hide
if (!$scope.showFilter) {
$scope.selectedState = $scope.states[0];
$scope.selectedTags = [];
$scope.selectedGroup = GROUP_ACCESS_UNSET;
$scope.selectedDomain = ALL_DOMAINS_DOMAIN;
}
};
$scope.$watch('selectedTags', function (newVal, oldVal) {
if (newVal === oldVal) return;
localStorage.selectedTags = newVal.join(',');
});
$scope.$watch('selectedState', function (newVal, oldVal) {
if (newVal === oldVal) return;
if (newVal === $scope.states[0]) localStorage.removeItem('selectedState');
else localStorage.selectedState = newVal.state;
});
$scope.$watch('selectedGroup', function (newVal, oldVal) {
if (newVal === oldVal) return;
if (newVal === GROUP_ACCESS_UNSET) localStorage.removeItem('selectedGroup');
else localStorage.selectedGroup = newVal.id;
});
$scope.$watch('selectedDomain', function (newVal, oldVal) {
if (newVal === oldVal) return;
if (newVal._alldomains) localStorage.removeItem('selectedDomain');
else localStorage.selectedDomain = newVal.domain;
});
$scope.onAppClick = function (app, $event) { onAppClick(app, $event, $scope.isOperator(app), $scope); };
$scope.appPostInstallConfirm = {
app: {},
message: '',
show: function (app) {
$scope.appPostInstallConfirm.app = app;
$scope.appPostInstallConfirm.message = app.manifest.postInstallMessage;
$('#appsPostInstallConfirmModal').modal('show');
return false; // prevent propagation and default
},
submit: function () {
$scope.appPostInstallConfirm.app.pendingPostInstallConfirmation = false;
delete localStorage['confirmPostInstall_' + $scope.appPostInstallConfirm.app.id];
$('#appsPostInstallConfirmModal').modal('hide');
}
};
$scope.applinksEdit = {
error: {},
busyEdit: false,
busyRemove: false,
applink: {},
id: '',
upstreamUri: '',
label: '',
tags: '',
accessRestrictionOption: '',
accessRestriction: { users: [], groups: [] },
icon: { data: null },
iconUrl: function () {
if ($scope.applinksEdit.icon.data === '__original__') { // user clicked reset
// https://png-pixel.com/ white pixel placeholder
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=';
} else if ($scope.applinksEdit.icon.data) { // user uploaded icon
return $scope.applinksEdit.icon.data;
} else { // current icon
return $scope.applinksEdit.applink.iconUrl;
}
},
resetCustomIcon: function () {
$scope.applinksEdit.icon.data = '__original__';
},
showCustomIconSelector: function () {
$('#applinksEditIconFileInput').click();
},
isAccessRestrictionValid: function () {
return !!($scope.applinksEdit.accessRestriction.users.length || $scope.applinksEdit.accessRestriction.groups.length);
},
show: function (applink) {
$scope.applinksEdit.error = {};
$scope.applinksEdit.busyEdit = false;
$scope.applinksEdit.busyRemove = false;
$scope.applinksEdit.applink = applink;
$scope.applinksEdit.id = applink.id;
$scope.applinksEdit.upstreamUri = applink.upstreamUri;
$scope.applinksEdit.label = applink.label;
$scope.applinksEdit.accessRestrictionOption = applink.accessRestriction ? 'groups' : 'any';
$scope.applinksEdit.accessRestriction = { users: [], groups: [] };
$scope.applinksEdit.icon = { data: null };
var userSet, groupSet;
if (applink.accessRestriction) {
userSet = {};
applink.accessRestriction.users.forEach(function (uid) { userSet[uid] = true; });
$scope.allUsers.forEach(function (u) { if (userSet[u.id] === true) $scope.applinksEdit.accessRestriction.users.push(u); });
groupSet = {};
if (applink.accessRestriction.groups) applink.accessRestriction.groups.forEach(function (gid) { groupSet[gid] = true; });
$scope.allGroups.forEach(function (g) { if (groupSet[g.id] === true) $scope.applinksEdit.accessRestriction.groups.push(g); });
}
// translate for tag-input
$scope.applinksEdit.tags = applink.tags ? applink.tags.join(' ') : '';
$scope.applinksEditForm.$setUntouched();
$scope.applinksEditForm.$setPristine();
$('#applinksEditModal').modal('show');
return false; // prevent propagation and default
},
submit: function () {
$scope.applinksEdit.busyEdit = true;
$scope.applinksEdit.error = {};
var accessRestriction = null;
if ($scope.applinksEdit.accessRestrictionOption === 'groups') {
accessRestriction = { users: [], groups: [] };
accessRestriction.users = $scope.applinksEdit.accessRestriction.users.map(function (u) { return u.id; });
accessRestriction.groups = $scope.applinksEdit.accessRestriction.groups.map(function (g) { return g.id; });
}
var data = {
upstreamUri: $scope.applinksEdit.upstreamUri,
label: $scope.applinksEdit.label,
accessRestriction: accessRestriction,
tags: $scope.applinksEdit.tags.split(' ').map(function (t) { return t.trim(); }).filter(function (t) { return !!t; })
};
if ($scope.applinksEdit.icon.data === '__original__') { // user reset the icon
data.icon = '';
} else if ($scope.applinksEdit.icon.data) { // user loaded custom icon
data.icon = $scope.applinksEdit.icon.data.replace(/^data:image\/[a-z]+;base64,/, '');
}
Client.updateApplink($scope.applinksEdit.id, data, function (error) {
$scope.applinksEdit.busyEdit = false;
if (error && error.statusCode === 400 && error.message.includes('upstreamUri')) {
$scope.applinksEdit.error.upstreamUri = error.message;
$scope.applinksEditForm.$setUntouched();
$scope.applinksEditForm.$setPristine();
return;
}
if (error) return console.error('Failed to update applink', error);
Client.refreshInstalledApps();
$('#applinksEditModal').modal('hide');
});
},
remove: function () {
$scope.applinksEdit.busyRemove = true;
Client.removeApplink($scope.applinksEdit.id, function (error) {
$scope.applinksEdit.busyRemove = false;
if (error) return console.error('Failed to remove applink', error);
Client.refreshInstalledApps();
$('#applinksEditModal').modal('hide');
});
}
};
$scope.showAppConfigure = function (app, view) {
$location.path('/app/' + app.id + '/' + view);
};
$scope.isOperator = function (app) {
return app.accessLevel === 'operator' || app.accessLevel === 'admin';
};
Client.onReady(function () {
setTimeout(function () { $('#appSearch').focus(); }, 1);
// refresh the new list immediately when switching from another view (appstore)
Client.refreshInstalledApps(function () {
var refreshAppsTimer = $interval(Client.refreshInstalledApps.bind(Client, function () {}), 5000);
$scope.$on('$destroy', function () {
$interval.cancel(refreshAppsTimer);
});
});
$scope.setView(localStorage.appsView);
$scope.orderBy = localStorage.appsOrderBy || 'location';
$scope.orderByReverse = !!localStorage.appsOrderByReverse;
$scope.showFilter = !!localStorage.appsShowFilter;
if (!$scope.user.isAtLeastAdmin) return;
// load local settings and apply tag filter
if (localStorage.selectedTags) {
if (!$scope.tags.length) localStorage.removeItem('selectedTags');
else $scope.selectedTags = localStorage.selectedTags.split(',');
}
if (localStorage.selectedState) $scope.selectedState = $scope.states.find(function (s) { return s.state === localStorage.selectedState; }) || $scope.states[0];
Client.getGroups(function (error, result) {
if (error) Client.error(error);
$scope.groups = [ GROUP_ACCESS_UNSET ].concat(result);
$scope.allGroups = result;
if (localStorage.selectedGroup) $scope.selectedGroup = $scope.groups.find(function (g) { return g.id === localStorage.selectedGroup; }) || GROUP_ACCESS_UNSET;
});
Client.getDomains(function (error, result) {
if (error) Client.error(error);
$scope.domains = result;
$scope.filterDomains = [ALL_DOMAINS_DOMAIN].concat(result);
if (localStorage.selectedDomain) $scope.selectedDomain = $scope.filterDomains.find(function (d) { return d.domain === localStorage.selectedDomain; }) || ALL_DOMAINS_DOMAIN;
});
Client.getAllUsers(function (error, users) {
if (error) Client.error(error);
$scope.allUsers = users;
});
});
$('#applinksEditIconFileInput').get(0).onchange = function (event) {
var fr = new FileReader();
fr.onload = function () {
$scope.$apply(function () {
// var file = event.target.files[0];
$scope.applinksEdit.icon.data = fr.result;
});
};
fr.readAsDataURL(event.target.files[0]);
};
// setup all the dialog focus handling
['applinksAddModal', 'applinksEditModal'].forEach(function (id) {
$('#' + id).on('shown.bs.modal', function () {
$(this).find('autofocus]:first').focus();
});
});
$('.collapse').on('shown.bs.collapse', function(){
$(this).parent().find('.fa-angle-right').removeClass('fa-angle-right').addClass('fa-angle-down');
}).on('hidden.bs.collapse', function(){
$(this).parent().find('.fa-angle-down').removeClass('fa-angle-down').addClass('fa-angle-right');
});
$('.modal-backdrop').remove();
function keyboardHandler(event) {
if (event.key === '/') {
document.getElementById('appSearch').focus();
event.preventDefault();
}
}
document.addEventListener('keydown', keyboardHandler);
$scope.$on('$destroy', function () {
document.removeEventListener('keydown', keyboardHandler);
});
}]);