Files
cloudron-box/src/views/apps.html

571 lines
43 KiB
HTML
Raw Normal View History

2018-01-22 13:01:38 -08:00
<!-- Modal configure/repair app -->
<div class="modal fade" id="appConfigureModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" ng-show="(appConfigure.app | installError)">Repair {{ appConfigure.app.fqdn }}</h4>
<h4 class="modal-title" ng-hide="(appConfigure.app | installError)">Configure {{ appConfigure.app.fqdn }}</h4>
</div>
2018-05-24 13:53:30 -07:00
<div class="modal-body" style="padding: 0 15px">
2018-01-22 13:01:38 -08:00
<fieldset>
<form role="form" name="appConfigureForm" ng-submit="appConfigure.submit()" autocomplete="off">
2019-01-19 22:04:46 -08:00
<uib-tabset active="appConfigure.action">
<uib-tab index="'general'" heading="General">
2018-05-24 13:53:30 -07:00
<br/>
<div class="has-error text-center" ng-show="appConfigure.error.other">{{ appConfigure.error.other }}</div>
<div class="form-group" ng-class="{ 'has-error': (appConfigureForm.location.$dirty && appConfigureForm.location.$invalid) || (!appConfigureForm.location.$dirty && appConfigure.error.location) }">
<label class="control-label" for="appConfigureLocationInput">Location {{ appConfigure.error.location }} </label>
<div class="input-group form-inline">
<input type="text" class="form-control" ng-model="appConfigure.location" id="appConfigureLocationInput" name="location" placeholder="{{ 'Leave empty to use bare domain' }}" autofocus>
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<!-- the admin check is to check for spaces user -->
<span ng-if="user.admin">{{ (!appConfigure.location ? '' : (appConfigure.domain.config.hyphenatedSubdomains ? '-' : '.')) + appConfigure.domain.domain }}</span>
2018-08-28 14:22:40 -07:00
<span ng-if="!user.admin">{{ (!appConfigure.location ? '' : '-') + spacesSuffix + (appConfigure.domain.config.hyphenatedSubdomains ? '-' : '.') + appConfigure.domain.domain }}</span>
2018-05-24 13:53:30 -07:00
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-repeat="domain in domains">
<a href="" ng-click="appConfigure.domain = domain">{{ domain.domain }}</a>
</li>
</ul>
</div>
</div>
</div>
2018-09-06 19:49:20 -07:00
<p class="text-center" ng-show="appConfigure.location && appConfigure.domain.provider === 'manual'">
2018-05-24 13:53:30 -07:00
<b>Add an A record manually for {{ appConfigure.location }} to this Cloudron's public IP</b>
<br>
</p>
<div class="has-error text-center" ng-show="appConfigure.error.port">{{ appConfigure.error.port }}</div>
<div ng-repeat="(env, info) in appConfigure.portBindingsInfo">
<ng-form name="portInfo_form">
<div class="form-group" ng-class="{ 'has-error': (!appConfigureForm.itemName{{$index}}.$dirty && appConfigure.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
<label class="control-label" for="appConfigurePortInput{{env}}"><input type="checkbox" ng-model="appConfigure.portBindingsEnabled[env]">
{{ info.title }}
<sup>
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}} ({{ HOST_PORT_MIN }} - {{ HOST_PORT_MAX }})"><i class="fa fa-question-circle"></i></a>
</sup>
</label>
2018-05-24 13:53:30 -07:00
<input type="number" class="form-control" ng-model="appConfigure.portBindings[env]" ng-disabled="!appConfigure.portBindingsEnabled[env]" id="appConfigurePortInput{{env}}" later-name="itemName{{$index}}" min="{{HOST_PORT_MIN}}" max="{{HOST_PORT_MAX}}" required>
</div>
</ng-form>
2018-01-22 13:01:38 -08:00
</div>
2018-05-24 13:53:30 -07:00
<div class="form-group">
<label ng-show="appConfigure.ssoAuth" class="control-label">User management</label>
<label ng-show="!appConfigure.ssoAuth" class="control-label">Dashboard visibility</label>
<p ng-show="!appConfigure.ssoAuth && !appConfigure.app.manifest.addons.email" class="text-small">
2018-05-24 13:53:30 -07:00
This app has it's own user management.
</p>
<p ng-show="!appConfigure.ssoAuth && appConfigure.app.manifest.addons.email">
This app is pre-configured for use with <a href="https://cloudron.io/documentation/email/" target="_blank">Cloudron Email</a>.
2018-05-24 13:53:30 -07:00
</p>
<div class="radio">
<label>
<input type="radio" ng-model="appConfigure.accessRestrictionOption" value="any">
<span ng-show="appConfigure.ssoAuth">Allow all users on this Cloudron</span>
<span ng-show="!appConfigure.ssoAuth">Visible to all users on this Cloudron</span>
2018-05-24 13:53:30 -07:00
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="appConfigure.accessRestrictionOption" value="groups">
<span ng-show="appConfigure.ssoAuth">Only allow the following users and groups</span>
<span ng-show="!appConfigure.ssoAuth">Only visible to the following users and groups</span>
<span class="label label-danger" ng-show="appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid()">Select at least one user or group</span>
2018-05-24 13:53:30 -07:00
</label>
2018-01-22 13:01:38 -08:00
</div>
2018-05-24 13:53:30 -07:00
<div>
<div style="margin-left: 20px;">
<div class="col-md-5">
Users:
<multiselect class="input-sm stretch" ng-model="appConfigure.accessRestriction.users" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" options="user.display for user in users" data-multiple="true"></multiselect>
</div>
<div class="col-md-5">
Groups:
2018-07-26 15:45:52 -07:00
<multiselect class="input-sm stretch" ng-model="appConfigure.accessRestriction.groups" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" options="group.name for group in groups" data-multiple="true"></multiselect>
2018-05-24 13:53:30 -07:00
</div>
</div>
</div>
<br/>
<br/>
</div>
2018-01-22 13:01:38 -08:00
2018-05-24 13:53:30 -07:00
</uib-tab>
2019-01-19 22:04:46 -08:00
<uib-tab index="'advanced'" heading="Advanced">
2018-05-24 13:53:30 -07:00
<br/>
<div class="form-group">
<label class="control-label" for="memoryLimit">Memory Limit <sup><a ng-href="{{ config.webServerOrigin }}/documentation/apps/#increasing-the-memory-limit-of-an-app" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ appConfigure.memoryLimit ? appConfigure.memoryLimit / 1024 / 1024 + 'MB' : 'Default (256 MB)' }}</b></label>
<br/>
<div style="padding: 0 10px;">
<slider id="memoryLimit" ng-model="appConfigure.memoryLimit" step="134217728" tooltip="hide" ticks="appConfigure.memoryTicks" ticks-snap-bounds="67108864"></slider>
2018-01-22 13:01:38 -08:00
</div>
</div>
2018-12-06 22:31:38 -08:00
<!-- recvmail currently only works with cloudron email -->
<div class="form-group" ng-show="appConfigure.app.manifest.addons.sendmail || appConfigure.app.manifest.addons.recvmail" ng-class="{ 'has-error': !appConfigureForm.mailboxName.$dirty && appConfigure.error.mailboxName }">
<input type="checkbox" id="appConfigureMailboxNameEnabled" ng-model="appConfigure.mailboxNameEnabled">
<label class="control-label" for="appConfigureMailboxNameEnabled">Custom Mailbox Name</label>
<div class="has-error" ng-show="appConfigure.error.mailboxName">{{ appConfigure.error.mailboxName }}</div>
<div class="input-group form-inline">
<input type="text" class="form-control" id="appConfigureMailboxNameInput" ng-required="appConfigure.mailboxNameEnabled" name="mailboxName" ng-model="appConfigure.mailboxName" uib-tooltip="App FROM email address. Addresses ending with '.app' are reserved." ng-disabled="!appConfigure.mailboxNameEnabled">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" ng-disabled="!appConfigure.mailboxNameEnabled">
@{{ appConfigure.domain.domain }}
</button>
</div>
</div>
</div>
<div class="form-group" ng-class="{ 'has-error': (appConfigureForm.alternateSubdomain.$dirty && appConfigureForm.alternateSubdomain.$invalid) || (!appConfigureForm.alternateSubdomain.$dirty && appConfigure.error.alternateDomains) }">
2018-08-20 09:44:30 -07:00
<input type="checkbox" id="appConfigureAlternateDomainEnabled" ng-model="appConfigure.alternateDomainEnabled">
<label class="control-label" for="appConfigureAlternateDomainEnabled">Redirect the following domain to this app</label>
<div class="has-error" ng-show="appConfigure.error.alternateDomains">{{ appConfigure.error.alternateDomains }}</div>
<div class="input-group form-inline">
<input type="text" class="form-control" ng-model="appConfigure.alternateSubdomain" id="appConfigureAlternateSubdomainInput" name="alternateSubdomain" placeholder="Leave empty to use bare domain" ng-disabled="!appConfigure.alternateDomainEnabled">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" ng-disabled="!appConfigure.alternateDomainEnabled">
2018-09-04 12:10:21 -07:00
<!-- the admin check is to check for spaces user -->
<span ng-if="user.admin">{{ (!appConfigure.alternateSubdomain ? '' : (appConfigure.alternateDomain.config.hyphenatedSubdomains ? '-' : '.')) + appConfigure.alternateDomain.domain }}</span>
<span ng-if="!user.admin">{{ (!appConfigure.alternateSubdomain ? '' : '-') + spacesSuffix + (appConfigure.alternateDomain.config.hyphenatedSubdomains ? '-' : '.') + appConfigure.alternateDomain.domain }}</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-repeat="domain in domains">
2018-09-04 12:10:21 -07:00
<a href="" ng-click="appConfigure.alternateDomain = domain">{{ domain.domain }}</a>
</li>
</ul>
</div>
</div>
</div>
2018-05-24 13:53:30 -07:00
<div class="form-group" ng-class="{ 'has-error': !appConfigureForm.xFrameOptions.$dirty && appConfigure.error.xFrameOptions }">
<label class="control-label">Allow embedding from the following site</label>
<div class="control-label" ng-show="appConfigure.error.xFrameOptions">{{appConfigure.error.xFrameOptions}}</div>
2018-05-24 13:53:30 -07:00
<input type="text" class="form-control" id="appConfigureXFrameOptionsInput" name="xFrameOptions" placeholder="https://example.com" ng-model="appConfigure.xFrameOptions" uib-tooltip="Leave blank to not allow embedding">
</div>
2018-01-22 13:01:38 -08:00
<div ng-hide="true" class="form-group" ng-class="{ 'has-error': !appConfigureForm.dataDir.$dirty && appConfigure.error.dataDir }">
2019-01-15 11:13:41 -08:00
<input type="checkbox" id="appConfigureEnableDataDir" ng-model="appConfigure.dataDirEnabled">
<label class="control-label" for="appConfigureEnableDataDir">Custom Data Directory</label>
<div class="control-label" ng-show="appConfigure.error.dataDir">{{appConfigure.error.dataDir}}</div>
<input type="text" class="form-control" id="appConfigureDataDirInput" name="dataDir" ng-disabled="!appConfigure.dataDirEnabled" placeholder="/mnt/appdata" ng-model="appConfigure.dataDir">
</div>
2018-05-24 13:53:30 -07:00
<div class="form-group">
2019-03-20 09:53:10 -07:00
<label class="control-label" style="width: 100%">Specify robots.txt file content <sup><a ng-href="{{ config.webServerOrigin }}/documentation/apps/#indexing-by-search-engines-robotstxt" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> <a href="" class="pull-right" style="font-weight: normal;" ng-click="appConfigure.robotsTxt = disableIndexingTemplate">Disable indexing</a></label>
<textarea ng-model="appConfigure.robotsTxt" placeholder="Leave empty to allow all bots to index this app." class="form-control" rows="4"></textarea>
2018-05-24 13:53:30 -07:00
</div>
2018-01-22 13:01:38 -08:00
2018-05-24 13:53:30 -07:00
<div class="form-group">
<input type="checkbox" id="appConfigureEnableBackup" ng-model="appConfigure.enableBackup">
<label class="control-label" for="appConfigureEnableBackup">Enable automatic daily backups</label>
2018-01-22 13:01:38 -08:00
</div>
2018-05-24 13:53:30 -07:00
2018-12-07 09:04:06 -08:00
<div class="form-group">
<input type="checkbox" id="appConfigureEnableAutomaticUpdate" ng-model="appConfigure.enableAutomaticUpdate">
<label class="control-label" for="appConfigureEnableAutomaticUpdate">Enable automatic updates</label>
</div>
2018-05-24 13:53:30 -07:00
<div class="hide">
<label class="control-label" for="appConfigureCertificateInput" ng-show="appConfigure.domain.provider !== 'caas'">Certificate (optional)</label>
<div class="has-error text-center" ng-show="appConfigure.error.cert && appConfigure.domain.provider !== 'caas'">{{ appConfigure.error.cert }}</div>
<div class="form-group" ng-class="{ 'has-error': !appConfigureForm.certificate.$dirty && appConfigure.error.cert }" ng-show="appConfigure.domain.provider !== 'caas'">
<div class="input-group">
<input type="file" id="appConfigureCertificateFileInput" onchange="readCertificate()" style="display:none"/>
<input type="text" class="form-control" placeholder="Certificate" ng-model="appConfigure.certificateFileName" id="appConfigureCertificateInput" name="certificate" onclick="getElementById('appConfigureCertificateFileInput').click();" style="cursor: pointer;" ng-required="appConfigure.keyFileName">
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('appConfigureCertificateFileInput').click();"></i>
</span>
</div>
2018-01-22 13:01:38 -08:00
</div>
2018-05-24 13:53:30 -07:00
<div class="form-group" ng-class="{ 'has-error': !appConfigureForm.key.$dirty && appConfigure.error.cert }" ng-show="appConfigure.domain.provider !== 'caas'">
<div class="input-group">
<input type="file" id="appConfigureKeyFileInput" onchange="readKey()" style="display:none"/>
<input type="text" class="form-control" placeholder="Key" ng-model="appConfigure.keyFileName" id="appConfigureKeyInput" name="key" onclick="getElementById('appConfigureKeyFileInput').click();" style="cursor: pointer;" ng-required="appConfigure.certificateFileName">
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('appConfigureKeyFileInput').click();"></i>
</span>
</div>
2018-01-22 13:01:38 -08:00
</div>
</div>
2018-05-24 13:53:30 -07:00
</uib-tab>
</uib-tabset>
2018-01-22 13:01:38 -08:00
2018-02-08 09:44:35 +01:00
<input class="ng-hide" type="submit" ng-disabled="appConfigureForm.$invalid || appConfigure.busy || (appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid())"/>
2018-01-22 13:01:38 -08:00
</form>
</fieldset>
</div>
<div class="modal-footer ">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" ng-click="appConfigure.submit()" ng-disabled="appConfigureForm.$invalid || appConfigure.busy || (appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid())"><i class="fa fa-circle-notch fa-spin" ng-show="appConfigure.busy"></i> Configure</button>
2018-01-22 13:01:38 -08:00
</div>
</div>
</div>
</div>
<!-- Modal restore app -->
<div class="modal fade" id="appRestoreModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
2018-05-28 00:47:53 -07:00
<h4 class="modal-title">Backups - {{ appRestore.app.fqdn }}</h4>
2018-01-22 13:01:38 -08:00
</div>
<div class="modal-body" style="padding: 0 15px">
<p class="text-center" ng-show="appRestore.busyFetching"><i class="fa fa-circle-notch fa-spin"></i> Fetching backups</p>
<button type="button" class="btn btn-primary pull-right" ng-click="appRestore.createBackup()" ng-hide="appRestore.busyFetching" ng-disabled="appRestore.app.installationState === 'pending_backup'"><i class="fa fa-circle-notch fa-spin" ng-show="appRestore.app.installationState === 'pending_backup'"></i> Create Backup</button>
2018-05-30 15:13:05 +02:00
<uib-tabset active="appRestore.action" ng-show="!appRestore.busyFetching">
<!-- restore -->
<uib-tab index="'restore'" heading="Restore">
<br/>
2018-05-30 15:13:05 +02:00
<p class="text-danger" ng-hide="appRestore.backups.length">This app has no backups to restore or clone from yet.</p>
<div ng-show="appRestore.backups.length">
<p>Restoring the app will lose all content generated since the backup.</p>
<label class="control-label">Select Backup</label>
<div class="dropdown">
<button type="button" class="btn btn-default" data-toggle="dropdown">{{ appRestore.selectedBackup.creationTime | prettyDate }} - v{{appRestore.selectedBackup.version}} ({{ appRestore.selectedBackup.creationTime | prettyLongDate }}) <span class="caret"></span></button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="backup in appRestore.backups | orderBy:'-creationTime'">
<a href="" ng-click="appRestore.selectBackup(backup)">{{ backup.creationTime | prettyDate }} - v{{backup.version}} ({{ backup.creationTime | prettyLongDate }})</a>
</li>
</ul>
2019-02-23 18:28:15 -08:00
<input type="text" class="offscreen" aria-hidden="true" id="appRestoreSelectedBackupId" value="{{appRestore.selectedBackup.id}}">
2019-02-24 19:18:43 +01:00
<i style="margin-left: 10px;" class="fa fa-copy hand" uib-tooltip="{{ appRestore.copyBackupIdDone ? 'Copied to clipboard' : 'Click to copy backup id' }}" tooltip-placement="right" ng-click="appRestore.copyBackupId()"></i>
2018-05-30 15:13:05 +02:00
</div>
<br/>
<fieldset>
<form role="form" ng-submit="appRestore.restore()" autocomplete="off">
<div class="form-group" ng-class="{ 'has-error': appRestore.error.password }">
<label class="control-label" for="appRestorePasswordInput">Provide your password to confirm this action</label>
<div ng-show="appRestore.error.password"><small>Wrong password</small></div>
<input type="password" class="form-control" ng-model="appRestore.password" id="appRestorePasswordInput" name="password" required autofocus>
</div>
<input class="ng-hide" type="submit" ng-disabled="!appRestore.password || appRestore.busy || !appRestore.selectedBackup"/>
</form>
</fieldset>
</div>
</uib-tab>
<!-- clone -->
<uib-tab index="'clone'" heading="Clone">
<br/>
2018-05-30 15:13:05 +02:00
<p class="text-danger" ng-hide="appRestore.backups.length">This app has no backups to restore or clone from yet.</p>
<div ng-show="appRestore.backups.length">
<label class="control-label">Select Backup</label>
<div class="dropdown">
<button type="button" class="btn btn-default" data-toggle="dropdown">{{ appRestore.selectedBackup.creationTime | prettyDate }} - v{{appRestore.selectedBackup.version}} ({{ appRestore.selectedBackup.creationTime | prettyLongDate }}) <span class="caret"></span></button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="backup in appRestore.backups | orderBy:'-creationTime'">
<a href="" ng-click="appRestore.selectBackup(backup)">{{ backup.creationTime | prettyDate }} - v{{backup.version}} ({{ backup.creationTime | prettyLongDate }})</a>
</li>
</ul>
</div>
<br/>
<fieldset>
2018-08-06 00:34:40 -07:00
<form role="form" ng-submit="appRestore.clone()" autocomplete="off">
<div class="form-group" ng-class="{ 'has-error': appRestore.error.location }">
<label class="control-label" for="appRestoreLocationInput">Location</label>
<div ng-show="appRestore.error.location"><small>{{ appRestore.error.location }}</small></div>
2018-05-30 15:13:05 +02:00
<div class="input-group form-inline">
<input type="text" class="form-control" ng-model="appRestore.location" id="appRestoreLocationInput" name="location" placeholder="Leave empty to use bare domain" autofocus>
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
2018-08-27 21:40:49 -07:00
<!-- the admin check is to check for spaces user -->
<span ng-if="user.admin">{{ (!appRestore.location ? '' : (appRestore.domain.config.hyphenatedSubdomains ? '-' : '.')) + appRestore.domain.domain }}</span>
2018-08-28 14:22:40 -07:00
<span ng-if="!user.admin">{{ (!appRestore.location ? '' : '-') + spacesSuffix + (appRestore.domain.config.hyphenatedSubdomains ? '-' : '.') + appRestore.domain.domain }}</span>
2018-05-30 15:13:05 +02:00
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-repeat="domain in domains">
<a href="" ng-click="appRestore.domain = domain">{{ domain.domain }}</a>
</li>
</ul>
</div>
</div>
2018-05-28 00:47:53 -07:00
</div>
2018-09-06 19:49:20 -07:00
<p class="text-center" ng-show="appRestore.location && appRestore.domain.provider === 'manual'">
2018-05-30 15:13:05 +02:00
<b>Add an A record manually for {{ appRestore.location }} to this Cloudron's public IP</b>
<br>
</p>
2018-05-30 15:13:05 +02:00
<div class="has-error text-center" ng-show="appRestore.error.port">{{ appRestore.error.port }}</div>
<div ng-repeat="(env, info) in appRestore.portBindingsInfo">
<ng-form name="portInfo_form">
<div class="form-group" ng-class="{ 'has-error': (!appRestore.itemName{{$index}}.$dirty && appRestore.error.port) || (portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid) }">
<label class="control-label" for="inputPortInfo{{env}}"><input type="checkbox" ng-model="appRestore.portBindingsEnabled[env]">
{{ info.title }}
<sup>
<a popover-placement="top-right" popover-trigger="outsideClick" uib-popover="{{info.description}} ({{ HOST_PORT_MIN }} - {{ HOST_PORT_MAX }})"><i class="fa fa-question-circle"></i></a>
</sup>
</label>
2018-05-30 15:13:05 +02:00
<input type="number" class="form-control" ng-model="appRestore.portBindings[env]" ng-disabled="!appRestore.portBindingsEnabled[env]" id="inputPortInfo{{env}}" later-name="itemName{{$index}}" min="{{hostPortMin}}" max="{{hostPortMax}}" required>
</div>
</ng-form>
</div>
</form>
</fieldset>
</div>
</uib-tab>
</uib-tabset>
2018-01-22 13:01:38 -08:00
</div>
2018-01-22 13:01:38 -08:00
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-success" ng-click="appRestore.clone()" ng-show="appRestore.action === 'clone' && appRestore.backups.length !== 0" ng-disabled="appRestore.busy || !appRestore.selectedBackup"><i class="fa fa-circle-notch fa-spin" ng-show="appRestore.busy"></i> Clone</button>
<button type="button" class="btn btn-danger" ng-click="appRestore.restore()" ng-show="appRestore.action === 'restore' && appRestore.backups.length !== 0" ng-disabled="!appRestore.password || appRestore.busy || !appRestore.selectedBackup"><i class="fa fa-circle-notch fa-spin" ng-show="appRestore.busy"></i> Restore</button>
2018-01-22 13:01:38 -08:00
</div>
</div>
</div>
</div>
<!-- Modal information of app -->
<div class="modal fade" id="appInfoModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<img ng-src="{{appInfo.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-info-icon"/>
<h5 class="app-info-title">
{{ appInfo.app.manifest.title }}
<span class="app-info-meta text-small">{{ appInfo.app.upstreamVersion }} (Package <a ng-href="/#/appstore/{{appInfo.app.manifest.id}}?version={{appInfo.app.manifest.version}}">v{{ appInfo.app.manifest.version }}</a>) </span>
2018-08-05 21:10:18 -07:00
<br/>
App ID <span class="app-info-meta text-small">{{ appInfo.app.id }}</a> </span>
<br/>
Last updated <span class="app-info-meta text-small">{{ appInfo.app.updateTime | prettyDate }}</span>
2018-01-22 13:01:38 -08:00
</h5>
</div>
<div class="modal-body">
<div class="app-postinstall-message" ng-hide="appInfo.app.manifest && appInfo.app.manifest.postInstallMessage">
This package has no special usage information.
</div>
<div class="app-postinstall-message" ng-show="appInfo.app.manifest && appInfo.app.manifest.postInstallMessage">
<div ng-bind-html="appInfo.message | postInstallMessage:appInfo.app | markdown2html"></div>
</div>
2019-03-20 21:29:24 -07:00
<div ng-show="appInfo.app.manifest.addons.localstorage.ftp">
<br/>
<b>SFTP</b> <sup><a ng-href="{{ config.webServerOrigin }}/documentation/apps/#ftp-access" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup><br/>
Server: {{ config.adminFqdn }}<br/>
Port: 222<br/>
Username: {{ user.username }}@{{ appInfo.app.fqdn }}<br/>
</div>
2018-01-22 13:01:38 -08:00
</div>
<div class="modal-footer">
<a ng-show="appInfo.app.manifest.documentationUrl" target="_blank" ng-href="{{appInfo.app.manifest.documentationUrl}}" class="btn btn-info pull-left">Documentation</a>
<button type="button" class="btn btn-default" data-dismiss="modal" autofocus>Close</button>
2018-01-22 13:01:38 -08:00
</div>
</div>
</div>
</div>
<!-- Modal postinstall confirm -->
<div class="modal fade" id="appPostInstallConfirmModal" 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"/>
<h5 class="app-info-title">
{{ appPostInstallConfirm.app.manifest.title }}
<span class="app-info-meta text-small">Package <a ng-href="/#/appstore/{{appPostInstallConfirm.app.manifest.id}}?version={{appPostInstallConfirm.app.manifest.version}}">v{{ appPostInstallConfirm.app.manifest.version }}</a> </span>
2018-08-05 21:36:40 -07:00
<br/>
<span ng-show="appPostInstallConfirm.app.manifest.documentationUrl"><a target="_blank" ng-href="{{appPostInstallConfirm.app.manifest.documentationUrl}}">Documentation</a> </span>
<br/>
</h5>
</div>
<div class="modal-body">
<div ng-bind-html="appPostInstallConfirm.app.manifest.postInstallMessage | postInstallMessage:appPostInstallConfirm.app | markdown2html"></div>
<div ng-show="appPostInstallConfirm.app.manifest.documentationUrl">
Please see the <a target="_blank" ng-href="{{appPostInstallConfirm.app.manifest.documentationUrl}}">documentation</a> for more information.
</div>
</div>
<div class="modal-footer">
<div class="form-group pull-left">
<input type="checkbox" id="appPostInstallConfirmCheckbox" ng-model="appPostInstallConfirm.confirmed">
2018-08-05 21:09:16 -07:00
<label class="control-label" for="appPostInstallConfirmCheckbox">Acknowledge instructions</label>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<a class="btn btn-success" ng-href="{{ appPostInstallConfirm.confirmed ? ('https://' + appPostInstallConfirm.app.fqdn) : '' }}" target="_blank" ng-disabled="!appPostInstallConfirm.confirmed" ng-click="appPostInstallConfirm.submit()">Open {{ appPostInstallConfirm.app.manifest.title }}</a>
</div>
</div>
</div>
</div>
2018-01-22 13:01:38 -08:00
<!-- Modal error app -->
<div class="modal fade" id="appErrorModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Error for {{ appError.app.fqdn }}</h4>
</div>
<div class="modal-body">
<p>{{ appError.app.message | prettyAppMessage }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary pull-left" ng-click="appConfigure.show(appError.app)" autofocus>Repair</button>
2018-12-08 21:45:49 -08:00
<a type="button" class="btn btn-default pull-left" ng-href="{{ '/logs.html?appId=' + appError.app.id }}" target="_blank">Logs</a>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
2018-01-22 13:01:38 -08:00
</div>
</div>
</div>
</div>
<!-- Modal uninstall app -->
<div class="modal fade" id="appUninstallModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Really uninstall {{ appUninstall.app.fqdn }} ?</h4>
</div>
<div class="modal-body">
<p>Deleting the app will also remove all content generated within this app!</p>
<fieldset>
<form role="form" name="appUninstallForm" ng-submit="doUninstall()" autocomplete="off">
<div class="form-group" ng-class="{ 'has-error': (appUninstallForm.password.$dirty && appUninstallForm.password.$invalid) || (!appUninstallForm.password.$dirty && appUninstall.error.password) }">
<label class="control-label" for="appUninstallPasswordInput">Provide your password to confirm this action</label>
<div class="control-label" ng-show="(appUninstallForm.password.$dirty && appUninstallForm.password.$invalid) || (!appUninstallForm.password.$dirty && appUninstall.error.password)">
<small ng-show=" appUninstallForm.password.$dirty && appUninstallForm.password.$invalid">Password required</small>
<small ng-show="!appUninstallForm.password.$dirty && appUninstall.error.password">Wrong password</small>
</div>
<input type="password" class="form-control" ng-model="appUninstall.password" id="appUninstallPasswordInput" name="password" required autofocus>
</div>
<input class="ng-hide" type="submit" ng-disabled="appUninstallForm.$invalid || busy"/>
</form>
</fieldset>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" ng-click="doUninstall()" ng-disabled="appUninstallForm.$invalid || appUninstall.busy"><i class="fa fa-circle-notch fa-spin" ng-show="appUninstall.busy"></i> Uninstall</button>
2018-01-22 13:01:38 -08:00
</div>
</div>
</div>
</div>
<!-- Modal update app -->
<div class="modal fade" id="appUpdateModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Update {{ appUpdate.app.fqdn }}</h4>
</div>
<div class="modal-body">
<p>Recent Changes for new version <b>{{ appUpdate.manifest.version}}</b>:</p>
<div ng-bind-html="appUpdate.manifest.changelog | markdown2html"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" ng-click="doUpdate()" ng-disabled="appUpdate.busy"><i class="fa fa-circle-notch fa-spin" ng-show="appUpdate.busy"></i> Update</button>
2018-01-22 13:01:38 -08:00
</div>
</div>
</div>
</div>
<script>
function imageErrorHandler(elem) {
'use strict';
var appstoreIconUrl = elem.getAttribute('appstore-icon');
var fallbackIconUrl = elem.getAttribute('fallback-icon');
if (elem.src === appstoreIconUrl) {
elem.src = fallbackIconUrl;
elem.onerror = null; // avoid retry after default icon cannot be loaded
} else {
elem.src = appstoreIconUrl;
}
}
</script>
<div class="content content-large">
<!-- Workaround for select-all issue, see commit message -->
<div style="font-size: 1px;">&nbsp;</div>
2018-08-28 21:35:17 -07:00
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && (user.admin || config.features.spaces)">
2018-01-22 13:01:38 -08:00
<div class="col-md-12" style="text-align: center;">
<br/><br/><br/><br/>
<h1><i class="fa fa-cloud-download fa-fw"></i> No apps installed yet!</h1>
<br/></br>
<h3>How about installing some? Check out the <a href="#/appstore">App Store</a></h3>
</div>
</div>
2018-08-28 21:35:17 -07:00
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && !(user.admin || config.features.spaces)">
2018-01-22 13:01:38 -08:00
<div class="col-md-12" style="text-align: center;">
<br/><br/><br/><br/>
<h1>You don't have access to any apps on this Cloudron yet!</h1>
<br/></br>
<h3>Once you do, they will show up here.</h3>
</div>
</div>
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
2019-04-02 13:11:00 +02:00
<h1 class="view-header">Your Apps</h1>
2018-01-22 13:01:38 -08:00
</div>
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
<div ng-repeat="domain in domains" ng-show="domainHasApps(domain)">
2019-03-25 16:33:49 +01:00
<h2 class="domain-header" ng-show="moreThanOneDomainHasApps()">{{ domain.domain }}</h2>
2019-03-25 10:42:31 +01:00
<div class="app-grid">
<div class="grid-item" ng-repeat="app in installedApps | filter:{domain:domain.domain}:true | orderBy:'location'">
2019-03-25 10:42:31 +01:00
<a ng-href="{{ app | applicationLink }}" ng-click="((app | installError) === true && showError(app)) || ((app | appIsInstalledAndHealthy) && app.pendingPostInstallConfirmation && appPostInstallConfirm.show(app))" target="_blank" ng-class="{ 'hand': (app | appIsInstalledAndHealthy) }">
<div style="background-color: white;" class="highlight grid-item-content" uib-tooltip="{{ app.fqdn }}">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12 text-center" style="padding-left: 5px; padding-right: 5px;">
<br/>
<img ng-src="{{app.iconUrl || 'img/appicon_fallback.png'}}" fallback-icon="img/appicon_fallback.png" appstore-icon="{{ app.iconUrlStore }}" onerror="imageErrorHandler(this)" class="app-icon"/>
2018-01-22 13:01:38 -08:00
</div>
2019-03-25 10:42:31 +01:00
</div>
<br/>
<div class="row">
<div class="col-xs-12 text-center">
<div class="grid-item-top-title" data-fittext>{{ app.location || app.fqdn }}</div>
<div class="text-muted status" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden" uib-tooltip="{{ app.message | shortAppMessage }}">
{{ app | installationStateLabel }}
</div>
<div class="status" ng-style="{ 'visibility': (app | installationActive) ? 'visible' : 'hidden' }">
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.progress }}%"></div>
2018-01-22 13:01:38 -08:00
</div>
2019-03-25 10:42:31 +01:00
</div>
2018-01-22 13:01:38 -08:00
</div>
2019-03-25 10:42:31 +01:00
</div>
</div>
2018-01-22 13:01:38 -08:00
2019-04-02 13:17:30 +02:00
<div class="grid-item-actions" ng-show="user.admin || (config.features.spaces && app.ownerId === user.id)">
<a href="" ng-click="showUninstall(app)" uib-tooltip="Uninstall" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-times scale"></i></a>
<a href="" ng-click="appRestore.show(app)" ng-show="backupConfig.provider !== 'noop'" uib-tooltip="Backups" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-archive scale"></i></a>
<a href="" ng-click="appConfigure.show(app)" ng-show="(app.installationState === 'installed' || app.installationState === 'pending_configure') && !(app | installError)" uib-tooltip="Configure" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-pencil-alt scale"></i></a>
<a href="" ng-click="appConfigure.show(app)" ng-show="app | installError" uib-tooltip="Repair" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-wrench scale"></i></a>
<a ng-href="{{ '/terminal.html?id=' + app.id }}" target="_blank" uib-tooltip="Terminal" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-terminal scale"></i></a>
<a ng-href="{{ '/logs.html?appId=' + app.id }}" target="_blank" uib-tooltip="Logs" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-file-alt scale"></i></a>
<a href="" ng-click="showInformation(app)" uib-tooltip="Information" tooltip-placement="right" tooltip-class="app-tooltip"><i class="fa fa-info-circle scale"></i></a>
2019-03-25 10:42:31 +01:00
</div>
2018-01-22 13:01:38 -08:00
2019-03-25 10:42:31 +01:00
<!-- we check the version here because the box updater does not know when an app gets updated -->
2019-04-02 13:11:00 +02:00
<div class="app-update-badge" ng-click="showUpdate(app, config.update.apps[app.id].manifest)" ng-show="config.update.apps[app.id].manifest.version && config.update.apps[app.id].manifest.version !== app.manifest.version && (app | installSuccess)">
Update available
2019-03-25 10:42:31 +01:00
</div>
</div>
</a>
</div>
2018-01-22 13:01:38 -08:00
</div>
2019-03-25 10:42:31 +01:00
</div>
2018-01-22 13:01:38 -08:00
</div>
</div>