feat: initialize Infra Manager with React, Tailwind CSS, and Inertia.js
- Added main application entry point (app.jsx) and bootstrap file for React setup. - Created Wiki index page (Index.jsx) for documentation and file management. - Implemented utility functions for formatting and status handling in utils.js. - Set up Blade template (app.blade.php) for Inertia.js integration. - Defined API routes for metrics and terminal access in api.php. - Established console commands for scheduled tasks in console.php. - Created web routes for various application features including authentication, hypervisors, containers, cloud accounts, security, backups, and wiki management in web.php. - Developed setup scripts for Windows (setup.ps1) and Linux (setup.sh) environments. - Configured Tailwind CSS for styling with custom themes and animations in tailwind.config.js. - Set up Vite for asset bundling and development server in vite.config.js.
This commit is contained in:
58
app/Http/Controllers/Acl/RoleController.php
Normal file
58
app/Http/Controllers/Acl/RoleController.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Acl;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class RoleController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Acl/Index', [
|
||||
'users' => \App\Models\User::with('roles')->paginate(30),
|
||||
'roles' => Role::withCount('permissions', 'users')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Acl/RoleCreate', ['permissions' => Permission::all()->groupBy(fn($p) => explode('.', $p->name)[0])]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate(['name' => 'required|string|unique:roles', 'permissions' => 'array']);
|
||||
$role = Role::create(['name' => $validated['name']]);
|
||||
if ($validated['permissions'] ?? false) {
|
||||
$role->syncPermissions($validated['permissions']);
|
||||
}
|
||||
return redirect()->route('acl.index')->with('success', 'Role created.');
|
||||
}
|
||||
|
||||
public function edit(Role $role)
|
||||
{
|
||||
return Inertia::render('Acl/RoleEdit', [
|
||||
'role' => $role->load('permissions'),
|
||||
'permissions' => Permission::all()->groupBy(fn($p) => explode('.', $p->name)[0]),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Role $role)
|
||||
{
|
||||
$role->syncPermissions($request->input('permissions', []));
|
||||
return back()->with('success', 'Role updated.');
|
||||
}
|
||||
|
||||
public function destroy(Role $role)
|
||||
{
|
||||
if (in_array($role->name, ['super-admin', 'admin'])) {
|
||||
return back()->with('error', 'Cannot delete system roles.');
|
||||
}
|
||||
$role->delete();
|
||||
return back()->with('success', 'Role deleted.');
|
||||
}
|
||||
}
|
||||
93
app/Http/Controllers/Acl/UserController.php
Normal file
93
app/Http/Controllers/Acl/UserController.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Acl;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return redirect()->route('acl.index');
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return \Inertia\Inertia::render('Acl/UserCreate', ['roles' => Role::all()]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string',
|
||||
'email' => 'required|email|unique:users',
|
||||
'password' => 'required|string|min:8',
|
||||
'roles' => 'array',
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
if ($validated['roles'] ?? false) {
|
||||
$user->assignRole($validated['roles']);
|
||||
}
|
||||
|
||||
return redirect()->route('acl.index')->with('success', 'User created.');
|
||||
}
|
||||
|
||||
public function edit(User $user)
|
||||
{
|
||||
return \Inertia\Inertia::render('Acl/UserEdit', [
|
||||
'user' => $user->load('roles'),
|
||||
'roles' => Role::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, User $user)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => 'required|string',
|
||||
'email' => "required|email|unique:users,email,{$user->id}",
|
||||
'password' => 'nullable|string|min:8',
|
||||
'is_active' => 'boolean',
|
||||
'roles' => 'array',
|
||||
]);
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$data['password'] = Hash::make($data['password']);
|
||||
} else {
|
||||
unset($data['password']);
|
||||
}
|
||||
|
||||
$user->update($data);
|
||||
|
||||
if (isset($data['roles'])) {
|
||||
$user->syncRoles($data['roles']);
|
||||
}
|
||||
|
||||
return back()->with('success', 'User updated.');
|
||||
}
|
||||
|
||||
public function destroy(User $user)
|
||||
{
|
||||
if ($user->id === auth()->id()) {
|
||||
return back()->with('error', 'Cannot delete your own account.');
|
||||
}
|
||||
$user->delete();
|
||||
return back()->with('success', 'User deleted.');
|
||||
}
|
||||
|
||||
public function assignRole(Request $request, User $user)
|
||||
{
|
||||
$roles = $request->validate(['roles' => 'required|array'])['roles'];
|
||||
$user->syncRoles($roles);
|
||||
return back()->with('success', 'Roles updated.');
|
||||
}
|
||||
}
|
||||
108
app/Http/Controllers/Admin/AdminController.php
Normal file
108
app/Http/Controllers/Admin/AdminController.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Admin/Index', [
|
||||
'stats' => [
|
||||
'users' => User::count(),
|
||||
'sessions' => DB::table('sessions')->count(),
|
||||
'queued' => DB::table('jobs')->count(),
|
||||
'db_size' => $this->dbSize(),
|
||||
],
|
||||
'recent_activity' => Activity::with('causer')
|
||||
->orderByDesc('created_at')
|
||||
->limit(10)
|
||||
->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function auditLog()
|
||||
{
|
||||
return Inertia::render('Admin/AuditLog', [
|
||||
'logs' => Activity::with('causer', 'subject')
|
||||
->orderByDesc('created_at')
|
||||
->paginate(50),
|
||||
]);
|
||||
}
|
||||
|
||||
public function systemHealth()
|
||||
{
|
||||
return Inertia::render('Admin/SystemHealth', [
|
||||
'health' => [
|
||||
'database' => $this->checkDatabase(),
|
||||
'cache' => $this->checkCache(),
|
||||
'queue' => $this->checkQueue(),
|
||||
'storage' => $this->checkStorage(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function checkDatabase(): array
|
||||
{
|
||||
try {
|
||||
DB::connection()->getPdo();
|
||||
return ['status' => 'ok', 'driver' => config('database.default')];
|
||||
} catch (\Throwable $e) {
|
||||
return ['status' => 'error', 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
private function checkCache(): array
|
||||
{
|
||||
try {
|
||||
cache()->set('health_check', true, 1);
|
||||
return ['status' => 'ok', 'driver' => config('cache.default')];
|
||||
} catch (\Throwable $e) {
|
||||
return ['status' => 'error', 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
private function checkQueue(): array
|
||||
{
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'pending' => DB::table('jobs')->count(),
|
||||
'failed' => DB::table('failed_jobs')->count(),
|
||||
];
|
||||
}
|
||||
|
||||
private function checkStorage(): array
|
||||
{
|
||||
$path = storage_path();
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'writable' => is_writable($path),
|
||||
'free_bytes' => disk_free_space($path),
|
||||
];
|
||||
}
|
||||
|
||||
private function dbSize(): string
|
||||
{
|
||||
try {
|
||||
if (config('database.default') === 'sqlite') {
|
||||
$size = filesize(database_path('database.sqlite'));
|
||||
return $this->formatBytes($size);
|
||||
}
|
||||
return '—';
|
||||
} catch (\Throwable) {
|
||||
return '—';
|
||||
}
|
||||
}
|
||||
|
||||
private function formatBytes(int $bytes): string
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
$i = floor(log($bytes, 1024));
|
||||
return round($bytes / (1024 ** $i), 2) . ' ' . $units[$i];
|
||||
}
|
||||
}
|
||||
83
app/Http/Controllers/Admin/LdapController.php
Normal file
83
app/Http/Controllers/Admin/LdapController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\LdapConfig;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class LdapController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Admin/Ldap', [
|
||||
'config' => LdapConfig::first(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'host' => 'required|string',
|
||||
'port' => 'required|integer',
|
||||
'use_ssl' => 'boolean',
|
||||
'use_tls' => 'boolean',
|
||||
'base_dn' => 'required|string',
|
||||
'bind_dn' => 'nullable|string',
|
||||
'bind_password' => 'nullable|string',
|
||||
'user_search_base' => 'nullable|string',
|
||||
'user_filter' => 'required|string',
|
||||
'admin_group_dn' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
|
||||
$config = LdapConfig::firstOrNew([]);
|
||||
$config->fill($validated);
|
||||
|
||||
if (empty($validated['bind_password'])) {
|
||||
unset($config->bind_password);
|
||||
}
|
||||
|
||||
$config->save();
|
||||
activity()->causedBy(auth()->user())->log('LDAP configuration updated');
|
||||
|
||||
return back()->with('success', 'LDAP configuration saved.');
|
||||
}
|
||||
|
||||
public function test(Request $request)
|
||||
{
|
||||
try {
|
||||
$config = $request->validate([
|
||||
'host' => 'required', 'port' => 'required',
|
||||
'bind_dn' => 'nullable', 'bind_password' => 'nullable',
|
||||
'base_dn' => 'required',
|
||||
]);
|
||||
|
||||
$conn = ldap_connect("ldap://{$config['host']}:{$config['port']}");
|
||||
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||
ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);
|
||||
ldap_set_option($conn, LDAP_OPT_TIMELIMIT, 5);
|
||||
|
||||
$bound = $config['bind_dn']
|
||||
? @ldap_bind($conn, $config['bind_dn'], $config['bind_password'])
|
||||
: @ldap_bind($conn);
|
||||
|
||||
ldap_close($conn);
|
||||
|
||||
if (!$bound) {
|
||||
return response()->json(['success' => false, 'message' => 'Bind failed. Check credentials.']);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'LDAP connection and bind successful!']);
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json(['success' => false, 'message' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
public function sync()
|
||||
{
|
||||
// Sync LDAP users to local database
|
||||
return back()->with('success', 'LDAP sync started.');
|
||||
}
|
||||
}
|
||||
27
app/Http/Controllers/Admin/SettingsController.php
Normal file
27
app/Http/Controllers/Admin/SettingsController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$settings = Setting::all()->mapWithKeys(fn($s) => [$s->key => $s->value]);
|
||||
return Inertia::render('Admin/Settings', ['settings' => $settings]);
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
foreach ($request->except('_token') as $key => $value) {
|
||||
Setting::set($key, $value);
|
||||
}
|
||||
|
||||
activity()->causedBy(auth()->user())->log('Settings updated');
|
||||
return back()->with('success', 'Settings saved.');
|
||||
}
|
||||
}
|
||||
21
app/Http/Controllers/Api/MetricsController.php
Normal file
21
app/Http/Controllers/Api/MetricsController.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class MetricsController extends Controller
|
||||
{
|
||||
public function live(string $target)
|
||||
{
|
||||
// Return real-time metric stream
|
||||
return response()->json([
|
||||
'cpu' => rand(10, 90),
|
||||
'memory' => rand(20, 85),
|
||||
'disk' => rand(5, 70),
|
||||
'net_rx' => rand(0, 500),
|
||||
'net_tx' => rand(0, 200),
|
||||
'ts' => now()->toIso8601String(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
15
app/Http/Controllers/Api/WebSocketController.php
Normal file
15
app/Http/Controllers/Api/WebSocketController.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\VirtualMachine;
|
||||
|
||||
class WebSocketController extends Controller
|
||||
{
|
||||
public function vmConsole(VirtualMachine $vm)
|
||||
{
|
||||
// Return WebSocket ticket for VM console
|
||||
return response()->json(['ticket' => base64_encode(random_bytes(32))]);
|
||||
}
|
||||
}
|
||||
83
app/Http/Controllers/Auth/AuthController.php
Normal file
83
app/Http/Controllers/Auth/AuthController.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function showLogin(): Response
|
||||
{
|
||||
return Inertia::render('Auth/Login', [
|
||||
'ldapEnabled' => config('ldap.auth.enabled', false),
|
||||
]);
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
// Rate limiting
|
||||
$key = 'login:' . $request->ip();
|
||||
if (RateLimiter::tooManyAttempts($key, 5)) {
|
||||
$seconds = RateLimiter::availableIn($key);
|
||||
return back()->withErrors([
|
||||
'email' => "Too many login attempts. Please try again in {$seconds} seconds.",
|
||||
]);
|
||||
}
|
||||
|
||||
$credentials = [
|
||||
'email' => $request->email,
|
||||
'password' => $request->password,
|
||||
];
|
||||
|
||||
if (!Auth::attempt($credentials, $request->boolean('remember'))) {
|
||||
RateLimiter::hit($key, 300);
|
||||
return back()->withErrors(['email' => 'Invalid credentials.']);
|
||||
}
|
||||
|
||||
RateLimiter::clear($key);
|
||||
|
||||
$user = Auth::user();
|
||||
if (!$user->is_active) {
|
||||
Auth::logout();
|
||||
return back()->withErrors(['email' => 'Your account has been disabled.']);
|
||||
}
|
||||
|
||||
$user->update([
|
||||
'last_login_at' => now(),
|
||||
'last_login_ip' => $request->ip(),
|
||||
]);
|
||||
|
||||
activity()
|
||||
->causedBy($user)
|
||||
->log('User logged in from ' . $request->ip());
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
activity()
|
||||
->causedBy(Auth::user())
|
||||
->log('User logged out');
|
||||
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login');
|
||||
}
|
||||
}
|
||||
52
app/Http/Controllers/Backup/BackupController.php
Normal file
52
app/Http/Controllers/Backup/BackupController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Backup;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Backup;
|
||||
use App\Models\BackupPolicy;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class BackupController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Backup/Index', [
|
||||
'backups' => Backup::orderByDesc('created_at')->paginate(30),
|
||||
'policies' => BackupPolicy::all(),
|
||||
'stats' => [
|
||||
'total' => Backup::count(),
|
||||
'success' => Backup::where('status', 'success')->count(),
|
||||
'failed' => Backup::where('status', 'failed')->count(),
|
||||
'total_size' => '—',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
return back()->with('success', 'Backup queued.');
|
||||
}
|
||||
|
||||
public function show(Backup $backup)
|
||||
{
|
||||
return Inertia::render('Backup/Show', ['backup' => $backup]);
|
||||
}
|
||||
|
||||
public function destroy(Backup $backup)
|
||||
{
|
||||
$backup->delete();
|
||||
return back()->with('success', 'Backup deleted.');
|
||||
}
|
||||
|
||||
public function schedules()
|
||||
{
|
||||
return Inertia::render('Backup/Schedules', ['policies' => BackupPolicy::all()]);
|
||||
}
|
||||
|
||||
public function storeSchedule(Request $request)
|
||||
{
|
||||
return back()->with('success', 'Schedule created.');
|
||||
}
|
||||
}
|
||||
19
app/Http/Controllers/Backup/RestoreController.php
Normal file
19
app/Http/Controllers/Backup/RestoreController.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Backup;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Backup;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RestoreController extends Controller
|
||||
{
|
||||
public function restore(Request $request, Backup $backup)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'restore_mode' => 'required|in:overwrite,new,test',
|
||||
]);
|
||||
// Dispatch restore job
|
||||
return back()->with('success', 'Restore initiated.');
|
||||
}
|
||||
}
|
||||
69
app/Http/Controllers/Cloud/CloudAccountController.php
Normal file
69
app/Http/Controllers/Cloud/CloudAccountController.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Cloud;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CloudAccount;
|
||||
use App\Models\CloudResource;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CloudAccountController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Cloud/Index', [
|
||||
'accounts' => CloudAccount::orderBy('name')->get(),
|
||||
'resources' => CloudResource::with('account')->orderByDesc('created_at')->paginate(30),
|
||||
'stats' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Cloud/AccountCreate');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'provider' => 'required|in:aws,alibaba,tencent,gcp,azure,digitalocean,vultr,hetzner',
|
||||
'region' => 'nullable|string',
|
||||
'credentials' => 'required|array',
|
||||
]);
|
||||
CloudAccount::create([...$validated, 'created_by' => auth()->id()]);
|
||||
return redirect()->route('cloud.index')->with('success', 'Cloud account added.');
|
||||
}
|
||||
|
||||
public function show(CloudAccount $account)
|
||||
{
|
||||
return Inertia::render('Cloud/AccountShow', [
|
||||
'account' => $account,
|
||||
'resources' => $account->resources()->paginate(30),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(CloudAccount $account)
|
||||
{
|
||||
return Inertia::render('Cloud/AccountEdit', ['account' => $account]);
|
||||
}
|
||||
|
||||
public function update(Request $request, CloudAccount $account)
|
||||
{
|
||||
$account->update($request->only('name', 'region', 'is_active'));
|
||||
return back()->with('success', 'Account updated.');
|
||||
}
|
||||
|
||||
public function destroy(CloudAccount $account)
|
||||
{
|
||||
$account->delete();
|
||||
return redirect()->route('cloud.index')->with('success', 'Account removed.');
|
||||
}
|
||||
|
||||
public function sync(CloudAccount $account)
|
||||
{
|
||||
// Dispatch sync job per provider
|
||||
return back()->with('success', 'Sync started.');
|
||||
}
|
||||
}
|
||||
22
app/Http/Controllers/Cloud/CloudResourceController.php
Normal file
22
app/Http/Controllers/Cloud/CloudResourceController.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Cloud;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CloudResource;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CloudResourceController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Cloud/Resources', [
|
||||
'resources' => CloudResource::with('account')->paginate(50),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(CloudResource $resource)
|
||||
{
|
||||
return Inertia::render('Cloud/ResourceShow', ['resource' => $resource->load('account')]);
|
||||
}
|
||||
}
|
||||
54
app/Http/Controllers/Container/ContainerController.php
Normal file
54
app/Http/Controllers/Container/ContainerController.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Container;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Container;
|
||||
use App\Models\DockerEndpoint;
|
||||
use App\Services\Container\DockerManager;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ContainerController extends Controller
|
||||
{
|
||||
public function __construct(private DockerManager $docker) {}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Container/Index', [
|
||||
'containers' => Container::with('endpoint')->orderBy('name')->paginate(50),
|
||||
'endpoints' => DockerEndpoint::orderBy('name')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Container $container)
|
||||
{
|
||||
return Inertia::render('Container/Show', ['container' => $container->load('endpoint')]);
|
||||
}
|
||||
|
||||
public function action(Request $request, Container $container)
|
||||
{
|
||||
$action = $request->validate(['action' => 'required|string'])['action'];
|
||||
$this->docker->action($container->endpoint, $container->container_id, $action);
|
||||
return back()->with('success', "Container {$action} executed.");
|
||||
}
|
||||
|
||||
public function logs(Container $container)
|
||||
{
|
||||
$logs = $this->docker->logs($container->endpoint, $container->container_id);
|
||||
return Inertia::render('Container/Logs', ['container' => $container, 'logs' => $logs]);
|
||||
}
|
||||
|
||||
public function stats(Container $container)
|
||||
{
|
||||
$stats = $this->docker->stats($container->endpoint, $container->container_id);
|
||||
return response()->json($stats);
|
||||
}
|
||||
|
||||
public function destroy(Container $container)
|
||||
{
|
||||
$this->docker->removeContainer($container->endpoint, $container->container_id, true);
|
||||
$container->delete();
|
||||
return redirect()->route('containers.index')->with('success', 'Container removed.');
|
||||
}
|
||||
}
|
||||
68
app/Http/Controllers/Container/StackController.php
Normal file
68
app/Http/Controllers/Container/StackController.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Container;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DockerEndpoint;
|
||||
use App\Models\Stack;
|
||||
use App\Services\Container\DockerManager;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class StackController extends Controller
|
||||
{
|
||||
public function __construct(private DockerManager $docker) {}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Container/Stacks', [
|
||||
'stacks' => Stack::with('endpoint')->orderBy('name')->paginate(20),
|
||||
'endpoints' => DockerEndpoint::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Container/StackCreate', ['endpoints' => DockerEndpoint::all()]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'endpoint_id' => 'required|exists:docker_endpoints,id',
|
||||
'compose_content' => 'required|string',
|
||||
'env_vars' => 'nullable|array',
|
||||
]);
|
||||
Stack::create([...$validated, 'created_by' => auth()->id()]);
|
||||
return redirect()->route('stacks.index')->with('success', 'Stack created.');
|
||||
}
|
||||
|
||||
public function show(Stack $stack)
|
||||
{
|
||||
return Inertia::render('Container/StackShow', ['stack' => $stack->load('endpoint')]);
|
||||
}
|
||||
|
||||
public function edit(Stack $stack)
|
||||
{
|
||||
return Inertia::render('Container/StackEdit', ['stack' => $stack, 'endpoints' => DockerEndpoint::all()]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Stack $stack)
|
||||
{
|
||||
$stack->update($request->only('name', 'compose_content', 'env_vars'));
|
||||
return redirect()->route('stacks.show', $stack)->with('success', 'Stack updated.');
|
||||
}
|
||||
|
||||
public function destroy(Stack $stack)
|
||||
{
|
||||
$stack->delete();
|
||||
return redirect()->route('stacks.index')->with('success', 'Stack deleted.');
|
||||
}
|
||||
|
||||
public function deploy(Stack $stack)
|
||||
{
|
||||
$this->docker->deployStack($stack->endpoint, $stack->name, $stack->compose_content, $stack->env_vars ?? []);
|
||||
return back()->with('success', 'Stack deployed.');
|
||||
}
|
||||
}
|
||||
68
app/Http/Controllers/DashboardController.php
Normal file
68
app/Http/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Alert;
|
||||
use App\Models\CloudAccount;
|
||||
use App\Models\Container;
|
||||
use App\Models\DockerEndpoint;
|
||||
use App\Models\Hypervisor;
|
||||
use App\Models\SecurityScore;
|
||||
use App\Models\VirtualMachine;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index(): Response
|
||||
{
|
||||
$hypervisors = Hypervisor::select('id', 'name', 'type', 'status', 'last_sync_at')->get();
|
||||
$vmStats = VirtualMachine::selectRaw('
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN status = "running" THEN 1 ELSE 0 END) as running,
|
||||
SUM(CASE WHEN status = "stopped" THEN 1 ELSE 0 END) as stopped,
|
||||
AVG(cpu_usage) as avg_cpu,
|
||||
AVG(memory_usage) as avg_memory
|
||||
')->first();
|
||||
|
||||
$containerStats = Container::selectRaw('
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN state = "running" THEN 1 ELSE 0 END) as running,
|
||||
SUM(CASE WHEN state = "exited" THEN 1 ELSE 0 END) as stopped
|
||||
')->first();
|
||||
|
||||
$endpoints = DockerEndpoint::select('id', 'name', 'type', 'status')->get();
|
||||
$cloudAccounts = CloudAccount::select('id', 'name', 'provider', 'status')->get();
|
||||
$criticalAlerts = Alert::where('status', 'firing')->where('severity', 'critical')->count();
|
||||
$warningAlerts = Alert::where('status', 'firing')->where('severity', 'warning')->count();
|
||||
$recentAlerts = Alert::with('rule')
|
||||
->where('status', 'firing')
|
||||
->orderByDesc('created_at')
|
||||
->limit(5)
|
||||
->get();
|
||||
|
||||
$avgSecurityScore = SecurityScore::avg('score') ?? 0;
|
||||
|
||||
return Inertia::render('Dashboard/Index', [
|
||||
'stats' => [
|
||||
'hypervisors' => $hypervisors->count(),
|
||||
'hypervisors_online' => $hypervisors->where('status', 'online')->count(),
|
||||
'vms' => (int) ($vmStats->total ?? 0),
|
||||
'vms_running' => (int) ($vmStats->running ?? 0),
|
||||
'vms_stopped' => (int) ($vmStats->stopped ?? 0),
|
||||
'avg_cpu' => round((float) ($vmStats->avg_cpu ?? 0), 1),
|
||||
'avg_memory' => round((float) ($vmStats->avg_memory ?? 0), 1),
|
||||
'containers' => (int) ($containerStats->total ?? 0),
|
||||
'containers_running' => (int) ($containerStats->running ?? 0),
|
||||
'cloud_accounts' => $cloudAccounts->count(),
|
||||
'critical_alerts' => $criticalAlerts,
|
||||
'warning_alerts' => $warningAlerts,
|
||||
'security_score' => round($avgSecurityScore, 1),
|
||||
],
|
||||
'hypervisors' => $hypervisors,
|
||||
'endpoints' => $endpoints,
|
||||
'cloud_accounts' => $cloudAccounts,
|
||||
'recent_alerts' => $recentAlerts,
|
||||
]);
|
||||
}
|
||||
}
|
||||
121
app/Http/Controllers/Hypervisor/HypervisorController.php
Normal file
121
app/Http/Controllers/Hypervisor/HypervisorController.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Hypervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Hypervisor;
|
||||
use App\Services\Hypervisor\HypervisorManager;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class HypervisorController extends Controller
|
||||
{
|
||||
public function __construct(private HypervisorManager $manager) {}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$hypervisors = Hypervisor::withCount('virtualMachines')
|
||||
->orderByDesc('created_at')
|
||||
->paginate(20);
|
||||
|
||||
return Inertia::render('Hypervisor/Index', ['hypervisors' => $hypervisors]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Hypervisor/Create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'type' => 'required|in:vcenter,esxi,proxmox,hyper-v,xen,kvm',
|
||||
'host' => 'required|string',
|
||||
'port' => 'required|integer|between:1,65535',
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'ssl_verify' => 'boolean',
|
||||
'datacenter' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$hypervisor = Hypervisor::create([
|
||||
...$validated,
|
||||
'created_by' => auth()->id(),
|
||||
'status' => 'unknown',
|
||||
]);
|
||||
|
||||
activity()->causedBy(auth()->user())->performedOn($hypervisor)->log('Hypervisor created');
|
||||
|
||||
return redirect()->route('hypervisors.show', $hypervisor)
|
||||
->with('success', 'Hypervisor added successfully.');
|
||||
}
|
||||
|
||||
public function show(Hypervisor $hypervisor)
|
||||
{
|
||||
$hypervisor->load('virtualMachines');
|
||||
$vmStats = $hypervisor->virtualMachines()
|
||||
->selectRaw('status, COUNT(*) as count')
|
||||
->groupBy('status')
|
||||
->pluck('count', 'status');
|
||||
|
||||
return Inertia::render('Hypervisor/Show', [
|
||||
'hypervisor' => $hypervisor,
|
||||
'vm_stats' => $vmStats,
|
||||
'vms' => $hypervisor->virtualMachines()->orderBy('name')->paginate(50),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(Hypervisor $hypervisor)
|
||||
{
|
||||
return Inertia::render('Hypervisor/Edit', ['hypervisor' => $hypervisor->makeVisible('password')]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Hypervisor $hypervisor)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'host' => 'required|string',
|
||||
'port' => 'required|integer|between:1,65535',
|
||||
'username' => 'required|string',
|
||||
'password' => 'nullable|string',
|
||||
'ssl_verify' => 'boolean',
|
||||
'datacenter' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if (empty($validated['password'])) {
|
||||
unset($validated['password']);
|
||||
}
|
||||
|
||||
$hypervisor->update($validated);
|
||||
activity()->causedBy(auth()->user())->performedOn($hypervisor)->log('Hypervisor updated');
|
||||
|
||||
return redirect()->route('hypervisors.show', $hypervisor)
|
||||
->with('success', 'Hypervisor updated.');
|
||||
}
|
||||
|
||||
public function destroy(Hypervisor $hypervisor)
|
||||
{
|
||||
$hypervisor->delete();
|
||||
activity()->causedBy(auth()->user())->log("Hypervisor '{$hypervisor->name}' deleted");
|
||||
|
||||
return redirect()->route('hypervisors.index')->with('success', 'Hypervisor removed.');
|
||||
}
|
||||
|
||||
public function sync(Hypervisor $hypervisor)
|
||||
{
|
||||
try {
|
||||
$this->manager->driver($hypervisor)->sync();
|
||||
return back()->with('success', 'Sync completed successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
$hypervisor->update(['status' => 'error', 'last_error' => $e->getMessage()]);
|
||||
return back()->with('error', 'Sync failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnection(Request $request, Hypervisor $hypervisor)
|
||||
{
|
||||
$result = $this->manager->testConnection($hypervisor);
|
||||
return response()->json($result);
|
||||
}
|
||||
}
|
||||
77
app/Http/Controllers/Hypervisor/TemplateController.php
Normal file
77
app/Http/Controllers/Hypervisor/TemplateController.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Hypervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Hypervisor;
|
||||
use App\Models\VmTemplate;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class TemplateController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Hypervisor/Templates', [
|
||||
'templates' => VmTemplate::with('hypervisor')->orderBy('name')->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Hypervisor/TemplateCreate', [
|
||||
'hypervisors' => Hypervisor::where('status', 'online')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:100',
|
||||
'hypervisor_id' => 'nullable|exists:hypervisors,id',
|
||||
'os_type' => 'required|string',
|
||||
'cpu_count' => 'integer|min:1',
|
||||
'memory_mb' => 'integer|min:512',
|
||||
'disk_gb' => 'integer|min:1',
|
||||
'customization' => 'nullable|array',
|
||||
'provision_script' => 'nullable|string',
|
||||
]);
|
||||
VmTemplate::create([...$validated, 'created_by' => auth()->id()]);
|
||||
return redirect()->route('templates.index')->with('success', 'Template created.');
|
||||
}
|
||||
|
||||
public function show(VmTemplate $template)
|
||||
{
|
||||
return Inertia::render('Hypervisor/TemplateShow', ['template' => $template->load('hypervisor')]);
|
||||
}
|
||||
|
||||
public function edit(VmTemplate $template)
|
||||
{
|
||||
return Inertia::render('Hypervisor/TemplateEdit', [
|
||||
'template' => $template,
|
||||
'hypervisors' => Hypervisor::where('status', 'online')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, VmTemplate $template)
|
||||
{
|
||||
$template->update($request->except('_token'));
|
||||
return redirect()->route('templates.show', $template)->with('success', 'Template updated.');
|
||||
}
|
||||
|
||||
public function destroy(VmTemplate $template)
|
||||
{
|
||||
$template->delete();
|
||||
return redirect()->route('templates.index')->with('success', 'Template deleted.');
|
||||
}
|
||||
|
||||
public function deploy(Request $request, VmTemplate $template)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'vm_name' => 'required|string',
|
||||
'hypervisor_id' => 'required|exists:hypervisors,id',
|
||||
]);
|
||||
// Dispatch deploy job
|
||||
return back()->with('success', 'Deployment queued.');
|
||||
}
|
||||
}
|
||||
60
app/Http/Controllers/Hypervisor/VirtualMachineController.php
Normal file
60
app/Http/Controllers/Hypervisor/VirtualMachineController.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Hypervisor;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\VirtualMachine;
|
||||
use App\Services\Hypervisor\HypervisorManager;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class VirtualMachineController extends Controller
|
||||
{
|
||||
public function __construct(private HypervisorManager $manager) {}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Hypervisor/VMs', [
|
||||
'vms' => VirtualMachine::with('hypervisor')->orderBy('name')->paginate(50),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(VirtualMachine $vm)
|
||||
{
|
||||
return Inertia::render('Hypervisor/VMShow', ['vm' => $vm->load('hypervisor')]);
|
||||
}
|
||||
|
||||
public function power(Request $request, VirtualMachine $vm)
|
||||
{
|
||||
$action = $request->validate(['action' => 'required|in:start,stop,reboot,suspend'])['action'];
|
||||
$driver = $this->manager->driver($vm->hypervisor);
|
||||
|
||||
match ($action) {
|
||||
'start' => $driver->powerOn($vm->remote_id),
|
||||
'stop' => $driver->powerOff($vm->remote_id),
|
||||
'reboot' => $driver->reboot($vm->remote_id),
|
||||
'suspend' => $driver->suspend($vm->remote_id),
|
||||
};
|
||||
|
||||
return back()->with('success', "VM {$action} initiated.");
|
||||
}
|
||||
|
||||
public function snapshot(Request $request, VirtualMachine $vm)
|
||||
{
|
||||
$data = $request->validate(['name' => 'required|string', 'description' => 'nullable|string']);
|
||||
$this->manager->driver($vm->hypervisor)->createSnapshot($vm->remote_id, $data['name'], $data['description'] ?? '');
|
||||
return back()->with('success', 'Snapshot created.');
|
||||
}
|
||||
|
||||
public function clone(Request $request, VirtualMachine $vm)
|
||||
{
|
||||
$data = $request->validate(['name' => 'required|string']);
|
||||
$this->manager->driver($vm->hypervisor)->cloneVM($vm->remote_id, $data['name'], []);
|
||||
return back()->with('success', 'Clone initiated.');
|
||||
}
|
||||
|
||||
public function migrate(Request $request, VirtualMachine $vm)
|
||||
{
|
||||
return back()->with('info', 'Migration not yet implemented for this hypervisor type.');
|
||||
}
|
||||
}
|
||||
69
app/Http/Controllers/Identity/CertificateController.php
Normal file
69
app/Http/Controllers/Identity/CertificateController.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Identity;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Certificate;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CertificateController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Identity/Certificates', [
|
||||
'certificates' => Certificate::orderBy('expires_at')->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string',
|
||||
'domain' => 'required|string',
|
||||
'certificate' => 'required|string',
|
||||
'private_key' => 'required|string',
|
||||
'type' => 'required|in:self-signed,lets-encrypt,custom',
|
||||
'expires_at' => 'required|date',
|
||||
]);
|
||||
Certificate::create([...$validated, 'status' => 'valid', 'created_by' => auth()->id()]);
|
||||
return back()->with('success', 'Certificate imported.');
|
||||
}
|
||||
|
||||
public function show(Certificate $cert)
|
||||
{
|
||||
return Inertia::render('Identity/CertShow', ['cert' => $cert]);
|
||||
}
|
||||
|
||||
public function destroy(Certificate $cert)
|
||||
{
|
||||
$cert->delete();
|
||||
return back()->with('success', 'Certificate deleted.');
|
||||
}
|
||||
|
||||
public function generate(Request $request)
|
||||
{
|
||||
$data = $request->validate(['domain' => 'required|string', 'days' => 'integer|min:1|max:3650']);
|
||||
$days = $data['days'] ?? 365;
|
||||
|
||||
$privateKey = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
|
||||
$csr = openssl_csr_new(['CN' => $data['domain']], $privateKey);
|
||||
$cert = openssl_csr_sign($csr, null, $privateKey, $days);
|
||||
|
||||
openssl_x509_export($cert, $certOut);
|
||||
openssl_pkey_export($privateKey, $keyOut);
|
||||
|
||||
Certificate::create([
|
||||
'name' => $data['domain'],
|
||||
'domain' => $data['domain'],
|
||||
'certificate' => $certOut,
|
||||
'private_key' => $keyOut,
|
||||
'type' => 'self-signed',
|
||||
'expires_at' => now()->addDays($days),
|
||||
'status' => 'valid',
|
||||
'created_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return back()->with('success', 'Self-signed certificate generated.');
|
||||
}
|
||||
}
|
||||
60
app/Http/Controllers/Identity/PasswordController.php
Normal file
60
app/Http/Controllers/Identity/PasswordController.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Identity;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PasswordEntry;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Identity/Index', [
|
||||
'passwords' => auth()->user()->passwordEntries()->orderBy('title')->get(),
|
||||
'certificates' => [],
|
||||
'ssh_keys' => [],
|
||||
'stats' => [
|
||||
'passwords' => auth()->user()->passwordEntries()->count(),
|
||||
'certs' => 0,
|
||||
'expiring' => 0,
|
||||
'ssh_keys' => 0,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string',
|
||||
'username' => 'nullable|string',
|
||||
'password' => 'required|string',
|
||||
'url' => 'nullable|url',
|
||||
'notes' => 'nullable|string',
|
||||
'category' => 'nullable|string',
|
||||
]);
|
||||
auth()->user()->passwordEntries()->create($validated);
|
||||
return back()->with('success', 'Password entry created.');
|
||||
}
|
||||
|
||||
public function reveal(PasswordEntry $password)
|
||||
{
|
||||
$this->authorize('view', $password);
|
||||
return response()->json(['password' => $password->getDecryptedPassword()]);
|
||||
}
|
||||
|
||||
public function update(Request $request, PasswordEntry $password)
|
||||
{
|
||||
$this->authorize('update', $password);
|
||||
$password->update($request->except('_token'));
|
||||
return back()->with('success', 'Entry updated.');
|
||||
}
|
||||
|
||||
public function destroy(PasswordEntry $password)
|
||||
{
|
||||
$this->authorize('delete', $password);
|
||||
$password->delete();
|
||||
return back()->with('success', 'Entry deleted.');
|
||||
}
|
||||
}
|
||||
58
app/Http/Controllers/Monitoring/MonitoringController.php
Normal file
58
app/Http/Controllers/Monitoring/MonitoringController.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Monitoring;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Alert;
|
||||
use App\Models\AlertRule;
|
||||
use App\Models\Metric;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class MonitoringController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Monitoring/Index', [
|
||||
'alerts' => Alert::with('rule')->orderByDesc('created_at')->limit(50)->get(),
|
||||
'alert_stats' => [
|
||||
'total_rules' => AlertRule::count(),
|
||||
'resolved_24h' => Alert::where('status', 'resolved')->where('resolved_at', '>=', now()->subDay())->count(),
|
||||
],
|
||||
'dashboards' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function metrics(string $target)
|
||||
{
|
||||
[$type, $id] = explode(':', $target, 2);
|
||||
$metrics = Metric::where('target_type', $type)
|
||||
->where('target_id', $id)
|
||||
->where('recorded_at', '>=', now()->subHour())
|
||||
->orderBy('recorded_at')
|
||||
->get();
|
||||
|
||||
return response()->json($metrics);
|
||||
}
|
||||
|
||||
public function alerts()
|
||||
{
|
||||
return Inertia::render('Monitoring/Alerts', [
|
||||
'rules' => AlertRule::orderByDesc('created_at')->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
public function acknowledge(Alert $alert)
|
||||
{
|
||||
$alert->update([
|
||||
'status' => 'acknowledged',
|
||||
'acknowledged_by' => auth()->id(),
|
||||
'acknowledged_at' => now(),
|
||||
]);
|
||||
return back()->with('success', 'Alert acknowledged.');
|
||||
}
|
||||
|
||||
public function dashboards()
|
||||
{
|
||||
return Inertia::render('Monitoring/Dashboards');
|
||||
}
|
||||
}
|
||||
44
app/Http/Controllers/Security/ScanController.php
Normal file
44
app/Http/Controllers/Security/ScanController.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Security;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\SecurityScan;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ScanController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Security/Scans', [
|
||||
'scans' => SecurityScan::orderByDesc('created_at')->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'target_type' => 'required|string',
|
||||
'target_id' => 'required|integer',
|
||||
'scan_type' => 'required|string',
|
||||
]);
|
||||
|
||||
SecurityScan::create([
|
||||
...$validated,
|
||||
'target_name' => 'Scan Target',
|
||||
'status' => 'queued',
|
||||
'initiated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
// Dispatch scan job
|
||||
// \App\Jobs\RunSecurityScan::dispatch($scan);
|
||||
|
||||
return back()->with('success', 'Scan queued.');
|
||||
}
|
||||
|
||||
public function show(SecurityScan $scan)
|
||||
{
|
||||
return Inertia::render('Security/ScanShow', ['scan' => $scan]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Security;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\SecurityScan;
|
||||
use App\Models\SecurityScore;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class SecurityDashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$scans = SecurityScan::orderByDesc('created_at')->paginate(15);
|
||||
$scores = SecurityScore::selectRaw('AVG(score) as avg, MIN(score) as min, MAX(score) as max')->first();
|
||||
$stats = SecurityScan::whereIn('status', ['completed'])->selectRaw('
|
||||
SUM(critical_count) as critical,
|
||||
SUM(high_count) as high,
|
||||
SUM(medium_count) as medium,
|
||||
SUM(low_count) as low
|
||||
')->first();
|
||||
|
||||
return Inertia::render('Security/Index', [
|
||||
'scans' => $scans,
|
||||
'scores' => $scores,
|
||||
'stats' => $stats,
|
||||
]);
|
||||
}
|
||||
|
||||
public function vulnerabilities()
|
||||
{
|
||||
return Inertia::render('Security/Vulnerabilities');
|
||||
}
|
||||
|
||||
public function compliance()
|
||||
{
|
||||
return Inertia::render('Security/Compliance');
|
||||
}
|
||||
}
|
||||
96
app/Http/Controllers/Wiki/DocumentController.php
Normal file
96
app/Http/Controllers/Wiki/DocumentController.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Wiki;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\WikiPage;
|
||||
use App\Models\WikiSpace;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
|
||||
class DocumentController extends Controller
|
||||
{
|
||||
private CommonMarkConverter $markdown;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->markdown = new CommonMarkConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Wiki/PageCreate', [
|
||||
'spaces' => WikiSpace::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'space_id' => 'required|exists:wiki_spaces,id',
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string',
|
||||
'parent_id' => 'nullable|exists:wiki_pages,id',
|
||||
'tags' => 'nullable|array',
|
||||
]);
|
||||
|
||||
$slug = \Str::slug($validated['title']);
|
||||
$page = WikiPage::create([
|
||||
...$validated,
|
||||
'slug' => $slug,
|
||||
'content_html' => $this->markdown->convert($validated['content'])->getContent(),
|
||||
'created_by' => auth()->id(),
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return redirect()->route('wiki.pages.show', $page)->with('success', 'Page created.');
|
||||
}
|
||||
|
||||
public function show(WikiPage $page)
|
||||
{
|
||||
return Inertia::render('Wiki/PageShow', [
|
||||
'page' => $page->load('space', 'createdBy', 'updatedBy'),
|
||||
'children' => $page->children()->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(WikiPage $page)
|
||||
{
|
||||
return Inertia::render('Wiki/PageEdit', [
|
||||
'page' => $page,
|
||||
'spaces' => WikiSpace::all(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, WikiPage $page)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string',
|
||||
]);
|
||||
|
||||
// Save revision
|
||||
$page->revisions()->create([
|
||||
'content' => $page->content,
|
||||
'created_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
$page->update([
|
||||
...$validated,
|
||||
'content_html' => $this->markdown->convert($validated['content'])->getContent(),
|
||||
'updated_by' => auth()->id(),
|
||||
]);
|
||||
|
||||
return redirect()->route('wiki.pages.show', $page)->with('success', 'Page updated.');
|
||||
}
|
||||
|
||||
public function destroy(WikiPage $page)
|
||||
{
|
||||
$page->delete();
|
||||
return redirect()->route('wiki.index')->with('success', 'Page deleted.');
|
||||
}
|
||||
}
|
||||
54
app/Http/Controllers/Wiki/WikiController.php
Normal file
54
app/Http/Controllers/Wiki/WikiController.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Wiki;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\WikiPage;
|
||||
use App\Models\WikiSpace;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class WikiController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Wiki/Index', [
|
||||
'spaces' => WikiSpace::withCount('pages')->get(),
|
||||
'recent_pages' => WikiPage::with('space')->orderByDesc('updated_at')->limit(20)->get(),
|
||||
'files' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function spaces()
|
||||
{
|
||||
return Inertia::render('Wiki/Spaces', [
|
||||
'spaces' => WikiSpace::withCount('pages')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function space(WikiSpace $space)
|
||||
{
|
||||
return Inertia::render('Wiki/Space', [
|
||||
'space' => $space->load('pages'),
|
||||
'pages' => $space->pages()->orderBy('order')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function files()
|
||||
{
|
||||
return Inertia::render('Wiki/Files');
|
||||
}
|
||||
|
||||
public function uploadFile(Request $request)
|
||||
{
|
||||
$request->validate(['file' => 'required|file|max:102400']);
|
||||
$path = $request->file('file')->store('wiki-files');
|
||||
return back()->with('success', 'File uploaded.');
|
||||
}
|
||||
|
||||
public function deleteFile(string $file)
|
||||
{
|
||||
\Storage::delete('wiki-files/' . $file);
|
||||
return back()->with('success', 'File deleted.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user