mirror of
https://github.com/crivion/laranode.git
synced 2026-09-03 06:24:09 +08:00
edit user account in progress -> requires shell and system user password updating
This commit is contained in:
@@ -3,10 +3,12 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\CreateAccountRequest;
|
||||
use App\Http\Requests\EditAccountRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\Accounts\CreateAccountException;
|
||||
use App\Services\Accounts\CreateAccountService;
|
||||
use App\Services\Accounts\DeleteAccountService;
|
||||
use App\Services\Accounts\UpdateAccountService;
|
||||
use Exception;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -39,11 +41,16 @@ class AccountsController extends Controller
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, string $id)
|
||||
public function update(User $account, EditAccountRequest $request): RedirectResponse
|
||||
{
|
||||
//
|
||||
(new UpdateAccountService($account, $request->validated()))->handle();
|
||||
|
||||
session()->flash('success', 'Account updated successfully!');
|
||||
|
||||
return redirect()->route('accounts.index');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
|
||||
38
app/Http/Requests/EditAccountRequest.php
Normal file
38
app/Http/Requests/EditAccountRequest.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class EditAccountRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'id' => ['required', 'integer', 'exists:users,id'],
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->id)],
|
||||
'new_password' => ['nullable', Password::defaults()],
|
||||
'role' => ['required', 'string', 'in:admin,user'],
|
||||
'domain_limit' => ['nullable', 'integer', 'min:1'],
|
||||
'database_limit' => ['nullable', 'integer', 'min:1'],
|
||||
'ssh_access' => ['required', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
57
app/Services/Accounts/UpdateAccountService.php
Normal file
57
app/Services/Accounts/UpdateAccountService.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Accounts;
|
||||
|
||||
use App\Models\PhpVersion;
|
||||
use App\Models\User;
|
||||
use App\Services\Laranode\CreatePhpFpmPoolService;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Exception;
|
||||
|
||||
class UpdateAccountException extends Exception {}
|
||||
|
||||
class UpdateAccountService
|
||||
{
|
||||
public function __construct(private User $account, private array $validated) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$this->account->update($this->validated);
|
||||
|
||||
$this->updateUserShell($this->account->ssh_access);
|
||||
|
||||
$this->updatePasswordIfRequested();
|
||||
}
|
||||
|
||||
private function updatePasswordIfRequested(): void
|
||||
{
|
||||
if (!empty($this->validated['new_password'])) {
|
||||
$this->account->password = $this->validated['new_password'];
|
||||
$this->account->save();
|
||||
|
||||
$this->updateSystemUserPasssword();
|
||||
}
|
||||
}
|
||||
|
||||
private function updateSystemUserPasssword(): void
|
||||
{
|
||||
$this->account->refresh();
|
||||
|
||||
if ($this->account->ssh_access) {
|
||||
// TODO: call laranode password update manager
|
||||
}
|
||||
}
|
||||
|
||||
private function updateUserShell(): void
|
||||
{
|
||||
// TODO: call laranode user manager
|
||||
if ($this->account->ssh_access != $this->validated['ssh_access']) {
|
||||
if ($this->validated['ssh_access']) {
|
||||
// add shell access
|
||||
} else {
|
||||
// remove shell access
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
public/favico.png
Normal file
BIN
public/favico.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -1,24 +1,32 @@
|
||||
import { Link, usePage } from "@inertiajs/react";
|
||||
import { RiDashboard3Fill } from "react-icons/ri";
|
||||
import { useState } from "react";
|
||||
import { RiDashboard3Fill, RiMvFill } from "react-icons/ri";
|
||||
import { ImProfile } from "react-icons/im";
|
||||
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";
|
||||
import { TbWorldWww } from "react-icons/tb";
|
||||
|
||||
const SidebarNavi = () => {
|
||||
|
||||
const { auth } = usePage().props;
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
|
||||
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">
|
||||
return (<div className={`fixed flex flex-col top-14 left-0 ${isSidebarOpen ? 'w-64' : 'w-14'} 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">
|
||||
<li className="hidden md:block">
|
||||
<li>
|
||||
<div className="flex flex-row items-center h-8">
|
||||
<div className="text-sm font-light tracking-wide text-gray-400 uppercase ml-4">
|
||||
<div className="text-sm font-light tracking-wide text-gray-400 uppercase ml-4 hidden md:block">
|
||||
Menu
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={() => setIsSidebarOpen(!isSidebarOpen)} className="text-gray-400 ml-4 block md:hidden">
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clipRule="evenodd"></path></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -38,7 +46,7 @@ const SidebarNavi = () => {
|
||||
{auth.user.role == 'admin' && (
|
||||
<li>
|
||||
<Link
|
||||
to={route('accounts.index')}
|
||||
href={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>
|
||||
@@ -49,6 +57,17 @@ const SidebarNavi = () => {
|
||||
</li>
|
||||
)}
|
||||
|
||||
<li>
|
||||
<Link
|
||||
href="/domains"
|
||||
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>
|
||||
<TbWorldWww className="ml-3 w-5 h-5" />
|
||||
</div>
|
||||
<span className="ml-2 text-sm tracking-wide truncate">Domains</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
{auth.user.role == 'admin' && (
|
||||
<li>
|
||||
@@ -78,7 +97,7 @@ const SidebarNavi = () => {
|
||||
|
||||
<li>
|
||||
<Link
|
||||
to="/mysql"
|
||||
href="/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>
|
||||
@@ -91,7 +110,7 @@ const SidebarNavi = () => {
|
||||
{auth.user.role == 'admin' && (
|
||||
<li>
|
||||
<Link
|
||||
to="/php-manager"
|
||||
href="/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>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FaUsers } from "react-icons/fa6";
|
||||
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
|
||||
import { Head, Link } from '@inertiajs/react';
|
||||
import CreateUserForm from './Partials/CreateAccountForm';
|
||||
import EditAccountForm from './Partials/EditAccountForm';
|
||||
import ConfirmationButton from "@/Components/ConfirmationButton";
|
||||
import { TiDelete } from "react-icons/ti";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -9,10 +10,10 @@ import { router } from '@inertiajs/react'
|
||||
import { TbWorldWww } from "react-icons/tb";
|
||||
import { RiLoginCircleLine } from "react-icons/ri";
|
||||
import { FaDatabase, FaEdit } from "react-icons/fa";
|
||||
import { Tooltip } from 'react-tooltip'
|
||||
|
||||
export default function Accounts({ accounts }) {
|
||||
|
||||
|
||||
const deleteUser = (id) => {
|
||||
router.delete(route('accounts.destroy', { account: id }), {
|
||||
onBefore: () => {
|
||||
@@ -74,13 +75,13 @@ export default function Accounts({ accounts }) {
|
||||
<td className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||
<div className='flex items-center'>
|
||||
<FaDatabase className='w-4 h-4 mr-1' />
|
||||
<span className="text-sm bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 p-1 rounded-full">
|
||||
<span className="text-xs bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 py-1 px-3 rounded-full">
|
||||
{account.domain_limit ?? '∞'}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center mt-1.5'>
|
||||
<TbWorldWww className='w-4 h-4 mr-1' />
|
||||
<span className="text-sm bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 p-1 rounded-full">
|
||||
<span className="text-xs bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 py-1 px-3 rounded-full">
|
||||
{account.database_limit ?? '∞'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -95,12 +96,18 @@ export default function Accounts({ accounts }) {
|
||||
whitespace-nowrap dark:text-white">
|
||||
|
||||
<div className='flex items-center space-x-2'>
|
||||
<FaEdit className='w-4 h-4' />
|
||||
<EditAccountForm account={account} />
|
||||
|
||||
<Link href={route('accounts.impersonate', { user: account.id })}>
|
||||
<Link href={route('accounts.impersonate', { user: account.id })}
|
||||
data-tooltip-id={`tooltip-impersonate-${account.id}`}
|
||||
data-tooltip-content="Impersonate User"
|
||||
data-tooltip-place="top"
|
||||
>
|
||||
<RiLoginCircleLine className='w-4 h-4' />
|
||||
</Link>
|
||||
|
||||
<Tooltip id={`tooltip-impersonate-${account.id}`} />
|
||||
|
||||
<ConfirmationButton doAction={() => deleteUser(account.id)}>
|
||||
<TiDelete className='w-6 h-6 text-red-500' />
|
||||
</ConfirmationButton>
|
||||
|
||||
280
resources/js/Pages/Accounts/Partials/EditAccountForm.jsx
Normal file
280
resources/js/Pages/Accounts/Partials/EditAccountForm.jsx
Normal file
@@ -0,0 +1,280 @@
|
||||
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 { useState } from 'react';
|
||||
import { FaEdit, FaUserEdit } from 'react-icons/fa';
|
||||
import InputRadio from '@/Components/InputRadio';
|
||||
import { router } from '@inertiajs/react'
|
||||
|
||||
export default function EditAccountForm({ account }) {
|
||||
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,
|
||||
patch,
|
||||
processing,
|
||||
errors,
|
||||
clearErrors,
|
||||
} = useForm({
|
||||
id: account.id,
|
||||
name: account.name,
|
||||
email: account.email,
|
||||
role: account.role,
|
||||
domain_limit: account.domain_limit,
|
||||
database_limit: account.database_limit,
|
||||
ssh_access: account.ssh_access,
|
||||
new_password: '',
|
||||
});
|
||||
|
||||
const showEditModal = () => {
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const updateUser = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
patch(route('accounts.update', { account: account.id }), {
|
||||
onSuccess: () => {
|
||||
router.visit(route('accounts.index'));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setShowModal(false);
|
||||
clearErrors();
|
||||
};
|
||||
|
||||
|
||||
if (!account) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={showEditModal} className='flex items-center text-gray-700 dark:text-gray-300'>
|
||||
<FaEdit className='mr-2' />
|
||||
</button>
|
||||
|
||||
<Modal show={showModal} onClose={closeModal}>
|
||||
<form onSubmit={updateUser} className="p-6">
|
||||
<h2 className="text-lg font-medium text-gray-900 dark:text-gray-100 flex items-center">
|
||||
<FaUserEdit className='mr-2' />
|
||||
Edit Account
|
||||
</h2>
|
||||
|
||||
<div className="mt-6 grid grid-cols-1 lg:grid-cols-2 gap-4 max-h-[500px] overflow-scroll">
|
||||
|
||||
<div>
|
||||
<InputLabel
|
||||
htmlFor="name"
|
||||
value="Name"
|
||||
className='my-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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel
|
||||
htmlFor="email"
|
||||
value="Email"
|
||||
className='my-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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel
|
||||
htmlFor="new_password"
|
||||
value="New Password (leave empty to keep current)"
|
||||
className='my-2'
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="new_password"
|
||||
name="new_password"
|
||||
onChange={(e) =>
|
||||
setData('new_password', e.target.value)
|
||||
}
|
||||
className="mt-1 block w-full"
|
||||
placeholder="New Password"
|
||||
/>
|
||||
|
||||
<InputError
|
||||
message={errors.new_password}
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel
|
||||
htmlFor="role"
|
||||
value="Role"
|
||||
className='my-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>
|
||||
|
||||
|
||||
<div>
|
||||
<InputLabel
|
||||
htmlFor="domain_limit"
|
||||
value="Domain Limit (leave empty for unlimited)"
|
||||
className='my-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='my-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 className='mt-4 flex items-center space-x-4'>
|
||||
<Checkbox
|
||||
id="ssh"
|
||||
name="ssh"
|
||||
checked={data.ssh_access}
|
||||
onChange={(e) =>
|
||||
setData('ssh_access', e.target.checked)
|
||||
}
|
||||
className="mr-1"
|
||||
/>
|
||||
<div className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Allow SSH/SFTP access
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<PrimaryButton className="mr-3" disabled={processing}>
|
||||
Update Account
|
||||
</PrimaryButton>
|
||||
|
||||
<SecondaryButton onClick={closeModal}>
|
||||
Cancel
|
||||
</SecondaryButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,6 @@ createInertiaApp({
|
||||
root.render(<App {...props} />);
|
||||
},
|
||||
progress: {
|
||||
color: '#4B5563',
|
||||
color: '#AC825F',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
<!-- Fonts -->
|
||||
<link href="{{ asset('fonts/satoshi/css/satoshi.css') }}" rel="stylesheet" />
|
||||
|
||||
<!-- Favicon for browsers -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favico.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favico.png">
|
||||
|
||||
<!-- Favicon for iOS devices -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favico.png">
|
||||
|
||||
<!-- Favicon for Android devices -->
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/favico.png">
|
||||
|
||||
<!-- Scripts -->
|
||||
@routes
|
||||
|
||||
0
storage/app/.gitignore
vendored
Normal file → Executable file
0
storage/app/.gitignore
vendored
Normal file → Executable file
0
storage/app/private/.gitignore
vendored
Normal file → Executable file
0
storage/app/private/.gitignore
vendored
Normal file → Executable file
0
storage/app/public/.gitignore
vendored
Normal file → Executable file
0
storage/app/public/.gitignore
vendored
Normal file → Executable file
0
storage/framework/.gitignore
vendored
Normal file → Executable file
0
storage/framework/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/data/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/data/.gitignore
vendored
Normal file → Executable file
0
storage/framework/sessions/.gitignore
vendored
Normal file → Executable file
0
storage/framework/sessions/.gitignore
vendored
Normal file → Executable file
0
storage/framework/testing/.gitignore
vendored
Normal file → Executable file
0
storage/framework/testing/.gitignore
vendored
Normal file → Executable file
0
storage/framework/views/.gitignore
vendored
Normal file → Executable file
0
storage/framework/views/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
@@ -1,7 +1,6 @@
|
||||
import { defineConfig } from "vite";
|
||||
import laravel from "laravel-vite-plugin";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import fs from "fs";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
|
||||
Reference in New Issue
Block a user