diff --git a/.gitignore b/.gitignore index c7cf1fa..40c3749 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ yarn-error.log /.nova /.vscode /.zed +/public diff --git a/app/Http/Controllers/AcceptanceLetterController.php b/app/Http/Controllers/AcceptanceLetterController.php deleted file mode 100644 index 580d574..0000000 --- a/app/Http/Controllers/AcceptanceLetterController.php +++ /dev/null @@ -1,87 +0,0 @@ -json($letters); - } - - /** - * Menyimpan data baru (POST /acceptance_letters). - */ - public function store(Request $request) - { - $request->validate([ - 'nomor_surat' => 'required', - 'nama_mahasiswa' => 'required', - 'nomor_induk_mahasiswa' => 'required', - 'program_studi_mahasiswa' => 'required', - 'tahun_ajaran' => 'required', - 'start_date' => 'required|date', - ]); - - $letter = AcceptanceLetter::create($request->all()); - - return response()->json([ - 'message' => 'Acceptance Letter berhasil disimpan.', - 'data' => $letter - ], 201); - } - - /** - * Menampilkan detail data (GET /acceptance_letters/{id}). - */ - public function show($id) - { - $letter = AcceptanceLetter::findOrFail($id); - return response()->json($letter); - } - - /** - * Mengupdate data (PUT/PATCH /acceptance_letters/{id}). - */ - public function update(Request $request, $id) - { - $letter = AcceptanceLetter::findOrFail($id); - - $request->validate([ - 'nomor_surat' => 'required', - 'nama_mahasiswa' => 'required', - 'nomor_induk_mahasiswa' => 'required', - 'program_studi_mahasiswa' => 'required', - 'tahun_ajaran' => 'required', - 'start_date' => 'required|date', - 'lampiran' => 'nullable' - ]); - - $letter->update($request->all()); - - return response()->json([ - 'message' => 'Acceptance Letter berhasil diupdate.', - 'data' => $letter - ]); - } - - /** - * Menghapus data (DELETE /acceptance_letters/{id}). - */ - public function destroy($id) - { - $letter = AcceptanceLetter::findOrFail($id); - $letter->delete(); - - return response()->json([ - 'message' => 'Acceptance Letter berhasil dihapus.' - ]); - } -} diff --git a/app/Http/Controllers/AdminMonitoringController.php b/app/Http/Controllers/AdminMonitoringController.php new file mode 100644 index 0000000..42597f7 --- /dev/null +++ b/app/Http/Controllers/AdminMonitoringController.php @@ -0,0 +1,44 @@ +validate([ + 'email' => ['required', 'email'], + 'password' => ['required'], + ]); + + // Ubah dari 'monitor' ke 'monitoring' sesuai config/auth.php + if (Auth::guard('monitoring')->attempt($credentials, $request->filled('remember'))) { + $request->session()->regenerate(); + + return redirect()->intended('/pulse'); + } + + return back()->withErrors([ + 'email' => 'Email atau password salah.', + ])->onlyInput('email'); + } + + public function logout(Request $request) + { + // Ubah dari 'monitor' ke 'monitoring' + Auth::guard('monitoring')->logout(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return redirect('/monitoring/login'); + } +} diff --git a/app/Http/Controllers/CompletionLetterController.php b/app/Http/Controllers/CompletionLetterController.php deleted file mode 100644 index 058aae7..0000000 --- a/app/Http/Controllers/CompletionLetterController.php +++ /dev/null @@ -1,62 +0,0 @@ -json($letters); - } - - public function store(Request $request) - { - $request->validate([ - 'nomor_surat' => 'required', - 'nama_mahasiswa' => 'required', - 'nomor_induk_mahasiswa' => 'required', - 'program_studi_mahasiswa' => 'required', - 'tahun_ajaran' => 'required', - 'completion_date' => 'required|date', - 'lampiran' => 'nullable' - ]); - - $letter = CompletionLetter::create($request->all()); - - return response()->json([ - 'message' => 'Completion Letter berhasil disimpan.', - 'data' => $letter - ], 201); - } - - public function show($id) - { - $letter = CompletionLetter::findOrFail($id); - return response()->json($letter); - } - - public function update(Request $request, $id) - { - $letter = CompletionLetter::findOrFail($id); - $letter->update($request->all()); - - return response()->json([ - 'message' => 'Completion Letter berhasil diupdate.', - 'data' => $letter - ]); - } - - public function destroy($id) - { - $letter = CompletionLetter::findOrFail($id); - $letter->delete(); - - return response()->json([ - 'message' => 'Completion Letter berhasil dihapus.' - ]); - } -} diff --git a/app/Models/AcceptanceLetter.php b/app/Models/AcceptanceLetter.php deleted file mode 100644 index 4168ec8..0000000 --- a/app/Models/AcceptanceLetter.php +++ /dev/null @@ -1,23 +0,0 @@ - 'datetime', + 'password' => 'hashed', + ]; +} diff --git a/app/Models/CompletionLetter.php b/app/Models/CompletionLetter.php deleted file mode 100644 index de973ef..0000000 --- a/app/Models/CompletionLetter.php +++ /dev/null @@ -1,23 +0,0 @@ -=13.0.0", - "phpunit/phpunit": "<11.5.3 || >=12.0.0" + "laravel/framework": "<11.44.2 || >=13.0.0", + "phpunit/phpunit": "<11.5.15 || >=13.0.0" }, "require-dev": { - "larastan/larastan": "^2.9.12", - "laravel/framework": "^11.39.1", - "laravel/pint": "^1.20.0", - "laravel/sail": "^1.40.0", - "laravel/sanctum": "^4.0.7", - "laravel/tinker": "^2.10.0", - "orchestra/testbench-core": "^9.9.2", - "pestphp/pest": "^3.7.3", - "sebastian/environment": "^6.1.0 || ^7.2.0" + "brianium/paratest": "^7.8.3", + "larastan/larastan": "^3.2", + "laravel/framework": "^11.44.2 || ^12.6", + "laravel/pint": "^1.21.2", + "laravel/sail": "^1.41.0", + "laravel/sanctum": "^4.0.8", + "laravel/tinker": "^2.10.1", + "orchestra/testbench-core": "^9.12.0 || ^10.1", + "pestphp/pest": "^3.8.0", + "sebastian/environment": "^7.2.0 || ^8.0" }, "type": "library", "extra": { @@ -6665,7 +6885,7 @@ "type": "patreon" } ], - "time": "2025-01-23T13:41:43+00:00" + "time": "2025-04-03T14:33:09+00:00" }, { "name": "phar-io/manifest", @@ -7110,16 +7330,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.11", + "version": "11.5.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3946ac38410be7440186c6e74584f31b15107fc7" + "reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3946ac38410be7440186c6e74584f31b15107fc7", - "reference": "3946ac38410be7440186c6e74584f31b15107fc7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fd2e863a2995cdfd864fb514b5e0b28b09895b5c", + "reference": "fd2e863a2995cdfd864fb514b5e0b28b09895b5c", "shasum": "" }, "require": { @@ -7139,14 +7359,14 @@ "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.2", - "sebastian/comparator": "^6.3.0", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.0", + "sebastian/type": "^5.1.2", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" }, @@ -7191,7 +7411,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.17" }, "funding": [ { @@ -7207,7 +7427,7 @@ "type": "tidelift" } ], - "time": "2025-03-05T07:36:02+00:00" + "time": "2025-04-08T07:59:11+00:00" }, { "name": "sebastian/cli-parser", @@ -7268,16 +7488,16 @@ }, { "name": "sebastian/code-unit", - "version": "3.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", - "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { @@ -7313,7 +7533,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { @@ -7321,7 +7541,7 @@ "type": "github" } ], - "time": "2024-12-12T09:59:06+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -7381,16 +7601,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.0", + "version": "6.3.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115" + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115", - "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", "shasum": "" }, "require": { @@ -7409,7 +7629,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.2-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -7449,7 +7669,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" }, "funding": [ { @@ -7457,7 +7677,7 @@ "type": "github" } ], - "time": "2025-01-06T10:28:19+00:00" + "time": "2025-03-07T06:57:01+00:00" }, { "name": "sebastian/complexity", @@ -8026,16 +8246,16 @@ }, { "name": "sebastian/type", - "version": "5.1.0", + "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", - "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", "shasum": "" }, "require": { @@ -8071,7 +8291,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" }, "funding": [ { @@ -8079,7 +8299,7 @@ "type": "github" } ], - "time": "2024-09-17T13:12:04+00:00" + "time": "2025-03-18T13:35:50+00:00" }, { "name": "sebastian/version", @@ -8189,16 +8409,16 @@ }, { "name": "symfony/yaml", - "version": "v7.2.3", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ac238f173df0c9c1120f862d0f599e17535a87ec" + "reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ac238f173df0c9c1120f862d0f599e17535a87ec", - "reference": "ac238f173df0c9c1120f862d0f599e17535a87ec", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912", + "reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912", "shasum": "" }, "require": { @@ -8241,7 +8461,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.2.3" + "source": "https://github.com/symfony/yaml/tree/v7.2.5" }, "funding": [ { @@ -8257,7 +8477,7 @@ "type": "tidelift" } ], - "time": "2025-01-07T12:55:42+00:00" + "time": "2025-03-03T07:12:39+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/auth.php b/config/auth.php index 0ba5d5d..6f22cfe 100644 --- a/config/auth.php +++ b/config/auth.php @@ -40,6 +40,10 @@ return [ 'driver' => 'session', 'provider' => 'users', ], + 'monitoring' => [ + 'driver' => 'session', + 'provider' => 'admins', + ], ], /* @@ -64,6 +68,10 @@ return [ 'driver' => 'eloquent', 'model' => env('AUTH_MODEL', App\Models\User::class), ], + 'admins' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_ADMIN_MODEL', App\Models\Admin::class), + ], // 'users' => [ // 'driver' => 'database', diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000..6dfaa92 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,36 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + + 'allowed_origins' => [ + 'https://cetaksuratkp.humicprototyping.com', // domain frontend kamu + 'http://localhost:3000', // kalau develop lokal, bisa dihapus kalau udah produksi + ], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => true, +]; diff --git a/config/pulse.php b/config/pulse.php new file mode 100644 index 0000000..1edfa4a --- /dev/null +++ b/config/pulse.php @@ -0,0 +1,236 @@ + env('PULSE_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Pulse Path + |-------------------------------------------------------------------------- + | + | This is the path which the Pulse dashboard will be accessible from. Feel + | free to change this path to anything you'd like. Note that this won't + | affect the path of the internal API that is never exposed to users. + | + */ + + 'path' => env('PULSE_PATH', 'pulse'), + + /* + |-------------------------------------------------------------------------- + | Pulse Master Switch + |-------------------------------------------------------------------------- + | + | This configuration option may be used to completely disable all Pulse + | data recorders regardless of their individual configurations. This + | provides a single option to quickly disable all Pulse recording. + | + */ + + 'enabled' => env('PULSE_ENABLED', true), + + /* + |-------------------------------------------------------------------------- + | Pulse Storage Driver + |-------------------------------------------------------------------------- + | + | This configuration option determines which storage driver will be used + | while storing entries from Pulse's recorders. In addition, you also + | may provide any options to configure the selected storage driver. + | + */ + + 'storage' => [ + 'driver' => env('PULSE_STORAGE_DRIVER', 'database'), + + 'trim' => [ + 'keep' => env('PULSE_STORAGE_KEEP', '7 days'), + ], + + 'database' => [ + 'connection' => env('PULSE_DB_CONNECTION'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Ingest Driver + |-------------------------------------------------------------------------- + | + | This configuration options determines the ingest driver that will be used + | to capture entries from Pulse's recorders. Ingest drivers are great to + | free up your request workers quickly by offloading the data storage. + | + */ + + 'ingest' => [ + 'driver' => env('PULSE_INGEST_DRIVER', 'storage'), + + 'buffer' => env('PULSE_INGEST_BUFFER', 5_000), + + 'trim' => [ + 'lottery' => [1, 1_000], + 'keep' => env('PULSE_INGEST_KEEP', '7 days'), + ], + + 'redis' => [ + 'connection' => env('PULSE_REDIS_CONNECTION'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Cache Driver + |-------------------------------------------------------------------------- + | + | This configuration option determines the cache driver that will be used + | for various tasks, including caching dashboard results, establishing + | locks for events that should only occur on one server and signals. + | + */ + + 'cache' => env('PULSE_CACHE_DRIVER'), + + /* + |-------------------------------------------------------------------------- + | Pulse Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will be assigned to every Pulse route, giving you the + | chance to add your own middleware to this list or change any of the + | existing middleware. Of course, reasonable defaults are provided. + | + */ + + 'middleware' => [ + 'web', + Authorize::class, + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Recorders + |-------------------------------------------------------------------------- + | + | The following array lists the "recorders" that will be registered with + | Pulse, along with their configuration. Recorders gather application + | event data from requests and tasks to pass to your ingest driver. + | + */ + + 'recorders' => [ + Recorders\CacheInteractions::class => [ + 'enabled' => env('PULSE_CACHE_INTERACTIONS_ENABLED', true), + 'sample_rate' => env('PULSE_CACHE_INTERACTIONS_SAMPLE_RATE', 1), + 'ignore' => [ + ...Pulse::defaultVendorCacheKeys(), + ], + 'groups' => [ + '/^job-exceptions:.*/' => 'job-exceptions:*', + // '/:\d+/' => ':*', + ], + ], + + Recorders\Exceptions::class => [ + 'enabled' => env('PULSE_EXCEPTIONS_ENABLED', true), + 'sample_rate' => env('PULSE_EXCEPTIONS_SAMPLE_RATE', 1), + 'location' => env('PULSE_EXCEPTIONS_LOCATION', true), + 'ignore' => [ + // '/^Package\\\\Exceptions\\\\/', + ], + ], + + Recorders\Queues::class => [ + 'enabled' => env('PULSE_QUEUES_ENABLED', true), + 'sample_rate' => env('PULSE_QUEUES_SAMPLE_RATE', 1), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\Servers::class => [ + 'server_name' => env('PULSE_SERVER_NAME', gethostname()), + 'directories' => explode(':', env('PULSE_SERVER_DIRECTORIES', '/')), + ], + + Recorders\SlowJobs::class => [ + 'enabled' => env('PULSE_SLOW_JOBS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_JOBS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\SlowOutgoingRequests::class => [ + 'enabled' => env('PULSE_SLOW_OUTGOING_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_OUTGOING_REQUESTS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000), + 'ignore' => [ + // '#^http://127\.0\.0\.1:13714#', // Inertia SSR... + ], + 'groups' => [ + // '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*', + // '#^https?://([^/]*).*$#' => '\1', + // '#/\d+#' => '/*', + ], + ], + + Recorders\SlowQueries::class => [ + 'enabled' => env('PULSE_SLOW_QUERIES_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_QUERIES_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000), + 'location' => env('PULSE_SLOW_QUERIES_LOCATION', true), + 'max_query_length' => env('PULSE_SLOW_QUERIES_MAX_QUERY_LENGTH'), + 'ignore' => [ + '/(["`])pulse_[\w]+?\1/', // Pulse tables... + '/(["`])telescope_[\w]+?\1/', // Telescope tables... + ], + ], + + Recorders\SlowRequests::class => [ + 'enabled' => env('PULSE_SLOW_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_REQUESTS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000), + 'ignore' => [ + '#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard... + '#^/telescope#', // Telescope dashboard... + ], + ], + + Recorders\UserJobs::class => [ + 'enabled' => env('PULSE_USER_JOBS_ENABLED', true), + 'sample_rate' => env('PULSE_USER_JOBS_SAMPLE_RATE', 1), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\UserRequests::class => [ + 'enabled' => env('PULSE_USER_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_USER_REQUESTS_SAMPLE_RATE', 1), + 'ignore' => [ + '#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard... + '#^/telescope#', // Telescope dashboard... + ], + ], + ], +]; diff --git a/database/migrations/2025_04_19_193321_create_pulse_tables.php b/database/migrations/2025_04_19_193321_create_pulse_tables.php new file mode 100644 index 0000000..5d194e2 --- /dev/null +++ b/database/migrations/2025_04_19_193321_create_pulse_tables.php @@ -0,0 +1,84 @@ +shouldRun()) { + return; + } + + Schema::create('pulse_values', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('timestamp'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->mediumText('value'); + + $table->index('timestamp'); // For trimming... + $table->index('type'); // For fast lookups and purging... + $table->unique(['type', 'key_hash']); // For data integrity and upserts... + }); + + Schema::create('pulse_entries', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('timestamp'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->bigInteger('value')->nullable(); + + $table->index('timestamp'); // For trimming... + $table->index('type'); // For purging... + $table->index('key_hash'); // For mapping... + $table->index(['timestamp', 'type', 'key_hash', 'value']); // For aggregate queries... + }); + + Schema::create('pulse_aggregates', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('bucket'); + $table->unsignedMediumInteger('period'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->string('aggregate'); + $table->decimal('value', 20, 2); + $table->unsignedInteger('count')->nullable(); + + $table->unique(['bucket', 'period', 'type', 'aggregate', 'key_hash']); // Force "on duplicate update"... + $table->index(['period', 'bucket']); // For trimming... + $table->index('type'); // For purging... + $table->index(['period', 'type', 'aggregate', 'bucket']); // For aggregate queries... + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pulse_values'); + Schema::dropIfExists('pulse_entries'); + Schema::dropIfExists('pulse_aggregates'); + } +}; diff --git a/database/migrations/2025_04_19_201544_create_admins_table.php b/database/migrations/2025_04_19_201544_create_admins_table.php new file mode 100644 index 0000000..09c0645 --- /dev/null +++ b/database/migrations/2025_04_19_201544_create_admins_table.php @@ -0,0 +1,32 @@ +uuid('id')->primary(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('admins'); + } +}; diff --git a/database/seeders/AdminSeeder.php b/database/seeders/AdminSeeder.php new file mode 100644 index 0000000..169ad55 --- /dev/null +++ b/database/seeders/AdminSeeder.php @@ -0,0 +1,22 @@ + Str::uuid()->toString(), + 'name' => 'Admin Monitoring', + 'email' => 'admin@monitoring.com', + 'password' => bcrypt('monitoring123'), + 'email_verified_at' => now(), + 'remember_token' => Str::random(10), + ]); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 1b576a5..860b8d1 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,21 +2,16 @@ namespace Database\Seeders; -use App\Models\User; -// use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { - /** - * Seed the application's database. - */ public function run(): void { - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', - 'password' => bcrypt('password') + // Run User Seeder + $this->call([ + UserSeeder::class, + AdminSeeder::class ]); } } diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 0000000..a3254fd --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,22 @@ + Str::uuid()->toString(), + 'name' => 'Test User', + 'email' => 'test@example.com', + 'password' => bcrypt('password'), + 'email_verified_at' => now(), + 'remember_token' => Str::random(10), + ]); + } +} diff --git a/resources/views/monitoring/login.blade.php b/resources/views/monitoring/login.blade.php new file mode 100644 index 0000000..1b0ba2c --- /dev/null +++ b/resources/views/monitoring/login.blade.php @@ -0,0 +1,63 @@ + + +
+ + +