280 lines
21 KiB
HTML
280 lines
21 KiB
HTML
<!-- Modal postinstall confirm -->
|
|
<div class="modal fade" id="appsPostInstallConfirmModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<img ng-src="{{appPostInstallConfirm.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-info-icon"/>
|
|
<div class="app-info-title">
|
|
{{ appPostInstallConfirm.app.manifest.title }}<br/>
|
|
<span class="text-muted text-small">{{ 'app.appInfo.package' | tr }} <a ng-href="/#/appstore/{{appPostInstallConfirm.app.manifest.id}}?version={{appPostInstallConfirm.app.manifest.version}}">v{{ appPostInstallConfirm.app.manifest.version }}</a> </span>
|
|
<br/>
|
|
<span ng-show="appPostInstallConfirm.app.manifest.documentationUrl" class="text-small"><a target="_blank" ng-href="{{appPostInstallConfirm.app.manifest.documentationUrl}}">{{ 'app.docsAction' | tr }}</a> </span>
|
|
<br/>
|
|
</div>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!--
|
|
<p ng-show="appPostInstallConfirm.app.manifest.addons.email">{{ 'app.appInfo.ssoEmail' | tr }}</p>
|
|
<p ng-show="appPostInstallConfirm.app.sso && !appPostInstallConfirm.app.manifest.addons.email">{{ 'app.appInfo.sso' | tr }}</p>
|
|
-->
|
|
<div ng-bind-html="appPostInstallConfirm.message | markdown2html"></div>
|
|
<div ng-show="appPostInstallConfirm.app.manifest.documentationUrl" ng-bind-html="'app.appInfo.appDocsUrl' | tr:{ docsUrl: appPostInstallConfirm.app.manifest.documentationUrl, title: appPostInstallConfirm.app.manifest.title, forumUrl: (appPostInstallConfirm.app.manifest.forumUrl || 'https://forum.cloudron.io') }"></div>
|
|
|
|
<div style="margin-top: 10px; margin-bottom: 5px;" ng-show="pendingChecklistItems(appPostInstallConfirm.app)">
|
|
<label class="control-label">{{ 'app.appInfo.checklist' | tr }}</label>
|
|
</div>
|
|
<div ng-repeat="item in appPostInstallConfirm.app.checklist">
|
|
<div class="checklist-item" ng-hide="item.acknowledged">
|
|
<span ng-bind-html="item.message | markdown2html"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
<a class="btn btn-success" ng-href="{{ 'https://' + appPostInstallConfirm.app.fqdn }}" target="_blank" ng-click="appPostInstallConfirm.submit()">{{ 'app.appInfo.openAction' | tr:{ app: appPostInstallConfirm.app.manifest.title } }}</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal applinks edit -->
|
|
<div class="modal fade" id="applinksEditModal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h4 class="modal-title">{{ 'app.editApplinkDialog.title' | tr }}</h4>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form name="applinksEditForm" role="form" ng-submit="applinksEdit.submit()" autocomplete="off">
|
|
<div class="form-group" ng-class="{ 'has-error': (applinksEditForm.upstreamUri.$dirty && applinksEditForm.upstreamUri.$invalid) || (!applinksEditForm.upstreamUri.$dirty && applinksEdit.error.upstreamUri) }">
|
|
<label class="control-label">{{ 'app.applinks.upstreamUri' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="applinksEdit.upstreamUri" name="upstreamUri" id="inputUpstreamUri" autofocus autocomplete="off" required>
|
|
<span class="text-danger" ng-show="applinksEdit.error.upstreamUri">{{ applinksEdit.error.upstreamUri }}</span>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="control-label">{{ 'app.applinks.label' | tr }}</label>
|
|
<input type="text" class="form-control" ng-model="applinksEdit.label" name="label" id="inputLabel" autocomplete="off">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div>
|
|
<label class="control-label">{{ 'app.display.icon' | tr }}</label>
|
|
</div>
|
|
<div id="previewIcon" class="app-custom-icon" ng-click="applinksEdit.showCustomIconSelector()">
|
|
<img ng-src="{{ applinksEdit.iconUrl() || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)"/>
|
|
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
|
|
</div>
|
|
<a href="" style="font-weight: normal;" ng-click="applinksEdit.resetCustomIcon()">{{ 'app.applinks.clearIconAction' | tr }}</a> - <span class="text-small">{{ 'app.applinks.clearIconDescription' | tr }}</span>
|
|
<input type="file" id="applinksEditIconFileInput" style="display: none" accept="image/png"/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="control-label">{{ 'app.display.tags' | tr }}</label>
|
|
<tag-input class="form-control" placeholder="{{ 'app.display.tagsPlaceholder' | tr }}" taglist="applinksEdit.tags" name="tags" uib-tooltip="{{ 'app.display.tagsTooltip' | tr }}"></tag-input>
|
|
</div>
|
|
|
|
<label class="control-label">{{ 'app.accessControl.userManagement.dashboardVisibility' | tr }} <sup><a ng-href="https://docs.cloudron.io/apps/#dashboard-visibility" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="applinksEdit.accessRestrictionOption" value="any">
|
|
<span>{{ 'app.accessControl.userManagement.visibleForAllUsers' | tr }}</span>
|
|
</label>
|
|
</div>
|
|
<div class="radio">
|
|
<label>
|
|
<input type="radio" ng-model="applinksEdit.accessRestrictionOption" value="groups">
|
|
<span>{{ 'app.accessControl.userManagement.visibleForSelected' | tr }}</span>
|
|
<span class="label label-danger" ng-show="applinksEdit.accessRestrictionOption === 'groups' && !applinksEdit.isAccessRestrictionValid()">{{ 'appstore.installDialog.errorUserManagementSelectAtLeastOne' | tr }}</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<div style="margin-left: 20px; display: flex;">
|
|
<div>
|
|
{{ 'appstore.installDialog.users' | tr }}: <multiselect name="accessUsersSelect" class="input-sm stretch" ng-model="applinksEdit.accessRestriction.users" ng-disabled="applinksEdit.accessRestrictionOption !== 'groups'" options="(user.username || user.email) for user in allUsers" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
</div>
|
|
|
|
<div>
|
|
{{ 'appstore.installDialog.groups' | tr }}: <multiselect name="accessGroupsSelect" class="input-sm stretch" ng-model="applinksEdit.accessRestriction.groups" ng-disabled="applinksEdit.accessRestrictionOption !== 'groups'" options="group.name for group in allGroups" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<input class="ng-hide" type="submit" ng-disabled="applinksEditForm.$invalid || applinksEdit.busyEdit || applinks.busyRemove"/>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-danger pull-left" ng-click="applinksEdit.remove()" ng-disabled="applinksEdit.busyRemove || applinksEdit.busyEdit"><i class="fa fa-circle-notch fa-spin" ng-show="applinksEdit.busyRemove"></i> {{ 'app.editApplinkDialog.deleteAction' | tr }}</button>
|
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'main.dialog.close' | tr }}</button>
|
|
<button type="button" class="btn btn-success" ng-click="applinksEdit.submit()" ng-disabled="applinksEditForm.$invalid || applinksEdit.busyRemove || applinksEdit.busyEdit"><i class="fa fa-circle-notch fa-spin" ng-show="applinksEdit.busyEdit"></i> {{ 'main.dialog.save' | tr }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content content-large">
|
|
|
|
<!-- Workaround for select-all issue, see commit message -->
|
|
<div style="font-size: 1px;"> </div>
|
|
|
|
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && user.isAtLeastAdmin">
|
|
<div class="col-md-12" style="text-align: center;">
|
|
<br/><br/><br/><br/>
|
|
<h1><i class="fa fa-cloud-download fa-fw"></i> {{ 'apps.noApps.title' | tr }}</h1>
|
|
<br/></br>
|
|
<h3 ng-bind-html="'apps.noApps.description' | tr:{ appStoreLink: '#/appstore' }"></h3>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && !user.isAtLeastAdmin">
|
|
<div class="col-md-12" style="text-align: center;">
|
|
<br/><br/><br/><br/>
|
|
<h1>{{ 'apps.noAccess.title' | tr }}</h1>
|
|
<br/></br>
|
|
<h3>{{ 'apps.noAccess.description' | tr }}</h3>
|
|
</div>
|
|
</div>
|
|
|
|
<h1 class="view-header" ng-show="installedApps.length > 0">
|
|
{{ 'apps.title' | tr }}
|
|
<div class="view-header-search-bar">
|
|
<form class="form-inline">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" placeholder="{{ 'apps.searchPlaceholder' | tr }} ( / )" id="appSearch" ng-model="appSearch"/>
|
|
<span class="input-group-btn">
|
|
<button class="btn btn-default" type="button" ng-class="{ 'active': showFilter }" ng-click="toggleFilter()"><i class="fas fa-filter"></i></button>
|
|
</span>
|
|
</div>
|
|
<button class="btn btn-default" style="margin-left: 10px;" type="button" ng-click="toggleView()"><i class="fas" ng-class="{ 'fa-list': view === VIEWS.GRID, 'fa-grip': view === VIEWS.LIST }"></i></button>
|
|
</form>
|
|
</div>
|
|
</h1>
|
|
|
|
<div ng-show="showFilter" class="view-header-filter-bar">
|
|
<form class="form-inline">
|
|
<multiselect ng-model="selectedGroup" ng-show="user.isAtLeastAdmin && groups.length > 1" ms-header="{{ selectedGroup.name }}" options="group.name for group in groups" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
<multiselect ng-model="selectedState" ng-show="user.isAtLeastAdmin" ms-header="{{ 'apps.stateFilterHeader' | tr }}" ms-selected="{{ selectedState }}" options="state.label for state in states" data-multiple="false"></multiselect>
|
|
<multiselect ng-model="selectedTags" ng-show="user.isAtLeastAdmin && tags.length > 0" ms-header="{{ 'apps.tagsFilterHeaderAll' | tr }}" ms-selected="{{ 'apps.tagsFilterHeader' | tr:{ tags: selectedTags.join(', ') } }}" options="tag for tag in tags" data-multiple="true" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
<multiselect ng-model="selectedDomain" data-compare-by="domain" ms-selected="{{ selectedDomain.domain }}" options="domain.domain for domain in filterDomains" data-multiple="false" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
|
<!-- <button class="btn btn-primary" ng-disabled="!selectedTags.length && !selectedState.state && selectedGroup._unset && selectedDomain._alldomains" ng-click="clearAllFilter()">{{ 'apps.filter.clearAll' | tr }}</button> -->
|
|
</form>
|
|
</div>
|
|
|
|
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
|
|
<div class="app-grid" ng-show="view === VIEWS.GRID">
|
|
<div class="grid-item" ng-class="{ 'stopped': app.runState === 'stopped' }" ng-repeat="app in installedApps | selectedGroupAccessFilter:selectedGroup | selectedStateFilter:selectedState | selectedTagFilter:selectedTags | selectedDomainFilter:selectedDomain | appSearchFilter:appSearch | orderBy:orderByFilter">
|
|
<div class="grid-item-content" uib-tooltip="{{ app.fqdn }}" tooltip-append-to-body="true">
|
|
<a ng-show="app.type !== APP_TYPES.LINK && isOperator(app)" ng-href="#/app/{{ app.id}}/info" class="btn btn-lg btn-default grid-item-action"><i class="fas fa-cog"></i></a>
|
|
<div ng-show="app.type === APP_TYPES.LINK && isOperator(app)" ng-click="applinksEdit.show(app)" class="btn btn-lg btn-default grid-item-action"><i class="fas fa-cog"></i></div>
|
|
<a ng-href="{{ app | applicationLink }}" ng-click="onAppClick(app, $event)" target="_blank">
|
|
<div class="grid-item-top">
|
|
<div class="row">
|
|
<div class="col-xs-12 text-center" style="padding-left: 5px; padding-right: 5px;">
|
|
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-icon"/>
|
|
</div>
|
|
</div>
|
|
<br/>
|
|
<div class="row">
|
|
<div class="col-xs-12 text-center">
|
|
<div class="grid-item-top-title" data-fittext>{{ app.label || app.subdomain || app.fqdn }}</div>
|
|
<div class="text-muted status" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden" uib-tooltip="{{ app | appProgressMessage }}">
|
|
{{ app | installationStateLabel }}
|
|
</div>
|
|
<div class="status" ng-style="{ 'visibility': isOperator(app) && (app | installationActive) ? 'visible' : 'hidden' }">
|
|
<div class="progress progress-striped active">
|
|
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="usermanagement-indicator" ng-show="app.type !== APP_TYPES.LINK">
|
|
<i class="fa-brands fa-openid" ng-show="app.ssoAuth && app.manifest.addons.oidc" uib-tooltip="{{ 'apps.auth.openid' | tr }}" tooltip-placement="right"></i>
|
|
<i class="fas fa-user" ng-show="app.ssoAuth && (!app.manifest.addons.oidc && !app.manifest.addons.email)" uib-tooltip="{{ 'apps.auth.sso' | tr }}" tooltip-placement="right"></i>
|
|
<i class="far fa-user" ng-show="!app.ssoAuth && !app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.nosso' | tr }}" tooltip-placement="right"></i>
|
|
<i class="fas fa-envelope" ng-show="app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.email' | tr }}" tooltip-placement="right"></i>
|
|
</div>
|
|
</div>
|
|
|
|
</a>
|
|
</div>
|
|
|
|
<!-- we check the version here because the box updater does not know when an app gets updated -->
|
|
<!-- update info is available to app users. but we should show update indicator only for operators since normal users cannot update -->
|
|
<div class="app-update-badge" ng-click="showAppConfigure(app, 'updates')" ng-show="isOperator(app) && config.update[app.id].manifest.version && config.update[app.id].manifest.version !== app.manifest.version && (app | installSuccess) && !(app.error || app.runState === 'stopped')" uib-tooltip="Update Available">
|
|
<i class="fa fa-arrow-up fa-inverse"></i>
|
|
</div>
|
|
|
|
<div class="app-checklist-badge" ng-click="showAppConfigure(app, 'info')" ng-show="pendingChecklistItems(app)">
|
|
{{ pendingChecklistItems(app) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="app-list card card-large" ng-show="view === VIEWS.LIST">
|
|
<table class="table table-hover" style="margin: 0;">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 32px" class="hand" ng-click="setOrderBy('status')"><i ng-show="orderBy === 'status'" class="fas fa-arrow-{{ orderByReverse ? 'up' : 'down' }}-long"></i></th>
|
|
<th style="width: 32px"> </th>
|
|
<th style="width: 35%" class="hand" ng-click="setOrderBy('location')">{{ 'app.display.label' | tr }} <i ng-show="orderBy === 'location'" class="fas fa-arrow-{{ orderByReverse ? 'up' : 'down' }}-long"></i></th>
|
|
<th style="width: 30%" class="hand hide-mobile" ng-click="setOrderBy('app')">App Title<i ng-show="orderBy === 'app'" class="fas fa-arrow-{{ orderByReverse ? 'up' : 'down' }}-long"></i></th>
|
|
<th style="width: 32px" class="hide-mobile"> </th>
|
|
<th style="width: 32px" class="hand hide-mobile text-center" ng-click="setOrderBy('sso')"><i class="fas fa-user-lock"></i> <i ng-show="orderBy === 'sso'" class="fas fa-arrow-{{ orderByReverse ? 'up' : 'down' }}-long"></i></th>
|
|
<th style="width:160px" class="text-right">{{ 'main.actions' | tr }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="app-list-item" ng-repeat="app in installedApps | selectedGroupAccessFilter:selectedGroup | selectedStateFilter:selectedState | selectedTagFilter:selectedTags | selectedDomainFilter:selectedDomain | appSearchFilter:appSearch | orderBy:orderByFilter:orderByReverse" uib-tooltip="{{ app | appProgressMessage }}">
|
|
<td class="elide-table-cell">
|
|
<i class="fa fa-circle" ng-class="app | installationStateClass" uib-tooltip="{{ app | installationStateLabel }}"></i>
|
|
</td>
|
|
<td class="elide-table-cell app-list-app-link-cell">
|
|
<a ng-href="{{ app | applicationLink }}" ng-click="onAppClick(app, $event)" target="_blank" class="app-list-app-link">
|
|
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-list-item-icon"/>
|
|
</a>
|
|
</td>
|
|
<td class="elide-table-cell app-list-app-link-cell">
|
|
<a ng-href="{{ app | applicationLink }}" ng-click="onAppClick(app, $event)" target="_blank" class="app-list-app-link">
|
|
<span style="font-size: 16px;">{{ app.label || app.subdomain || app.fqdn }}</span><br/>
|
|
<span class="text-muted text-small">{{ app.fqdn.indexOf('http') === 0 ? app.fqdn : 'https://'+app.fqdn }}</span>
|
|
</a>
|
|
</td>
|
|
<td class="elide-table-cell hide-mobile">{{ app.manifest.title || 'App Link' }}</td>
|
|
<td class="elide-table-cell hide-mobile text-center">
|
|
<a class="badge badge-danger" ng-show="pendingChecklistItems(app)" ng-href="#/app/{{ app.id}}/info">{{ pendingChecklistItems(app) }}</a>
|
|
</td>
|
|
<td class="elide-table-cell hide-mobile text-center">
|
|
<div ng-show="app.type !== APP_TYPES.LINK">
|
|
<i class="fa-brands fa-openid" ng-show="app.ssoAuth && app.manifest.addons.oidc" uib-tooltip="{{ 'apps.auth.openid' | tr }}"></i>
|
|
<i class="fas fa-user" ng-show="app.ssoAuth && (!app.manifest.addons.oidc && !app.manifest.addons.email)" uib-tooltip="{{ 'apps.auth.sso' | tr }}"></i>
|
|
<i class="far fa-user" ng-show="!app.ssoAuth && !app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.nosso' | tr }}"></i>
|
|
<i class="fas fa-envelope" ng-show="app.manifest.addons.email" uib-tooltip="{{ 'apps.auth.email' | tr }}"></i>
|
|
</div>
|
|
</td>
|
|
<td class="text-right" style="vertical-align: middle; white-space: nowrap;">
|
|
<span ng-show="isOperator(app)">
|
|
<a class="btn btn-xs btn-success" style="padding: 1px 7px;" ng-show="config.update[app.id].manifest.version && config.update[app.id].manifest.version !== app.manifest.version && (app | installSuccess) && !(app.error || app.runState === 'stopped')" ng-href="#/app/{{ app.id}}/updates" uib-tooltip="Update Available"><i class="fa fa-arrow-up"></i></a>
|
|
|
|
<div class="btn-group btn-group-xs" role="group">
|
|
<a class="btn btn-xs btn-default" ng-show="app.type !== APP_TYPES.LINK" ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank" tooltip-append-to-body="true" uib-tooltip="{{ 'app.logsActionTooltip' | tr }}"><i class="fas fa-align-left"></i></a>
|
|
<a class="btn btn-xs btn-default" ng-show="app.type !== APP_TYPES.PROXIED && app.type !== APP_TYPES.LINK" ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank" tooltip-append-to-body="true" uib-tooltip="{{ 'app.terminalActionTooltip' | tr }}"><i class="fa fa-terminal"></i></a>
|
|
<a class="btn btn-xs btn-default" ng-show="app.manifest.addons.localstorage" ng-href="{{ '/filemanager.html#/home/app/' + app.id }}" target="_blank" tooltip-append-to-body="true" uib-tooltip="{{ 'app.filemanagerActionTooltip' | tr }}"><i class="fas fa-folder"></i></a>
|
|
</div>
|
|
|
|
<button class="btn btn-xs btn-default" ng-show="app.type === APP_TYPES.LINK" ng-click="applinksEdit.show(app)" uib-tooltip="Configure Applink"><i class="fa fa-cog"></i></button>
|
|
<a class="btn btn-xs btn-default" ng-show="app.type !== APP_TYPES.LINK" ng-href="#/app/{{ app.id}}/info" uib-tooltip="Configure App"><i class="fa fa-cog"></i></a>
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<br/>
|
|
<div>
|
|
{{ 'apps.apps.count' | tr:{ count: (installedApps | selectedGroupAccessFilter:selectedGroup | selectedStateFilter:selectedState | selectedTagFilter:selectedTags | selectedDomainFilter:selectedDomain | appSearchFilter:appSearch).length } }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|