1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 05:09:42 +02:00

Provide in-app What's New & new release notifications

This commit is contained in:
sauls8t 2018-03-22 17:29:59 +00:00
parent 97cc5374f0
commit eaf46f06c1
25 changed files with 1166 additions and 772 deletions

View file

@ -24,4 +24,7 @@ INSERT INTO search (orgid, documentid, itemid, itemtype, content)
SELECT orgid, documentid, refid as itemid, "page" as itemtype, title as content
FROM page WHERE status=0
-- whats new support
ALTER TABLE user ADD COLUMN `lastversion` CHAR(16) NOT NULL DEFAULT '' AFTER `active`;
-- deprecations

View file

@ -39,6 +39,7 @@ type RequestContext struct {
Expires time.Time
Fullname string
Transaction *sqlx.Tx
AppVersion string
}
//GetAppURL returns full HTTP url for the app

View file

@ -100,6 +100,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
requestedPassword := secrets.GenerateRandomPassword()
userModel.Salt = secrets.GenerateSalt()
userModel.Password = secrets.GeneratePassword(requestedPassword, userModel.Salt)
userModel.LastVersion = ctx.AppVersion
// only create account if not dupe
addUser := true

View file

@ -35,8 +35,8 @@ func (s Scope) Add(ctx domain.RequestContext, u user.User) (err error) {
u.Created = time.Now().UTC()
u.Revised = time.Now().UTC()
_, err = ctx.Transaction.Exec("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
u.RefID, u.Firstname, u.Lastname, strings.ToLower(u.Email), u.Initials, u.Password, u.Salt, "", u.Created, u.Revised)
_, err = ctx.Transaction.Exec("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, lastversion, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
u.RefID, u.Firstname, u.Lastname, strings.ToLower(u.Email), u.Initials, u.Password, u.Salt, "", u.LastVersion, u.Created, u.Revised)
if err != nil {
err = errors.Wrap(err, "execute user insert")
@ -47,7 +47,7 @@ func (s Scope) Add(ctx domain.RequestContext, u user.User) (err error) {
// Get returns the user record for the given id.
func (s Scope) Get(ctx domain.RequestContext, id string) (u user.User, err error) {
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE refid=?", id)
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, lastversion, created, revised FROM user WHERE refid=?", id)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("unable to execute select for user %s", id))
@ -60,7 +60,7 @@ func (s Scope) Get(ctx domain.RequestContext, id string) (u user.User, err error
func (s Scope) GetByDomain(ctx domain.RequestContext, domain, email string) (u user.User, err error) {
email = strings.TrimSpace(strings.ToLower(email))
err = s.Runtime.Db.Get(&u, "SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.global, u.password, u.salt, u.reset, u.created, u.revised FROM user u, account a, organization o WHERE TRIM(LOWER(u.email))=? AND u.refid=a.userid AND a.orgid=o.refid AND TRIM(LOWER(o.domain))=?",
err = s.Runtime.Db.Get(&u, "SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.global, u.password, u.salt, u.reset, u.lastversion, u.created, u.revised FROM user u, account a, organization o WHERE TRIM(LOWER(u.email))=? AND u.refid=a.userid AND a.orgid=o.refid AND TRIM(LOWER(o.domain))=?",
email, domain)
if err != nil && err != sql.ErrNoRows {
@ -74,7 +74,7 @@ func (s Scope) GetByDomain(ctx domain.RequestContext, domain, email string) (u u
func (s Scope) GetByEmail(ctx domain.RequestContext, email string) (u user.User, err error) {
email = strings.TrimSpace(strings.ToLower(email))
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE TRIM(LOWER(email))=?", email)
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, lastversion, created, revised FROM user WHERE TRIM(LOWER(email))=?", email)
if err != nil && err != sql.ErrNoRows {
err = errors.Wrap(err, fmt.Sprintf("execute select user by email %s", email))
@ -85,7 +85,7 @@ func (s Scope) GetByEmail(ctx domain.RequestContext, email string) (u user.User,
// GetByToken returns a user record given a reset token value.
func (s Scope) GetByToken(ctx domain.RequestContext, token string) (u user.User, err error) {
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE reset=?", token)
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, lastversion, created, revised FROM user WHERE reset=?", token)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute user select by token %s", token))
@ -98,7 +98,7 @@ func (s Scope) GetByToken(ctx domain.RequestContext, token string) (u user.User,
// This occurs when we you share a folder with a new user and they have to complete
// the onboarding process.
func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.User, err error) {
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, created, revised FROM user WHERE salt=?", serial)
err = s.Runtime.Db.Get(&u, "SELECT id, refid, firstname, lastname, email, initials, global, password, salt, reset, lastversion, created, revised FROM user WHERE salt=?", serial)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute user select by serial %s", serial))
@ -111,7 +111,7 @@ func (s Scope) GetBySerial(ctx domain.RequestContext, serial string) (u user.Use
// identified in the Persister.
func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) {
err = s.Runtime.Db.Select(&u,
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised,
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.lastversion, u.created, u.revised,
u.global, a.active, a.editor, a.admin, a.users as viewusers
FROM user u, account a
WHERE u.refid=a.userid AND a.orgid=? AND a.active=1
@ -139,7 +139,7 @@ func (s Scope) GetUsersForOrganization(ctx domain.RequestContext, filter string)
}
err = s.Runtime.Db.Select(&u,
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised,
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.lastversion, u.created, u.revised,
u.global, a.active, a.editor, a.admin, a.users as viewusers
FROM user u, account a
WHERE u.refid=a.userid AND a.orgid=? `+likeQuery+
@ -160,7 +160,7 @@ func (s Scope) GetUsersForOrganization(ctx domain.RequestContext, filter string)
// GetSpaceUsers returns a slice containing all user records for given space.
func (s Scope) GetSpaceUsers(ctx domain.RequestContext, spaceID string) (u []user.User, err error) {
err = s.Runtime.Db.Select(&u, `
SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised, u.global,
SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.lastversion, u.revised, u.global,
a.active, a.users AS viewusers, a.editor, a.admin
FROM user u, account a
WHERE a.orgid=? AND u.refid = a.userid AND a.active=1 AND u.refid IN (
@ -189,7 +189,7 @@ func (s Scope) GetUsersForSpaces(ctx domain.RequestContext, spaces []string) (u
}
query, args, err := sqlx.In(`
SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised, u.global,
SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.lastversion, u.created, u.revised, u.global,
a.active, a.users AS viewusers, a.editor, a.admin
FROM user u, account a
WHERE a.orgid=? AND u.refid = a.userid AND a.active=1 AND u.refid IN (
@ -219,7 +219,7 @@ func (s Scope) UpdateUser(ctx domain.RequestContext, u user.User) (err error) {
u.Email = strings.ToLower(u.Email)
_, err = ctx.Transaction.NamedExec(
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials WHERE refid=:refid", &u)
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, initials=:initials, lastversion=:lastversion WHERE refid=:refid", &u)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute user update %s", u.RefID))
@ -289,7 +289,7 @@ func (s Scope) MatchUsers(ctx domain.RequestContext, text string, maxMatches int
}
err = s.Runtime.Db.Select(&u,
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised,
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.lastversion, u.created, u.revised,
u.global, a.active, a.editor, a.admin, a.users as viewusers
FROM user u, account a
WHERE a.orgid=? AND u.refid=a.userid AND a.active=1 `+likeQuery+

File diff suppressed because one or more lines are too long

View file

@ -9,11 +9,30 @@
//
// https://documize.com
import $ from 'jquery';
import { empty } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
export default Component.extend({
appMeta: service(),
LicenseError: empty('model.license'),
changelog: '',
init() {
this._super(...arguments);
let self = this;
let cacheBuster = + new Date();
$.ajax({
url: `https://storage.googleapis.com/documize/downloads/updates/summary.html?cb=${cacheBuster}`,
type: 'GET',
dataType: 'html',
success: function (response) {
self.set('changelog', response);
}
});
},
actions: {
saveLicense() {

View file

@ -9,12 +9,14 @@
//
// https://documize.com
import Component from '@ember/component';
import $ from 'jquery';
import { notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { inject as service } from '@ember/service'
import ModalMixin from '../../mixins/modal';
import constants from '../../utils/constants';
import Component from '@ember/component';
export default Component.extend({
export default Component.extend(ModalMixin, {
folderService: service('folder'),
appMeta: service(),
session: service(),
@ -24,6 +26,8 @@ export default Component.extend({
hasPins: notEmpty('pins'),
hasSpacePins: notEmpty('spacePins'),
hasDocumentPins: notEmpty('documentPins'),
hasWhatsNew: false,
newsContent: '',
init() {
this._super(...arguments);
@ -34,6 +38,23 @@ export default Component.extend({
config = JSON.parse(config);
this.set('enableLogout', !config.disableLogout);
}
this.get('session').hasWhatsNew().then((v) => {
this.set('hasWhatsNew', v);
});
let version = this.get('appMeta.version');
let self = this;
let cacheBuster = + new Date();
$.ajax({
url: `https://storage.googleapis.com/documize/downloads/updates/${version}.html?cb=${cacheBuster}`,
type: 'GET',
dataType: 'html',
success: function (response) {
self.set('newsContent', response);
}
});
},
didInsertElement() {
@ -80,6 +101,12 @@ export default Component.extend({
let folder = this.get('store').peekRecord('folder', folderId);
this.get('router').transitionTo('document', folderId, folder.get('slug'), documentId, 'document');
}
},
onShowWhatsNewModal() {
this.modalOpen("#whats-new-modal", { "show": true });
this.get('session').seenNewVersion();
this.set('hasWhatsNew', false);
}
}
});

View file

@ -25,6 +25,7 @@ export default Model.extend({
global: attr('boolean', { defaultValue: false }),
accounts: attr(),
groups: attr(),
lastVersion: attr('string'),
created: attr(),
revised: attr(),

View file

@ -10,7 +10,6 @@
// https://documize.com
import { Promise as EmberPromise } from 'rsvp';
import { inject as service } from '@ember/service';
import Controller from '@ember/controller';
import NotifierMixin from "../../../mixins/notifier";

View file

@ -14,24 +14,20 @@
<div class="form-group mt-4">
{{focus-input type="text" value=filter class="form-control mb-4" placeholder='a OR b, x AND y, "phrase mat*"'}}
<div class="form-check form-check-inline">
<label class="form-check-label">
{{input type="checkbox" id=checkId class="form-check-input" checked=matchDoc}} document title
</label>
{{input type="checkbox" id="search-1" class="form-check-input" checked=matchDoc}}
<label class="form-check-label" for="search-1">&nbsp;document title</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
{{input type="checkbox" id=checkId class="form-check-input" checked=matchContent}} content
</label>
{{input type="checkbox" id="search-2" class="form-check-input" checked=matchContent}}
<label class="form-check-label" for="search-2">&nbsp;content</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
{{input type="checkbox" id=checkId class="form-check-input" checked=matchTag}} tag name
</label>
{{input type="checkbox" id="search-3" class="form-check-input" checked=matchTag}}
<label class="form-check-label" for="search-3">&nbsp;tag name</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
{{input type="checkbox" id=checkId class="form-check-input" checked=matchFile}} attachment name
</label>
{{input type="checkbox" id="search-4" class="form-check-input" checked=matchFile}}
<label class="form-check-label" for="search-4">&nbsp;attachment name</label>
</div>
</div>

View file

@ -9,6 +9,7 @@
//
// https://documize.com
import $ from 'jquery';
import { htmlSafe } from '@ember/string';
import { resolve } from 'rsvp';
import Service, { inject as service } from '@ember/service';
@ -63,6 +64,7 @@ export default Service.extend({
return this.get('ajax').request('public/meta').then((response) => {
this.setProperties(response);
this.set('version', 'v' + this.get('version'));
if (requestedRoute === 'secure') {
this.setProperties({
@ -76,6 +78,26 @@ export default Service.extend({
this.get('localStorage').storeSessionItem('entryUrl', requestedUrl);
}
let self = this;
let cacheBuster = + new Date();
$.getJSON(`https://storage.googleapis.com/documize/downloads/updates/meta.json?cb=${cacheBuster}`, function (versions) {
let cv = 'v' + versions.community.version;
let ev = 'v' + versions.enterprise.version;
let re = self.get('edition');
let rv = self.get('version');
self.set('communityLatest', cv);
self.set('enterpriseLatest', ev);
self.set('updateAvailable', false); // set to true for testing
if (re === 'Community' && rv < cv) {
self.set('updateAvailable', true);
}
if (re === 'Enterprise' && rv < ev) {
self.set('updateAvailable', true);
}
});
return response;
});
}

View file

@ -11,11 +11,13 @@
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { Promise as EmberPromise } from 'rsvp';
import SimpleAuthSession from 'ember-simple-auth/services/session';
export default SimpleAuthSession.extend({
ajax: service(),
appMeta: service(),
userSvc: service('user'),
store: service(),
localStorage: service(),
folderPermissions: null,
@ -35,7 +37,9 @@ export default SimpleAuthSession.extend({
if (this.get('isAuthenticated')) {
let user = this.get('session.content.authenticated.user') || { id: '0' };
let data = this.get('store').normalize('user', user);
return this.get('store').push(data);
let um = this.get('store').push(data)
return um;
}
}),
@ -71,5 +75,21 @@ export default SimpleAuthSession.extend({
logout() {
this.get('localStorage').clearAll();
},
seenNewVersion() {
this.get('userSvc').getUser(this.get('user.id')).then((user) => {
user.set('lastVersion', this.get('appMeta.version'));
this.get('userSvc').save(user);
});
},
// set what's new indicator
hasWhatsNew() {
return new EmberPromise((resolve) => {
return this.get('userSvc').getUser(this.get('user.id')).then((user) => {
resolve(user.get('lastVersion') !== this.get('appMeta.version'));
});
});
}
});

View file

@ -31,6 +31,7 @@
@import "section/plantuml.scss";
@import "section/papertrail.scss";
@import "section/wysiwyg.scss";
@import "news.scss";
@import "enterprise/all.scss";
// Bootstrap override that removes gutter space on smaller screens

View file

@ -101,8 +101,7 @@ $link-hover-decoration: none;
@import "node_modules/bootstrap/scss/tooltip";
@import "node_modules/bootstrap/scss/tables";
@import "node_modules/bootstrap/scss/badge";
// @import "node_modules/bootstrap/scss/navbar";
// @import "node_modules/bootstrap/scss/images";
.modal-80 {
max-width: 80% !important;
@ -111,3 +110,18 @@ $link-hover-decoration: none;
body.modal-open {
padding-right: 0 !important;
}
.modal-header-white {
background-color: $color-white !important;
border: none !important;
> .close {
background-color: $color-white !important;
border: none !important;
> span {
color: $color-gray;
font-size: 2rem;
}
}
}

View file

@ -33,6 +33,7 @@ $color-red: #9E0D1F;
$color-green: #348A37;
$color-blue: #2667af;
$color-goldy: #FFD700;
$color-orange: #FFAD15;
// widgets
$color-checkbox: #2667af;
@ -40,6 +41,7 @@ $color-symbol-box: #dce5e8;
$color-symbol-icon: #a4b8be;
$color-card: #F9F9F9;
$color-stroke: #e1e1e1;
$color-whats-new: #fc1530;
// Color utility classes for direct usage in HTML
.color-white {
@ -75,6 +77,12 @@ $color-stroke: #e1e1e1;
.color-gold {
color: $color-goldy !important;
}
.color-orange {
color: $color-orange !important;
}
.color-whats-new {
color: $color-whats-new !important;
}
.background-color-white {
background-color: $color-white !important;

175
gui/app/styles/news.scss Normal file
View file

@ -0,0 +1,175 @@
.product-update {
text-align: left;
margin: 50px 0;
> .update-summary {
padding: 25px;
border: 1px solid $color-orange;
background-color: $color-off-white;
@include border-radius(2px);
> .caption {
font-weight: bold;
font-size: 1.5rem;
color: $color-orange;
margin-bottom: 15px;
display: inline-block;
}
> .instructions {
font-weight: normal;
font-size: 1.3rem;
color: $color-gray;
}
> .version {
margin: 30px 0 0 20px;
font-size: 1.3rem;
color: $color-gray;
font-weight: bold;
}
> .changes {
margin: 10px 0 0 40px;
> li {
list-style-type: disc;
padding: 5px 0;
font-size: 1.2rem;
color: $color-black;
> .tag-edition {
margin: 10px 10px 10px 10px;
padding: 5px 10px;
background-color: $color-gray-light;
color: $color-primary;
font-weight: bold;
font-size: 0.9rem;
}
}
}
}
}
.product-about {
text-align: center;
margin: 30px 30px;
> .edition {
font-weight: normal;
font-size: 1.5rem;
color: $color-black;
margin-bottom: 5px;
}
> .version {
font-weight: bold;
font-size: 1.1rem;
color: $color-gray;
margin-bottom: 20px;
}
> .dotcom {
font-weight: bold;
font-size: 1.2rem;
color: $color-link;
margin-bottom: 40px;
}
> .copyright {
text-align: center;
font-weight: normal;
font-size: 1rem;
color: $color-off-black;
margin-bottom: 20px;
}
> .license {
text-align: left;
font-weight: normal;
font-size: 1rem;
color: $color-gray;
}
}
.update-available-dot {
border-radius: 10px;
width: 10px;
height: 10px;
background-color: $color-orange;
position: absolute;
bottom: 0;
right: 0;
}
.whats-new-dot {
border-radius: 10px;
width: 10px;
height: 10px;
background-color: $color-whats-new;
position: absolute;
top: 0;
right: 0;
}
.product-news {
text-align: left;
margin: 0 30px;
> h2 {
margin: 0 0 10px 0;
text-align: center;
font-size: 2rem;
color: $color-off-black;
}
> .news-item {
padding: 30px 0;
border-bottom: 1px solid $color-border;
text-align: center;
> .title {
color: $color-primary;
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 5px;
}
> .date {
color: $color-gray;
font-size: 1rem;
font-weight: 600;
margin-bottom: 10px;
}
> .info {
color: $color-black;
font-size: 1.1rem;
font-weight: normal;
margin-top: 15px;
}
> .tag-edition {
margin: 10px 10px 10px 10px;
padding: 5px 10px;
background-color: $color-off-white;
color: $color-gray;
font-weight: 700;
font-size: 0.9rem;
display: inline-block;
}
> img {
max-width: 450px;
max-height: 350px;
}
}
> .action {
margin: 20px 0;
text-align: center;
color: $color-gray;
font-weight: 800;
font-size: 1.3rem;
}
}

View file

@ -201,7 +201,7 @@
display: inline-block;
cursor: default;
position: relative;
overflow: hidden;
// overflow: hidden; // kills update dots
width: 35px;
height: 35px;
line-height: 34px;

View file

@ -8,9 +8,7 @@
</div>
<div class="view-customize">
<form class="mt-5">
<div class="form-group row">
<label class="col-sm-2 col-form-label">Provider</label>
<div class="col-sm-10">
@ -62,7 +60,8 @@
<label for="keycloak-admin-user" class="col-sm-2 col-form-label">Keycloak Username</label>
<div class="col-sm-10">
{{input id="keycloak-admin-user" type="text" value=keycloakConfig.adminUser class=(if KeycloakAdminUserError 'form-control is-invalid' 'form-control')}}
<small class="form-text text-muted">Used to connect with Keycloak and sync users with Documize (create user under Master Realm and assign 'view-users' role against Realm specified above)</small>
<small class="form-text text-muted">Used to connect with Keycloak and sync users with Documize (create user under Master Realm and assign 'view-users' role
against Realm specified above)</small>
</div>
</div>
<div class="form-group row">
@ -75,24 +74,26 @@
<div class="form-group row">
<label for="keycloak-admin-password" class="col-sm-2 col-form-label">Logout</label>
<div class="col-sm-10">
<label class="form-check-label">
{{input type="checkbox" class="form-check-input" checked=keycloakConfig.disableLogout}}
<div class="form-check">
{{input type="checkbox" class="form-check-input" id="keycloak-logout" checked=keycloakConfig.disableLogout}}
<label class="form-check-label" for="keycloak-logout">
Hide the logout button for Keycloak users
</label>
</div>
</div>
</div>
<div class="form-group row">
<label for="keycloak-admin-password" class="col-sm-2 col-form-label">Space Permission</label>
<div class="col-sm-10">
<label class="form-check-label">
{{input type="checkbox" class="form-check-input" checked=keycloakConfig.defaultPermissionAddSpace}}
<div class="form-check">
{{input type="checkbox" class="form-check-input" id="keycloak-perm" checked=keycloakConfig.defaultPermissionAddSpace}}
<label class="form-check-label" for="keycloak-perm">
Can add spaces
</label>
</div>
</div>
</div>
{{/if}}
<div class="btn btn-success mt-4" {{action 'onSave'}}>Save</div>
</form>
</div>

View file

@ -26,18 +26,22 @@
<div class="form-group row">
<label class="col-sm-2 col-form-label">Anonymous Access</label>
<div class="col-sm-10">
<label class="form-check-label">
<input type="checkbox" class="form-check-input" id="allowAnonymousAccess" checked={{model.general.allowAnonymousAccess}} />
<div class="form-check">
<input type="checkbox" class="form-check-input" id="allowAnonymousAccess" checked= {{model.general.allowAnonymousAccess}}
/>
<label class="form-check-label" for="allowAnonymousAccess">
Make content marked as "Everyone" available to anonymous users
</label>
</div>
</div>
</div>
<div class="form-group row">
<label for="conversionEndpoint" class="col-sm-2 col-form-label">Conversion Service URL</label>
<div class="col-sm-10">
{{input id="conversionEndpoint" type="text" value=model.general.conversionEndpoint class=(if hasConversionEndpointInputError 'form-control is-invalid' 'form-control')}}
<small class="form-text text-muted">
Endpoint for handling import/export (e.g. https://api.documize.com, <a href="https://docs.documize.com/s/WNEpptWJ9AABRnha/administration-guides/d/WO0pt_MXigAB6sJ7/general-options">view documentation</a>)
Endpoint for handling import/export (e.g. https://api.documize.com,
<a href="https://docs.documize.com/s/WNEpptWJ9AABRnha/administration-guides/d/WO0pt_MXigAB6sJ7/general-options">view documentation</a>)
</small>
</div>
</div>

View file

@ -1,8 +1,10 @@
<div class="row">
<div class="col">
<div class="view-customize">
<h1 class="admin-heading">Product License</h1>
<h2 class="sub-heading">Optional Enterprise Edition license</h2>
<h1 class="admin-heading">{{appMeta.edition}} Edition {{appMeta.version}}</h1>
<h2 class="sub-heading">Enterprise Edition unlocks
<a class="" href="https://documize.com/pricing">premium capabilities and product support</a>
</h2>
</div>
</div>
</div>
@ -10,12 +12,44 @@
<div class="view-customize">
<form class="mt-5 ">
<div class="form-group row">
<label for="smtp-host" class="col-sm-2 col-form-label">License Key</label>
<label for="smtp-host " class="col-sm-2 col-form-label ">Enterprise Edition License Key</label>
<div class="col-sm-10 ">
{{textarea value=model.license rows="15" class=(if LicenseError 'form-control is-invalid' 'form-control')}}
{{textarea value=model.license rows="10" class=(if LicenseError 'form-control is-invalid' 'form-control')}}
<small class="form-text text-muted ">XML format</small>
{{#if appMeta.valid}}
<p class="mt-2 color-green">Valid</p>
{{else}}
<p class="mt-2 color-red">Invalid</p>
{{/if}}
<div class="btn btn-success mt-3" {{action 'saveLicense'}}>Save</div>
</div>
</div>
<div class="btn btn-success mt-4" {{action 'saveLicense'}}>Save</div>
</form>
</div>
<div class="product-update">
<div class="update-summary">
{{#if appMeta.updateAvailable}}
<a href="https://documize.com/downloads" class="caption">New version available</a>
<p class="instructions">
To upgrade, replace existing binary and restart Documize. Migrate between Community and Enterprise editions seamlessly.
</p>
{{else}}
<div class="caption">Release Summary</div>
{{/if}}
<p>
<span class="color-off-black">Community Edition {{appMeta.communityLatest}}</span>&nbsp;&nbsp;&nbsp;
<a href="https://storage.googleapis.com/documize/downloads/documize-community-windows-amd64.exe" class="font-weight-bold">Windows</a>&nbsp;&middot;
<a href="https://storage.googleapis.com/documize/downloads/documize-community-linux-amd64" class="font-weight-bold">Linux</a>&nbsp;&middot;
<a href="https://storage.googleapis.com/documize/downloads/documize-community-darwin-amd64" class="font-weight-bold">macOS</a>&nbsp;
</p>
<p>
<span class="color-off-black">Enterprise Edition {{appMeta.enterpriseLatest}}</span>&nbsp;&nbsp;&nbsp;
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-windows-amd64.exe" class="font-weight-bold color-blue">Windows</a>&nbsp;&middot;
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-linux-amd64" class="font-weight-bold color-blue">Linux</a>&nbsp;&middot;
<a href="https://storage.googleapis.com/documize/downloads/documize-enterprise-darwin-amd64" class="font-weight-bold color-blue">macOS</a>&nbsp;
</p>
<div class="my-5" />
{{{changelog}}}
</div>
</div>

View file

@ -42,12 +42,28 @@
<div class="btn-group">
<div class="button-gravatar-white align-text-bottom" id="profile-button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{session.user.initials}}
{{#if hasWhatsNew}}
<div class="whats-new-dot" />
{{/if}}
{{#if session.isAdmin}}
{{#if appMeta.updateAvailable}}
<div class="update-available-dot" />
{{/if}}
{{/if}}
</div>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="profile-button">
{{#link-to 'profile' class="dropdown-item" }}Profile{{/link-to}}
{{#if session.isAdmin}}
{{#link-to 'customize.general' class="dropdown-item" }}Settings{{/link-to}}
{{/if}}
<div class="dropdown-divider"></div>
{{#if session.isAdmin}}
{{#if appMeta.updateAvailable}}
{{#link-to 'customize.license' class="dropdown-item font-weight-bold color-orange" }}Update available{{/link-to}}
{{/if}}
{{/if}}
<a href="#" class="dropdown-item {{if hasWhatsNew 'color-whats-new font-weight-bold'}}" {{action 'onShowWhatsNewModal'}}>What's New</a>
<a href="#" class="dropdown-item" data-toggle="modal" data-target="#about-documize-modal" data-backdrop="static">About Documize</a>
{{#if enableLogout}}
<div class="dropdown-divider"></div>
{{#link-to 'auth.logout' class="dropdown-item" }}Logout{{/link-to}}
@ -66,3 +82,67 @@
</div>
</div>
</div>
{{#if session.authenticated}}
<div id="whats-new-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-header modal-header-white">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true" data-dismiss="modal" aria-label="Close">&times;</span>
</button>
</div>
<div class="modal-content">
<div class="modal-body">
<div class="product-news">
<h2>What's New</h2>
{{{newsContent}}}
<div class="action">
Have an idea? Suggestion or feedback? <a href="mailto:support@documize.com">Get in touch!</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="about-documize-modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-body">
<div class="product-about">
<div class="edition">
Documize {{appMeta.edition}} Edition
</div>
<div class="version">
{{appMeta.version}}
</div>
<div class="dotcom">
<a href="https://documize.com">https://documize.com</a>
</div>
{{#if (eq appMeta.edition 'Community')}}
<div class="copyright">
&copy; Documize Inc. All rights reserved.
</div>
<div class="license">
<br/>
<br/> This software (Documize Community Edition) is licensed under
<a href="http://www.gnu.org/licenses/agpl-3.0.en.html">GNU AGPL v3</a>
You can operate outside the AGPL restrictions by purchasing Documize Enterprise Edition and obtaining a commercial licenseby
contacting
<a href="mailto:sales@documize.com">sales@documize.com</a>
</div>
{{/if}}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{{/if}}

View file

@ -1,14 +0,0 @@
{
"community": {
"version": "1.59.0",
"major": 1,
"minor": 59,
"patch": 0
},
"enterprise": {
"version": "1.61.0",
"major": 1,
"minor": 61,
"patch": 0
}
}

View file

@ -34,6 +34,7 @@ type User struct {
Password string `json:"-"`
Salt string `json:"-"`
Reset string `json:"-"`
LastVersion string `json:"lastVersion"`
Accounts []account.Account `json:"accounts"`
Groups []group.Record `json:"groups"`
}

View file

@ -144,6 +144,7 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
rc.AppURL = r.Host
rc.Subdomain = organization.GetSubdomainFromHost(r)
rc.SSL = r.TLS != nil
rc.AppVersion = fmt.Sprintf("v%s", m.Runtime.Product.Version)
// get user IP from request
i := strings.LastIndex(r.RemoteAddr, ":")