diff --git a/.DS_Store b/.DS_Store
index 0171a60..e28070c 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/app/Livewire/Admin/ActivityLogTable.php b/app/Livewire/Admin/ActivityLogTable.php
new file mode 100644
index 0000000..1dd0b4a
--- /dev/null
+++ b/app/Livewire/Admin/ActivityLogTable.php
@@ -0,0 +1,97 @@
+showCheckBox();
+
+ return [
+ PowerGrid::header()
+ ->showSearchInput(),
+ PowerGrid::footer()
+ ->showPerPage()
+ ->showRecordCount(),
+ ];
+ }
+
+ public function datasource(): Builder
+ {
+ return Activity::with(['causer'])->latest();
+ }
+
+ public function relationSearch(): array
+ {
+ return [];
+ }
+
+ public function fields(): PowerGridFields
+ {
+ return PowerGrid::fields()
+ ->add('id')
+ ->add('created_at_formatted', fn (Activity $model) => Carbon::parse($model->created_at)->format('Y-m-d H:i:s'))
+ ->add('causer_name', fn (Activity $model) => optional($model->causer)->name)
+ ->add('subject_type_label', fn (Activity $model) => class_basename($model->subject_type))
+ ->add('subject_type')
+ ->add('subject_id')
+ ->add('description')
+ ->add('log_name')
+ ->add('changes', function (Activity $model) {
+ $old = $model->properties['old'] ?? [];
+ $new = $model->properties['attributes'] ?? [];
+
+ $changes = [];
+
+ foreach ($new as $key => $newValue) {
+ if (in_array($key, ['updated_at', 'created_at'])) continue;
+ $oldValue = $old[$key] ?? '(空)';
+ if ($newValue != $oldValue) {
+ $changes[] = "{$key}: {$oldValue} → {$newValue}";
+ }
+ }
+
+ return implode('
', $changes);
+ })
+ ;
+
+ }
+
+ public function columns(): array
+ {
+ $column=[];
+ $column[]=Column::make('時間', 'created_at_formatted', 'created_at')->sortable()->searchable();
+ $column[]=Column::make('操作者', 'causer.name')->sortable()->searchable()->bodyAttribute('whitespace-nowrap');
+ $column[]=Column::make('模型', 'subject_type_label')->sortable()->searchable();
+ $column[]=Column::make('模型 ID', 'subject_id')->sortable()->searchable();
+ $column[]=Column::make('動作', 'description')->sortable()->searchable();
+ $column[]=Column::make('變更內容', 'changes')->sortable(false)->searchable(false)->bodyAttribute('whitespace-normal text-sm text-gray-700');
+ $column[]=Column::make('Log 名稱', 'log_name')->sortable()->searchable();
+ return $column;
+ }
+
+ public function filters(): array
+ {
+ return [
+ Filter::datetimepicker('created_at'),
+ ];
+ }
+
+
+
+
+}
diff --git a/app/Models/Artist.php b/app/Models/Artist.php
index de7989d..554c107 100644
--- a/app/Models/Artist.php
+++ b/app/Models/Artist.php
@@ -6,12 +6,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Helpers\ChineseNameConverter;
use App\Helpers\ChineseStrokesConverter;
-
+use App\Traits\LogsModelActivity;
class Artist extends Model
{
/** @use HasFactory<\Database\Factories\ArtistFactory> */
- use HasFactory;
+ use HasFactory, LogsModelActivity;
protected $fillable = [
'category',
@@ -29,7 +29,7 @@ class Artist extends Model
'category' => \App\Enums\ArtistCategory::class,
];
}
-
+
public function songs() {
return $this->belongsToMany(Song::class);
}
diff --git a/app/Models/Branch.php b/app/Models/Branch.php
index 48de8f2..de0c80e 100644
--- a/app/Models/Branch.php
+++ b/app/Models/Branch.php
@@ -4,15 +4,17 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use App\Traits\LogsModelActivity;
class Branch extends Model
{
/** @use HasFactory<\Database\Factories\ArtistFactory> */
- use HasFactory;
-
+ use HasFactory, LogsModelActivity;
+
protected $fillable = [
'name',
'external_ip',
+ 'enable',
];
public function rooms() {
diff --git a/app/Models/Room.php b/app/Models/Room.php
index ad8c5df..53aea8f 100644
--- a/app/Models/Room.php
+++ b/app/Models/Room.php
@@ -4,11 +4,12 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use App\Traits\LogsModelActivity;
class Room extends Model
{
/** @use HasFactory<\Database\Factories\ArtistFactory> */
- use HasFactory;
+ use HasFactory, LogsModelActivity;
protected $casts = [
'started_at' => 'datetime',
diff --git a/app/Models/Song.php b/app/Models/Song.php
index 762e330..83dda1e 100644
--- a/app/Models/Song.php
+++ b/app/Models/Song.php
@@ -6,11 +6,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Helpers\ChineseNameConverter;
use App\Helpers\ChineseStrokesConverter;
+use App\Traits\LogsModelActivity;
class Song extends Model
{
/** @use HasFactory<\Database\Factories\SongFactory> */
- use HasFactory;
+ use HasFactory, LogsModelActivity;
protected $fillable = [
'id',
diff --git a/app/Models/User.php b/app/Models/User.php
index 79be10f..c6057b0 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -7,11 +7,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
+use App\Traits\LogsModelActivity;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
- use HasFactory, Notifiable, HasRoles;
+ use HasFactory, Notifiable, HasRoles, LogsModelActivity;
/**
* The attributes that are mass assignable.
diff --git a/app/Traits/LogsModelActivity.php b/app/Traits/LogsModelActivity.php
new file mode 100644
index 0000000..7c68b62
--- /dev/null
+++ b/app/Traits/LogsModelActivity.php
@@ -0,0 +1,23 @@
+useLogName(strtolower(class_basename(static::class)))
+ ->logOnly($this->getFillable())
+ ->logOnlyDirty()
+ ->dontSubmitEmptyLogs()
+ ->setDescriptionForEvent(function (string $eventName) {
+ return class_basename(static::class) . " 已 {$eventName}";
+ });
+ }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index cb45c53..5a85aa9 100644
--- a/composer.json
+++ b/composer.json
@@ -16,6 +16,7 @@
"overtrue/php-opencc": "^1.2",
"overtrue/pinyin": "^5.3",
"power-components/livewire-powergrid": "^6.3",
+ "spatie/laravel-activitylog": "^4.10",
"spatie/laravel-permission": "^6.17",
"wire-elements/modal": "^2.0",
"wireui/wireui": "^2.4"
diff --git a/composer.lock b/composer.lock
index c1a859c..8f10ccb 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d5d6344c1be46378bdaa0bfecd2e300e",
+ "content-hash": "54bbfde233e690f726d54157b29f12a3",
"packages": [
{
"name": "brick/math",
@@ -4346,6 +4346,97 @@
],
"time": "2024-04-27T21:32:50+00:00"
},
+ {
+ "name": "spatie/laravel-activitylog",
+ "version": "4.10.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-activitylog.git",
+ "reference": "466f30f7245fe3a6e328ad5e6812bd43b4bddea5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/466f30f7245fe3a6e328ad5e6812bd43b4bddea5",
+ "reference": "466f30f7245fe3a6e328ad5e6812bd43b4bddea5",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
+ "illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0",
+ "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
+ "php": "^8.1",
+ "spatie/laravel-package-tools": "^1.6.3"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.0 || ^10.0",
+ "pestphp/pest": "^1.20 || ^2.0 || ^3.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\Activitylog\\ActivitylogServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Spatie\\Activitylog\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian De Deyne",
+ "email": "sebastian@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ },
+ {
+ "name": "Tom Witkowski",
+ "email": "dev.gummibeer@gmail.com",
+ "homepage": "https://gummibeer.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A very simple activity logger to monitor the users of your website or application",
+ "homepage": "https://github.com/spatie/activitylog",
+ "keywords": [
+ "activity",
+ "laravel",
+ "log",
+ "spatie",
+ "user"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-activitylog/issues",
+ "source": "https://github.com/spatie/laravel-activitylog/tree/4.10.1"
+ },
+ "funding": [
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-10T15:38:25+00:00"
+ },
{
"name": "spatie/laravel-package-tools",
"version": "1.92.4",
diff --git a/config/activitylog.php b/config/activitylog.php
new file mode 100644
index 0000000..f1262f5
--- /dev/null
+++ b/config/activitylog.php
@@ -0,0 +1,52 @@
+ env('ACTIVITY_LOGGER_ENABLED', true),
+
+ /*
+ * When the clean-command is executed, all recording activities older than
+ * the number of days specified here will be deleted.
+ */
+ 'delete_records_older_than_days' => 365,
+
+ /*
+ * If no log name is passed to the activity() helper
+ * we use this default log name.
+ */
+ 'default_log_name' => 'default',
+
+ /*
+ * You can specify an auth driver here that gets user models.
+ * If this is null we'll use the current Laravel auth driver.
+ */
+ 'default_auth_driver' => null,
+
+ /*
+ * If set to true, the subject returns soft deleted models.
+ */
+ 'subject_returns_soft_deleted_models' => false,
+
+ /*
+ * This model will be used to log activity.
+ * It should implement the Spatie\Activitylog\Contracts\Activity interface
+ * and extend Illuminate\Database\Eloquent\Model.
+ */
+ 'activity_model' => \Spatie\Activitylog\Models\Activity::class,
+
+ /*
+ * This is the name of the table that will be created by the migration and
+ * used by the Activity model shipped with this package.
+ */
+ 'table_name' => env('ACTIVITY_LOGGER_TABLE_NAME', 'activity_log'),
+
+ /*
+ * This is the database connection that will be used by the migration and
+ * the Activity model shipped with this package. In case it's not set
+ * Laravel's database.default will be used instead.
+ */
+ 'database_connection' => env('ACTIVITY_LOGGER_DB_CONNECTION'),
+];
diff --git a/config/database.php b/config/database.php
index 3082351..8910562 100644
--- a/config/database.php
+++ b/config/database.php
@@ -41,17 +41,6 @@ return [
'journal_mode' => null,
'synchronous' => null,
],
-
- 'KTVsqlite' => [
- 'driver' => 'sqlite',
- 'url' => env('DB_URL'),
- 'database' => env('SQLITE_DB', database_path('KSongDatabase.db')),
- 'prefix' => '',
- 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
- 'busy_timeout' => null,
- 'journal_mode' => null,
- 'synchronous' => null,
- ],
'mysql' => [
'driver' => 'mysql',
diff --git a/database/migrations/2025_04_22_193851_create_activity_log_table.php b/database/migrations/2025_04_22_193851_create_activity_log_table.php
new file mode 100644
index 0000000..7c05bc8
--- /dev/null
+++ b/database/migrations/2025_04_22_193851_create_activity_log_table.php
@@ -0,0 +1,27 @@
+create(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->bigIncrements('id');
+ $table->string('log_name')->nullable();
+ $table->text('description');
+ $table->nullableMorphs('subject', 'subject');
+ $table->nullableMorphs('causer', 'causer');
+ $table->json('properties')->nullable();
+ $table->timestamps();
+ $table->index('log_name');
+ });
+ }
+
+ public function down()
+ {
+ Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name'));
+ }
+}
diff --git a/database/migrations/2025_04_22_193852_add_event_column_to_activity_log_table.php b/database/migrations/2025_04_22_193852_add_event_column_to_activity_log_table.php
new file mode 100644
index 0000000..7b797fd
--- /dev/null
+++ b/database/migrations/2025_04_22_193852_add_event_column_to_activity_log_table.php
@@ -0,0 +1,22 @@
+table(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->string('event')->nullable()->after('subject_type');
+ });
+ }
+
+ public function down()
+ {
+ Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->dropColumn('event');
+ });
+ }
+}
diff --git a/database/migrations/2025_04_22_193853_add_batch_uuid_column_to_activity_log_table.php b/database/migrations/2025_04_22_193853_add_batch_uuid_column_to_activity_log_table.php
new file mode 100644
index 0000000..8f7db66
--- /dev/null
+++ b/database/migrations/2025_04_22_193853_add_batch_uuid_column_to_activity_log_table.php
@@ -0,0 +1,22 @@
+table(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->uuid('batch_uuid')->nullable()->after('properties');
+ });
+ }
+
+ public function down()
+ {
+ Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
+ $table->dropColumn('batch_uuid');
+ });
+ }
+}
diff --git a/resources/views/livewire/admin/activity-log.blade.php b/resources/views/livewire/admin/activity-log.blade.php
new file mode 100644
index 0000000..560ce2d
--- /dev/null
+++ b/resources/views/livewire/admin/activity-log.blade.php
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/resources/views/livewire/layout/admin/sidebar.blade.php b/resources/views/livewire/layout/admin/sidebar.blade.php
index 34e6f26..d0efb7b 100644
--- a/resources/views/livewire/layout/admin/sidebar.blade.php
+++ b/resources/views/livewire/layout/admin/sidebar.blade.php
@@ -3,6 +3,7 @@
$menus = [
['label' => 'Dashboard', 'route' => 'admin.dashboard', 'icon' => 'home', 'permission' => null],
+ ['label' => 'ActivityLog', 'route' => 'admin.activity-log', 'icon' => 'clock', 'permission' => null],
['label' => 'Role', 'route' => 'admin.roles', 'icon' => 'user-circle', 'permission' => 'role-list'],
['label' => 'User', 'route' => 'admin.users', 'icon' => 'user-circle', 'permission' => 'user-list'],
['label' => 'Artist', 'route' => 'admin.artists', 'icon' => 'musical-note', 'permission' => 'song-list'],
diff --git a/routes/web.php b/routes/web.php
index 660026d..3e9cd7a 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -21,7 +21,7 @@ require __DIR__.'/auth.php';
Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
Route::get('/dashboard', AdminDashboard::class)->name('dashboard');
-
+ Route::get('/activity-log', function () {return view('livewire.admin.activity-log');})->name('activity-log');
Route::get('/roles', function () {return view('livewire.admin.roles');})->name('roles');
Route::get('/users', function () {return view('livewire.admin.users');})->name('users');
Route::get('/artists', function () {return view('livewire.admin.artists');})->name('artists');
diff --git a/開發手冊.ini b/開發手冊.ini
index 2ced5c6..2fc1393 100644
--- a/開發手冊.ini
+++ b/開發手冊.ini
@@ -61,6 +61,7 @@ npm install && npm run build
php artisan convert:unihan-strokes \
--input=resources/data/Unihan_IRGSources.txt \
--output=resources/data/unihan_strokes.php
+
php artisan cache:forget unihan_strokes
@@ -75,6 +76,13 @@ php artisan vendor:publish --tag=livewire-powergrid-config
composer require overtrue/php-opencc -vvv
composer require overtrue/pinyin
+操作記錄
+composer require spatie/laravel-activitylog
+php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
+php artisan migrate
+php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-config"
+php artisan make:model ActivityLog
+
建立分頁table
php artisan powergrid:create