Fixes
This commit is contained in:
parent
f04bf74026
commit
571df9a0a4
37
app/Exceptions/Handler.php
Normal file
37
app/Exceptions/Handler.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* The list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
protected function context(): array
|
||||
{
|
||||
return array_merge(parent::context(), [
|
||||
'current_url' => request()->url(),
|
||||
]);
|
||||
}
|
||||
}
|
41
app/Helpers/AbstractHelper.php
Normal file
41
app/Helpers/AbstractHelper.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class AbstractHelper
|
||||
{
|
||||
public static function classes_in_namespace($namespace)
|
||||
{
|
||||
$namespace .= '\\';
|
||||
$myClasses = array_filter(get_declared_classes(), function ($item) use ($namespace) {
|
||||
return substr($item, 0, strlen($namespace)) === $namespace;
|
||||
});
|
||||
|
||||
$theClasses = [];
|
||||
foreach ($myClasses as $class) {
|
||||
$theParts = explode('\\', $class);
|
||||
$theClasses[] = end($theParts);
|
||||
}
|
||||
|
||||
return $theClasses;
|
||||
}
|
||||
|
||||
public static function getClassNames($path)
|
||||
{
|
||||
$out = [];
|
||||
$results = scandir($path);
|
||||
foreach ($results as $result) {
|
||||
if ($result === '.' or $result === '..') continue;
|
||||
$filename = $path . '/' . $result;
|
||||
if (is_dir($filename)) {
|
||||
$out = array_merge($out, self::getClassNames($filename));
|
||||
} else {
|
||||
$classFilePath = explode('/',$filename);
|
||||
$out[] = substr($classFilePath[count($classFilePath)-1], 0, -4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
71
app/Http/Controllers/Auth/ProfileController.php
Normal file
71
app/Http/Controllers/Auth/ProfileController.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Requests\Auth\UpdateUserRequest;
|
||||
use App\Http\Requests\Auth\CreateApiTokenRequest;
|
||||
use App\Http\Requests\Auth\RemoveApiTokenRequest;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class ProfileController extends BaseController
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('auth.profile', [
|
||||
'user' => $request->user(),
|
||||
'sessions' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateUserRequest $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
if (!Hash::check($validated['password'], auth()->user()->password))
|
||||
{
|
||||
return redirect()->route('profile.index')->with('error', __('boilerplate::ui.incorect.old.password'));
|
||||
}
|
||||
|
||||
if (!empty($validated['newPassword']) && !empty($validated['password'])) {
|
||||
$validated['password'] = Hash::make($validated['newPassword']);
|
||||
unset($validated['newPassword']);
|
||||
} else {
|
||||
unset($validated['newPassword']);
|
||||
unset($validated['password']);
|
||||
}
|
||||
$request->user()->update($validated);
|
||||
return redirect()->route('profile.index')->with('success', __('boilerplate::ui.updated'));
|
||||
}
|
||||
|
||||
|
||||
public function api(Request $request)
|
||||
{
|
||||
return view('auth.profile_api', [
|
||||
'user' => $request->user(),
|
||||
'tokens' => $request->user()->tokens->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function createApiToken(CreateApiTokenRequest $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
if (empty($validated['expire_at'])) {
|
||||
$validated['expire_at'] = null;
|
||||
}
|
||||
$newToken = $request->user()->createToken($validated['token_name'], ['*'], Carbon::parse($validated['expire_at']))->plainTextToken;
|
||||
return redirect()->route('profile.api')->with([
|
||||
'success'=> __('boilerplate::ui.created'),
|
||||
'secret'=> $newToken,
|
||||
]);
|
||||
}
|
||||
|
||||
public function removeApiToken(RemoveApiTokenRequest $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
$request->user()->tokens()->where('id', $validated['token_id'])->first()->delete();
|
||||
return redirect()->route('profile.api')->with('success', __('boilerplate::ui.removed'));
|
||||
}
|
||||
}
|
13
app/Http/Controllers/BaseController.php
Normal file
13
app/Http/Controllers/BaseController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class BaseController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
}
|
@ -2,7 +2,11 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
//
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
}
|
||||
|
11
app/Http/Controllers/HomeController.php
Normal file
11
app/Http/Controllers/HomeController.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
class HomeController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('home');
|
||||
}
|
||||
}
|
11
app/Http/Controllers/HostController.php
Normal file
11
app/Http/Controllers/HostController.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
class HostController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('hosts.index');
|
||||
}
|
||||
}
|
11
app/Http/Controllers/MaintenanceController.php
Normal file
11
app/Http/Controllers/MaintenanceController.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
class MaintenanceController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('maintenance.index');
|
||||
}
|
||||
}
|
99
app/Http/Controllers/System/ApiController.php
Normal file
99
app/Http/Controllers/System/ApiController.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$routes = [];
|
||||
$routesCollection = Route::getRoutes();
|
||||
|
||||
foreach ($routesCollection as $route) {
|
||||
if (!str_starts_with($route->uri(), "api"))
|
||||
continue;
|
||||
|
||||
if (!str_starts_with($route->getActionName(), "@"))
|
||||
continue;
|
||||
|
||||
[$class, $method] = explode("@", $route->getActionName());
|
||||
$reflectionClass = new ReflectionClass($class);
|
||||
$reflectionMethod = $reflectionClass->getMethod($method);
|
||||
|
||||
$routes[] = [
|
||||
"Method" => $route->methods()[0],
|
||||
"Uri" => $route->uri(),
|
||||
"Description" => $this->phpDocsDescription($reflectionMethod),
|
||||
"Parameters" => $this->phpDocsParameters($reflectionMethod),
|
||||
"Returns" => $reflectionMethod->getReturnType() ? $reflectionMethod->getReturnType()->getName() : "NULL",
|
||||
];
|
||||
}
|
||||
|
||||
return view('system.api.index', [
|
||||
'routes' => $routes,
|
||||
]);
|
||||
}
|
||||
|
||||
private function phpDocsParameters(ReflectionMethod $method): array
|
||||
{
|
||||
// Retrieve the full PhpDoc comment block
|
||||
$doc = $method->getDocComment();
|
||||
|
||||
// Trim each line from space and star chars
|
||||
$lines = array_map(function ($line) {
|
||||
return trim($line, " *");
|
||||
}, explode("\n", $doc));
|
||||
|
||||
// Retain lines that start with an @
|
||||
$lines = array_filter($lines, function ($line) {
|
||||
return strpos($line, "@param") === 0;
|
||||
});
|
||||
|
||||
$args = [];
|
||||
|
||||
// Push each value in the corresponding @param array
|
||||
foreach ($lines as $line) {
|
||||
[$null, $type, $name, $comment] = explode(' ', $line, 4);
|
||||
|
||||
$args[] = [
|
||||
'type' => $type,
|
||||
'name' => $name,
|
||||
'comment' => $comment
|
||||
];
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
private function phpDocsDescription(ReflectionMethod $method): string
|
||||
{
|
||||
$doc = $method->getDocComment();
|
||||
$lines = [];
|
||||
|
||||
foreach (explode("\n", $doc) as $i =>$line) {
|
||||
$trimedLine =trim(trim($line, " *"), "/");
|
||||
|
||||
if (str_starts_with($trimedLine, "@")) {
|
||||
break;
|
||||
}
|
||||
|
||||
$lines[$i] = $trimedLine;
|
||||
}
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
}
|
32
app/Http/Controllers/System/AuditController.php
Normal file
32
app/Http/Controllers/System/AuditController.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Models\Activity;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AuditController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
//TODO: Clean Up and pagination
|
||||
$activities = Activity::with(["affected", "user"])->orderByDesc("created_at")->get();
|
||||
$urls = [];
|
||||
|
||||
foreach ($activities as $activity) {
|
||||
if (!Str::contains($activity->lang_text, "delete")) {
|
||||
// if(!empty($activity->user)) {
|
||||
// $urls[$activity->id] = route('admin.users.update', ['user' => $activity->user]);
|
||||
// continue;
|
||||
// }
|
||||
$urls[$activity->id] = "";
|
||||
}
|
||||
}
|
||||
|
||||
return view('system.audit.index', [
|
||||
'activities' => $activities,
|
||||
'urls' => $urls,
|
||||
]);
|
||||
}
|
||||
}
|
77
app/Http/Controllers/System/BackupController.php
Normal file
77
app/Http/Controllers/System/BackupController.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Jobs\Backup;
|
||||
use Artisan;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BackupController extends BaseController
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
Backup::dispatchSync();
|
||||
return redirect()->back()->with('success', __('boilerplate::ui.backup-running'));
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$backups = [];
|
||||
$path = storage_path('backups');
|
||||
|
||||
if (file_exists($path)) {
|
||||
foreach (File::allFiles($path. "/") as $file) {
|
||||
if (!Str::endsWith($file->getFilename(), ".zip")) {
|
||||
continue;
|
||||
}
|
||||
$date = explode("_", str_replace(".zip", "", $file->getFilename()))[0];
|
||||
if (empty($backups[$date]['fileSize'])) {
|
||||
$backups[$date]['fileSize'] = $file->getSize();
|
||||
} else {
|
||||
$backups[$date]['fileSize'] += $file->getSize();
|
||||
}
|
||||
$backups[$date]['fileName'][] = $file->getFilename();
|
||||
}
|
||||
|
||||
foreach ($backups as $key => $backup) {
|
||||
$backups[$key]['fileSize'] = $this->humanFileSize($backup['fileSize']);
|
||||
}
|
||||
}
|
||||
|
||||
return view('system.backup.index', ['backups' => $backups]);
|
||||
}
|
||||
public function download($file_name = null)
|
||||
{
|
||||
if (!empty($file_name)) {
|
||||
$path = storage_path('backups/' . $file_name);
|
||||
if (!\File::exists($path)) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return response()->download($path);
|
||||
}
|
||||
|
||||
abort(404);
|
||||
}
|
||||
|
||||
public function delete($backup_date)
|
||||
{
|
||||
foreach ([$backup_date . '_database.zip',$backup_date . '_storage.zip'] as $file) {
|
||||
$path = storage_path('backups/' . $file);
|
||||
if (!empty($path)) {
|
||||
File::delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('boilerplate::ui.deleted'));
|
||||
}
|
||||
|
||||
private function humanFileSize($bytes, $decimals = 2)
|
||||
{
|
||||
$sz = 'BKMGTP';
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
|
||||
}
|
||||
}
|
50
app/Http/Controllers/System/CacheController.php
Normal file
50
app/Http/Controllers/System/CacheController.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class CacheController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// Cache::put('test', 'test');
|
||||
// Cache::remember('users', 200, function () {
|
||||
// return DB::table('users')->get();
|
||||
// });
|
||||
$cache_driver = config('cache.default');
|
||||
|
||||
$cache_items = [];
|
||||
$storage = Cache::getStore(); // will return instance of FileStore
|
||||
if ($cache_driver == 'redis') {
|
||||
$redisConnection = $storage->connection();
|
||||
foreach ($redisConnection->command('keys', ['*']) as $full_key) {
|
||||
$cache_items[] = str_replace($storage->getPrefix(), "", $full_key);
|
||||
}
|
||||
} elseif ($cache_driver == 'file') {
|
||||
$cachePath = $storage->getDirectory();
|
||||
$items = File::allFiles($cachePath);
|
||||
foreach ($items as $file2) {
|
||||
$cache_items[] = $file2->getFilename();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: ADD SUPPORT FOR MEM CASH AND DB
|
||||
|
||||
return view('system.cache.index', [
|
||||
'cache_items' => $cache_items,
|
||||
'cache_driver' => $cache_driver,
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function clear()
|
||||
{
|
||||
Cache::flush();
|
||||
|
||||
return redirect()->route('system.cache.index')->with('success', __('boilerplate::ui.cache-cleared'));
|
||||
}
|
||||
}
|
40
app/Http/Controllers/System/JobsController.php
Normal file
40
app/Http/Controllers/System/JobsController.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Helpers\AbstractHelper;
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class JobsController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$rules = AbstractHelper::getClassNames(app_path() . "/Jobs");
|
||||
|
||||
$job_actions = $rules;
|
||||
$failed_jobs = DB::table('failed_jobs')->select(['id', 'uuid', 'queue', 'exception', 'failed_at'])->selectRaw('SUBSTRING(payload, 1, 150) AS payload')->get();
|
||||
$jobs = DB::table('jobs')->select(['id', 'queue', 'available_at'])->selectRaw('SUBSTRING(payload, 1, 150) AS payload')->get();
|
||||
|
||||
return view('system.jobs.index', [
|
||||
'failed_jobs' => $failed_jobs,
|
||||
'jobs' => $jobs,
|
||||
'job_actions' => $job_actions,
|
||||
]);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
DB::table('failed_jobs')->delete();
|
||||
|
||||
return redirect()->route('system.jobs.index')->with('success', __('boilerplate::ui.jobs-cleared'));
|
||||
}
|
||||
|
||||
public function call($job)
|
||||
{
|
||||
$class = '\\App\\Jobs\\' . $job;
|
||||
dispatch(new $class());
|
||||
return redirect()->route('system.jobs.index')->with('success', __('Job přidán do fronty'));
|
||||
}
|
||||
}
|
111
app/Http/Controllers/System/LogController.php
Normal file
111
app/Http/Controllers/System/LogController.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class LogController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
$path = storage_path('logs');
|
||||
|
||||
foreach (File::allFiles($path) as $file) {
|
||||
$items[] = [
|
||||
'fileName' => $file->getFilename(),
|
||||
'humanReadableSize' => $this->getHumanReadableSize($file->getSize()),
|
||||
];
|
||||
}
|
||||
|
||||
$items = array_reverse($items);
|
||||
|
||||
$todayStats = [
|
||||
'ERROR' => 0,
|
||||
'WARNING' => 0,
|
||||
'INFO' => 0,
|
||||
];
|
||||
|
||||
$todayLog = storage_path('logs/laravel.log');
|
||||
if (config('logging.default') == "daily") {
|
||||
$today = date('Y-m-d');
|
||||
$todayLog = storage_path('logs/laravel-' . $today . '.log');
|
||||
}
|
||||
|
||||
if (File::exists($todayLog)) {
|
||||
if (File::size($todayLog) > 1000 * 1000 * 1000 * 1000) {
|
||||
$todayStats = [
|
||||
'ERROR' => '??',
|
||||
'WARNING' => '??',
|
||||
'INFO' => '??',
|
||||
];
|
||||
} else {
|
||||
$content = File::get($todayLog);
|
||||
$todayStats['ERROR'] = substr_count($content, '.ERROR:');
|
||||
$todayStats['WARNING'] = substr_count($content, '.WARNING:');
|
||||
$todayStats['INFO'] = substr_count($content, '.INFO:');
|
||||
}
|
||||
}
|
||||
|
||||
return view('system.log.index', [
|
||||
'items' => $items,
|
||||
'todayStats' => $todayStats,
|
||||
]);
|
||||
}
|
||||
|
||||
public function detail($filename)
|
||||
{
|
||||
$path = storage_path('logs/' . $filename);
|
||||
|
||||
if (File::exists($path)) {
|
||||
if (File::size($path) > 1000 * 1000 * 1000 * 1000) {
|
||||
return response()->download($path);
|
||||
}
|
||||
|
||||
return view('system.log.detail', [
|
||||
'content' => File::get($path),
|
||||
'filename' => $filename,
|
||||
]);
|
||||
} else {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
public function download($filename){
|
||||
$path = storage_path('logs/' . $filename);
|
||||
|
||||
if (File::exists($path)) {
|
||||
return response()->download($path);
|
||||
} else {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
private function getHumanReadableSize($bytes)
|
||||
{
|
||||
if ($bytes > 0) {
|
||||
$base = floor(log($bytes) / log(1024));
|
||||
$units = array("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"); //units of measurement
|
||||
return number_format(($bytes / pow(1024, floor($base))), 3) . " $units[$base]";
|
||||
} else {
|
||||
return "0 bytes";
|
||||
}
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$path = storage_path('logs');
|
||||
$files = glob($path.'/lar*.log');
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('system.log.index')->with('success', __('boilerplate::ui.jobs-cleared'));
|
||||
}
|
||||
}
|
13
app/Http/Controllers/System/SubscriptionController.php
Normal file
13
app/Http/Controllers/System/SubscriptionController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
|
||||
class SubscriptionController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('system.subscription.index');
|
||||
}
|
||||
}
|
13
app/Http/Controllers/System/UserController.php
Normal file
13
app/Http/Controllers/System/UserController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\System;
|
||||
|
||||
use App\Http\Controllers\BaseController;
|
||||
|
||||
class UserController extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('system.user.index');
|
||||
}
|
||||
}
|
17
app/Http/Middleware/EncryptCookies.php
Normal file
17
app/Http/Middleware/EncryptCookies.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'theme',
|
||||
];
|
||||
}
|
72
app/Http/Middleware/GenerateMenus.php
Normal file
72
app/Http/Middleware/GenerateMenus.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use SteelAnts\LaravelBoilerplate\Facades\Menu;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class GenerateMenus
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->route()->getName() === 'livewire.message') {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (!auth()->check()) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
Menu::make('main-menu', function ($menu) {
|
||||
$systemRoutes = [
|
||||
'boilerplate::ui.home' => [' fas fa-home', 'home'],
|
||||
'boilerplate::ui.host' => [' fas fa-server', 'host'],
|
||||
'boilerplate::ui.maintenance' => [' fas fa-calendar', 'maintenance'],
|
||||
];
|
||||
|
||||
foreach ($systemRoutes as $title => $route_data) {
|
||||
$icon = $route_data[0];
|
||||
$route = $route_data[1];
|
||||
|
||||
$menu = $menu->add($title, [
|
||||
'id' => strtolower($title),
|
||||
'icon' => $icon,
|
||||
'route' => $route,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
//CHECK IF USER IS SYSTEM ADMIN
|
||||
Menu::make('system-menu', function ($menu) {
|
||||
$systemRoutes = [
|
||||
'boilerplate::ui.audit' => ['fas fa-eye', 'system.audit.index'],
|
||||
'boilerplate::ui.api' => ['fas fa-file-archive', 'system.api.index'],
|
||||
'boilerplate::ui.user' => ['fas fa-users', 'system.user.index'],
|
||||
'boilerplate::subscriptions.title' => ['fas fa-dollar-sign', 'system.subscription.index'],
|
||||
'boilerplate::ui.log' => ['fas fa-bug', 'system.log.index'],
|
||||
'boilerplate::ui.jobs' => ['fas fa-business-time', 'system.jobs.index'],
|
||||
'boilerplate::ui.cache' => ['fas fa-box', 'system.cache.index'],
|
||||
'boilerplate::ui.backup' => ['fas fa-file-archive', 'system.backup.index']
|
||||
];
|
||||
foreach ($systemRoutes as $title => $route_data) {
|
||||
$icon = $route_data[0];
|
||||
$route = $route_data[1];
|
||||
|
||||
$menu = $menu->add($title, [
|
||||
'id' => strtolower($title),
|
||||
'icon' => $icon,
|
||||
'route' => $route,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
29
app/Http/Requests/Auth/CreateApiTokenRequest.php
Normal file
29
app/Http/Requests/Auth/CreateApiTokenRequest.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateApiTokenRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'token_name' => ['required', 'string'],
|
||||
'expire_at' => ['nullable', 'date', 'after_or_equal:' . date('Y-m-d')],
|
||||
];
|
||||
}
|
||||
}
|
28
app/Http/Requests/Auth/RemoveApiTokenRequest.php
Normal file
28
app/Http/Requests/Auth/RemoveApiTokenRequest.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RemoveApiTokenRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'token_id' => ['required', 'exists:personal_access_tokens,id'],
|
||||
];
|
||||
}
|
||||
}
|
29
app/Http/Requests/Auth/UpdateUserRequest.php
Normal file
29
app/Http/Requests/Auth/UpdateUserRequest.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['sometimes', 'string', 'email', 'max:255', 'unique:users'],
|
||||
'newPassword' => ['sometimes', 'nullable', 'string', 'min:8', 'confirmed'],
|
||||
];
|
||||
}
|
||||
}
|
137
app/Jobs/Backup.php
Normal file
137
app/Jobs/Backup.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Directory;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Backup implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
//PREPARATION
|
||||
ini_set('date.timezone', 'Europe/Prague');
|
||||
$days = 3;
|
||||
$dbName = config('database.connections.mysql.database');
|
||||
$dbUserName = config('database.connections.mysql.username');
|
||||
$dbPassword = config('database.connections.mysql.password');
|
||||
|
||||
$fs_backup_path = storage_path('backups/tmp/storage');
|
||||
$db_backup_path = storage_path('backups/tmp/db');
|
||||
|
||||
//TODO: verifi all folders exists
|
||||
foreach ([$db_backup_path, $fs_backup_path] as $backupPath) {
|
||||
if (!File::exists($backupPath)) {
|
||||
File::makeDirectory($backupPath, 0755, true);
|
||||
} else {
|
||||
$command = "rm -r -f " . $backupPath . "/*";
|
||||
exec($command, $output);
|
||||
Log::Info('Clean Old Temp ' . $backupPath);
|
||||
Log::Debug($output);
|
||||
}
|
||||
}
|
||||
|
||||
//REMOVE OLD BACKUPS
|
||||
$command = "rm -f " . storage_path('app/backups') . "/" . date("Y-m-d", strtotime('-' . $days . ' days')) . ".zip";
|
||||
exec($command, $output);
|
||||
Log::info('Clean Old backups ' . $days . ' old');
|
||||
|
||||
///DATABASE
|
||||
if (config('database.default') == 'sqlite') {
|
||||
$dbFile = database_path('database.sqlite');
|
||||
$backupFile = $db_backup_path . '/' . $dbName . '_' . date("Y-m-d", time()) . '.sqlite';
|
||||
$command = "cp $dbFile $backupFile 2>&1";
|
||||
exec($command, $output);
|
||||
Log::info('Backup ' . $dbName . ' db ');
|
||||
Log::Debug($output);
|
||||
} else {
|
||||
foreach (['data', 'scheme'] as $type) {
|
||||
$parameters = "--no-data";
|
||||
if ($type == "data") {
|
||||
$parameters = "--no-create-info";
|
||||
}
|
||||
|
||||
$backupFile = $db_backup_path . '/' . $dbName . '_' . $type . '_' . date("Y-m-d", time()) . '.sql';
|
||||
$command = "mysqldump --skip-comments " . $parameters . " -h localhost -u " . $dbUserName . " -p" . $dbPassword . " " . $dbName . " -r $backupFile 2>&1";
|
||||
exec($command, $output);
|
||||
Log::info('Backup ' . $dbName . ' db ' . $type);
|
||||
Log::Debug($output);
|
||||
}
|
||||
}
|
||||
|
||||
//STORAGE
|
||||
$command = "cp -R " . storage_path('app') . " " . storage_path('backups/tmp/storage');
|
||||
exec($command, $output);
|
||||
Log::info('storage backup done');
|
||||
Log::Debug($output);
|
||||
|
||||
//Backupo .env
|
||||
$envBackupFile = storage_path("backups/tmp/storage/env.backup");
|
||||
$envSourceFile = app()->environmentFilePath();
|
||||
|
||||
$command = "cp " . $envSourceFile . " " . $envBackupFile;
|
||||
exec($command, $output);
|
||||
Log::info('Backup .env');
|
||||
|
||||
//Clear previouse backups from same day
|
||||
$command = "rm -f " . storage_path('backups') . "/" . date("Y-m-d", time()) . ".zip";
|
||||
exec($command, $output);
|
||||
Log::info('Clean previous backup');
|
||||
|
||||
foreach (['database' => $db_backup_path, 'storage' => $fs_backup_path] as $filename => $backupPath) {
|
||||
$zippedFilePath = storage_path('backups/' . date("Y-m-d", time()) . '_' . $filename . ".zip");
|
||||
|
||||
if (File::exists($zippedFilePath)) {
|
||||
$command = "rm -r -f " . $zippedFilePath;
|
||||
exec($command, $output);
|
||||
Log::Info('Clean Old Backup File' . $zippedFilePath);
|
||||
Log::Debug($output);
|
||||
}
|
||||
|
||||
$command = "cd ".$backupPath."; zip -rm ".$zippedFilePath." ./*" ;
|
||||
exec($command, $output);
|
||||
Log::info($backupPath . '=>' . $zippedFilePath);
|
||||
|
||||
$command = "md5sum ". $zippedFilePath;
|
||||
exec($command, $output);
|
||||
Log::info('Zipping hash');
|
||||
|
||||
$charSet = preg_replace(array('/\s{2,}/', '/[\t\n]/'), ' ', $output[count($output)-1]);
|
||||
$charSet = rtrim($charSet);
|
||||
|
||||
$fileMD5Hash = explode(" ", $charSet)[0];
|
||||
Log::debug($fileMD5Hash);
|
||||
Log::info($backupPath . '=>'.$zippedFilePath.'=>' .$fileMD5Hash);
|
||||
}
|
||||
|
||||
if (!empty(env('APP_ADMIN'))) {
|
||||
Mail::raw(__('Backup Run successfully'), function ($message) {
|
||||
$message->to('vasek@steelants.cz')->subject(_('Backup Run successfully ') . env('APP_NAME'));
|
||||
});
|
||||
Log::info('Sending Notification');
|
||||
}
|
||||
}
|
||||
|
||||
private function execShellCommand($command, &$output)
|
||||
{
|
||||
$output = $null;
|
||||
exec($command, $output);
|
||||
Log::debug($output);
|
||||
}
|
||||
}
|
45
app/Livewire/Audit/DataTable.php
Normal file
45
app/Livewire/Audit/DataTable.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Audit;
|
||||
|
||||
use App\Models\Activity;
|
||||
use SteelAnts\DataTable\Livewire\DataTableComponent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DataTable extends DataTableComponent
|
||||
{
|
||||
public bool $paginated = true;
|
||||
public int $itemsPerPage = 100;
|
||||
|
||||
public function query(): Builder
|
||||
{
|
||||
return Activity::with(["affected", "user"])->orderByDesc("created_at");
|
||||
}
|
||||
|
||||
public function row($row): array
|
||||
{
|
||||
$affectedJson = json_encode([
|
||||
'id'=> $row->affected->id ?? '',
|
||||
'name'=> ($row->affected->title ??($row->affected->name ?? ($row->affected->description ?? ''))),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
|
||||
return [
|
||||
'created_at' => $row->created_at,
|
||||
'ip_address' => $row->ip,
|
||||
'note' => $row->lang_text ,
|
||||
'user_id' => ($row->user->name ?? 'Unknown'),
|
||||
'affected_id' => $affectedJson,
|
||||
];
|
||||
}
|
||||
|
||||
public function headers(): array
|
||||
{
|
||||
return [
|
||||
'created_at' => "Created",
|
||||
'ip_address' => "IP Address",
|
||||
'note' => "Note",
|
||||
'user_id' => "Author",
|
||||
'affected_id' => "Model"
|
||||
];
|
||||
}
|
||||
}
|
53
app/Livewire/Host/DataTable.php
Normal file
53
app/Livewire/Host/DataTable.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
namespace App\Livewire\Host;
|
||||
|
||||
use App\Models\Host;
|
||||
use SteelAnts\DataTable\Livewire\DataTableComponent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DataTable extends DataTableComponent
|
||||
{
|
||||
public $listeners = [
|
||||
'hostAdded' => '$refresh',
|
||||
'closeModal' => '$refresh',
|
||||
];
|
||||
|
||||
public function query(): Builder
|
||||
{
|
||||
return Host::query();
|
||||
}
|
||||
|
||||
public function headers(): array
|
||||
{
|
||||
return [
|
||||
'hostname' => 'hostname',
|
||||
];
|
||||
}
|
||||
|
||||
public function remove($host_id){
|
||||
Host::find($host_id)->delete();
|
||||
}
|
||||
|
||||
public function actions($item)
|
||||
{
|
||||
return [
|
||||
[
|
||||
'type' => "livewire",
|
||||
'action' => "edit",
|
||||
'text' => "edit",
|
||||
'parameters' => $item['id']
|
||||
],
|
||||
[
|
||||
'type' => "livewire",
|
||||
'action' => "remove",
|
||||
'text' => "remove",
|
||||
'parameters' => $item['id']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function edit($host_id)
|
||||
{
|
||||
$this->dispatch('openModal', 'host.form', __('boilerplate::host.edit'), ['model' => $host_id]);
|
||||
}
|
||||
}
|
54
app/Livewire/Host/Form.php
Normal file
54
app/Livewire/Host/Form.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace App\Livewire\Host;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Host;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
public $model;
|
||||
public string $hostname;
|
||||
|
||||
public $action = 'store';
|
||||
|
||||
protected function rules()
|
||||
{
|
||||
return [
|
||||
'hostname' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function mount ($model = null){
|
||||
if (!empty($model)) {
|
||||
$host = Host::find($model);
|
||||
|
||||
$this->model = $model;
|
||||
|
||||
$this->hostname = $host->hostname;
|
||||
|
||||
$this->action = 'update';
|
||||
}
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$validatedData = $this->validate();
|
||||
Host::create($validatedData);
|
||||
$this->dispatch('closeModal');
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$validatedData = $this->validate();
|
||||
$host = Host::find($this->model);
|
||||
if (!empty($host)) {
|
||||
$host->update($validatedData);
|
||||
}
|
||||
$this->dispatch('closeModal');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.host.form');
|
||||
}
|
||||
}
|
55
app/Livewire/Maintenance/DataTable.php
Normal file
55
app/Livewire/Maintenance/DataTable.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace App\Livewire\Maintenance;
|
||||
|
||||
use App\Models\Maintenance;
|
||||
use SteelAnts\DataTable\Livewire\DataTableComponent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DataTable extends DataTableComponent
|
||||
{
|
||||
public $listeners = [
|
||||
'maintenanceAdded' => '$refresh',
|
||||
'closeModal' => '$refresh',
|
||||
];
|
||||
|
||||
public function query(): Builder
|
||||
{
|
||||
return Maintenance::query();
|
||||
}
|
||||
|
||||
public function headers(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'name',
|
||||
'description' => 'description',
|
||||
'schedule' => 'schedule',
|
||||
];
|
||||
}
|
||||
|
||||
public function remove($maintenance_id){
|
||||
Maintenance::find($maintenance_id)->delete();
|
||||
}
|
||||
|
||||
public function actions($item)
|
||||
{
|
||||
return [
|
||||
[
|
||||
'type' => "livewire",
|
||||
'action' => "edit",
|
||||
'text' => "edit",
|
||||
'parameters' => $item['id']
|
||||
],
|
||||
[
|
||||
'type' => "livewire",
|
||||
'action' => "remove",
|
||||
'text' => "remove",
|
||||
'parameters' => $item['id']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function edit($maintenance_id)
|
||||
{
|
||||
$this->dispatch('openModal', 'maintenance.form', __('boilerplate::maintenances.edit'), ['model' => $maintenance_id]);
|
||||
}
|
||||
}
|
60
app/Livewire/Maintenance/Form.php
Normal file
60
app/Livewire/Maintenance/Form.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace App\Livewire\Maintenance;
|
||||
|
||||
use Livewire\Component;
|
||||
use App\Models\Maintenance;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
public $model;
|
||||
public string $name = "";
|
||||
public string $description = "";
|
||||
public string $schedule = "";
|
||||
|
||||
public $action = 'store';
|
||||
|
||||
protected function rules()
|
||||
{
|
||||
return [
|
||||
'name' => 'required',
|
||||
'description' => 'required',
|
||||
'schedule' => 'required',
|
||||
];
|
||||
}
|
||||
|
||||
public function mount ($model = null){
|
||||
if (!empty($model)) {
|
||||
$maintenance = Maintenance::find($model);
|
||||
|
||||
$this->model = $model;
|
||||
|
||||
$this->name = $maintenance->name;
|
||||
$this->description = $maintenance->description;
|
||||
$this->schedule = $maintenance->schedule;
|
||||
|
||||
$this->action = 'update';
|
||||
}
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$validatedData = $this->validate();
|
||||
Maintenance::create($validatedData);
|
||||
$this->dispatch('closeModal');
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$validatedData = $this->validate();
|
||||
$maintenance = Maintenance::find($this->model);
|
||||
if (!empty($maintenance)) {
|
||||
$maintenance->update($validatedData);
|
||||
}
|
||||
$this->dispatch('closeModal');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.maintenance.form');
|
||||
}
|
||||
}
|
62
app/Livewire/Session/DataTable.php
Normal file
62
app/Livewire/Session/DataTable.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Session;
|
||||
|
||||
use App\Models\Session;
|
||||
use SteelAnts\DataTable\Livewire\DataTableComponent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DataTable extends DataTableComponent
|
||||
{
|
||||
public bool $paginated = false;
|
||||
public bool $sortable = false;
|
||||
|
||||
public function query(): Builder
|
||||
{
|
||||
return request()->user()->sessions()->orderByDesc("last_activity")->getQuery();
|
||||
}
|
||||
|
||||
public function row($row): array
|
||||
{
|
||||
return [
|
||||
'id' => $row->id,
|
||||
'ip_address' => $row->ip_address,
|
||||
'last_activity' => $row->last_activity->format('d. m. Y H:m'),
|
||||
'browser_os_name' => $row->browser_o_s_name,
|
||||
'browser_name' => $row->browser_name,
|
||||
'last_activity' => $row->last_activity->format('d. m. Y H:m'),
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
public function headers(): array
|
||||
{
|
||||
return [
|
||||
'ip_address' => "IP Address",
|
||||
'browser_os_name' => "OS Name",
|
||||
'browser_name' => "Browser",
|
||||
'last_activity' => "Last Activity"
|
||||
];
|
||||
}
|
||||
|
||||
public function actions($item)
|
||||
{
|
||||
return [
|
||||
[
|
||||
'type' => "livewire",
|
||||
'action' => "logout",
|
||||
'parameters' => $item['id'],
|
||||
'text' => "Logout",
|
||||
'actionClass' => 'text-danger',
|
||||
'iconClass' => 'fas fa-trash',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function logout($session_id)
|
||||
{
|
||||
request()->user()->sessions()->find($session_id)->delete();
|
||||
return redirect(request()->header('Referer'));
|
||||
}
|
||||
}
|
52
app/Livewire/Subscription/DataTable.php
Normal file
52
app/Livewire/Subscription/DataTable.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Subscription;
|
||||
|
||||
use App\Models\Subscription;
|
||||
use App\Models\User;
|
||||
use App\Types\SubscriptionTier;
|
||||
use SteelAnts\DataTable\Livewire\DataTableComponent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DataTable extends DataTableComponent
|
||||
{
|
||||
public $listeners = [
|
||||
'subscriptionRefresh' => '$refresh'
|
||||
];
|
||||
|
||||
public function query(): Builder
|
||||
{
|
||||
return Subscription::query();
|
||||
}
|
||||
|
||||
public function row($row): array
|
||||
{
|
||||
return [
|
||||
'id' => $row->id,
|
||||
'tier' => SubscriptionTier::getName($row->tier),
|
||||
'valid_to' => $row->valid_to->format('d. m. Y'),
|
||||
];
|
||||
}
|
||||
|
||||
public function headers(): array
|
||||
{
|
||||
return ["id", "tier", "valid_to"];
|
||||
}
|
||||
|
||||
public function actions($item)
|
||||
{
|
||||
return [
|
||||
[
|
||||
'type' => "livewire",
|
||||
'action' => "edit",
|
||||
'text' => "edit",
|
||||
'parameters' => $item['id']
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$this->dispatch('openModal', 'subscription.form', __('boilerplate::subscriptions.edit'), $id);
|
||||
}
|
||||
}
|
80
app/Livewire/Subscription/Form.php
Normal file
80
app/Livewire/Subscription/Form.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Subscription;
|
||||
|
||||
use App\Models\Subscription;
|
||||
use Livewire\Component;
|
||||
use App\Types\SubscriptionTier;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
public $model;
|
||||
public $tier;
|
||||
public $valid_to;
|
||||
|
||||
public $tiers;
|
||||
public $action = 'store';
|
||||
|
||||
protected function rules()
|
||||
{
|
||||
return [
|
||||
'tier' => 'required|integer|min:1',
|
||||
'valid_to' => 'required|date',
|
||||
];
|
||||
}
|
||||
|
||||
public function mount($model = null)
|
||||
{
|
||||
$this->tiers = SubscriptionTier::getNames();
|
||||
|
||||
if (!empty($model)) {
|
||||
$sub = Subscription::find($model);
|
||||
if (empty($sub)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->model = $model;
|
||||
$this->tier = $sub->tier;
|
||||
$this->valid_to = $sub->valid_to->format('Y-m-d');
|
||||
$this->action = 'update';
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.subscription.form');
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$validatedData = $this->validate();
|
||||
|
||||
Subscription::create($validatedData);
|
||||
|
||||
$this->dispatch('close-modal');
|
||||
$this->dispatch('snackbar', ['message' => __('boilerplate::ui.item-created'), 'type' => 'success', 'icon' => 'fas fa-check']);
|
||||
|
||||
$this->dispatch('subscriptionRefresh');
|
||||
|
||||
$this->reset('tier');
|
||||
$this->reset('valid_to');
|
||||
}
|
||||
|
||||
public function update()
|
||||
{
|
||||
$validatedData = $this->validate();
|
||||
|
||||
$sub = Subscription::find($this->model);
|
||||
if (!empty($sub)) {
|
||||
$sub->update($validatedData);
|
||||
}
|
||||
|
||||
$this->dispatch('close-modal');
|
||||
$this->dispatch('snackbar', ['message' => __('boilerplate::ui.item-updated'), 'type' => 'success', 'icon' => 'fas fa-check']);
|
||||
|
||||
$this->dispatch('subscriptionRefresh');
|
||||
|
||||
$this->reset('tier');
|
||||
$this->reset('valid_to');
|
||||
}
|
||||
}
|
51
app/Livewire/User/DataTable.php
Normal file
51
app/Livewire/User/DataTable.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\User;
|
||||
|
||||
use App\Models\User;
|
||||
use SteelAnts\DataTable\Livewire\DataTableComponent;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class DataTable extends DataTableComponent
|
||||
{
|
||||
public $listeners = [
|
||||
'userAdded' => '$refresh'
|
||||
];
|
||||
|
||||
public function query(): Builder
|
||||
{
|
||||
return User::query();
|
||||
}
|
||||
|
||||
public function headers(): array
|
||||
{
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'name' => 'Name',
|
||||
'email' => 'E-mail',
|
||||
];
|
||||
}
|
||||
|
||||
public function actions($item)
|
||||
{
|
||||
if ($item['id'] == auth()->user()->id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'type' => "livewire",
|
||||
'action' => "remove",
|
||||
'parameters' => $item['id'],
|
||||
'text' => "Remove",
|
||||
'actionClass' => 'text-danger',
|
||||
'iconClass' => 'fas fa-trash',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function remove($user_id)
|
||||
{
|
||||
User::find($user_id)->delete();
|
||||
}
|
||||
}
|
45
app/Livewire/User/Form.php
Normal file
45
app/Livewire/User/Form.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\User;
|
||||
|
||||
use App\Http\Requests\System\CreateUserRequest;
|
||||
use Livewire\Component;
|
||||
use App\Models\User;
|
||||
|
||||
class Form extends Component
|
||||
{
|
||||
public string $name ='';
|
||||
public string $email ='';
|
||||
public string $password ='';
|
||||
public string $password_confirmation ='';
|
||||
|
||||
protected function rules()
|
||||
{
|
||||
return [
|
||||
'name' => 'required|max:255|unique:users,name',
|
||||
'email' => 'required|string|email|max:255|unique:users,email',
|
||||
'password' => 'required|string|min:8|max:255|confirmed',
|
||||
];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.user.form');
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$validatedData = $this->validate();
|
||||
User::create($validatedData);
|
||||
|
||||
$this->dispatch('close-modal');
|
||||
$this->dispatch('snackbar', ['message' => __('boilerplate::ui.create'), 'type' => 'success', 'icon' => 'fas fa-check']);
|
||||
|
||||
$this->dispatch('userAdded');
|
||||
|
||||
$this->reset('name');
|
||||
$this->reset('email');
|
||||
$this->reset('password');
|
||||
$this->reset('password_confirmation');
|
||||
}
|
||||
}
|
46
app/Models/Activity.php
Normal file
46
app/Models/Activity.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Observers\ActivityObserver;
|
||||
|
||||
class Activity extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $casts = [
|
||||
'data' => 'array',
|
||||
];
|
||||
const UPDATED_AT = null;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'ip',
|
||||
'user_id',
|
||||
'affected_type',
|
||||
'affected_id',
|
||||
'lang_text',
|
||||
'data',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
Activity::observe(ActivityObserver::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function affected()
|
||||
{
|
||||
return $this->morphTo('affected');
|
||||
}
|
||||
}
|
@ -8,4 +8,8 @@
|
||||
class Host extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'hostname',
|
||||
];
|
||||
}
|
||||
|
@ -8,4 +8,10 @@
|
||||
class Maintenance extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'schedule',
|
||||
];
|
||||
}
|
||||
|
63
app/Models/Session.php
Normal file
63
app/Models/Session.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Session extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
protected $keyType = 'string';
|
||||
public $incrementing = false;
|
||||
public $timestamps = false;
|
||||
protected $casts = [
|
||||
'last_activity' => 'datetime',
|
||||
];
|
||||
protected $appends = [
|
||||
'browser_name',
|
||||
'browser_os_name',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
protected function browserName(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$pattern = '/\b(Edge|Safari|Chrome|Firefox|Opera)\b/i'; // Regular expression pattern to match common browser names
|
||||
$matches = [];
|
||||
preg_match($pattern, $this->user_agent, $matches);
|
||||
|
||||
if (isset($matches[1])) {
|
||||
$browserName = $matches[1];
|
||||
return $browserName;
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
protected function browserOSName(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$pattern = '/\b(Android|Linux|Windows|iOS|MacOS)\b/i'; // Regular expression pattern to match common browser names
|
||||
$matches = [];
|
||||
preg_match($pattern, $this->user_agent, $matches);
|
||||
|
||||
if (isset($matches[1])) {
|
||||
$browserName = $matches[1];
|
||||
return $browserName;
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
17
app/Models/Setting.php
Normal file
17
app/Models/Setting.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public function settingable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
}
|
20
app/Models/Subscription.php
Normal file
20
app/Models/Subscription.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Subscription extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'tier',
|
||||
'valid_to',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'valid_to' => 'datetime',
|
||||
];
|
||||
}
|
21
app/Models/Tenant.php
Normal file
21
app/Models/Tenant.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Tenant extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'domain',
|
||||
];
|
||||
|
||||
public function setDomainAttribute($value)
|
||||
{
|
||||
$this->attributes['domain'] = strtolower($value);
|
||||
}
|
||||
}
|
@ -6,10 +6,12 @@
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use App\Traits\Auditable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasFactory, Notifiable;
|
||||
use HasFactory, Notifiable, HasApiTokens, Auditable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@ -44,4 +46,9 @@ protected function casts(): array
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
public function sessions()
|
||||
{
|
||||
return $this->hasMany(Session::class);
|
||||
}
|
||||
}
|
||||
|
37
app/Observers/ActivityObserver.php
Normal file
37
app/Observers/ActivityObserver.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Activity;
|
||||
|
||||
class ActivityObserver
|
||||
{
|
||||
/**
|
||||
* Handle the Activity "created" event.
|
||||
*/
|
||||
public function creating(Activity $activity): void
|
||||
{
|
||||
if (!app()->runningInConsole()) {
|
||||
$activity->ip = $this->getIp();
|
||||
$activity->user_id = auth()->id();
|
||||
} else {
|
||||
$activity->ip = "localhost";
|
||||
$activity->user_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function getIp()
|
||||
{
|
||||
foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key) {
|
||||
if (array_key_exists($key, $_SERVER) === true) {
|
||||
foreach (explode(',', $_SERVER[$key]) as $ip) {
|
||||
$ip = trim($ip); // just to be safe
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return request()->ip(); // it will return server ip when no client ip found
|
||||
}
|
||||
}
|
@ -19,6 +19,8 @@ public function register(): void
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
if (config('app.env') != 'local'){
|
||||
\URL::forceScheme('https');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
43
app/Providers/TenantServiceProvider.php
Normal file
43
app/Providers/TenantServiceProvider.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use App\Services\TenantManager;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class TenantServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->app->singleton(TenantManager::class, function () {
|
||||
$tenant = null;
|
||||
if (!app()->runningInConsole()) {
|
||||
$tenant = Tenant::where('domain', explode(".", request()->getHost())[0])
|
||||
->with(['users', 'settings'])
|
||||
->first();
|
||||
|
||||
if (is_null($tenant)) {
|
||||
abort(404, 'Tenant ' . explode(".", request()->getHost())[0] . ' not found (' . request()->getHost() . ')');
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
return new TenantManager($tenant);
|
||||
});
|
||||
}
|
||||
}
|
35
app/Traits/Auditable.php
Normal file
35
app/Traits/Auditable.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Activity;
|
||||
|
||||
trait Auditable
|
||||
{
|
||||
public static function bootAuditable()
|
||||
{
|
||||
if (app()->runningInConsole()) {
|
||||
return;
|
||||
}
|
||||
|
||||
static::created(function ($model) {
|
||||
$activity = new Activity();
|
||||
$activity->lang_text = __('boilerplate::ui.created', ["model" => class_basename($model) . " " . $model->name]);
|
||||
$activity->affected()->associate($model);
|
||||
$activity->save();
|
||||
});
|
||||
|
||||
static::updating(function ($model) {
|
||||
$activity = new Activity();
|
||||
$activity->lang_text = __('boilerplate::ui.updated', ["model" => class_basename($model) . " " . $model->name]);
|
||||
$activity->affected()->associate($model);
|
||||
$activity->save();
|
||||
});
|
||||
|
||||
static::deleting(function ($model) {
|
||||
$activity = new Activity();
|
||||
$activity->lang_text = __('boilerplate::ui.deleted', ["model" => class_basename($model) . " " . $model->name]);
|
||||
$activity->save();
|
||||
});
|
||||
}
|
||||
}
|
15
app/Traits/HasSettings.php
Normal file
15
app/Traits/HasSettings.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
trait HasSettings
|
||||
{
|
||||
public function settings() : MorphMany
|
||||
{
|
||||
return $this->morphMany(Setting::class, 'settable');
|
||||
}
|
||||
}
|
41
app/Types/SubscriptionTier.php
Normal file
41
app/Types/SubscriptionTier.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Types;
|
||||
|
||||
class SubscriptionTier
|
||||
{
|
||||
const TIER_1 = 1;
|
||||
const TIER_2 = 2;
|
||||
const TIER_3 = 3;
|
||||
// Add more tiers if needed
|
||||
|
||||
public function getLimits()
|
||||
{
|
||||
return [
|
||||
self::TIER_1 => [
|
||||
'limit_name' => 100,
|
||||
// Add more limits if needed
|
||||
],
|
||||
self::TIER_2 => [
|
||||
'limit_name' => 1000,
|
||||
],
|
||||
self::TIER_3 => [
|
||||
'limit_name' => 10000,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function getNames()
|
||||
{
|
||||
return [
|
||||
self::TIER_1 => __('boilerplate::subscriptions.tier_1.title'),
|
||||
self::TIER_2 => __('boilerplate::subscriptions.tier_2.title'),
|
||||
self::TIER_3 => __('boilerplate::subscriptions.tier_3.title'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getName($type)
|
||||
{
|
||||
return self::getNames()[$type] ?? 'undefined';
|
||||
}
|
||||
}
|
58
app/View/Components/Alerts.php
Normal file
58
app/View/Components/Alerts.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Alerts extends Component
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?array $alerts = [],
|
||||
)
|
||||
{
|
||||
$types = [
|
||||
'success',
|
||||
'error',
|
||||
'warning',
|
||||
'info',
|
||||
'message',
|
||||
];
|
||||
|
||||
foreach($types as $type){
|
||||
$message = session()->get($type);
|
||||
if(!empty($message)){
|
||||
$this->alerts[] = [
|
||||
'type' => $type,
|
||||
'message' => $message
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if(session()->has('errors')){
|
||||
$items = session()->get('errors')->toArray();
|
||||
foreach($items as $item){
|
||||
if(!empty($item['error'])){
|
||||
$this->alerts[] = [
|
||||
'type' => 'error',
|
||||
'message' => $item['error']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.alerts');
|
||||
}
|
||||
}
|
30
app/View/Components/Navigation.php
Normal file
30
app/View/Components/Navigation.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use App\Menus\MainMenu;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
use SteelAnts\LaravelBoilerplate\Facades\Menu;
|
||||
|
||||
class Navigation extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.navigation', [
|
||||
'mainMenuItems' => Menu::get('main-menu')->items() ?? [],
|
||||
'systemMenuItems' => Menu::get('system-menu')->items() ?? [],
|
||||
]);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
web: __DIR__.'/../routes/web.php',
|
||||
api: __DIR__.'/../routes/api.php',
|
||||
commands: __DIR__.'/../routes/console.php',
|
||||
health: '/up',
|
||||
)
|
||||
|
@ -7,6 +7,7 @@
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"laravel/framework": "^11.9",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.9",
|
||||
"steelants/laravel-boilerplate": "^1.2"
|
||||
},
|
||||
|
66
composer.lock
generated
66
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e715a272525d211a1790b5afb115f9be",
|
||||
"content-hash": "d901703e92531876ccae1dd19ef6f1cb",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@ -1314,6 +1314,70 @@
|
||||
},
|
||||
"time": "2024-06-17T13:58:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sanctum",
|
||||
"version": "v4.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/sanctum.git",
|
||||
"reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/sanctum/zipball/9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
|
||||
"reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"illuminate/console": "^11.0",
|
||||
"illuminate/contracts": "^11.0",
|
||||
"illuminate/database": "^11.0",
|
||||
"illuminate/support": "^11.0",
|
||||
"php": "^8.2",
|
||||
"symfony/console": "^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"orchestra/testbench": "^9.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Sanctum\\SanctumServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Sanctum\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
|
||||
"keywords": [
|
||||
"auth",
|
||||
"laravel",
|
||||
"sanctum"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/sanctum/issues",
|
||||
"source": "https://github.com/laravel/sanctum"
|
||||
},
|
||||
"time": "2024-04-10T19:39:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v1.3.3",
|
||||
|
158
config/livewire.php
Normal file
158
config/livewire.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Class Namespace
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the root namespace for Livewire component classes in
|
||||
| your application. This value affects component auto-discovery and
|
||||
| any Livewire file helper commands, like `artisan make:livewire`.
|
||||
|
|
||||
| After changing this item, run: `php artisan livewire:discover`.
|
||||
|
|
||||
*/
|
||||
|
||||
'class_namespace' => 'App\\Livewire',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| View Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the path for Livewire component views. This affects
|
||||
| file manipulation helper commands like `artisan make:livewire`.
|
||||
|
|
||||
*/
|
||||
|
||||
'view_path' => resource_path('views/livewire'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Layout
|
||||
|--------------------------------------------------------------------------
|
||||
| The default layout view that will be used when rendering a component via
|
||||
| Route::get('/some-endpoint', SomeComponent::class);. In this case the
|
||||
| the view returned by SomeComponent will be wrapped in "layouts.app"
|
||||
|
|
||||
*/
|
||||
|
||||
'layout' => 'components.layout-app',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire Assets URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the path to Livewire JavaScript assets, for cases where
|
||||
| your app's domain root is not the correct path. By default, Livewire
|
||||
| will load its JavaScript assets from the app's "relative root".
|
||||
|
|
||||
| Examples: "/assets", "myurl.com/app".
|
||||
|
|
||||
*/
|
||||
|
||||
'asset_url' => env('ASSET_URL', null),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire App URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value should be used if livewire assets are served from CDN.
|
||||
| Livewire will communicate with an app through this url.
|
||||
|
|
||||
| Examples: "https://my-app.com", "myurl.com/app".
|
||||
|
|
||||
*/
|
||||
|
||||
'app_url' => env('ASSET_URL', null),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire Endpoint Middleware Group
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the middleware group that will be applied to the main
|
||||
| Livewire "message" endpoint (the endpoint that gets hit everytime
|
||||
| a Livewire component updates). It is set to "web" by default.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware_group' => 'web',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire Temporary File Uploads Endpoint Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Livewire handles file uploads by storing uploads in a temporary directory
|
||||
| before the file is validated and stored permanently. All file uploads
|
||||
| are directed to a global endpoint for temporary storage. The config
|
||||
| items below are used for customizing the way the endpoint works.
|
||||
|
|
||||
*/
|
||||
|
||||
'temporary_file_upload' => [
|
||||
'disk' => null, // Example: 'local', 's3' Default: 'default'
|
||||
'rules' => null, // Example: ['file', 'mimes:png,jpg'] Default: ['required', 'file', 'max:12288'] (12MB)
|
||||
'directory' => null, // Example: 'tmp' Default 'livewire-tmp'
|
||||
'middleware' => null, // Example: 'throttle:5,1' Default: 'throttle:60,1'
|
||||
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs.
|
||||
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
|
||||
'mov', 'avi', 'wmv', 'mp3', 'm4a',
|
||||
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
|
||||
],
|
||||
'max_upload_time' => 5, // Max duration (in minutes) before an upload gets invalidated.
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Manifest File Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value sets the path to the Livewire manifest file.
|
||||
| The default should work for most cases (which is
|
||||
| "<app_root>/bootstrap/cache/livewire-components.php"), but for specific
|
||||
| cases like when hosting on Laravel Vapor, it could be set to a different value.
|
||||
|
|
||||
| Example: for Laravel Vapor, it would be "/tmp/storage/bootstrap/cache/livewire-components.php".
|
||||
|
|
||||
*/
|
||||
|
||||
'manifest_path' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Back Button Cache
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines whether the back button cache will be used on pages
|
||||
| that contain Livewire. By disabling back button cache, it ensures that
|
||||
| the back button shows the correct state of components, instead of
|
||||
| potentially stale, cached data.
|
||||
|
|
||||
| Setting it to "false" (default) will disable back button cache.
|
||||
|
|
||||
*/
|
||||
|
||||
'back_button_cache' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Render On Redirect
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value determines whether Livewire will render before it's redirected
|
||||
| or not. Setting it to "false" (default) will mean the render method is
|
||||
| skipped when redirecting. And "true" will mean the render method is
|
||||
| run before redirecting. Browsers bfcache can store a potentially
|
||||
| stale view if render is skipped on redirect.
|
||||
|
|
||||
*/
|
||||
|
||||
'render_on_redirect' => false,
|
||||
|
||||
];
|
@ -12,13 +12,13 @@
|
||||
| Default Log Channel
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option defines the default log channel that is utilized to write
|
||||
| messages to your logs. The value provided here should match one of
|
||||
| the channels present in the list of "channels" configured below.
|
||||
| This option defines the default log channel that gets used when writing
|
||||
| messages to the logs. The name specified in this option should match
|
||||
| one of the channels defined in the "channels" configuration array.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
'default' => env('LOG_CHANNEL', 'daily'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@ -33,7 +33,7 @@
|
||||
|
||||
'deprecations' => [
|
||||
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
|
||||
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
|
||||
'trace' => false,
|
||||
],
|
||||
|
||||
/*
|
||||
@ -41,20 +41,20 @@
|
||||
| Log Channels
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log channels for your application. Laravel
|
||||
| utilizes the Monolog PHP logging library, which includes a variety
|
||||
| of powerful log handlers and formatters that you're free to use.
|
||||
| Here you may configure the log channels for your application. Out of
|
||||
| the box, Laravel uses the Monolog PHP logging library. This gives
|
||||
| you a variety of powerful log handlers / formatters to utilize.
|
||||
|
|
||||
| Available drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog", "custom", "stack"
|
||||
| Available Drivers: "single", "daily", "slack", "syslog",
|
||||
| "errorlog", "monolog",
|
||||
| "custom", "stack"
|
||||
|
|
||||
*/
|
||||
|
||||
'channels' => [
|
||||
|
||||
'stack' => [
|
||||
'driver' => 'stack',
|
||||
'channels' => explode(',', env('LOG_STACK', 'single')),
|
||||
'channels' => ['single'],
|
||||
'ignore_exceptions' => false,
|
||||
],
|
||||
|
||||
@ -69,15 +69,15 @@
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'days' => env('LOG_DAILY_DAYS', 14),
|
||||
'days' => 14,
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
|
||||
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
|
||||
'username' => 'Laravel Log',
|
||||
'emoji' => ':boom:',
|
||||
'level' => env('LOG_LEVEL', 'critical'),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
@ -108,7 +108,7 @@
|
||||
'syslog' => [
|
||||
'driver' => 'syslog',
|
||||
'level' => env('LOG_LEVEL', 'debug'),
|
||||
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
|
||||
'facility' => LOG_USER,
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
@ -126,7 +126,6 @@
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
83
config/sanctum.php
Normal file
83
config/sanctum.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Stateful Domains
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Requests from the following domains / hosts will receive stateful API
|
||||
| authentication cookies. Typically, these should include your local
|
||||
| and production domains which access your API via a frontend SPA.
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'%s%s',
|
||||
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||
Sanctum::currentApplicationUrlWithPort()
|
||||
))),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This array contains the authentication guards that will be checked when
|
||||
| Sanctum is trying to authenticate a request. If none of these guards
|
||||
| are able to authenticate the request, Sanctum will use the bearer
|
||||
| token that's present on an incoming request for authentication.
|
||||
|
|
||||
*/
|
||||
|
||||
'guard' => ['web'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expiration Minutes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This value controls the number of minutes until an issued token will be
|
||||
| considered expired. This will override any values set in the token's
|
||||
| "expires_at" attribute, but first-party sessions are not affected.
|
||||
|
|
||||
*/
|
||||
|
||||
'expiration' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Token Prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Sanctum can prefix new tokens in order to take advantage of numerous
|
||||
| security scanning initiatives maintained by open source platforms
|
||||
| that notify developers if they commit tokens into repositories.
|
||||
|
|
||||
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
||||
|
|
||||
*/
|
||||
|
||||
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Sanctum Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When authenticating your first-party SPA with Sanctum you may need to
|
||||
| customize some of the middleware Sanctum uses while processing the
|
||||
| request. You may change the middleware listed below as required.
|
||||
|
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
],
|
||||
|
||||
];
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use App\Models\User;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('activities', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('ip')->nullable();
|
||||
$table->foreignId("user_id")->nullable();
|
||||
$table->foreign("user_id")->references("id")->on("users")->constrained();
|
||||
$table->nullableMorphs('affected');
|
||||
$table->string('lang_text')->nullable();
|
||||
$table->json('data')->nullable();
|
||||
$table->timestamp("created_at");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('activities');
|
||||
}
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('subscriptions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('tier');
|
||||
$table->date('valid_to');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('subscriptions');
|
||||
}
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('settings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->nullableMorphs('settable');
|
||||
$table->string('index')->indexed();
|
||||
$table->string('type', 50)->default('string'); //TODO: @Vasek Maybe use Set
|
||||
$table->text('value');
|
||||
$table->timestamps();
|
||||
$table->unique(['settable_type', 'settable_id', 'index']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('settings');
|
||||
}
|
||||
};
|
@ -2,7 +2,7 @@
|
||||
|
||||
use App\Models\Host;
|
||||
use App\Models\MaintenanceHistory;
|
||||
use Illuminate\Console\View\Components\Task;
|
||||
use App\Models\MaintenanceTask;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@ -16,7 +16,7 @@ public function up(): void
|
||||
{
|
||||
Schema::create('maintenance_task_histories', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(Task::class);
|
||||
$table->foreignIdFor(MaintenanceTask::class);
|
||||
$table->foreignIdFor(MaintenanceHistory::class);
|
||||
$table->foreignIdFor(Host::class);
|
||||
$table->string('status');
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->string('name');
|
||||
$table->string('token', 64)->unique();
|
||||
$table->text('abilities')->nullable();
|
||||
$table->timestamp('last_used_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('personal_access_tokens');
|
||||
}
|
||||
};
|
296
package-lock.json
generated
296
package-lock.json
generated
@ -4,6 +4,17 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"bootstrap": "^5.3",
|
||||
"jquery": "^3.6.1",
|
||||
"laravel-vite-plugin": "^1.0.0",
|
||||
"quill": "2.0.0-rc.2",
|
||||
"quill-table-ui": "^1.0.7",
|
||||
"sass": "^1.56.1",
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.6.4",
|
||||
"laravel-vite-plugin": "^1.0",
|
||||
@ -378,6 +389,24 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free": {
|
||||
"version": "5.15.4",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz",
|
||||
"integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz",
|
||||
@ -592,6 +621,18 @@
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@ -609,6 +650,69 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
||||
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@ -668,6 +772,27 @@
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
@ -706,7 +831,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
@ -716,6 +840,65 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
|
||||
"integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw=="
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jquery": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.5.tgz",
|
||||
@ -735,6 +918,21 @@
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
@ -774,6 +972,19 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parchment": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
|
||||
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A=="
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||
@ -784,7 +995,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
@ -792,6 +1002,11 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/positioning": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/positioning/-/positioning-2.0.1.tgz",
|
||||
"integrity": "sha512-DsAgM42kV/ObuwlRpAzDTjH9E8fGKkMDJHWFX+kfNXSxh7UCCQxEmdjv/Ws5Ft1XDnt3JT8fIDYeKNSE2TbttA=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.40",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
|
||||
@ -826,6 +1041,55 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/quill": {
|
||||
"version": "2.0.0-rc.2",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.0-rc.2.tgz",
|
||||
"integrity": "sha512-3uh7uZqXpN4t0HmHCjBHXSaeSgYNij6LP1t8ncEjr6+JMq5zR6svpH5Frx7Lxmxv1DUHYDE28eV7R3f4r8Z2Kw==",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"parchment": "^3.0.0-alpha.2",
|
||||
"quill-delta": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=8.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/quill-delta": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
|
||||
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/quill-table-ui": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/quill-table-ui/-/quill-table-ui-1.0.7.tgz",
|
||||
"integrity": "sha512-Zr/KWiLmCkaaS1eybwkQX17FUGJEBvpHAOSP7A8J+E2P4R56S+Uvs+U2i+fIxaydfnPksDV/sDJ8EWsFg37dsQ==",
|
||||
"dependencies": {
|
||||
"positioning": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.1.tgz",
|
||||
@ -861,15 +1125,41 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.77.8",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz",
|
||||
"integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==",
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz",
|
||||
|
11
package.json
11
package.json
@ -9,5 +9,16 @@
|
||||
"axios": "^1.6.4",
|
||||
"laravel-vite-plugin": "^1.0",
|
||||
"vite": "^5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"jquery": "^3.6.1",
|
||||
"sass": "^1.56.1",
|
||||
"bootstrap": "^5.3",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"vite": "^5.0.0",
|
||||
"laravel-vite-plugin": "^1.0.0",
|
||||
"quill": "2.0.0-rc.2",
|
||||
"quill-table-ui": "^1.0.7"
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,3 @@
|
||||
import './bootstrap';
|
||||
import './functions';
|
||||
import './quill';
|
43
resources/js/bootstrap.js
vendored
43
resources/js/bootstrap.js
vendored
@ -1,4 +1,41 @@
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
// import $ from 'jquery';
|
||||
// window.$ = $;
|
||||
|
||||
import $ from 'jquery';
|
||||
window.$ = window.jQuery = $;
|
||||
|
||||
import * as bootstrap from 'bootstrap';
|
||||
window.bootstrap = bootstrap;
|
||||
|
||||
// import axios from 'axios';
|
||||
// window.axios = axios;
|
||||
|
||||
// window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// import Pusher from 'pusher-js';
|
||||
// window.Pusher = Pusher;
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||||
// wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
|
||||
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
|
||||
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
|
||||
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
|
||||
// enabledTransports: ['ws', 'wss'],
|
||||
// });
|
||||
|
86
resources/js/functions.js
Normal file
86
resources/js/functions.js
Normal file
@ -0,0 +1,86 @@
|
||||
window.setCookie = function(name, value, days) {
|
||||
var expires = "";
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (days*24*60*60*1000));
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
}
|
||||
|
||||
window.getCookie = function(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0;i < ca.length;i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
window.eraseCookie = function(name) {
|
||||
document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
|
||||
}
|
||||
|
||||
window.toggleDatkTheme = function(){
|
||||
if(document.getElementById('datk-theme').checked){
|
||||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||||
setCookie('theme', 'dark', 360);
|
||||
}else{
|
||||
document.documentElement.setAttribute('data-bs-theme', 'light');
|
||||
setCookie('theme', 'light', 360);
|
||||
}
|
||||
}
|
||||
|
||||
window.snackbar = function (message, details = false, type = false, icon = false){
|
||||
|
||||
var template = `
|
||||
<div class="snackbar alert border-0">
|
||||
<button type="button" class="btn-close btn-close-white close ${details ? '' : 'mt-2'}" data-bs-dismiss="alert"></button>
|
||||
|
||||
<div class="alert-content">`;
|
||||
|
||||
if(icon){
|
||||
template += `<i class="alert-ico ${icon} text-${type}"></i>`;
|
||||
}
|
||||
|
||||
template += `
|
||||
<div>
|
||||
<div class="alert-title ${details ? '' : 'mt-2'}">${message}</div>`
|
||||
if(details){
|
||||
template += `<div class="alert-text">${details}</div>`
|
||||
}
|
||||
|
||||
template += `
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
document.querySelector('.snackbar-container').insertAdjacentHTML('beforeend', template);
|
||||
}
|
||||
|
||||
window.addEventListener('snackbar', function(e){
|
||||
snackbar(e.detail.message, e.detail.details || false, e.detail.type || false, e.detail.icon || false);
|
||||
});
|
||||
|
||||
window.copyToClipboard = function (text, el = false) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text).then(() => {})
|
||||
.catch(() => {snackbar('something went wrong');});
|
||||
} else {
|
||||
let textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
textArea.style.position = "fixed";
|
||||
textArea.style.left = "-999999px";
|
||||
textArea.style.top = "-999999px";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
new Promise((res, rej) => {
|
||||
document.execCommand('copy') ? res() : rej();
|
||||
textArea.remove();
|
||||
});
|
||||
}
|
||||
snackbar('Copied to clipboard');
|
||||
}
|
77
resources/js/quill.js
Normal file
77
resources/js/quill.js
Normal file
@ -0,0 +1,77 @@
|
||||
import Quill from 'quill';
|
||||
window.Quill = Quill;
|
||||
|
||||
import * as quillTableUI from 'quill-table-ui'
|
||||
|
||||
Quill.register({
|
||||
'modules/tableUI': quillTableUI.default
|
||||
}, true);
|
||||
|
||||
// Quill.register(Quill.import('attributors/attribute/direction'), true);
|
||||
// Quill.register(Quill.import('attributors/class/align'), true);
|
||||
// Quill.register(Quill.import('attributors/class/background'), true);
|
||||
// Quill.register(Quill.import('attributors/class/color'), true);
|
||||
// Quill.register(Quill.import('attributors/class/direction'), true);
|
||||
// Quill.register(Quill.import('attributors/class/font'), true);
|
||||
// Quill.register(Quill.import('attributors/class/size'), true);
|
||||
Quill.register(Quill.import('attributors/style/align'), true);
|
||||
Quill.register(Quill.import('attributors/style/background'), true);
|
||||
Quill.register(Quill.import('attributors/style/color'), true);
|
||||
Quill.register(Quill.import('attributors/style/direction'), true);
|
||||
Quill.register(Quill.import('attributors/style/font'), true);
|
||||
Quill.register(Quill.import('attributors/style/size'), true);
|
||||
|
||||
window.loadQuill = function(){
|
||||
document.querySelectorAll('.quill-editor:not(.ready)').forEach(function(element){
|
||||
let container = element.closest('.quill-container');
|
||||
let textarea = container.querySelector('.quill-textarea');
|
||||
|
||||
const toolbarOptions = [
|
||||
[{ header: [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image'],
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
|
||||
['table'],
|
||||
['clean'],
|
||||
];
|
||||
|
||||
let quill = new Quill(element, {
|
||||
theme: 'snow',
|
||||
modules: {
|
||||
table: true,
|
||||
tableUI: true,
|
||||
toolbar: {
|
||||
container: toolbarOptions,
|
||||
handlers: {
|
||||
table: function() {
|
||||
this.quill.getModule('table').insertTable(3, 3);
|
||||
}
|
||||
}
|
||||
},
|
||||
clipboard: {
|
||||
matchVisual: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// let table = quill.getModule('table')
|
||||
// container.querySelector('.ql-insert-table').addEventListener('click', function(){
|
||||
// console.log('click');
|
||||
// table.insertTable(2, 2);
|
||||
// });
|
||||
|
||||
quill.root.innerHTML = textarea.value;
|
||||
|
||||
quill.on('text-change', function () {
|
||||
let value = quill.root.innerHTML;
|
||||
textarea.value = value;
|
||||
textarea.dispatchEvent(new Event('input'));
|
||||
console.log(quill.getContents());
|
||||
});
|
||||
|
||||
element.classList.add('ready');
|
||||
container.querySelector('.quill-loading').remove();
|
||||
});
|
||||
}
|
||||
window.loadQuill();
|
90
resources/sass/_quill.scss
Normal file
90
resources/sass/_quill.scss
Normal file
@ -0,0 +1,90 @@
|
||||
@import "./quill/snow.scss";
|
||||
@import "quill-table-ui/src/index.scss";
|
||||
|
||||
.quill-editor-wrap{
|
||||
position: relative;
|
||||
min-height: 9rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.quill-editor-wrap textarea{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quill-editor{
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.quill-editor .ql-editor{
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.quill-loading{
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
border-radius: var(--bs-border-radius);
|
||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
.quill-editor.ready + .quill-loading{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.ql-toolbar.ql-snow{
|
||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
||||
border-radius: var(--bs-border-radius) var(--bs-border-radius) 0 0;
|
||||
}
|
||||
|
||||
.ql-container.ql-snow{
|
||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
||||
border-top: 0;
|
||||
border-radius: 0 0 var(--bs-border-radius) var(--bs-border-radius);
|
||||
}
|
||||
|
||||
.ql-table-menu{
|
||||
background: var(--bs-body-bg);
|
||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
||||
}
|
||||
.ql-table-menu__item-icon svg *{
|
||||
fill: var(--bs-secondary);
|
||||
}
|
||||
.ql-table-menu__item:hover{
|
||||
background: var(--bs-tertiary-bg);
|
||||
}
|
||||
|
||||
// .ql-snow.ql-toolbar button:hover, .ql-snow .ql-toolbar button:hover, .ql-snow.ql-toolbar button:focus, .ql-snow .ql-toolbar button:focus, .ql-snow.ql-toolbar button.ql-active, .ql-snow .ql-toolbar button.ql-active, .ql-snow.ql-toolbar .ql-picker-label:hover, .ql-snow .ql-toolbar .ql-picker-label:hover, .ql-snow.ql-toolbar .ql-picker-label.ql-active, .ql-snow .ql-toolbar .ql-picker-label.ql-active, .ql-snow.ql-toolbar .ql-picker-item:hover, .ql-snow .ql-toolbar .ql-picker-item:hover, .ql-snow.ql-toolbar .ql-picker-item.ql-selected, .ql-snow .ql-toolbar .ql-picker-item.ql-selected{
|
||||
// color: var(--bs-primary);
|
||||
// }
|
||||
// .ql-snow.ql-toolbar button:hover .ql-stroke, .ql-snow .ql-toolbar button:hover .ql-stroke, .ql-snow.ql-toolbar button:focus .ql-stroke, .ql-snow .ql-toolbar button:focus .ql-stroke, .ql-snow.ql-toolbar button.ql-active .ql-stroke, .ql-snow .ql-toolbar button.ql-active .ql-stroke, .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke, .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke, .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke, .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke, .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke, .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke, .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke, .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke, .ql-snow.ql-toolbar button:hover .ql-stroke-miter, .ql-snow .ql-toolbar button:hover .ql-stroke-miter, .ql-snow.ql-toolbar button:focus .ql-stroke-miter, .ql-snow .ql-toolbar button:focus .ql-stroke-miter, .ql-snow.ql-toolbar button.ql-active .ql-stroke-miter, .ql-snow .ql-toolbar button.ql-active .ql-stroke-miter, .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter, .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter, .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter, .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter, .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter, .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter, .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter{
|
||||
// stroke: var(--bs-primary);
|
||||
// }
|
||||
|
||||
// .ql-snow .ql-stroke{
|
||||
// stroke: var(--bs-body-color);
|
||||
// }
|
||||
|
||||
// .ql-snow .ql-picker{
|
||||
// color: var(--bs-body-color);
|
||||
// }
|
||||
|
||||
// .ql-snow .ql-picker-options{
|
||||
// background-color: var(--bs-body-bg);
|
||||
// color: var(--bs-body-color);
|
||||
// }
|
||||
|
||||
// .ql-toolbar.ql-snow .ql-picker-options{
|
||||
// border: var(--bs-dropdown-border-width) solid var(--bs-border-color-translucent);
|
||||
// }
|
280
resources/sass/admin/_misc.scss
Normal file
280
resources/sass/admin/_misc.scss
Normal file
@ -0,0 +1,280 @@
|
||||
a{
|
||||
// text-decoration: none;
|
||||
}
|
||||
|
||||
.card-header{
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.logincard{
|
||||
width: 430px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.stat{
|
||||
display: flex;
|
||||
align-items: start;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-ico{
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, .7);
|
||||
|
||||
&.is-green{
|
||||
background: rgba(40, 199, 111,.7);
|
||||
}
|
||||
&.is-red{
|
||||
background: rgba(234, 84, 85, .7);
|
||||
}
|
||||
&.is-purple{
|
||||
background: rgba(99, 132, 255, .7)
|
||||
}
|
||||
}
|
||||
|
||||
.stat-name{
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.stat-value{
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.nav-item .stat{
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.btn-facebook{
|
||||
color: white;
|
||||
background: #4267B2;
|
||||
|
||||
&:hover,
|
||||
&:focus{
|
||||
color: white;
|
||||
background: #3a5b9c;
|
||||
}
|
||||
}
|
||||
|
||||
.table-actions{
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
.list-notifications{
|
||||
width: 360px;
|
||||
max-width: 80vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
// .alert{
|
||||
// position: fixed;
|
||||
// z-index: 100;
|
||||
// bottom: 0;
|
||||
// width: 500px;
|
||||
// right: 2rem;
|
||||
// max-width: calc(100% - 4rem);
|
||||
// }
|
||||
|
||||
|
||||
.card-action {
|
||||
padding: 0 $card-spacer-x $card-spacer-y;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.custom-checkbox{
|
||||
cursor: pointer;
|
||||
|
||||
.custom-control-label{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group label{
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.invalid-feedback strong{
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table th{
|
||||
font-weight: 500;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.form-group{
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.editor{
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.image-thumb{
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&:hover::before{
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0,0,0,.2);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.image-thumb-ico{
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
}
|
||||
.image-thumb:hover .image-thumb-ico{
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.collapsed .collapse-indicator{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
|
||||
.ql-toolbar.ql-snow,
|
||||
.ql-container.ql-snow{
|
||||
background: white;
|
||||
}
|
||||
|
||||
@include color-mode(dark) {
|
||||
.ql-toolbar.ql-snow,
|
||||
.ql-container.ql-snow{
|
||||
background: transparent;
|
||||
border-color: #495057;
|
||||
}
|
||||
|
||||
.ql-editor.ql-blank::before{
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.ql-snow .ql-stroke{
|
||||
stroke: #a5a5a5;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-mobile{
|
||||
@include media-breakpoint-down(md) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
select[multiple]{
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.select2{
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.breadcrumb-item a{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-danger{
|
||||
color: white;
|
||||
|
||||
&:hover{
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.bi{
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
display: inline-block;
|
||||
vertical-align: -.125em;
|
||||
fill: currentcolor;
|
||||
}
|
||||
|
||||
.random-bg-1{
|
||||
background-color: rgba($tw-green-500, .5);
|
||||
}
|
||||
.random-bg-2{
|
||||
background-color: rgba($tw-sky-400, .5);
|
||||
}
|
||||
.random-bg-3{
|
||||
background-color: rgba($tw-red-500, .5);
|
||||
}
|
||||
.random-bg-4{
|
||||
background-color: rgba($tw-yellow-500, .5);
|
||||
}
|
||||
.random-bg-5{
|
||||
background-color: rgba($tw-indigo-500, .5);
|
||||
}
|
||||
.random-bg-6{
|
||||
background-color: rgba($tw-orange-500, .5);
|
||||
}
|
||||
|
||||
.dropdown-menu{
|
||||
box-shadow: var(--bs-dropdown-box-shadow);
|
||||
}
|
||||
|
||||
@include color-mode(dark) {
|
||||
.dropdown-menu{
|
||||
background-color: #0f0f0f;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-item{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdown-ico{
|
||||
width: 1.25rem;
|
||||
height: 1rem;
|
||||
text-align: center;
|
||||
line-height: 1rem;
|
||||
color: var(--bs-tertiary-color);
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.dropdown-img{
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
line-height: 2rem;
|
||||
margin-right: .75rem;
|
||||
font-size: .875rem;
|
||||
|
||||
img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.page-link{
|
||||
border-radius: $pagination-border-radius;
|
||||
}
|
5
resources/sass/admin/_variables-dark.scss
Normal file
5
resources/sass/admin/_variables-dark.scss
Normal file
@ -0,0 +1,5 @@
|
||||
$body-color-dark: $gray-300;
|
||||
$body-bg-dark: #000;
|
||||
|
||||
$body-secondary-bg-dark: $gray-800;
|
||||
$body-tertiary-bg-dark: $gray-900;
|
113
resources/sass/admin/_variables.scss
Normal file
113
resources/sass/admin/_variables.scss
Normal file
@ -0,0 +1,113 @@
|
||||
$enable-container-classes: false;
|
||||
$container-max-widths: (
|
||||
xs: 360px,
|
||||
sm: 540px,
|
||||
md: 720px,
|
||||
lg: 960px,
|
||||
xl: 1140px,
|
||||
xxl: 1320px
|
||||
);
|
||||
|
||||
$gray-100: $tw-neutral-100;
|
||||
$gray-200: $tw-neutral-200;
|
||||
$gray-300: $tw-neutral-300;
|
||||
$gray-400: $tw-neutral-400;
|
||||
$gray-500: $tw-neutral-500;
|
||||
$gray-600: $tw-neutral-600;
|
||||
$gray-700: $tw-neutral-700;
|
||||
$gray-800: $tw-neutral-800;
|
||||
$gray-900: $tw-neutral-900;
|
||||
|
||||
$blue: $tw-blue-500 !default;
|
||||
$indigo: $tw-indigo-500 !default;
|
||||
$purple: $tw-purple-500 !default;
|
||||
$pink: $tw-pink-500 !default;
|
||||
$red: $tw-red-500 !default;
|
||||
$orange: $tw-amber-500 !default;
|
||||
$yellow: $tw-yellow-500 !default;
|
||||
$green: $tw-green-500 !default;
|
||||
$teal: $tw-teal-500 !default;
|
||||
$cyan: $tw-cyan-500 !default;
|
||||
|
||||
// Body
|
||||
$body-color: $gray-900;
|
||||
$body-bg: #fff;
|
||||
|
||||
$primary: $tw-indigo-600;
|
||||
// $body-secondary-bg: #ededed;
|
||||
$body-secondary-bg: #f0f0f0;
|
||||
$body-tertiary-bg: $tw-neutral-050;
|
||||
|
||||
// Typography
|
||||
$font-family-sans-serif: 'Inter', sans-serif;
|
||||
$h1-font-size: 2.25rem;
|
||||
$h2-font-size: 1.875rem;
|
||||
$h3-font-size: 1.5rem;
|
||||
$h4-font-size: 1.25rem;
|
||||
$h5-font-size: 1.125rem;
|
||||
|
||||
$card-cap-bg: #f9fbfc;
|
||||
|
||||
$border-radius: 6px;
|
||||
$card-spacer-y: 1rem;
|
||||
|
||||
$headings-font-weight: 700;
|
||||
|
||||
$text-muted: #a7abc3;
|
||||
|
||||
$border-color-translucent: rgba($gray-900, .075);
|
||||
|
||||
$box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
$box-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
$box-shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
|
||||
// $btn-font-size: 0.875rem;
|
||||
$btn-font-weight: 500;
|
||||
$input-btn-padding-x: .75rem;
|
||||
$input-btn-padding-y: .375rem;
|
||||
|
||||
$navbar-brand-font-size: 1rem;
|
||||
|
||||
$nav-link-font-weight: 500;
|
||||
$nav-tabs-link-active-color: $primary;
|
||||
$nav-tabs-link-active-border-color: $primary;
|
||||
$nav-tabs-link-hover-border-color: var(--bs-border-color);
|
||||
$nav-tabs-border-width: 2px;
|
||||
$nav-tabs-border-radius: 0;
|
||||
$nav-tabs-link-active-bg: transparent;
|
||||
$nav-tabs-border-color: var(--bs-secondary-bg);
|
||||
$nav-pills-link-active-color: var(--bs-body-color);
|
||||
$nav-pills-link-active-bg: var(--bs-body-bg);
|
||||
|
||||
$label-margin-bottom: .25rem;
|
||||
|
||||
$alert-padding-x: 1rem;
|
||||
|
||||
$paragraph-margin-bottom: .5rem;
|
||||
|
||||
$badge-font-weight: 500;
|
||||
|
||||
$breadcrumb-divider: '›';
|
||||
|
||||
$dropdown-padding-x: .5rem;
|
||||
$dropdown-padding-y: .5rem;
|
||||
$dropdown-item-padding-y: .4rem;
|
||||
$dropdown-item-padding-x: .4rem;
|
||||
$dropdown-border-radius: calc(4px + .5rem);
|
||||
:root{
|
||||
--bs-dropdown-item-border-radius: 4px;
|
||||
}
|
||||
$dropdown-link-active-bg: var(--bs-secondary-bg);
|
||||
$dropdown-link-active-color: var(--bs-body-color);
|
||||
$dropdown-border-color: rgba($gray-500, .075);
|
||||
$dropdown-divider-bg: rgba($gray-500, .1);
|
||||
|
||||
$pagination-color: var(--bs-body-color);
|
||||
$pagination-border-width: 0;
|
||||
$pagination-bg: transparent;
|
||||
$pagination-active-bg: var(--bs-secondary-bg);
|
||||
$pagination-active-color: var(--bs-body-color);
|
||||
$pagination-hover-color: var(--bs-body-color);
|
||||
$pagination-focus-color: var(--bs-body-color);
|
||||
$pagination-disabled-color: var(--bs-tertiary-color);
|
||||
$pagination-disabled-bg: transparent;
|
144
resources/sass/admin/components/_nav.scss
Normal file
144
resources/sass/admin/components/_nav.scss
Normal file
@ -0,0 +1,144 @@
|
||||
.app-nav{
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app-nav-header,
|
||||
.app-nav-user{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: var(--bs-body-color);
|
||||
text-decoration: none;
|
||||
padding-right: .5rem;
|
||||
padding-left: .5rem;
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover,
|
||||
&.show{
|
||||
background-color: var(--bs-secondary-bg);
|
||||
}
|
||||
|
||||
&+.dropdown-menu{
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.app-nav-header-content{
|
||||
line-height: 1;
|
||||
padding: .5rem .25rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
& > *{
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.app-nav-logo,
|
||||
.app-nav-profile{
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
margin: 0.5rem;
|
||||
margin-left: 0;
|
||||
|
||||
img{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.app-nav-profile{
|
||||
border-radius: 100%;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.app-nav .nav-link{
|
||||
border-radius: 6px;
|
||||
color: var(--bs-body-color);
|
||||
padding: 0.5rem .75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover{
|
||||
color: var(--bs-body-color);
|
||||
background: var(--bs-secondary-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link-ico{
|
||||
margin-right: .75rem;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
text-align: center;
|
||||
line-height: 1.25rem;
|
||||
color: var(--bs-tertiary-color);
|
||||
}
|
||||
|
||||
.app-nav .is-active{
|
||||
.nav-link{
|
||||
background: var(--bs-secondary-bg);
|
||||
}
|
||||
.nav-link-ico{
|
||||
color: var(--bs-secodnary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs{
|
||||
.nav-link{
|
||||
border-top: none !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
color: var(--bs-tertiary-color);
|
||||
|
||||
&.disabled,
|
||||
&:disabled{
|
||||
color: var(--bs-tertiary-color);
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.active,
|
||||
.nav-item.show .nav-link {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-pills{
|
||||
border-radius: calc($nav-pills-border-radius + 3px);
|
||||
background-color: var(--bs-tertiary-bg);
|
||||
padding: 3px;
|
||||
width: max-content;
|
||||
align-items: center;
|
||||
|
||||
.nav-link{
|
||||
padding: calc(var(--#{$prefix}nav-link-padding-y) - 2px) calc(var(--#{$prefix}nav-link-padding-x) - 1px);
|
||||
color: var(--bs-tertiary-color);
|
||||
|
||||
&:hover{
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
&.disabled,
|
||||
&:disabled{
|
||||
color: var(--bs-tertiary-color);
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link.active,
|
||||
.nav-item.show .nav-link {
|
||||
color: var(--bs-body-color);
|
||||
box-shadow: 0 0 1px 1px var(--bs-secondary-bg), inset 0 0 1px $gray-500;
|
||||
background-color: var(--bs-body-bg);
|
||||
}
|
||||
}
|
64
resources/sass/admin/components/_snackbar.scss
Normal file
64
resources/sass/admin/components/_snackbar.scss
Normal file
@ -0,0 +1,64 @@
|
||||
.snackbar-container {
|
||||
position: fixed;
|
||||
top: 65px;
|
||||
right: 2rem;
|
||||
height: auto;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// flex-direction: column;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.snackbar {
|
||||
position: relative;
|
||||
color: var(--bs-body-color);
|
||||
background: var(--bs-body-bg);
|
||||
max-width: 360px;
|
||||
font-size: 0.875rem;
|
||||
box-shadow: 0 4px 6px -1px rgb(0 0 0/0.1), 0 2px 4px -2px rgb(0 0 0/0.1);
|
||||
border: 1px solid var(--bs-secondary-bg);
|
||||
|
||||
margin-right: -400px;
|
||||
opacity: 0;
|
||||
animation: slideSnackbar 3s forwards;
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
@keyframes slideSnackbar {
|
||||
6% {
|
||||
margin-right: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
94% {
|
||||
margin-right: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
99% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.alert-ico {
|
||||
font-size: 1.1rem;
|
||||
margin-right: 0.75rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.alert-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert .close {
|
||||
float: right;
|
||||
}
|
68
resources/sass/admin/components/_tables.scss
Normal file
68
resources/sass/admin/components/_tables.scss
Normal file
@ -0,0 +1,68 @@
|
||||
.table thead th{
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--bs-secondary-color);
|
||||
background: var(--bs-tertiary-bg);
|
||||
|
||||
&:first-child{
|
||||
border-radius: $border-radius 0 0 $border-radius;
|
||||
}
|
||||
|
||||
&:last-child{
|
||||
border-radius: 0 $border-radius $border-radius 0;
|
||||
}
|
||||
}
|
||||
|
||||
.table thead + tbody tr:first-child td{
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.table tbody tr:last-child td{
|
||||
&:first-child{
|
||||
border-radius: 0 0 0 $border-radius;
|
||||
}
|
||||
|
||||
&:last-child{
|
||||
border-radius: 0 0 $border-radius 0;
|
||||
}
|
||||
}
|
||||
|
||||
.table-selectable th:first-child,
|
||||
.table-selectable td:first-child{
|
||||
padding-right: 0;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.table-selectable th:last-child,
|
||||
.table-selectable td:last-child{
|
||||
width: 1px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table-selectable tbody tr:hover td{
|
||||
cursor: pointer;
|
||||
background: hsl(220, 20%, 99%);
|
||||
}
|
||||
|
||||
.table-selectable tbody tr.selected td{
|
||||
background: #f6f5ff;
|
||||
}
|
||||
|
||||
.table-selectable .custom-control{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.datatable-head-sort{
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
margin: -4px -6px;
|
||||
|
||||
&:hover{
|
||||
background: var(--bs-secondary-bg);
|
||||
}
|
||||
}
|
10
resources/sass/admin/layout/_containers.scss
Normal file
10
resources/sass/admin/layout/_containers.scss
Normal file
@ -0,0 +1,10 @@
|
||||
.container{
|
||||
@include make-container();
|
||||
}
|
||||
|
||||
@each $breakpoint, $container-max-width in $container-max-widths {
|
||||
.container-#{$breakpoint} {
|
||||
@extend .container;
|
||||
max-width: $container-max-width;
|
||||
}
|
||||
}
|
27
resources/sass/admin/layout/_layout-nav-default.scss
Normal file
27
resources/sass/admin/layout/_layout-nav-default.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.layout-nav{
|
||||
width: 260px;
|
||||
flex-basis: 260px;
|
||||
flex-shrink: 0;
|
||||
background: var(--bs-tertiary-bg);
|
||||
padding: .75rem;
|
||||
border-right: 1px solid var(--bs-secondary-bg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
display: none;
|
||||
z-index: 99;
|
||||
height: 100%;
|
||||
max-height: calc(100vh - 60px);
|
||||
border: none;
|
||||
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
width: 100%;
|
||||
|
||||
&.layout-nav-open{
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
52
resources/sass/admin/layout/_layout-nav-mobile.scss
Normal file
52
resources/sass/admin/layout/_layout-nav-mobile.scss
Normal file
@ -0,0 +1,52 @@
|
||||
.layout-nav-mobile{
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
.layout-nav-mobile{
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 90;
|
||||
width: 100%;
|
||||
flex-basis: 100%;
|
||||
padding: .5rem;
|
||||
padding-bottom: calc(.5rem + env(safe-area-inset-bottom, 0));
|
||||
background: var(--bs-tertiary-bg);
|
||||
border-top: 1px solid var(--bs-secondary-bg);
|
||||
box-shadow: 0 0px 6px -1px rgb(0 0 0/0.1),0 2px 4px -2px rgb(0 0 0/0.1);
|
||||
|
||||
.app-nav{
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.app-nav > *:not(.nav-item-mobile) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-nav .nav-item-mobile{
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.app-nav .nav-item-mobile .nav-link{
|
||||
padding: .5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.app-nav .nav-item-mobile .nav-link-ico{
|
||||
margin-right: 0;
|
||||
margin-bottom: .25rem;
|
||||
margin-top: .25rem;
|
||||
}
|
||||
|
||||
.app-nav .nav-item-mobile .nav-link-content{
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
75
resources/sass/admin/layout/_layout.scss
Normal file
75
resources/sass/admin/layout/_layout.scss
Normal file
@ -0,0 +1,75 @@
|
||||
html{
|
||||
@include media-breakpoint-up(lg) {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
body{
|
||||
height: 100vh;
|
||||
padding-top: env(safe-area-inset-top, 0);
|
||||
}
|
||||
|
||||
#app{
|
||||
height: 100%;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
|
||||
& > .layout{
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
padding-top: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.layout{
|
||||
display: flex;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layout-content{
|
||||
flex-grow: 1;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.layout-content .content{
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-main{
|
||||
display: none;
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
display: block;
|
||||
height: 60px;
|
||||
flex-basis: 60px;
|
||||
background-color: var(--bs-body-bg);
|
||||
border-bottom: 1px solid var(--bs-secondary-bg);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header{
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
h1{
|
||||
margin-bottom: 0;
|
||||
font-size: $h2-font-size;
|
||||
padding: .25rem 0;
|
||||
}
|
||||
}
|
23
resources/sass/app.scss
Normal file
23
resources/sass/app.scss
Normal file
@ -0,0 +1,23 @@
|
||||
@import "@fortawesome/fontawesome-free/css/all.css";
|
||||
|
||||
// Variables
|
||||
@import "./tailwind/variables/colors";
|
||||
@import "./admin/variables";
|
||||
@import "./admin/variables-dark";
|
||||
|
||||
// Bootstrap
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
|
||||
@import "./quill";
|
||||
|
||||
// Other
|
||||
@import "./admin/misc";
|
||||
|
||||
@import "./admin/layout/layout";
|
||||
@import "./admin/layout/containers";
|
||||
@import "./admin/layout/layout-nav-default";
|
||||
@import "./admin/layout/layout-nav-mobile";
|
||||
|
||||
@import "./admin/components/nav";
|
||||
@import "./admin/components/tables";
|
||||
@import "./admin/components/snackbar";
|
471
resources/sass/quill/_base.scss
Normal file
471
resources/sass/quill/_base.scss
Normal file
@ -0,0 +1,471 @@
|
||||
// Styles shared between snow and bubble
|
||||
|
||||
$controlHeight: 24px !default;
|
||||
$inputPaddingWidth: 5px !default;
|
||||
$inputPaddingHeight: 3px !default;
|
||||
$colorItemMargin: 2px !default;
|
||||
$colorItemSize: 16px !default;
|
||||
$colorItemsPerRow: 7 !default;
|
||||
|
||||
.ql-#{$themeName}.ql-toolbar,
|
||||
.ql-#{$themeName} .ql-toolbar {
|
||||
&:after {
|
||||
clear: both;
|
||||
content: '';
|
||||
display: table;
|
||||
}
|
||||
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
height: $controlHeight;
|
||||
padding: $inputPaddingHeight $inputPaddingWidth;
|
||||
width: $controlHeight + ($inputPaddingWidth - $inputPaddingHeight) * 2;
|
||||
|
||||
svg {
|
||||
float: left;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:active:hover {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
input.ql-image[type='file'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:focus,
|
||||
button.ql-active,
|
||||
.ql-picker-label:hover,
|
||||
.ql-picker-label.ql-active,
|
||||
.ql-picker-item:hover,
|
||||
.ql-picker-item.ql-selected {
|
||||
color: var(--ql-active-color);
|
||||
|
||||
.ql-fill,
|
||||
.ql-stroke.ql-fill {
|
||||
fill: var(--ql-active-color);
|
||||
}
|
||||
|
||||
.ql-stroke,
|
||||
.ql-stroke-miter {
|
||||
stroke: var(--ql-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix for iOS not losing hover on touch
|
||||
@media (pointer: coarse) {
|
||||
.ql-#{$themeName}.ql-toolbar,
|
||||
.ql-#{$themeName} .ql-toolbar {
|
||||
button:hover:not(.ql-active) {
|
||||
color: var(--ql-inactive-color);
|
||||
|
||||
.ql-fill,
|
||||
.ql-stroke.ql-fill {
|
||||
fill: var(--ql-inactive-color);
|
||||
}
|
||||
|
||||
.ql-stroke,
|
||||
.ql-stroke-miter {
|
||||
stroke: var(--ql-inactive-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ql-#{$themeName} {
|
||||
box-sizing: border-box;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ql-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ql-out-bottom,
|
||||
.ql-out-top {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.ql-tooltip {
|
||||
position: absolute;
|
||||
transform: translateY(10px);
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-tooltip.ql-flip {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
.ql-formats {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
&:after {
|
||||
clear: both;
|
||||
content: '';
|
||||
display: table;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-stroke {
|
||||
fill: none;
|
||||
stroke: var(--ql-inactive-color);
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.ql-stroke-miter {
|
||||
fill: none;
|
||||
stroke: var(--ql-inactive-color);
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.ql-fill,
|
||||
.ql-stroke.ql-fill {
|
||||
fill: var(--ql-inactive-color);
|
||||
}
|
||||
|
||||
.ql-empty {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.ql-even {
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
.ql-thin,
|
||||
.ql-stroke.ql-thin {
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.ql-transparent {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.ql-direction {
|
||||
svg:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-direction.ql-active {
|
||||
svg:last-child {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
svg:first-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker {
|
||||
color: var(--ql-inactive-color);
|
||||
display: inline-block;
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
height: $controlHeight;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
|
||||
&.ql-expanded {
|
||||
.ql-picker-label {
|
||||
color: var(--ql-border-color);
|
||||
z-index: 2;
|
||||
|
||||
.ql-fill {
|
||||
fill: var(--ql-border-color);
|
||||
}
|
||||
|
||||
.ql-stroke {
|
||||
stroke: var(--ql-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker-options {
|
||||
display: block;
|
||||
margin-top: -1px;
|
||||
top: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker-label {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
padding-left: 8px;
|
||||
padding-right: 2px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before {
|
||||
display: inline-block;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker-options {
|
||||
background-color: var(--ql-background-color);
|
||||
display: none;
|
||||
min-width: 100%;
|
||||
padding: 4px 8px;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
.ql-picker-item {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-color-picker,
|
||||
.ql-icon-picker {
|
||||
width: $controlHeight + 4;
|
||||
}
|
||||
|
||||
.ql-color-picker .ql-picker-label,
|
||||
.ql-icon-picker .ql-picker-label {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.ql-color-picker .ql-picker-label svg,
|
||||
.ql-icon-picker .ql-picker-label svg {
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.ql-icon-picker {
|
||||
.ql-picker-options {
|
||||
padding: 4px 0px;
|
||||
}
|
||||
.ql-picker-item {
|
||||
height: $controlHeight;
|
||||
width: $controlHeight;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-color-picker {
|
||||
.ql-picker-options {
|
||||
padding: $inputPaddingHeight $inputPaddingWidth;
|
||||
width:
|
||||
($colorItemSize + 2 * $colorItemMargin) *
|
||||
$colorItemsPerRow + 2 * $inputPaddingWidth + 2
|
||||
;
|
||||
}
|
||||
|
||||
.ql-picker-item {
|
||||
border: 1px solid transparent;
|
||||
float: left;
|
||||
height: $colorItemSize;
|
||||
margin: $colorItemMargin;
|
||||
padding: 0px;
|
||||
width: $colorItemSize;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker {
|
||||
&:not(.ql-color-picker):not(.ql-icon-picker) {
|
||||
svg {
|
||||
position: absolute;
|
||||
margin-top: -9px;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker.ql-header,
|
||||
.ql-picker.ql-font,
|
||||
.ql-picker.ql-size {
|
||||
.ql-picker-label[data-label]:not([data-label='']),
|
||||
.ql-picker-item[data-label]:not([data-label='']) {
|
||||
&::before {
|
||||
content: attr(data-label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker.ql-header {
|
||||
width: 98px;
|
||||
|
||||
.ql-picker-label::before,
|
||||
.ql-picker-item::before {
|
||||
content: 'Normal';
|
||||
}
|
||||
|
||||
@for $num from 1 through 3 {
|
||||
.ql-picker-label[data-value="#{$num}"]::before,
|
||||
.ql-picker-item[data-value="#{$num}"]::before {
|
||||
content: 'Heading #{$num}';
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker-item[data-value='1']::before {
|
||||
font-size: 2em;
|
||||
}
|
||||
.ql-picker-item[data-value='2']::before {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.ql-picker-item[data-value='3']::before {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
.ql-picker-item[data-value='4']::before {
|
||||
font-size: 1em;
|
||||
}
|
||||
.ql-picker-item[data-value='5']::before {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
.ql-picker-item[data-value='6']::before {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker.ql-font {
|
||||
width: 108px;
|
||||
|
||||
.ql-picker-label::before,
|
||||
.ql-picker-item::before {
|
||||
content: 'Sans Serif';
|
||||
}
|
||||
.ql-picker-label[data-value='serif']::before,
|
||||
.ql-picker-item[data-value='serif']::before {
|
||||
content: 'Serif';
|
||||
}
|
||||
.ql-picker-label[data-value='monospace']::before,
|
||||
.ql-picker-item[data-value='monospace']::before {
|
||||
content: 'Monospace';
|
||||
}
|
||||
.ql-picker-item[data-value='serif']::before {
|
||||
font-family: Georgia, Times New Roman, serif;
|
||||
}
|
||||
.ql-picker-item[data-value='monospace']::before {
|
||||
font-family: Monaco, Courier New, monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-picker.ql-size {
|
||||
width: 98px;
|
||||
|
||||
.ql-picker-label::before,
|
||||
.ql-picker-item::before {
|
||||
content: 'Normal';
|
||||
}
|
||||
.ql-picker-label[data-value='small']::before,
|
||||
.ql-picker-item[data-value='small']::before {
|
||||
content: 'Small';
|
||||
}
|
||||
.ql-picker-label[data-value='large']::before,
|
||||
.ql-picker-item[data-value='large']::before {
|
||||
content: 'Large';
|
||||
}
|
||||
.ql-picker-label[data-value='huge']::before,
|
||||
.ql-picker-item[data-value='huge']::before {
|
||||
content: 'Huge';
|
||||
}
|
||||
.ql-picker-item[data-value='small']::before {
|
||||
font-size: 10px;
|
||||
}
|
||||
.ql-picker-item[data-value='large']::before {
|
||||
font-size: 18px;
|
||||
}
|
||||
.ql-picker-item[data-value='huge']::before {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-color-picker.ql-background {
|
||||
.ql-picker-item {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-color-picker.ql-color {
|
||||
.ql-picker-item {
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ql-code-block-container {
|
||||
position: relative;
|
||||
|
||||
.ql-ui {
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-editor, .quill-render {
|
||||
// h1 {
|
||||
// font-size: 2em;
|
||||
// }
|
||||
// h2 {
|
||||
// font-size: 1.5em;
|
||||
// }
|
||||
// h3 {
|
||||
// font-size: 1.17em;
|
||||
// }
|
||||
// h4 {
|
||||
// font-size: 1em;
|
||||
// }
|
||||
// h5 {
|
||||
// font-size: 0.83em;
|
||||
// }
|
||||
// h6 {
|
||||
// font-size: 0.67em;
|
||||
// }
|
||||
// a {
|
||||
// text-decoration: underline;
|
||||
// }
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #ccc;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
code,
|
||||
.ql-code-block-container {
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.ql-code-block-container {
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
padding: 5px 10px;
|
||||
background-color: #23241f;
|
||||
color: #f8f8f2;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 85%;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
312
resources/sass/quill/core.scss
Normal file
312
resources/sass/quill/core.scss
Normal file
@ -0,0 +1,312 @@
|
||||
// Styles necessary for Quill
|
||||
|
||||
$LIST_STYLE: (decimal, lower-alpha, lower-roman) !default;
|
||||
$LIST_STYLE_WIDTH: 1.2em !default;
|
||||
$LIST_STYLE_MARGIN: 0.3em !default;
|
||||
$LIST_STYLE_OUTER_WIDTH: $LIST_STYLE_MARGIN + $LIST_STYLE_WIDTH !default;
|
||||
$MAX_INDENT: 9 !default;
|
||||
|
||||
@function resets($from, $to) {
|
||||
$list: (unquote('')); // sass wont allow empty "()" property list
|
||||
|
||||
@for $num from $from through $to {
|
||||
$list: append($list, unquote('list-' + $num), space);
|
||||
}
|
||||
|
||||
@return $list;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
box-sizing: border-box;
|
||||
// font-family: Helvetica, Arial, sans-serif;
|
||||
// font-size: 13px;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ql-container.ql-disabled {
|
||||
.ql-tooltip {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-container:not(.ql-disabled) {
|
||||
li[data-list='checked'],
|
||||
li[data-list='unchecked'] {
|
||||
> .ql-ui {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ql-clipboard {
|
||||
left: -100000px;
|
||||
height: 1px;
|
||||
overflow-y: hidden;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
box-sizing: border-box;
|
||||
counter-reset: resets(0, $MAX_INDENT);
|
||||
line-height: 1.42;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
overflow-y: auto;
|
||||
padding: 12px 15px;
|
||||
tab-size: 4;
|
||||
-moz-tab-size: 4;
|
||||
text-align: left;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.ql-editor, .quill-render {
|
||||
> * {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
// p,
|
||||
// ol,
|
||||
// pre,
|
||||
// blockquote,
|
||||
// h1,
|
||||
// h2,
|
||||
// h3,
|
||||
// h4,
|
||||
// h5,
|
||||
// h6 {
|
||||
// margin: 0;
|
||||
// padding: 0;
|
||||
// }
|
||||
|
||||
p,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@supports (counter-set: none) {
|
||||
counter-set: resets(0, $MAX_INDENT);
|
||||
}
|
||||
@supports not (counter-set: none) {
|
||||
counter-reset: resets(0, $MAX_INDENT);
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
@extend .table;
|
||||
@extend .table-bordered;
|
||||
@extend .table-sm;
|
||||
}
|
||||
|
||||
|
||||
ol {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
padding-left: $LIST_STYLE_OUTER_WIDTH;
|
||||
position: relative;
|
||||
|
||||
> .ql-ui:before {
|
||||
display: inline-block;
|
||||
margin-left: -1 * $LIST_STYLE_OUTER_WIDTH;
|
||||
margin-right: $LIST_STYLE_MARGIN;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
width: $LIST_STYLE_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
li[data-list='checked'],
|
||||
li[data-list='unchecked'] {
|
||||
> .ql-ui {
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
|
||||
li[data-list='bullet'] > .ql-ui:before {
|
||||
content: '\2022';
|
||||
}
|
||||
|
||||
li[data-list='checked'] > .ql-ui:before {
|
||||
content: '\2611';
|
||||
}
|
||||
|
||||
li[data-list='unchecked'] > .ql-ui:before {
|
||||
content: '\2610';
|
||||
}
|
||||
|
||||
li[data-list] {
|
||||
@supports (counter-set: none) {
|
||||
counter-set: resets(1, $MAX_INDENT);
|
||||
}
|
||||
@supports not (counter-set: none) {
|
||||
counter-reset: resets(1, $MAX_INDENT);
|
||||
}
|
||||
}
|
||||
|
||||
li[data-list='ordered'] {
|
||||
counter-increment: list-0;
|
||||
> .ql-ui:before {
|
||||
content: unquote('counter(list-0, ' + nth($LIST_STYLE, 1) + ')') '. '; // indexs stars with 1
|
||||
}
|
||||
}
|
||||
|
||||
@for $num from 1 through $MAX_INDENT {
|
||||
li[data-list='ordered'].ql-indent-#{$num} {
|
||||
counter-increment: unquote('list-' + $num);
|
||||
|
||||
> .ql-ui:before {
|
||||
content: unquote(
|
||||
'counter(list-' + $num + ', ' + nth($LIST_STYLE, 1 + ($num % 3)) + ')'
|
||||
)
|
||||
'. ';
|
||||
}
|
||||
}
|
||||
|
||||
@if $num < $MAX_INDENT {
|
||||
li[data-list].ql-indent-#{$num} {
|
||||
@supports (counter-set: none) {
|
||||
counter-set: resets(($num + 1), $MAX_INDENT);
|
||||
}
|
||||
@supports not (counter-set: none) {
|
||||
counter-reset: resets(($num + 1), $MAX_INDENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@for $num from 1 through $MAX_INDENT {
|
||||
.ql-indent-#{$num}:not(.ql-direction-rtl) {
|
||||
padding-left: #{3 * $num}em;
|
||||
}
|
||||
li.ql-indent-#{$num}:not(.ql-direction-rtl) {
|
||||
padding-left: #{(3 * $num + $LIST_STYLE_OUTER_WIDTH)};
|
||||
}
|
||||
.ql-indent-#{$num}.ql-direction-rtl.ql-align-right {
|
||||
padding-right: #{(3 * $num)}em;
|
||||
}
|
||||
li.ql-indent-#{$num}.ql-direction-rtl.ql-align-right {
|
||||
padding-right: #{(3 * $num + $LIST_STYLE_OUTER_WIDTH)};
|
||||
}
|
||||
}
|
||||
|
||||
li.ql-direction-rtl {
|
||||
padding-right: $LIST_STYLE_OUTER_WIDTH;
|
||||
|
||||
> .ql-ui:before {
|
||||
margin-left: $LIST_STYLE_MARGIN;
|
||||
margin-right: -1 * $LIST_STYLE_OUTER_WIDTH;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
|
||||
td {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-code-block-container {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.ql-video {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ql-video.ql-align-center {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.ql-video.ql-align-right {
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
.ql-bg-black {
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
.ql-bg-red {
|
||||
background-color: rgb(230, 0, 0);
|
||||
}
|
||||
.ql-bg-orange {
|
||||
background-color: rgb(255, 153, 0);
|
||||
}
|
||||
.ql-bg-yellow {
|
||||
background-color: rgb(255, 255, 0);
|
||||
}
|
||||
.ql-bg-green {
|
||||
background-color: rgb(0, 138, 0);
|
||||
}
|
||||
.ql-bg-blue {
|
||||
background-color: rgb(0, 102, 204);
|
||||
}
|
||||
.ql-bg-purple {
|
||||
background-color: rgb(153, 51, 255);
|
||||
}
|
||||
.ql-color-white {
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
.ql-color-red {
|
||||
color: rgb(230, 0, 0);
|
||||
}
|
||||
.ql-color-orange {
|
||||
color: rgb(255, 153, 0);
|
||||
}
|
||||
.ql-color-yellow {
|
||||
color: rgb(255, 255, 0);
|
||||
}
|
||||
.ql-color-green {
|
||||
color: rgb(0, 138, 0);
|
||||
}
|
||||
.ql-color-blue {
|
||||
color: rgb(0, 102, 204);
|
||||
}
|
||||
.ql-color-purple {
|
||||
color: rgb(153, 51, 255);
|
||||
}
|
||||
.ql-font-serif {
|
||||
font-family: Georgia, Times New Roman, serif;
|
||||
}
|
||||
.ql-font-monospace {
|
||||
font-family: Monaco, Courier New, monospace;
|
||||
}
|
||||
.ql-size-small {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
.ql-size-large {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.ql-size-huge {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
.ql-direction-rtl {
|
||||
direction: rtl;
|
||||
text-align: inherit;
|
||||
}
|
||||
.ql-align-center {
|
||||
text-align: center;
|
||||
}
|
||||
.ql-align-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
.ql-align-right {
|
||||
text-align: right;
|
||||
}
|
||||
.ql-ui {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
27
resources/sass/quill/snow.scss
Normal file
27
resources/sass/quill/snow.scss
Normal file
@ -0,0 +1,27 @@
|
||||
$themeName: 'snow';
|
||||
$tooltipMargin: 8px;
|
||||
|
||||
:root {
|
||||
--ql-active-color: var(--bs-body-color);
|
||||
--ql-border-color: var(--bs-border-color);
|
||||
--ql-background-color: var(--bs-body-bg);
|
||||
--ql-inactive-color: var(--bs-secondary);
|
||||
--ql-shadow-color: #ddd;
|
||||
--ql-text-color: var(--bs-body-color);
|
||||
--ql-tooltip-background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
@import 'core.scss';
|
||||
@import 'base';
|
||||
@import 'snow/toolbar';
|
||||
@import 'snow/tooltip';
|
||||
|
||||
// .ql-snow {
|
||||
// a {
|
||||
// color: var(--ql-active-color);
|
||||
// }
|
||||
// }
|
||||
|
||||
.ql-container.ql-snow {
|
||||
border: 1px solid var(--ql-border-color);
|
||||
}
|
39
resources/sass/quill/snow/_toolbar.scss
Normal file
39
resources/sass/quill/snow/_toolbar.scss
Normal file
@ -0,0 +1,39 @@
|
||||
.ql-toolbar.ql-snow {
|
||||
border: 1px solid var(--ql-border-color);
|
||||
box-sizing: border-box;
|
||||
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
|
||||
padding: 8px;
|
||||
|
||||
.ql-formats {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.ql-picker-label {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.ql-picker-options {
|
||||
border: 1px solid transparent;
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0 2px 8px;
|
||||
}
|
||||
|
||||
.ql-picker.ql-expanded {
|
||||
.ql-picker-label {
|
||||
border-color: var(--ql-border-color);
|
||||
}
|
||||
.ql-picker-options {
|
||||
border-color: var(--ql-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.ql-color-picker {
|
||||
.ql-picker-item.ql-selected,
|
||||
.ql-picker-item:hover {
|
||||
border-color: var(--ql-border-color-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ql-toolbar.ql-snow + .ql-container.ql-snow {
|
||||
border-top: 0px;
|
||||
}
|
79
resources/sass/quill/snow/_tooltip.scss
Normal file
79
resources/sass/quill/snow/_tooltip.scss
Normal file
@ -0,0 +1,79 @@
|
||||
.ql-snow {
|
||||
.ql-tooltip {
|
||||
background-color: var(--ql-tooltip-background);
|
||||
border: 1px solid var(--ql-border-color);
|
||||
box-shadow: 0px 0px 5px var(--ql-shadow-color);
|
||||
color: var(--ql-text-color);
|
||||
padding: 5px 12px;
|
||||
white-space: nowrap;
|
||||
|
||||
&::before {
|
||||
content: 'Visit URL:';
|
||||
line-height: 26px;
|
||||
margin-right: $tooltipMargin;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
display: none;
|
||||
border: 1px solid var(--ql-border-color);
|
||||
font-size: 13px;
|
||||
height: 26px;
|
||||
margin: 0px;
|
||||
padding: 3px 5px;
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
a {
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
a.ql-preview {
|
||||
display: inline-block;
|
||||
max-width: $tooltipMargin * 2;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
a.ql-action::after {
|
||||
border-right: 1px solid var(--ql-border-color);
|
||||
content: 'Edit';
|
||||
margin-left: $tooltipMargin * 2;
|
||||
padding-right: $tooltipMargin;
|
||||
}
|
||||
|
||||
a.ql-remove::before {
|
||||
content: 'Remove';
|
||||
margin-left: $tooltipMargin;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-tooltip.ql-editing {
|
||||
a.ql-preview,
|
||||
a.ql-remove {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a.ql-action::after {
|
||||
border-right: 0px;
|
||||
content: 'Save';
|
||||
padding-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-tooltip[data-mode='link']::before {
|
||||
content: 'Enter link:';
|
||||
}
|
||||
|
||||
.ql-tooltip[data-mode='formula']::before {
|
||||
content: 'Enter formula:';
|
||||
}
|
||||
|
||||
.ql-tooltip[data-mode='video']::before {
|
||||
content: 'Enter video:';
|
||||
}
|
||||
}
|
263
resources/sass/tailwind/variables/_colors.scss
Normal file
263
resources/sass/tailwind/variables/_colors.scss
Normal file
@ -0,0 +1,263 @@
|
||||
$tw-slate-050: #f8fafc;
|
||||
$tw-slate-100: #f1f5f9;
|
||||
$tw-slate-200: #e2e8f0;
|
||||
$tw-slate-300: #cbd5e1;
|
||||
$tw-slate-400: #94a3b8;
|
||||
$tw-slate-500: #64748b;
|
||||
$tw-slate-600: #475569;
|
||||
$tw-slate-700: #334155;
|
||||
$tw-slate-800: #1e293b;
|
||||
$tw-slate-900: #0f172a;
|
||||
$tw-slate-950: #020617;
|
||||
|
||||
$tw-gray-050: #f9fafb;
|
||||
$tw-gray-100: #f3f4f6;
|
||||
$tw-gray-200: #e5e7eb;
|
||||
$tw-gray-300: #d1d5db;
|
||||
$tw-gray-400: #9ca3af;
|
||||
$tw-gray-500: #6b7280;
|
||||
$tw-gray-600: #4b5563;
|
||||
$tw-gray-700: #374151;
|
||||
$tw-gray-800: #1f2937;
|
||||
$tw-gray-900: #111827;
|
||||
$tw-gray-950: #030712;
|
||||
|
||||
$tw-zinc-050: #fafafa;
|
||||
$tw-zinc-100: #f4f4f5;
|
||||
$tw-zinc-200: #e4e4e7;
|
||||
$tw-zinc-300: #d4d4d8;
|
||||
$tw-zinc-400: #a1a1aa;
|
||||
$tw-zinc-500: #71717a;
|
||||
$tw-zinc-600: #52525b;
|
||||
$tw-zinc-700: #3f3f46;
|
||||
$tw-zinc-800: #27272a;
|
||||
$tw-zinc-900: #18181b;
|
||||
$tw-zinc-950: #09090b;
|
||||
|
||||
$tw-neutral-050: #fafafa;
|
||||
$tw-neutral-100: #f5f5f5;
|
||||
$tw-neutral-200: #e5e5e5;
|
||||
$tw-neutral-300: #d4d4d4;
|
||||
$tw-neutral-400: #a3a3a3;
|
||||
$tw-neutral-500: #737373;
|
||||
$tw-neutral-600: #525252;
|
||||
$tw-neutral-700: #404040;
|
||||
$tw-neutral-800: #262626;
|
||||
$tw-neutral-900: #171717;
|
||||
$tw-neutral-950: #0a0a0a;
|
||||
|
||||
$tw-stone-050: #fafaf9;
|
||||
$tw-stone-100: #f5f5f4;
|
||||
$tw-stone-200: #e7e5e4;
|
||||
$tw-stone-300: #d6d3d1;
|
||||
$tw-stone-400: #a8a29e;
|
||||
$tw-stone-500: #78716c;
|
||||
$tw-stone-600: #57534e;
|
||||
$tw-stone-700: #44403c;
|
||||
$tw-stone-800: #292524;
|
||||
$tw-stone-900: #1c1917;
|
||||
$tw-stone-950: #0c0a09;
|
||||
|
||||
$tw-red-050: #fef2f2;
|
||||
$tw-red-100: #fee2e2;
|
||||
$tw-red-200: #fecaca;
|
||||
$tw-red-300: #fca5a5;
|
||||
$tw-red-400: #f87171;
|
||||
$tw-red-500: #ef4444;
|
||||
$tw-red-600: #dc2626;
|
||||
$tw-red-700: #b91c1c;
|
||||
$tw-red-800: #991b1b;
|
||||
$tw-red-900: #7f1d1d;
|
||||
$tw-red-950: #450a0a;
|
||||
|
||||
$tw-orange-050: #fff7ed;
|
||||
$tw-orange-100: #ffedd5;
|
||||
$tw-orange-200: #fed7aa;
|
||||
$tw-orange-300: #fdba74;
|
||||
$tw-orange-400: #fb923c;
|
||||
$tw-orange-500: #f97316;
|
||||
$tw-orange-600: #ea580c;
|
||||
$tw-orange-700: #c2410c;
|
||||
$tw-orange-800: #9a3412;
|
||||
$tw-orange-900: #7c2d12;
|
||||
$tw-orange-950: #431407;
|
||||
|
||||
$tw-amber-050: #fffbeb;
|
||||
$tw-amber-100: #fef3c7;
|
||||
$tw-amber-200: #fde68a;
|
||||
$tw-amber-300: #fcd34d;
|
||||
$tw-amber-400: #fbbf24;
|
||||
$tw-amber-500: #f59e0b;
|
||||
$tw-amber-600: #d97706;
|
||||
$tw-amber-700: #b45309;
|
||||
$tw-amber-800: #92400e;
|
||||
$tw-amber-900: #78350f;
|
||||
$tw-amber-950: #451a03;
|
||||
|
||||
$tw-yellow-050: #fefce8;
|
||||
$tw-yellow-100: #fef9c3;
|
||||
$tw-yellow-200: #fef08a;
|
||||
$tw-yellow-300: #fde047;
|
||||
$tw-yellow-400: #facc15;
|
||||
$tw-yellow-500: #eab308;
|
||||
$tw-yellow-600: #ca8a04;
|
||||
$tw-yellow-700: #a16207;
|
||||
$tw-yellow-800: #854d0e;
|
||||
$tw-yellow-900: #713f12;
|
||||
$tw-yellow-950: #422006;
|
||||
|
||||
$tw-lime-050: #f7fee7;
|
||||
$tw-lime-100: #ecfccb;
|
||||
$tw-lime-200: #d9f99d;
|
||||
$tw-lime-300: #bef264;
|
||||
$tw-lime-400: #a3e635;
|
||||
$tw-lime-500: #84cc16;
|
||||
$tw-lime-600: #65a30d;
|
||||
$tw-lime-700: #4d7c0f;
|
||||
$tw-lime-800: #3f6212;
|
||||
$tw-lime-900: #365314;
|
||||
$tw-lime-950: #1a2e05;
|
||||
|
||||
$tw-green-050: #f0fdf4;
|
||||
$tw-green-100: #dcfce7;
|
||||
$tw-green-200: #bbf7d0;
|
||||
$tw-green-300: #86efac;
|
||||
$tw-green-400: #4ade80;
|
||||
$tw-green-500: #22c55e;
|
||||
$tw-green-600: #16a34a;
|
||||
$tw-green-700: #15803d;
|
||||
$tw-green-800: #166534;
|
||||
$tw-green-900: #14532d;
|
||||
$tw-green-950: #052e16;
|
||||
|
||||
$tw-emerald-050: #ecfdf5;
|
||||
$tw-emerald-100: #d1fae5;
|
||||
$tw-emerald-200: #a7f3d0;
|
||||
$tw-emerald-300: #6ee7b7;
|
||||
$tw-emerald-400: #34d399;
|
||||
$tw-emerald-500: #10b981;
|
||||
$tw-emerald-600: #059669;
|
||||
$tw-emerald-700: #047857;
|
||||
$tw-emerald-800: #065f46;
|
||||
$tw-emerald-900: #064e3b;
|
||||
$tw-emerald-950: #022c22;
|
||||
|
||||
$tw-teal-050: #f0fdfa;
|
||||
$tw-teal-100: #ccfbf1;
|
||||
$tw-teal-200: #99f6e4;
|
||||
$tw-teal-300: #5eead4;
|
||||
$tw-teal-400: #2dd4bf;
|
||||
$tw-teal-500: #14b8a6;
|
||||
$tw-teal-600: #0d9488;
|
||||
$tw-teal-700: #0f766e;
|
||||
$tw-teal-800: #115e59;
|
||||
$tw-teal-900: #134e4a;
|
||||
$tw-teal-950: #042f2e;
|
||||
|
||||
$tw-cyan-050: #ecfeff;
|
||||
$tw-cyan-100: #cffafe;
|
||||
$tw-cyan-200: #a5f3fc;
|
||||
$tw-cyan-300: #67e8f9;
|
||||
$tw-cyan-400: #22d3ee;
|
||||
$tw-cyan-500: #06b6d4;
|
||||
$tw-cyan-600: #0891b2;
|
||||
$tw-cyan-700: #0e7490;
|
||||
$tw-cyan-800: #155e75;
|
||||
$tw-cyan-900: #164e63;
|
||||
$tw-cyan-950: #083344;
|
||||
|
||||
$tw-sky-050: #f0f9ff;
|
||||
$tw-sky-100: #e0f2fe;
|
||||
$tw-sky-200: #bae6fd;
|
||||
$tw-sky-300: #7dd3fc;
|
||||
$tw-sky-400: #38bdf8;
|
||||
$tw-sky-500: #0ea5e9;
|
||||
$tw-sky-600: #0284c7;
|
||||
$tw-sky-700: #0369a1;
|
||||
$tw-sky-800: #075985;
|
||||
$tw-sky-900: #0c4a6e;
|
||||
$tw-sky-950: #083344;
|
||||
|
||||
$tw-blue-050: #eff6ff;
|
||||
$tw-blue-100: #dbeafe;
|
||||
$tw-blue-200: #bfdbfe;
|
||||
$tw-blue-300: #93c5fd;
|
||||
$tw-blue-400: #60a5fa;
|
||||
$tw-blue-500: #3b82f6;
|
||||
$tw-blue-600: #2563eb;
|
||||
$tw-blue-700: #1d4ed8;
|
||||
$tw-blue-800: #1e40af;
|
||||
$tw-blue-900: #1e3a8a;
|
||||
$tw-blue-950: #172554;
|
||||
|
||||
$tw-indigo-050: #eef2ff;
|
||||
$tw-indigo-100: #e0e7ff;
|
||||
$tw-indigo-200: #c7d2fe;
|
||||
$tw-indigo-300: #a5b4fc;
|
||||
$tw-indigo-400: #818cf8;
|
||||
$tw-indigo-500: #6366f1;
|
||||
$tw-indigo-600: #4f46e5;
|
||||
$tw-indigo-700: #4338ca;
|
||||
$tw-indigo-800: #3730a3;
|
||||
$tw-indigo-900: #312e81;
|
||||
$tw-indigo-950: #1e1b4b;
|
||||
|
||||
$tw-violet-050: #f5f3ff;
|
||||
$tw-violet-100: #ede9fe;
|
||||
$tw-violet-200: #ddd6fe;
|
||||
$tw-violet-300: #c4b5fd;
|
||||
$tw-violet-400: #a78bfa;
|
||||
$tw-violet-500: #8b5cf6;
|
||||
$tw-violet-600: #7c3aed;
|
||||
$tw-violet-700: #6d28d9;
|
||||
$tw-violet-800: #5b21b6;
|
||||
$tw-violet-900: #4c1d95;
|
||||
$tw-violet-950: #2e1065;
|
||||
|
||||
$tw-purple-050: #faf5ff;
|
||||
$tw-purple-100: #f3e8ff;
|
||||
$tw-purple-200: #e9d5ff;
|
||||
$tw-purple-300: #d8b4fe;
|
||||
$tw-purple-400: #c084fc;
|
||||
$tw-purple-500: #a855f7;
|
||||
$tw-purple-600: #9333ea;
|
||||
$tw-purple-700: #7e22ce;
|
||||
$tw-purple-800: #6b21a8;
|
||||
$tw-purple-900: #581c87;
|
||||
$tw-purple-950: #3b0764;
|
||||
|
||||
$tw-fuchsia-050: #fdf4ff;
|
||||
$tw-fuchsia-100: #fae8ff;
|
||||
$tw-fuchsia-200: #f5d0fe;
|
||||
$tw-fuchsia-300: #f0abfc;
|
||||
$tw-fuchsia-400: #e879f9;
|
||||
$tw-fuchsia-500: #d946ef;
|
||||
$tw-fuchsia-600: #c026d3;
|
||||
$tw-fuchsia-700: #a21caf;
|
||||
$tw-fuchsia-800: #86198f;
|
||||
$tw-fuchsia-900: #701a75;
|
||||
$tw-fuchsia-950: #4a044e;
|
||||
|
||||
$tw-pink-050: #fdf2f8;
|
||||
$tw-pink-100: #fce7f3;
|
||||
$tw-pink-200: #fbcfe8;
|
||||
$tw-pink-300: #f9a8d4;
|
||||
$tw-pink-400: #f472b6;
|
||||
$tw-pink-500: #ec4899;
|
||||
$tw-pink-600: #db2777;
|
||||
$tw-pink-700: #be185d;
|
||||
$tw-pink-800: #9d174d;
|
||||
$tw-pink-900: #831843;
|
||||
$tw-pink-950: #500724;
|
||||
|
||||
$tw-rose-050: #fff1f2;
|
||||
$tw-rose-100: #ffe4e6;
|
||||
$tw-rose-200: #fecdd3;
|
||||
$tw-rose-300: #fda4af;
|
||||
$tw-rose-400: #fb7185;
|
||||
$tw-rose-500: #f43f5e;
|
||||
$tw-rose-600: #e11d48;
|
||||
$tw-rose-700: #be123c;
|
||||
$tw-rose-800: #9f1239;
|
||||
$tw-rose-900: #881337;
|
||||
$tw-rose-950: #4c0519;
|
@ -1,34 +1,21 @@
|
||||
<form method="POST"action="{{ route('login.submit') }}">
|
||||
@csrf
|
||||
<x-layout-auth>
|
||||
<x-form::form method="POST" action="{{ route('login.submit') }}">
|
||||
<x-form::input class="mb-3" type="email" name="email" id="email" label="{{ __('boilerplate::auth.email') }}:" />
|
||||
<x-form::input class="mb-3" type="password" name="password" id="password" label="{{ __('boilerplate::auth.password') }}:" />
|
||||
<x-form::checkbox name="remmeber" id="remmeber" label="{{ __('boilerplate::auth.remember') }}:" />
|
||||
|
||||
<label for="email">{{ __('Email') }}:</label><br>
|
||||
<input type="email" id="email" name="email" placeholder="email@post.xx"><br>
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
|
||||
<label for="password">{{ __('Password') }}:</label><br>
|
||||
<input type="password" id="password" name="password"><br>
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
|
||||
<input type="checkbox" id="remember" name="remember"><br>
|
||||
|
||||
@if (Route::has('password'))
|
||||
<a href="{{ route('password') }}">
|
||||
{{ __('Password Reset') }} ?
|
||||
</a><br>
|
||||
@endif
|
||||
|
||||
<input type="submit" value="{{ __('Login') }}">
|
||||
<div class="d-flex">
|
||||
<x-form::button class="btn-primary" type="submit">{{ __('boilerplate::auth.login') }}</x-form::button>
|
||||
@if (Route::has('register'))
|
||||
<a href="{{ route('register') }}">
|
||||
{{ __('Register') }} ?
|
||||
<a class="ms-2 btn btn-primary" href="{{ route('register') }}">
|
||||
{{ __('boilerplate::auth.register') }}
|
||||
</a>
|
||||
@endif
|
||||
</form>
|
||||
@if (Route::has('password'))
|
||||
<a class="ms-auto text-nowrap" href="{{ route('password') }}">
|
||||
{{ __('boilerplate::auth.password_reset') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</x-form::form>
|
||||
</x-layout-auth>
|
||||
|
61
resources/views/auth/profile.blade.php
Normal file
61
resources/views/auth/profile.blade.php
Normal file
@ -0,0 +1,61 @@
|
||||
<x-layout-app>
|
||||
<div class="container-xl">
|
||||
<div class="page-header">
|
||||
<h1>{{ __('boilerplate::ui.profile') }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label"><b>{{ __('boilerplate::ui.name') }}:</b></label>
|
||||
<p>{{ $user->name ?? '' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label"><b>{{ __('boilerplate::ui.email') }}:</b></label>
|
||||
<p>{{ $user->email ?? '' }}</p>
|
||||
</div>
|
||||
|
||||
@if (config('session.driver') == 'database')
|
||||
<div>
|
||||
<h4>{{ __('boilerplate::ui.sessions') }}</h4>
|
||||
@livewire('session.data-table', [], key('data-table'))
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<h4>{{ __('boilerplate::ui.change_password') }}</h4>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('profile.update', ['user' => $user]) }}" method="POST">
|
||||
@csrf
|
||||
@method('put')
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="password">{{ __('boilerplate::ui.old.password') }}</label>
|
||||
<input class="form-control @error('password') is-invalid @enderror" id="password" name="password" type="password">
|
||||
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newPassword">{{ __('boilerplate::ui.new.password') }}</label>
|
||||
<input class="form-control @error('newPassword') is-invalid @enderror" id="newPassword" name="newPassword" type="password">
|
||||
|
||||
@error('newPassword')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label class="form-label" for="newPassword-confirm">{{ __('boilerplate::ui.confirm.password') }}</label>
|
||||
<input class="form-control" id="newPassword-confirm" name="newPassword_confirmation" type="password">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" type="submit">{{ __('boilerplate::ui.update') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</x-layout-app>
|
69
resources/views/auth/profile_api.blade.php
Normal file
69
resources/views/auth/profile_api.blade.php
Normal file
@ -0,0 +1,69 @@
|
||||
<x-layout-app>
|
||||
<div class="container-xl">
|
||||
<div class="page-header">
|
||||
<h1>{{ __('boilerplate::ui.api_tokens') }}</h1>
|
||||
<div>
|
||||
@if (session()->has('secret'))
|
||||
<code>{{ session()->get('secret') }}</code>
|
||||
@else
|
||||
<form action="{{ route('profile.api.create') }}" class="row row-cols-lg-auto g-3 align-items-center mb-3" method="post">
|
||||
@csrf
|
||||
<div class="col-12">
|
||||
<label class="visually-hidden" for="token-name">{{ __('boilerplate::ui.name') }}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="token-name" name="token_name" placeholder="{{ __('boilerplate::ui.name') }}" type="text">
|
||||
</div>
|
||||
</div>
|
||||
<x-form::input class="form-control" type="date" name="expire_at" id="expire_at" min="{{ now()->toDateString('Y-m-d') }}" placeholder="{{ __('boilerplate::ui.expire_at') }}" />
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" type="submit">{{ __('boilerplate::ui.create') }}</button>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
@error('token_name')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="col-12">
|
||||
@error('expire_at')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong>
|
||||
</span>
|
||||
@enderror
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ __('boilerplate::ui.name') }}</th>
|
||||
<th scope="col">{{ __('boilerplate::ui.last_used_at') }}</th>
|
||||
<th scope="col">{{ __('boilerplate::ui.expire_at') }}</th>
|
||||
<th scope="col">{{ __('boilerplate::ui.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($tokens as $token)
|
||||
<tr>
|
||||
<th scope="row">{{ $token->name }}</th>
|
||||
<td>{{ $token->last_used_at ?? __('boilerplate::ui.never') }}</td>
|
||||
<td>{{ $token->expire_at ?? __('boilerplate::ui.never') }}</td>
|
||||
<td>
|
||||
<form action="{{ route('profile.api.remove', ['token_id' => $token->id]) }}" method="post">
|
||||
@method('DELETE')
|
||||
@csrf
|
||||
<input class="btn btn-danger" type="submit" value="{{ __('boilerplate::ui.remove') }}" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</x-layout-app>
|
@ -1,37 +1,17 @@
|
||||
<form method="POST"action="{{ route('register.submit') }}">
|
||||
@csrf
|
||||
<x-layout-auth>
|
||||
<x-form::form method="POST" action="{{ route('register.submit') }}">
|
||||
<x-form::input class="mb-3" type="text" name="name" id="name" label="{{ __('boilerplate::auth.name') }}:" placeholder="JohnDoe" />
|
||||
<x-form::input class="mb-3" type="email" name="email" id="email" label="{{ __('boilerplate::auth.email') }}:" placeholder="email@post.xx" />
|
||||
<x-form::input class="mb-3" type="password" name="password" id="password" label="{{ __('boilerplate::auth.password') }}:" />
|
||||
<x-form::input class="mb-3" type="password" name="password_confirmation" id="password_confirmation" label="{{ __('boilerplate::auth.password_confirm') }}:" />
|
||||
|
||||
<label for="name">{{ __('Name') }}:</label><br>
|
||||
<input type="text" id="name" name="name" placeholder="JohnDoe"><br>
|
||||
@error('name')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
|
||||
<label for="email">{{ __('Email') }}:</label><br>
|
||||
<input type="email" id="email" name="email" placeholder="email@post.xx"><br>
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
|
||||
<label for="password">{{ __('Password') }}:</label><br>
|
||||
<input type="password" id="password" name="password"><br>
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
|
||||
<label for="password_confirmation">{{ __('Confirm Password') }}:</label><br>
|
||||
<input type="password" id="password_confirmation" name="password_confirmation"><br>
|
||||
@error('password_confirmation')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
|
||||
<input type="submit" value="{{ __('Register') }}">
|
||||
</form>
|
||||
<div class="d-flex">
|
||||
<x-form::button class="p-2 btn-primary" type="submit">{{ __('boilerplate::auth.register') }}</x-form::button>
|
||||
@if (Route::has('login'))
|
||||
<a class="ms-auto text-nowrap" href="{{ route('login') }}">
|
||||
{{ __('boilerplate::auth.login') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</x-form::form>
|
||||
</x-layout-auth>
|
||||
|
@ -1,42 +1,21 @@
|
||||
<form method="POST"action="{{ (isset($token) ? route('password.update') : route('password.email')) }}">
|
||||
@csrf
|
||||
|
||||
<x-layout-auth>
|
||||
<x-form::form method="POST" action="{{ isset($token) ? route('password.update') : route('password.email') }}">
|
||||
@if (isset($token))
|
||||
<input type="hidden" name="token" value="{{ $token }}">
|
||||
|
||||
<label for="email">{{ __('Email') }}:</label><br>
|
||||
<input type="email" id="email" name="email" placeholder="email@post.xx" value="{{ $email }}"><br>
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
|
||||
<label for="password">{{ __('Password') }}:</label><br>
|
||||
<input type="password" id="password" name="password" value="{{ $password ?? old('password') }}"><br>
|
||||
@error('password')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
|
||||
<label for="password_confirmation">{{ __('Confirm Password') }}:</label><br>
|
||||
<input type="password" id="password_confirmation" name="password_confirmation"
|
||||
value="{{ $password_confirmation ?? old('password_confirmation') }}"><br>
|
||||
@error('password_confirmation')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
<x-form::input type="hidden" id="token" name="token" value="{{ $token }}" />
|
||||
<x-form::input class="mb-3" type="email" id="email" name="email" label="{{ __('boilerplate::auth.email') }}" value="{{ $email }}" />
|
||||
<x-form::input class="mb-3" type="password" id="password" name="password" label="{{ __('Password') }}" />
|
||||
<x-form::input class="mb-3" type="password" id="password_confirmation" name="password_confirmation" label="{{ __('boilerplate::auth.password_confirm') }}" />
|
||||
@else
|
||||
<label for="email">{{ __('Email') }}:</label><br>
|
||||
<input type="email" id="email" name="email" placeholder="email@post.xx" value="{{ old('email') }}"><br>
|
||||
@error('email')
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $message }}</strong><br>
|
||||
</span>
|
||||
@enderror
|
||||
<x-form::input class="mb-3" type="email" id="email" name="email" label="{{ __('boilerplate::auth.email') }}" placeholder="email@post.xx" required/>
|
||||
@endif
|
||||
|
||||
<input type="submit" value="{{ __('Send Password Reset Link') }}">
|
||||
</form>
|
||||
<div class="d-flex">
|
||||
<x-form::button class="btn-primary" type="submit">{{ __('boilerplate::auth.send_password_reset') }}</x-form::button>
|
||||
@if (Route::has('login'))
|
||||
<a class="ms-auto text-nowrap" href="{{ route('login') }}">
|
||||
{{ __('boilerplate::auth.login') }} ?
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</x-form::form>
|
||||
</x-layout-auth>
|
||||
|
26
resources/views/auth/verify.blade.php
Normal file
26
resources/views/auth/verify.blade.php
Normal file
@ -0,0 +1,26 @@
|
||||
<x-layout-auth>
|
||||
<div class="container-xl">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ __('auth.Verify') }}</div>
|
||||
|
||||
<div class="card-body">
|
||||
@if (session('resent'))
|
||||
<div class="alert alert-success" role="alert">
|
||||
{{ __('auth.sendEmail') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{ __('auth.checkEmail') }}
|
||||
{{ __('auth.notReceiveEmail') }},
|
||||
<form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('auth.requestNew') }}</button>.
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-layout-auth>
|
26
resources/views/components/alerts.blade.php
Normal file
26
resources/views/components/alerts.blade.php
Normal file
@ -0,0 +1,26 @@
|
||||
<div class="snackbar-container">
|
||||
@foreach ($alerts as $alert)
|
||||
<div class="snackbar alert">
|
||||
<button type="button" class="btn-close close" data-bs-dismiss="alert"></button>
|
||||
|
||||
<div class="alert-content">
|
||||
@switch($alert['type'])
|
||||
@case('success')
|
||||
<i class="alert-ico far fa-check-circle text-success"></i>
|
||||
@break
|
||||
@case('error')
|
||||
<i class="alert-ico fas fa-times-circle text-danger"></i>
|
||||
@break
|
||||
@case('warning')
|
||||
<i class="alert-ico fas fa-exclamation-triangle text-warning"></i>
|
||||
@break
|
||||
@default
|
||||
<i class="alert-ico fas fa-info-circle text-info"></i>
|
||||
@endswitch
|
||||
<div>
|
||||
<div class="alert-title">{{ $alert['message'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
133
resources/views/components/layout-app.blade.php
Normal file
133
resources/views/components/layout-app.blade.php
Normal file
@ -0,0 +1,133 @@
|
||||
<!doctype html>
|
||||
<html data-bs-theme="{{ Cookie::get('theme') }}" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
|
||||
<!-- CSRF Token -->
|
||||
<meta content="{{ csrf_token() }}" name="csrf-token">
|
||||
|
||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
{{-- <link href="{{ asset('/manifest.json') }}" rel="manifest"> --}}
|
||||
<link href="{{ asset('/favicon.ico') }}" rel="shortcut icon" type="image/x-icon">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com" rel="preconnect">
|
||||
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.20/dist/summernote-lite.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Quill -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.0-rc.2/dist/quill.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.0-rc.2/dist/quill.snow.css" rel="stylesheet">
|
||||
<style>
|
||||
.quill-editor-wrap {
|
||||
position: relative;
|
||||
min-height: 9rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.quill-editor-wrap textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.quill-editor {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.quill-editor .ql-editor {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.quill-loading {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
border-radius: var(--bs-border-radius);
|
||||
border: var(--bs-border-width) solid var(--bs-border-color);
|
||||
}
|
||||
|
||||
.quill-editor.ready+.quill-loading {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
window.loadQuill = function() {
|
||||
document.querySelectorAll('.quill-editor:not(.ready)').forEach(function(element) {
|
||||
let textarea = element.closest('.quill-container').querySelector('.quill-textarea');
|
||||
|
||||
let quill = new Quill(element, {
|
||||
theme: 'snow'
|
||||
});
|
||||
|
||||
quill.root.innerHTML = textarea.value;
|
||||
|
||||
quill.on('text-change', function() {
|
||||
let value = quill.root.innerHTML;
|
||||
textarea.value = value;
|
||||
textarea.dispatchEvent(new Event('input'));
|
||||
});
|
||||
|
||||
element.classList.add('ready');
|
||||
element.closest('.quill-container').querySelector('.quill-loading').remove();
|
||||
});
|
||||
}
|
||||
window.loadQuill();
|
||||
</script>
|
||||
|
||||
<!-- Scripts -->
|
||||
@livewireStyles
|
||||
@vite(['resources/sass/app.scss', 'resources/js/app.js'])
|
||||
|
||||
{{-- <script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('{{ asset('/service-worker.js') }}');
|
||||
});
|
||||
}
|
||||
</script> --}}
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
@auth
|
||||
@include('partials.navbar')
|
||||
@endauth
|
||||
|
||||
<div class="layout">
|
||||
|
||||
<x-navigation />
|
||||
|
||||
@include('partials.navigation-mobile')
|
||||
|
||||
<div class="layout-content">
|
||||
<div class="content">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-alerts/>
|
||||
|
||||
@livewireScripts
|
||||
@livewire('modal-basic', key('modal'))
|
||||
@stack('scripts')
|
||||
</body>
|
||||
|
||||
</html>
|
58
resources/views/components/layout-auth.blade.php
Normal file
58
resources/views/components/layout-auth.blade.php
Normal file
@ -0,0 +1,58 @@
|
||||
<!doctype html>
|
||||
<html data-bs-theme="{{ Cookie::get('theme') }}" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
|
||||
<!-- CSRF Token -->
|
||||
<meta content="{{ csrf_token() }}" name="csrf-token">
|
||||
|
||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
<link href="{{ asset('/manifest.json') }}" rel="manifest">
|
||||
<link href="{{ asset('/favicon.ico') }}" rel="shortcut icon" type="image/x-icon">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="https://fonts.googleapis.com" rel="preconnect">
|
||||
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/sass/app.scss', 'resources/js/app.js'])
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="row g-0 h-100">
|
||||
<div class="align-content-center col-md-6 d-none d-md-flex flex-column justify-content-center bg-body-tertiary">
|
||||
<div class="container py-4 text-center">
|
||||
<a href="{{ url('/') }}">
|
||||
<img class="mb-4" src="{{ asset('storage/images/logo.png') }}">
|
||||
</a>
|
||||
<h1>{{ config('app.name', 'Laravel') }}</h1>
|
||||
<p class="text-black-50">
|
||||
{{ __('general.MetaDescription') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 d-flex flex-column grid justify-content-center align-content-center">
|
||||
<div class="container py-4 px-4">
|
||||
<div class="d-md-none text-center mb-4">
|
||||
<img src="{{ asset('storage/images/logo.png') }}" class="mb-4" width="64px" height="64px">
|
||||
</div>
|
||||
|
||||
<x-alerts/>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xl-6">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
128
resources/views/components/navigation.blade.php
Normal file
128
resources/views/components/navigation.blade.php
Normal file
@ -0,0 +1,128 @@
|
||||
<div class="layout-nav">
|
||||
<div class="dropdown mb-2">
|
||||
<a class="app-nav-header mb-2" data-bs-toggle="dropdown">
|
||||
<div class="app-nav-logo">
|
||||
<img src="{{ asset('storage/images/logo.png') }}" width="32px" height="32px">
|
||||
</div>
|
||||
{{-- <div class="app-nav-logo random-bg-2">
|
||||
SA
|
||||
</div> --}}
|
||||
<div class="app-nav-header-content">
|
||||
<div class="fw-semibold">{{ config('app.name', 'Laravel') }}</div>
|
||||
{{-- <div class="fw-semibold">Steelants</div> --}}
|
||||
{{-- <small class="text-body-secondary">steelants.cz</small> --}}
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<svg class="bi bi-chevron-expand" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M3.646 9.146a.5.5 0 0 1 .708 0L8 12.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708m0-2.292a.5.5 0 0 0 .708 0L8 3.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="#">
|
||||
<div class="dropdown-img random-bg-2">
|
||||
SA
|
||||
</div>
|
||||
{{-- <div class="dropdown-img">
|
||||
<img src="{{ asset('storage/images/logo.png') }}" width="2rem" height="2rem">
|
||||
</div> --}}
|
||||
<div class="lh-1">
|
||||
<div class="fw-semibold">Steelants</div>
|
||||
<small class="text-body-secondary">1 member</small>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#">
|
||||
{{-- <div class="dropdown-img random-bg-3">
|
||||
A
|
||||
</div> --}}
|
||||
<div class="dropdown-img">
|
||||
<img src="{{ asset('storage/images/logo.png') }}" width="2rem" height="2rem">
|
||||
</div>
|
||||
<div class="lh-1">
|
||||
<div class="fw-semibold">Anthill</div>
|
||||
<small class="text-body-secondary">42 members</small>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="app-nav nav flex-column">
|
||||
@foreach ($mainMenuItems as $item)
|
||||
<li class="nav-item {{ ($item->isActive() || $item->isUse()) ? 'is-active' : '' }}">
|
||||
<a class="nav-link" href="{{ route($item->route, $item->parameters) }}">
|
||||
<i class="nav-link-ico {{ $item->icon }}"></i>
|
||||
{{ __($item->title) }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
{{-- MAIN NAVIGATION ALL --}}
|
||||
@auth
|
||||
<li class="mt-4 text-body-secondary"><small>{{ __('boilerplate::ui.system') }}</small></li>
|
||||
@foreach ($systemMenuItems as $item)
|
||||
<li class="nav-item {{ ($item->isActive() || $item->isUse()) ? 'is-active' : '' }}">
|
||||
<a class="nav-link" href="{{ route($item->route, $item->parameters) }}">
|
||||
<i class="nav-link-ico {{ $item->icon }}"></i>
|
||||
{{ __($item->title) }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
{{-- MAIN NAVIGATION SYSTEM --}}
|
||||
@endauth
|
||||
</ul>
|
||||
|
||||
<div class="mt-auto">
|
||||
<div class="dropup">
|
||||
<a class="app-nav-user" data-bs-toggle="dropdown">
|
||||
<div class="app-nav-profile random-bg-1">
|
||||
PS
|
||||
</div>
|
||||
<div class="app-nav-header-content">
|
||||
<div class="fw-semibold">{{ Auth::user()->name }}</div>
|
||||
<small class="text-body-secondary">{{ Auth::user()->email }}</small>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-three-dots-vertical" viewBox="0 0 16 16">
|
||||
<path d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0m0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-header d-flex align-items-center">
|
||||
<div class="fw-semibold me-auto">{{ Auth::user()->name }}</div>
|
||||
<small class="text-body-tertiary">v1.23</small>
|
||||
</div>
|
||||
|
||||
<a class="dropdown-item" href="{{ route('profile.index') }}">
|
||||
<i class="dropdown-ico fas fa-user"></i>
|
||||
{{ __('boilerplate::ui.profile') }}
|
||||
</a>
|
||||
|
||||
<label class="dropdown-item">
|
||||
<i class="dropdown-ico fas fa-moon"></i>
|
||||
<div class="me-auto">{{ __('boilerplate::ui.dark_mode') }}</div>
|
||||
<div class="form-switch ms-4">
|
||||
<input {{ Cookie::get('theme') == 'dark' ? 'checked' : '' }} class="form-check-input me-0" id="datk-theme" onchange="toggleDatkTheme()" type="checkbox">
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<a class="dropdown-item" href="{{ route('profile.api') }}">
|
||||
<i class="dropdown-ico fas fa-server"></i>
|
||||
{{ __('boilerplate::ui.api_tokens') }}
|
||||
</a>
|
||||
|
||||
<hr class="dropdown-divider">
|
||||
|
||||
<a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault();document.getElementById('logout-form').submit();">
|
||||
<i class="dropdown-ico fas fa-sign-out-alt text-danger"></i>
|
||||
{{ __('boilerplate::ui.logout') }}
|
||||
</a>
|
||||
|
||||
<form action="{{ route('logout') }}" class="d-none" id="logout-form" method="POST">
|
||||
@csrf
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
5
resources/views/errors/404.blade.php
Normal file
5
resources/views/errors/404.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<x-layout-auth>
|
||||
<h1>404</h1>
|
||||
<p>{{ __('boilerplate::ui.not_found') }}</p>
|
||||
<a class="btn btn-primary" href="{{ url('/') }}">{{ __('boilerplate::ui.home') }}</a>
|
||||
</x-layout-auth>
|
5
resources/views/errors/4xx.blade.php
Normal file
5
resources/views/errors/4xx.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<x-layout-auth>
|
||||
<h1>{{ $exception->getStatusCode() }}</h1>
|
||||
<p>{{ $exception->getMessage() }}</p>
|
||||
<a class="btn btn-primary" href="{{ url('/') }}">{{ __('boilerplate::ui.home') }}</a>
|
||||
</x-layout-auth>
|
5
resources/views/errors/503.blade.php
Normal file
5
resources/views/errors/503.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<x-layout-auth>
|
||||
<h1>503</h1>
|
||||
<p>{{ __('boilerplate::ui.maintenance') }}</p>
|
||||
<a class="btn btn-primary" href="{{ url('/') }}">{{ __('boilerplate::ui.home') }}</a>
|
||||
</x-layout-auth>
|
5
resources/views/errors/5xx.blade.php
Normal file
5
resources/views/errors/5xx.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<x-layout-auth>
|
||||
<h1>{{ $exception->getStatusCode() }}</h1>
|
||||
<p>{{ $exception->getMessage() }}</p>
|
||||
<a class="btn btn-primary" href="{{ url('/') }}">{{ __('boilerplate::ui.home') }}</a>
|
||||
</x-layout-auth>
|
189
resources/views/home.blade.php
Normal file
189
resources/views/home.blade.php
Normal file
@ -0,0 +1,189 @@
|
||||
<x-layout-app>
|
||||
<div class="container-xl">
|
||||
<div class="page-header">
|
||||
<h1>Welcolm Back !</h1>
|
||||
<a class="btn btn-primary" href="{{ url('home') }}"><i class="fa fa-plus me-2"></i> Page Action</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-xl">
|
||||
|
||||
<h1>head 1</h1>
|
||||
<h2>head 2</h2>
|
||||
<h3>head 3</h3>
|
||||
<h4>head 4</h4>
|
||||
<h5>head 5</h5>
|
||||
<h6>head 6</h6>
|
||||
|
||||
<div class="my-4">
|
||||
<h4>Tabs</h4>
|
||||
<ul class="nav nav-tabs mb-4">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="#">Active</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Link</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Link</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="#">Active</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Link</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Link</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="my-4 d-flex">
|
||||
<div class="app-nav-profile random-bg-1">
|
||||
PS
|
||||
</div>
|
||||
<div class="app-nav-profile random-bg-2">
|
||||
PS
|
||||
</div>
|
||||
<div class="app-nav-profile random-bg-3">
|
||||
PS
|
||||
</div>
|
||||
<div class="app-nav-profile random-bg-4">
|
||||
PS
|
||||
</div>
|
||||
<div class="app-nav-profile random-bg-5">
|
||||
PS
|
||||
</div>
|
||||
<div class="app-nav-profile random-bg-6">
|
||||
PS
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<h4>Buttons</h4>
|
||||
<button type="button" class="btn btn-primary">Primary</button>
|
||||
<button type="button" class="btn btn-secondary">Secondary</button>
|
||||
<button type="button" class="btn btn-success">Success</button>
|
||||
<button type="button" class="btn btn-danger">Danger</button>
|
||||
<button type="button" class="btn btn-warning">Warning</button>
|
||||
<button type="button" class="btn btn-info">Info</button>
|
||||
<button type="button" class="btn btn-light">Light</button>
|
||||
<button type="button" class="btn btn-dark">Dark</button>
|
||||
<button type="button" class="btn">Button</button>
|
||||
<button type="button" class="btn btn-link">Link</button>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<h4>Badges</h4>
|
||||
<span class="badge text-bg-primary">Primary</span>
|
||||
<span class="badge text-bg-secondary">Secondary</span>
|
||||
<span class="badge text-bg-success">Success</span>
|
||||
<span class="badge text-bg-danger">Danger</span>
|
||||
<span class="badge text-bg-warning">Warning</span>
|
||||
<span class="badge text-bg-info">Info</span>
|
||||
<span class="badge text-bg-light">Light</span>
|
||||
<span class="badge text-bg-dark">Dark</span>
|
||||
<br>
|
||||
<span class="badge rounded-pill text-bg-primary">Primary</span>
|
||||
<span class="badge rounded-pill text-bg-secondary">Secondary</span>
|
||||
<span class="badge rounded-pill text-bg-success">Success</span>
|
||||
<span class="badge rounded-pill text-bg-danger">Danger</span>
|
||||
<span class="badge rounded-pill text-bg-warning">Warning</span>
|
||||
<span class="badge rounded-pill text-bg-info">Info</span>
|
||||
<span class="badge rounded-pill text-bg-light">Light</span>
|
||||
<span class="badge rounded-pill text-bg-dark">Dark</span>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<h4>Breadcrumbs</h4>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item active" aria-current="page">Home</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Library</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
||||
<li class="breadcrumb-item"><a href="#">Library</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Data</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<h4>Alerts</h4>
|
||||
<div class="alert alert-primary" role="alert">
|
||||
A simple primary alert—check it out!
|
||||
</div>
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
A simple secondary alert—check it out!
|
||||
</div>
|
||||
<div class="alert alert-success" role="alert">
|
||||
A simple success alert—check it out!
|
||||
</div>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
A simple danger alert—check it out!
|
||||
</div>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
A simple warning alert—check it out!
|
||||
</div>
|
||||
<div class="alert alert-info" role="alert">
|
||||
A simple info alert—check it out!
|
||||
</div>
|
||||
<div class="alert alert-light" role="alert">
|
||||
A simple light alert—check it out!
|
||||
</div>
|
||||
<div class="alert alert-dark" role="alert">
|
||||
A simple dark alert—check it out!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<h4>Pagination</h4>
|
||||
<nav aria-label="...">
|
||||
<ul class="pagination">
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link">Previous</a>
|
||||
</li>
|
||||
<li class="page-item"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item active" aria-current="page">
|
||||
<a class="page-link" href="#">2</a>
|
||||
</li>
|
||||
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<h4>Colors</h4>
|
||||
<div class="text-bg-primary p-3">Primary with contrasting color</div>
|
||||
<div class="text-bg-secondary p-3">Secondary with contrasting color</div>
|
||||
<div class="text-bg-success p-3">Success with contrasting color</div>
|
||||
<div class="text-bg-danger p-3">Danger with contrasting color</div>
|
||||
<div class="text-bg-warning p-3">Warning with contrasting color</div>
|
||||
<div class="text-bg-info p-3">Info with contrasting color</div>
|
||||
<div class="text-bg-light p-3">Light with contrasting color</div>
|
||||
<div class="text-bg-dark p-3">Dark with contrasting color</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-layout-app>
|
13
resources/views/hosts/index.blade.php
Normal file
13
resources/views/hosts/index.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
<x-layout-app>
|
||||
<div class="container-xl">
|
||||
<div class="page-header">
|
||||
<h1>{{ __('boilerplate::hosts.title') }}</h1>
|
||||
|
||||
<button class="btn btn-primary" onclick="Livewire.dispatch('openModal', {livewireComponents: 'host.form', title: '{{ __('boilerplate::host.create') }}'})">
|
||||
<i class="me-2 fas fa-plus"></i><span>{{ __('boilerplate::ui.add') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@livewire('host.data-table', [], key('data-table'))
|
||||
</div>
|
||||
</x-layout-app>
|
6
resources/views/livewire/host/form.blade.php
Normal file
6
resources/views/livewire/host/form.blade.php
Normal file
@ -0,0 +1,6 @@
|
||||
<div>
|
||||
<x-form::form wire:submit.prevent="{{$action}}">
|
||||
<x-form::input group-class="mb-3" type="text" wire:model="hostname" id="hostname" label="hostname"/>
|
||||
<x-form::button class="btn-primary" type="submit">Create</x-form::button>
|
||||
</x-form::form>
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user