'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') { if (item.ssoAuth && item.manifest.addons.oidc) return 'oidc'; if (item.ssoAuth && (!item.manifest.addons.oidc && !item.manifest.addons.email)) return 'sso'; if (item.manifest.addons.email) return 'email'; return ''; } 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); }); }]);