1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-24 15:49:44 +02:00

Merge branch 'master' into github-tidy-up

# Conflicts:
#	embed/bindata_assetfs.go
This commit is contained in:
Elliott Stoneham 2016-08-15 16:32:06 +01:00
commit 6f0d503cb5
34 changed files with 792 additions and 449 deletions

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# These files are text and should be normalized (convert crlf > lf)
*.hbs text

5
.gitconfig Normal file
View file

@ -0,0 +1,5 @@
[core]
whitespace = trailing-space,space-before-tab
autocrlf = input
[apply]
whitespace = fix

2
.gitignore vendored
View file

@ -61,3 +61,5 @@ debug
Dockerfile
container.sh
make.sh
embed/bindata_assetfs.go

View file

@ -30,6 +30,12 @@ v0.15.0
![Alt text](screenshot.png "Documize")
## Auth0 Integration
Documize is compatible with Auth0 identity as a service.
<a width="150" height="50" href="https://auth0.com/?utm_source=oss&utm_medium=gp&utm_campaign=oss" target="_blank" alt="Single Sign On & Token Based Authentication - Auth0"><img width="150" height="50" alt="JWT Auth for open source projects" src="https://cdn.auth0.com/oss/badges/a0-badge-dark.png"/></a>
## Legal
https://documize.com

View file

@ -0,0 +1,71 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isEmpty,
computed,
set
} = Ember;
export default Ember.Component.extend({
titleEmpty: computed.empty('model.title'),
firstnameEmpty: computed.empty('model.firstname'),
lastnameEmpty: computed.empty('model.lastname'),
emailEmpty: computed.empty('model.email'),
passwordEmpty: computed.empty('model.password'),
hasEmptyTitleError: computed.and('titleEmpty', 'titleError'),
hasEmptyFirstnameError: computed.and('firstnameEmpty', 'adminFirstnameError'),
hasEmptyLastnameError: computed.and('lastnameEmpty', 'adminLastnameError'),
hasEmptyEmailError: computed.and('emailEmpty', 'adminEmailError'),
hasEmptyPasswordError: computed.and('passwordEmpty', 'adminPasswordError'),
actions: {
save() {
if (isEmpty(this.get('model.title'))) {
set(this, 'titleError', true);
return $("#siteTitle").focus();
}
if (isEmpty(this.get('model.firstname'))) {
set(this, 'adminFirstnameError', true);
return $("#adminFirstname").focus();
}
if (isEmpty(this.get('model.lastname'))) {
set(this, 'adminLastnameError', true);
return $("#adminLastname").focus();
}
if (isEmpty(this.get('model.email')) || !is.email(this.get('model.email'))) {
set(this, 'adminEmailError', true);
return $("#adminEmail").focus();
}
if (isEmpty(this.get('model.password'))) {
set(this, 'adminPasswordError', true);
return $("#adminPassword").focus();
}
this.model.allowAnonymousAccess = Ember.$("#allowAnonymousAccess").prop('checked');
this.get('save')().then(() => {
set(this, 'titleError', false);
set(this, 'adminFirstnameError', false);
set(this, 'adminLastnameError', false);
set(this, 'adminEmailError', false);
set(this, 'adminPasswordError', false);
});
}
}
});

View file

@ -0,0 +1,41 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
computed,
isEmpty
} = Ember;
export default Ember.Component.extend({
email: "",
sayThanks: false,
emailEmpty: computed.empty('email'),
hasEmptyEmailError: computed.and('emailEmpty', 'emailIsEmpty'),
actions: {
forgot() {
let email = this.get('email');
if (isEmpty(email)) {
Ember.set(this, 'emailIsEmpty', true);
return $("#email").focus();
}
this.get('forgot')(email).then(() => {
Ember.set(this, 'sayThanks', true);
Ember.set(this, 'email', '');
Ember.set(this, 'emailIsEmpty', false);
});
}
}
});

View file

@ -0,0 +1,45 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isEmpty,
computed,
set
} = Ember;
export default Ember.Component.extend({
titleEmpty: computed.empty('model.title'),
messageEmpty: computed.empty('model.message'),
hasTitleInputError: computed.and('titleEmpty', 'titleError'),
hasMessageInputError: computed.and('messageEmpty', 'messageError'),
actions: {
save() {
if (isEmpty(this.get('model.title'))) {
set(this, 'titleError', true);
return $("#siteTitle").focus();
}
if (isEmpty(this.get('model.message'))) {
set(this, 'messageError', true);
return $("#siteMessage").focus();
}
this.model.set('allowAnonymousAccess', Ember.$("#allowAnonymousAccess").prop('checked'));
this.get('save')().then(() => {
set(this, 'titleError', false);
set(this, 'messageError', false);
});
}
}
});

View file

@ -0,0 +1,59 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isEmpty,
isEqual,
computed,
set
} = Ember;
export default Ember.Component.extend({
password: "",
passwordConfirm: "",
mustMatch: false,
passwordEmpty: computed.empty('password'),
confirmEmpty: computed.empty('passwordConfirm'),
hasPasswordError: computed.and('passwordEmpty', 'passwordIsEmpty'),
hasConfirmError: computed.and('confirmEmpty', 'passwordConfirmIsEmpty'),
actions: {
reset() {
let password = this.get('password');
let passwordConfirm = this.get('passwordConfirm');
if (isEmpty(password)) {
set(this, 'passwordIsEmpty', true);
return $("#newPassword").focus();
}
if (isEmpty(passwordConfirm)) {
set(this, 'passwordConfirmIsEmpty', true);
return $("#passwordConfirm").focus();
}
if (!isEqual(password, passwordConfirm)) {
set(this, 'hasPasswordError', true);
set(this, 'hasConfirmError', true);
set(this, 'mustMatch', true);
return;
}
this.get('reset')(password).then(() => {
set(this, 'passwordIsEmpty', false);
set(this, 'passwordConfirmIsEmpty', false);
});
}
}
});

View file

@ -12,103 +12,103 @@
import Ember from 'ember';
export default Ember.Component.extend({
pageBody: "",
appMeta: Ember.inject.service(),
pageBody: "",
appMeta: Ember.inject.service(),
didReceiveAttrs() {
this.set('pageBody', this.get('meta.rawBody'));
},
didReceiveAttrs() {
this.set('pageBody', this.get('meta.rawBody'));
},
didInsertElement() {
let self = this;
didInsertElement() {
let self = this;
let options = {
selector: "#rich-text-editor",
relative_urls: false,
cache_suffix: "?v=430",
browser_spellcheck: false,
gecko_spellcheck: false,
theme: "modern",
statusbar: false,
height: $(document).height() - $(".document-editor > .toolbar").height() - 200,
entity_encoding: "raw",
paste_data_images: true,
image_advtab: true,
image_caption: true,
media_live_embeds: true,
fontsize_formats: "8pt 10pt 12pt 14pt 16pt 18pt 20pt 22pt 24pt 26pt 28pt 30pt 32pt 34pt 36pt",
formats: {
bold: {
inline: 'b'
},
italic: {
inline: 'i'
}
},
extended_valid_elements: "b,i,b/strong,i/em",
plugins: [
'advlist autolink lists link image charmap print preview hr anchor pagebreak',
'searchreplace wordcount visualblocks visualchars code codesample fullscreen',
'insertdatetime media nonbreaking save table directionality',
'emoticons template paste textcolor colorpicker textpattern imagetools'
],
menu: {
edit: {
title: 'Edit',
items: 'undo redo | cut copy paste pastetext | selectall | searchreplace'
},
insert: {
title: 'Insert',
items: 'anchor link media | hr | charmap emoticons | blockquote'
},
format: {
title: 'Format',
items: 'bold italic underline strikethrough superscript subscript | formats fonts | removeformat'
},
table: {
title: 'Table',
items: 'inserttable tableprops deletetable | cell row column'
}
},
toolbar1: "formatselect fontselect fontsizeselect | bold italic underline | link unlink | image media | codesample | outdent indent | alignleft aligncenter alignright alignjustify | bullist numlist | forecolor backcolor",
save_onsavecallback: function() {
self.send('onAction');
}
};
let options = {
selector: "#rich-text-editor",
relative_urls: false,
cache_suffix: "?v=430",
browser_spellcheck: false,
gecko_spellcheck: false,
theme: "modern",
statusbar: false,
height: $(document).height() - $(".document-editor > .toolbar").height() - 200,
entity_encoding: "raw",
paste_data_images: true,
image_advtab: true,
image_caption: true,
media_live_embeds: true,
fontsize_formats: "8pt 10pt 12pt 14pt 16pt 18pt 20pt 22pt 24pt 26pt 28pt 30pt 32pt 34pt 36pt",
formats: {
bold: {
inline: 'b'
},
italic: {
inline: 'i'
}
},
extended_valid_elements: "b,i,b/strong,i/em",
plugins: [
'advlist autolink lists link image charmap print preview hr anchor pagebreak',
'searchreplace wordcount visualblocks visualchars code codesample fullscreen',
'insertdatetime media nonbreaking save table directionality',
'emoticons template paste textcolor colorpicker textpattern imagetools'
],
menu: {
edit: {
title: 'Edit',
items: 'undo redo | cut copy paste pastetext | selectall | searchreplace'
},
insert: {
title: 'Insert',
items: 'anchor link media | hr | charmap emoticons | blockquote'
},
format: {
title: 'Format',
items: 'bold italic underline strikethrough superscript subscript | formats fonts | removeformat'
},
table: {
title: 'Table',
items: 'inserttable tableprops deletetable | cell row column'
}
},
toolbar1: "formatselect fontselect fontsizeselect | bold italic underline | link unlink | image media | codesample | outdent indent | alignleft aligncenter alignright alignjustify | bullist numlist | forecolor backcolor",
save_onsavecallback: function () {
self.send('onAction');
}
};
if (typeof tinymce === 'undefined') {
$.getScript(this.get("appMeta").getBaseUrl("tinymce/tinymce.min.js?v=430"), function() {
window.tinymce.dom.Event.domLoaded = true;
tinymce.baseURL = "//" + window.location.host + "/tinymce";
tinymce.suffix = ".min";
tinymce.init(options);
});
} else {
tinymce.init(options);
}
},
if (typeof tinymce === 'undefined') {
$.getScript("tinymce/tinymce.min.js?v=430", function () {
window.tinymce.dom.Event.domLoaded = true;
tinymce.baseURL = "//" + window.location.host + "/tinymce";
tinymce.suffix = ".min";
tinymce.init(options);
});
} else {
tinymce.init(options);
}
},
willDestroyElement() {
tinymce.remove();
},
willDestroyElement() {
tinymce.remove();
},
actions: {
isDirty() {
return is.not.undefined(tinymce) && is.not.undefined(tinymce.activeEditor) && tinymce.activeEditor.isDirty();
},
actions: {
isDirty() {
return is.not.undefined(tinymce) && is.not.undefined(tinymce.activeEditor) && tinymce.activeEditor.isDirty();
},
onCancel() {
this.attrs.onCancel();
},
onCancel() {
this.attrs.onCancel();
},
onAction(title) {
let page = this.get('page');
let meta = this.get('meta');
onAction(title) {
let page = this.get('page');
let meta = this.get('meta');
page.set('title', title);
meta.set('rawBody', tinymce.activeEditor.getContent());
page.set('title', title);
meta.set('rawBody', tinymce.activeEditor.getContent());
this.attrs.onAction(page, meta);
}
}
});
this.attrs.onAction(page, meta);
}
}
});

View file

@ -0,0 +1,83 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
computed,
isEmpty,
isEqual,
isPresent
} = Ember;
export default Ember.Component.extend({
password: { password: "", confirmation: "" },
hasFirstnameError: computed.empty('model.firstname'),
hasLastnameError: computed.empty('model.lastname'),
hasEmailError: computed.empty('model.email'),
hasPasswordError: computed('passwordError', 'password.password', {
get() {
if (isPresent(this.get('passwordError'))) {
return `error`;
}
if (isEmpty(this.get('password.password'))) {
return null;
}
}
}),
hasConfirmPasswordError: computed('confirmPasswordError', {
get() {
if (isPresent(this.get("confirmPasswordError"))) {
return `error`;
}
return;
}
}),
actions: {
save() {
let password = this.get('password.password');
let confirmation = this.get('password.confirmation');
if (isEmpty(this.get('model.firstname'))) {
return $("#firstname").focus();
}
if (isEmpty(this.get('model.lastname'))) {
return $("#lastname").focus();
}
if (isEmpty(this.get('model.email'))) {
return $("#email").focus();
}
if (isPresent(password) && isEmpty(confirmation)) {
Ember.set(this, 'confirmPasswordError', 'error');
return $("#confirmPassword").focus();
}
if (isEmpty(password) && isPresent(confirmation)) {
Ember.set(this, 'passwordError', 'error');
return $("#password").focus();
}
if (!isEqual(password, confirmation)) {
Ember.set(this, 'passwordError', 'error');
return $("#password").focus();
}
let passwords = this.get('password');
this.get('save')(passwords).finally(() => {
Ember.set(this, 'password.password', '');
Ember.set(this, 'password.confirmation', '');
});
}
}
});

View file

@ -0,0 +1,56 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isEmpty,
computed,
set,
get
} = Ember;
export default Ember.Component.extend({
newUser: { firstname: "", lastname: "", email: "", active: true },
firstnameEmpty: computed.empty('newUser.firstname'),
lastnameEmpty: computed.empty('newUser.lastname'),
emailEmpty: computed.empty('newUser.email'),
hasFirstnameEmptyError: computed.and('firstnameEmpty', 'firstnameError'),
hasLastnameEmptyError: computed.and('lastnameEmpty', 'lastnameError'),
hasEmailEmptyError: computed.and('emailEmpty', 'emailError'),
actions: {
add() {
if (isEmpty(this.get('newUser.firstname'))) {
set(this, 'firstnameError', true);
return $("#newUserFirstname").focus();
}
if (isEmpty(this.get('newUser.lastname'))) {
set(this, 'lastnameError', true);
return $("#newUserLastname").focus();
}
if (isEmpty(this.get('newUser.email')) || is.not.email(this.get('newUser.email'))) {
set(this, 'emailError', true);
return $("#newUserEmail").focus();
}
let user = get(this, 'newUser');
get(this, 'add')(user).then(() => {
this.set('newUser', { firstname: "", lastname: "", email: "", active: true });
set(this, 'firstnameError', false);
set(this, 'lastnameError', false);
set(this, 'emailError', false);
$("#newUserFirstname").focus();
});
}
}
});

View file

@ -1,11 +1,11 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
// by contacting <sales@documize.com>.
//
// https://documize.com
@ -13,23 +13,10 @@ import Ember from 'ember';
export default Ember.Controller.extend({
userService: Ember.inject.service('user'),
email: "",
sayThanks: false,
actions: {
forgot: function () {
var self = this;
var email = this.get('email');
if (is.empty(email)) {
$("#email").addClass("error").focus();
return;
}
self.set('sayThanks', true);
this.set('email', '');
this.get('userService').forgotPassword(email);
forgot: function (email) {
return this.get('userService').forgotPassword(email);
}
}
});
});

View file

@ -3,20 +3,6 @@
<img src="assets/img/logo-color.png" title="Documize" alt="Documize" class="responsive-img" />
</div>
<div class="login-form">
<form {{action 'forgot' on="submit"}}>
{{#if sayThanks}}
<div class="reset-thanks margin-bottom-30">Thanks. Check your email for instructions.</div>
{{else}}
<div class="input-control">
<label>Email</label>
{{focus-input type="email" value=email id="email"}}
</div>
<div class="clearfix" />
<div class="margin-top-10 margin-bottom-20">
<button type="submit" class="regular-button button-blue">Reset</button>
</div>
{{/if}}
{{#link-to 'auth.login'}}Sign In{{/link-to}}
</form>
{{forgot-password forgot=(action 'forgot')}}
</div>
</div>

View file

@ -1,11 +1,11 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
// by contacting <sales@documize.com>.
//
// https://documize.com
@ -18,31 +18,10 @@ export default Ember.Controller.extend({
mustMatch: false,
actions: {
reset() {
let self = this;
let password = this.get('password');
let passwordConfirm = this.get('passwordConfirm');
if (is.empty(password)) {
$("#newPassword").addClass("error").focus();
return;
}
if (is.empty(passwordConfirm)) {
$("#passwordConfirm").addClass("error").focus();
return;
}
if (is.not.equal(password, passwordConfirm)) {
$("#newPassword").addClass("error").focus();
$("#passwordConfirm").addClass("error");
self.set('mustMatch', true);
return;
}
this.get('userService').resetPassword(self.model, password).then(function (response) { /* jshint ignore:line */
self.transitionToRoute('auth.login');
reset(password) {
return this.get('userService').resetPassword(this.model, password).then(() => { /* jshint ignore:line */
this.transitionToRoute('auth.login');
});
}
}
});
});

View file

@ -2,23 +2,5 @@
<div class="logo">
<img src="assets/img/logo-color.png" title="Documize" alt="Documize" class="responsive-img" />
</div>
<div class="login-form">
<form {{action 'reset' on="submit"}}>
<div class="input-control">
<label>New Password</label>
<div class="tip">Choose a strong password</div>
{{focus-input type="password" value=password id="newPassword"}}
</div>
<div class="input-control">
<label>Confirm Password</label>
<div class="tip">Please type your new password again</div>
{{input type="password" value=passwordConfirm id="passwordConfirm"}}
</div>
<div class="clearfix" />
<div class="margin-top-10 margin-bottom-20">
<button type="submit" class="regular-button button-blue">Reset</button>
<span class="{{unless mustMatch "hide"}} color-red margin-left-20">Passwords must match</span>
</div>
</form>
</div>
{{password-reset reset=(action 'reset')}}
</div>

View file

@ -1,11 +1,11 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
// by contacting <sales@documize.com>.
//
// https://documize.com
@ -17,19 +17,9 @@ export default Ember.Controller.extend(NotifierMixin, {
actions: {
save() {
if (is.empty(this.model.get('title'))) {
$("#siteTitle").addClass("error").focus();
return;
}
if (is.empty(this.model.get('message'))) {
$("#siteMessage").addClass("error").focus();
return;
}
this.model.set('allowAnonymousAccess', Ember.$("#allowAnonymousAccess").prop('checked'));
this.get('orgService').save(this.model);
this.showNotification('Saved');
return this.get('orgService').save(this.model).then(() => {
this.showNotification('Saved');
});
}
}
});
});

View file

@ -1,29 +1,3 @@
<div class="input-form form-borderless">
<form>
<div class="heading">
<div class="title">General Settings</div>
<div class="tip">Tell people about this Documize instance</div>
</div>
<div>
<div class="input-control">
<label>Title</label>
<div class="tip">Describe the title of this Documize instance</div>
{{focus-input id="siteTitle" type="text" value=model.title}}
</div>
<div class="input-control">
<label>Message</label>
<div class="tip">Describe the purpose of this Documize instance</div>
{{textarea id="siteMessage" rows="3" value=model.message}}
</div>
<div class="input-control">
<label>Anonymous Access</label>
<div class="tip">Content within "Everyone" will be made available to anonymous users</div>
<div class="checkbox">
<input type="checkbox" id="allowAnonymousAccess" checked= {{model.allowAnonymousAccess}} />
<label for="allowAnonymousAccess">Allow anyone to access this Documize instance</label>
</div>
</div>
<div class="regular-button button-blue" {{ action 'save' }}>save</div>
</div>
</form>
{{general-settings model=model save=(action 'save')}}
</div>

View file

@ -1,11 +1,11 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
// by contacting <sales@documize.com>.
//
// https://documize.com
@ -17,30 +17,13 @@ export default Ember.Controller.extend(NotifierMixin, {
newUser: { firstname: "", lastname: "", email: "", active: true },
actions: {
add: function () {
if (is.empty(this.newUser.firstname)) {
$("#newUserFirstname").addClass("error").focus();
return;
}
if (is.empty(this.newUser.lastname)) {
$("#newUserLastname").addClass("error").focus();
return;
}
if (is.empty(this.newUser.email) || is.not.email(this.newUser.email)) {
$("#newUserEmail").addClass("error").focus();
return;
}
add(user) {
Ember.set(this, 'newUser', user);
$("#newUserFirstname").removeClass("error");
$("#newUserLastname").removeClass("error");
$("#newUserEmail").removeClass("error");
this.get('userService')
return this.get('userService')
.add(this.get('newUser'))
.then((user) => {
this.showNotification('Added');
this.set('newUser', { firstname: "", lastname: "", email: "", active: true });
$("#newUserFirstname").focus();
this.get('model').pushObject(user);
})
.catch(function (error) {
@ -76,4 +59,4 @@ export default Ember.Controller.extend(NotifierMixin, {
this.showNotification('Password changed');
}
}
});
});

View file

@ -1,23 +1,5 @@
<div class="input-form form-borderless">
<form>
<div class="heading">
<div class="title">Add User</div>
<div class="tip">New users receive an invitation email with a random password</div>
</div>
<div class="input-control">
<label>Firstname</label>
{{focus-input id="newUserFirstname" type="text" value=newUser.firstname}}
</div>
<div class="input-control">
<label>Lastname</label>
{{input id="newUserLastname" type="text" value=newUser.lastname}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="newUserEmail" type="text" value=newUser.email}}
</div>
<div class="regular-button button-blue" {{ action 'add' }}>Add</div>
</form>
{{user-settings add=(action 'add')}}
</div>
<div class="clearfix" /> {{settings/user-list users=model onDelete=(action "onDelete") onSave=(action "onSave") onPassword=(action "onPassword")}}

View file

@ -1,62 +1,37 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isPresent
} = Ember;
export default Ember.Controller.extend({
userService: Ember.inject.service('user'),
password: { password: "", confirmation: "" },
session: Ember.inject.service(),
actions: {
save: function () {
if (is.empty(this.model.get('firstname'))) {
$("#firstname").addClass("error").focus();
return;
}
if (is.empty(this.model.get('lastname'))) {
$("#lastname").addClass("error").focus();
return;
}
if (is.empty(this.model.get('email'))) {
$("#email").addClass("error").focus();
return;
}
if (is.not.empty(this.password.password) && is.empty(this.password.confirmation)) {
$("#confirmPassword").addClass("error").focus();
return;
}
if (is.empty(this.password.password) && is.not.empty(this.password.confirmation)) {
$("#password").addClass("error").focus();
return;
}
if (is.not.equal(this.password.password, this.password.confirmation)) {
$("#password").addClass("error").focus();
return;
}
save(passwords) {
let password = passwords.password;
let confirmation = passwords.confirmation;
let self = this;
this.get('userService').save(this.model).then(function () {
if (is.not.empty(self.password.password) && is.not.empty(self.password.confirmation)) {
self.get('userService').updatePassword(self.model.get('id'), self.password.password).then(function () {
self.password.password = "";
self.password.confirmation = "";
});
return this.get('userService').save(this.model).then(() => {
if (isPresent(password) && isPresent(confirmation)) {
this.get('userService').updatePassword(this.get('model.id'), password);
}
self.model.generateInitials();
self.get('session').set('user', self.model);
this.model.generateInitials();
this.get('session').set('user', this.model);
this.transitionToRoute('folders');
});
this.transitionToRoute('folders');
}
}
});
});

View file

@ -13,33 +13,7 @@
{{/layout/zone-sidebar}}
{{#layout/zone-content}}
<div class="input-form form-borderless">
<form>
<div class="input-control">
<label>Firstname</label>
{{focus-input id="firstname" type="text" value=model.firstname}}
</div>
<div class="input-control">
<label>Lastname</label>
{{input id="lastname" type="text" value=model.lastname}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="email" type="text" value=model.email}}
</div>
<div class="input-control">
<label>Password</label>
<div class="tip">Optional change your password</div>
{{input id="password" type="password" value=password.password}}
</div>
<div class="input-control">
<label>Confirm Password</label>
<div class="tip">Confirm your new password</div>
{{input id="confirmPassword" type="password" value=password.confirmation}}
</div>
<div class="regular-button button-blue" {{ action 'save' }}>save</div>
</form>
</div>
{{user-profile model=model save=(action 'save')}}
{{/layout/zone-content}}
{{/layout/zone-container}}

View file

@ -1,11 +1,11 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
// by contacting <sales@documize.com>.
//
// https://documize.com
@ -19,39 +19,7 @@ export default Ember.Controller.extend(NotifierMixin, {
actions: {
save() {
if (is.empty(this.model.title)) {
$("#siteTitle").addClass("error").focus();
return;
}
if (is.empty(this.model.firstname)) {
$("#adminFirstname").addClass("error").focus();
return;
}
if (is.empty(this.model.lastname)) {
$("#adminLastname").addClass("error").focus();
return;
}
if (is.empty(this.model.email)) {
$("#adminEmail").addClass("error").focus();
return;
}
if (!is.email(this.model.email)) {
$("#adminEmail").addClass("error").focus();
return;
}
if (is.empty(this.model.password)) {
$("#adminPassword").addClass("error").focus();
return;
}
this.model.allowAnonymousAccess = Ember.$("#allowAnonymousAccess").prop('checked');
this.get('ajax').request("/setup", {
return this.get('ajax').request("/setup", {
method: 'POST',
data: this.model,
dataType: "text",
@ -64,4 +32,4 @@ export default Ember.Controller.extend(NotifierMixin, {
});
}
}
});
});

View file

@ -9,46 +9,7 @@
<img src="/assets/img/setup/cogs.png" width="157" height="187" alt="Setup new database" class="no-select no-outline" />
</div>
</div>
{{documize-setup model=model save=(action 'save')}}
<div class="col-lg-9 col-md-9 col-sm-9">
<div class="input-form">
<form>
<div class="heading">
<div class="title">Let's setup Documize</div>
<div class="tip">Database name is <em>{{model.dbname}}</em></div>
</div>
<div>
<div class="input-control">
<label>Team</label>
<div class="tip">What's your tribe called?</div>
{{focus-input id="siteTitle" type="text" value=model.title}}
</div>
<div class="input-control">
<label>Firstname</label>
<div class="tip">What do people call you?</div>
{{input id="adminFirstname" type="text" value=model.firstname}}
</div>
<div class="input-control">
<label>Lastname</label>
<div class="tip">How the government refers to you.</div>
{{input id="adminLastname" type="text" value=model.lastname}}
</div>
<div class="input-control">
<label>Email</label>
<div class="tip">No spam. Ever!</div>
{{input id="adminEmail" type="email" value=model.email}}
</div>
<div class="input-control">
<label>Password</label>
<div class="tip">Something you can remember without writing it down.</div>
{{input id="adminPassword" type="text" value=model.password}}
</div>
<div class="regular-button button-green" {{ action 'save' }}>Go Setup</div>
</div>
</form>
</div>
</div>
</div>
</div>

View file

@ -126,4 +126,4 @@ export default Ember.Service.extend({
data: password
});
}
});
});

View file

@ -0,0 +1,38 @@
<div class="col-lg-9 col-md-9 col-sm-9">
<div class="input-form">
<form>
<div class="heading">
<div class="title">Let's setup Documize</div>
<div class="tip">Database name is <em>{{model.dbname}}</em></div>
</div>
<div>
<div class="input-control">
<label>Team</label>
<div class="tip">What's your tribe called?</div>
{{focus-input id="siteTitle" type="text" value=model.title class=(if hasEmptyTitleError 'error')}}
</div>
<div class="input-control">
<label>Firstname</label>
<div class="tip">What do people call you?</div>
{{input id="adminFirstname" type="text" value=model.firstname class=(if hasEmptyFirstnameError 'error')}}
</div>
<div class="input-control">
<label>Lastname</label>
<div class="tip">How the government refers to you.</div>
{{input id="adminLastname" type="text" value=model.lastname class=(if hasEmptyLastnameError 'error')}}
</div>
<div class="input-control">
<label>Email</label>
<div class="tip">No spam. Ever!</div>
{{input id="adminEmail" type="email" value=model.email class=(if hasEmptyEmailError 'error')}}
</div>
<div class="input-control">
<label>Password</label>
<div class="tip">Something you can remember without writing it down.</div>
{{input id="adminPassword" type="text" value=model.password class=(if hasEmptyPasswordError 'error')}}
</div>
<div class="regular-button button-green" {{ action 'save' }}>Go Setup</div>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,15 @@
<form {{action 'forgot' on="submit"}}>
{{#if sayThanks}}
<div class="reset-thanks margin-bottom-30">Thanks. Check your email for instructions.</div>
{{else}}
<div class="input-control">
<label>Email</label>
{{focus-input type="email" value=email id="email" class=(if hasEmptyEmailError 'error')}}
</div>
<div class="clearfix" />
<div class="margin-top-10 margin-bottom-20">
<button type="submit" class="regular-button button-blue">Reset</button>
</div>
{{/if}}
{{#link-to 'auth.login'}}Sign In{{/link-to}}
</form>

View file

@ -0,0 +1,27 @@
<form>
<div class="heading">
<div class="title">General Settings</div>
<div class="tip">Tell people about this Documize instance</div>
</div>
<div>
<div class="input-control">
<label>Title</label>
<div class="tip">Describe the title of this Documize instance</div>
{{focus-input id="siteTitle" type="text" value=model.title class=(if hasTitleInputError 'error')}}
</div>
<div class="input-control">
<label>Message</label>
<div class="tip">Describe the purpose of this Documize instance</div>
{{textarea id="siteMessage" rows="3" value=model.message class=(if hasMessageInputError 'error')}}
</div>
<div class="input-control">
<label>Anonymous Access</label>
<div class="tip">Content within "Everyone" will be made available to anonymous users</div>
<div class="checkbox">
<input type="checkbox" id="allowAnonymousAccess" checked= {{model.allowAnonymousAccess}} />
<label for="allowAnonymousAccess">Allow anyone to access this Documize instance</label>
</div>
</div>
<div class="regular-button button-blue" {{ action 'save' }}>save</div>
</div>
</form>

View file

@ -0,0 +1,19 @@
<div class="login-form">
<form {{action 'reset' on="submit"}}>
<div class="input-control">
<label>New Password</label>
<div class="tip">Choose a strong password</div>
{{focus-input type="password" value=password id="newPassword" class=(if hasPasswordError 'error')}}
</div>
<div class="input-control">
<label>Confirm Password</label>
<div class="tip">Please type your new password again</div>
{{input type="password" value=passwordConfirm id="passwordConfirm" class=(if hasConfirmError 'error')}}
</div>
<div class="clearfix" />
<div class="margin-top-10 margin-bottom-20">
<button type="submit" class="regular-button button-blue">Reset</button>
<span class="{{unless mustMatch "hide"}} color-red margin-left-20">Passwords must match</span>
</div>
</form>
</div>

View file

@ -0,0 +1,27 @@
<div class="input-form form-borderless">
<form>
<div class="input-control">
<label>Firstname</label>
{{focus-input id="firstname" type="text" value=model.firstname class=(if hasFirstnameError 'error')}}
</div>
<div class="input-control">
<label>Lastname</label>
{{input id="lastname" type="text" value=model.lastname class=(if hasLastnameError 'error')}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="email" type="text" value=model.email class=(if hasEmailError 'error')}}
</div>
<div class="input-control">
<label>Password</label>
<div class="tip">Optional change your password</div>
{{input id="password" type="password" value=password.password class=hasPasswordError}}
</div>
<div class="input-control">
<label>Confirm Password</label>
<div class="tip">Confirm your new password</div>
{{input id="confirmPassword" type="password" value=password.confirmation class=hasConfirmPasswordError}}
</div>
<div class="regular-button button-blue" {{ action 'save' }}>save</div>
</form>
</div>

View file

@ -0,0 +1,19 @@
<form>
<div class="heading">
<div class="title">Add User</div>
<div class="tip">New users receive an invitation email with a random password</div>
</div>
<div class="input-control">
<label>Firstname</label>
{{focus-input id="newUserFirstname" type="text" value=newUser.firstname class=(if hasFirstnameEmptyError 'error')}}
</div>
<div class="input-control">
<label>Lastname</label>
{{input id="newUserLastname" type="text" value=newUser.lastname class=(if hasLastnameEmptyError 'error')}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="newUserEmail" type="text" value=newUser.email class=(if hasEmailEmptyError 'error')}}
</div>
<div class="regular-button button-blue" {{ action 'add' }}>Add</div>
</form>

View file

@ -50,8 +50,9 @@ func init() {
var err error
environment.GetString(&connectionString, "db", true,
`"username:password@protocol(hostname:port)/databasename" for example "fred:bloggs@tcp(localhost:3306)/documize"`,
`'username:password@protocol(hostname:port)/databasename" for example "fred:bloggs@tcp(localhost:3306)/documize"`,
func(*string, string) bool {
Db, err = sqlx.Open("mysql", stdConn(connectionString))
if err != nil {

View file

@ -19,9 +19,9 @@ import (
"time"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/web"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
"github.com/documize/community/core/web"
)
// runSQL creates a transaction per call

View file

@ -13,16 +13,20 @@ package database
import (
"bytes"
"crypto/rand"
"database/sql"
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
"github.com/jmoiron/sqlx"
"github.com/documize/community/core/web"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
"github.com/documize/community/core/web"
)
const migrationsDir = "bindata/scripts"
@ -69,97 +73,176 @@ func migrations(lastMigration string) (migrationsT, error) {
func (m migrationsT) migrate(tx *sqlx.Tx) error {
for _, v := range m {
log.Info("Processing migration file: " + v)
buf, err := web.Asset(migrationsDir + "/" + v)
if err != nil {
return err
}
//fmt.Println("DEBUG database.Migrate() ", v, ":\n", string(buf)) // TODO actually run the SQL
err = processSQLfile(tx, buf)
if err != nil {
return err
}
json := `{"database":"` + v + `"}`
sql := "INSERT INTO `config` (`key`,`config`) " +
"VALUES ('META','" + json +
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
_, err = tx.Exec(sql)
_, err = tx.Exec(sql) // add a record in the config file to say we have done the upgrade
if err != nil {
return err
}
//fmt.Println("DEBUG insert 10s wait for testing")
//time.Sleep(10 * time.Second)
}
return nil
}
func migrateEnd(tx *sqlx.Tx, err error) error {
if tx != nil {
_, ulerr := tx.Exec("UNLOCK TABLES;")
log.IfErr(ulerr)
if err == nil {
log.IfErr(tx.Commit())
log.Info("Database checks: completed")
return nil
}
log.IfErr(tx.Rollback())
func lockDB() (bool, error) {
b := make([]byte, 2)
_, err := rand.Read(b)
if err != nil {
return false, err
}
log.Error("Database checks: failed: ", err)
return err
wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait
time.Sleep(wait)
tx, err := (*dbPtr).Beginx()
if err != nil {
return false, err
}
_, err = tx.Exec("LOCK TABLE `config` WRITE;")
if err != nil {
return false, err
}
defer func() {
_, err = tx.Exec("UNLOCK TABLES;")
log.IfErr(err)
log.IfErr(tx.Commit())
}()
_, err = tx.Exec("INSERT INTO `config` (`key`,`config`) " +
fmt.Sprintf(`VALUES ('DBLOCK','{"pid": "%d"}');`, os.Getpid()))
if err != nil {
// good error would be "Error 1062: Duplicate entry 'DBLOCK' for key 'idx_config_area'"
if strings.HasPrefix(err.Error(), "Error 1062:") {
log.Info("Database locked by annother Documize instance")
return false, nil
}
return false, err
}
log.Info("Database locked by this Documize instance")
return true, err // success!
}
func unlockDB() error {
tx, err := (*dbPtr).Beginx()
if err != nil {
return err
}
_, err = tx.Exec("DELETE FROM `config` WHERE `key`='DBLOCK';")
if err != nil {
return err
}
return tx.Commit()
}
func migrateEnd(tx *sqlx.Tx, err error, amLeader bool) error {
if amLeader {
defer func() { log.IfErr(unlockDB()) }()
if tx != nil {
if err == nil {
log.IfErr(tx.Commit())
log.Info("Database checks: completed")
return nil
}
log.IfErr(tx.Rollback())
}
log.Error("Database checks: failed: ", err)
return err
}
return nil // not the leader, so ignore errors
}
func getLastMigration(tx *sqlx.Tx) (lastMigration string, err error) {
var stmt *sql.Stmt
stmt, err = tx.Prepare("SELECT JSON_EXTRACT(`config`,'$.database') FROM `config` WHERE `key` = 'META';")
if err == nil {
defer utility.Close(stmt)
var item = make([]uint8, 0)
row := stmt.QueryRow()
err = row.Scan(&item)
if err == nil {
if len(item) > 1 {
q := []byte(`"`)
lastMigration = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
}
}
}
return
}
// Migrate the database as required, consolidated action.
func Migrate(ConfigTableExists bool) error {
lastMigration := ""
amLeader := false
if ConfigTableExists {
var err error
amLeader, err = lockDB()
log.IfErr(err)
} else {
amLeader = true // what else can you do?
}
tx, err := (*dbPtr).Beginx()
if err != nil {
return migrateEnd(tx, err)
return migrateEnd(tx, err, amLeader)
}
lastMigration := ""
if ConfigTableExists {
_, err = tx.Exec("LOCK TABLE `config` WRITE;")
lastMigration, err = getLastMigration(tx)
if err != nil {
return migrateEnd(tx, err)
}
log.Info("Database checks: lock taken")
var stmt *sql.Stmt
stmt, err = tx.Prepare("SELECT JSON_EXTRACT(`config`,'$.database') FROM `config` WHERE `key` = 'META';")
if err == nil {
defer utility.Close(stmt)
var item = make([]uint8, 0)
row := stmt.QueryRow()
err = row.Scan(&item)
if err != nil {
return migrateEnd(tx, err)
}
if len(item) > 1 {
q := []byte(`"`)
lastMigration = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
}
return migrateEnd(tx, err, amLeader)
}
log.Info("Database checks: last previously applied file was " + lastMigration)
}
mig, err := migrations(lastMigration)
if err != nil {
return migrateEnd(tx, err)
return migrateEnd(tx, err, amLeader)
}
if len(mig) == 0 {
log.Info("Database checks: no updates to perform")
return migrateEnd(tx, nil) // no migrations to perform
return migrateEnd(tx, nil, amLeader) // no migrations to perform
}
log.Info("Database checks: will execute the following update files: " + strings.Join([]string(mig), ", "))
return migrateEnd(tx, mig.migrate(tx))
if amLeader {
log.Info("Database checks: will execute the following update files: " + strings.Join([]string(mig), ", "))
return migrateEnd(tx, mig.migrate(tx), amLeader)
}
// a follower instance
targetMigration := string(mig[len(mig)-1])
for targetMigration != lastMigration {
time.Sleep(time.Second)
log.Info("Waiting for database migration process to complete")
tx.Rollback() // ignore error
tx, err := (*dbPtr).Beginx() // need this in order to see the changed situation since last tx
if err != nil {
return migrateEnd(tx, err, amLeader)
}
lastMigration, _ = getLastMigration(tx)
}
return migrateEnd(tx, nil, amLeader)
}
func processSQLfile(tx *sqlx.Tx, buf []byte) error {

View file

@ -27,7 +27,7 @@ type ProdInfo struct {
func Product() (p ProdInfo) {
p.Major = "0"
p.Minor = "16"
p.Patch = "0"
p.Patch = "1"
p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch)
p.Edition = "Community"
p.Title = fmt.Sprintf("%s Edition", p.Edition)