Admin feature

This commit is contained in:
Daniel Brendel
2023-12-03 16:53:24 +01:00
parent 5bc170db79
commit 88687fac77
17 changed files with 588 additions and 17 deletions

View File

@@ -93,11 +93,11 @@ you can start it via:
```shell
php asatru serve
```
Now browse to http://localhost:8000/ and you should see a message indicating that the access is forbidden with error 403.
At this point you need to create your database users. Go to your database control panel and switch to the users table.
Add all new users that should get access to the application. The following is an example:
Now browse to http://localhost:8000/ and you should be redirected to the authentication page.
At this point you need to create your first user. Go to your database control panel and switch to the users table.
Add the user account that should get access to the application with admin privileges. The following is an example:
```sql
INSERT INTO `users` (`id`, `name`, `email`, `password`, `password_reset`, `session`, `status`, `lang`, `chatcolor`, `show_log`, `last_seen_msg`, `last_typing`, `last_action`, `created_at`) VALUES
INSERT INTO `users` (`id`, `name`, `email`, `password`, `password_reset`, `session`, `status`, `admin`, `lang`, `chatcolor`, `show_log`, `last_seen_msg`, `last_typing`, `last_action`, `created_at`) VALUES
(
NULL,
'Username',
@@ -106,6 +106,7 @@ INSERT INTO `users` (`id`, `name`, `email`, `password`, `password_reset`, `sessi
NULL,
NULL,
0,
1,
NULL,
NULL,
1,
@@ -115,15 +116,17 @@ INSERT INTO `users` (`id`, `name`, `email`, `password`, `password_reset`, `sessi
CURRENT_TIMESTAMP
);
```
As you might have noticed the values that you need to customize are name, email and password. All others are left with their default values.
As you might have noticed the values that you need to customize are name, email, password and admin. All others are left with their default values.
The password hash must be created manually. For testing purposes you might just want to quickly use something like:
```shell
php -r "echo password_hash('test', PASSWORD_BCRYPT);"
```
If you want to test it now you can again browse to the URL and the system will redirect you to the /auth page.
After logging in, you should then be redirected to your dashboard. Users can change their passwords in their profile preferences. They can also
reset their password. Therefore an e-mail will be sent to them with restoration instructions. Last but not least you need to add all your locations
of your local environment to the database. Therefore go to the locations table and add your locatios:
You may now login with your initial admin user account using your e-mail address and the password of which you have stored the hash in the table.
After logging in, you should then be redirected to the dashboard. Further users can now be created via the admin area. Users can change their
passwords in their profile preferences. They can also reset their password. Therefore an e-mail will be sent to them with restoration instructions.
Each new created user will get a confirmation e-mail with an automatically generated password in order to log in. It is recommended that users change
their passwords after their first login.
Last but not least you need to add all your locations of your local environment to the database. Therefore go to the locations table and add your locatios:
```sql
INSERT INTO `locations` (`id`, `name`, `icon`, `active`, `created_at`) VALUES

View File

@@ -56,5 +56,10 @@ return [
array('/chat/typing', 'ANY', 'index@get_chat_typing_status'),
array('/chat/typing/update', 'ANY', 'index@update_chat_typing'),
array('/user/online', 'ANY', 'index@get_online_users'),
array('/admin', 'GET', 'admin@index'),
array('/admin/environment/save', 'POST', 'admin@save_environment'),
array('/admin/user/create', 'POST', 'admin@create_user'),
array('/admin/user/update', 'POST', 'admin@update_user'),
array('/admin/user/remove', 'ANY', 'admin@remove_user'),
array('$404', 'ANY', 'error404@index')
];

137
app/controller/admin.php Normal file
View File

@@ -0,0 +1,137 @@
<?php
/**
* This class represents your controller
*/
class AdminController extends BaseController {
/**
* Perform base initialization
*
* @return void
*/
public function __construct()
{
parent::__construct();
if (!UserModel::isCurrentlyAdmin()) {
header('Location: /');
exit();
}
}
/**
* Handles URL: /admin
*
* @param Asatru\Controller\ControllerArg $request
* @return Asatru\View\ViewHandler
*/
public function index($request)
{
$user = UserModel::getAuthUser();
$locs = LocationsModel::getAll();
$user_accounts = UserModel::getAll();
return parent::view(['content', 'admin'], [
'user' => $user,
'locations' => $locs,
'user_accounts' => $user_accounts
]);
}
/**
* Handles URL: /admin/environment/save
*
* @param Asatru\Controller\ControllerArg $request
* @return Asatru\View\RedirectHandler
*/
public function save_environment($request)
{
try {
$workspace = $request->params()->query('workspace', env('APP_WORKSPACE'));
$lang = $request->params()->query('lang', env('APP_LANG'));
$scroller = (bool)$request->params()->query('scroller', 0);
$onlinetimelimit = (int)$request->params()->query('onlinetimelimit', env('APP_ONLINEMINUTELIMIT'));
$chatonlineusers = (bool)$request->params()->query('chatonlineusers', 0);
$chattypingindicator = (bool)$request->params()->query('chattypingindicator', 0);
UtilsModule::saveEnvironment($workspace, $lang, $scroller, $onlinetimelimit, $chatonlineusers, $chattypingindicator);
FlashMessage::setMsg('success', __('app.environment_settings_saved'));
return redirect('/admin');
} catch (\Exception $e) {
FlashMessage::setMsg('error', $e->getMessage());
return back();
}
}
/**
* Handles URL: /admin/user/create
*
* @param Asatru\Controller\ControllerArg $request
* @return Asatru\View\RedirectHandler
*/
public function create_user($request)
{
try {
$name = $request->params()->query('name', null);
$email = $request->params()->query('email', null);
UserModel::createUser($name, $email);
FlashMessage::setMsg('success', __('app.user_created_successfully'));
return redirect('/admin');
} catch (\Exception $e) {
FlashMessage::setMsg('error', $e->getMessage());
return back();
}
}
/**
* Handles URL: /admin/user/update
*
* @param Asatru\Controller\ControllerArg $request
* @return Asatru\View\RedirectHandler
*/
public function update_user($request)
{
try {
$id = $request->params()->query('id');
$name = $request->params()->query('name', null);
$email = $request->params()->query('email', null);
$admin = $request->params()->query('admin', 0);
UserModel::updateUser($id, $name, $email, (int)$admin);
FlashMessage::setMsg('success', __('app.user_updated_successfully'));
return redirect('/admin');
} catch (\Exception $e) {
FlashMessage::setMsg('error', $e->getMessage());
return back();
}
}
/**
* Handles URL: /admin/user/remove
*
* @param Asatru\Controller\ControllerArg $request
* @return Asatru\View\RedirectHandler
*/
public function remove_user($request)
{
try {
$id = $request->params()->query('id');
UserModel::removeUser($id);
FlashMessage::setMsg('success', __('app.user_removed_successfully'));
return redirect('/admin');
} catch (\Exception $e) {
FlashMessage::setMsg('error', $e->getMessage());
return back();
}
}
}

View File

@@ -14,6 +14,7 @@ return [
'enter_password' => 'Gib dein Passwort ein',
'enter_password_confirmation' => 'Bestätige dein Passwort',
'login' => 'Anmelden',
'logout' => 'Abmelden',
'user_not_found' => 'User konnte nicht gefunden werden: {email}',
'password_mismatch' => 'Die Passwörter stimmen nicht überein',
'password' => 'Passwort',
@@ -21,7 +22,7 @@ return [
'restore_password' => 'Passwort wiederherstellen',
'restore_password_info' => 'Bitte befolge die Anweisungen, die an dein E-Mail Konto geschickt wurden.',
'reset_password' => 'Passwort zurücksetzen',
'reset_password_hint' => 'Bitte navigiere zum folgenden Link, um deinen Zugriff auf {workspace} wiederzuerlangen: <a href="{url}">Zugang wiedererlangen</a>',
'reset_password_hint' => 'Bitte navigiere zu folgendem Link, um deinen Zugriff auf {workspace} wiederzuerlangen: <a href="{url}">Zugang wiedererlangen</a>',
'dashboard' => 'Dashboard',
'welcome_message' => 'Hallo {name}, willkommen zurück!',
'locations' => 'Räume',
@@ -145,5 +146,24 @@ return [
'no_photo_available' => 'Kein Foto verfügbar',
'due' => 'Fällig',
'overdue_tasks' => 'Überfällige Aufgaben',
'view_task_details' => 'Gehe zu den Details'
'view_task_details' => 'Gehe zu den Details',
'admin_area' => 'Administration',
'environment' => 'Environment',
'workspace' => 'Arbeitsraum',
'enable_scroller' => 'Scroller aktivieren',
'online_time_limit' => 'Zeitlimit für Online-Status',
'show_chat_onlineusers' => 'User anzeigen, die gerade online sind',
'show_chat_typingindicator' => 'Indikator anzeigen, wenn jemand gerade im Chat schreibt',
'admin' => 'Admin',
'create' => 'Erstellen',
'update' => 'Aktualisieren',
'remove' => 'Entfernen',
'create_user' => 'User erstellen',
'account_created' => 'Konto wurde erstellt',
'account_created_hint' => 'Dein Account für {workspace} wurde gerade erstellt. Bitte <a href="{url}">melde</a> mit deiner E-Mail Adresse und dem Passwort <strong>{password}</strong> an. Es wird dringend empfohlen, nach der Anmeldung dein Passwort zu ändern.',
'environment_settings_saved' => 'Umgebungseinstellungen wurden erfolgreich gespeichert',
'user_created_successfully' => 'Neuer User-Account wurde erfolgreich erstellt',
'user_updated_successfully' => 'Die Daten wurden erfolgreich aktualisiert',
'user_removed_successfully' => 'Das User-Konto wurde erfolgreich entfernt',
'confirm_user_removal' => 'Soll dieser Account wirklich entfernt werden?'
];

View File

@@ -14,6 +14,7 @@ return [
'enter_password' => 'Enter your password',
'enter_password_confirmation' => 'Confirm your password',
'login' => 'Login',
'logout' => 'Logout',
'user_not_found' => 'User not found: {email}',
'password_mismatch' => 'The passwords do not match',
'password' => 'Password',
@@ -21,7 +22,7 @@ return [
'restore_password' => 'Restore password',
'restore_password_info' => 'Please follow the instructions that were sent to your E-Mail account.',
'reset_password' => 'Reset password',
'reset_password_hint' => 'Please navigate on the following link in order to reset your password for your access to {workspace}: <a href="{url}">Reset password</a>',
'reset_password_hint' => 'Please navigate to the following link in order to reset your password for your access to {workspace}: <a href="{url}">Reset password</a>',
'dashboard' => 'Dashboard',
'welcome_message' => 'Hi {name}, welcome back!',
'locations' => 'Locations',
@@ -145,5 +146,24 @@ return [
'no_photo_available' => 'No photo available',
'due' => 'Due',
'overdue_tasks' => 'Overdue tasks',
'view_task_details' => 'View task details'
'view_task_details' => 'View task details',
'admin_area' => 'Admin area',
'environment' => 'Environment',
'workspace' => 'Workspace',
'enable_scroller' => 'Enable scroller',
'online_time_limit' => 'Online status time limit',
'show_chat_onlineusers' => 'Show users that are currently online',
'show_chat_typingindicator' => 'Show chat typing indicator',
'admin' => 'Admin',
'create' => 'Create',
'update' => 'Update',
'remove' => 'Remove',
'create_user' => 'Create user',
'account_created' => 'Account created',
'account_created_hint' => 'Your account for {workspace} was just created. Please <a href="{url}">login</a> with your e-mail and the password <strong>{password}</strong>. It is strongly recommended to change your password after logging in.',
'environment_settings_saved' => 'Saved environment settings successfully',
'user_created_successfully' => 'New user was created successfully',
'user_updated_successfully' => 'User data successfully updated',
'user_removed_successfully' => 'The user account was removed successfully',
'confirm_user_removal' => 'Do you really want to remove this user account?'
];

View File

@@ -34,6 +34,7 @@ class UserModel_Migration {
$this->database->add('password_reset VARCHAR(1024) NULL');
$this->database->add('session VARCHAR(1024) NULL');
$this->database->add('status BOOLEAN NOT NULL DEFAULT 0');
$this->database->add('admin BOOLEAN NOT NULL DEFAULT 0');
$this->database->add('lang VARCHAR(512) NULL');
$this->database->add('chatcolor VARCHAR(512) NULL');
$this->database->add('show_log BOOLEAN NOT NULL DEFAULT 1');

View File

@@ -104,7 +104,7 @@ class UserModel extends \Asatru\Database\Model {
$mailobj = new Asatru\SMTPMailer\SMTPMailer();
$mailobj->setRecipient($email);
$mailobj->setSubject(__('app.reset_password'));
$mailobj->setView('mailreset', [], ['workspace' => env('APP_WORKSPACE'), 'token' => $reset_token]);
$mailobj->setView('mail/mailreset', [], ['workspace' => env('APP_WORKSPACE'), 'token' => $reset_token]);
$mailobj->send();
} catch (\Exception $e) {
throw $e;
@@ -147,6 +147,23 @@ class UserModel extends \Asatru\Database\Model {
}
}
/**
* @return bool
*/
public static function isCurrentlyAdmin()
{
try {
$user = static::getAuthUser();
if ((!$user) || (!$user->get('admin'))) {
return false;
}
return true;
} catch (\Exception $e) {
return null;
}
}
/**
* @return int
* @throws \Exception
@@ -373,6 +390,78 @@ class UserModel extends \Asatru\Database\Model {
}
}
/**
* @return mixed
*/
public static function getAll()
{
try {
return static::raw('SELECT * FROM `' . self::tableName() . '`');
} catch (\Exception $e) {
throw $e;
}
}
/**
* @param $name
* @param $email
* @return void
* @throws \Exception
*/
public static function createUser($name, $email)
{
try {
$password = substr(md5(random_bytes(55) . date('Y-m-d H:i:s')), 0, 10);
static::raw('INSERT INTO `' . self::tableName() . '` (name, email, password) VALUES(?, ?, ?)', [
$name, $email, password_hash($password, PASSWORD_BCRYPT)
]);
$mailobj = new Asatru\SMTPMailer\SMTPMailer();
$mailobj->setRecipient($email);
$mailobj->setSubject(__('app.account_created'));
$mailobj->setView('mail/mailacccreated', [], ['workspace' => env('APP_WORKSPACE'), 'password' => $password]);
$mailobj->send();
} catch (\Exception $e) {
throw $e;
}
}
/**
* @param $id
* @param $name
* @param $email
* @param $admin
* @return void
* @throws \Exception
*/
public static function updateUser($id, $name, $email, $admin)
{
try {
static::raw('UPDATE `' . self::tableName() . '` SET name = ?, email = ?, admin = ? WHERE id = ?', [
$name, $email, $admin, $id
]);
} catch (\Exception $e) {
throw $e;
}
}
/**
* @param $id
* @return void
* @throws \Exception
*/
public static function removeUser($id)
{
try {
static::raw('DELETE FROM `' . self::tableName() . '` WHERE id = ?', [
$id
]);
} catch (\Exception $e) {
throw $e;
}
}
/**
* Return the associated table name of the migration
*

View File

@@ -256,4 +256,52 @@ class UtilsModule {
{
return ($dt !== null) && (Carbon::parse($dt)->diffInSeconds() <= self::TYPING_SECONDS);
}
/**
* @param $workspace
* @param $lang
* @param $scroller
* @param $onlinetimelimit
* @param $chatonlineusers
* @param $chattypingindicator
*/
public static function saveEnvironment($workspace, $lang, $scroller, $onlinetimelimit, $chatonlineusers, $chattypingindicator)
{
$new_env_settings = [
'APP_WORKSPACE' => $workspace,
'APP_LANG' => $lang,
'APP_ENABLESCROLLER' => $scroller,
'APP_ONLINEMINUTELIMIT' => $onlinetimelimit,
'APP_SHOWCHATONLINEUSERS' => $chatonlineusers,
'APP_SHOWCHATTYPINGINDICATOR' => $chattypingindicator,
];
foreach ($new_env_settings as $key => $value) {
if (isset($_ENV[$key])) {
$_ENV[$key] = $value;
}
}
$env_content = "# Automatically generated at " . date('Y-m-d H:i:s') . "\n";
foreach ($_ENV as $key => $value) {
if (gettype($value) === 'boolean') {
$env_content .= $key . '=' . (($value) ? "true" : "false");
} else if (gettype($value) === 'double') {
$env_content .= $key . '=' . $value;
} else if (gettype($value) === 'integer') {
$env_content .= $key . '=' . $value;
} else if (gettype($value) === 'string') {
$env_content .= $key . '="' . $value . '"';
} else if ($value === null) {
$env_content .= $key . '=null';
} else {
$env_content .= $key . '=' . $value;
}
$env_content .= "\n";
}
file_put_contents(base_path() . '/.env', $env_content);
}
}

View File

@@ -33,6 +33,7 @@ window.vue = new Vue({
bShowEditInventoryItem: false,
bShowManageGroups: false,
bShowRestorePassword: false,
bShowCreateNewUser: false,
comboLocation: [],
comboCuttingMonth: [],
comboLightLevel: [],

View File

@@ -1143,4 +1143,79 @@ a.navbar-burger:hover {
.reset-form input[type=submit] {
width: 100%;
}
.admin-environment {
position: relative;
}
.admin-environment h2 {
margin-top: 20px;
margin-bottom: 10px;
}
.admin-environment label, .admin-environment span {
color: rgb(150, 150, 150);
}
.admin-environment input, .admin-environment select {
color: rgb(150, 150, 150);
background-color: rgb(50, 50, 50);
}
.admin-users {
position: relative;
width: 100%;
}
.admin-users h2 {
margin-top: 20px;
margin-bottom: 20px;
}
.admin-users-list {
position: relative;
}
.admin-user-account {
position: relative;
margin-bottom: 15px;
}
.admin-user-account label, .admin-user-account span {
color: rgb(150, 150, 150);
}
.admin-user-account input, .admin-user-account select {
color: rgb(150, 150, 150);
background-color: rgb(50, 50, 50);
}
.admin-user-account-item {
position: relative;
display: inline-block;
margin-left: 5px;
margin-right: 5px;
}
.admin-user-account-item-input {
width: 34%;
}
.admin-user-account-actions {
position: relative;
display: inline-block;
}
.admin-user-account-item-centered {
text-align: center;
}
.admin-user-account-action-item {
position: relative;
}
.admin-users-actions {
position: relative;
margin-top: 20px;
}

107
app/views/admin.php Normal file
View File

@@ -0,0 +1,107 @@
<div class="columns">
<div class="column is-2"></div>
<div class="column is-8 is-image-container" style="background-image: url('{{ asset('img/plants.jpg') }}');">
<div class="column-overlay">
<h1>{{ __('app.admin_area') }}</h1>
@include('flashmsg.php')
<div class="admin-environment">
<h2>{{ __('app.environment') }}</h2>
<form method="POST" action="{{ url('/admin/environment/save') }}">
@csrf
<div class="field">
<label class="label">{{ __('app.workspace') }}</label>
<div class="control">
<input type="text" class="input" name="workspace" value="{{ env('APP_WORKSPACE') }}">
</div>
</div>
<div class="field">
<label class="label">{{ __('app.language') }}</label>
<div class="control">
<select class="input" name="lang">
@foreach (UtilsModule::getLanguageList() as $lang)
<option value="{{ $lang }}" {{ (env('APP_LANG') === $lang) ? 'selected' : ''}}>{{ $lang }}</option>
@endforeach
</select>
</div>
</div>
<div class="field">
<div class="control">
<input type="checkbox" class="checkbox" name="scroller" value="1" {{ (env('APP_ENABLESCROLLER')) ? 'checked': '' }}>&nbsp;<span>{{ __('app.enable_scroller') }}</span>
</div>
</div>
<div class="field">
<label class="label">{{ __('app.online_time_limit') }}</label>
<div class="control">
<input type="number" class="input" name="onlinetimelimit" value="{{ env('APP_ONLINEMINUTELIMIT') }}" required>
</div>
</div>
<div class="field">
<div class="control">
<input type="checkbox" class="checkbox" name="chatonlineusers" value="1" {{ (env('APP_SHOWCHATONLINEUSERS')) ? 'checked': '' }}>&nbsp;<span>{{ __('app.show_chat_onlineusers') }}</span>
</div>
</div>
<div class="field">
<div class="control">
<input type="checkbox" class="checkbox" name="chattypingindicator" value="1" {{ (env('APP_SHOWCHATTYPINGINDICATOR')) ? 'checked': '' }}>&nbsp;<span>{{ __('app.show_chat_typingindicator') }}</span>
</div>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button is-success" value="{{ __('app.save') }}"/>
</div>
</div>
</form>
</div>
<div class="admin-users">
<h2>{{ __('app.users') }}</h2>
<div class="admin-users-list">
@foreach ($user_accounts as $user_account)
<div class="admin-user-account">
<form method="POST" action="{{ url('/admin/user/update') }}">
@csrf
<input type="hidden" name="id" value="{{ $user_account->get('id') }}"/>
<div class="admin-user-account-item admin-user-account-item-input">
<input type="text" class="input" name="name" value="{{ $user_account->get('name') }}"/>
</div>
<div class="admin-user-account-item admin-user-account-item-input">
<input type="email" class="input" name="email" value="{{ $user_account->get('email') }}"/>
</div>
<div class="admin-user-account-item admin-user-account-item-centered">
<input type="checkbox" name="admin" value="1" {{ ($user_account->get('admin')) ? 'checked' : '' }}/>&nbsp;<span>{{ __('app.admin') }}</span>
</div>
<div class="admin-user-account-actions">
<span class="admin-user-account-action-item"><input type="submit" class="button is-success" value="{{ __('app.update') }}"/></span>
<span class="admin-user-account-action-item"><a class="button is-danger" href="javascript:void(0);" onclick="if (confirm('{{ __('app.confirm_user_removal') }}')) { location.href = '{{ url('/admin/user/remove?id=' . $user_account->get('id')) }}'; }">{{ __('app.remove') }}</a></span>
</div>
</form>
</div>
@endforeach
</div>
<div class="admin-users-actions">
<span><a class="button is-info" href="javascript:void(0);" onclick="window.vue.bShowCreateNewUser = true;">{{ __('app.create') }}</a></span>
</div>
</div>
</div>
</div>
<div class="column is-2"></div>
</div>

View File

@@ -76,7 +76,7 @@
<button class="delete" aria-label="close" onclick="window.vue.bShowRestorePassword = false;"></button>
</header>
<section class="modal-card-body is-stretched">
<form id="frmEditText" method="POST" action="{{ url('/password/restore') }}">
<form id="frmRestorePassword" method="POST" action="{{ url('/password/restore') }}">
@csrf
<div class="field">
@@ -87,7 +87,7 @@
</form>
</section>
<footer class="modal-card-foot is-stretched">
<button class="button is-success" onclick="this.innerHTML = '<i class=\'fas fa-spinner fa-spin\'></i>&nbsp;{{ __('app.loading_please_wait') }}'; document.getElementById('frmEditText').submit();">{{ __('app.restore_password') }}</button>
<button class="button is-success" onclick="this.innerHTML = '<i class=\'fas fa-spinner fa-spin\'></i>&nbsp;{{ __('app.loading_please_wait') }}'; document.getElementById('frmRestorePassword').submit();">{{ __('app.restore_password') }}</button>
<button class="button" onclick="window.vue.bShowRestorePassword = false;">{{ __('app.cancel') }}</button>
</footer>
</div>

View File

@@ -645,6 +645,39 @@
</div>
</div>
<div class="modal" :class="{'is-active': bShowCreateNewUser}">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head is-stretched">
<p class="modal-card-title">{{ __('app.create_user') }}</p>
<button class="delete" aria-label="close" onclick="window.vue.bShowCreateNewUser = false;"></button>
</header>
<section class="modal-card-body is-stretched">
<form id="frmCreateNewUser" method="POST" action="{{ url('/admin/user/create') }}">
@csrf
<div class="field">
<label class="label">{{ __('app.name') }}</label>
<div class="control">
<input type="text" class="input" name="name" required>
</div>
</div>
<div class="field">
<label class="label">{{ __('app.email') }}</label>
<div class="control">
<input type="email" class="input" name="email" required>
</div>
</div>
</form>
</section>
<footer class="modal-card-foot is-stretched">
<button class="button is-success" onclick="this.innerHTML = '<i class=\'fas fa-spinner fa-spin\'></i>&nbsp;{{ __('app.loading_please_wait') }}'; document.getElementById('frmCreateNewUser').submit();">{{ __('app.create') }}</button>
<button class="button" onclick="window.vue.bShowCreateNewUser = false;">{{ __('app.cancel') }}</button>
</footer>
</div>
</div>
@include('scroller.php')
</div>

View File

@@ -0,0 +1,20 @@
<!doctype html>
<html lang="{{ getLocale() }}">
<head>
<meta charset="utf-8"/>
<title>{{ __('app.account_created') }}</title>
</head>
<body>
<h1>{{ __('app.account_created') }}</h1>
<p>
{!! __('app.account_created_hint', ['workspace' => $workspace, 'url' => url('/auth'), 'password' => $password]) !!}
</p>
<p>
<small>Powered by {{ env('APP_NAME') }}</small>
</p>
</body>
</html>

View File

@@ -12,5 +12,9 @@
<p>
{!! __('app.reset_password_hint', ['workspace' => $workspace, 'url' => url('/password/reset?token=' . $token)]) !!}
</p>
<p>
<small>Powered by {{ env('APP_NAME') }}</small>
</p>
</body>
</html>

View File

@@ -59,6 +59,14 @@
</a>
</div>
@if (UserModel::isCurrentlyAdmin())
<div class="navbar-item">
<a href="{{ url('/admin') }}">
<i class="fas fa-cog" title="{{ __('app.admin_area') }}"></i><span class="navbar-item-only-mobile">&nbsp;{{ __('app.admin_area') }}</span>
</a>
</div>
@endif
<div class="navbar-item">
<a href="{{ url('/logout') }}">
<i class="fas fa-sign-out-alt" title="{{ __('app.logout') }}"></i><span class="navbar-item-only-mobile">&nbsp;{{ __('app.logout') }}</span>

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long