mirror of
https://github.com/vitodeploy/vito.git
synced 2025-04-19 09:51:37 +00:00
User management (#185)
This commit is contained in:
parent
35f896eab1
commit
d846acaa8d
@ -10,10 +10,13 @@ class CreateProject
|
|||||||
{
|
{
|
||||||
public function create(User $user, array $input): Project
|
public function create(User $user, array $input): Project
|
||||||
{
|
{
|
||||||
|
if (isset($input['name'])) {
|
||||||
|
$input['name'] = strtolower($input['name']);
|
||||||
|
}
|
||||||
|
|
||||||
$this->validate($user, $input);
|
$this->validate($user, $input);
|
||||||
|
|
||||||
$project = new Project([
|
$project = new Project([
|
||||||
'user_id' => $user->id,
|
|
||||||
'name' => $input['name'],
|
'name' => $input['name'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -29,7 +32,7 @@ private function validate(User $user, array $input): void
|
|||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
'max:255',
|
'max:255',
|
||||||
'unique:projects,name,NULL,id,user_id,'.$user->id,
|
'unique:projects,name',
|
||||||
],
|
],
|
||||||
])->validate();
|
])->validate();
|
||||||
}
|
}
|
||||||
|
40
app/Actions/User/CreateUser.php
Normal file
40
app/Actions/User/CreateUser.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\User;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class CreateUser
|
||||||
|
{
|
||||||
|
public function create(array $input): User
|
||||||
|
{
|
||||||
|
$this->validate($input);
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = User::query()->create([
|
||||||
|
'name' => $input['name'],
|
||||||
|
'email' => $input['email'],
|
||||||
|
'role' => $input['role'],
|
||||||
|
'password' => bcrypt($input['password']),
|
||||||
|
'timezone' => 'UTC',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:users,email',
|
||||||
|
'password' => 'required|string|min:8',
|
||||||
|
'role' => [
|
||||||
|
'required',
|
||||||
|
Rule::in([UserRole::ADMIN, UserRole::USER]),
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
48
app/Actions/User/UpdateUser.php
Normal file
48
app/Actions/User/UpdateUser.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\User;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateUser
|
||||||
|
{
|
||||||
|
public function update(User $user, array $input): void
|
||||||
|
{
|
||||||
|
$this->validate($user, $input);
|
||||||
|
|
||||||
|
$user->name = $input['name'];
|
||||||
|
$user->email = $input['email'];
|
||||||
|
$user->timezone = $input['timezone'];
|
||||||
|
$user->role = $input['role'];
|
||||||
|
|
||||||
|
if (isset($input['password']) && $input['password'] !== null) {
|
||||||
|
$user->password = bcrypt($input['password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validate(User $user, array $input): void
|
||||||
|
{
|
||||||
|
Validator::make($input, [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
|
||||||
|
'timezone' => [
|
||||||
|
'required',
|
||||||
|
Rule::in(timezone_identifiers_list()),
|
||||||
|
],
|
||||||
|
'role' => [
|
||||||
|
'required',
|
||||||
|
Rule::in([UserRole::ADMIN, UserRole::USER]),
|
||||||
|
function ($attribute, $value, $fail) use ($user) {
|
||||||
|
if ($user->is(auth()->user()) && $value !== $user->role) {
|
||||||
|
$fail('You cannot change your own role');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])->validate();
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
class CreateUserCommand extends Command
|
class CreateUserCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'user:create {name} {email} {password}';
|
protected $signature = 'user:create {name} {email} {password} {--role=admin}';
|
||||||
|
|
||||||
protected $description = 'Create a new user';
|
protected $description = 'Create a new user';
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ public function handle(): void
|
|||||||
'name' => $this->argument('name'),
|
'name' => $this->argument('name'),
|
||||||
'email' => $this->argument('email'),
|
'email' => $this->argument('email'),
|
||||||
'password' => bcrypt($this->argument('password')),
|
'password' => bcrypt($this->argument('password')),
|
||||||
|
'role' => $this->option('role'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->info('User created!');
|
$this->info('User created!');
|
||||||
|
10
app/Enums/UserRole.php
Normal file
10
app/Enums/UserRole.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
final class UserRole
|
||||||
|
{
|
||||||
|
const USER = 'user';
|
||||||
|
|
||||||
|
const ADMIN = 'admin';
|
||||||
|
}
|
@ -22,6 +22,8 @@ class ApplicationController extends Controller
|
|||||||
{
|
{
|
||||||
public function deploy(Server $server, Site $site): HtmxResponse
|
public function deploy(Server $server, Site $site): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
app(Deploy::class)->run($site);
|
app(Deploy::class)->run($site);
|
||||||
|
|
||||||
@ -41,11 +43,15 @@ public function deploy(Server $server, Site $site): HtmxResponse
|
|||||||
|
|
||||||
public function showDeploymentLog(Server $server, Site $site, Deployment $deployment): RedirectResponse
|
public function showDeploymentLog(Server $server, Site $site, Deployment $deployment): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return back()->with('content', $deployment->log?->getContent());
|
return back()->with('content', $deployment->log?->getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateDeploymentScript(Server $server, Site $site, Request $request): RedirectResponse
|
public function updateDeploymentScript(Server $server, Site $site, Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(UpdateDeploymentScript::class)->update($site, $request->input());
|
app(UpdateDeploymentScript::class)->update($site, $request->input());
|
||||||
|
|
||||||
Toast::success('Deployment script updated!');
|
Toast::success('Deployment script updated!');
|
||||||
@ -55,6 +61,8 @@ public function updateDeploymentScript(Server $server, Site $site, Request $requ
|
|||||||
|
|
||||||
public function updateBranch(Server $server, Site $site, Request $request): RedirectResponse
|
public function updateBranch(Server $server, Site $site, Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(UpdateBranch::class)->update($site, $request->input());
|
app(UpdateBranch::class)->update($site, $request->input());
|
||||||
|
|
||||||
Toast::success('Branch updated!');
|
Toast::success('Branch updated!');
|
||||||
@ -64,11 +72,15 @@ public function updateBranch(Server $server, Site $site, Request $request): Redi
|
|||||||
|
|
||||||
public function getEnv(Server $server, Site $site): RedirectResponse
|
public function getEnv(Server $server, Site $site): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return back()->with('env', $site->getEnv());
|
return back()->with('env', $site->getEnv());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateEnv(Server $server, Site $site, Request $request): RedirectResponse
|
public function updateEnv(Server $server, Site $site, Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(UpdateEnv::class)->update($site, $request->input());
|
app(UpdateEnv::class)->update($site, $request->input());
|
||||||
|
|
||||||
Toast::success('Env updated!');
|
Toast::success('Env updated!');
|
||||||
@ -78,6 +90,8 @@ public function updateEnv(Server $server, Site $site, Request $request): Redirec
|
|||||||
|
|
||||||
public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
if (! $site->isAutoDeployment()) {
|
if (! $site->isAutoDeployment()) {
|
||||||
try {
|
try {
|
||||||
$site->enableAutoDeployment();
|
$site->enableAutoDeployment();
|
||||||
@ -101,6 +115,8 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
|||||||
|
|
||||||
public function disableAutoDeployment(Server $server, Site $site): HtmxResponse
|
public function disableAutoDeployment(Server $server, Site $site): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
if ($site->isAutoDeployment()) {
|
if ($site->isAutoDeployment()) {
|
||||||
try {
|
try {
|
||||||
$site->disableAutoDeployment();
|
$site->disableAutoDeployment();
|
||||||
|
@ -11,6 +11,8 @@ class ConsoleController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('console.index', [
|
return view('console.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
]);
|
]);
|
||||||
@ -18,6 +20,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function run(Server $server, Request $request)
|
public function run(Server $server, Request $request)
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'user' => [
|
'user' => [
|
||||||
'required',
|
'required',
|
||||||
|
@ -16,6 +16,8 @@ class CronjobController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('cronjobs.index', [
|
return view('cronjobs.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'cronjobs' => $server->cronJobs,
|
'cronjobs' => $server->cronJobs,
|
||||||
@ -24,6 +26,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function store(Server $server, Request $request): HtmxResponse
|
public function store(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(CreateCronJob::class)->create($server, $request->input());
|
app(CreateCronJob::class)->create($server, $request->input());
|
||||||
|
|
||||||
Toast::success('Cronjob created successfully.');
|
Toast::success('Cronjob created successfully.');
|
||||||
@ -33,6 +37,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function destroy(Server $server, CronJob $cronJob): RedirectResponse
|
public function destroy(Server $server, CronJob $cronJob): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeleteCronJob::class)->delete($server, $cronJob);
|
app(DeleteCronJob::class)->delete($server, $cronJob);
|
||||||
|
|
||||||
Toast::success('Cronjob deleted successfully.');
|
Toast::success('Cronjob deleted successfully.');
|
||||||
|
@ -18,6 +18,8 @@ class DatabaseBackupController extends Controller
|
|||||||
{
|
{
|
||||||
public function show(Server $server, Backup $backup): View
|
public function show(Server $server, Backup $backup): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('databases.backups', [
|
return view('databases.backups', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'databases' => $server->databases,
|
'databases' => $server->databases,
|
||||||
@ -28,6 +30,8 @@ public function show(Server $server, Backup $backup): View
|
|||||||
|
|
||||||
public function run(Server $server, Backup $backup): RedirectResponse
|
public function run(Server $server, Backup $backup): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(RunBackup::class)->run($backup);
|
app(RunBackup::class)->run($backup);
|
||||||
|
|
||||||
Toast::success('Backup is running.');
|
Toast::success('Backup is running.');
|
||||||
@ -37,6 +41,8 @@ public function run(Server $server, Backup $backup): RedirectResponse
|
|||||||
|
|
||||||
public function store(Server $server, Request $request): HtmxResponse
|
public function store(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(CreateBackup::class)->create('database', $server, $request->input());
|
app(CreateBackup::class)->create('database', $server, $request->input());
|
||||||
|
|
||||||
Toast::success('Backup created successfully.');
|
Toast::success('Backup created successfully.');
|
||||||
@ -46,6 +52,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function destroy(Server $server, Backup $backup): RedirectResponse
|
public function destroy(Server $server, Backup $backup): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$backup->delete();
|
$backup->delete();
|
||||||
|
|
||||||
Toast::success('Backup deleted successfully.');
|
Toast::success('Backup deleted successfully.');
|
||||||
@ -55,6 +63,8 @@ public function destroy(Server $server, Backup $backup): RedirectResponse
|
|||||||
|
|
||||||
public function restore(Server $server, Backup $backup, BackupFile $backupFile, Request $request): HtmxResponse
|
public function restore(Server $server, Backup $backup, BackupFile $backupFile, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(RestoreBackup::class)->restore($backupFile, $request->input());
|
app(RestoreBackup::class)->restore($backupFile, $request->input());
|
||||||
|
|
||||||
Toast::success('Backup restored successfully.');
|
Toast::success('Backup restored successfully.');
|
||||||
@ -64,6 +74,8 @@ public function restore(Server $server, Backup $backup, BackupFile $backupFile,
|
|||||||
|
|
||||||
public function destroyFile(Server $server, Backup $backup, BackupFile $backupFile): RedirectResponse
|
public function destroyFile(Server $server, Backup $backup, BackupFile $backupFile): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$backupFile->delete();
|
$backupFile->delete();
|
||||||
|
|
||||||
Toast::success('Backup file deleted successfully.');
|
Toast::success('Backup file deleted successfully.');
|
||||||
|
@ -17,6 +17,8 @@ class DatabaseController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('databases.index', [
|
return view('databases.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'databases' => $server->databases,
|
'databases' => $server->databases,
|
||||||
@ -27,6 +29,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function store(Server $server, Request $request): HtmxResponse
|
public function store(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$database = app(CreateDatabase::class)->create($server, $request->input());
|
$database = app(CreateDatabase::class)->create($server, $request->input());
|
||||||
|
|
||||||
if ($request->input('user')) {
|
if ($request->input('user')) {
|
||||||
@ -40,6 +44,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function destroy(Server $server, Database $database): RedirectResponse
|
public function destroy(Server $server, Database $database): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeleteDatabase::class)->delete($server, $database);
|
app(DeleteDatabase::class)->delete($server, $database);
|
||||||
|
|
||||||
Toast::success('Database deleted successfully.');
|
Toast::success('Database deleted successfully.');
|
||||||
|
@ -16,6 +16,8 @@ class DatabaseUserController extends Controller
|
|||||||
{
|
{
|
||||||
public function store(Server $server, Request $request): HtmxResponse
|
public function store(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$database = app(CreateDatabaseUser::class)->create($server, $request->input());
|
$database = app(CreateDatabaseUser::class)->create($server, $request->input());
|
||||||
|
|
||||||
if ($request->input('user')) {
|
if ($request->input('user')) {
|
||||||
@ -29,6 +31,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function destroy(Server $server, DatabaseUser $databaseUser): RedirectResponse
|
public function destroy(Server $server, DatabaseUser $databaseUser): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeleteDatabaseUser::class)->delete($server, $databaseUser);
|
app(DeleteDatabaseUser::class)->delete($server, $databaseUser);
|
||||||
|
|
||||||
Toast::success('User deleted successfully.');
|
Toast::success('User deleted successfully.');
|
||||||
@ -38,6 +42,8 @@ public function destroy(Server $server, DatabaseUser $databaseUser): RedirectRes
|
|||||||
|
|
||||||
public function password(Server $server, DatabaseUser $databaseUser): RedirectResponse
|
public function password(Server $server, DatabaseUser $databaseUser): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return back()->with([
|
return back()->with([
|
||||||
'password' => $databaseUser->password,
|
'password' => $databaseUser->password,
|
||||||
]);
|
]);
|
||||||
@ -45,6 +51,8 @@ public function password(Server $server, DatabaseUser $databaseUser): RedirectRe
|
|||||||
|
|
||||||
public function link(Server $server, DatabaseUser $databaseUser, Request $request): HtmxResponse
|
public function link(Server $server, DatabaseUser $databaseUser, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(LinkUser::class)->link($databaseUser, $request->input());
|
app(LinkUser::class)->link($databaseUser, $request->input());
|
||||||
|
|
||||||
Toast::success('Database linked successfully.');
|
Toast::success('Database linked successfully.');
|
||||||
|
@ -16,6 +16,8 @@ class FirewallController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('firewall.index', [
|
return view('firewall.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'rules' => $server->firewallRules,
|
'rules' => $server->firewallRules,
|
||||||
@ -24,6 +26,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function store(Server $server, Request $request): HtmxResponse
|
public function store(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(CreateRule::class)->create($server, $request->input());
|
app(CreateRule::class)->create($server, $request->input());
|
||||||
|
|
||||||
Toast::success('Firewall rule created!');
|
Toast::success('Firewall rule created!');
|
||||||
@ -33,6 +37,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function destroy(Server $server, FirewallRule $firewallRule): RedirectResponse
|
public function destroy(Server $server, FirewallRule $firewallRule): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeleteRule::class)->delete($server, $firewallRule);
|
app(DeleteRule::class)->delete($server, $firewallRule);
|
||||||
|
|
||||||
Toast::success('Firewall rule deleted!');
|
Toast::success('Firewall rule deleted!');
|
||||||
|
@ -15,6 +15,8 @@ class MetricController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server, Request $request): View|RedirectResponse
|
public function index(Server $server, Request $request): View|RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$this->checkIfMonitoringServiceInstalled($server);
|
$this->checkIfMonitoringServiceInstalled($server);
|
||||||
|
|
||||||
return view('metrics.index', [
|
return view('metrics.index', [
|
||||||
@ -26,6 +28,8 @@ public function index(Server $server, Request $request): View|RedirectResponse
|
|||||||
|
|
||||||
public function settings(Server $server, Request $request): HtmxResponse
|
public function settings(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$this->checkIfMonitoringServiceInstalled($server);
|
$this->checkIfMonitoringServiceInstalled($server);
|
||||||
|
|
||||||
app(UpdateMetricSettings::class)->update($server, $request->input());
|
app(UpdateMetricSettings::class)->update($server, $request->input());
|
||||||
@ -37,6 +41,8 @@ public function settings(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
private function checkIfMonitoringServiceInstalled(Server $server): void
|
private function checkIfMonitoringServiceInstalled(Server $server): void
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
if (! $server->monitoring()) {
|
if (! $server->monitoring()) {
|
||||||
abort(404, 'Monitoring service is not installed on this server');
|
abort(404, 'Monitoring service is not installed on this server');
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ class PHPController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('php.index', [
|
return view('php.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'phps' => $server->services()->where('type', 'php')->get(),
|
'phps' => $server->services()->where('type', 'php')->get(),
|
||||||
@ -29,6 +31,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function install(Server $server, Request $request): HtmxResponse
|
public function install(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
app(InstallNewPHP::class)->install($server, $request->input());
|
app(InstallNewPHP::class)->install($server, $request->input());
|
||||||
|
|
||||||
@ -42,6 +46,8 @@ public function install(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function installExtension(Server $server, Request $request): HtmxResponse
|
public function installExtension(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(InstallPHPExtension::class)->install($server, $request->input());
|
app(InstallPHPExtension::class)->install($server, $request->input());
|
||||||
|
|
||||||
Toast::success('PHP extension is being installed! Check the logs');
|
Toast::success('PHP extension is being installed! Check the logs');
|
||||||
@ -51,6 +57,8 @@ public function installExtension(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function defaultCli(Server $server, Request $request): HtmxResponse
|
public function defaultCli(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(ChangeDefaultCli::class)->change($server, $request->input());
|
app(ChangeDefaultCli::class)->change($server, $request->input());
|
||||||
|
|
||||||
Toast::success('Default PHP CLI is being changed!');
|
Toast::success('Default PHP CLI is being changed!');
|
||||||
@ -60,6 +68,8 @@ public function defaultCli(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function getIni(Server $server, Request $request): RedirectResponse
|
public function getIni(Server $server, Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$ini = app(GetPHPIni::class)->getIni($server, $request->input());
|
$ini = app(GetPHPIni::class)->getIni($server, $request->input());
|
||||||
|
|
||||||
return back()->with('ini', $ini);
|
return back()->with('ini', $ini);
|
||||||
@ -67,6 +77,8 @@ public function getIni(Server $server, Request $request): RedirectResponse
|
|||||||
|
|
||||||
public function updateIni(Server $server, Request $request): RedirectResponse
|
public function updateIni(Server $server, Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(UpdatePHPIni::class)->update($server, $request->input());
|
app(UpdatePHPIni::class)->update($server, $request->input());
|
||||||
|
|
||||||
Toast::success('PHP ini updated!');
|
Toast::success('PHP ini updated!');
|
||||||
@ -78,6 +90,8 @@ public function updateIni(Server $server, Request $request): RedirectResponse
|
|||||||
|
|
||||||
public function uninstall(Server $server, Request $request): RedirectResponse
|
public function uninstall(Server $server, Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(UninstallPHP::class)->uninstall($server, $request->input());
|
app(UninstallPHP::class)->uninstall($server, $request->input());
|
||||||
|
|
||||||
Toast::success('PHP is being uninstalled!');
|
Toast::success('PHP is being uninstalled!');
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\Settings;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Actions\User\UpdateUserPassword;
|
use App\Actions\User\UpdateUserPassword;
|
||||||
use App\Actions\User\UpdateUserProfileInformation;
|
use App\Actions\User\UpdateUserProfileInformation;
|
||||||
use App\Facades\Toast;
|
use App\Facades\Toast;
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -14,7 +13,7 @@ class ProfileController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(): View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return view('settings.profile.index');
|
return view('profile.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function info(Request $request): RedirectResponse
|
public function info(Request $request): RedirectResponse
|
@ -19,6 +19,8 @@ class QueueController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server, Site $site): View
|
public function index(Server $server, Site $site): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('queues.index', [
|
return view('queues.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
@ -28,6 +30,8 @@ public function index(Server $server, Site $site): View
|
|||||||
|
|
||||||
public function store(Server $server, Site $site, Request $request): HtmxResponse
|
public function store(Server $server, Site $site, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(CreateQueue::class)->create($site, $request->input());
|
app(CreateQueue::class)->create($site, $request->input());
|
||||||
|
|
||||||
Toast::success('Queue is being created.');
|
Toast::success('Queue is being created.');
|
||||||
@ -37,6 +41,8 @@ public function store(Server $server, Site $site, Request $request): HtmxRespons
|
|||||||
|
|
||||||
public function action(Server $server, Site $site, Queue $queue, string $action): HtmxResponse
|
public function action(Server $server, Site $site, Queue $queue, string $action): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(ManageQueue::class)->{$action}($queue);
|
app(ManageQueue::class)->{$action}($queue);
|
||||||
|
|
||||||
Toast::success('Queue is about to '.$action);
|
Toast::success('Queue is about to '.$action);
|
||||||
@ -46,6 +52,8 @@ public function action(Server $server, Site $site, Queue $queue, string $action)
|
|||||||
|
|
||||||
public function destroy(Server $server, Site $site, Queue $queue): RedirectResponse
|
public function destroy(Server $server, Site $site, Queue $queue): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeleteQueue::class)->delete($queue);
|
app(DeleteQueue::class)->delete($queue);
|
||||||
|
|
||||||
Toast::success('Queue is being deleted.');
|
Toast::success('Queue is being deleted.');
|
||||||
@ -55,6 +63,8 @@ public function destroy(Server $server, Site $site, Queue $queue): RedirectRespo
|
|||||||
|
|
||||||
public function logs(Server $server, Site $site, Queue $queue): RedirectResponse
|
public function logs(Server $server, Site $site, Queue $queue): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return back()->with('content', app(GetQueueLogs::class)->getLogs($queue));
|
return back()->with('content', app(GetQueueLogs::class)->getLogs($queue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ class SSHKeyController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('server-ssh-keys.index', [
|
return view('server-ssh-keys.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'keys' => $server->sshKeys,
|
'keys' => $server->sshKeys,
|
||||||
@ -25,6 +27,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function store(Server $server, Request $request): HtmxResponse
|
public function store(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
/** @var \App\Models\SshKey $key */
|
/** @var \App\Models\SshKey $key */
|
||||||
$key = app(CreateSshKey::class)->create(
|
$key = app(CreateSshKey::class)->create(
|
||||||
$request->user(),
|
$request->user(),
|
||||||
@ -38,6 +42,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function destroy(Server $server, SshKey $sshKey): RedirectResponse
|
public function destroy(Server $server, SshKey $sshKey): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeleteKeyFromServer::class)->delete($server, $sshKey);
|
app(DeleteKeyFromServer::class)->delete($server, $sshKey);
|
||||||
|
|
||||||
Toast::success('SSH Key has been deleted.');
|
Toast::success('SSH Key has been deleted.');
|
||||||
@ -47,6 +53,8 @@ public function destroy(Server $server, SshKey $sshKey): RedirectResponse
|
|||||||
|
|
||||||
public function deploy(Server $server, Request $request): HtmxResponse
|
public function deploy(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeployKeyToServer::class)->deploy(
|
app(DeployKeyToServer::class)->deploy(
|
||||||
$request->user(),
|
$request->user(),
|
||||||
$server,
|
$server,
|
||||||
|
@ -17,6 +17,8 @@ class SSLController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server, Site $site): View
|
public function index(Server $server, Site $site): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('ssls.index', [
|
return view('ssls.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
@ -26,6 +28,8 @@ public function index(Server $server, Site $site): View
|
|||||||
|
|
||||||
public function store(Server $server, Site $site, Request $request): HtmxResponse
|
public function store(Server $server, Site $site, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(CreateSSL::class)->create($site, $request->input());
|
app(CreateSSL::class)->create($site, $request->input());
|
||||||
|
|
||||||
Toast::success('SSL certificate is being created.');
|
Toast::success('SSL certificate is being created.');
|
||||||
@ -35,6 +39,8 @@ public function store(Server $server, Site $site, Request $request): HtmxRespons
|
|||||||
|
|
||||||
public function destroy(Server $server, Site $site, Ssl $ssl): RedirectResponse
|
public function destroy(Server $server, Site $site, Ssl $ssl): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeleteSSL::class)->delete($ssl);
|
app(DeleteSSL::class)->delete($ssl);
|
||||||
|
|
||||||
Toast::success('SSL certificate has been deleted.');
|
Toast::success('SSL certificate has been deleted.');
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@ -23,10 +24,22 @@ public function search(Request $request): JsonResponse
|
|||||||
$query->where('name', 'like', '%'.$request->input('q').'%')
|
$query->where('name', 'like', '%'.$request->input('q').'%')
|
||||||
->orWhere('ip', 'like', '%'.$request->input('q').'%');
|
->orWhere('ip', 'like', '%'.$request->input('q').'%');
|
||||||
})
|
})
|
||||||
|
->whereHas('project', function (Builder $projectQuery) {
|
||||||
|
$projectQuery->whereHas('users', function (Builder $userQuery) {
|
||||||
|
$userQuery->where('user_id', auth()->user()->id);
|
||||||
|
});
|
||||||
|
})
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$sites = Site::query()
|
$sites = Site::query()
|
||||||
->where('domain', 'like', '%'.$request->input('q').'%')
|
->where('domain', 'like', '%'.$request->input('q').'%')
|
||||||
|
->whereHas('server', function (Builder $serverQuery) {
|
||||||
|
$serverQuery->whereHas('project', function (Builder $projectQuery) {
|
||||||
|
$projectQuery->whereHas('users', function (Builder $userQuery) {
|
||||||
|
$userQuery->where('user_id', auth()->user()->id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
|
@ -19,6 +19,9 @@ public function index(): View
|
|||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$this->authorize('viewAny', [Server::class, $user->currentProject]);
|
||||||
|
|
||||||
$servers = $user->currentProject->servers()->orderByDesc('created_at')->get();
|
$servers = $user->currentProject->servers()->orderByDesc('created_at')->get();
|
||||||
|
|
||||||
return view('servers.index', compact('servers'));
|
return view('servers.index', compact('servers'));
|
||||||
@ -26,6 +29,11 @@ public function index(): View
|
|||||||
|
|
||||||
public function create(Request $request): View
|
public function create(Request $request): View
|
||||||
{
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$this->authorize('create', [Server::class, $user->currentProject]);
|
||||||
|
|
||||||
$provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM));
|
$provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM));
|
||||||
$serverProviders = ServerProvider::query()->where('provider', $provider)->get();
|
$serverProviders = ServerProvider::query()->where('provider', $provider)->get();
|
||||||
|
|
||||||
@ -40,8 +48,13 @@ public function create(Request $request): View
|
|||||||
*/
|
*/
|
||||||
public function store(Request $request): HtmxResponse
|
public function store(Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$this->authorize('create', [Server::class, $user->currentProject]);
|
||||||
|
|
||||||
$server = app(CreateServer::class)->create(
|
$server = app(CreateServer::class)->create(
|
||||||
$request->user(),
|
$user,
|
||||||
$request->input()
|
$request->input()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -52,6 +65,8 @@ public function store(Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function show(Server $server): View
|
public function show(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('view', $server);
|
||||||
|
|
||||||
return view('servers.show', [
|
return view('servers.show', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
]);
|
]);
|
||||||
@ -59,6 +74,8 @@ public function show(Server $server): View
|
|||||||
|
|
||||||
public function delete(Server $server): RedirectResponse
|
public function delete(Server $server): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('delete', $server);
|
||||||
|
|
||||||
$server->delete();
|
$server->delete();
|
||||||
|
|
||||||
Toast::success('Server deleted successfully.');
|
Toast::success('Server deleted successfully.');
|
||||||
|
@ -14,6 +14,8 @@ class ServerLogController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('server-logs.index', [
|
return view('server-logs.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'pageTitle' => __('Vito Logs'),
|
'pageTitle' => __('Vito Logs'),
|
||||||
@ -22,6 +24,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function show(Server $server, ServerLog $serverLog): RedirectResponse
|
public function show(Server $server, ServerLog $serverLog): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
if ($server->id != $serverLog->server_id) {
|
if ($server->id != $serverLog->server_id) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
@ -33,6 +37,8 @@ public function show(Server $server, ServerLog $serverLog): RedirectResponse
|
|||||||
|
|
||||||
public function remote(Server $server): View
|
public function remote(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('server-logs.remote-logs', [
|
return view('server-logs.remote-logs', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'remote' => true,
|
'remote' => true,
|
||||||
@ -42,6 +48,8 @@ public function remote(Server $server): View
|
|||||||
|
|
||||||
public function store(Server $server, Request $request): \App\Helpers\HtmxResponse
|
public function store(Server $server, Request $request): \App\Helpers\HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(CreateServerLog::class)->create($server, $request->input());
|
app(CreateServerLog::class)->create($server, $request->input());
|
||||||
|
|
||||||
Toast::success('Log added successfully.');
|
Toast::success('Log added successfully.');
|
||||||
@ -51,6 +59,8 @@ public function store(Server $server, Request $request): \App\Helpers\HtmxRespon
|
|||||||
|
|
||||||
public function destroy(Server $server, ServerLog $serverLog): RedirectResponse
|
public function destroy(Server $server, ServerLog $serverLog): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$serverLog->delete();
|
$serverLog->delete();
|
||||||
|
|
||||||
Toast::success('Remote log deleted successfully.');
|
Toast::success('Remote log deleted successfully.');
|
||||||
|
@ -15,11 +15,15 @@ class ServerSettingController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('server-settings.index', compact('server'));
|
return view('server-settings.index', compact('server'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkConnection(Server $server): RedirectResponse|HtmxResponse
|
public function checkConnection(Server $server): RedirectResponse|HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$oldStatus = $server->status;
|
$oldStatus = $server->status;
|
||||||
|
|
||||||
$server = $server->checkConnection();
|
$server = $server->checkConnection();
|
||||||
@ -41,6 +45,8 @@ public function checkConnection(Server $server): RedirectResponse|HtmxResponse
|
|||||||
|
|
||||||
public function reboot(Server $server): HtmxResponse
|
public function reboot(Server $server): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(RebootServer::class)->reboot($server);
|
app(RebootServer::class)->reboot($server);
|
||||||
|
|
||||||
Toast::info('Server is rebooting.');
|
Toast::info('Server is rebooting.');
|
||||||
@ -50,6 +56,8 @@ public function reboot(Server $server): HtmxResponse
|
|||||||
|
|
||||||
public function edit(Request $request, Server $server): RedirectResponse
|
public function edit(Request $request, Server $server): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(EditServer::class)->edit($server, $request->input());
|
app(EditServer::class)->edit($server, $request->input());
|
||||||
|
|
||||||
Toast::success('Server updated.');
|
Toast::success('Server updated.');
|
||||||
|
@ -16,6 +16,8 @@ class ServiceController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('services.index', [
|
return view('services.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'services' => $server->services,
|
'services' => $server->services,
|
||||||
@ -24,6 +26,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function start(Server $server, Service $service): RedirectResponse
|
public function start(Server $server, Service $service): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$service->start();
|
$service->start();
|
||||||
|
|
||||||
Toast::success('Service is being started!');
|
Toast::success('Service is being started!');
|
||||||
@ -33,6 +37,8 @@ public function start(Server $server, Service $service): RedirectResponse
|
|||||||
|
|
||||||
public function stop(Server $server, Service $service): RedirectResponse
|
public function stop(Server $server, Service $service): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$service->stop();
|
$service->stop();
|
||||||
|
|
||||||
Toast::success('Service is being stopped!');
|
Toast::success('Service is being stopped!');
|
||||||
@ -42,6 +48,8 @@ public function stop(Server $server, Service $service): RedirectResponse
|
|||||||
|
|
||||||
public function restart(Server $server, Service $service): RedirectResponse
|
public function restart(Server $server, Service $service): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$service->restart();
|
$service->restart();
|
||||||
|
|
||||||
Toast::success('Service is being restarted!');
|
Toast::success('Service is being restarted!');
|
||||||
@ -51,6 +59,8 @@ public function restart(Server $server, Service $service): RedirectResponse
|
|||||||
|
|
||||||
public function enable(Server $server, Service $service): RedirectResponse
|
public function enable(Server $server, Service $service): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$service->enable();
|
$service->enable();
|
||||||
|
|
||||||
Toast::success('Service is being enabled!');
|
Toast::success('Service is being enabled!');
|
||||||
@ -60,6 +70,8 @@ public function enable(Server $server, Service $service): RedirectResponse
|
|||||||
|
|
||||||
public function disable(Server $server, Service $service): RedirectResponse
|
public function disable(Server $server, Service $service): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$service->disable();
|
$service->disable();
|
||||||
|
|
||||||
Toast::success('Service is being disabled!');
|
Toast::success('Service is being disabled!');
|
||||||
@ -69,6 +81,8 @@ public function disable(Server $server, Service $service): RedirectResponse
|
|||||||
|
|
||||||
public function install(Server $server, Request $request): HtmxResponse
|
public function install(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(Install::class)->install($server, $request->input());
|
app(Install::class)->install($server, $request->input());
|
||||||
|
|
||||||
Toast::success('Service is being installed!');
|
Toast::success('Service is being installed!');
|
||||||
@ -78,6 +92,8 @@ public function install(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function uninstall(Server $server, Service $service): HtmxResponse
|
public function uninstall(Server $server, Service $service): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(Uninstall::class)->uninstall($service);
|
app(Uninstall::class)->uninstall($service);
|
||||||
|
|
||||||
Toast::success('Service is being uninstalled!');
|
Toast::success('Service is being uninstalled!');
|
||||||
|
@ -29,7 +29,7 @@ public function add(Request $request): HtmxResponse
|
|||||||
|
|
||||||
Toast::success('Channel added successfully');
|
Toast::success('Channel added successfully');
|
||||||
|
|
||||||
return htmx()->redirect(route('notification-channels'));
|
return htmx()->redirect(route('settings.notification-channels'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(int $id): RedirectResponse
|
public function delete(int $id): RedirectResponse
|
||||||
@ -40,6 +40,6 @@ public function delete(int $id): RedirectResponse
|
|||||||
|
|
||||||
Toast::success('Channel deleted successfully');
|
Toast::success('Channel deleted successfully');
|
||||||
|
|
||||||
return redirect()->route('notification-channels');
|
return redirect()->route('settings.notification-channels');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class ProjectController extends Controller
|
|||||||
public function index(): View
|
public function index(): View
|
||||||
{
|
{
|
||||||
return view('settings.projects.index', [
|
return view('settings.projects.index', [
|
||||||
'projects' => auth()->user()->projects,
|
'projects' => Project::all(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ public function create(Request $request): HtmxResponse
|
|||||||
|
|
||||||
Toast::success('Project created.');
|
Toast::success('Project created.');
|
||||||
|
|
||||||
return htmx()->redirect(route('projects'));
|
return htmx()->redirect(route('settings.projects'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, Project $project): HtmxResponse
|
public function update(Request $request, Project $project): HtmxResponse
|
||||||
@ -42,7 +42,7 @@ public function update(Request $request, Project $project): HtmxResponse
|
|||||||
|
|
||||||
Toast::success('Project updated.');
|
Toast::success('Project updated.');
|
||||||
|
|
||||||
return htmx()->redirect(route('projects'));
|
return htmx()->redirect(route('settings.projects'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Project $project): RedirectResponse
|
public function delete(Project $project): RedirectResponse
|
||||||
@ -74,6 +74,8 @@ public function switch($projectId): RedirectResponse
|
|||||||
/** @var Project $project */
|
/** @var Project $project */
|
||||||
$project = $user->projects()->findOrFail($projectId);
|
$project = $user->projects()->findOrFail($projectId);
|
||||||
|
|
||||||
|
$this->authorize('view', $project);
|
||||||
|
|
||||||
$user->current_project_id = $project->id;
|
$user->current_project_id = $project->id;
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ public function add(Request $request): HtmxResponse
|
|||||||
|
|
||||||
Toast::success('SSH Key added');
|
Toast::success('SSH Key added');
|
||||||
|
|
||||||
return htmx()->redirect(route('ssh-keys'));
|
return htmx()->redirect(route('settings.ssh-keys'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(int $id): RedirectResponse
|
public function delete(int $id): RedirectResponse
|
||||||
@ -40,6 +40,6 @@ public function delete(int $id): RedirectResponse
|
|||||||
|
|
||||||
Toast::success('SSH Key deleted');
|
Toast::success('SSH Key deleted');
|
||||||
|
|
||||||
return redirect()->route('ssh-keys');
|
return redirect()->route('settings.ssh-keys');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ public function connect(Request $request): HtmxResponse
|
|||||||
|
|
||||||
Toast::success('Server provider connected.');
|
Toast::success('Server provider connected.');
|
||||||
|
|
||||||
return htmx()->redirect(route('server-providers'));
|
return htmx()->redirect(route('settings.server-providers'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(ServerProvider $serverProvider): RedirectResponse
|
public function delete(ServerProvider $serverProvider): RedirectResponse
|
||||||
|
@ -29,7 +29,7 @@ public function connect(Request $request): HtmxResponse
|
|||||||
|
|
||||||
Toast::success('Source control connected.');
|
Toast::success('Source control connected.');
|
||||||
|
|
||||||
return htmx()->redirect(route('source-controls'));
|
return htmx()->redirect(route('settings.source-controls'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(SourceControl $sourceControl): RedirectResponse
|
public function delete(SourceControl $sourceControl): RedirectResponse
|
||||||
@ -44,6 +44,6 @@ public function delete(SourceControl $sourceControl): RedirectResponse
|
|||||||
|
|
||||||
Toast::success('Source control deleted.');
|
Toast::success('Source control deleted.');
|
||||||
|
|
||||||
return redirect()->route('source-controls');
|
return redirect()->route('settings.source-controls');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ public function connect(Request $request): HtmxResponse
|
|||||||
|
|
||||||
Toast::success('Storage provider connected.');
|
Toast::success('Storage provider connected.');
|
||||||
|
|
||||||
return htmx()->redirect(route('storage-providers'));
|
return htmx()->redirect(route('settings.storage-providers'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(StorageProvider $storageProvider): RedirectResponse
|
public function delete(StorageProvider $storageProvider): RedirectResponse
|
||||||
|
78
app/Http/Controllers/Settings/UserController.php
Normal file
78
app/Http/Controllers/Settings/UserController.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
|
use App\Actions\User\CreateUser;
|
||||||
|
use App\Actions\User\UpdateUser;
|
||||||
|
use App\Facades\Toast;
|
||||||
|
use App\Helpers\HtmxResponse;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UserController extends Controller
|
||||||
|
{
|
||||||
|
public function index(): View
|
||||||
|
{
|
||||||
|
$users = User::query()->paginate(20);
|
||||||
|
|
||||||
|
return view('settings.users.index', compact('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
$user = app(CreateUser::class)->create($request->input());
|
||||||
|
|
||||||
|
return htmx()->redirect(route('settings.users.show', $user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(User $user): View
|
||||||
|
{
|
||||||
|
return view('settings.users.show', [
|
||||||
|
'user' => $user,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
app(UpdateUser::class)->update($user, $request->input());
|
||||||
|
|
||||||
|
Toast::success('User updated successfully');
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateProjects(User $user, Request $request): HtmxResponse
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'projects.*' => [
|
||||||
|
'required',
|
||||||
|
Rule::exists('projects', 'id'),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->projects()->sync($request->projects);
|
||||||
|
|
||||||
|
Toast::success('Projects updated successfully');
|
||||||
|
|
||||||
|
return htmx()->redirect(route('settings.users.show', $user));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(User $user): RedirectResponse
|
||||||
|
{
|
||||||
|
if ($user->is(request()->user())) {
|
||||||
|
Toast::error('You cannot delete your own account');
|
||||||
|
|
||||||
|
return back();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->delete();
|
||||||
|
|
||||||
|
Toast::success('User deleted successfully');
|
||||||
|
|
||||||
|
return redirect()->route('settings.users.index');
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,8 @@ class SiteController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server): View
|
public function index(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('sites.index', [
|
return view('sites.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'sites' => $server->sites()->orderByDesc('id')->get(),
|
'sites' => $server->sites()->orderByDesc('id')->get(),
|
||||||
@ -27,6 +29,8 @@ public function index(Server $server): View
|
|||||||
|
|
||||||
public function store(Server $server, Request $request): HtmxResponse
|
public function store(Server $server, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$site = app(CreateSite::class)->create($server, $request->input());
|
$site = app(CreateSite::class)->create($server, $request->input());
|
||||||
|
|
||||||
Toast::success('Site created');
|
Toast::success('Site created');
|
||||||
@ -36,6 +40,8 @@ public function store(Server $server, Request $request): HtmxResponse
|
|||||||
|
|
||||||
public function create(Server $server): View
|
public function create(Server $server): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('sites.create', [
|
return view('sites.create', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'type' => old('type', request()->query('type', SiteType::LARAVEL)),
|
'type' => old('type', request()->query('type', SiteType::LARAVEL)),
|
||||||
@ -45,6 +51,8 @@ public function create(Server $server): View
|
|||||||
|
|
||||||
public function show(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
public function show(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
if (in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
if (in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||||
if ($request->hasHeader('HX-Request')) {
|
if ($request->hasHeader('HX-Request')) {
|
||||||
return htmx()->redirect(route('servers.sites.installing', [$server, $site]));
|
return htmx()->redirect(route('servers.sites.installing', [$server, $site]));
|
||||||
@ -61,6 +69,8 @@ public function show(Server $server, Site $site, Request $request): View|Redirec
|
|||||||
|
|
||||||
public function installing(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
public function installing(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
if (! in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
if (! in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||||
if ($request->hasHeader('HX-Request')) {
|
if ($request->hasHeader('HX-Request')) {
|
||||||
return htmx()->redirect(route('servers.sites.show', [$server, $site]));
|
return htmx()->redirect(route('servers.sites.show', [$server, $site]));
|
||||||
@ -77,6 +87,8 @@ public function installing(Server $server, Site $site, Request $request): View|R
|
|||||||
|
|
||||||
public function destroy(Server $server, Site $site): RedirectResponse
|
public function destroy(Server $server, Site $site): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
app(DeleteSite::class)->delete($site);
|
app(DeleteSite::class)->delete($site);
|
||||||
|
|
||||||
Toast::success('Site is being deleted');
|
Toast::success('Site is being deleted');
|
||||||
|
@ -10,6 +10,8 @@ class SiteLogController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server, Site $site): View
|
public function index(Server $server, Site $site): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('site-logs.index', [
|
return view('site-logs.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
|
@ -18,6 +18,8 @@ class SiteSettingController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Server $server, Site $site): View
|
public function index(Server $server, Site $site): View
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
return view('site-settings.index', [
|
return view('site-settings.index', [
|
||||||
'server' => $server,
|
'server' => $server,
|
||||||
'site' => $site,
|
'site' => $site,
|
||||||
@ -26,6 +28,8 @@ public function index(Server $server, Site $site): View
|
|||||||
|
|
||||||
public function getVhost(Server $server, Site $site): RedirectResponse
|
public function getVhost(Server $server, Site $site): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
/** @var Webserver $handler */
|
/** @var Webserver $handler */
|
||||||
$handler = $server->webserver()->handler();
|
$handler = $server->webserver()->handler();
|
||||||
|
|
||||||
@ -34,6 +38,8 @@ public function getVhost(Server $server, Site $site): RedirectResponse
|
|||||||
|
|
||||||
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
|
public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'vhost' => 'required|string',
|
'vhost' => 'required|string',
|
||||||
]);
|
]);
|
||||||
@ -53,6 +59,8 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
|
|||||||
|
|
||||||
public function updatePHPVersion(Server $server, Site $site, Request $request): HtmxResponse
|
public function updatePHPVersion(Server $server, Site $site, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'version' => [
|
'version' => [
|
||||||
'required',
|
'required',
|
||||||
@ -73,6 +81,8 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
|
|||||||
|
|
||||||
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
|
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
|
||||||
{
|
{
|
||||||
|
$this->authorize('manage', $server);
|
||||||
|
|
||||||
$site = app(UpdateSourceControl::class)->update($site, $request->input());
|
$site = app(UpdateSourceControl::class)->update($site, $request->input());
|
||||||
|
|
||||||
Toast::success('Source control updated successfully!');
|
Toast::success('Source control updated successfully!');
|
||||||
|
@ -68,5 +68,6 @@ class Kernel extends HttpKernel
|
|||||||
'server-is-ready' => ServerIsReadyMiddleware::class,
|
'server-is-ready' => ServerIsReadyMiddleware::class,
|
||||||
'handle-ssh-errors' => HandleSSHErrors::class,
|
'handle-ssh-errors' => HandleSSHErrors::class,
|
||||||
'select-current-project' => SelectCurrentProject::class,
|
'select-current-project' => SelectCurrentProject::class,
|
||||||
|
'is-admin' => \App\Http\Middleware\IsAdmin::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
25
app/Http/Middleware/IsAdmin.php
Normal file
25
app/Http/Middleware/IsAdmin.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class IsAdmin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (auth()->user()->role !== UserRole::ADMIN) {
|
||||||
|
abort(403, 'You are not authorized to access this page.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,4 +54,9 @@ public function notificationChannels(): HasMany
|
|||||||
{
|
{
|
||||||
return $this->hasMany(NotificationChannel::class);
|
return $this->hasMany(NotificationChannel::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function users(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(User::class, 'user_project')->withTimestamps();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
@ -30,6 +32,7 @@
|
|||||||
* @property int $current_project_id
|
* @property int $current_project_id
|
||||||
* @property Project $currentProject
|
* @property Project $currentProject
|
||||||
* @property Collection<Project> $projects
|
* @property Collection<Project> $projects
|
||||||
|
* @property string $role
|
||||||
*/
|
*/
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
@ -43,6 +46,7 @@ class User extends Authenticatable
|
|||||||
'password',
|
'password',
|
||||||
'timezone',
|
'timezone',
|
||||||
'current_project_id',
|
'current_project_id',
|
||||||
|
'role',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
@ -60,7 +64,9 @@ public static function boot(): void
|
|||||||
parent::boot();
|
parent::boot();
|
||||||
|
|
||||||
static::created(function (User $user) {
|
static::created(function (User $user) {
|
||||||
$user->createDefaultProject();
|
if (Project::count() === 0) {
|
||||||
|
$user->createDefaultProject();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,9 +123,9 @@ public function connectedSourceControls(): array
|
|||||||
return $connectedSourceControls;
|
return $connectedSourceControls;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function projects(): HasMany
|
public function projects(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Project::class);
|
return $this->belongsToMany(Project::class, 'user_project')->withTimestamps();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function currentProject(): HasOne
|
public function currentProject(): HasOne
|
||||||
@ -138,9 +144,10 @@ public function createDefaultProject(): Project
|
|||||||
|
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
$project = new Project();
|
$project = new Project();
|
||||||
$project->user_id = $this->id;
|
$project->name = 'default';
|
||||||
$project->name = 'Default';
|
|
||||||
$project->save();
|
$project->save();
|
||||||
|
|
||||||
|
$project->users()->attach($this->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->current_project_id = $project->id;
|
$this->current_project_id = $project->id;
|
||||||
@ -148,4 +155,9 @@ public function createDefaultProject(): Project
|
|||||||
|
|
||||||
return $project;
|
return $project;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isAdmin(): bool
|
||||||
|
{
|
||||||
|
return $this->role === UserRole::ADMIN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ public function connect(): bool
|
|||||||
__('Congratulations! 🎉'),
|
__('Congratulations! 🎉'),
|
||||||
__("You've connected your Discord to :app", ['app' => config('app.name')])."\n".
|
__("You've connected your Discord to :app", ['app' => config('app.name')])."\n".
|
||||||
__('Manage your notification channels')."\n".
|
__('Manage your notification channels')."\n".
|
||||||
route('notification-channels')
|
route('settings.notification-channels')
|
||||||
);
|
);
|
||||||
|
|
||||||
if (! $connect) {
|
if (! $connect) {
|
||||||
|
@ -35,7 +35,7 @@ public function connect(): bool
|
|||||||
__('Congratulations! 🎉'),
|
__('Congratulations! 🎉'),
|
||||||
__("You've connected your Slack to :app", ['app' => config('app.name')])."\n".
|
__("You've connected your Slack to :app", ['app' => config('app.name')])."\n".
|
||||||
__('Manage your notification channels')."\n".
|
__('Manage your notification channels')."\n".
|
||||||
route('notification-channels')
|
route('settings.notification-channels')
|
||||||
);
|
);
|
||||||
|
|
||||||
if (! $connect) {
|
if (! $connect) {
|
||||||
|
35
app/Policies/ProjectPolicy.php
Normal file
35
app/Policies/ProjectPolicy.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class ProjectPolicy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Project $project): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN || $project->users->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Project $project): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Project $project): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN;
|
||||||
|
}
|
||||||
|
}
|
41
app/Policies/ServerPolicy.php
Normal file
41
app/Policies/ServerPolicy.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class ServerPolicy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user, Project $project): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN || $project->users->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN || $server->project->users->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user, Project $project): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN || $project->users->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN || $server->project->users->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN || $server->project->users->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function manage(User $user, Server $server): bool
|
||||||
|
{
|
||||||
|
return $user->role === UserRole::ADMIN || $server->project->users->contains($user);
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,10 @@
|
|||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\View\Component;
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
class ProfileLayout extends Component
|
class SettingsLayout extends Component
|
||||||
{
|
{
|
||||||
public function render(): View
|
public function render(): View
|
||||||
{
|
{
|
||||||
return view('layouts.profile');
|
return view('layouts.settings');
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,7 +16,6 @@ class ProjectFactory extends Factory
|
|||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'user_id' => $this->faker->randomNumber(),
|
|
||||||
'name' => $this->faker->name(),
|
'name' => $this->faker->name(),
|
||||||
'created_at' => Carbon::now(),
|
'created_at' => Carbon::now(),
|
||||||
'updated_at' => Carbon::now(),
|
'updated_at' => Carbon::now(),
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
use App\Enums\ServerStatus;
|
use App\Enums\ServerStatus;
|
||||||
use App\Enums\ServerType;
|
use App\Enums\ServerType;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
class ServerFactory extends Factory
|
class ServerFactory extends Factory
|
||||||
@ -16,11 +15,7 @@ class ServerFactory extends Factory
|
|||||||
|
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
|
||||||
$user = User::factory()->create();
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'user_id' => $user->id,
|
|
||||||
'name' => $this->faker->name(),
|
'name' => $this->faker->name(),
|
||||||
'ssh_user' => 'vito',
|
'ssh_user' => 'vito',
|
||||||
'ip' => $this->faker->ipv4(),
|
'ip' => $this->faker->ipv4(),
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@ -18,6 +19,7 @@ public function definition(): array
|
|||||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
||||||
'remember_token' => Str::random(10),
|
'remember_token' => Str::random(10),
|
||||||
'timezone' => 'UTC',
|
'timezone' => 'UTC',
|
||||||
|
'role' => UserRole::ADMIN,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,15 +9,13 @@
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('storage_providers', function (Blueprint $table) {
|
Schema::table('storage_providers', function (Blueprint $table) {
|
||||||
$table->dropColumn('token');
|
|
||||||
$table->dropColumn('refresh_token');
|
|
||||||
$table->dropColumn('token_expires_at');
|
|
||||||
$table->dropColumn('label');
|
|
||||||
$table->dropColumn('connected');
|
|
||||||
$table->unsignedBigInteger('user_id')->after('id');
|
$table->unsignedBigInteger('user_id')->after('id');
|
||||||
$table->string('profile')->after('user_id');
|
$table->string('profile')->after('user_id');
|
||||||
$table->longText('credentials')->nullable()->after('provider');
|
$table->longText('credentials')->nullable()->after('provider');
|
||||||
});
|
});
|
||||||
|
Schema::table('storage_providers', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['token', 'refresh_token', 'token_expires_at', 'label', 'connected']);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
@ -27,9 +25,9 @@ public function down(): void
|
|||||||
$table->string('refresh_token')->nullable();
|
$table->string('refresh_token')->nullable();
|
||||||
$table->string('token_expires_at')->nullable();
|
$table->string('token_expires_at')->nullable();
|
||||||
$table->string('label')->nullable();
|
$table->string('label')->nullable();
|
||||||
$table->dropColumn('user_id');
|
});
|
||||||
$table->dropColumn('profile');
|
Schema::table('storage_providers', function (Blueprint $table) {
|
||||||
$table->dropColumn('credentials');
|
$table->dropColumn(['user_id', 'profile', 'credentials']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\UserRole;
|
||||||
|
use App\Models\User;
|
||||||
|
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::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('role')->default(UserRole::USER);
|
||||||
|
});
|
||||||
|
User::query()->update(['role' => UserRole::ADMIN]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('role');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\User;
|
||||||
|
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('user_project', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('user_id');
|
||||||
|
$table->unsignedBigInteger('project_id');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
Project::all()->each(function (Project $project) {
|
||||||
|
$project->users()->attach($project->user_id);
|
||||||
|
});
|
||||||
|
User::all()->each(function (User $user) {
|
||||||
|
$user->current_project_id = $user->projects()->first()?->id;
|
||||||
|
$user->save();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('user_project');
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,28 @@
|
|||||||
|
<?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::table('projects', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('user_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('projects', function (Blueprint $table) {
|
||||||
|
$table->bigInteger('user_id')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -1,4 +1,11 @@
|
|||||||
@props(["open" => false, "align" => "right", "width" => "48", "contentClasses" => "list-none divide-y divide-gray-100 rounded-md border border-gray-200 bg-white py-1 text-base dark:divide-gray-600 dark:border-gray-600 dark:bg-gray-700"])
|
@props([
|
||||||
|
"open" => false,
|
||||||
|
"align" => "right",
|
||||||
|
"width" => "48",
|
||||||
|
"contentClasses" => "list-none divide-y divide-gray-100 rounded-md border border-gray-200 bg-white py-1 text-base dark:divide-gray-600 dark:border-gray-600 dark:bg-gray-700",
|
||||||
|
"search" => false,
|
||||||
|
"searchUrl" => "",
|
||||||
|
])
|
||||||
|
|
||||||
@php
|
@php
|
||||||
switch ($align) {
|
switch ($align) {
|
||||||
@ -42,6 +49,28 @@ class="{{ $width }} {{ $alignmentClasses }} absolute z-50 mt-2 rounded-md"
|
|||||||
@click="open = false"
|
@click="open = false"
|
||||||
>
|
>
|
||||||
<div class="{{ $contentClasses }} rounded-md">
|
<div class="{{ $contentClasses }} rounded-md">
|
||||||
|
@if ($search)
|
||||||
|
<div class="p-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-ref="search"
|
||||||
|
x-model="search"
|
||||||
|
x-on:keydown.window.prevent.enter="open = false"
|
||||||
|
x-on:keydown.window.prevent.escape="open = false"
|
||||||
|
x-on:keydown.window.prevent.arrow-up="
|
||||||
|
open = true
|
||||||
|
$refs.search.focus()
|
||||||
|
"
|
||||||
|
x-on:keydown.window.prevent.arrow-down="
|
||||||
|
open = true
|
||||||
|
$refs.search.focus()
|
||||||
|
"
|
||||||
|
class="w-full rounded-md border border-gray-200 p-2"
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
{{ $content }}
|
{{ $content }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
{{ $attributes }}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="m11.25 9-3 3m0 0 3 3m-3-3h7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 323 B |
14
resources/views/components/heroicons/o-user-group.blade.php
Normal file
14
resources/views/components/heroicons/o-user-group.blade.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
{{ $attributes }}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 767 B |
10
resources/views/components/heroicons/o-x-mark.blade.php
Normal file
10
resources/views/components/heroicons/o-x-mark.blade.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
{{ $attributes }}
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 249 B |
0
resources/views/components/select2.blade.php
Normal file
0
resources/views/components/select2.blade.php
Normal file
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
@php
|
@php
|
||||||
$class = [
|
$class = [
|
||||||
"success" => "rounded-full bg-green-50 px-2 py-1 text-xs uppercase text-green-500 dark:bg-green-500 dark:bg-opacity-10",
|
"success" => "rounded-md border border-green-300 bg-green-50 px-2 py-1 text-xs uppercase text-green-500 dark:border-green-600 dark:bg-green-500 dark:bg-opacity-10",
|
||||||
"danger" => "rounded-full bg-red-50 px-2 py-1 text-xs uppercase text-red-500 dark:bg-red-500 dark:bg-opacity-10",
|
"danger" => "rounded-md border border-red-300 bg-red-50 px-2 py-1 text-xs uppercase text-red-500 dark:border-red-600 dark:bg-red-500 dark:bg-opacity-10",
|
||||||
"warning" => "rounded-full bg-yellow-50 px-2 py-1 text-xs uppercase text-yellow-500 dark:bg-yellow-500 dark:bg-opacity-10",
|
"warning" => "rounded-md border border-yellow-300 bg-yellow-50 px-2 py-1 text-xs uppercase text-yellow-500 dark:border-yellow-600 dark:bg-yellow-500 dark:bg-opacity-10",
|
||||||
"disabled" => "rounded-full bg-gray-50 px-2 py-1 text-xs uppercase text-gray-500 dark:bg-gray-500 dark:bg-opacity-30 dark:text-gray-400",
|
"disabled" => "rounded-md border border-gray-300 bg-gray-50 px-2 py-1 text-xs uppercase text-gray-500 dark:border-gray-600 dark:bg-gray-500 dark:bg-opacity-30 dark:text-gray-400",
|
||||||
"info" => "rounded-full bg-primary-50 px-2 py-1 text-xs uppercase text-primary-500 dark:bg-primary-500 dark:bg-opacity-10",
|
"info" => "rounded-md border border-primary-300 bg-primary-50 px-2 py-1 text-xs uppercase text-primary-500 dark:border-primary-600 dark:bg-primary-500 dark:bg-opacity-10",
|
||||||
];
|
];
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
|
@ -9,22 +9,22 @@
|
|||||||
<x-dropdown-link :href="route('profile')">
|
<x-dropdown-link :href="route('profile')">
|
||||||
{{ __("Profile") }}
|
{{ __("Profile") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<x-dropdown-link :href="route('projects')">
|
<x-dropdown-link :href="route('settings.projects')">
|
||||||
{{ __("Projects") }}
|
{{ __("Projects") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<x-dropdown-link :href="route('server-providers')">
|
<x-dropdown-link :href="route('settings.server-providers')">
|
||||||
{{ __("Server Providers") }}
|
{{ __("Server Providers") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<x-dropdown-link :href="route('source-controls')">
|
<x-dropdown-link :href="route('settings.source-controls')">
|
||||||
{{ __("Source Controls") }}
|
{{ __("Source Controls") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<x-dropdown-link :href="route('storage-providers')">
|
<x-dropdown-link :href="route('settings.storage-providers')">
|
||||||
{{ __("Storage Providers") }}
|
{{ __("Storage Providers") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<x-dropdown-link :href="route('notification-channels')">
|
<x-dropdown-link :href="route('settings.notification-channels')">
|
||||||
{{ __("Notification Channels") }}
|
{{ __("Notification Channels") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<x-dropdown-link :href="route('ssh-keys')">
|
<x-dropdown-link :href="route('settings.ssh-keys')">
|
||||||
{{ __("SSH Keys") }}
|
{{ __("SSH Keys") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<!-- Authentication -->
|
<!-- Authentication -->
|
||||||
|
@ -41,7 +41,7 @@ class="p-6"
|
|||||||
</option>
|
</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</x-select-input>
|
</x-select-input>
|
||||||
<x-secondary-button :href="route('storage-providers')" class="ml-2 flex-none">
|
<x-secondary-button :href="route('settings.storage-providers')" class="ml-2 flex-none">
|
||||||
Connect
|
Connect
|
||||||
</x-secondary-button>
|
</x-secondary-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ class="fixed top-0 z-50 flex h-[64px] w-full items-center border-b border-gray-2
|
|||||||
>
|
>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center justify-start">
|
<div class="flex flex-none items-center justify-start">
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-start border-r border-gray-200 px-3 py-3 dark:border-gray-700 md:w-64"
|
class="flex items-center justify-start border-r border-gray-200 px-3 py-3 dark:border-gray-700 md:w-64"
|
||||||
>
|
>
|
||||||
@ -17,7 +17,7 @@ class="inline-flex items-center rounded-md p-2 text-sm text-gray-500 hover:bg-gr
|
|||||||
<span class="sr-only">Open sidebar</span>
|
<span class="sr-only">Open sidebar</span>
|
||||||
<x-heroicon name="o-bars-3-center-left" class="h-6 w-6" />
|
<x-heroicon name="o-bars-3-center-left" class="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
<a href="/" class="ms-2 flex md:me-24">
|
<a href="/" class="ms-2 flex flex-none md:me-24">
|
||||||
<div class="relative flex items-center justify-start text-3xl font-extrabold">
|
<div class="relative flex items-center justify-start text-3xl font-extrabold">
|
||||||
<x-application-logo class="h-9 w-9 rounded-md" />
|
<x-application-logo class="h-9 w-9 rounded-md" />
|
||||||
<span class="ml-1 hidden md:block">Deploy</span>
|
<span class="ml-1 hidden md:block">Deploy</span>
|
||||||
@ -70,9 +70,11 @@ class="flex rounded-full p-1 text-sm focus:ring-2 focus:ring-gray-300 dark:focus
|
|||||||
{{ __("Profile") }}
|
{{ __("Profile") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
|
|
||||||
<x-dropdown-link :href="route('projects')">
|
@if (auth()->user()->isAdmin())
|
||||||
{{ __("Projects") }}
|
<x-dropdown-link :href="route('settings.projects')">
|
||||||
</x-dropdown-link>
|
{{ __("Projects") }}
|
||||||
|
</x-dropdown-link>
|
||||||
|
@endif
|
||||||
|
|
||||||
<!-- Authentication -->
|
<!-- Authentication -->
|
||||||
<form method="POST" action="{{ route("logout") }}">
|
<form method="POST" action="{{ route("logout") }}">
|
||||||
|
@ -5,7 +5,14 @@
|
|||||||
<div
|
<div
|
||||||
class="flex h-10 w-max items-center rounded-md border border-gray-200 bg-gray-100 px-4 py-2 pr-7 text-sm text-gray-900 focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:focus:ring-gray-600"
|
class="flex h-10 w-max items-center rounded-md border border-gray-200 bg-gray-100 px-4 py-2 pr-7 text-sm text-gray-900 focus:ring-4 focus:ring-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:focus:ring-gray-600"
|
||||||
>
|
>
|
||||||
{{ auth()->user()->currentProject?->name ?? __("Select Project") }}
|
<x-heroicon name="o-inbox-stack" class="mr-2 h-4 w-4 lg:hidden" />
|
||||||
|
<span class="hidden lg:block">
|
||||||
|
@if (auth()->user()->currentProject &&auth()->user()->can("view", auth()->user()->currentProject))
|
||||||
|
{{ auth()->user()->currentProject->name }}
|
||||||
|
@else
|
||||||
|
{{ __("Select Project") }}
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
||||||
</button>
|
</button>
|
||||||
@ -14,8 +21,8 @@ class="flex h-10 w-max items-center rounded-md border border-gray-200 bg-gray-10
|
|||||||
</x-slot>
|
</x-slot>
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
@foreach (auth()->user()->projects as $project)
|
@foreach (auth()->user()->projects as $project)
|
||||||
<x-dropdown-link class="relative" :href="route('projects.switch', ['project' => $project])">
|
<x-dropdown-link class="relative" :href="route('settings.projects.switch', ['project' => $project])">
|
||||||
<span class="block truncate">{{ ucfirst($project->name) }}</span>
|
<span class="block truncate">{{ $project->name }}</span>
|
||||||
@if ($project->id == auth()->user()->current_project_id)
|
@if ($project->id == auth()->user()->current_project_id)
|
||||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-primary-600">
|
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-primary-600">
|
||||||
<x-heroicon name="o-check" class="h-5 w-5" />
|
<x-heroicon name="o-check" class="h-5 w-5" />
|
||||||
@ -24,12 +31,14 @@ class="flex h-10 w-max items-center rounded-md border border-gray-200 bg-gray-10
|
|||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
<x-dropdown-link href="{{ route('projects') }}">
|
@if (auth()->user()->isAdmin())
|
||||||
{{ __("Projects List") }}
|
<x-dropdown-link href="{{ route('settings.projects') }}">
|
||||||
</x-dropdown-link>
|
{{ __("Projects List") }}
|
||||||
<x-dropdown-link href="{{ route('projects', ['create' => 'open']) }}">
|
</x-dropdown-link>
|
||||||
{{ __("Create a Project") }}
|
<x-dropdown-link href="{{ route('settings.projects', ['create' => 'open']) }}">
|
||||||
</x-dropdown-link>
|
{{ __("Create a Project") }}
|
||||||
|
</x-dropdown-link>
|
||||||
|
@endif
|
||||||
</x-slot>
|
</x-slot>
|
||||||
</x-dropdown>
|
</x-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,35 +1,37 @@
|
|||||||
<div data-tooltip="Servers" class="cursor-pointer">
|
@if (auth()->user()->currentProject &&auth()->user()->can("view", auth()->user()->currentProject))
|
||||||
<x-dropdown width="full">
|
<div data-tooltip="Servers" class="cursor-pointer">
|
||||||
<x-slot:trigger>
|
<x-dropdown width="full">
|
||||||
<div>
|
<x-slot:trigger>
|
||||||
<div
|
<div>
|
||||||
class="block w-full rounded-md border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500"
|
<div
|
||||||
>
|
class="block w-full rounded-md border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500"
|
||||||
{{ isset($server) ? $server->name : "Select Server" }}
|
>
|
||||||
|
{{ isset($server) ? $server->name : "Select Server" }}
|
||||||
|
</div>
|
||||||
|
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
</x-slot>
|
||||||
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
<x-slot:content>
|
||||||
</button>
|
@foreach (auth()->user()->currentProject->servers as $s)
|
||||||
</div>
|
<x-dropdown-link class="relative" :href="route('servers.show', ['server' => $s])">
|
||||||
</x-slot>
|
<span class="block truncate">{{ ucfirst($s->name) }}</span>
|
||||||
<x-slot:content>
|
@if (isset($server) && $server->id == $s->id)
|
||||||
@foreach (auth()->user()->currentProject->servers as $s)
|
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-primary-600">
|
||||||
<x-dropdown-link class="relative" :href="route('servers.show', ['server' => $s])">
|
<x-heroicon name="o-check" class="h-5 w-5" />
|
||||||
<span class="block truncate">{{ ucfirst($s->name) }}</span>
|
</span>
|
||||||
@if (isset($server) && $server->id == $s->id)
|
@endif
|
||||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-primary-600">
|
</x-dropdown-link>
|
||||||
<x-heroicon name="o-check" class="h-5 w-5" />
|
@endforeach
|
||||||
</span>
|
|
||||||
@endif
|
|
||||||
</x-dropdown-link>
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
<x-dropdown-link href="{{ route('servers') }}">
|
<x-dropdown-link href="{{ route('servers') }}">
|
||||||
{{ __("Servers List") }}
|
{{ __("Servers List") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
<x-dropdown-link href="{{ route('servers.create') }}">
|
<x-dropdown-link href="{{ route('servers.create') }}">
|
||||||
{{ __("Create a Server") }}
|
{{ __("Create a Server") }}
|
||||||
</x-dropdown-link>
|
</x-dropdown-link>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
</x-dropdown>
|
</x-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
|
@ -1,124 +0,0 @@
|
|||||||
<div x-data="siteCombobox()">
|
|
||||||
<div class="relative">
|
|
||||||
<div
|
|
||||||
@click="open = !open"
|
|
||||||
class="text-md flex h-10 w-full cursor-pointer items-center rounded-md bg-gray-200 px-4 py-3 pr-10 leading-5 focus:ring-1 focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-100"
|
|
||||||
x-text="selected.domain ?? 'Select Site'"
|
|
||||||
></div>
|
|
||||||
<button type="button" @click="open = !open" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-5 w-5 text-gray-400"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<div
|
|
||||||
x-show="open"
|
|
||||||
@click.away="open = false"
|
|
||||||
class="absolute mt-1 w-full overflow-auto rounded-md bg-white pb-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-700 sm:text-sm"
|
|
||||||
>
|
|
||||||
<div class="relative p-2">
|
|
||||||
<input
|
|
||||||
x-model="query"
|
|
||||||
@input="filterSitesAndOpen"
|
|
||||||
placeholder="Filter"
|
|
||||||
class="dark:focus:ring-800 w-full rounded-md bg-gray-200 py-2 pl-3 pr-10 text-sm leading-5 focus:ring-1 focus:ring-gray-400 dark:bg-gray-800 dark:text-gray-100"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="relative max-h-[350px] overflow-y-auto">
|
|
||||||
<template x-for="(site, index) in filteredSites" :key="index">
|
|
||||||
<div
|
|
||||||
@click="selectSite(site); open = false"
|
|
||||||
:class="site.id === selected.id ? 'cursor-default bg-primary-600 text-white' : 'cursor-pointer'"
|
|
||||||
class="relative select-none px-4 py-2 text-gray-700 hover:bg-primary-600 hover:text-white dark:text-white"
|
|
||||||
>
|
|
||||||
<span class="block truncate" x-text="site.domain"></span>
|
|
||||||
<template x-if="site.id === selected.id">
|
|
||||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 text-white">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
class="h-5 w-5"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
x-show="filteredSites.length === 0"
|
|
||||||
class="relative block cursor-default select-none truncate px-4 py-2 text-gray-700 dark:text-white"
|
|
||||||
>
|
|
||||||
No sites found!
|
|
||||||
</div>
|
|
||||||
<div class="py-1">
|
|
||||||
<hr class="border-gray-300 dark:border-gray-600" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="{{ route("servers.sites", ["server" => $server]) }}"
|
|
||||||
class="@if(request()->routeIs('sites')) cursor-default bg-primary-600 text-white @else cursor-pointer @endif relative block select-none px-4 py-2 text-gray-700 hover:bg-primary-600 hover:text-white dark:text-white"
|
|
||||||
>
|
|
||||||
<span class="block truncate">Sites List</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href="{{ route("servers.sites.create", ["server" => $server]) }}"
|
|
||||||
class="@if(request()->routeIs('sites.create')) cursor-default bg-primary-600 text-white @else cursor-pointer @endif relative block select-none px-4 py-2 text-gray-700 hover:bg-primary-600 hover:text-white dark:text-white"
|
|
||||||
>
|
|
||||||
<span class="block truncate">Create a Site</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function siteCombobox() {
|
|
||||||
const sites = @json(\App\Models\Site::query()->where('server_id', $server->id)->select('id', 'domain')->get());
|
|
||||||
return {
|
|
||||||
open: false,
|
|
||||||
query: '',
|
|
||||||
sites: sites,
|
|
||||||
selected: @if(isset($site)) @json($site->only('id', 'domain')) @else {} @endif,
|
|
||||||
filteredSites: sites,
|
|
||||||
selectSite(site) {
|
|
||||||
if (this.selected.id !== site.id) {
|
|
||||||
this.selected = site;
|
|
||||||
window.location.href = '{{ url('/servers') }}/' + '{{ $server->id }}/sites/' + site.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
filterSitesAndOpen() {
|
|
||||||
if (this.query === '') {
|
|
||||||
this.filteredSites = this.sites;
|
|
||||||
this.open = false;
|
|
||||||
} else {
|
|
||||||
this.filteredSites = this.sites.filter((site) =>
|
|
||||||
site.domain
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/\s+/g, '')
|
|
||||||
.includes(this.query.toLowerCase().replace(/\s+/g, ''))
|
|
||||||
);
|
|
||||||
this.open = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -143,7 +143,7 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
|||||||
>
|
>
|
||||||
<x-heroicon name="o-wrench-screwdriver" class="h-6 w-6" />
|
<x-heroicon name="o-wrench-screwdriver" class="h-6 w-6" />
|
||||||
<span class="ml-2">
|
<span class="ml-2">
|
||||||
{{ __("Settings") }}
|
{{ __("Server Settings") }}
|
||||||
</span>
|
</span>
|
||||||
</x-sidebar-link>
|
</x-sidebar-link>
|
||||||
</li>
|
</li>
|
||||||
@ -168,45 +168,72 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
|||||||
<span class="ml-2">Profile</span>
|
<span class="ml-2">Profile</span>
|
||||||
</x-sidebar-link>
|
</x-sidebar-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<x-sidebar-link :href="route('projects')" :active="request()->routeIs('projects')">
|
@if (auth()->user()->isAdmin())
|
||||||
<x-heroicon name="o-inbox-stack" class="h-6 w-6" />
|
<li>
|
||||||
<span class="ml-2">Projects</span>
|
<x-sidebar-link
|
||||||
</x-sidebar-link>
|
:href="route('settings.users.index')"
|
||||||
</li>
|
:active="request()->routeIs('settings.users*')"
|
||||||
<li>
|
>
|
||||||
<x-sidebar-link :href="route('server-providers')" :active="request()->routeIs('server-providers')">
|
<x-heroicon name="o-user-group" class="h-6 w-6" />
|
||||||
<x-heroicon name="o-server-stack" class="h-6 w-6" />
|
<span class="ml-2">Users</span>
|
||||||
<span class="ml-2">Server Providers</span>
|
</x-sidebar-link>
|
||||||
</x-sidebar-link>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li>
|
<x-sidebar-link
|
||||||
<x-sidebar-link :href="route('source-controls')" :active="request()->routeIs('source-controls')">
|
:href="route('settings.projects')"
|
||||||
<x-heroicon name="o-code-bracket" class="h-6 w-6" />
|
:active="request()->routeIs('settings.projects')"
|
||||||
<span class="ml-2">Source Controls</span>
|
>
|
||||||
</x-sidebar-link>
|
<x-heroicon name="o-inbox-stack" class="h-6 w-6" />
|
||||||
</li>
|
<span class="ml-2">Projects</span>
|
||||||
<li>
|
</x-sidebar-link>
|
||||||
<x-sidebar-link :href="route('storage-providers')" :active="request()->routeIs('storage-providers')">
|
</li>
|
||||||
<x-heroicon name="o-circle-stack" class="h-6 w-6" />
|
<li>
|
||||||
<span class="ml-2">Storage Providers</span>
|
<x-sidebar-link
|
||||||
</x-sidebar-link>
|
:href="route('settings.server-providers')"
|
||||||
</li>
|
:active="request()->routeIs('settings.server-providers')"
|
||||||
<li>
|
>
|
||||||
<x-sidebar-link
|
<x-heroicon name="o-server-stack" class="h-6 w-6" />
|
||||||
:href="route('notification-channels')"
|
<span class="ml-2">Server Providers</span>
|
||||||
:active="request()->routeIs('notification-channels')"
|
</x-sidebar-link>
|
||||||
>
|
</li>
|
||||||
<x-heroicon name="o-bell" class="h-6 w-6" />
|
<li>
|
||||||
<span class="ml-2">Notification Channels</span>
|
<x-sidebar-link
|
||||||
</x-sidebar-link>
|
:href="route('settings.source-controls')"
|
||||||
</li>
|
:active="request()->routeIs('settings.source-controls')"
|
||||||
<li>
|
>
|
||||||
<x-sidebar-link :href="route('ssh-keys')" :active="request()->routeIs('ssh-keys')">
|
<x-heroicon name="o-code-bracket" class="h-6 w-6" />
|
||||||
<x-heroicon name="o-key" class="h-6 w-6" />
|
<span class="ml-2">Source Controls</span>
|
||||||
<span class="ml-2">SSH Keys</span>
|
</x-sidebar-link>
|
||||||
</x-sidebar-link>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
|
<x-sidebar-link
|
||||||
|
:href="route('settings.storage-providers')"
|
||||||
|
:active="request()->routeIs('settings.storage-providers')"
|
||||||
|
>
|
||||||
|
<x-heroicon name="o-circle-stack" class="h-6 w-6" />
|
||||||
|
<span class="ml-2">Storage Providers</span>
|
||||||
|
</x-sidebar-link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<x-sidebar-link
|
||||||
|
:href="route('settings.notification-channels')"
|
||||||
|
:active="request()->routeIs('settings.notification-channels')"
|
||||||
|
>
|
||||||
|
<x-heroicon name="o-bell" class="h-6 w-6" />
|
||||||
|
<span class="ml-2">Notification Channels</span>
|
||||||
|
</x-sidebar-link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<x-sidebar-link
|
||||||
|
:href="route('settings.ssh-keys')"
|
||||||
|
:active="request()->routeIs('settings.ssh-keys')"
|
||||||
|
>
|
||||||
|
<x-heroicon name="o-key" class="h-6 w-6" />
|
||||||
|
<span class="ml-2">SSH Keys</span>
|
||||||
|
</x-sidebar-link>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
9
resources/views/profile/index.blade.php
Normal file
9
resources/views/profile/index.blade.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<x-settings-layout>
|
||||||
|
<x-slot name="pageTitle">{{ __("Profile") }}</x-slot>
|
||||||
|
|
||||||
|
@include("profile.partials.update-profile-information")
|
||||||
|
|
||||||
|
@include("profile.partials.update-password")
|
||||||
|
|
||||||
|
@include("profile.partials.two-factor-authentication")
|
||||||
|
</x-settings-layout>
|
@ -0,0 +1,78 @@
|
|||||||
|
<x-card>
|
||||||
|
<x-slot name="title">
|
||||||
|
{{ __("Two Factor Authentication") }}
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<x-slot name="description">
|
||||||
|
{{ __("Here you can activate 2FA to secure your account") }}
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<div id="two-factor">
|
||||||
|
@if (! auth()->user()->two_factor_secret)
|
||||||
|
{{-- Enable 2FA --}}
|
||||||
|
<form
|
||||||
|
hx-post="{{ route("two-factor.enable") }}"
|
||||||
|
hx-target="#two-factor"
|
||||||
|
hx-select="#two-factor"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<x-primary-button type="submit">
|
||||||
|
{{ __("Enable Two-Factor") }}
|
||||||
|
</x-primary-button>
|
||||||
|
</form>
|
||||||
|
@else
|
||||||
|
{{-- Disable 2FA --}}
|
||||||
|
<form
|
||||||
|
hx-post="{{ route("two-factor.disable") }}"
|
||||||
|
hx-target="#two-factor"
|
||||||
|
hx-select="#two-factor"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
@method("DELETE")
|
||||||
|
|
||||||
|
<x-danger-button type="submit">
|
||||||
|
{{ __("Disable Two-Factor") }}
|
||||||
|
</x-danger-button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@if (session("status") == "two-factor-authentication-enabled")
|
||||||
|
<div class="mt-5">
|
||||||
|
{{ __('Two factor authentication is now enabled. Scan the following QR code using your phone\'s authenticator application.') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
{!! auth()->user()->twoFactorQrCodeSvg() !!}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Show 2FA Recovery Codes --}}
|
||||||
|
<div class="mt-5">
|
||||||
|
{{ __("Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 rounded-md border border-gray-100 p-2 dark:border-gray-700">
|
||||||
|
@foreach (json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true) as $code)
|
||||||
|
<div class="mt-2">{{ $code }}</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Regenerate 2FA Recovery Codes --}}
|
||||||
|
<form
|
||||||
|
class="mt-5"
|
||||||
|
hx-post="{{ route("two-factor.recovery-codes") }}"
|
||||||
|
hx-target="#two-factor"
|
||||||
|
hx-select="#two-factor"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<x-primary-button type="submit">
|
||||||
|
{{ __("Regenerate Recovery Codes") }}
|
||||||
|
</x-primary-button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</x-card>
|
@ -70,7 +70,7 @@ class="mt-6 space-y-6"
|
|||||||
@endforeach
|
@endforeach
|
||||||
</x-select-input>
|
</x-select-input>
|
||||||
<x-secondary-button
|
<x-secondary-button
|
||||||
:href="route('server-providers', ['provider' => $provider])"
|
:href="route('settings.server-providers', ['provider' => $provider])"
|
||||||
class="ml-2 flex-none"
|
class="ml-2 flex-none"
|
||||||
>
|
>
|
||||||
{{ __("Connect") }}
|
{{ __("Connect") }}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<x-profile-layout>
|
<x-settings-layout>
|
||||||
<x-slot name="pageTitle">{{ __("Notification Channels") }}</x-slot>
|
<x-slot name="pageTitle">{{ __("Notification Channels") }}</x-slot>
|
||||||
|
|
||||||
@include("settings.notification-channels.partials.channels-list")
|
@include("settings.notification-channels.partials.channels-list")
|
||||||
</x-profile-layout>
|
</x-settings-layout>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
id="add-channel-form"
|
id="add-channel-form"
|
||||||
hx-post="{{ route("notification-channels.add") }}"
|
hx-post="{{ route("settings.notification-channels.add") }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select="#add-channel-form"
|
hx-select="#add-channel-form"
|
||||||
hx-ext="disable-element"
|
hx-ext="disable-element"
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
<x-icon-button
|
<x-icon-button
|
||||||
x-on:click="deleteAction = '{{ route('notification-channels.delete', $channel->id) }}'; $dispatch('open-modal', 'delete-channel')"
|
x-on:click="deleteAction = '{{ route('settings.notification-channels.delete', $channel->id) }}'; $dispatch('open-modal', 'delete-channel')"
|
||||||
>
|
>
|
||||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||||
</x-icon-button>
|
</x-icon-button>
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
<x-profile-layout>
|
|
||||||
<x-slot name="pageTitle">{{ __("Profile") }}</x-slot>
|
|
||||||
|
|
||||||
@include("settings.profile.partials.update-profile-information")
|
|
||||||
|
|
||||||
@include("settings.profile.partials.update-password")
|
|
||||||
|
|
||||||
@include("settings.profile.partials.two-factor-authentication")
|
|
||||||
</x-profile-layout>
|
|
@ -1,60 +0,0 @@
|
|||||||
<x-card>
|
|
||||||
<x-slot name="title">
|
|
||||||
{{ __("Two Factor Authentication") }}
|
|
||||||
</x-slot>
|
|
||||||
|
|
||||||
<x-slot name="description">
|
|
||||||
{{ __("Here you can activate 2FA to secure your account") }}
|
|
||||||
</x-slot>
|
|
||||||
|
|
||||||
@if (! auth()->user()->two_factor_secret)
|
|
||||||
{{-- Enable 2FA --}}
|
|
||||||
<form method="POST" action="{{ route("two-factor.enable") }}">
|
|
||||||
@csrf
|
|
||||||
|
|
||||||
<x-primary-button type="submit">
|
|
||||||
{{ __("Enable Two-Factor") }}
|
|
||||||
</x-primary-button>
|
|
||||||
</form>
|
|
||||||
@else
|
|
||||||
{{-- Disable 2FA --}}
|
|
||||||
<form method="POST" action="{{ route("two-factor.disable") }}">
|
|
||||||
@csrf
|
|
||||||
@method("DELETE")
|
|
||||||
|
|
||||||
<x-danger-button type="submit">
|
|
||||||
{{ __("Disable Two-Factor") }}
|
|
||||||
</x-danger-button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
@if (session("status") == "two-factor-authentication-enabled")
|
|
||||||
<div class="mt-5">
|
|
||||||
{{ __('Two factor authentication is now enabled. Scan the following QR code using your phone\'s authenticator application.') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-5">
|
|
||||||
{!! auth()->user()->twoFactorQrCodeSvg() !!}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
{{-- Show 2FA Recovery Codes --}}
|
|
||||||
<div class="mt-5">
|
|
||||||
{{ __("Store these recovery codes in a secure password manager. They can be used to recover access to your account if your two factor authentication device is lost.") }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-5 rounded-md border border-gray-100 p-2 dark:border-gray-700">
|
|
||||||
@foreach (json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true) as $code)
|
|
||||||
<div class="mt-2">{{ $code }}</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Regenerate 2FA Recovery Codes --}}
|
|
||||||
<form class="mt-5" method="POST" action="{{ route("two-factor.recovery-codes") }}">
|
|
||||||
@csrf
|
|
||||||
|
|
||||||
<x-primary-button type="submit">
|
|
||||||
{{ __("Regenerate Recovery Codes") }}
|
|
||||||
</x-primary-button>
|
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
</x-card>
|
|
@ -1,5 +1,5 @@
|
|||||||
<x-profile-layout>
|
<x-settings-layout>
|
||||||
<x-slot name="pageTitle">{{ __("Projects") }}</x-slot>
|
<x-slot name="pageTitle">{{ __("Projects") }}</x-slot>
|
||||||
|
|
||||||
@include("settings.projects.partials.projects-list")
|
@include("settings.projects.partials.projects-list")
|
||||||
</x-profile-layout>
|
</x-settings-layout>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<x-modal name="create-project" :show="request()->has('create')">
|
<x-modal name="create-project" :show="request()->has('create')">
|
||||||
<form
|
<form
|
||||||
id="create-project-form"
|
id="create-project-form"
|
||||||
hx-post="{{ route("projects.create") }}"
|
hx-post="{{ route("settings.projects.create") }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select="#create-project-form"
|
hx-select="#create-project-form"
|
||||||
hx-ext="disable-element"
|
hx-ext="disable-element"
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<x-modal name="edit-project-{{ $project->id }}">
|
<x-modal name="edit-project-{{ $project->id }}">
|
||||||
<form
|
<form
|
||||||
id="edit-project-form-{{ $project->id }}"
|
id="edit-project-form-{{ $project->id }}"
|
||||||
hx-post="{{ route("projects.update", $project) }}"
|
hx-post="{{ route("settings.projects.update", $project) }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select="#edit-project-form-{{ $project->id }}"
|
hx-select="#edit-project-form-{{ $project->id }}"
|
||||||
hx-ext="disable-element"
|
hx-ext="disable-element"
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@include("settings.projects.partials.edit-project", ["project" => $project])
|
@include("settings.projects.partials.edit-project", ["project" => $project])
|
||||||
<x-icon-button
|
<x-icon-button
|
||||||
x-on:click="deleteAction = '{{ route('projects.delete', $project) }}'; $dispatch('open-modal', 'delete-project')"
|
x-on:click="deleteAction = '{{ route('settings.projects.delete', $project) }}'; $dispatch('open-modal', 'delete-project')"
|
||||||
>
|
>
|
||||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||||
</x-icon-button>
|
</x-icon-button>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<x-profile-layout>
|
<x-settings-layout>
|
||||||
<x-slot name="pageTitle">{{ __("Storage Providers") }}</x-slot>
|
<x-slot name="pageTitle">{{ __("Storage Providers") }}</x-slot>
|
||||||
|
|
||||||
@include("settings.server-providers.partials.providers-list")
|
@include("settings.server-providers.partials.providers-list")
|
||||||
</x-profile-layout>
|
</x-settings-layout>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
id="connect-provider-form"
|
id="connect-provider-form"
|
||||||
hx-post="{{ route("server-providers.connect") }}"
|
hx-post="{{ route("settings.server-providers.connect") }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select="#connect-provider-form"
|
hx-select="#connect-provider-form"
|
||||||
hx-ext="disable-element"
|
hx-ext="disable-element"
|
||||||
|
@ -26,7 +26,7 @@ class="h-10 w-10"
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
<x-icon-button
|
<x-icon-button
|
||||||
x-on:click="deleteAction = '{{ route('server-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
x-on:click="deleteAction = '{{ route('settings.server-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
||||||
>
|
>
|
||||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||||
</x-icon-button>
|
</x-icon-button>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<x-profile-layout>
|
<x-settings-layout>
|
||||||
<x-slot name="pageTitle">{{ __("Source Controls") }}</x-slot>
|
<x-slot name="pageTitle">{{ __("Source Controls") }}</x-slot>
|
||||||
|
|
||||||
@include("settings.source-controls.partials.source-controls-list")
|
@include("settings.source-controls.partials.source-controls-list")
|
||||||
</x-profile-layout>
|
</x-settings-layout>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
id="connect-source-control-form"
|
id="connect-source-control-form"
|
||||||
hx-post="{{ route("source-controls.connect") }}"
|
hx-post="{{ route("settings.source-controls.connect") }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select="#connect-source-control-form"
|
hx-select="#connect-source-control-form"
|
||||||
hx-ext="disable-element"
|
hx-ext="disable-element"
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
<x-icon-button
|
<x-icon-button
|
||||||
x-on:click="deleteAction = '{{ route('source-controls.delete', $sourceControl->id) }}'; $dispatch('open-modal', 'delete-source-control')"
|
x-on:click="deleteAction = '{{ route('settings.source-controls.delete', $sourceControl->id) }}'; $dispatch('open-modal', 'delete-source-control')"
|
||||||
>
|
>
|
||||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||||
</x-icon-button>
|
</x-icon-button>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<x-profile-layout>
|
<x-settings-layout>
|
||||||
<x-slot name="pageTitle">{{ __("SSH Keys") }}</x-slot>
|
<x-slot name="pageTitle">{{ __("SSH Keys") }}</x-slot>
|
||||||
|
|
||||||
@include("settings.ssh-keys.partials.keys-list")
|
@include("settings.ssh-keys.partials.keys-list")
|
||||||
</x-profile-layout>
|
</x-settings-layout>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<x-modal name="add-key">
|
<x-modal name="add-key">
|
||||||
<form
|
<form
|
||||||
id="add-ssh-key-form"
|
id="add-ssh-key-form"
|
||||||
hx-post="{{ route("ssh-keys.add") }}"
|
hx-post="{{ route("settings.ssh-keys.add") }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select="#add-ssh-key-form"
|
hx-select="#add-ssh-key-form"
|
||||||
hx-ext="disable-element"
|
hx-ext="disable-element"
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
<x-icon-button
|
<x-icon-button
|
||||||
x-on:click="deleteAction = '{{ route('ssh-keys.delete', $key->id) }}'; $dispatch('open-modal', 'delete-ssh-key')"
|
x-on:click="deleteAction = '{{ route('settings.ssh-keys.delete', $key->id) }}'; $dispatch('open-modal', 'delete-ssh-key')"
|
||||||
>
|
>
|
||||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||||
</x-icon-button>
|
</x-icon-button>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<x-profile-layout>
|
<x-settings-layout>
|
||||||
<x-slot name="pageTitle">{{ __("Storage Providers") }}</x-slot>
|
<x-slot name="pageTitle">{{ __("Storage Providers") }}</x-slot>
|
||||||
|
|
||||||
@include("settings.storage-providers.partials.providers-list")
|
@include("settings.storage-providers.partials.providers-list")
|
||||||
</x-profile-layout>
|
</x-settings-layout>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
id="connect-storage-provider-form"
|
id="connect-storage-provider-form"
|
||||||
hx-post="{{ route("storage-providers.connect") }}"
|
hx-post="{{ route("settings.storage-providers.connect") }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-select="#connect-storage-provider-form"
|
hx-select="#connect-storage-provider-form"
|
||||||
hx-ext="disable-element"
|
hx-ext="disable-element"
|
||||||
|
@ -43,7 +43,7 @@ class="h-10 w-10"
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="inline">
|
<div class="inline">
|
||||||
<x-icon-button
|
<x-icon-button
|
||||||
x-on:click="deleteAction = '{{ route('storage-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
x-on:click="deleteAction = '{{ route('settings.storage-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
||||||
>
|
>
|
||||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||||
</x-icon-button>
|
</x-icon-button>
|
||||||
|
61
resources/views/settings/users/index.blade.php
Normal file
61
resources/views/settings/users/index.blade.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<x-settings-layout>
|
||||||
|
<x-slot name="pageTitle">Users</x-slot>
|
||||||
|
|
||||||
|
<x-container>
|
||||||
|
<x-card-header>
|
||||||
|
<x-slot name="title">Users</x-slot>
|
||||||
|
<x-slot name="description">Here you can manage users</x-slot>
|
||||||
|
<x-slot name="aside">
|
||||||
|
@include("settings.users.partials.create-user")
|
||||||
|
</x-slot>
|
||||||
|
</x-card-header>
|
||||||
|
<div class="space-y-3" x-data="{ deleteAction: '' }">
|
||||||
|
<x-table>
|
||||||
|
<x-thead>
|
||||||
|
<x-tr>
|
||||||
|
<x-th>ID</x-th>
|
||||||
|
<x-th>Name</x-th>
|
||||||
|
<x-th>Email</x-th>
|
||||||
|
<x-th>Role</x-th>
|
||||||
|
<x-th></x-th>
|
||||||
|
</x-tr>
|
||||||
|
</x-thead>
|
||||||
|
<x-tbody>
|
||||||
|
@foreach ($users as $user)
|
||||||
|
<x-tr>
|
||||||
|
<x-td>{{ $user->id }}</x-td>
|
||||||
|
<x-td>{{ $user->name }}</x-td>
|
||||||
|
<x-td>{{ $user->email }}</x-td>
|
||||||
|
<x-td>
|
||||||
|
<div class="inline-flex">
|
||||||
|
@if ($user->role === \App\Enums\UserRole::ADMIN)
|
||||||
|
<x-status status="success">ADMIN</x-status>
|
||||||
|
@else
|
||||||
|
<x-status status="info">USER</x-status>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</x-td>
|
||||||
|
<x-td class="text-right">
|
||||||
|
<x-icon-button
|
||||||
|
x-on:click="deleteAction = '{{ route('settings.users.delete', ['user' => $user]) }}'; $dispatch('open-modal', 'delete-user')"
|
||||||
|
>
|
||||||
|
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||||
|
</x-icon-button>
|
||||||
|
<x-icon-button :href="route('settings.users.show', ['user' => $user])">
|
||||||
|
<x-heroicon name="o-cog-6-tooth" class="h-5 w-5" />
|
||||||
|
</x-icon-button>
|
||||||
|
</x-td>
|
||||||
|
</x-tr>
|
||||||
|
@endforeach
|
||||||
|
</x-tbody>
|
||||||
|
</x-table>
|
||||||
|
<x-confirmation-modal
|
||||||
|
name="delete-user"
|
||||||
|
:title="__('Confirm')"
|
||||||
|
:description="__('Are you sure that you want to delete this user?')"
|
||||||
|
method="delete"
|
||||||
|
x-bind:action="deleteAction"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</x-container>
|
||||||
|
</x-settings-layout>
|
@ -0,0 +1,69 @@
|
|||||||
|
<div>
|
||||||
|
<x-primary-button x-data="" x-on:click.prevent="$dispatch('open-modal', 'create-user')">New User</x-primary-button>
|
||||||
|
|
||||||
|
<x-modal name="create-user">
|
||||||
|
<form
|
||||||
|
id="create-user-form"
|
||||||
|
hx-post="{{ route("settings.users.store") }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-select="#create-user-form"
|
||||||
|
hx-ext="disable-element"
|
||||||
|
hx-disable-element="#btn-create-user"
|
||||||
|
class="p-6"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Create New User</h2>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="name" value="Name" />
|
||||||
|
<x-text-input value="{{ old('name') }}" id="name" name="name" type="text" class="mt-1 w-full" />
|
||||||
|
@error("name")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="email" value="Email" />
|
||||||
|
<x-text-input value="{{ old('email') }}" id="email" name="email" type="text" class="mt-1 w-full" />
|
||||||
|
@error("email")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="password" value="Password" />
|
||||||
|
<x-text-input id="password" name="password" type="password" class="mt-1 w-full" />
|
||||||
|
@error("password")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="role" value="Role" />
|
||||||
|
<x-select-input id="role" name="role" class="mt-1 w-full">
|
||||||
|
<option
|
||||||
|
value="{{ \App\Enums\UserRole::USER }}"
|
||||||
|
@if(old('role') === \App\Enums\UserRole::USER) selected @endif
|
||||||
|
>
|
||||||
|
User
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="{{ \App\Enums\UserRole::ADMIN }}"
|
||||||
|
@if(old('role') === \App\Enums\UserRole::ADMIN) selected @endif
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
</option>
|
||||||
|
</x-select-input>
|
||||||
|
@error("role")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 flex justify-end">
|
||||||
|
<x-secondary-button type="button" x-on:click="$dispatch('close')">Cancel</x-secondary-button>
|
||||||
|
|
||||||
|
<x-primary-button id="btn-create-project" class="ml-3">Create</x-primary-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</x-modal>
|
||||||
|
</div>
|
@ -0,0 +1,120 @@
|
|||||||
|
<x-card>
|
||||||
|
<x-slot name="title">Projects</x-slot>
|
||||||
|
|
||||||
|
<x-slot name="description">Manage the projects that the user is in</x-slot>
|
||||||
|
|
||||||
|
<x-slot name="aside">
|
||||||
|
<x-secondary-button :href="route('settings.users.index')">Back to Users</x-secondary-button>
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="update-projects"
|
||||||
|
hx-post="{{ route("settings.users.update-projects", ["user" => $user]) }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-select="#update-projects"
|
||||||
|
hx-trigger="submit"
|
||||||
|
hx-ext="disable-element"
|
||||||
|
hx-disable-element="#btn-save-projects"
|
||||||
|
class="mt-6"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let projects = @json($user->projects);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="space-y-6"
|
||||||
|
x-data="{
|
||||||
|
q: '',
|
||||||
|
projects: projects,
|
||||||
|
search() {
|
||||||
|
htmx.ajax('GET', '{{ request()->getUri() }}?q=' + this.q, {
|
||||||
|
target: '#projects-list',
|
||||||
|
swap: 'outerHTML',
|
||||||
|
select: '#projects-list',
|
||||||
|
}).then(() => {
|
||||||
|
document.getElementById('q').focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
addProject(project) {
|
||||||
|
if (this.projects.find((p) => p.id === project.id)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.projects.push(project)
|
||||||
|
this.q = ''
|
||||||
|
},
|
||||||
|
removeProject(id) {
|
||||||
|
this.projects = this.projects.filter((project) => project.id !== id)
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<x-input-label value="Projects" />
|
||||||
|
|
||||||
|
<div class="mt-1">
|
||||||
|
<template x-for="project in projects">
|
||||||
|
<div class="mr-1 inline-flex">
|
||||||
|
<x-status status="info" class="flex items-center">
|
||||||
|
<span x-text="project.name"></span>
|
||||||
|
<x-heroicon
|
||||||
|
name="o-x-mark"
|
||||||
|
class="ml-1 h-4 w-4 cursor-pointer"
|
||||||
|
x-on:click="removeProject(project.id)"
|
||||||
|
/>
|
||||||
|
<input type="hidden" name="projects[]" x-bind:value="project.id" />
|
||||||
|
</x-status>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label value="Add new Project" />
|
||||||
|
|
||||||
|
@php
|
||||||
|
$projects = \App\Models\Project::query()
|
||||||
|
->where(function ($query) {
|
||||||
|
if (request()->has("q")) {
|
||||||
|
$query->where("name", "like", "%" . request("q") . "%");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->take(5)
|
||||||
|
->get();
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<x-dropdown width="full">
|
||||||
|
<x-slot name="trigger">
|
||||||
|
<x-text-input
|
||||||
|
id="q"
|
||||||
|
name="q"
|
||||||
|
x-model="q"
|
||||||
|
type="text"
|
||||||
|
class="mt-1 w-full"
|
||||||
|
placeholder="Search for projects..."
|
||||||
|
autocomplete="off"
|
||||||
|
x-on:input.debounce.500ms="search"
|
||||||
|
/>
|
||||||
|
</x-slot>
|
||||||
|
<x-slot name="content">
|
||||||
|
<div id="projects-list">
|
||||||
|
@foreach ($projects as $project)
|
||||||
|
<x-dropdown-link
|
||||||
|
class="cursor-pointer"
|
||||||
|
x-on:click="addProject({ id: {{ $project->id }}, name: '{{ $project->name }}' })"
|
||||||
|
>
|
||||||
|
{{ $project->name }}
|
||||||
|
</x-dropdown-link>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
</x-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<x-slot name="actions">
|
||||||
|
<x-primary-button id="btn-save-projects" form="update-projects">Save</x-primary-button>
|
||||||
|
</x-slot>
|
||||||
|
</x-card>
|
@ -0,0 +1,99 @@
|
|||||||
|
<x-card>
|
||||||
|
<x-slot name="title">User Info</x-slot>
|
||||||
|
|
||||||
|
<x-slot name="description">You can update user's info here</x-slot>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="update-user-info"
|
||||||
|
hx-post="{{ route("settings.users.update", ["user" => $user]) }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-select="#update-user-info"
|
||||||
|
hx-trigger="submit"
|
||||||
|
hx-ext="disable-element"
|
||||||
|
hx-disable-element="#btn-save-info"
|
||||||
|
class="mt-6 space-y-6"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
<div>
|
||||||
|
<x-input-label for="name" value="Name" />
|
||||||
|
<x-text-input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
value="{{ old('name', $user->name) }}"
|
||||||
|
class="mt-1 block w-full"
|
||||||
|
required
|
||||||
|
autocomplete="name"
|
||||||
|
/>
|
||||||
|
@error("name")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="email" value="Email" />
|
||||||
|
<x-text-input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
value="{{ old('email', $user->email) }}"
|
||||||
|
class="mt-1 block w-full"
|
||||||
|
required
|
||||||
|
autocomplete="email"
|
||||||
|
/>
|
||||||
|
@error("email")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="timezone" value="Timezone" />
|
||||||
|
<x-select-input id="timezone" name="timezone" class="mt-1 block w-full" required>
|
||||||
|
@foreach (timezone_identifiers_list() as $timezone)
|
||||||
|
<option
|
||||||
|
value="{{ $timezone }}"
|
||||||
|
@if(old('timezone', $user->timezone) == $timezone) selected @endif
|
||||||
|
>
|
||||||
|
{{ $timezone }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</x-select-input>
|
||||||
|
@error("timezone")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="role" value="Role" />
|
||||||
|
<x-select-input id="role" name="role" class="mt-1 w-full">
|
||||||
|
<option
|
||||||
|
value="{{ \App\Enums\UserRole::USER }}"
|
||||||
|
@if(old('role', $user->role) === \App\Enums\UserRole::USER) selected @endif
|
||||||
|
>
|
||||||
|
User
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="{{ \App\Enums\UserRole::ADMIN }}"
|
||||||
|
@if(old('role', $user->role) === \App\Enums\UserRole::ADMIN) selected @endif
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
</option>
|
||||||
|
</x-select-input>
|
||||||
|
@error("role")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="password" value="New Password" />
|
||||||
|
<x-text-input id="password" name="password" type="password" class="mt-1 w-full" />
|
||||||
|
@error("password")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<x-slot name="actions">
|
||||||
|
<x-primary-button id="btn-save-info" form="update-user-info">Save</x-primary-button>
|
||||||
|
</x-slot>
|
||||||
|
</x-card>
|
@ -0,0 +1,69 @@
|
|||||||
|
<x-card>
|
||||||
|
<x-slot name="title">
|
||||||
|
{{ __("Update Password") }}
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<x-slot name="description">
|
||||||
|
{{ __("Ensure your account is using a long, random password to stay secure.") }}
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="update-password"
|
||||||
|
class="mt-6 space-y-6"
|
||||||
|
hx-post="{{ route("profile.password") }}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-select="#update-password"
|
||||||
|
hx-trigger="submit"
|
||||||
|
hx-ext="disable-element"
|
||||||
|
hx-disable-element="#btn-save-password"
|
||||||
|
>
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="current_password" :value="__('Current Password')" />
|
||||||
|
<x-text-input
|
||||||
|
id="current_password"
|
||||||
|
name="current_password"
|
||||||
|
type="password"
|
||||||
|
class="mt-1 block w-full"
|
||||||
|
autocomplete="current-password"
|
||||||
|
/>
|
||||||
|
@error("current_password")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="password" :value="__('New Password')" />
|
||||||
|
<x-text-input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
class="mt-1 block w-full"
|
||||||
|
autocomplete="new-password"
|
||||||
|
/>
|
||||||
|
@error("password")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||||
|
<x-text-input
|
||||||
|
id="password_confirmation"
|
||||||
|
name="password_confirmation"
|
||||||
|
type="password"
|
||||||
|
class="mt-1 block w-full"
|
||||||
|
autocomplete="new-password"
|
||||||
|
/>
|
||||||
|
@error("password_confirmation")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<x-slot name="actions">
|
||||||
|
<x-primary-button id="btn-save-password" form="update-password">
|
||||||
|
{{ __("Save") }}
|
||||||
|
</x-primary-button>
|
||||||
|
</x-slot>
|
||||||
|
</x-card>
|
9
resources/views/settings/users/show.blade.php
Normal file
9
resources/views/settings/users/show.blade.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<x-settings-layout>
|
||||||
|
<x-slot name="pageTitle">Users</x-slot>
|
||||||
|
|
||||||
|
<x-container>
|
||||||
|
@include("settings.users.partials.update-projects")
|
||||||
|
|
||||||
|
@include("settings.users.partials.update-user-info")
|
||||||
|
</x-container>
|
||||||
|
</x-settings-layout>
|
@ -13,7 +13,10 @@
|
|||||||
</option>
|
</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</x-select-input>
|
</x-select-input>
|
||||||
<x-secondary-button :href="route('source-controls', ['redirect' => request()->url()])" class="ml-2 flex-none">
|
<x-secondary-button
|
||||||
|
:href="route('settings.source-controls', ['redirect' => request()->url()])"
|
||||||
|
class="ml-2 flex-none"
|
||||||
|
>
|
||||||
{{ __("Connect") }}
|
{{ __("Connect") }}
|
||||||
</x-secondary-button>
|
</x-secondary-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,66 +1,62 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\Settings\NotificationChannelController;
|
use App\Http\Controllers\Settings\NotificationChannelController;
|
||||||
use App\Http\Controllers\Settings\ProfileController;
|
|
||||||
use App\Http\Controllers\Settings\ProjectController;
|
use App\Http\Controllers\Settings\ProjectController;
|
||||||
use App\Http\Controllers\Settings\ServerProviderController;
|
use App\Http\Controllers\Settings\ServerProviderController;
|
||||||
use App\Http\Controllers\Settings\SourceControlController;
|
use App\Http\Controllers\Settings\SourceControlController;
|
||||||
use App\Http\Controllers\Settings\SSHKeyController;
|
use App\Http\Controllers\Settings\SSHKeyController;
|
||||||
use App\Http\Controllers\Settings\StorageProviderController;
|
use App\Http\Controllers\Settings\StorageProviderController;
|
||||||
|
use App\Http\Controllers\Settings\UserController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
// profile
|
Route::prefix('settings/users')->group(function () {
|
||||||
Route::prefix('settings/profile')->group(function () {
|
Route::get('/', [UserController::class, 'index'])->name('settings.users.index');
|
||||||
Route::get('/', [ProfileController::class, 'index'])->name('profile');
|
Route::post('/', [UserController::class, 'store'])->name('settings.users.store');
|
||||||
Route::post('info', [ProfileController::class, 'info'])->name('profile.info');
|
Route::get('/{user}', [UserController::class, 'show'])->name('settings.users.show');
|
||||||
Route::post('password', [ProfileController::class, 'password'])->name('profile.password');
|
Route::post('/{user}', [UserController::class, 'update'])->name('settings.users.update');
|
||||||
|
Route::post('/{user}/projects', [UserController::class, 'updateProjects'])->name('settings.users.update-projects');
|
||||||
|
Route::delete('/{user}', [UserController::class, 'destroy'])->name('settings.users.delete');
|
||||||
});
|
});
|
||||||
|
|
||||||
// profile
|
// projects
|
||||||
Route::prefix('settings/projects')->group(function () {
|
Route::prefix('settings/projects')->group(function () {
|
||||||
Route::get('/', [ProjectController::class, 'index'])->name('projects');
|
Route::get('/', [ProjectController::class, 'index'])->name('settings.projects');
|
||||||
Route::post('create', [ProjectController::class, 'create'])->name('projects.create');
|
Route::post('create', [ProjectController::class, 'create'])->name('settings.projects.create');
|
||||||
Route::post('update/{project}', [ProjectController::class, 'update'])->name('projects.update');
|
Route::post('update/{project}', [ProjectController::class, 'update'])->name('settings.projects.update');
|
||||||
Route::delete('delete/{project}', [ProjectController::class, 'delete'])->name('projects.delete');
|
Route::delete('delete/{project}', [ProjectController::class, 'delete'])->name('settings.projects.delete');
|
||||||
Route::get('switch/{project}', [ProjectController::class, 'switch'])->name('projects.switch');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// server-providers
|
// server-providers
|
||||||
Route::prefix('settings/server-providers')->group(function () {
|
Route::prefix('settings/server-providers')->group(function () {
|
||||||
Route::get('/', [ServerProviderController::class, 'index'])->name('server-providers');
|
Route::get('/', [ServerProviderController::class, 'index'])->name('settings.server-providers');
|
||||||
Route::post('connect', [ServerProviderController::class, 'connect'])->name('server-providers.connect');
|
Route::post('connect', [ServerProviderController::class, 'connect'])->name('settings.server-providers.connect');
|
||||||
Route::delete('delete/{serverProvider}', [ServerProviderController::class, 'delete'])
|
Route::delete('delete/{serverProvider}', [ServerProviderController::class, 'delete'])->name('settings.server-providers.delete');
|
||||||
->name('server-providers.delete');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// source-controls
|
// source-controls
|
||||||
Route::prefix('settings/source-controls')->group(function () {
|
Route::prefix('settings/source-controls')->group(function () {
|
||||||
Route::get('/', [SourceControlController::class, 'index'])->name('source-controls');
|
Route::get('/', [SourceControlController::class, 'index'])->name('settings.source-controls');
|
||||||
Route::post('connect', [SourceControlController::class, 'connect'])->name('source-controls.connect');
|
Route::post('connect', [SourceControlController::class, 'connect'])->name('settings.source-controls.connect');
|
||||||
Route::delete('delete/{sourceControl}', [SourceControlController::class, 'delete'])
|
Route::delete('delete/{sourceControl}', [SourceControlController::class, 'delete'])->name('settings.source-controls.delete');
|
||||||
->name('source-controls.delete');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// storage-providers
|
// storage-providers
|
||||||
Route::prefix('settings/storage-providers')->group(function () {
|
Route::prefix('settings/storage-providers')->group(function () {
|
||||||
Route::get('/', [StorageProviderController::class, 'index'])->name('storage-providers');
|
Route::get('/', [StorageProviderController::class, 'index'])->name('settings.storage-providers');
|
||||||
Route::post('connect', [StorageProviderController::class, 'connect'])->name('storage-providers.connect');
|
Route::post('connect', [StorageProviderController::class, 'connect'])->name('settings.storage-providers.connect');
|
||||||
Route::delete('delete/{storageProvider}', [StorageProviderController::class, 'delete'])
|
Route::delete('delete/{storageProvider}', [StorageProviderController::class, 'delete'])->name('settings.storage-providers.delete');
|
||||||
->name('storage-providers.delete');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// notification-channels
|
// notification-channels
|
||||||
Route::prefix('settings/notification-channels')->group(function () {
|
Route::prefix('settings/notification-channels')->group(function () {
|
||||||
Route::get('/', [NotificationChannelController::class, 'index'])->name('notification-channels');
|
Route::get('/', [NotificationChannelController::class, 'index'])->name('settings.notification-channels');
|
||||||
Route::post('add', [NotificationChannelController::class, 'add'])
|
Route::post('add', [NotificationChannelController::class, 'add'])->name('settings.notification-channels.add');
|
||||||
->name('notification-channels.add');
|
Route::delete('delete/{id}', [NotificationChannelController::class, 'delete'])->name('settings.notification-channels.delete');
|
||||||
Route::delete('delete/{id}', [NotificationChannelController::class, 'delete'])
|
|
||||||
->name('notification-channels.delete');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ssh-keys
|
// ssh-keys
|
||||||
Route::prefix('settings/ssh-keys')->group(function () {
|
Route::prefix('settings/ssh-keys')->group(function () {
|
||||||
Route::get('/', [SSHKeyController::class, 'index'])->name('ssh-keys');
|
Route::get('/', [SSHKeyController::class, 'index'])->name('settings.ssh-keys');
|
||||||
Route::post('add', [SshKeyController::class, 'add'])->name('ssh-keys.add');
|
Route::post('add', [SshKeyController::class, 'add'])->name('settings.ssh-keys.add');
|
||||||
Route::delete('delete/{id}', [SshKeyController::class, 'delete'])->name('ssh-keys.delete');
|
Route::delete('delete/{id}', [SshKeyController::class, 'delete'])->name('settings.ssh-keys.delete');
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\ProfileController;
|
||||||
use App\Http\Controllers\SearchController;
|
use App\Http\Controllers\SearchController;
|
||||||
|
use App\Http\Controllers\Settings\ProjectController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
@ -8,7 +10,19 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware('auth')->group(function () {
|
Route::middleware('auth')->group(function () {
|
||||||
require __DIR__.'/settings.php';
|
// profile
|
||||||
|
Route::prefix('profile')->group(function () {
|
||||||
|
Route::get('/', [ProfileController::class, 'index'])->name('profile');
|
||||||
|
Route::post('info', [ProfileController::class, 'info'])->name('profile.info');
|
||||||
|
Route::post('password', [ProfileController::class, 'password'])->name('profile.password');
|
||||||
|
});
|
||||||
|
|
||||||
|
// switch project
|
||||||
|
Route::get('settings/projects/switch/{project}', [ProjectController::class, 'switch'])->name('settings.projects.switch');
|
||||||
|
|
||||||
|
Route::middleware('is-admin')->group(function () {
|
||||||
|
require __DIR__.'/settings.php';
|
||||||
|
});
|
||||||
|
|
||||||
Route::prefix('/servers')->group(function () {
|
Route::prefix('/servers')->group(function () {
|
||||||
require __DIR__.'/server.php';
|
require __DIR__.'/server.php';
|
||||||
|
@ -17,7 +17,7 @@ public function test_add_email_channel(): void
|
|||||||
{
|
{
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$this->post(route('notification-channels.add'), [
|
$this->post(route('settings.notification-channels.add'), [
|
||||||
'provider' => NotificationChannel::EMAIL,
|
'provider' => NotificationChannel::EMAIL,
|
||||||
'email' => 'email@example.com',
|
'email' => 'email@example.com',
|
||||||
'label' => 'Email',
|
'label' => 'Email',
|
||||||
@ -40,7 +40,7 @@ public function test_cannot_add_email_channel(): void
|
|||||||
|
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$this->post(route('notification-channels.add'), [
|
$this->post(route('settings.notification-channels.add'), [
|
||||||
'provider' => NotificationChannel::EMAIL,
|
'provider' => NotificationChannel::EMAIL,
|
||||||
'email' => 'email@example.com',
|
'email' => 'email@example.com',
|
||||||
'label' => 'Email',
|
'label' => 'Email',
|
||||||
@ -61,7 +61,7 @@ public function test_add_slack_channel(): void
|
|||||||
|
|
||||||
Http::fake();
|
Http::fake();
|
||||||
|
|
||||||
$this->post(route('notification-channels.add'), [
|
$this->post(route('settings.notification-channels.add'), [
|
||||||
'provider' => NotificationChannel::SLACK,
|
'provider' => NotificationChannel::SLACK,
|
||||||
'webhook_url' => 'https://hooks.slack.com/services/123/token',
|
'webhook_url' => 'https://hooks.slack.com/services/123/token',
|
||||||
'label' => 'Slack',
|
'label' => 'Slack',
|
||||||
@ -84,7 +84,7 @@ public function test_cannot_add_slack_channel(): void
|
|||||||
'slack.com/*' => Http::response(['ok' => false], 401),
|
'slack.com/*' => Http::response(['ok' => false], 401),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->post(route('notification-channels.add'), [
|
$this->post(route('settings.notification-channels.add'), [
|
||||||
'provider' => NotificationChannel::SLACK,
|
'provider' => NotificationChannel::SLACK,
|
||||||
'webhook_url' => 'https://hooks.slack.com/services/123/token',
|
'webhook_url' => 'https://hooks.slack.com/services/123/token',
|
||||||
'label' => 'Slack',
|
'label' => 'Slack',
|
||||||
@ -104,7 +104,7 @@ public function test_add_discord_channel(): void
|
|||||||
|
|
||||||
Http::fake();
|
Http::fake();
|
||||||
|
|
||||||
$this->post(route('notification-channels.add'), [
|
$this->post(route('settings.notification-channels.add'), [
|
||||||
'provider' => NotificationChannel::DISCORD,
|
'provider' => NotificationChannel::DISCORD,
|
||||||
'webhook_url' => 'https://discord.com/api/webhooks/123/token',
|
'webhook_url' => 'https://discord.com/api/webhooks/123/token',
|
||||||
'label' => 'Discord',
|
'label' => 'Discord',
|
||||||
@ -127,7 +127,7 @@ public function test_cannot_add_discord_channel(): void
|
|||||||
'discord.com/*' => Http::response(['ok' => false], 401),
|
'discord.com/*' => Http::response(['ok' => false], 401),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->post(route('notification-channels.add'), [
|
$this->post(route('settings.notification-channels.add'), [
|
||||||
'provider' => NotificationChannel::DISCORD,
|
'provider' => NotificationChannel::DISCORD,
|
||||||
'webhook_url' => 'https://discord.com/api/webhooks/123/token',
|
'webhook_url' => 'https://discord.com/api/webhooks/123/token',
|
||||||
'label' => 'Discord',
|
'label' => 'Discord',
|
||||||
@ -147,7 +147,7 @@ public function test_add_telegram_channel(): void
|
|||||||
|
|
||||||
Http::fake();
|
Http::fake();
|
||||||
|
|
||||||
$this->post(route('notification-channels.add'), [
|
$this->post(route('settings.notification-channels.add'), [
|
||||||
'provider' => NotificationChannel::TELEGRAM,
|
'provider' => NotificationChannel::TELEGRAM,
|
||||||
'bot_token' => 'token',
|
'bot_token' => 'token',
|
||||||
'chat_id' => '123',
|
'chat_id' => '123',
|
||||||
@ -172,7 +172,7 @@ public function test_cannot_add_telegram_channel(): void
|
|||||||
'api.telegram.org/*' => Http::response(['ok' => false], 401),
|
'api.telegram.org/*' => Http::response(['ok' => false], 401),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->post(route('notification-channels.add'), [
|
$this->post(route('settings.notification-channels.add'), [
|
||||||
'provider' => NotificationChannel::TELEGRAM,
|
'provider' => NotificationChannel::TELEGRAM,
|
||||||
'bot_token' => 'token',
|
'bot_token' => 'token',
|
||||||
'chat_id' => '123',
|
'chat_id' => '123',
|
||||||
@ -193,7 +193,7 @@ public function test_see_channels_list(): void
|
|||||||
|
|
||||||
$channel = \App\Models\NotificationChannel::factory()->create();
|
$channel = \App\Models\NotificationChannel::factory()->create();
|
||||||
|
|
||||||
$this->get(route('notification-channels'))
|
$this->get(route('settings.notification-channels'))
|
||||||
->assertSuccessful()
|
->assertSuccessful()
|
||||||
->assertSee($channel->provider);
|
->assertSee($channel->provider);
|
||||||
}
|
}
|
||||||
@ -204,7 +204,7 @@ public function test_delete_channel(): void
|
|||||||
|
|
||||||
$channel = \App\Models\NotificationChannel::factory()->create();
|
$channel = \App\Models\NotificationChannel::factory()->create();
|
||||||
|
|
||||||
$this->delete(route('notification-channels.delete', $channel->id))
|
$this->delete(route('settings.notification-channels.delete', $channel->id))
|
||||||
->assertSessionDoesntHaveErrors();
|
->assertSessionDoesntHaveErrors();
|
||||||
|
|
||||||
$this->assertDatabaseMissing('notification_channels', [
|
$this->assertDatabaseMissing('notification_channels', [
|
||||||
|
@ -14,7 +14,7 @@ public function test_create_project(): void
|
|||||||
{
|
{
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$this->post(route('projects.create'), [
|
$this->post(route('settings.projects.create'), [
|
||||||
'name' => 'test',
|
'name' => 'test',
|
||||||
])->assertSessionDoesntHaveErrors();
|
])->assertSessionDoesntHaveErrors();
|
||||||
|
|
||||||
@ -27,11 +27,11 @@ public function test_see_projects_list(): void
|
|||||||
{
|
{
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$project = Project::factory()->create([
|
$project = Project::factory()->create();
|
||||||
'user_id' => $this->user->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->get(route('projects'))
|
$this->user->projects()->attach($project);
|
||||||
|
|
||||||
|
$this->get(route('settings.projects'))
|
||||||
->assertSuccessful()
|
->assertSuccessful()
|
||||||
->assertSee($project->name);
|
->assertSee($project->name);
|
||||||
}
|
}
|
||||||
@ -40,11 +40,11 @@ public function test_delete_project(): void
|
|||||||
{
|
{
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$project = Project::factory()->create([
|
$project = Project::factory()->create();
|
||||||
'user_id' => $this->user->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->delete(route('projects.delete', $project))
|
$this->user->projects()->attach($project);
|
||||||
|
|
||||||
|
$this->delete(route('settings.projects.delete', $project))
|
||||||
->assertSessionDoesntHaveErrors();
|
->assertSessionDoesntHaveErrors();
|
||||||
|
|
||||||
$this->assertDatabaseMissing('projects', [
|
$this->assertDatabaseMissing('projects', [
|
||||||
@ -56,11 +56,11 @@ public function test_edit_project(): void
|
|||||||
{
|
{
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$project = Project::factory()->create([
|
$project = Project::factory()->create();
|
||||||
'user_id' => $this->user->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->post(route('projects.update', $project), [
|
$this->user->projects()->attach($project);
|
||||||
|
|
||||||
|
$this->post(route('settings.projects.update', $project), [
|
||||||
'name' => 'new-name',
|
'name' => 'new-name',
|
||||||
])->assertSessionDoesntHaveErrors();
|
])->assertSessionDoesntHaveErrors();
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ public function test_cannot_delete_last_project(): void
|
|||||||
{
|
{
|
||||||
$this->actingAs($this->user);
|
$this->actingAs($this->user);
|
||||||
|
|
||||||
$this->delete(route('projects.delete', [
|
$this->delete(route('settings.projects.delete', [
|
||||||
'project' => $this->user->currentProject,
|
'project' => $this->user->currentProject,
|
||||||
]))
|
]))
|
||||||
->assertSessionDoesntHaveErrors()
|
->assertSessionDoesntHaveErrors()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user