Offer opt in to usage and error reporting during install (#13906)

and on the about page
This commit is contained in:
Tony Murray
2022-12-15 19:52:22 -06:00
committed by GitHub
parent 510f9d340d
commit 8ea3f5cd06
19 changed files with 336 additions and 192 deletions

View File

@@ -25,6 +25,7 @@
namespace LibreNMS; namespace LibreNMS;
use App\Models\Callback;
use App\Models\GraphType; use App\Models\GraphType;
use Exception; use Exception;
use Illuminate\Database\QueryException; use Illuminate\Database\QueryException;
@@ -472,6 +473,9 @@ class Config
if (! self::has('snmp.unescape')) { if (! self::has('snmp.unescape')) {
self::persist('snmp.unescape', version_compare(Version::get()->netSnmp(), '5.8.0', '<')); self::persist('snmp.unescape', version_compare(Version::get()->netSnmp(), '5.8.0', '<'));
} }
if (! self::has('reporting.usage')) {
self::persist('reporting.usage', (bool) Callback::get('enabled'));
}
self::populateTime(); self::populateTime();

View File

@@ -50,6 +50,7 @@ use App\Models\Syslog;
use App\Models\Vlan; use App\Models\Vlan;
use App\Models\Vrf; use App\Models\Vrf;
use App\Models\WirelessSensor; use App\Models\WirelessSensor;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use LibreNMS\Config; use LibreNMS\Config;
use LibreNMS\Data\Store\Rrd; use LibreNMS\Data\Store\Rrd;
@@ -59,12 +60,12 @@ class AboutController extends Controller
{ {
public function index(Request $request) public function index(Request $request)
{ {
$callback_status = Callback::get('enabled') === '1';
$version = Version::get(); $version = Version::get();
return view('about.index', [ return view('about.index', [
'callback_status' => $callback_status, 'usage_reporting_status' => Config::get('reporting.usage'),
'callback_uuid' => $callback_status ? Callback::get('uuid') : null, 'error_reporting_status' => Config::get('reporting.error'),
'reporting_clearable' => Callback::whereIn('name', ['uuid', 'error_reporting_uuid'])->exists(),
'db_schema' => $version->database(), 'db_schema' => $version->database(),
'git_log' => $version->git->log(), 'git_log' => $version->git->log(),
@@ -105,4 +106,21 @@ class AboutController extends Controller
'stat_wireless' => WirelessSensor::count(), 'stat_wireless' => WirelessSensor::count(),
]); ]);
} }
public function clearReportingData(): JsonResponse
{
$usage_uuid = Callback::get('uuid');
// try to clear usage data if we have a uuid
if ($usage_uuid) {
if (! \Http::post(Config::get('callback_clear'), ['uuid' => $usage_uuid])->successful()) {
return response()->json([], 500); // don't clear if this fails to delete upstream data
}
}
// clear all reporting ids
Callback::truncate();
return response()->json();
}
} }

View File

@@ -26,9 +26,12 @@
namespace App\Http\Controllers\Install; namespace App\Http\Controllers\Install;
use Exception; use Exception;
use Illuminate\Http\Request;
use LibreNMS\Config;
use LibreNMS\Exceptions\FileWriteFailedException; use LibreNMS\Exceptions\FileWriteFailedException;
use LibreNMS\Interfaces\InstallerStep; use LibreNMS\Interfaces\InstallerStep;
use LibreNMS\Util\EnvHelper; use LibreNMS\Util\EnvHelper;
use LibreNMS\Util\Git;
class FinalizeController extends InstallationController implements InstallerStep class FinalizeController extends InstallationController implements InstallerStep
{ {
@@ -40,6 +43,29 @@ class FinalizeController extends InstallationController implements InstallerStep
return $this->redirectToIncomplete(); return $this->redirectToIncomplete();
} }
return view('install.finish', $this->formatData([
'can_update' => Git::make()->isAvailable(),
'success' => '',
'env' => '',
'config' => '',
'messages' => '',
'env_message' => '',
'config_message' => '',
]));
}
public function saveConfig(Request $request): \Illuminate\Http\JsonResponse
{
$request->validate([
'update_channel' => 'in:master,release',
'site_style' => 'in:light,dark',
]);
$this->saveSetting('update_channel', $request->get('update_channel', 'master'));
$this->saveSetting('site_style', $request->get('site_style'));
$this->saveSetting('reporting.error', $request->has('error_reporting'));
$this->saveSetting('reporting.usage', $request->has('usage_reporting'));
$env = ''; $env = '';
$config = ''; $config = '';
$config_file = base_path('config.php'); $config_file = base_path('config.php');
@@ -65,14 +91,14 @@ class FinalizeController extends InstallationController implements InstallerStep
$env_message = trans('install.finish.env_not_written'); $env_message = trans('install.finish.env_not_written');
} }
return view('install.finish', $this->formatData([ return response()->json([
'success' => $success, 'success' => $success,
'env' => $env, 'env' => $env,
'config' => $config, 'config' => $config,
'messages' => $messages, 'messages' => $messages,
'env_message' => $env_message, 'env_message' => $env_message,
'config_message' => $config_message, 'config_message' => $config_message,
])); ]);
} }
private function writeEnvFile() private function writeEnvFile()
@@ -138,6 +164,18 @@ class FinalizeController extends InstallationController implements InstallerStep
); );
} }
/**
* @param string $name
* @param mixed $value
* @return void
*/
private function saveSetting(string $name, $value): void
{
if (Config::get($name) !== $value) {
Config::persist($name, $value);
}
}
public function enabled(): bool public function enabled(): bool
{ {
foreach ($this->hydrateControllers() as $step => $controller) { foreach ($this->hydrateControllers() as $step => $controller) {

View File

@@ -60,8 +60,8 @@ class AppServiceProvider extends ServiceProvider
private function bootCustomBladeDirectives() private function bootCustomBladeDirectives()
{ {
Blade::if('config', function ($key) { Blade::if('config', function ($key, $value = true) {
return \LibreNMS\Config::get($key); return \LibreNMS\Config::get($key) == $value;
}); });
Blade::if('notconfig', function ($key) { Blade::if('notconfig', function ($key) {
return ! \LibreNMS\Config::get($key); return ! \LibreNMS\Config::get($key);

View File

@@ -1,21 +0,0 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version. Please see LICENSE.txt at the top level of
* the source code distribution for details.
*/
header('Content-type: text/plain');
if (! Auth::user()->hasGlobalAdmin()) {
exit('ERROR: You need to be admin');
}
\App\Models\Callback::set('enabled', '2');

View File

@@ -1,21 +0,0 @@
<?php
/*
* LibreNMS
*
* Copyright (c) 2014 Neil Lathwood <https://github.com/laf/ http://www.lathwood.co.uk/fa>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version. Please see LICENSE.txt at the top level of
* the source code distribution for details.
*/
header('Content-type: text/plain');
if (! Auth::user()->hasGlobalAdmin()) {
exit('ERROR: You need to be admin');
}
\App\Models\Callback::set('enabled', (int) ($_POST['state'] == 'true'));

View File

@@ -4866,6 +4866,16 @@
"type": "boolean" "type": "boolean"
}, },
"reporting.dump_errors": { "reporting.dump_errors": {
"group": "system",
"section": "reporting",
"order": 1,
"default": false,
"type": "boolean"
},
"reporting.usage": {
"group": "system",
"section": "reporting",
"order": 0,
"default": false, "default": false,
"type": "boolean" "type": "boolean"
}, },

View File

@@ -737,8 +737,8 @@ return [
'update_channel' => [ 'update_channel' => [
'description' => 'Definiere Updatekanal', 'description' => 'Definiere Updatekanal',
'options' => [ 'options' => [
'master' => 'master', 'master' => 'Daily',
'release' => 'release', 'release' => 'Monthly',
], ],
], ],
'virsh' => [ 'virsh' => [

View File

@@ -28,18 +28,19 @@ return [
'config_not_written' => 'Could not write config.php', 'config_not_written' => 'Could not write config.php',
'config_written' => 'config.php file written', 'config_written' => 'config.php file written',
'copied' => 'Copied to clipboard', 'copied' => 'Copied to clipboard',
'dashboard' => 'Dashboard',
'env_manual' => 'Manually update :file with the following content', 'env_manual' => 'Manually update :file with the following content',
'env_not_written' => 'Could not write .env file', 'env_not_written' => 'Could not write .env file',
'env_written' => '.env file written', 'env_written' => '.env file written',
'failed' => 'Failed to save .env',
'finish' => 'Finish Install',
'manual_copy' => 'Press Ctrl-C to copy', 'manual_copy' => 'Press Ctrl-C to copy',
'not_finished' => 'You have not quite finished yet!',
'retry' => 'Retry', 'retry' => 'Retry',
'statistics' => 'It would be great if you would consider contributing to our statistics, you can do this on the :about and check the box under Statistics.', 'settings' => 'Additional Settings',
'statistics_link' => 'About LibreNMS Page', 'success' => 'Install Complete',
'thanks' => 'Thank you for setting up LibreNMS.', 'thanks' => 'Thank you for setting up LibreNMS.',
'title' => 'Finish Install', 'title' => 'Finish Install',
'validate' => 'First, you need to :validate and fix any issues.', 'validate_button' => 'Validate Install',
'validate_link' => 'validate your install',
], ],
'install' => 'Install', 'install' => 'Install',
'migrate' => [ 'migrate' => [

View File

@@ -72,6 +72,7 @@ return [
'proxy' => ['name' => 'Proxy'], 'proxy' => ['name' => 'Proxy'],
'updates' => ['name' => 'Updates'], 'updates' => ['name' => 'Updates'],
'server' => ['name' => 'Server'], 'server' => ['name' => 'Server'],
'reporting' => ['name' => 'Reporting'],
], ],
'webui' => [ 'webui' => [
'availability-map' => ['name' => 'Availability Map Settings'], 'availability-map' => ['name' => 'Availability Map Settings'],
@@ -1250,6 +1251,16 @@ return [
'help' => 'Networks/IPs which will not be discovered automatically. Excludes also IPs from Autodiscovery Networks', 'help' => 'Networks/IPs which will not be discovered automatically. Excludes also IPs from Autodiscovery Networks',
], ],
], ],
'reporting' => [
'error' => [
'description' => 'Send Error Reports',
'help' => 'Sends some errors to LibreNMS for analysis and fixing',
],
'usage' => [
'description' => 'Send Usage Reports',
'help' => 'Reports usage and versions to LibreNMS. To delete anonymous stats, visit the about page. You can view stats at https://stats.librenms.org',
],
],
'route_purge' => [ 'route_purge' => [
'description' => 'Route entries older than', 'description' => 'Route entries older than',
'help' => 'Cleanup done by daily.sh', 'help' => 'Cleanup done by daily.sh',
@@ -1381,7 +1392,7 @@ return [
'help' => 'Shrinks hostname to maximum length, but always complete subdomain parts', 'help' => 'Shrinks hostname to maximum length, but always complete subdomain parts',
], ],
'site_style' => [ 'site_style' => [
'description' => 'Set the site css style', 'description' => 'Default Theme',
'options' => [ 'options' => [
'blue' => 'Blue', 'blue' => 'Blue',
'dark' => 'Dark', 'dark' => 'Dark',
@@ -1511,10 +1522,10 @@ return [
'description' => 'Enable updates in ./daily.sh', 'description' => 'Enable updates in ./daily.sh',
], ],
'update_channel' => [ 'update_channel' => [
'description' => 'Set update Channel', 'description' => 'Update Channel',
'options' => [ 'options' => [
'master' => 'master', 'master' => 'Daily',
'release' => 'release', 'release' => 'Monthly',
], ],
], ],
'uptime_warning' => [ 'uptime_warning' => [

View File

@@ -1077,8 +1077,8 @@ return [
'update_channel' => [ 'update_channel' => [
'description' => 'Choisir le canal des mises à jour', 'description' => 'Choisir le canal des mises à jour',
'options' => [ 'options' => [
'master' => 'master', 'master' => 'Daily',
'release' => 'release', 'release' => 'Monthly',
], ],
], ],
'uptime_warning' => [ 'uptime_warning' => [

View File

@@ -1491,8 +1491,8 @@ return [
'update_channel' => [ 'update_channel' => [
'description' => 'Set update Channel', 'description' => 'Set update Channel',
'options' => [ 'options' => [
'master' => 'master', 'master' => 'Daily',
'release' => 'release', 'release' => 'Monthly',
], ],
], ],
'uptime_warning' => [ 'uptime_warning' => [

View File

@@ -1482,8 +1482,8 @@ return [
'update_channel' => [ 'update_channel' => [
'description' => 'Визначити канал оновлень', 'description' => 'Визначити канал оновлень',
'options' => [ 'options' => [
'master' => 'master', 'master' => 'Daily',
'release' => 'release', 'release' => 'Monthly',
], ],
], ],
'uptime_warning' => [ 'uptime_warning' => [

View File

@@ -752,8 +752,8 @@ return [
'update_channel' => [ 'update_channel' => [
'description' => '设定更新频道', 'description' => '设定更新频道',
'options' => [ 'options' => [
'master' => 'master', 'master' => 'Daily',
'release' => 'release', 'release' => 'Monthly',
], ],
], ],
'virsh' => [ 'virsh' => [

View File

@@ -925,8 +925,8 @@ return [
'update_channel' => [ 'update_channel' => [
'description' => '設定更新頻道', 'description' => '設定更新頻道',
'options' => [ 'options' => [
'master' => 'master', 'master' => 'Daily',
'release' => 'release', 'release' => 'Monthly',
], ],
], ],
'uptime_warning' => [ 'uptime_warning' => [

View File

@@ -90,29 +90,29 @@
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<h3>{{ __('Statistics') }}</h3> <h3>{{ __('Reporting & Statistics') }}</h3>
<table class='table table-condensed'> <table class='table table-condensed'>
@admin @admin
<tr> <tr>
<td colspan='4'> <td colspan='4'>
<span class='bg-danger'> <div>
<label for="callback">{{ __('Opt in to send anonymous usage statistics to LibreNMS?') }}</label><br /> <label for="reporting.usage" class="bg-info">{{ __('Opt in to send anonymous reports to LibreNMS?') }}</label>
</span> </div>
<input type="checkbox" id="callback" data-size="normal" name="statistics" @if($callback_status) checked @endif> <div>
<br /> {{ __('Error reporting:') }} <input type="checkbox" id="reporting.error" name="reporting" data-size="small" @if($error_reporting_status) checked @endif>
{{ __('Online stats:') }} <a target="_blank" href='https://stats.librenms.org/'>stats.librenms.org</a> </div>
<div class="tw-mt-2">
{{ __('Usage statistics:') }} <input type="checkbox" id="reporting.usage" name="reporting" data-size="small" @if($usage_reporting_status) checked @endif> <a target="_blank" href='https://stats.librenms.org/'>stats.librenms.org</a>
</div>
@if($reporting_clearable)
<div class="tw-mt-2">
<button class='btn btn-danger btn-xs' type='submit' name='clear-reporting' id='clear-reporting'>{{ __('Clear reporting data') }}</button>
</div>
@endif
</td> </td>
</tr> </tr>
@isset($callback_uuid)
<tr>
<td colspan='4'>
<button class='btn btn-danger btn-xs' type='submit' name='clear-stats' id='clear-stats'>{{ __('Clear remote stats') }}</button>
</td>
</tr>
@endisset
@endadmin @endadmin
<tr> <tr>
@@ -202,29 +202,29 @@ along with this program. If not, see <a target="_blank" href="https://www.gnu.o
@section('scripts') @section('scripts')
<script> <script>
$("[name='statistics']").bootstrapSwitch('offColor','danger','size','mini'); $("[name='reporting']").bootstrapSwitch('offColor','danger','size','mini');
$('input[name="statistics"]').on('switchChange.bootstrapSwitch', function(event, state) { $('input[name="reporting"]').on('switchChange.bootstrapSwitch', function(event, state) {
event.preventDefault(); event.preventDefault();
const type = event.target.id;
$.ajax({ $.ajax({
type: 'POST', type: 'PUT',
url: 'ajax_form.php', url: '{{ route('settings.update', '?') }}'.replace('?', type),
data: { type: "callback-statistics", state: state}, data: JSON.stringify({value: state}),
dataType: "json", contentType: "application/json",
success: function(data){}, success: function(data){},
error:function(){ error:function(){
return $("#switch-state").bootstrapSwitch("toggle"); return $("#" + type).bootstrapSwitch("toggle");
} }
}); });
}); });
$('#clear-stats').on("click", function(event) { $('#clear-reporting').on("click", function(event) {
event.preventDefault(); event.preventDefault();
$.ajax({ $.ajax({
type: 'POST', type: 'DELETE',
url: 'ajax_form.php', url: '{{ route('reporting.clear') }}',
data: { type: "callback-clear"}, success: function(){
dataType: "json", $('#clear-reporting').remove();
success: function(data){ $("#callback").bootstrapSwitch('state', false);
location.reload(true);
}, },
error:function(){} error:function(){}
}); });

View File

@@ -20,7 +20,7 @@
@endif @endif
</span> </span>
{{ __('install.database.credentials') }} {{ __('install.database.credentials') }}
<span class="fa-pull-right"><i class="fa-solid fa-lg fa-chevron-down rotate-if-collapsed"></i></span> <span class="float-right"><i class="fa-solid fa-lg fa-chevron-down rotate-if-collapsed"></i></span>
</div> </div>
<div id="db-form-container" class="card-body collapse @if(!$valid_credentials) show @endif"> <div id="db-form-container" class="card-body collapse @if(!$valid_credentials) show @endif">
<form id="database-form" class="form-horizontal" role="form" method="post" action="{{ route('install.acton.test-database') }}"> <form id="database-form" class="form-horizontal" role="form" method="post" action="{{ route('install.acton.test-database') }}">
@@ -90,7 +90,7 @@
@endif @endif
</span> </span>
{{ __('install.migrate.migrate') }} {{ __('install.migrate.migrate') }}
<span class="fa-pull-right"><i class="fa-solid fa-lg fa-chevron-down rotate-if-collapsed"></i></span> <span class="float-right"><i class="fa-solid fa-lg fa-chevron-down rotate-if-collapsed"></i></span>
</div> </div>
<div id="migrate-container" class="card-body collapse @if(!$migrated) show @endif"> <div id="migrate-container" class="card-body collapse @if(!$migrated) show @endif">
<div class="row"> <div class="row">

View File

@@ -1,104 +1,206 @@
@extends('layouts.install') @extends('layouts.install')
@section('content') @section('content')
<div class="card mb-2"> <div class="card mb-2">
<div class="card-header h6" data-toggle="collapse" data-target="#env-file-text" aria-expanded="{{ $success ? 'false' : 'true' }}"> <div class="card-header h6">
@if($success) {{ __('install.finish.settings') }}
<i class="fa-solid fa-lg fa-square-check text-success"></i>
@else
<i class="fa-solid fa-lg fa-rectangle-xmark text-danger"></i>
@endif
{{ $env_message }}
@if($env)<i class="fa-solid fa-lg fa-chevron-down rotate-if-collapsed pull-right"></i>@endif($env)
</div>
@if($env)
<div id="env-file-text" class="card-body collapse @if(!$success) show @endif">
<button class="btn btn-primary float-right" onclick="location.reload()">{{ __('install.finish.retry') }}</button>
<strong>
{{ __('install.finish.env_manual', ['file' => base_path('.env')]) }}
</strong>
<div class="text-right mt-3">
<button
class="btn btn-sm btn-light text-muted copy-btn"
data-clipboard-target="#env-content"
data-toggle="tooltip"
data-placement="bottom"
data-trigger="manual"
data-title="{{ __('install.finish.copied') }}"
>
<i class="fa-solid fa-clipboard"></i>
</button>
</div> </div>
<pre id="env-content" class="card bg-light p-3">{{ $env }}</pre> <div class="card-body">
</div> <form id="settings">
@endif @if($can_update)
</div> <div class="mb-3">
<div class="card mb-2"> <span>{{ __('settings.settings.update_channel.description') }}</span>
<div class="card-header h6" data-toggle="collapse" data-target="#config-file-text" aria-expanded="false"> <div class="custom-control custom-radio custom-control-inline">
<i class="fa-solid fa-lg fa-square-check text-success"></i> <input type="radio" id="update_channel_daily" name="update_channel" class="custom-control-input" value="master" @config('update_channel', 'master') checked @endconfig>
{{ $config_message }} <label class="custom-control-label" for="update_channel_daily">{{ __('settings.settings.update_channel.options.master') }}</label>
@if($config)<i class="fa-solid fa-lg fa-chevron-down rotate-if-collapsed pull-right"></i>@endif </div>
</div> <div class="custom-control custom-radio custom-control-inline">
@if($config) <input type="radio" id="update_channel_monthly" name="update_channel" class="custom-control-input" value="release" @config('update_channel', 'release') checked @endconfig>
<div id="config-file-text" class="card-body collapse"> <label class="custom-control-label" for="update_channel_monthly">{{ __('settings.settings.update_channel.options.release') }}</label>
<strong> </div>
{{ __('install.finish.config_not_required') }} </div>
</strong> @endif
<div class="text-right mt-3">
<button <div class="mb-3">
class="btn btn-sm btn-light text-muted copy-btn" <span>{{ __('settings.settings.site_style.description') }}</span>
data-clipboard-target="#config-content" <div class="custom-control custom-radio custom-control-inline">
data-toggle="tooltip" <input type="radio" id="site_style_light" name="site_style" class="custom-control-input" value="light" @config('site_style', 'light') checked @endconfig>
data-placement="bottom" <label class="custom-control-label" for="site_style_light">{{ __('settings.settings.site_style.options.light') }}</label>
data-trigger="manual" </div>
data-title="{{ __('install.finish.copied') }}" <div class="custom-control custom-radio custom-control-inline">
> <input type="radio" id="site_style_dark" name="site_style" class="custom-control-input" value="dark" @config('site_style', 'dark') checked @endconfig>
<i class="fa-solid fa-clipboard"></i> <label class="custom-control-label" for="site_style_dark">{{ __('settings.settings.site_style.options.dark') }}</label>
</button> </div>
</div> </div>
<pre id="config-content" class="card bg-light p-3">{{ $config }}</pre>
</div> <div class="custom-control custom-checkbox mb-3">
@endif <input type="checkbox" class="custom-control-input" id="usage_reporting" name="usage_reporting" @config('reporting.usage') checked @endconfig>
</div> <label class="custom-control-label" for="usage_reporting"><a target="_blank" href="https://stats.librenms.org/">{{ __('settings.settings.reporting.usage.description') }}</a></label>
@if($success) </div>
<div class="row">
<div class="col-12"> <div class="custom-control custom-checkbox mb-3">
<div class="alert alert-warning"> <input type="checkbox" class="custom-control-input" id="error_reporting" name="error_reporting" @config('reporting.error') checked @endconfig>
<p>{{ __('install.finish.not_finished') }}</p> <label class="custom-control-label" for="error_reporting">{{ __('settings.settings.reporting.error.description') }}</label>
<p> </div>
{{ explode('|', __('install.finish.validate', ['validate' => '|']), 2)[0] }}
<a href="{{ url('validate') }}">{{ __('install.finish.validate_link') }}</a> <div>
{{ explode('|', __('install.finish.validate', ['validate' => '|']), 2)[1] }} <button type="button" class="btn btn-primary finalize-buttons">{{ __('install.finish.finish') }}</button>
</p> </div>
</form>
</div> </div>
</div> </div>
</div> <div id="finished" class="modal" tabindex="-1">
<div class="row"> <div class="modal-dialog">
<div class="col-12"> <div class="modal-content">
<div class="alert alert-success"> <div class="modal-header">
<p>{{ __('install.finish.thanks') }}</p> <h5 class="modal-title"></h5>
{{ explode('|', __('install.finish.statistics', ['about' => '|']), 2)[0] }} <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<a href="{{ url('about') }}">{{ __('install.finish.statistics_link') }}</a> <span aria-hidden="true">&times;</span>
{{ explode('|', __('install.finish.statistics', ['about' => '|']), 2)[1] }} </button>
</div>
<div class="modal-body">
<div class="card mb-2">
<div id="env-header" class="card-header h6" data-toggle="collapse" data-target="#env-file-text" aria-expanded="false">
<i id="env-icon" class="fa-solid fa-lg"></i>
<span id="env-message"></span>
<span id="env-chevron" class="float-right"><i class="fa-solid fa-lg fa-chevron-down rotate-if-collapsed"></i></span>
</div>
<div id="env-file-text" class="card-body collapse">
<button class="btn btn-primary float-right finalize-buttons">{{ __('install.finish.retry') }}</button>
<strong>
{{ __('install.finish.env_manual', ['file' => base_path('.env')]) }}
</strong>
<div class="text-right mt-3">
<button
class="btn btn-sm btn-light text-muted copy-btn"
data-clipboard-target="#env-content"
data-toggle="tooltip"
data-placement="bottom"
data-trigger="manual"
data-title="{{ __('install.finish.copied') }}"
>
<i class="fa-solid fa-clipboard"></i>
</button>
</div>
<pre id="env-content" class="card bg-light p-3"></pre>
</div>
</div>
<div class="card mb-2">
<div class="card-header h6" data-toggle="collapse" data-target="#config-file-text" aria-expanded="false">
<i id="config-icon" class="fa-solid fa-lg"></i>
<span id="config-message"></span>
<span id="config-chevron" class="float-right"><i class="fa-solid fa-lg fa-chevron-down rotate-if-collapsed"></i></span>
</div>
<div id="config-file-text" class="card-body collapse">
<strong>
{{ __('install.finish.config_not_required') }}
</strong>
<div class="text-right mt-3">
<button
class="btn btn-sm btn-light text-muted copy-btn"
data-clipboard-target="#config-content"
data-toggle="tooltip"
data-placement="bottom"
data-trigger="manual"
data-title="{{ __('install.finish.copied') }}"
>
<i class="fa-solid fa-clipboard"></i>
</button>
</div>
<pre id="config-content" class="card bg-light p-3"></pre>
</div>
</div>
<div class="row" id="success-message">
<div class="col-12">
<div class="alert alert-success">
<i class="fa-solid fa-2x fa-heart" style="color: #ff4033;"></i>
<span class="h4 align-text-bottom">{{ __('install.finish.thanks') }}</span>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button id="modal-retry" type="button" class="btn btn-primary finalize-buttons">{{ __('install.finish.retry') }}</button>
<div id="modal-finished">
<a href="{{ route('home') }}">
<button type="button" class="btn btn-secondary">{{ __('install.finish.dashboard') }}</button>
</a>
<a href="{{ url('validate') }}">
<button type="button" class="btn btn-primary">{{ __('install.finish.validate_button') }}</button>
</a>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div>
@endif
@endsection @endsection
@section('scripts') @section('scripts')
<script> <script>
var clipboard = new ClipboardJS('.copy-btn'); $('.finalize-buttons').on('click', function (e) {
clipboard.on('success', function(e) { var data = $('#settings').serializeArray();
$(e.trigger).tooltip('show'); $.ajax('{{ route('install.finish.save') }}', {
setTimeout(() => $(e.trigger).tooltip('hide'), 2000); method: 'post',
headers: {'X-CSRF-TOKEN': '{{ csrf_token() }}'},
data: data
}).done((result) => {
if (result.success) {
$('#env-header').attr('aria-expanded', 'false');
$('#env-file-text').addClass('show');
$('#success-message').show();
$('.modal-title').text('{{ __('install.finish.success') }}')
$('#modal-retry').hide();
$('#modal-finished').show();
} else {
$('#env-header').attr('aria-expanded', 'true');
$('#env-file-text').removeClass('show');
$('#success-message').hide();
$('.modal-title').text('{{ __('install.finish.failed') }}')
$('#modal-retry').show();
$('#modal-finished').hide();
}
e.clearSelection(); $('#env-message').text(result.env_message);
}); $('#env-content').text(result.env);
if (result.env) {
$('#env-chevron').show();
$('#env-file-text').removeAttr('style').addClass('show');
$('#env-icon').removeClass(['fa-square-check', 'text-success']).addClass(['fa-rectangle-xmark', 'text-danger']);
} else {
$('#env-file-text').hide();
$('#env-chevron').hide();
$('#env-icon').addClass(['fa-square-check', 'text-success']).removeClass(['fa-rectangle-xmark', 'text-danger']);
}
clipboard.on('error', function(e) { $('#config-message').text(result.config_message);
$(e.trigger).data('title', '{{ __('install.finish.manual_copy') }}').tooltip('show'); $('#config-content').text(result.config);
setTimeout(() => $(e.trigger).tooltip('hide'), 2000); if (result.config) {
}); $('#config-file-text').removeAttr('style');
</script> $('#config-chevron').show();
$('#config-icon').removeClass(['fa-square-check', 'text-success']).addClass(['fa-rectangle-xmark', 'text-danger']);
} else {
$('#config-file-text').hide();
$('#config-chevron').hide();
$('#config-icon').addClass(['fa-square-check', 'text-success']).removeClass(['fa-rectangle-xmark', 'text-danger']);
}
$('#finished').modal('show')
}).fail(function (output) {
location.reload();
});
});
var clipboard = new ClipboardJS('.copy-btn');
clipboard.on('success', function (e) {
$(e.trigger).tooltip('show');
setTimeout(() => $(e.trigger).tooltip('hide'), 2000);
e.clearSelection();
});
clipboard.on('error', function (e) {
$(e.trigger).data('title', '{{ __('install.finish.manual_copy') }}').tooltip('show');
setTimeout(() => $(e.trigger).tooltip('hide'), 2000);
});
</script>
@endsection @endsection

View File

@@ -51,7 +51,8 @@ Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
Route::get('locations', 'LocationController@index'); Route::get('locations', 'LocationController@index');
Route::resource('preferences', 'UserPreferencesController', ['only' => ['index', 'store']]); Route::resource('preferences', 'UserPreferencesController', ['only' => ['index', 'store']]);
Route::resource('users', 'UserController'); Route::resource('users', 'UserController');
Route::get('about', 'AboutController@index'); Route::get('about', [\App\Http\Controllers\AboutController::class, 'index'])->name('about');
Route::delete('reporting', [\App\Http\Controllers\AboutController::class, 'clearReportingData'])->name('reporting.clear');
Route::get('authlog', 'UserController@authlog'); Route::get('authlog', 'UserController@authlog');
Route::get('overview', 'OverviewController@index')->name('overview'); Route::get('overview', 'OverviewController@index')->name('overview');
Route::get('/', 'OverviewController@index')->name('home'); Route::get('/', 'OverviewController@index')->name('home');
@@ -227,6 +228,7 @@ Route::group(['prefix' => 'install', 'namespace' => 'Install'], function () {
Route::get('/user', 'MakeUserController@index')->name('install.user'); Route::get('/user', 'MakeUserController@index')->name('install.user');
Route::get('/finish', 'FinalizeController@index')->name('install.finish'); Route::get('/finish', 'FinalizeController@index')->name('install.finish');
Route::post('/finish', 'FinalizeController@saveConfig')->name('install.finish.save');
Route::post('/user/create', 'MakeUserController@create')->name('install.action.user'); Route::post('/user/create', 'MakeUserController@create')->name('install.action.user');
Route::post('/database/test', 'DatabaseController@test')->name('install.acton.test-database'); Route::post('/database/test', 'DatabaseController@test')->name('install.acton.test-database');
Route::get('/ajax/database/migrate', 'DatabaseController@migrate')->name('install.action.migrate'); Route::get('/ajax/database/migrate', 'DatabaseController@migrate')->name('install.action.migrate');