Integrate the terminal with the logs ui

This commit is contained in:
Johannes Zellner
2017-08-15 20:00:52 +02:00
committed by Girish Ramakrishnan
parent f98e68edc1
commit 50074b936a
16 changed files with 1021 additions and 931 deletions

View File

@@ -101,69 +101,68 @@
</div>
</div>
<div class="section-header">
<div class="text-left">
<h1>Account</h1>
</div>
</div>
<div class="content">
<div class="card">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-4" style="min-width: 150px;">
<img width="128" height="128" ng-src="{{ user.gravatarHuge }}"/>
</div>
<div class="col-xs-8 text-medium">
<table width="100%">
<tr>
<td class="text-muted" style="vertical-align: top;">Username</td>
<td class="text-right" style="vertical-align: top;">{{ user.username }} &nbsp;&nbsp;&nbsp;</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Display name</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ user.displayName }} <a href="" ng-click="displayNameChange.show()"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Email</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ user.email }} <a href="" ng-click="emailchange.show()" ng-show="!user.alternateEmail"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr ng-show="user.alternateEmail">
<td class="text-muted" style="vertical-align: top;">Password recovery email</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ user.alternateEmail }} <a href="" ng-click="emailchange.show()"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr>
<td class="text-right" colspan="2" style="vertical-align: top;">
<br/>
<button class="btn btn-outline btn-xs btn-danger" ng-click="passwordchange.show()">Change Password</button>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="text-left">
<h1>Account</h1>
</div>
<br/>
<div class="card">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-4" style="min-width: 150px;">
<img width="128" height="128" ng-src="{{ user.gravatarHuge }}"/>
</div>
<div class="col-xs-8 text-medium">
<table width="100%">
<tr>
<td class="text-muted" style="vertical-align: top;">Username</td>
<td class="text-right" style="vertical-align: top;">{{ user.username }} &nbsp;&nbsp;&nbsp;</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Display name</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ user.displayName }} <a href="" ng-click="displayNameChange.show()"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Email</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ user.email }} <a href="" ng-click="emailchange.show()" ng-show="!user.alternateEmail"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr ng-show="user.alternateEmail">
<td class="text-muted" style="vertical-align: top;">Password recovery email</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ user.alternateEmail }} <a href="" ng-click="emailchange.show()"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr>
<td class="text-right" colspan="2" style="vertical-align: top;">
<br/>
<button class="btn btn-outline btn-xs btn-danger" ng-click="passwordchange.show()">Change Password</button>
</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="section-header">
<div class="text-left">
<h3>Sessions</h3>
</div>
</div>
<br/>
<div class="card">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<p>You are logged into {{ activeClients.length + 1 }} app(s), including this session.</p>
<span ng-show="activeTokenCount > 1">
<hr/>
<h4>Active Apps:</h4>
<p ng-repeat="client in activeClients"><b>{{ client.name }} - {{client.activeTokens.length}} time(s)</b></p>
<hr/>
</span>
<button class="btn btn-outline btn-xs btn-danger pull-right" ng-click="revokeTokens()">Logout From All</button>
</div>
</div>
</div>
<div class="text-left">
<h3>Sessions</h3>
</div>
<div class="card">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<p>You are logged into {{ activeClients.length + 1 }} app(s), including this session.</p>
<span ng-show="activeTokenCount > 1">
<hr/>
<h4>Active Apps:</h4>
<p ng-repeat="client in activeClients"><b>{{ client.name }} - {{client.activeTokens.length}} time(s)</b></p>
<hr/>
</span>
<button class="btn btn-outline btn-xs btn-danger pull-right" ng-click="revokeTokens()">Logout From All</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -361,7 +361,7 @@
}
</script>
<div class="content">
<div class="content content-large">
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length === 0 && user.admin">
<div class="col-md-12" style="text-align: center;">

View File

@@ -87,160 +87,156 @@
</div>
</div>
<div class="section-header">
<div class="text-left">
<h1>Domain & Certificates</h1>
</div>
</div>
<div class="section-header">
<div class="text-left">
<h3>Domain</h3>
</div>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
<p ng-show="!config.isCustomDomain">To use a custom domain, configure your domain to use <a target="_blank" href="https://aws.amazon.com/route53/">Route53.</a> Moving to a custom domain will retain all your apps and data and will take around 15 minutes.</p>
<table width="100%">
<tr>
<td class="text-muted" style="vertical-align: top;">Domain name</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.fqdn }}</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">DNS provider</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ dnsConfig.provider }}</td>
</tr>
<tr ng-show="dnsConfig.provider === 'manual' && !dnsConfig.wildcard">
<td colspan="2">
<br/>
No DNS provider is configured. All DNS records need to be setup manually.
To avoid manual setup for each installed app, set a DNS API provider.
</td>
</tr>
<tr ng-show="dnsConfig.provider === 'manual' && dnsConfig.wildcard">
<td colspan="2">
<br/>
Wildcard DNS provider is configured. Always ensure there is a wildcard DNS record for this server's IP.
</td>
</tr>
<tr ng-show="dnsConfig.provider === 'noop'">
<td colspan="2">
<br/>
No DNS provider configured. All DNS records need to be setup manually and all DNS checks are skipped.
</td>
</tr>
<tr ng-show="config.isCustomDomain && dnsConfig.provider === 'route53'">
<td class="text-muted" style="vertical-align: top;">Access key id</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ dnsConfig.accessKeyId || 'unset' }}</td>
</tr>
<tr ng-show="config.isCustomDomain && dnsConfig.provider === 'route53'">
<td class="text-muted" style="vertical-align: top;">Secret access key</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;" ng-click-reveal="dnsConfig.secretAccessKey"><i>hidden</i></td>
</tr>
<tr ng-show="config.isCustomDomain && dnsConfig.provider === 'digitalocean'">
<td class="text-muted" style="vertical-align: top;">DigitalOcean token</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;" ng-click-reveal="dnsConfig.token"><i>hidden</i></td>
</tr>
<!-- add some space -->
<tr>
<td><br/></td>
<td></td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;"></td>
<td class="text-right" style="vertical-align: top;"><button class="btn btn-outline btn-primary" ng-click="showChangeDnsCredentials()">Change</button></td>
</tr>
</table>
</div>
</div>
</div>
<div class="section-header">
<div class="text-left">
<h3>SSL Certificates</h3>
</div>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row" ng-show="!config.isCustomDomain">
<div class="col-md-12">
Certificates can only by set for custom domains.
</div>
</div>
<div class="row" ng-show="config.isCustomDomain">
<div class="col-md-12">
<form name="defaultCertForm" ng-submit="setDefaultCert()">
<fieldset>
<p>Certificates are automatically obtained and renewed from <a href="https://letsencrypt.org/" target="_blank">Lets Encrypt</a>. See the current rate limit <a href="https://letsencrypt.org/docs/rate-limits/" target="_blank">here</a>.</p>
<br/>
<label class="control-label" for="defaultCertInput">Fallback Certificate</label>
<p>This wildcard certificate will be used for apps, should getting a Lets Encrypt certificate fail.</p>
<div class="has-error text-center" ng-show="defaultCert.error">{{ defaultCert.error }}</div>
<div class="text-success text-center" ng-show="defaultCert.success"><b>Upload successful</b></div>
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.cert.$dirty && defaultCert.error) }">
<div class="input-group">
<input type="file" id="defaultCertFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Certificate" ng-model="defaultCert.certificateFileName" id="defaultCertInput" name="cert" onclick="getElementById('defaultCertFileInput').click();" style="cursor: pointer;" ng-disabled="defaultCert.busy" required>
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('defaultCertFileInput').click();"></i>
</span>
</div>
</div>
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.key.$dirty && defaultCert.error) }">
<div class="input-group">
<input type="file" id="defaultKeyFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Key" ng-model="defaultCert.keyFileName" id="defaultKeyInput" name="key" onclick="getElementById('defaultKeyFileInput').click();" style="cursor: pointer;" ng-disabled="defaultCert.busy" required>
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('defaultKeyFileInput').click();"></i>
</span>
</div>
</div>
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="defaultCertForm.$invalid || busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="defaultCert.busy"></i> Upload</button>
</fieldset>
</form>
</div>
</div>
<div class="row hide">
<div class="col-md-12">
<form name="adminCertForm" ng-submit="setAdminCert()">
<fieldset>
<label class="control-label" for="adminCertInput">Settings Certificate</label>
<p>This certificate will be used for this Settings application.</p>
<div class="has-error text-center" ng-show="adminCert.error">{{ adminCert.error }}</div>
<div class="text-success text-center" ng-show="adminCert.success"><b>Upload successful</b></div>
<div class="form-group" ng-class="{ 'has-error': (!adminCert.cert.$dirty && adminCert.error) }">
<div class="input-group">
<input type="file" id="adminCertFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Certificate" ng-model="adminCert.certificateFileName" id="adminCertInput" name="cert" onclick="getElementById('adminCertFileInput').click();" style="cursor: pointer;" ng-disabled="adminCert.busy" required>
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('adminCertFileInput').click();"></i>
</span>
</div>
</div>
<div class="form-group" ng-class="{ 'has-error': (!adminCert.key.$dirty && adminCert.error) }">
<div class="input-group">
<input type="file" id="adminKeyFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Key" ng-model="adminCert.keyFileName" id="adminKeyInput" name="key" onclick="getElementById('adminKeyFileInput').click();" style="cursor: pointer;" ng-disabled="adminCert.busy" required>
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('adminKeyFileInput').click();"></i>
</span>
</div>
</div>
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="adminCertForm.$invalid || busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="adminCert.busy"></i> Upload</button>
</fieldset>
</form>
</div>
</div>
<div class="content">
<div class="text-left">
<h1>Domain & Certificates</h1>
</div>
<div class="text-left">
<h3>Domain</h3>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
<p ng-show="!config.isCustomDomain">To use a custom domain, configure your domain to use <a target="_blank" href="https://aws.amazon.com/route53/">Route53.</a> Moving to a custom domain will retain all your apps and data and will take around 15 minutes.</p>
<table width="100%">
<tr>
<td class="text-muted" style="vertical-align: top;">Domain name</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.fqdn }}</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">DNS provider</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ dnsConfig.provider }}</td>
</tr>
<tr ng-show="dnsConfig.provider === 'manual' && !dnsConfig.wildcard">
<td colspan="2">
<br/>
No DNS provider is configured. All DNS records need to be setup manually.
To avoid manual setup for each installed app, set a DNS API provider.
</td>
</tr>
<tr ng-show="dnsConfig.provider === 'manual' && dnsConfig.wildcard">
<td colspan="2">
<br/>
Wildcard DNS provider is configured. Always ensure there is a wildcard DNS record for this server's IP.
</td>
</tr>
<tr ng-show="dnsConfig.provider === 'noop'">
<td colspan="2">
<br/>
No DNS provider configured. All DNS records need to be setup manually and all DNS checks are skipped.
</td>
</tr>
<tr ng-show="config.isCustomDomain && dnsConfig.provider === 'route53'">
<td class="text-muted" style="vertical-align: top;">Access key id</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ dnsConfig.accessKeyId || 'unset' }}</td>
</tr>
<tr ng-show="config.isCustomDomain && dnsConfig.provider === 'route53'">
<td class="text-muted" style="vertical-align: top;">Secret access key</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;" ng-click-reveal="dnsConfig.secretAccessKey"><i>hidden</i></td>
</tr>
<tr ng-show="config.isCustomDomain && dnsConfig.provider === 'digitalocean'">
<td class="text-muted" style="vertical-align: top;">DigitalOcean token</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;" ng-click-reveal="dnsConfig.token"><i>hidden</i></td>
</tr>
<!-- add some space -->
<tr>
<td><br/></td>
<td></td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;"></td>
<td class="text-right" style="vertical-align: top;"><button class="btn btn-outline btn-primary" ng-click="showChangeDnsCredentials()">Change</button></td>
</tr>
</table>
</div>
</div>
</div>
<div class="text-left">
<h3>SSL Certificates</h3>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row" ng-show="!config.isCustomDomain">
<div class="col-md-12">
Certificates can only by set for custom domains.
</div>
</div>
<div class="row" ng-show="config.isCustomDomain">
<div class="col-md-12">
<form name="defaultCertForm" ng-submit="setDefaultCert()">
<fieldset>
<p>Certificates are automatically obtained and renewed from <a href="https://letsencrypt.org/" target="_blank">Lets Encrypt</a>. See the current rate limit <a href="https://letsencrypt.org/docs/rate-limits/" target="_blank">here</a>.</p>
<br/>
<label class="control-label" for="defaultCertInput">Fallback Certificate</label>
<p>This wildcard certificate will be used for apps, should getting a Lets Encrypt certificate fail.</p>
<div class="has-error text-center" ng-show="defaultCert.error">{{ defaultCert.error }}</div>
<div class="text-success text-center" ng-show="defaultCert.success"><b>Upload successful</b></div>
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.cert.$dirty && defaultCert.error) }">
<div class="input-group">
<input type="file" id="defaultCertFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Certificate" ng-model="defaultCert.certificateFileName" id="defaultCertInput" name="cert" onclick="getElementById('defaultCertFileInput').click();" style="cursor: pointer;" ng-disabled="defaultCert.busy" required>
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('defaultCertFileInput').click();"></i>
</span>
</div>
</div>
<div class="form-group" ng-class="{ 'has-error': (!defaultCert.key.$dirty && defaultCert.error) }">
<div class="input-group">
<input type="file" id="defaultKeyFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Key" ng-model="defaultCert.keyFileName" id="defaultKeyInput" name="key" onclick="getElementById('defaultKeyFileInput').click();" style="cursor: pointer;" ng-disabled="defaultCert.busy" required>
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('defaultKeyFileInput').click();"></i>
</span>
</div>
</div>
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="defaultCertForm.$invalid || busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="defaultCert.busy"></i> Upload</button>
</fieldset>
</form>
</div>
</div>
<div class="row hide">
<div class="col-md-12">
<form name="adminCertForm" ng-submit="setAdminCert()">
<fieldset>
<label class="control-label" for="adminCertInput">Settings Certificate</label>
<p>This certificate will be used for this Settings application.</p>
<div class="has-error text-center" ng-show="adminCert.error">{{ adminCert.error }}</div>
<div class="text-success text-center" ng-show="adminCert.success"><b>Upload successful</b></div>
<div class="form-group" ng-class="{ 'has-error': (!adminCert.cert.$dirty && adminCert.error) }">
<div class="input-group">
<input type="file" id="adminCertFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Certificate" ng-model="adminCert.certificateFileName" id="adminCertInput" name="cert" onclick="getElementById('adminCertFileInput').click();" style="cursor: pointer;" ng-disabled="adminCert.busy" required>
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('adminCertFileInput').click();"></i>
</span>
</div>
</div>
<div class="form-group" ng-class="{ 'has-error': (!adminCert.key.$dirty && adminCert.error) }">
<div class="input-group">
<input type="file" id="adminKeyFileInput" style="display:none"/>
<input type="text" class="form-control" placeholder="Key" ng-model="adminCert.keyFileName" id="adminKeyInput" name="key" onclick="getElementById('adminKeyFileInput').click();" style="cursor: pointer;" ng-disabled="adminCert.busy" required>
<span class="input-group-addon">
<i class="fa fa-upload" onclick="getElementById('adminKeyFileInput').click();"></i>
</span>
</div>
</div>
<button type="submit" class="btn btn-outline btn-success pull-right" ng-disabled="adminCertForm.$invalid || busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="adminCert.busy"></i> Upload</button>
</fieldset>
</form>
</div>
</div>
</div>
</div>

View File

@@ -1,211 +1,203 @@
<!-- Modal enable email -->
<div class="modal fade" id="enableEmailModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Cloudron Email Server</h4>
</div>
<div class="modal-body" ng-show="dnsConfig.provider === 'noop' || dnsConfig.provider === 'manual'">
No DNS provider is setup. Displayed DNS records will have to be setup manually.<br/>
</div>
<div class="modal-body" ng-show="dnsConfig.provider === 'route53' || dnsConfig.provider === 'digitalocean'">
The Cloudron will setup Email related DNS records automatically.
If this domain is already configured to handle email with some other provider, it will <b>overwrite</b> those records.
<br/><br/>
Disabling Cloudron Email later will <b>not</b> put the old records back.
<br/><br/>
Status of DNS Records will show an error when DNS is propagating (~5 minutes).
<br/>
</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="email.enable()">I understand, enable</button>
</div>
</div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Cloudron Email Server</h4>
</div>
<div class="modal-body" ng-show="dnsConfig.provider === 'noop' || dnsConfig.provider === 'manual'">
No DNS provider is setup. Displayed DNS records will have to be setup manually.<br/>
</div>
<div class="modal-body" ng-show="dnsConfig.provider === 'route53' || dnsConfig.provider === 'digitalocean'">
The Cloudron will setup Email related DNS records automatically.
If this domain is already configured to handle email with some other provider, it will <b>overwrite</b> those records.
<br/><br/>
Disabling Cloudron Email later will <b>not</b> put the old records back.
<br/><br/>
Status of DNS Records will show an error when DNS is propagating (~5 minutes).
<br/>
</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="email.enable()">I understand, enable</button>
</div>
</div>
</div>
</div>
<div class="section-header">
<div class="text-left">
<h1>Email</h1>
</div>
</div>
<div class="content">
<div class="text-left">
<h1>Email</h1>
</div>
<div class="section-header">
<div class="text-left">
<h3>IMAP and SMTP Server</h3>
</div>
</div>
<div class="text-left">
<h3>IMAP and SMTP Server</h3>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
Cloudron has a built-in <a ng-href="{{ config.webServerOrigin + '/documentation/email/' }}" target="_blank">email server</a> that allows users to send and receive email for your domain.
</div>
<div class="col-md-12">
Cloudron has a built-in <a ng-href="{{ config.webServerOrigin + '/documentation/email/' }}" target="_blank">email server</a> that allows users to send and receive email for your domain.
</div>
</div>
<div class="row" ng-show="mailConfig.enabled">
<br/>
<div class="col-md-12">
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#mail_settings">Mail server settings for email clients</a>
<div id="mail_settings" class="panel-collapse collapse">
<br/>
<p><b>Incoming Mail (IMAP)</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 993 (TLS)</p>
<p><b>Outgoing Mail (SMTP)</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 587 (STARTTLS)</p>
<p><b>ManageSieve</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 4190 (TLS)</p>
<p>All the servers require your Cloudron credentials for authentication.</p>
</div>
<br/>
<div class="col-md-12">
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#mail_settings">Mail server settings for email clients</a>
<div id="mail_settings" class="panel-collapse collapse">
<br/>
<p><b>Incoming Mail (IMAP)</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 993 (TLS)</p>
<p><b>Outgoing Mail (SMTP)</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 587 (STARTTLS)</p>
<p><b>ManageSieve</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 4190 (TLS)</p>
<p>All the servers require your Cloudron credentials for authentication.</p>
</div>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-12" ng-show="dnsConfig.provider !== 'caas'">
<button ng-class="mailConfig.enabled ? 'btn btn-danger' : 'btn btn-primary'" ng-click="email.toggle()" ng-enabled="mailConfig">{{ mailConfig.enabled ? "Disable Email" : "Enable Email" }}</button>
</div>
<div class="col-md-12" ng-show="dnsConfig.provider === 'caas'">
<span class="text-danger text-bold">This feature requires the Cloudron to be on <a ng-href="{{ config.webServerOrigin + '/documentation/managed-hosting/#using-a-custom-domain' }}" target="_blank">custom domain</a>.</span>
</div>
<div class="col-md-12" ng-show="dnsConfig.provider !== 'caas'">
<button ng-class="mailConfig.enabled ? 'btn btn-danger' : 'btn btn-primary'" ng-click="email.toggle()" ng-enabled="mailConfig">{{ mailConfig.enabled ? "Disable Email" : "Enable Email" }}</button>
</div>
<div class="col-md-12" ng-show="dnsConfig.provider === 'caas'">
<span class="text-danger text-bold">This feature requires the Cloudron to be on <a ng-href="{{ config.webServerOrigin + '/documentation/managed-hosting/#using-a-custom-domain' }}" target="_blank">custom domain</a>.</span>
</div>
</div>
</div>
</div>
<div class="section-header" ng-show="isPaying">
<div class="text-left">
<h3>Outbound Mail Relay</h3>
</div>
</div>
<div class="text-left" ng-show="isPaying">
<h3>Outbound Mail Relay</h3>
</div>
<div class="card" style="margin-bottom: 15px;" ng-show="isPaying">
<div class="row">
<div class="card" style="margin-bottom: 15px;" ng-show="isPaying">
<div class="row">
<div class="col-md-12">
Select the mail server through which Cloudron will send outbound mails:
Select the mail server through which Cloudron will send outbound mails:
</div>
</div>
<br/>
</div>
<br/>
<div class="row">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<select class="form-control" style="width: 50%;" ng-model="mailRelay.preset" ng-options="a.name for a in mailRelayPresets track by a.provider" ng-change="mailRelay.presetChanged()"></select>
</div>
<div class="form-group">
<select class="form-control" style="width: 50%;" ng-model="mailRelay.preset" ng-options="a.name for a in mailRelayPresets track by a.provider" ng-change="mailRelay.presetChanged()"></select>
</div>
</div>
</div>
<div class="row" ng-show="mailRelay.preset.provider !== 'cloudron-smtp'">
</div>
<div class="row" ng-show="mailRelay.preset.provider !== 'cloudron-smtp'">
<div class="col-md-6">
<div>
<form name="mailRelayForm" role="form" ng-submit="mailRelay.submit()" autocomplete="off" novalidate>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.host.$dirty && mailRelayForm.host.$invalid) }">
<label class="control-label">SMTP Host</label>
<div class="control-label" ng-show="(!mailRelayForm.host.$dirty && mailRelay.error.host) || (mailRelayForm.host.$dirty && mailRelayForm.host.$invalid)">
<small ng-show="!mailRelayForm.host.$dirty && mailRelay.error.host">{{ mailRelay.error.host }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.host" name="host" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.port.$dirty && mailRelayForm.port.$invalid) }">
<label class="control-label">SMTP Port (STARTTLS)</label>
<div class="control-label" ng-show="(!mailRelayForm.port.$dirty && mailRelay.error.port) || (mailRelayForm.port.$dirty && mailRelayForm.port.$invalid)">
<small ng-show="!mailRelayForm.port.$dirty && mailRelay.error.port">{{ mailRelay.error.port }}</small>
</div>
<input type="number" class="form-control" ng-model="mailRelay.relay.port" name="port" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.username.$dirty && mailRelayForm.username.$invalid) }">
<label class="control-label">Username</label>
<div class="control-label" ng-show="(!mailRelayForm.username.$dirty && mailRelay.error.username) || (mailRelayForm.username.$dirty && mailRelayForm.username.$invalid)">
<small ng-show="!mailRelayForm.username.$dirty && mailRelay.error.username">{{ mailRelay.error.username }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.username" name="username" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.password.$dirty && mailRelayForm.password.$invalid) }">
<label class="control-label">Password</label>
<div class="control-label" ng-show="(!mailRelayForm.password.$dirty && mailRelay.error.password) || (mailRelayForm.password.$dirty && mailRelayForm.password.$invalid)">
<small ng-show="!mailRelayForm.password.$dirty && mailRelay.error.password">{{ mailRelay.error.password }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.password" name="password" required>
</div>
<input class="ng-hide" type="submit" ng-disabled="mailRelayForm.$invalid"/>
</form>
</div>
<div>
<form name="mailRelayForm" role="form" ng-submit="mailRelay.submit()" autocomplete="off" novalidate>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.host.$dirty && mailRelayForm.host.$invalid) }">
<label class="control-label">SMTP Host</label>
<div class="control-label" ng-show="(!mailRelayForm.host.$dirty && mailRelay.error.host) || (mailRelayForm.host.$dirty && mailRelayForm.host.$invalid)">
<small ng-show="!mailRelayForm.host.$dirty && mailRelay.error.host">{{ mailRelay.error.host }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.host" name="host" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.port.$dirty && mailRelayForm.port.$invalid) }">
<label class="control-label">SMTP Port (STARTTLS)</label>
<div class="control-label" ng-show="(!mailRelayForm.port.$dirty && mailRelay.error.port) || (mailRelayForm.port.$dirty && mailRelayForm.port.$invalid)">
<small ng-show="!mailRelayForm.port.$dirty && mailRelay.error.port">{{ mailRelay.error.port }}</small>
</div>
<input type="number" class="form-control" ng-model="mailRelay.relay.port" name="port" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.username.$dirty && mailRelayForm.username.$invalid) }">
<label class="control-label">Username</label>
<div class="control-label" ng-show="(!mailRelayForm.username.$dirty && mailRelay.error.username) || (mailRelayForm.username.$dirty && mailRelayForm.username.$invalid)">
<small ng-show="!mailRelayForm.username.$dirty && mailRelay.error.username">{{ mailRelay.error.username }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.username" name="username" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (mailRelayForm.password.$dirty && mailRelayForm.password.$invalid) }">
<label class="control-label">Password</label>
<div class="control-label" ng-show="(!mailRelayForm.password.$dirty && mailRelay.error.password) || (mailRelayForm.password.$dirty && mailRelayForm.password.$invalid)">
<small ng-show="!mailRelayForm.password.$dirty && mailRelay.error.password">{{ mailRelay.error.password }}</small>
</div>
<input type="text" class="form-control" ng-model="mailRelay.relay.password" name="password" required>
</div>
<input class="ng-hide" type="submit" ng-disabled="mailRelayForm.$invalid"/>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-2">
<button class="btn btn-primary" ng-click="mailRelay.submit()" ng-disabled="(mailRelay.preset.provider !== 'cloudron-smtp' && (!mailRelayForm.$dirty || mailRelayForm.$invalid)) || mailRelay.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="mailRelay.busy"></i> Save</button>
</div>
<div class="col-md-10">
<span class="has-error text-center" ng-show="mailRelay.error">{{ mailRelay.error }}</span>
</div>
<div class="col-md-2">
<button class="btn btn-primary" ng-click="mailRelay.submit()" ng-disabled="(mailRelay.preset.provider !== 'cloudron-smtp' && (!mailRelayForm.$dirty || mailRelayForm.$invalid)) || mailRelay.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="mailRelay.busy"></i> Save</button>
</div>
<div class="col-md-10">
<span class="has-error text-center" ng-show="mailRelay.error">{{ mailRelay.error }}</span>
</div>
</div>
</div>
</div>
<div class="section-header" ng-show="mailConfig.enabled && isPaying">
<div class="text-left">
<h3>Catch-all</h3>
</div>
</div>
<div class="text-left" ng-show="mailConfig.enabled && isPaying">
<h3>Catch-all</h3>
</div>
<div class="card" style="margin-bottom: 15px;" ng-show="mailConfig.enabled && isPaying">
<div class="card" style="margin-bottom: 15px;" ng-show="mailConfig.enabled && isPaying">
<div class="row">
<div class="col-md-12">
Emails sent to non existing addresses will be forwarded to the following accounts:
</div>
<div class="col-md-12">
Emails sent to non existing addresses will be forwarded to the following accounts:
</div>
</div>
<br/>
<div class="row">
<div class="col-md-6">
<multiselect ng-model="catchall.addresses" options="address for address in catchall.availableAddresses" data-multiple="true"></multiselect>
<button class="btn btn-outline btn-primary" ng-disabled="catchall.busy" ng-click="catchall.submit()"><i class="fa fa-circle-o-notch fa-spin" ng-show="catchall.busy"></i> Save</button>
</div>
<div class="col-md-6">
<multiselect ng-model="catchall.addresses" options="address for address in catchall.availableAddresses" data-multiple="true"></multiselect>
<button class="btn btn-outline btn-primary" ng-disabled="catchall.busy" ng-click="catchall.submit()"><i class="fa fa-circle-o-notch fa-spin" ng-show="catchall.busy"></i> Save</button>
</div>
</div>
</div>
</div>
<div class="section-header" ng-show="dnsConfig.provider && dnsConfig.provider !== 'caas'">
<div class="text-left">
<h3>DNS Records</h3>
</div>
</div>
<div class="text-left" ng-show="dnsConfig.provider && dnsConfig.provider !== 'caas'">
<h3>DNS Records</h3>
</div>
<div class="card" style="margin-bottom: 15px;" ng-show="dnsConfig.provider && dnsConfig.provider !== 'caas'">
<div class="card" style="margin-bottom: 15px;" ng-show="dnsConfig.provider && dnsConfig.provider !== 'caas'">
<div class="row">
<div class="col-md-12">
Set the following DNS records to guarantee email delivery:
<div class="col-md-12">
Set the following DNS records to guarantee email delivery:
<br/><br/>
<br/><br/>
<div ng-repeat="record in expectedDnsRecordsTypes">
<div class="row" ng-if="expectedDnsRecords[record.value] && (mailConfig.enabled || (record.name !== 'DMARC' && record.name !== 'MX'))">
<div class="col-xs-12">
<p class="text-muted">
<i ng-class="expectedDnsRecords[record.value].status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_dns_{{ record.value }}">{{ record.name }} record</a>
<button class="btn btn-xs btn-default" ng-click="email.refresh()" ng-disabled="email.refreshBusy" ng-show="!expectedDnsRecords[record.value].status"><i class="fa fa-refresh" ng-class="{ 'fa-pulse': email.refreshBusy }"></i></button>
</p>
<div id="collapse_dns_{{ record.value }}" class="panel-collapse collapse">
<div class="panel-body">
<p>Domain: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].domain }}</tt></b></p>
<p>Record type: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].type }}</tt></b></p>
<p style="overflow: auto; white-space: nowrap;">Expected value: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].expected }}</tt></b></p>
<p style="overflow: auto; white-space: nowrap;">Current value: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].value ? expectedDnsRecords[record.value].value : '[not set]' }}</tt></b></p>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<p class="text-muted">
<i ng-class="relay.status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_dns_port">
Outbound SMTP
</a>
<button class="btn btn-xs btn-default" ng-click="email.refresh()" ng-disabled="email.refreshBusy" ng-show="!relay.status"><i class="fa fa-refresh" ng-class="{ 'fa-pulse': email.refreshBusy }"></i></button>
</p>
<div id="collapse_dns_port" class="panel-collapse collapse">
<div class="panel-body">
<p><b> {{ relay.value }} </b> </p>
</div>
</div>
<div ng-repeat="record in expectedDnsRecordsTypes">
<div class="row" ng-if="expectedDnsRecords[record.value] && (mailConfig.enabled || (record.name !== 'DMARC' && record.name !== 'MX'))">
<div class="col-xs-12">
<p class="text-muted">
<i ng-class="expectedDnsRecords[record.value].status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_dns_{{ record.value }}">{{ record.name }} record</a>
<button class="btn btn-xs btn-default" ng-click="email.refresh()" ng-disabled="email.refreshBusy" ng-show="!expectedDnsRecords[record.value].status"><i class="fa fa-refresh" ng-class="{ 'fa-pulse': email.refreshBusy }"></i></button>
</p>
<div id="collapse_dns_{{ record.value }}" class="panel-collapse collapse">
<div class="panel-body">
<p>Domain: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].domain }}</tt></b></p>
<p>Record type: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].type }}</tt></b></p>
<p style="overflow: auto; white-space: nowrap;">Expected value: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].expected }}</tt></b></p>
<p style="overflow: auto; white-space: nowrap;">Current value: <b ng-click-select><tt>{{ expectedDnsRecords[record.value].value ? expectedDnsRecords[record.value].value : '[not set]' }}</tt></b></p>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<p class="text-muted">
<i ng-class="relay.status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_dns_port">
Outbound SMTP
</a>
<button class="btn btn-xs btn-default" ng-click="email.refresh()" ng-disabled="email.refreshBusy" ng-show="!relay.status"><i class="fa fa-refresh" ng-class="{ 'fa-pulse': email.refreshBusy }"></i></button>
</p>
<div id="collapse_dns_port" class="panel-collapse collapse">
<div class="panel-body">
<p><b> {{ relay.value }} </b> </p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div class="content">
<div class="content content-large">
<div class="text-left">
<h2>Memory</h2>

View File

@@ -1,24 +1,16 @@
<div class="logs-main">
<div class="logs-header">
<div class="col-md-10 col-md-offset-1">
<div class="text-left">
<h1>Logs</h1>
</div>
</div>
</div>
<div class="logs-controls">
<div class="col-md-10 col-md-offset-1">
<div class="filter">
<select class="form-control" ng-options="log.name for log in logs track by log.value" ng-model="selected"></select>
</div>
<div class="pagination pull-right">
<a class="btn btn-default btn-outline" ng-href="{{ selected.url }}&format=short&lines=800"><i class="fa fa-download"></i> Download </a>
</div>
<uib-tabset active="active">
<uib-tab index="0" heading="Logs" select="showLogs()"></uib-tab>
<uib-tab index="1" heading="Terminal" select="showTerminal()"></uib-tab>
</uib-tabset>
<select class="form-control pull-right" ng-options="log.name for log in logs track by log.value" ng-model="selected"></select>
<a class="btn btn-default pull-right" ng-href="{{ selected.url }}&format=short&lines=800" ng-hide="terminalVisible"><i class="fa fa-download"></i> Download </a>
</div>
</div>
<div class="col-md-10 col-md-offset-1 log-line-container"></div>
<div class="logs-and-term-container"></div>
</div>

View File

@@ -1,6 +1,7 @@
'use strict';
/* global moment */
/* global Terminal */
angular.module('Application').controller('LogsController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
Client.onReady(function () { if (!Client.getUserInfo().admin) $location.path('/'); });
@@ -11,7 +12,9 @@ angular.module('Application').controller('LogsController', ['$scope', '$location
$scope.logs = [];
$scope.selected = '';
$scope.activeEventSource = null;
$scope.terminal = null;
$scope.lines = 10;
$scope.terminalVisible = false;
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
@@ -29,21 +32,30 @@ angular.module('Application').controller('LogsController', ['$scope', '$location
$scope.selected = $scope.logs[0];
};
$scope.$watch('selected', function (newVal) {
if (!newVal) return;
function reset() {
// close the old event source so we wont receive any new logs
if ($scope.activeEventSource) {
$scope.activeEventSource.close();
$scope.activeEventSource = null;
}
var func = newVal.type === 'platform' ? Client.getPlatformLogs : Client.getAppLogs;
func(newVal.value, true, $scope.lines, function handleLogs(error, result) {
if (error) return console.error(error);
var logViewer = $('.logs-and-term-container');
logViewer.empty();
var logViewer = $('.log-line-container');
logViewer.empty();
if ($scope.terminal) {
$scope.terminal.destroy();
$scope.terminal = null;
}
}
$scope.showLogs = function () {
$scope.terminalVisible = false;
reset();
var func = $scope.selected.type === 'platform' ? Client.getPlatformLogs : Client.getAppLogs;
func($scope.selected.value, true, $scope.lines, function handleLogs(error, result) {
if (error) return console.error(error);
$scope.activeEventSource = result;
result.onmessage = function handleMessage(message) {
@@ -56,17 +68,44 @@ angular.module('Application').controller('LogsController', ['$scope', '$location
}
// check if we want to auto scroll (this is before the appending, as that skews the check)
var tmp = document.querySelector('.log-line-container');
var tmp = $('.logs-and-term-container');
var autoScroll = tmp.scrollTop > (tmp.scrollTopMax - 24);
var logLine = $('<div class="log-line">');
var timeString = moment.utc(data.realtimeTimestamp/1000).format('MMM DD HH:mm:ss');
logLine.html('<span class="time">' + timeString + ' </span>' + window.ansiToHTML(typeof data.message === 'string' ? data.message : ab2str(data.message)));
logViewer.append(logLine);
tmp.append(logLine);
if (autoScroll) tmp.lastChild.scrollIntoView({ behavior: 'instant', block: 'end' });
};
});
};
$scope.showTerminal = function () {
$scope.terminalVisible = true;
reset();
$scope.terminal = new Terminal();
try {
// websocket cannot use relative urls
var url = Client.apiOrigin.replace('https', 'wss') + '/api/v1/apps/' + $scope.selected.value + '/exec?tty=true';
var socket = new WebSocket(url);
$scope.terminal.attach(socket);
} catch (e) {
console.error('-----', e);
}
$scope.terminal.open(document.querySelector('.logs-and-term-container'));
$scope.terminal.fit();
};
$scope.$watch('selected', function (newVal) {
if (!newVal) return;
if ($scope.terminalVisible) $scope.showTerminal();
else $scope.showLogs();
});
Client.onReady($scope.populateLogTypes);
@@ -77,5 +116,9 @@ angular.module('Application').controller('LogsController', ['$scope', '$location
$scope.activeEventSource.close();
$scope.activeEventSource = null;
}
if ($scope.terminal) {
$scope.terminal.destroy();
}
});
}]);

View File

@@ -228,234 +228,225 @@
</div>
</div>
<div class="section-header">
<div class="text-left">
<h1>Settings</h1>
</div>
</div>
<div class="section-header">
<div class="text-left">
<h3>About</h3>
</div>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-xs-4" style="min-width: 150px;">
<div class="settings-avatar" ng-click="avatarChange.showChangeAvatar()" style="background-image: url('{{ client.avatar }}');">
<div class="overlay"></div>
</div>
</div>
<div class="col-xs-8">
<table width="100%">
<tr>
<td class="text-muted" style="vertical-align: top;">Name</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.cloudronName }} <a href="" ng-click="cloudronNameChange.show()"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr ng-show="config.provider === 'caas'">
<td class="text-muted" style="vertical-align: top;">Model</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.size }} - {{ config.region }}</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Version</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.version }}</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Provider</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.provider }}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="section-header" ng-show="config.provider === 'caas'">
<div class="text-left">
<h3>Plans</h3>
</div>
</div>
<div class="card" style="margin-bottom: 15px;" ng-show="config.provider === 'caas'">
<div class="row">
<div class="col-xs-12 text-right">
<a href="{{ config.webServerOrigin }}/console.html#/userprofile?view=credit_card" target="_blank">Change payment method</a>
or
<a href="{{ config.webServerOrigin }}/console.html" target="_blank">Cancel this Cloudron</a>
</div>
</div>
<div class="row">
<div class="col-xs-10 plans" style="margin-left: 20px">
<div ng-repeat="plan in availablePlans">
<label>
<input type="radio" ng-model="planChange.requestedPlan" ng-value="plan">
{{ plan.name }} ({{ plan.slug | uppercase }}) - {{ plan.price/100 }}{{ currency }}/month
<span ng-show="currentPlan.name === plan.name" style="font-weight: bold"> (current plan)
</span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<button class="btn btn-primary pull-right" ng-disabled="planChange.requestedPlan.name === currentPlan.name" ng-click="planChange.showChangePlan()">Change Plan</button>
</div>
</div>
</div>
<div class="section-header" ng-show="backupConfig.provider !== 'caas'">
<div class="text-left">
<h3>Cloudron.io Account</h3>
</div>
</div>
<div class="card" style="margin-bottom: 15px;" ng-show="backupConfig.provider !== 'caas'">
<div class="row" ng-show="currentSubscription.plan.id === 'free' || currentSubscription.plan.id === 'undecided'">
<div class="col-xs-12">
With a paid plan, you get continuous updates for the Cloudron and apps. This ensures you are running the latest versions of apps and keeps your server secure. All paid plans come with support via <a href="mailto:support@cloudron.io">email</a> and <a target="_blank" href="https://chat.cloudron.io">live chat</a>.
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Account Email</span>
</div>
<div class="col-xs-6 text-right">
<a ng-href="{{ config.webServerOrigin + '/console.html#/userprofile?email=' + appstoreConfig.profile.email }}" target="_blank">{{ appstoreConfig.profile.email }}</a>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Cloudron ID</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ appstoreConfig.cloudronId }}</span>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Subscription</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ currentSubscription.plan.name }}</span>
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-12">
<a class="btn btn-primary pull-right" ng-href="{{ config.webServerOrigin + '/console.html#/userprofile?view=subscriptions&email=' + appstoreConfig.profile.email }}" target="_blank" ng-show="currentSubscription.plan && currentSubscription.plan.id !== 'free' && currentSubscription.plan.id !== 'undecided'">Configure</a>
<a class="btn btn-success pull-right" ng-href="{{ config.webServerOrigin + '/console.html#/userprofile?view=subscriptions&email=' + appstoreConfig.profile.email + '&cloudronId=' + appstoreConfig.cloudronId }}" target="_blank" ng-show="currentSubscription.plan.id === 'free' || currentSubscription.plan.id === 'undecided'">Setup Subscription</a>
</div>
</div>
</div>
<div class="section-header">
<div class="text-left">
<h3>Backups</h3>
</div>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<div class="col-xs-6">
<span class="text-muted">Provider</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ backupConfig.provider === 'caas' ? 'cloudron.io' : backupConfig.provider }}</span>
</div>
</div>
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<div class="col-xs-6">
<span class="text-muted">Location</span>
</div>
<div class="col-xs-6 text-right">
<span ng-show="backupConfig.provider === 'filesystem'">{{ backupConfig.backupFolder || '/var/backups' }}</span>
<span ng-show="backupConfig.provider === 'minio' || backupConfig.provider === 'exoscale-sos'">{{ backupConfig.bucket + '/' + backupConfig.prefix }}</span>
<span ng-show="backupConfig.provider === 's3'">{{ backupConfig.region + ' ' + backupConfig.bucket + '/' + backupConfig.prefix }}</span>
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Last backup</span>
</div>
<div class="col-xs-6 text-right">
<span ng-show="lastBackup">{{ lastBackup.creationTime | prettyDate }}</span>
<span ng-hide="lastBackup">No backups have been made yet</span>
</div>
</div>
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<br/>
<div class="col-md-12">
<div ng-show="createBackup.busy" class="progress progress-striped active animateMe">
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ createBackup.percent }}%"></div>
</div>
<br/>
</div>
</div>
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<div class="col-md-6">
<p ng-show="createBackup.busy">{{ createBackup.message }}</p>
</div>
<div class="col-md-6 text-right">
<button class="btn btn-outline btn-primary pull-right" ng-click="configureBackup.show()" ng-disabled="createBackup.busy">Configure</button>
<button class="btn btn-outline btn-primary" ng-click="createBackup.doCreateBackup()" ng-disabled="createBackup.busy" style="margin-right: 10px">Backup now</button>
</div>
</div>
</div>
<div class="section-header">
<div class="text-left">
<h3>Updates</h3>
</div>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
<p>Configure the update schedule for the platform and the apps</p>
<p class="text-danger" ng-show="autoUpdate.error"><br/>{{ autoUpdate.error }}</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="radio">
<label>
<input type="radio" name="scheduleRadio" ng-change="autoUpdate.success = false" ng-model="autoUpdate.pattern" value="00 00 1,3,5,23 * * *">
Every night
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="scheduleRadio" ng-change="autoUpdate.success = false" ng-model="autoUpdate.pattern" value="00 00 1,3,5,23 * * 6">
Saturday night
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="scheduleRadio" ng-change="autoUpdate.success = false" ng-model="autoUpdate.pattern" value="never">
Update manually (Not recommended)
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<i class="fa fa-circle-o-notch fa-spin" ng-show="autoUpdate.busy"></i>
<span class="text-success text-bold" ng-show="autoUpdate.success && autoUpdate.pattern === autoUpdate.currentPattern">Saved</span>
</div>
<div class="col-md-6 text-right">
<button class="btn btn-outline btn-primary pull-right" ng-click="autoUpdate.submit()" ng-disabled="autoUpdate.busy || autoUpdate.pattern === autoUpdate.currentPattern"> Save</button>
<button class="btn btn-outline btn-primary" ng-click="autoUpdate.checkNow()" ng-disabled="autoUpdate.busy" style="margin-right: 10px">Check now</button>
</div>
</div>
<div class="content">
<div class="text-left">
<h1>Settings</h1>
</div>
<div class="text-left">
<h3>About</h3>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-xs-4" style="min-width: 150px;">
<div class="settings-avatar" ng-click="avatarChange.showChangeAvatar()" style="background-image: url('{{ client.avatar }}');">
<div class="overlay"></div>
</div>
</div>
<div class="col-xs-8">
<table width="100%">
<tr>
<td class="text-muted" style="vertical-align: top;">Name</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.cloudronName }} <a href="" ng-click="cloudronNameChange.show()"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr ng-show="config.provider === 'caas'">
<td class="text-muted" style="vertical-align: top;">Model</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.size }} - {{ config.region }}</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Version</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.version }}</td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Provider</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.provider }}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="text-left" ng-show="config.provider === 'caas'">
<h3>Plans</h3>
</div>
<div class="card" style="margin-bottom: 15px;" ng-show="config.provider === 'caas'">
<div class="row">
<div class="col-xs-12 text-right">
<a href="{{ config.webServerOrigin }}/console.html#/userprofile?view=credit_card" target="_blank">Change payment method</a>
or
<a href="{{ config.webServerOrigin }}/console.html" target="_blank">Cancel this Cloudron</a>
</div>
</div>
<div class="row">
<div class="col-xs-10 plans" style="margin-left: 20px">
<div ng-repeat="plan in availablePlans">
<label>
<input type="radio" ng-model="planChange.requestedPlan" ng-value="plan">
{{ plan.name }} ({{ plan.slug | uppercase }}) - {{ plan.price/100 }}{{ currency }}/month
<span ng-show="currentPlan.name === plan.name" style="font-weight: bold"> (current plan)
</span>
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<button class="btn btn-primary pull-right" ng-disabled="planChange.requestedPlan.name === currentPlan.name" ng-click="planChange.showChangePlan()">Change Plan</button>
</div>
</div>
</div>
<div class="text-left" ng-show="backupConfig.provider !== 'caas'">
<h3>Cloudron.io Account</h3>
</div>
<div class="card" style="margin-bottom: 15px;" ng-show="backupConfig.provider !== 'caas'">
<div class="row" ng-show="currentSubscription.plan.id === 'free' || currentSubscription.plan.id === 'undecided'">
<div class="col-xs-12">
With a paid plan, you get continuous updates for the Cloudron and apps. This ensures you are running the latest versions of apps and keeps your server secure. All paid plans come with support via <a href="mailto:support@cloudron.io">email</a> and <a target="_blank" href="https://chat.cloudron.io">live chat</a>.
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Account Email</span>
</div>
<div class="col-xs-6 text-right">
<a ng-href="{{ config.webServerOrigin + '/console.html#/userprofile?email=' + appstoreConfig.profile.email }}" target="_blank">{{ appstoreConfig.profile.email }}</a>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Cloudron ID</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ appstoreConfig.cloudronId }}</span>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Subscription</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ currentSubscription.plan.name }}</span>
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-12">
<a class="btn btn-primary pull-right" ng-href="{{ config.webServerOrigin + '/console.html#/userprofile?view=subscriptions&email=' + appstoreConfig.profile.email }}" target="_blank" ng-show="currentSubscription.plan && currentSubscription.plan.id !== 'free' && currentSubscription.plan.id !== 'undecided'">Configure</a>
<a class="btn btn-success pull-right" ng-href="{{ config.webServerOrigin + '/console.html#/userprofile?view=subscriptions&email=' + appstoreConfig.profile.email + '&cloudronId=' + appstoreConfig.cloudronId }}" target="_blank" ng-show="currentSubscription.plan.id === 'free' || currentSubscription.plan.id === 'undecided'">Setup Subscription</a>
</div>
</div>
</div>
<div class="text-left">
<h3>Backups</h3>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<div class="col-xs-6">
<span class="text-muted">Provider</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ backupConfig.provider === 'caas' ? 'cloudron.io' : backupConfig.provider }}</span>
</div>
</div>
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<div class="col-xs-6">
<span class="text-muted">Location</span>
</div>
<div class="col-xs-6 text-right">
<span ng-show="backupConfig.provider === 'filesystem'">{{ backupConfig.backupFolder || '/var/backups' }}</span>
<span ng-show="backupConfig.provider === 'minio' || backupConfig.provider === 'exoscale-sos'">{{ backupConfig.bucket + '/' + backupConfig.prefix }}</span>
<span ng-show="backupConfig.provider === 's3'">{{ backupConfig.region + ' ' + backupConfig.bucket + '/' + backupConfig.prefix }}</span>
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Last backup</span>
</div>
<div class="col-xs-6 text-right">
<span ng-show="lastBackup">{{ lastBackup.creationTime | prettyDate }}</span>
<span ng-hide="lastBackup">No backups have been made yet</span>
</div>
</div>
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<br/>
<div class="col-md-12">
<div ng-show="createBackup.busy" class="progress progress-striped active animateMe">
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ createBackup.percent }}%"></div>
</div>
<br/>
</div>
</div>
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<div class="col-md-6">
<p ng-show="createBackup.busy">{{ createBackup.message }}</p>
</div>
<div class="col-md-6 text-right">
<button class="btn btn-outline btn-primary pull-right" ng-click="configureBackup.show()" ng-disabled="createBackup.busy">Configure</button>
<button class="btn btn-outline btn-primary" ng-click="createBackup.doCreateBackup()" ng-disabled="createBackup.busy" style="margin-right: 10px">Backup now</button>
</div>
</div>
</div>
<div class="text-left">
<h3>Updates</h3>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
<p>Configure the update schedule for the platform and the apps</p>
<p class="text-danger" ng-show="autoUpdate.error"><br/>{{ autoUpdate.error }}</p>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="radio">
<label>
<input type="radio" name="scheduleRadio" ng-change="autoUpdate.success = false" ng-model="autoUpdate.pattern" value="00 00 1,3,5,23 * * *">
Every night
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="scheduleRadio" ng-change="autoUpdate.success = false" ng-model="autoUpdate.pattern" value="00 00 1,3,5,23 * * 6">
Saturday night
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="scheduleRadio" ng-change="autoUpdate.success = false" ng-model="autoUpdate.pattern" value="never">
Update manually (Not recommended)
</label>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<i class="fa fa-circle-o-notch fa-spin" ng-show="autoUpdate.busy"></i>
<span class="text-success text-bold" ng-show="autoUpdate.success && autoUpdate.pattern === autoUpdate.currentPattern">Saved</span>
</div>
<div class="col-md-6 text-right">
<button class="btn btn-outline btn-primary pull-right" ng-click="autoUpdate.submit()" ng-disabled="autoUpdate.busy || autoUpdate.pattern === autoUpdate.currentPattern"> Save</button>
<button class="btn btn-outline btn-primary" ng-click="autoUpdate.checkNow()" ng-disabled="autoUpdate.busy" style="margin-right: 10px">Check now</button>
</div>
</div>
</div>
</div>

View File

@@ -1,84 +1,79 @@
<div class="section-header">
<div class="text-left">
<h1>Support</h1>
</div>
</div>
<div class="content">
<div class="section-header">
<div class="text-left">
<h3>Documentation and Chat</h3>
</div>
</div>
<div class="text-left">
<h1>Support</h1>
</div>
<div class="card">
<div class="grid-item-top">
<div class="row animateMeOpacity">
<div class="col-lg-12">
For user manuals and app development related questions, please refer to our <a href="{{ config.webServerOrigin }}/documentation.html" target="_blank"> documentation</a>.
Cloudron is <a href="https://git.cloudron.io" target="_blank">open source</a> - use the <a href="https://git.cloudron.io/cloudron/box/issues" target="_blank">issue tracker</a>
to report bugs and raise feature requests.
<br/><br/>
For any other questions, chat with us live at <a href="https://chat.cloudron.io/" target="_blank">chat.cloudron.io</a>.
</div>
</div>
</div>
</div>
<div class="text-left">
<h3>Documentation and Chat</h3>
</div>
<div class="section-header">
<div class="text-left">
<h3>Feedback</h3>
</div>
</div>
<div class="card">
<div class="grid-item-top">
<div class="row animateMeOpacity">
<div class="col-lg-12">
For user manuals and app development related questions, please refer to our <a href="{{ config.webServerOrigin }}/documentation.html" target="_blank"> documentation</a>.
Cloudron is <a href="https://git.cloudron.io" target="_blank">open source</a> - use the <a href="https://git.cloudron.io/cloudron/box/issues" target="_blank">issue tracker</a>
to report bugs and raise feature requests.
<br/><br/>
For any other questions, chat with us live at <a href="https://chat.cloudron.io/" target="_blank">chat.cloudron.io</a>.
</div>
</div>
</div>
</div>
<div class="card">
<div class="grid-item-top">
<div class="row animateMeOpacity">
<div class="col-lg-12">
We would love to hear your feedback. Help us improve our product by reporting any bugs or feature requests.
<br/>
<br/>
<form name="feedbackForm" ng-submit="submitFeedback()">
<div class="form-group">
<select class="form-control" name="type" style="width: 50%;" ng-model="feedback.type" required>
<option value="feedback">Enhancement / Idea</option>
<option value="ticket">Bug Report</option>
<option value="app_missing">Missing App</option>
<option value="app_error">App Error/Failing</option>
</select>
</div>
<div class="form-group" ng-class="{ 'has-error': (feedbackForm.subject.$dirty && feedbackForm.subject.$invalid) }">
<input type="text" class="form-control" name="subject" placeholder="Enter your idea or issue" ng-model="feedback.subject" ng-maxlength="512" ng-minlength="1" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (feedbackForm.description.$dirty && feedbackForm.description.$invalid) }">
<textarea class="form-control" name="description" rows="3" placeholder="Describe your idea or issue" ng-model="feedback.description" ng-minlength="1" required></textarea>
</div>
<button type="submit" class="btn btn-primary" ng-disabled="feedbackForm.$invalid || feedback.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="feedback.busy"></i> Submit</button>
<span ng-show="feedback.error" class="text-danger text-bold">{{feedback.error}}</span>
<span ng-show="feedback.success" class="text-success text-bold">Thank You!</span>
</form>
</div>
</div>
</div>
</div>
<div class="text-left">
<h3>Feedback</h3>
</div>
<div class="section-header">
<div class="text-left">
<h3>Remote Support</h3>
</div>
</div>
<div class="card">
<div class="grid-item-top">
<div class="row animateMeOpacity">
<div class="col-lg-12">
We would love to hear your feedback. Help us improve our product by reporting any bugs or feature requests.
<br/>
<br/>
<form name="feedbackForm" ng-submit="submitFeedback()">
<div class="form-group">
<select class="form-control" name="type" style="width: 50%;" ng-model="feedback.type" required>
<option value="feedback">Enhancement / Idea</option>
<option value="ticket">Bug Report</option>
<option value="app_missing">Missing App</option>
<option value="app_error">App Error/Failing</option>
</select>
</div>
<div class="form-group" ng-class="{ 'has-error': (feedbackForm.subject.$dirty && feedbackForm.subject.$invalid) }">
<input type="text" class="form-control" name="subject" placeholder="Enter your idea or issue" ng-model="feedback.subject" ng-maxlength="512" ng-minlength="1" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (feedbackForm.description.$dirty && feedbackForm.description.$invalid) }">
<textarea class="form-control" name="description" rows="3" placeholder="Describe your idea or issue" ng-model="feedback.description" ng-minlength="1" required></textarea>
</div>
<button type="submit" class="btn btn-primary" ng-disabled="feedbackForm.$invalid || feedback.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="feedback.busy"></i> Submit</button>
<span ng-show="feedback.error" class="text-danger text-bold">{{feedback.error}}</span>
<span ng-show="feedback.success" class="text-success text-bold">Thank You!</span>
</form>
</div>
</div>
</div>
</div>
<div class="card" ng-show="config.provider !== 'caas' && user.admin">
<div class="grid-item-top">
<div class="row animateMeOpacity">
<div class="col-lg-12">
Enable this option to allow Cloudron engineers to connect to this server via SSH.
<br/>
<br/>
Do not enable this option before contacting us first at <a href="https://chat.cloudron.io/" target="_blank">chat.cloudron.io</a>.
<br/>
<br/>
<button class="btn" ng-class="{ 'btn-danger': !sshSupportEnabled, 'btn-primary': sshSupportEnabled }" ng-click="toggleSshSupport()">{{ sshSupportEnabled ? 'Disable SSH support access' : 'Enable SSH support access' }}</button>
</div>
</div>
</div>
<div class="text-left">
<h3>Remote Support</h3>
</div>
<div class="card" ng-show="config.provider !== 'caas' && user.admin">
<div class="grid-item-top">
<div class="row animateMeOpacity">
<div class="col-lg-12">
Enable this option to allow Cloudron engineers to connect to this server via SSH.
<br/>
<br/>
Do not enable this option before contacting us first at <a href="https://chat.cloudron.io/" target="_blank">chat.cloudron.io</a>.
<br/>
<br/>
<button class="btn" ng-class="{ 'btn-danger': !sshSupportEnabled, 'btn-primary': sshSupportEnabled }" ng-click="toggleSshSupport()">{{ sshSupportEnabled ? 'Disable SSH support access' : 'Enable SSH support access' }}</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -78,84 +78,80 @@
</div>
</div>
<div class="section-header">
<div class="text-left">
<h3>Access Tokens <button class="btn btn-xs btn-primary btn-outline pull-right" ng-click="tokenAdd.show(apiClient)"><i class="fa fa-plus"></i> New Token</button> </h3>
</div>
</div>
<!-- we will always at least have the webadmin token here, so activeClients always will have one entry with at least one token -->
<div class="card">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<p>These tokens can be used to access the <a ng-href="{{ config.webServerOrigin + '/documentation/developer/api/' }}" target="_blank">Cloudron API</a>.</p>
<br/>
<h4 class="text-muted">Active Tokens</h4>
<hr/>
<p ng-repeat="token in apiClient.activeTokens">
<span ng-click-select>{{ token.accessToken }}</span> <button class="btn btn-xs btn-danger pull-right" ng-click="removeToken(apiClient, token)" title="Revoke Token"><i class="fa fa-trash-o"></i></button>
</p>
</div>
</div>
</div>
</div>
<br/>
<div class="section-header">
<div class="text-left">
<h3>OAuth Apps<button class="btn btn-xs btn-primary btn-outline pull-right" ng-click="clientAdd.show()"><i class="fa fa-plus"></i> New OAuth Client</button></h3>
</div>
</div>
<br/>
<!-- we will always at least have the webadmin token here, so activeClients always will have one entry with at least one token -->
<div class="card" ng-repeat="client in activeClients | activeOAuthClients:user">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<h4 class="text-muted">
{{client.name}} <span ng-show="client.type !== 'external' && client.type !== 'built-in'">on {{client.location}}{{ config.isCustomDomain ? '.' : '-' }}{{config.fqdn}}</span>
</h4>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12">
<b>{{ client.activeTokens.length }}</b> active token(s).
<br/>
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse{{client.id}}">Advanced</a>
<div id="collapse{{client.id}}" class="panel-collapse collapse">
<div class="panel-body">
<h4 class="text-muted">Credentials <button class="btn btn-xs btn-danger pull-right" ng-click="clientRemove.show(client)" title="Remove OAuth Client" ng-show="client.type === 'external'">Remove OAuth Client</button></h4>
<hr/>
<p>Scope: <b ng-click-select>{{ client.scope }}</b></p>
<p>RedirectURI: <b ng-click-select>{{ client.redirectURI }}</b></p>
<p>Client ID: <b ng-click-select>{{ client.id }}</b></p>
<p ng-show="client.clientSecret" style="overflow: auto; white-space: nowrap;">Client Secret: <b ng-click-select>{{ client.clientSecret }}</b></p>
<br/>
<h4 class="text-muted">Tokens
<div class="pull-right">
<button class="btn btn-xs btn-default" ng-click="removeAccessTokens(client)" ng-disabled="!client.activeTokens.length || client.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="client.busy"></i> Revoke All</button>
<button class="btn btn-xs btn-primary btn-outline" ng-click="tokenAdd.show(client)"><i class="fa fa-plus"></i> New Token</button>
</div>
</h4>
<hr/>
<p ng-repeat="token in client.activeTokens">
<b ng-click-select>{{ token.accessToken }}</b> <button class="btn btn-xs btn-danger pull-right" ng-click="removeToken(client, token)" title="Revoke Token"><i class="fa fa-trash-o"></i></button>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="content">
<div class="text-left">
<h3>Access Tokens <button class="btn btn-xs btn-primary btn-outline pull-right" ng-click="tokenAdd.show(apiClient)"><i class="fa fa-plus"></i> New Token</button> </h3>
</div>
<!-- we will always at least have the webadmin token here, so activeClients always will have one entry with at least one token -->
<div class="card">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<p>These tokens can be used to access the <a ng-href="{{ config.webServerOrigin + '/documentation/developer/api/' }}" target="_blank">Cloudron API</a>.</p>
<br/>
<h4 class="text-muted">Active Tokens</h4>
<hr/>
<p ng-repeat="token in apiClient.activeTokens">
<span ng-click-select>{{ token.accessToken }}</span> <button class="btn btn-xs btn-danger pull-right" ng-click="removeToken(apiClient, token)" title="Revoke Token"><i class="fa fa-trash-o"></i></button>
</p>
</div>
</div>
</div>
</div>
<br/>
<div class="text-left">
<h3>OAuth Apps<button class="btn btn-xs btn-primary btn-outline pull-right" ng-click="clientAdd.show()"><i class="fa fa-plus"></i> New OAuth Client</button></h3>
</div>
<!-- we will always at least have the webadmin token here, so activeClients always will have one entry with at least one token -->
<div class="card" ng-repeat="client in activeClients | activeOAuthClients:user">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12">
<h4 class="text-muted">
{{client.name}} <span ng-show="client.type !== 'external' && client.type !== 'built-in'">on {{client.location}}{{ config.isCustomDomain ? '.' : '-' }}{{config.fqdn}}</span>
</h4>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12">
<b>{{ client.activeTokens.length }}</b> active token(s).
<br/>
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse{{client.id}}">Advanced</a>
<div id="collapse{{client.id}}" class="panel-collapse collapse">
<div class="panel-body">
<h4 class="text-muted">Credentials <button class="btn btn-xs btn-danger pull-right" ng-click="clientRemove.show(client)" title="Remove OAuth Client" ng-show="client.type === 'external'">Remove OAuth Client</button></h4>
<hr/>
<p>Scope: <b ng-click-select>{{ client.scope }}</b></p>
<p>RedirectURI: <b ng-click-select>{{ client.redirectURI }}</b></p>
<p>Client ID: <b ng-click-select>{{ client.id }}</b></p>
<p ng-show="client.clientSecret" style="overflow: auto; white-space: nowrap;">Client Secret: <b ng-click-select>{{ client.clientSecret }}</b></p>
<br/>
<h4 class="text-muted">Tokens
<div class="pull-right">
<button class="btn btn-xs btn-default" ng-click="removeAccessTokens(client)" ng-disabled="!client.activeTokens.length || client.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="client.busy"></i> Revoke All</button>
<button class="btn btn-xs btn-primary btn-outline" ng-click="tokenAdd.show(client)"><i class="fa fa-plus"></i> New Token</button>
</div>
</h4>
<hr/>
<p ng-repeat="token in client.activeTokens">
<b ng-click-select>{{ token.accessToken }}</b> <button class="btn btn-xs btn-danger pull-right" ng-click="removeToken(client, token)" title="Revoke Token"><i class="fa fa-trash-o"></i></button>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -231,106 +231,106 @@
</div>
</div>
<div class="content">
<div class="content content-large">
<div class="text-left">
<h1>Users <button class="btn btn-primary btn-outline pull-right" ng-click="useradd.show()"><i class="fa fa-user-plus"></i> New User</button></h1>
</div>
<div class="text-left">
<h1>Users <button class="btn btn-primary btn-outline pull-right" ng-click="useradd.show()"><i class="fa fa-user-plus"></i> New User</button></h1>
</div>
<div class="card card-large">
<div class="grid-item-top">
<div class="row ng-hide" ng-show="!ready">
<div class="col-lg-12 text-center">
<h2><i class="fa fa-circle-o-notch fa-spin"></i></h2>
</div>
</div>
<div class="row animateMeOpacity ng-hide" ng-show="ready">
<div class="col-lg-12">
<span ng-show="mailConfig.enabled">
Each user has a mailbox at <b><i>username</i>@{{ config.fqdn }}</b>.
Please refer to the <a ng-href="{{ config.webServerOrigin + '/documentation/email/#imap-settings-for-cloudron-email' }}" target="_blank">user documentation</a> on how to use Cloudron email accounts.
<br/>
<br/>
</span>
<table class="table table-hover">
<thead>
<tr>
<th style="width: 1px;"></th>
<th style="">User</th>
<th style="" class="text-left hidden-xs hidden-sm">Groups</th>
<th style="width: 100px" class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in users">
<td>
<i class="fa fa-briefcase arrow" ng-show="user.admin" uib-tooltip="This user is an admin and can manage apps, groups and other users"></i>
</td>
<td class="hand elide-table-cell" ng-click="useredit.show(user)" ng-show="user.username">
{{ user.username }} &nbsp; <span class="text-muted" ng-hide="mailConfig.enabled">{{ user.email }}</span>
</td>
<td class="hand elide-table-cell" ng-click="useredit.show(user)" ng-hide="user.username">
<span class="text-muted" uib-tooltip="User is not activated yet">{{ user.alternateEmail || user.email }}</span>
</td>
<td class="text-left hand elide-table-cell hidden-xs hidden-sm" ng-click="useredit.show(user)">
<span class="group-badge" ng-repeat="groupId in user.groupIds | ignoreAdminGroup">
{{ groupsById[groupId].name }}
</span>
</td>
<div class="card card-large">
<div class="grid-item-top">
<div class="row ng-hide" ng-show="!ready">
<div class="col-lg-12 text-center">
<h2><i class="fa fa-circle-o-notch fa-spin"></i></h2>
</div>
</div>
<div class="row animateMeOpacity ng-hide" ng-show="ready">
<div class="col-lg-12">
<span ng-show="mailConfig.enabled">
Each user has a mailbox at <b><i>username</i>@{{ config.fqdn }}</b>.
Please refer to the <a ng-href="{{ config.webServerOrigin + '/documentation/email/#imap-settings-for-cloudron-email' }}" target="_blank">user documentation</a> on how to use Cloudron email accounts.
<br/>
<br/>
</span>
<table class="table table-hover">
<thead>
<tr>
<th style="width: 1px;"></th>
<th style="">User</th>
<th style="" class="text-left hidden-xs hidden-sm">Groups</th>
<th style="width: 100px" class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in users">
<td>
<i class="fa fa-briefcase arrow" ng-show="user.admin" uib-tooltip="This user is an admin and can manage apps, groups and other users"></i>
</td>
<td class="hand elide-table-cell" ng-click="useredit.show(user)" ng-show="user.username">
{{ user.username }} &nbsp; <span class="text-muted" ng-hide="mailConfig.enabled">{{ user.email }}</span>
</td>
<td class="hand elide-table-cell" ng-click="useredit.show(user)" ng-hide="user.username">
<span class="text-muted" uib-tooltip="User is not activated yet">{{ user.alternateEmail || user.email }}</span>
</td>
<td class="text-left hand elide-table-cell hidden-xs hidden-sm" ng-click="useredit.show(user)">
<span class="group-badge" ng-repeat="groupId in user.groupIds | ignoreAdminGroup">
{{ groupsById[groupId].name }}
</span>
</td>
</td>
<td class="text-right no-wrap" style="vertical-align: bottom">
<button ng-show="!isMe(user)" class="btn btn-xs btn-default" ng-click="sendInvite(user)" title="Send invitation email"><i class="fa fa-paper-plane-o"></i></button>
<button class="btn btn-xs btn-default" ng-click="useredit.show(user)" title="Edit User Profile"><i class="fa fa-pencil"></i></button>
<button ng-show="!isMe(user)" class="btn btn-xs btn-danger" ng-click="userremove.show(user)" title="Remove User"><i class="fa fa-trash-o"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</td>
<td class="text-right no-wrap" style="vertical-align: bottom">
<button ng-show="!isMe(user)" class="btn btn-xs btn-default" ng-click="sendInvite(user)" title="Send invitation email"><i class="fa fa-paper-plane-o"></i></button>
<button class="btn btn-xs btn-default" ng-click="useredit.show(user)" title="Edit User Profile"><i class="fa fa-pencil"></i></button>
<button ng-show="!isMe(user)" class="btn btn-xs btn-danger" ng-click="userremove.show(user)" title="Remove User"><i class="fa fa-trash-o"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<br/>
<br/>
<div class="text-left">
<h1>Groups <button class="btn btn-primary btn-outline pull-right" ng-click="groupAdd.show()"><i class="fa fa-plus"></i> New Group</button></h1>
</div>
<div class="text-left">
<h1>Groups <button class="btn btn-primary btn-outline pull-right" ng-click="groupAdd.show()"><i class="fa fa-plus"></i> New Group</button></h1>
</div>
<div class="card card-large">
<div class="grid-item-top">
<div class="row ng-hide" ng-show="!ready">
<div class="col-lg-12 text-center">
<h2><i class="fa fa-circle-o-notch fa-spin"></i></h2>
</div>
</div>
<div class="row animateMeOpacity ng-hide" ng-show="ready">
<div class="col-lg-12">
Groups can be used to control access to an app.
<span ng-show="mailConfig.enabled">Each group serves as an email list at <b><i>groupname</i>@{{ config.fqdn }}</b>. Any email sent to this address will be forwarded to each group member.</span>
<br/>
<br/>
<table class="table table-hover">
<thead>
<tr>
<th style="">Name</th>
<th style="width: 300px" class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="group in groups | ignoreAdminGroup">
<td class="text-overflow: ellipsis; white-space: nowrap;">
{{ group.name }}
</td>
<td class="text-right" style="vertical-align: bottom">
<button class="btn btn-xs btn-danger" ng-click="groupRemove.show(group)" title="Remove Group"><i class="fa fa-trash-o"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card card-large">
<div class="grid-item-top">
<div class="row ng-hide" ng-show="!ready">
<div class="col-lg-12 text-center">
<h2><i class="fa fa-circle-o-notch fa-spin"></i></h2>
</div>
</div>
<div class="row animateMeOpacity ng-hide" ng-show="ready">
<div class="col-lg-12">
Groups can be used to control access to an app.
<span ng-show="mailConfig.enabled">Each group serves as an email list at <b><i>groupname</i>@{{ config.fqdn }}</b>. Any email sent to this address will be forwarded to each group member.</span>
<br/>
<br/>
<table class="table table-hover">
<thead>
<tr>
<th style="">Name</th>
<th style="width: 300px" class="text-right">Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="group in groups | ignoreAdminGroup">
<td class="text-overflow: ellipsis; white-space: nowrap;">
{{ group.name }}
</td>
<td class="text-right" style="vertical-align: bottom">
<button class="btn btn-xs btn-danger" ng-click="groupRemove.show(group)" title="Remove Group"><i class="fa fa-trash-o"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>