ability to download log from admin

This commit is contained in:
Čarodej
2022-03-08 10:51:32 +01:00
parent 4e2155b75a
commit dd1f3b299d
52 changed files with 182 additions and 46 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
/**
* vee-validate v3.4.14
* (c) 2021 Abdelrahman Awad
* @license MIT
*/
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
"use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4738],{3187:(t,e,a)=>{a.d(e,{Z:()=>r});const n={name:"CardNavigation",props:["pages"],computed:{routeName:function(){return this.$route.name}},data:function(){return{fixedNav:!1}},created:function(){var t=this;window.addEventListener("scroll",(function(){var e=document.getElementById("card-navigation");t.fixedNav=e.getBoundingClientRect().top<0}))}};const r=(0,a(1900).Z)(n,(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"mb-7",staticStyle:{height:"62px"},attrs:{id:"card-navigation"}},[a("div",{class:{"fixed top-0 left-0 right-0 z-10 rounded-none bg-white bg-opacity-50 px-6 backdrop-blur-lg backdrop-filter dark:bg-dark-foreground":t.fixedNav}},[a("div",{staticClass:"overflow-x-auto whitespace-nowrap"},t._l(t.pages,(function(e,n){return a("router-link",{key:n,staticClass:"border-bottom-theme inline-block border-b-2 border-transparent px-4 py-5 text-sm font-bold",class:{"text-theme":t.routeName===e.route,"text-gray-600 dark:text-gray-100":t.routeName!==e.route},attrs:{to:{name:e.route},replace:""}},[t._v("\n "+t._s(e.title)+"\n ")])})),1)])])}),[],!1,null,null,null).exports},6534:(t,e,a)=>{a.r(e),a.d(e,{default:()=>r});const n={name:"AppSettings",components:{CardNavigation:a(3187).Z},data:function(){return{pages:[{title:this.$t("admin_settings.tabs.others"),route:"AppOthers"},{title:this.$t("Login & Registration"),route:"AppSignInUp"},{title:this.$t("admin_settings.tabs.appearance"),route:"AppAppearance"},{title:this.$t("Adsense"),route:"AppAdsense"},{title:this.$t("Homepage"),route:"AppIndex"},{title:this.$t("admin_settings.tabs.email"),route:"AppEmail"},{title:this.$t("Server"),route:"AppServer"}]}},mounted:function(){this.$router.push({name:"AppOthers"})}};const r=(0,a(1900).Z)(n,(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",[a("div",{staticClass:"card z-10 shadow-card",staticStyle:{"padding-bottom":"0","padding-top":"0"}},[a("CardNavigation",{staticClass:"-mx-1",attrs:{pages:t.pages}})],1),t._v(" "),a("router-view")],1)}),[],!1,null,null,null).exports}}]); "use strict";(self.webpackChunk=self.webpackChunk||[]).push([[4738],{3187:(t,e,n)=>{n.d(e,{Z:()=>r});const a={name:"CardNavigation",props:["pages"],computed:{routeName:function(){return this.$route.name}},data:function(){return{fixedNav:!1}},created:function(){var t=this;window.addEventListener("scroll",(function(){var e=document.getElementById("card-navigation");t.fixedNav=e.getBoundingClientRect().top<0}))}};const r=(0,n(1900).Z)(a,(function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"mb-7",staticStyle:{height:"62px"},attrs:{id:"card-navigation"}},[n("div",{class:{"fixed top-0 left-0 right-0 z-10 rounded-none bg-white bg-opacity-50 px-6 backdrop-blur-lg backdrop-filter dark:bg-dark-foreground":t.fixedNav}},[n("div",{staticClass:"overflow-x-auto whitespace-nowrap"},t._l(t.pages,(function(e,a){return n("router-link",{key:a,staticClass:"border-bottom-theme inline-block border-b-2 border-transparent px-4 py-5 text-sm font-bold",class:{"text-theme":t.routeName===e.route,"text-gray-600 dark:text-gray-100":t.routeName!==e.route},attrs:{to:{name:e.route},replace:""}},[t._v("\n "+t._s(e.title)+"\n ")])})),1)])])}),[],!1,null,null,null).exports},5002:(t,e,n)=>{n.r(e),n.d(e,{default:()=>r});const a={name:"AppSettings",components:{CardNavigation:n(3187).Z},data:function(){return{pages:[{title:this.$t("admin_settings.tabs.others"),route:"AppOthers"},{title:this.$t("Login & Registration"),route:"AppSignInUp"},{title:this.$t("admin_settings.tabs.appearance"),route:"AppAppearance"},{title:this.$t("Adsense"),route:"AppAdsense"},{title:this.$t("Homepage"),route:"AppIndex"},{title:this.$t("Environment"),route:"AppEnvironment"},{title:this.$t("Server"),route:"AppServer"}]}},mounted:function(){"/admin/settings"===this.$route.path&&this.$router.push({name:"AppOthers"})}};const r=(0,n(1900).Z)(a,(function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",[n("div",{staticClass:"card z-10 shadow-card",staticStyle:{"padding-bottom":"0","padding-top":"0"}},[n("CardNavigation",{staticClass:"-mx-1",attrs:{pages:t.pages}})],1),t._v(" "),n("router-view")],1)}),[],!1,null,null,null).exports}}]);
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -3
View File
@@ -42,7 +42,7 @@
"/js/chunks/app-settings.js": "/js/chunks/app-settings.js", "/js/chunks/app-settings.js": "/js/chunks/app-settings.js",
"/js/chunks/app-appearance.js": "/js/chunks/app-appearance.js", "/js/chunks/app-appearance.js": "/js/chunks/app-appearance.js",
"/js/chunks/app-index.js": "/js/chunks/app-index.js", "/js/chunks/app-index.js": "/js/chunks/app-index.js",
"/js/chunks/app-email.js": "/js/chunks/app-email.js", "/js/chunks/app-environment.js": "/js/chunks/app-environment.js",
"/js/chunks/app-others.js": "/js/chunks/app-others.js", "/js/chunks/app-others.js": "/js/chunks/app-others.js",
"/js/chunks/app-sign-in-out.js": "/js/chunks/app-sign-in-out.js", "/js/chunks/app-sign-in-out.js": "/js/chunks/app-sign-in-out.js",
"/js/chunks/app-adsense.js": "/js/chunks/app-adsense.js", "/js/chunks/app-adsense.js": "/js/chunks/app-adsense.js",
@@ -71,6 +71,5 @@
"/js/chunks/shared-with-me.js": "/js/chunks/shared-with-me.js", "/js/chunks/shared-with-me.js": "/js/chunks/shared-with-me.js",
"/js/chunks/invitation.js": "/js/chunks/invitation.js", "/js/chunks/invitation.js": "/js/chunks/invitation.js",
"/css/tailwind.css": "/css/tailwind.css", "/css/tailwind.css": "/css/tailwind.css",
"/css/app.css": "/css/app.css", "/css/app.css": "/css/app.css"
"/js/chunks/app-environment.js": "/js/chunks/app-environment.js"
} }
@@ -2,6 +2,7 @@
<div class="mb-8 flex items-center"> <div class="mb-8 flex items-center">
<edit-2-icon v-if="!icon" size="22" class="vue-feather text-theme dark-text-theme mr-3" /> <edit-2-icon v-if="!icon" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<frown-icon v-if="icon === 'frown'" size="22" class="vue-feather text-theme dark-text-theme mr-3" /> <frown-icon v-if="icon === 'frown'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<list-icon v-if="icon === 'list'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<info-icon v-if="icon === 'info'" size="22" class="vue-feather text-theme dark-text-theme mr-3" /> <info-icon v-if="icon === 'info'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<database-icon v-if="icon === 'database'" size="22" class="vue-feather text-theme dark-text-theme mr-3" /> <database-icon v-if="icon === 'database'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
<file-text-icon v-if="icon === 'file-text'" size="22" class="vue-feather text-theme dark-text-theme mr-3" /> <file-text-icon v-if="icon === 'file-text'" size="22" class="vue-feather text-theme dark-text-theme mr-3" />
@@ -24,6 +25,7 @@
<script> <script>
import { import {
ListIcon,
MailIcon, MailIcon,
InfoIcon, InfoIcon,
DatabaseIcon, DatabaseIcon,
@@ -46,6 +48,7 @@ export default {
name: 'FormLabel', name: 'FormLabel',
props: ['icon'], props: ['icon'],
components: { components: {
ListIcon,
MailIcon, MailIcon,
InfoIcon, InfoIcon,
DatabaseIcon, DatabaseIcon,
@@ -28,6 +28,31 @@
</div> </div>
</div> </div>
<!--Logs-->
<div class="card shadow-card">
<FormLabel icon="list">Latest Server Logs</FormLabel>
<InfoBox v-if="!logs.length" class="!mb-0">
<p v-html="$t('There is not any server log.')"></p>
</InfoBox>
<div
v-if="logs.length"
v-for="(log, i) in logs"
:key="i"
class="md:flex md:space-y-0 space-y-3 items-center justify-between border-b border-dashed border-light py-3 dark:border-opacity-5"
>
<div class="text-left">
<b class="block text-sm font-bold">
{{ log }}
</b>
</div>
<div @click="downloadLog(log)" class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-green-100 dark:bg-2x-dark-foreground cursor-pointer">
<DownloadCloudIcon size="15" class="opacity-75" />
</div>
</div>
</div>
<!--Database Backups check--> <!--Database Backups check-->
<div class="card shadow-card"> <div class="card shadow-card">
<FormLabel icon="database"> Latest Database Backups </FormLabel> <FormLabel icon="database"> Latest Database Backups </FormLabel>
@@ -150,10 +175,10 @@
</template> </template>
<script> <script>
import InfoBox from '../../../../components/Others/Forms/InfoBox' import { CheckIcon, XIcon, DownloadCloudIcon } from 'vue-feather-icons'
import FormLabel from '../../../../components/Others/Forms/FormLabel' import FormLabel from '../../../../components/Others/Forms/FormLabel'
import PageTab from '../../../../components/Others/Layout/PageTab' import PageTab from '../../../../components/Others/Layout/PageTab'
import { CheckIcon, XIcon } from 'vue-feather-icons' import InfoBox from '../../../../components/Others/Forms/InfoBox'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import axios from 'axios' import axios from 'axios'
@@ -163,6 +188,7 @@ export default {
FormLabel, FormLabel,
InfoBox, InfoBox,
PageTab, PageTab,
DownloadCloudIcon,
CheckIcon, CheckIcon,
XIcon, XIcon,
}, },
@@ -181,6 +207,20 @@ export default {
phpVersion: undefined, phpVersion: undefined,
apiRunning: undefined, apiRunning: undefined,
backups: undefined, backups: undefined,
logs: undefined,
}
},
methods: {
downloadLog(log) {
let anchor = document.createElement('a')
anchor.href = `/admin/log/${log}`
anchor.download = log
document.body.appendChild(anchor)
anchor.click()
} }
}, },
created() { created() {
@@ -192,6 +232,7 @@ export default {
this.modules = response.data.modules this.modules = response.data.modules
this.phpVersion = response.data.php_version this.phpVersion = response.data.php_version
this.backups = response.data.backups this.backups = response.data.backups
this.logs = response.data.logs
}) })
// Ping API // Ping API
+5
View File
@@ -2,6 +2,7 @@
use Domain\Homepage\Controllers\IndexController; use Domain\Homepage\Controllers\IndexController;
use Domain\Invoices\Controllers\GetInvoiceController; use Domain\Invoices\Controllers\GetInvoiceController;
use Domain\Settings\Controllers\DownloadLogController;
use Domain\Sharing\Controllers\SharePublicIndexController; use Domain\Sharing\Controllers\SharePublicIndexController;
use App\Socialite\Controllers\SocialiteCallbackController; use App\Socialite\Controllers\SocialiteCallbackController;
use Domain\Sharing\Controllers\WebCrawlerOpenGraphController; use Domain\Sharing\Controllers\WebCrawlerOpenGraphController;
@@ -16,6 +17,10 @@ Route::get('/translations/{lang}', CurrentLocalizationController::class);
Route::get('/invoices/{invoice}', GetInvoiceController::class) Route::get('/invoices/{invoice}', GetInvoiceController::class)
->middleware('auth:sanctum'); ->middleware('auth:sanctum');
Route::get('/admin/log/{log}', DownloadLogController::class)
->middleware(['auth:sanctum', 'admin']);
// Get og site for web crawlers // Get og site for web crawlers
if (Crawler::isCrawler()) { if (Crawler::isCrawler()) {
Route::get('/share/{share}', WebCrawlerOpenGraphController::class); Route::get('/share/{share}', WebCrawlerOpenGraphController::class);
+3
View File
@@ -1,7 +1,9 @@
<?php <?php
namespace App\Http; namespace App\Http;
use Fruitcake\Cors\HandleCors; use Fruitcake\Cors\HandleCors;
use Support\Middleware\AdminCheck;
use Support\Middleware\TrimStrings; use Support\Middleware\TrimStrings;
use Support\Middleware\TrustProxies; use Support\Middleware\TrustProxies;
use Support\Middleware\EncryptCookies; use Support\Middleware\EncryptCookies;
@@ -75,6 +77,7 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'admin' => AdminCheck::class,
'setup-wizard' => ProtectSetupWizardRoutes::class, 'setup-wizard' => ProtectSetupWizardRoutes::class,
'upload-request' => ProtectUploadRequestRoutes::class, 'upload-request' => ProtectUploadRequestRoutes::class,
]; ];
+1 -1
View File
@@ -48,7 +48,7 @@ class RouteServiceProvider extends ServiceProvider
->group(base_path('routes/upload-request.php')); ->group(base_path('routes/upload-request.php'));
Route::prefix('api/admin') Route::prefix('api/admin')
->middleware(['api', 'auth:sanctum']) ->middleware(['api', 'auth:sanctum', 'admin'])
->group(base_path('routes/admin.php')); ->group(base_path('routes/admin.php'));
Route::middleware(['setup-wizard']) Route::middleware(['setup-wizard'])
@@ -0,0 +1,34 @@
<?php
namespace Domain\Settings\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Routing\ResponseFactory;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\File;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class DownloadLogController extends Controller
{
public function __invoke($log): Response|BinaryFileResponse|Application|ResponseFactory
{
if (is_demo()) {
return response('Done.', 204);
}
// Get log path
$logPath = storage_path("logs/$log");
// Download log
return response()->download(
storage_path("logs/$log"), $log, [
'Accept-Ranges' => 'bytes',
'Content-Type' => 'text/plain',
'Content-Length' => File::size($logPath),
'Content-Range' => 'bytes 0-600/' . File::size($logPath),
'Content-Disposition' => "attachment; filename=$log",
]
);
}
}
@@ -16,6 +16,9 @@ class GetServerStatusController
// Get server data // Get server data
$status = ($this->getServerSetupStatus)(); $status = ($this->getServerSetupStatus)();
// Get latest logs
$status['logs'] = getListOfLatestLogs();
// Add latest database backups // Add latest database backups
$status['backups'] = collect(Storage::allFiles('app-backup')) $status['backups'] = collect(Storage::allFiles('app-backup'))
->map(fn ($path) => str_replace('app-backup/', '', $path)) ->map(fn ($path) => str_replace('app-backup/', '', $path))
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace Support\Middleware;
use Closure;
class AdminCheck
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// Check if user have access to administration settings
if ( $request->user()->role !== 'admin') {
abort(403, 'You don\'t have access for this operation!');
}
return $next($request);
}
}
+16
View File
@@ -18,6 +18,22 @@ use Domain\Localization\Models\Language;
use Intervention\Image\ImageManagerStatic as Image; use Intervention\Image\ImageManagerStatic as Image;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
if (! function_exists('getListOfLatestLogs')) {
/**
* Check if cron is running
*/
function getListOfLatestLogs(): array
{
return array_slice(
array_reverse(
array_filter(
scandir(storage_path() . '/logs'), fn($fn) => !str_starts_with($fn, '.')
)
), 0, 5, true
);
}
}
if (! function_exists('isRunningCron')) { if (! function_exists('isRunningCron')) {
/** /**
* Check if cron is running * Check if cron is running