Ivan - Fix Installation issues [skip ci]

This commit is contained in:
ivan 2017-03-29 18:46:26 -03:00
parent 3047831864
commit 48614fcc00
33 changed files with 223 additions and 107 deletions

View File

@ -2,6 +2,16 @@ import API from 'lib-app/api-call';
import sessionStore from 'lib-app/session-store';
export default {
checkInstallation() {
return {
type: 'CHECK_INSTALLATION',
payload: API.call({
path: '/system/installation-done',
data: {}
})
};
},
init() {
if (sessionStore.isLoggedIn()) {
return {

View File

@ -74,7 +74,7 @@ class App extends React.Component {
}
if (validations.languageChanged) {
browserHistory.push(props.location.pathname);
window.location.reload();
}
if (validations.loggedOut) {
@ -99,7 +99,7 @@ class App extends React.Component {
browserHistory.push('/');
}
if(!props.config['user-system-enabled'] && _.includes(props.location.pathname, '/check-ticket')) {
if(props.config['user-system-enabled'] && _.includes(props.location.pathname, '/check-ticket')) {
browserHistory.push('/');
}
}

View File

@ -122,7 +122,7 @@ class AdminPanelEmailTemplates extends React.Component {
getItems() {
return this.state.items.map((item) => {
return {
content: i18n(item.type)
content: item.type
};
});
}

View File

@ -174,8 +174,8 @@ class AdminPanelSystemPreferences extends React.Component {
'smtp-port': form['smtp-port'],
'smtp-user': form['smtp-user'],
'smtp-pass': form['smtp-pass'],
'maintenance-mode': form['maintenance-mode'],
'allow-attachments': form['allow-attachments'],
'maintenance-mode': form['maintenance-mode'] * 1,
'allow-attachments': form['allow-attachments'] * 1,
'max-size': form['max-size'],
'allowedLanguages': JSON.stringify(form.allowedLanguages.map(index => languageKeys[index])),
'supportedLanguages': JSON.stringify(form.supportedLanguages.map(index => languageKeys[index]))

View File

@ -62,10 +62,16 @@ class AdminPanelDepartments extends React.Component {
<div className="admin-panel-departments__discard-button">
<Button onClick={this.onDiscardChangesClick.bind(this)} size="medium">{i18n('DISCARD_CHANGES')}</Button>
</div>
{this.props.departments.length > 1 ? this.renderDeleteButton() : null}
</div>
);
}
renderDeleteButton() {
return (
<div className="admin-panel-departments__delete-button">
<Button onClick={this.onDeleteClick.bind(this)} size="medium">{i18n('DELETE')}</Button>
</div>
</div>
);
}

View File

@ -37,11 +37,16 @@ class InstallStep2Requirements extends React.Component {
</Button>
</div>
<Table {...this.getTableProps()} />
<div className="install-step-2__buttons">
<div className="install-step-2__next">
<Button disabled={!this.isAllOk()} size="medium" type="secondary" onClick={() => browserHistory.push('/install/step-3')}>
{i18n('NEXT')}
</Button>
</div>
<div className="install-step-2__previous">
<Button size="medium" onClick={this.onPreviousClick.bind(this)}>{i18n('PREVIOUS')}</Button>
</div>
</div>
</div>
);
}
@ -83,6 +88,11 @@ class InstallStep2Requirements extends React.Component {
return classNames(classes);
}
onPreviousClick(event) {
event.preventDefault();
browserHistory.push('/install/step-1');
}
isAllOk() {
return _.every(this.state.requirements, {ok: true});
}

View File

@ -25,13 +25,10 @@
&__requirement {
color: $secondary-blue;
&-value {
width: 200px;
}
&-assert {
color: $primary-green;
float: right;
float: left;
margin-right: 11px;
margin-top: 3px;
}
@ -45,7 +42,13 @@
}
}
&__next {
&__previous {
float: left;
}
&__next {
float: left;
position: absolute;
margin-left: 103px;
}
}

View File

@ -26,9 +26,9 @@ class InstallStep3Database extends React.Component {
{this.renderMessage()}
<Form loading={this.state.loading} onSubmit={this.onSubmit.bind(this)}>
<FormField name="dbHost" label={i18n('DATABASE_HOST')} fieldProps={{size: 'large'}} required/>
<FormField name="dbName" label={i18n('DATABASE_NAME')} fieldProps={{size: 'large'}} required/>
<FormField name="dbName" label={i18n('DATABASE_NAME')} fieldProps={{size: 'large'}} infoMessage={i18n('LEFT_EMPTY_DATABASE')}/>
<FormField name="dbUser" label={i18n('DATABASE_USER')} fieldProps={{size: 'large'}} required/>
<FormField name="dbPassword" label={i18n('DATABASE_PASSWORD')} fieldProps={{size: 'large', password: true}} required/>
<FormField name="dbPassword" label={i18n('DATABASE_PASSWORD')} fieldProps={{size: 'large', password: true}}/>
<div className="install-step-3__buttons">
<SubmitButton className="install-step-3__next" size="medium" type="secondary">{i18n('NEXT')}</SubmitButton>
<Button className="install-step-3__previous" size="medium" onClick={this.onPreviousClick.bind(this)}>{i18n('PREVIOUS')}</Button>

View File

@ -15,6 +15,7 @@ import SubmitButton from 'core-components/submit-button';
class InstallStep4UserSystem extends React.Component {
state = {
loading: false,
form: {
'user-system-enabled': true,
'registration': true
@ -25,7 +26,7 @@ class InstallStep4UserSystem extends React.Component {
return (
<div className="install-step-4">
<Header title={i18n('STEP_TITLE', {title: i18n('USER_SYSTEM'), current: 4, total: 6})} description={i18n('STEP_4_DESCRIPTION')}/>
<Form onSubmit={this.onSubmit.bind(this)} values={this.state.form} onChange={this.onChange.bind(this)}>
<Form onSubmit={this.onSubmit.bind(this)} values={this.state.form} onChange={this.onChange.bind(this)} loading={this.state.loading}>
<FormField name="user-system-enabled" label={i18n('ENABLE_USER_SYSTEM')} decorator={ToggleButton}/>
<FormField name="registration" label={i18n('ENABLE_USER_REGISTRATION')} decorator={ToggleButton} fieldProps={{disabled: this.isDisabled()}}/>
<div className="install-step-4__buttons">
@ -52,14 +53,18 @@ class InstallStep4UserSystem extends React.Component {
}
onSubmit(form) {
API.call({
this.setState({
loading: true
}, () => API.call({
path: '/system/init-settings',
data: {
'language': this.props.language,
'user-system-enabled': form['user-system-enabled'] * 1,
'registration': form['registration'] * 1
}
}).then(() => browserHistory.push('/install/step-5'));
}).then(() => this.setState({
loading: false
}, () => browserHistory.push('/install/step-5'))));
}
isDisabled() {

View File

@ -2,7 +2,6 @@ import React from 'react';
import {browserHistory} from 'react-router';
import i18n from 'lib-app/i18n';
import API from 'lib-app/api-call';
import Header from 'core-components/header';
import Message from 'core-components/message';

View File

@ -57,13 +57,13 @@ class DashboardListArticlesPage extends React.Component {
);
}
renderSearchResultsItem(item) {
renderSearchResultsItem(item, index) {
let content = this.stripHTML(item.content);
content = content.substring(0, 100);
content += '...';
return (
<div className="dashboard-list-articles-page__search-result">
<div className="dashboard-list-articles-page__search-result" key={index}>
<div className="dashboard-list-articles-page__search-result-title">
<Link to={((this.props.location.pathname == '/articles') ? '/article/' : '/dashboard/article/') + item.id}>{item.title}</Link>
</div>

View File

@ -36,9 +36,9 @@ class ColorSelector extends React.Component {
);
}
renderTooltipColor(color) {
renderTooltipColor(color, index) {
return (
<span className="color-selector__tooltip-color" onClick={this.onColorClick.bind(this, color)} style={{backgroundColor: color}}/>
<span className="color-selector__tooltip-color" onClick={this.onColorClick.bind(this, color)} style={{backgroundColor: color}} key={index}/>
);
}

View File

@ -16,7 +16,7 @@ class FormField extends React.Component {
};
static propTypes = {
decorator: React.PropTypes.func,
decorator: React.PropTypes.oneOfType(React.PropTypes.func, React.PropTypes.string),
validation: React.PropTypes.string,
onChange: React.PropTypes.func,
onBlur: React.PropTypes.func,

View File

@ -39,9 +39,9 @@ class IconSelector extends React.Component {
);
}
renderTooltipIcon(name) {
renderTooltipIcon(name, index) {
return (
<div className="icon-selector__tooltip-icon" onClick={this.onIconClick.bind(this, name)}>
<div className="icon-selector__tooltip-icon" onClick={this.onIconClick.bind(this, name)} key={index}>
<Icon name={name} />
</div>
);

View File

@ -349,5 +349,6 @@ export default {
'ACTIVITY_RE_OPEN_THIS': 'reopened this ticket',
'ACTIVITY_DEPARTMENT_CHANGED_THIS': 'changed department of this ticket to ',
'ACTIVITY_PRIORITY_CHANGED_THIS': 'changed priority of this ticket to',
'DATE_PREFIX': 'on'
'DATE_PREFIX': 'on',
'LEFT_EMPTY_DATABASE': 'Left empty for automatic database creation'
};

View File

@ -6,7 +6,7 @@
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<title>OS4</title>
<title>OpenSupports</title>
<link rel="stylesheet" href="/css/main.css">
<link rel="icon" type="image/x-icon" href="/images/icon.png">
@ -17,6 +17,5 @@
<script src="/js/config.js"></script>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -37,5 +37,6 @@ let unsubscribe = store.subscribe(() => {
}
});
store.dispatch(ConfigActions.checkInstallation());
store.dispatch(ConfigActions.init());
store.dispatch(SessionActions.initSession());

23
client/src/index.php Normal file
View File

@ -0,0 +1,23 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<title>OpenSupports</title>
<link rel="stylesheet" href="/css/main.css">
<link rel="icon" type="image/x-icon" href="/images/icon.png">
</head>
<body>
<div id="app"></div>
<script>
root = "<?php echo ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']); ?>";
apiRoot = "<?php echo ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']); ?>" + 'api';
</script>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -69,7 +69,7 @@ class SessionStore {
return {
language: this.getItem('language'),
reCaptchaKey: this.getItem('reCaptchaKey'),
departments: this.getDepartments(),
departments: this.getDepartments() || [],
allowedLanguages: JSON.parse(this.getItem('allowedLanguages')),
supportedLanguages: JSON.parse(this.getItem('supportedLanguages')),
layout: this.getItem('layout'),
@ -110,7 +110,7 @@ class SessionStore {
}
setItem(key, value) {
return this.storage.setItem(key, value);
return this.storage.setItem(key, (value !== undefined) ? value : '');
}
removeItem(key) {

View File

@ -8,7 +8,9 @@ class ConfigReducer extends Reducer {
getInitialState() {
return {
language: sessionStore.getItem('language'),
initDone: false
initDone: false,
installedDone: false,
installed: false
};
}
@ -16,6 +18,7 @@ class ConfigReducer extends Reducer {
return {
'CHANGE_LANGUAGE': this.onLanguageChange,
'INIT_CONFIGS_FULFILLED': this.onInitConfigs,
'CHECK_INSTALLATION_FULFILLED': this.onInstallationChecked,
'UPDATE_DATA_FULFILLED': this.onInitConfigs
};
}
@ -44,6 +47,13 @@ class ConfigReducer extends Reducer {
initDone: true
});
}
onInstallationChecked(state, payload) {
return _.extend({}, state, {
installedDone: true,
installed: payload.data
});
}
}
export default ConfigReducer.getInstance();

View File

@ -57,7 +57,7 @@ class EditStaffController extends Controller {
$this->staffInstance->sharedDepartmentList = $this->getDepartmentList();
}
if($fileUploader = $this->uploadFile()) {
if($fileUploader = $this->uploadFile(true)) {
$this->staffInstance->profilePic = ($fileUploader instanceof FileUploader) ? $fileUploader->getFileName() : null;
}

View File

@ -18,6 +18,13 @@ class GetAllTicketsStaffController extends Controller {
}
public function handler() {
if (Ticket::isTableEmpty()) {
Response::respondSuccess([
'tickets' => [],
'pages' => 0
]);
return;
}
Response::respondSuccess([
'tickets' => $this->getTicketList()->toArray(),

View File

@ -13,6 +13,11 @@ class GetNewTicketsStaffController extends Controller {
];
}
public function handler() {
if (Ticket::isTableEmpty()) {
Response::respondSuccess([]);
return;
}
$user = Controller::getLoggedUser();
$query = ' (';
foreach ($user->sharedDepartmentList as $department) {

View File

@ -14,6 +14,8 @@ class CheckRequirementsController extends Controller {
}
public function handler() {
$configWritable = !!fopen('config.php', 'w+');
Response::respondSuccess([
'phpVersion' => [
'name' => 'PHP Version',
@ -32,8 +34,8 @@ class CheckRequirementsController extends Controller {
],
'files' => [
'name' => 'Folder: /api/files',
'value' => is_writable('files/') ? 'Writable' : 'Not writable',
'ok' => is_writable('files/')
'value' => $configWritable ? 'Writable' : 'Not writable',
'ok' => $configWritable
]
]);
}

View File

@ -12,16 +12,18 @@ class GetSettingsController extends Controller {
}
public function handler() {
$settingsList = [];
if(InstallationDoneController::isInstallationDone()) {
if(Controller::request('allSettings') && Controller::isStaffLogged(3)) {
$settingsList = [
'language' => Setting::getSetting('language')->getValue(),
'reCaptchaKey' => Setting::getSetting('recaptcha-public')->getValue(),
'reCaptchaPrivate' => Setting::getSetting('recaptcha-private')->getValue(),
'time-zone' => Setting::getSetting('time-zone')->getValue(),
'maintenance-mode' => Setting::getSetting('maintenance-mode')->getValue(),
'maintenance-mode' => Setting::getSetting('maintenance-mode')->getValue() * 1,
'layout' => Setting::getSetting('layout')->getValue(),
'allow-attachments' => Setting::getSetting('allow-attachments')->getValue(),
'allow-attachments' => Setting::getSetting('allow-attachments')->getValue() * 1,
'max-size' => Setting::getSetting('max-size')->getValue(),
'url' => Setting::getSetting('url')->getValue(),
'title' => Setting::getSetting('title')->getValue(),
@ -39,18 +41,19 @@ class GetSettingsController extends Controller {
'language' => Setting::getSetting('language')->getValue(),
'reCaptchaKey' => Setting::getSetting('recaptcha-public')->getValue(),
'time-zone' => Setting::getSetting('time-zone')->getValue(),
'maintenance-mode' => Setting::getSetting('maintenance-mode')->getValue(),
'maintenance-mode' => Setting::getSetting('maintenance-mode')->getValue() * 1,
'layout' => Setting::getSetting('layout')->getValue(),
'allow-attachments' => Setting::getSetting('allow-attachments')->getValue(),
'allow-attachments' => Setting::getSetting('allow-attachments')->getValue() * 1,
'max-size' => Setting::getSetting('max-size')->getValue(),
'title' => Setting::getSetting('title')->getValue(),
'registration' => Setting::getSetting('registration')->getValue(),
'departments' => Department::getDepartmentNames(),
'supportedLanguages' => Language::getSupportedLanguages(),
'allowedLanguages' => Language::getAllowedLanguages(),
'user-system-enabled' => Setting::getSetting('user-system-enabled')->getValue()
'user-system-enabled' => Setting::getSetting('user-system-enabled')->getValue() * 1
];
}
}
Response::respondSuccess($settingsList);
}

View File

@ -14,17 +14,43 @@ class InitDatabaseController extends Controller {
}
public function handler() {
if(defined('MYSQL_HOST')) {
if(InstallationDoneController::isInstallationDone()) {
throw new Exception(ERRORS::INIT_SETTINGS_DONE);
return;
}
$dbHost = Controller::request('dbHost');
$dbName = Controller::request('dbName');
$dbUser = Controller::request('dbUser');
$dbPass = Controller::request('dbPassword');
if(!defined('MYSQL_HOST')) {
RedBean::setup('mysql:host=' . $dbHost, $dbUser, $dbPass);
}
if($dbName) {
RedBean::addDatabase($dbName, 'mysql:host='. $dbHost . ';dbname=' . $dbName, $dbUser, $dbPass);
RedBean::selectDatabase($dbName);
if(!RedBean::testConnection()) {
throw new Exception(ERRORS::DATABASE_CONNECTION);
}
} else {
$dbName = 'opensupports_' . Hashing::generateRandomNumber(100, 999);
RedBean::exec('CREATE DATABASE ' . $dbName);
RedBean::addDatabase($dbName, 'mysql:host='. $dbHost . ';dbname=' . $dbName, $dbUser, $dbPass);
RedBean::selectDatabase($dbName);
if(!RedBean::testConnection()) {
throw new Exception(ERRORS::DATABASE_CREATION);
}
}
$configFile = fopen('config.php', 'w+') or die(ERRORS::INVALID_FILE);
$content = '<?php' . PHP_EOL;
$content .= 'define(\'MYSQL_HOST\', \'' . Controller::request('dbHost') . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_USER\', \'' . Controller::request('dbUser') . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_PASSWORD\', \'' . Controller::request('dbPassword') . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_DATABASE\', \'' . Controller::request('dbName') . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_HOST\', \'' . $dbHost . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_USER\', \'' . $dbUser . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_PASSWORD\', \'' . $dbPass . '\');' . PHP_EOL;
$content .= 'define(\'MYSQL_DATABASE\', \'' . $dbName . '\');' . PHP_EOL;
fwrite($configFile, $content);
fclose($configFile);

View File

@ -51,9 +51,9 @@ class InitSettingsController extends Controller {
'registration' => !!Controller::request('registration'),
'user-system-enabled' => !!Controller::request('user-system-enabled'),
'last-stat-day' => date('YmdHi', strtotime(' -12 day ')),
'ticket-gap' => Hashing::generateRandomPrime(100000, 999999),
'file-gap' => Hashing::generateRandomPrime(100000, 999999),
'file-first-number' => Hashing::generateRandomNumber(100000, 999999),
'ticket-gap' => Hashing::generateRandomPrime(1000000, 9999999),
'file-gap' => Hashing::generateRandomPrime(1000000, 9999999),
'file-first-number' => Hashing::generateRandomNumber(1000000, 9999999),
'file-quantity' => 0
]);
}

View File

@ -5,6 +5,10 @@ class InstallationDoneController extends Controller {
const PATH = '/installation-done';
const METHOD = 'POST';
public static function isInstallationDone() {
return RedBean::testConnection() && !Setting::isTableEmpty() && !Staff::isTableEmpty();
}
public function validations() {
return [
'permission' => 'any',
@ -13,7 +17,7 @@ class InstallationDoneController extends Controller {
}
public function handler() {
if(RedBean::testConnection() && !Setting::isTableEmpty() && !Staff::isTableEmpty()) {
if(InstallationDoneController::isInstallationDone()) {
Response::respondSuccess(1);
} else {
Response::respondSuccess(0);

View File

@ -99,8 +99,8 @@ class CommentController extends Controller {
$mailSender = new MailSender();
$mailSender->setTemplate(MailTemplate::TICKET_RESPONDED, [
'to' => $this->ticket->author->email,
'name' => $this->ticket->author->name,
'to' => ($this->ticket->author) ? $this->ticket->author->email : $this->ticket->authorEmail,
'name' => ($this->ticket->author) ? $this->ticket->author->name : $this->ticket->authorName,
'ticketNumber' => $this->ticket->ticketNumber,
'title' => $this->ticket->title,
'url' => Setting::getSetting('url')->getValue()

View File

@ -41,4 +41,6 @@ class ERRORS {
const INVALID_PERIOD = 'INVALID_PERIOD';
const NAME_ALREADY_USED = 'NAME_ALREADY_USED';
const INVALID_FILE = 'INVALID_FILE';
const DATABASE_CONNECTION = 'DATABASE_CONNECTION';
const DATABASE_CREATION = 'DATABASE_CREATION';
}

View File

@ -1,5 +1,5 @@
<?php
include 'config.php';
@include 'config.php';
require_once 'vendor/autoload.php';
// REDBEAN CONFIGURATION

View File

@ -17,7 +17,7 @@ abstract class Controller {
$this->validate();
$this->handler();
} catch (\Exception $exception) {
Response::respondError($exception->getMessage());
Response::respondError($exception->getMessage() . ' on line ' . $exception->getFile() . ':' . $exception->getLine());
return;
}
};

View File

@ -60,15 +60,15 @@ class Ticketevent extends DataStore {
}
public function toArray() {
$user = ($this->authorUser instanceof User) ? $this->authorUser : $this->authorStaff;
$user = ($this->authorStaff) ? $this->authorStaff : $this->authorUser;
return [
'type' => $this->type,
'ticketNumber' => $this->ticket->ticketNumber,
'author' => [
'name' => $user->name,
'name' => $user ? $user->name : null,
'staff' => $user instanceOf Staff,
'id' => $user->id
'id' => $user ? $user->id : null
]
];
}