mirror of
https://github.com/crivion/laranode.git
synced 2026-05-06 21:52:34 +08:00
remove registration, move toastcontainer in parent, create account in progress
This commit is contained in:
20
app/Actions/Accounts/CreateAccountAction.php
Normal file
20
app/Actions/Accounts/CreateAccountAction.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Accounts;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
|
||||
class CreateAccountAction
|
||||
{
|
||||
public function execute(array $validated): void
|
||||
{
|
||||
$user = User::create($validated);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
if ($validated['notify']) {
|
||||
\Illuminate\Support\Facades\Log::info('Would notify ' . $user->email);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
app/Http/Controllers/AccountsController.php
Normal file
50
app/Http/Controllers/AccountsController.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Accounts\CreateAccountAction;
|
||||
use App\Http\Requests\CreateAccountRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class AccountsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$accounts = User::all();
|
||||
return Inertia::render('Accounts/Index', compact('accounts'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(CreateAccountRequest $request, CreateAccountAction $createAccount)
|
||||
{
|
||||
$createAccount->execute($request->validated());
|
||||
|
||||
return redirect()->route('accounts.index');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy($account)
|
||||
{
|
||||
User::findOrFail($account)->delete();
|
||||
|
||||
return redirect()->route('accounts.index');
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the registration view.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Auth/Register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
37
app/Http/Requests/CreateAccountRequest.php
Normal file
37
app/Http/Requests/CreateAccountRequest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class CreateAccountRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->role == 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'username' => 'required|regex:/^[a-zA-Z0-9_-]+$/|string|max:25|unique:' . User::class,
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:' . User::class,
|
||||
'password' => ['required', Password::defaults()],
|
||||
'role' => ['required', 'string', 'in:admin,user'],
|
||||
'domain_limit' => ['nullable', 'integer', 'min:1'],
|
||||
'database_limit' => ['nullable', 'integer', 'min:1'],
|
||||
'notify' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ namespace App\Listeners;
|
||||
|
||||
use App\Events\SystemStatsEvent;
|
||||
use App\Events\TopStatsEvent;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Laravel\Reverb\Events\MessageReceived;
|
||||
|
||||
class MessageReceivedListener
|
||||
@@ -15,13 +14,10 @@ class MessageReceivedListener
|
||||
*/
|
||||
public function handle(MessageReceived $event)
|
||||
{
|
||||
Log::info('MessageReceivedListener::handle called');
|
||||
|
||||
$msg = json_decode($event->message);
|
||||
|
||||
if ($msg->event == 'client-typing') {
|
||||
Log::info('Received client-typing');
|
||||
|
||||
match ($msg->channel) {
|
||||
'private-systemstats' => (new SystemStatsEvent())->dispatch(),
|
||||
'private-topstats' => (new TopStatsEvent())->dispatch(),
|
||||
|
||||
@@ -19,8 +19,12 @@ class User extends Authenticatable
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'username',
|
||||
'email',
|
||||
'password',
|
||||
'role',
|
||||
'domain_limit',
|
||||
'database_limit',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,12 @@ return new class extends Migration
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
$table->string('username')->unique();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->string('password');
|
||||
$table->enum('role', ['admin', 'user'])->default('user');
|
||||
$table->integer('domain_limit')->nullable();
|
||||
$table->integer('database_limit')->nullable();
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
47
resources/js/Components/ConfirmationButton.jsx
Normal file
47
resources/js/Components/ConfirmationButton.jsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import Modal from '@/Components/Modal';
|
||||
import { useState } from 'react';
|
||||
import DangerButton from './DangerButton';
|
||||
import SecondaryButton from './SecondaryButton';
|
||||
|
||||
export default function ConfirmationButton({ children, buttonClassName = '', doAction }) {
|
||||
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const closeModal = () => {
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
return (<>
|
||||
<button className={buttonClassName} onClick={() => setShowModal(true)}>
|
||||
{children}
|
||||
</button>
|
||||
<Modal show={showModal} onClose={closeModal} maxWidth="sm">
|
||||
<div className="p-4">
|
||||
<h2 className="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
Are you sure?
|
||||
</h2>
|
||||
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<DangerButton onClick={() => {
|
||||
doAction();
|
||||
closeModal();
|
||||
}} className='mr-2'>
|
||||
Yes, I'm sure
|
||||
</DangerButton>
|
||||
|
||||
<SecondaryButton onClick={() => closeModal()}>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
12
resources/js/Components/InputRadio.jsx
Normal file
12
resources/js/Components/InputRadio.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
export default function InputRadio({ className = '', ...props }) {
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
type="radio"
|
||||
className={
|
||||
'rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800 ' +
|
||||
className
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export default function AuthenticatedLayout({ header, children }) {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col flex-auto flex-shrink-0 antialiase bg-gray-100 dark:bg-gray-900">
|
||||
<ToastContainer theme='dark' />
|
||||
<TopNavi />
|
||||
<SidebarNavi />
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { Link } from "@inertiajs/react";
|
||||
import { Link, usePage } from "@inertiajs/react";
|
||||
import { RiDashboard3Fill } from "react-icons/ri";
|
||||
import { ImProfile } from "react-icons/im";
|
||||
import { FaUsers } from "react-icons/fa6";
|
||||
import { FaPhp, FaUsers } from "react-icons/fa6";
|
||||
import { VscFileSubmodule } from "react-icons/vsc";
|
||||
import { TbBrandMysql } from "react-icons/tb";
|
||||
import { MdSecurity } from "react-icons/md";
|
||||
import { IoLockClosedOutline } from "react-icons/io5";
|
||||
|
||||
const SidebarNavi = () => {
|
||||
|
||||
const { auth } = usePage().props;
|
||||
|
||||
return (<div className="fixed flex flex-col top-14 left-0 w-14 hover:w-64 md:w-64 bg-gray-950 dark:bg-gray-900 h-full text-white transition-all duration-300 border-none z-10 sidebar">
|
||||
<div className="overflow-y-auto overflow-x-hidden flex flex-col justify-between flex-grow dark:border-gray-800 dark:border-r">
|
||||
<ul className="flex flex-col py-4 space-y-2">
|
||||
@@ -19,7 +25,7 @@ const SidebarNavi = () => {
|
||||
<li>
|
||||
|
||||
<Link
|
||||
href="/dashboard"
|
||||
href={route('dashboard')}
|
||||
className="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-900 text-gray-300 border-l-4 border-transparent hover:border-indigo-900 pr-6"
|
||||
>
|
||||
<div>
|
||||
@@ -29,6 +35,35 @@ const SidebarNavi = () => {
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
{auth.user.role == 'admin' && (
|
||||
<li>
|
||||
<Link
|
||||
to={route('accounts.index')}
|
||||
className="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-900 text-gray-300 border-l-4 border-transparent hover:border-indigo-900 pr-6"
|
||||
>
|
||||
<div>
|
||||
<FaUsers className="ml-3 w-5 h-5" />
|
||||
</div>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">Accounts</span>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
|
||||
|
||||
{auth.user.role == 'admin' && (
|
||||
<li>
|
||||
<Link
|
||||
href="/admin/firewall"
|
||||
className="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-900 text-gray-300 border-l-4 border-transparent hover:border-indigo-900 pr-6"
|
||||
>
|
||||
<div>
|
||||
<MdSecurity className="ml-3 w-5 h-5" />
|
||||
</div>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">Firewall</span>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/filemanager"
|
||||
@@ -43,13 +78,37 @@ const SidebarNavi = () => {
|
||||
|
||||
<li>
|
||||
<Link
|
||||
to="/admin/accounts"
|
||||
to="/mysql"
|
||||
className="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-900 text-gray-300 border-l-4 border-transparent hover:border-indigo-900 pr-6"
|
||||
>
|
||||
<div>
|
||||
<FaUsers className="ml-3 w-5 h-5" />
|
||||
<TbBrandMysql className="ml-3 w-5 h-5" />
|
||||
</div>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">Accounts</span>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">MySQL DBs</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
to="/admin/php-manager"
|
||||
className="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-900 text-gray-300 border-l-4 border-transparent hover:border-indigo-900 pr-6"
|
||||
>
|
||||
<div>
|
||||
<FaPhp className="ml-3 w-5 h-5" />
|
||||
</div>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">PHP Manager</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/ssl"
|
||||
className="relative flex flex-row items-center h-11 focus:outline-none hover:bg-gray-900 text-gray-300 border-l-4 border-transparent hover:border-indigo-900 pr-6"
|
||||
>
|
||||
<div>
|
||||
<IoLockClosedOutline className="ml-3 w-5 h-5" />
|
||||
</div>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">SSL Manager</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
|
||||
88
resources/js/Pages/Accounts/Index.jsx
Normal file
88
resources/js/Pages/Accounts/Index.jsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { FaUsers } from "react-icons/fa6";
|
||||
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
|
||||
import { Head, Link } from '@inertiajs/react';
|
||||
import CreateUserForm from './Partials/CreateAccountForm';
|
||||
import ConfirmationButton from "@/Components/ConfirmationButton";
|
||||
import { TiDelete } from "react-icons/ti";
|
||||
import { toast } from "react-toastify";
|
||||
import { router } from '@inertiajs/react'
|
||||
|
||||
export default function Accounts({ accounts }) {
|
||||
|
||||
const deleteUser = (id) => {
|
||||
router.delete(route('accounts.destroy', { account: id }), {
|
||||
onSuccess: page => {
|
||||
toast("Account deleted successfully.");
|
||||
},
|
||||
onError: errors => {
|
||||
toast("Error occured while deleting account.");
|
||||
console.log(errors);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout
|
||||
header={
|
||||
<div className="flex flex-col xl:flex-row xl:justify-between max-w-7xl pr-5">
|
||||
<h2 className="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight flex items-center">
|
||||
<FaUsers className='mr-2' />
|
||||
Accounts
|
||||
</h2>
|
||||
<CreateUserForm className="max-w-xl" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Head title="Accounts" />
|
||||
|
||||
<div className="max-w-7xl px-4 my-8">
|
||||
|
||||
<div className="relative overflow-x-auto bg-white dark:bg-gray-850 mt-3">
|
||||
<table className="w-full text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
||||
<thead className="text-gray-700 uppercase bg-gray-200 dark:bg-gray-700 dark:text-gray-300 text-sm">
|
||||
<tr>
|
||||
<th className="px-6 py-3">ID</th>
|
||||
<th className="px-6 py-3">Name</th>
|
||||
<th className="px-6 py-3">Email</th>
|
||||
<th className="px-6 py-3">Limits</th>
|
||||
<th className="px-6 py-3">Role</th>
|
||||
<th className="px-6 py-3">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-sm">
|
||||
{accounts?.map((account, index) => (
|
||||
<tr key={`acc-${index}`} className="bg-white border-b text-gray-700 dark:text-gray-200 dark:bg-gray-850 dark:border-gray-700 border-gray-200">
|
||||
<td className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{account.id}
|
||||
</td>
|
||||
<td className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{account.name}
|
||||
</td>
|
||||
<td className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{account.email}
|
||||
</td>
|
||||
<td className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{account.limits?.db}/{account.limits?.domains}
|
||||
</td>
|
||||
<td className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
{account.role == "admin" ? <span className='bg-green-300 text-green-700 px-2 py-1 text-sm rounded-lg'>Admin</span> : <span className='bg-gray-300 text-gray-700 px-2 py-1 text-sm rounded-lg'>User</span>}
|
||||
</td>
|
||||
<td className="px-6 py-4 font-medium text-gray-900
|
||||
whitespace-nowrap dark:text-white">
|
||||
Edit / Impersonate
|
||||
|
||||
<ConfirmationButton doAction={() => deleteUser(account.id)}>
|
||||
<TiDelete className='w-6 h-6 text-red-500' />
|
||||
</ConfirmationButton>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
|
||||
311
resources/js/Pages/Accounts/Partials/CreateAccountForm.jsx
Normal file
311
resources/js/Pages/Accounts/Partials/CreateAccountForm.jsx
Normal file
@@ -0,0 +1,311 @@
|
||||
import DangerButton from '@/Components/DangerButton';
|
||||
import InputError from '@/Components/InputError';
|
||||
import InputLabel from '@/Components/InputLabel';
|
||||
import Modal from '@/Components/Modal';
|
||||
import PrimaryButton from '@/Components/PrimaryButton';
|
||||
import SecondaryButton from '@/Components/SecondaryButton';
|
||||
import Checkbox from '@/Components/Checkbox';
|
||||
import TextInput from '@/Components/TextInput';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { FaUserPlus } from 'react-icons/fa6';
|
||||
import InputRadio from '@/Components/InputRadio';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default function CreateAccountForm() {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const randomPassword = () => {
|
||||
const characters =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
let result = '';
|
||||
for (let i = 0; i < 12; i++) {
|
||||
result += characters.charAt(
|
||||
Math.floor(Math.random() * charactersLength)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
setData,
|
||||
post,
|
||||
processing,
|
||||
reset,
|
||||
errors,
|
||||
clearErrors,
|
||||
} = useForm({
|
||||
username: '',
|
||||
name: '',
|
||||
email: '',
|
||||
password: randomPassword(),
|
||||
role: '',
|
||||
domain_limit: null,
|
||||
database_limit: null,
|
||||
notify: false,
|
||||
});
|
||||
|
||||
const showCreateModal = () => {
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const createUser = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
post(route('accounts.store'), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
closeModal();
|
||||
reset();
|
||||
toast("Account created successfully.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setShowModal(false);
|
||||
|
||||
clearErrors();
|
||||
reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={showCreateModal} className='flex items-center text-gray-700 dark:text-gray-300'>
|
||||
<FaUserPlus className='mr-2' />
|
||||
Create Account
|
||||
</button>
|
||||
|
||||
<Modal show={showModal} onClose={closeModal}>
|
||||
<form onSubmit={createUser} className="p-6">
|
||||
<h2 className="text-lg font-medium text-gray-900 dark:text-gray-100 flex items-center">
|
||||
<FaUserPlus className='mr-2' />
|
||||
Create a New Account
|
||||
</h2>
|
||||
|
||||
<div className="mt-6">
|
||||
<InputLabel
|
||||
htmlFor="name"
|
||||
value="Name"
|
||||
className='mt-4 mb-2'
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<TextInput
|
||||
id="name"
|
||||
name="name"
|
||||
value={data.name}
|
||||
onChange={(e) =>
|
||||
setData('name', e.target.value)
|
||||
}
|
||||
className="mt-1 block w-full"
|
||||
isFocused
|
||||
placeholder="Full Name"
|
||||
/>
|
||||
|
||||
<InputError
|
||||
message={errors.name}
|
||||
className="mt-2"
|
||||
/>
|
||||
|
||||
|
||||
<InputLabel
|
||||
htmlFor="name"
|
||||
value="Username (a-z0-9-_)"
|
||||
className='mt-4 mb-2'
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="username"
|
||||
name="username"
|
||||
value={data.username}
|
||||
onChange={(e) =>
|
||||
setData('username', e.target.value)
|
||||
}
|
||||
className="mt-1 block w-full"
|
||||
placeholder="Username"
|
||||
/>
|
||||
|
||||
<InputError
|
||||
message={errors.username}
|
||||
className="mt-2"
|
||||
/>
|
||||
|
||||
<InputLabel
|
||||
htmlFor="email"
|
||||
value="Email"
|
||||
className='mt-4 mb-2'
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="email"
|
||||
name="email"
|
||||
value={data.email}
|
||||
onChange={(e) =>
|
||||
setData('email', e.target.value)
|
||||
}
|
||||
className="mt-1 block w-full"
|
||||
placeholder="Email"
|
||||
/>
|
||||
|
||||
<InputError
|
||||
message={errors.email}
|
||||
className="mt-2"
|
||||
/>
|
||||
|
||||
<InputLabel
|
||||
htmlFor="password"
|
||||
value="Password"
|
||||
className='mt-4 mb-2'
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="password"
|
||||
name="password"
|
||||
value={data.password}
|
||||
onChange={(e) =>
|
||||
setData('password', e.target.value)
|
||||
}
|
||||
className="mt-1 block w-full"
|
||||
placeholder="Password"
|
||||
/>
|
||||
|
||||
<InputError
|
||||
message={errors.password}
|
||||
className="mt-2"
|
||||
/>
|
||||
|
||||
<InputLabel
|
||||
htmlFor="role"
|
||||
value="Role"
|
||||
className='mt-4 mb-2'
|
||||
/>
|
||||
|
||||
<div className='flex items-center space-x-4'>
|
||||
<InputRadio
|
||||
id="admin"
|
||||
name="admin"
|
||||
checked={data.role == 'admin'}
|
||||
onChange={(e) =>
|
||||
setData('role', e.target.checked ? 'admin' : 'user')
|
||||
}
|
||||
className="h-4 w-4"
|
||||
value="admin"
|
||||
/>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Admin
|
||||
</span>
|
||||
|
||||
<InputRadio
|
||||
id="user"
|
||||
name="user"
|
||||
value={'user'}
|
||||
checked={data.role == 'user'}
|
||||
onChange={(e) =>
|
||||
setData('role', e.target.checked ? 'user' : 'admin')
|
||||
}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">
|
||||
User
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<InputError
|
||||
message={errors.role}
|
||||
className="mt-2"
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
|
||||
<div>
|
||||
<InputLabel
|
||||
htmlFor="domain_limit"
|
||||
value="Domain Limit (leave empty for unlimited)"
|
||||
className='mt-4 mb-2'
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="domain_limit"
|
||||
name="domain_limit"
|
||||
type="number"
|
||||
value={data.domain_limit}
|
||||
onChange={(e) =>
|
||||
setData('domain_limit', e.target.value)
|
||||
}
|
||||
className="mt-1 block w-full"
|
||||
placeholder="Domain Limit"
|
||||
/>
|
||||
|
||||
<InputError
|
||||
message={errors.domain_limit}
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel
|
||||
htmlFor="database_limit"
|
||||
value="Database Limit (leave empty for unlimited)"
|
||||
className='mt-4 mb-2'
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="database_limit"
|
||||
name="database_limit"
|
||||
type="number"
|
||||
value={data.database_limit}
|
||||
onChange={(e) =>
|
||||
setData('database_limit', e.target.value)
|
||||
}
|
||||
className="mt-1 block w-full"
|
||||
placeholder="Database Limit"
|
||||
/>
|
||||
|
||||
<InputError
|
||||
message={errors.database_limit}
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className='mt-4 flex items-center space-x-4'>
|
||||
<Checkbox
|
||||
id="notify"
|
||||
name="notify"
|
||||
checked={data.notify}
|
||||
onChange={(e) =>
|
||||
setData('notify', e.target.checked)
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
<div className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Notify user with account logins via email
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InputError
|
||||
message={errors.notify}
|
||||
className="mt-2"
|
||||
/>
|
||||
|
||||
<div className="mt-6 flex justify-end">
|
||||
<PrimaryButton className="mr-3" disabled={processing}>
|
||||
Create Account
|
||||
</PrimaryButton>
|
||||
|
||||
<SecondaryButton onClick={closeModal}>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -163,7 +163,6 @@ const Filemanager = () => {
|
||||
<VscFileSubmodule className='mr-2' />
|
||||
Filemanager
|
||||
</h2>
|
||||
<ToastContainer />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -187,7 +186,6 @@ const Filemanager = () => {
|
||||
<VscFileSubmodule className='mr-2' />
|
||||
Filemanager
|
||||
</h2>
|
||||
<ToastContainer />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -52,7 +52,6 @@ export default function StatsHistory({ selectedDate, cpuStats, memoryStats, netw
|
||||
}
|
||||
>
|
||||
<Head title="CPU & Memory Stats History" />
|
||||
<ToastContainer />
|
||||
|
||||
<div className="max-w-7xl">
|
||||
|
||||
|
||||
@@ -12,11 +12,6 @@ use App\Http\Controllers\Auth\VerifyEmailController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::middleware('guest')->group(function () {
|
||||
Route::get('register', [RegisteredUserController::class, 'create'])
|
||||
->name('register');
|
||||
|
||||
Route::post('register', [RegisteredUserController::class, 'store']);
|
||||
|
||||
Route::get('login', [AuthenticatedSessionController::class, 'create'])
|
||||
->name('login');
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\AccountsController;
|
||||
use App\Http\Controllers\DashboardController;
|
||||
use App\Http\Controllers\FilemanagerController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
@@ -20,7 +21,11 @@ Route::get('/dashboard/admin/get/top-sort', [DashboardController::class, 'getTop
|
||||
Route::patch('/dashboard/admin/set/top-sort', [DashboardController::class, 'setTopSort'])->middleware(['auth', AdminMiddleware::class])->name('dashboard.admin.setTopSort');
|
||||
Route::get('/dashboard/user', [DashboardController::class, 'user'])->middleware(['auth'])->name('dashboard.user');
|
||||
|
||||
// Filemanager
|
||||
|
||||
// Accounts [Admin]
|
||||
Route::resource('/accounts', AccountsController::class)->middleware(['auth', AdminMiddleware::class])->except(['create', 'edit', 'show']);
|
||||
|
||||
// Filemanager [Admin | User]
|
||||
Route::get('/filemanager', [FilemanagerController::class, 'index'])->middleware(['auth'])->name('filemanager');
|
||||
Route::get('/filemanager/get-directory-contents', [FilemanagerController::class, 'getDirectoryContents'])->middleware(['auth'])->name('filemanager.getDirectorContents');
|
||||
Route::get('/filemanager/get-file-contents', [FilemanagerController::class, 'getFileContents'])->middleware(['auth'])->name('filemanager.getFileContents');
|
||||
@@ -31,7 +36,7 @@ Route::patch('/filemanager/paste-files', [FilemanagerController::class, 'pasteFi
|
||||
Route::post('/filemanager/delete-files', [FilemanagerController::class, 'deleteFiles'])->middleware(['auth'])->name('filemanager.deleteFiles');
|
||||
Route::post('/filemanager/upload-file', [FilemanagerController::class, 'uploadFile'])->middleware(['auth'])->name('filemanager.uploadFile');
|
||||
|
||||
// Stats History
|
||||
// Stats History [Admin]
|
||||
Route::get('/stats/history', [StatsHistoryController::class, 'cpuAndMemory'])->middleware(['auth', AdminMiddleware::class])->name('stats.history');
|
||||
|
||||
// Accounts
|
||||
|
||||
Reference in New Issue
Block a user