diff --git a/app/Console/Commands/Test.php b/app/Console/Commands/Test.php index 39efff59..03783499 100644 --- a/app/Console/Commands/Test.php +++ b/app/Console/Commands/Test.php @@ -14,12 +14,10 @@ use Illuminate\Console\Command; use NexusPlugin\Menu\Filament\MenuItemResource\Pages\ManageMenuItems; use NexusPlugin\Menu\MenuRepository; use NexusPlugin\Menu\Models\MenuItem; -use NexusPlugin\Permission\Models\Permission; use NexusPlugin\Permission\Models\Role; use NexusPlugin\PostLike\PostLikeRepository; use NexusPlugin\StickyPromotion\Models\StickyPromotion; use NexusPlugin\StickyPromotion\Models\StickyPromotionParticipator; -use NexusPlugin\Tracker\TrackerRepository; use NexusPlugin\Work\Models\RoleWork; use NexusPlugin\Work\WorkRepository; use Stichoza\GoogleTranslate\GoogleTranslate; @@ -57,9 +55,9 @@ class Test extends Command */ public function handle() { - $a = [1,2,3]; - $b = array_slice($a, 0, 2); - dd($a, $b); + $rep = new MenuRepository(); + $result = \Nexus\Plugin\Plugin::listEnabled(); + dd($result); } } diff --git a/app/Console/ExecuteCommandTrait.php b/app/Console/ExecuteCommandTrait.php deleted file mode 100644 index 73240d55..00000000 --- a/app/Console/ExecuteCommandTrait.php +++ /dev/null @@ -1,16 +0,0 @@ -info("Running $command ..."); - $result = exec($command, $output, $result_code); - do_log(sprintf('command: %s, result_code: %s, output: %s, result: %s', $command, $result_code, json_encode($output), $result)); - if ($result_code != 0) { - throw new \RuntimeException(json_encode($output)); - } - } -} diff --git a/app/Filament/Resources/System/PluginResource.php b/app/Filament/Resources/System/PluginResource.php index fe6aa57c..eec15ff5 100644 --- a/app/Filament/Resources/System/PluginResource.php +++ b/app/Filament/Resources/System/PluginResource.php @@ -35,7 +35,7 @@ class PluginResource extends Resource return self::getNavigationLabel(); } - protected static bool $shouldRegisterNavigation = true; + protected static bool $shouldRegisterNavigation = false; public static function form(Form $form): Form { diff --git a/app/Filament/Resources/System/PluginStoreResource.php b/app/Filament/Resources/System/PluginStoreResource.php index 30f5104d..7a105f59 100644 --- a/app/Filament/Resources/System/PluginStoreResource.php +++ b/app/Filament/Resources/System/PluginStoreResource.php @@ -18,6 +18,7 @@ use Filament\Infolists\Components; use Filament\Infolists; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\SoftDeletingScope; +use Illuminate\Support\Facades\App; use Illuminate\Support\HtmlString; use Filament\Actions\Action; use Livewire\Livewire; @@ -29,6 +30,13 @@ class PluginStoreResource extends Resource protected static ?string $navigationGroup = 'System'; + protected static ?int $navigationSort = 99; + + public static function getNavigationBadge(): ?string + { + return PluginStore::getHasNewVersionCount(); + } + public static function form(Form $form): Form { return $form @@ -43,15 +51,26 @@ class PluginStoreResource extends Resource ->columns([ Tables\Columns\Layout\Stack::make([ Tables\Columns\Layout\Stack::make([ - Tables\Columns\TextColumn::make('title') + Tables\Columns\TextColumn::make(self::getColumnLabelKey("title")) ->weight(FontWeight::Bold) , - Tables\Columns\TextColumn::make('description'), + Tables\Columns\TextColumn::make(self::getColumnLabelKey("description")), ]), Tables\Columns\Layout\Stack::make([ Tables\Columns\TextColumn::make('version') - ->formatStateUsing(fn (PluginStore $record) => sprintf("版本: %s | 更新时间: %s", $record->version, $record->release_date)) - ->color('gray') + ->formatStateUsing(function (PluginStore $record) { + $installedVersion = $record->installed_version; + $latestVersion = $record->version; + if ($installedVersion) { + return sprintf('%s: %s', nexus_trans("plugin.labels.installed_version"), $installedVersion); + } + return sprintf( + '%s: %s | %s: %s', + nexus_trans("plugin.labels.latest_version"), $latestVersion, + nexus_trans("plugin.labels.release_date"), $record->release_date + ); + }) + ->color(fn ($record) => $record->installed_version ? 'success' : 'gray') , ]) ])->space(3), @@ -65,24 +84,29 @@ class PluginStoreResource extends Resource ]) ->actions([ Tables\Actions\ViewAction::make() - ->modalHeading("详细介绍") + ->modalHeading(nexus_trans("plugin.labels.introduce")) ->modalContent(fn (PluginStore $record) => $record->getFullDescription()) ->extraModalFooterActions([ - Action::make("viewOnBlog") + Action::make(nexus_trans("plugin.labels.view_on_blog")) ->url(fn (PluginStore $record) => $record->getBlogPostUrl()) ->extraAttributes(['target' => '_blank']) , ]) , Tables\Actions\Action::make("install") - ->label("安装") - ->modalHeading(fn (PluginStore $record) => sprintf("安装插件: %s", $record->title)) + ->label(function(PluginStore $record) { + if ($record->hasNewVersion()) { + return sprintf('%s(new: %s)', nexus_trans("plugin.actions.update"), $record->version); + } + return nexus_trans("plugin.actions.install"); + }) + ->modalHeading(fn (PluginStore $record) => sprintf("%s: %s", nexus_trans("plugin.actions.install_or_update") ,$record->title)) ->modalContent(function (PluginStore $record) { $infolist = new Infolist(); $infolist->record = $record; $infolist->schema([ Infolists\Components\TextEntry::make('plugin_id') - ->label(fn () => sprintf("进入目录: %s, 以 root 用户的身份依次执行以下命令进行安装: ", base_path())) + ->label(fn () => nexus_trans("plugin.labels.install_title", ['web_root' => base_path()])) ->html(true) ->formatStateUsing(function (PluginStore $record) { return self::getPluginInstruction($record); @@ -92,6 +116,7 @@ class PluginStoreResource extends Resource return $infolist; }) ->modalFooterActions(fn () => []) + ->color(fn (PluginStore $record) => $record->hasNewVersion() ? 'danger' : 'primary') , ]) ->recordAction(null) @@ -99,14 +124,23 @@ class PluginStoreResource extends Resource ; } + private static function getColumnLabelKey($column): string + { + $locale = App::getLocale(); + if (in_array($locale, ['zh_CN', 'zh_TW'])) { + return "$column.zh_CN"; + } + return "$column.en"; + } + private static function getPluginInstruction(PluginStore $record): string { $result = []; - $result[] = "配置扩展地址"; + $result[] = nexus_trans("plugin.labels.config_plugin_address"); $result[] = sprintf("composer config repositories.%s git %s", $record->plugin_id, $record->remote_url); - $result[] = "
下载扩展. 这里展示的最新版本号, 如果需要安装其他版本(可在查看页面底部获得)自行替换"; + $result[] = "
" . nexus_trans("plugin.labels.download_specific_version"); $result[] = sprintf("composer require %s:%s", $record->package_name, $record->version); - $result[] = "
执行安装"; + $result[] = "
" . nexus_trans("plugin.labels.execute_install"); $result[] = sprintf("php artisan plugin install %s", $record->package_name); return implode("
", $result); } diff --git a/app/Filament/Resources/System/SeedBoxRecordResource.php b/app/Filament/Resources/System/SeedBoxRecordResource.php index 60c6f90e..c4f33f67 100644 --- a/app/Filament/Resources/System/SeedBoxRecordResource.php +++ b/app/Filament/Resources/System/SeedBoxRecordResource.php @@ -29,7 +29,7 @@ class SeedBoxRecordResource extends Resource protected static ?string $navigationGroup = 'System'; - protected static ?int $navigationSort = 98; + protected static ?int $navigationSort = 8; public static function getNavigationLabel(): string { diff --git a/app/Filament/Resources/System/SettingResource.php b/app/Filament/Resources/System/SettingResource.php index 7a515580..8022a8a4 100644 --- a/app/Filament/Resources/System/SettingResource.php +++ b/app/Filament/Resources/System/SettingResource.php @@ -24,7 +24,7 @@ class SettingResource extends Resource protected static ?string $navigationGroup = 'System'; - protected static ?int $navigationSort = 100; + protected static ?int $navigationSort = 1000; public static function getNavigationLabel(): string { diff --git a/app/Filament/Resources/System/TorrentStateResource.php b/app/Filament/Resources/System/TorrentStateResource.php index f33e6c1b..fb8c3c91 100644 --- a/app/Filament/Resources/System/TorrentStateResource.php +++ b/app/Filament/Resources/System/TorrentStateResource.php @@ -24,7 +24,7 @@ class TorrentStateResource extends Resource protected static ?string $navigationGroup = 'System'; - protected static ?int $navigationSort = 99; + protected static ?int $navigationSort = 9; public static function getNavigationLabel(): string { diff --git a/app/Models/PluginStore.php b/app/Models/PluginStore.php index 6e3ee7f2..7f073c02 100644 --- a/app/Models/PluginStore.php +++ b/app/Models/PluginStore.php @@ -5,20 +5,35 @@ namespace App\Models; use Illuminate\Contracts\Support\Htmlable; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Route; use Illuminate\Support\HtmlString; +use Nexus\Database\NexusDB; use Sushi\Sushi; +use Nexus\Plugin\Plugin; class PluginStore extends Model { use Sushi; + protected $casts = [ + 'title' => 'array', + 'description' => 'array', + ]; + const PLUGIN_LIST_API = "https://nppl.nexusphp.workers.dev"; const BLOG_POST_INFO_API = "https://nexusphp.org/wp-json/wp/v2/posts/%d"; const BLOG_POST_URL = "https://nexusphp.org/?p=%d"; + private static array|null $rows =null; + public function getRows() { - return Http::get(self::PLUGIN_LIST_API)->json(); + $list = self::listAll(true); + $enabled = Plugin::listEnabled(); + foreach ($list as &$row) { + $row['installed_version'] = $enabled[$row['plugin_id']] ?? ''; + } + return $list; } public function getBlogPostUrl(): string @@ -30,7 +45,7 @@ class PluginStore extends Model { $url = $this->getBlogPostInfoUrl($this->post_id); $logPrefix = sprintf("post_id: %s, url: %s", $this->post_id, $url); - $defaultContent = "无法获取详细信息 ..."; + $defaultContent = "Fail to get content ..."; try { $result = Http::get($url)->json(); do_log("$logPrefix, result: " . json_encode($result)); @@ -50,8 +65,69 @@ class PluginStore extends Model return sprintf(self::BLOG_POST_INFO_API, $postId); } + public function hasNewVersion(): bool + { + return $this->installed_version + && version_compare($this->version, $this->installed_version, '>'); + } + public static function getInfo(string $id) { return Http::get(self::PLUGIN_LIST_API . "/plugin/$id")->json(); } + + public static function listAll($withoutCache = false) + { + $log = "listAll, withoutCache: $withoutCache"; + $cacheKey = "nexus_plugin_store_all"; + $cacheTime = 86400*100; + if (is_null(self::$rows)) { + $log .= ", is_null"; + if ($withoutCache) { + $log .= ", WITHOUT_CACHE"; + self::$rows = self::listAllFromRemote(); + NexusDB::cache_put($cacheKey, self::$rows, $cacheTime); + } else { + $log .= ", WITH_CACHE"; + self::$rows = NexusDB::remember($cacheKey, $cacheTime, function () { + return self::listAllFromRemote(); + }); + } + } else { + $log .= ", not_null"; + } + do_log($log, 'debug'); + return self::$rows; + } + + private static function listAllFromRemote() + { + $list = Http::get(self::PLUGIN_LIST_API)->json(); + foreach ($list as &$row) { + foreach ($row as $key => $value) { + if (is_array($value)) { + $row[$key] = json_encode($value); + } + } + } + return $list; + } + + public static function getHasNewVersionCount(): int + { + $currentRouteName = Route::currentRouteName(); + $withoutCacheRouteName = ['filament.admin.resources.system.plugin-stores.index', 'filament.admin.pages.dashboard']; + $list = self::listAll(in_array($currentRouteName, $withoutCacheRouteName)); + $enabled = Plugin::listEnabled(); + $count = 0; + foreach ($list as $row) { + $installedVersion = $enabled[$row['plugin_id']] ?? ''; + if ($installedVersion && version_compare($installedVersion, $row['version'], '<=')) { + $count++; + } + } + return $count; + } + + } diff --git a/app/Policies/PluginStorePolicy.php b/app/Policies/PluginStorePolicy.php new file mode 100644 index 00000000..4152d0c0 --- /dev/null +++ b/app/Policies/PluginStorePolicy.php @@ -0,0 +1,74 @@ +can($user); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, PluginStore $pluginStore): bool + { + return $this->can($user); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return false; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, PluginStore $pluginStore): bool + { + return false; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, PluginStore $pluginStore): bool + { + return false; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, PluginStore $pluginStore): bool + { + return false; + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, PluginStore $pluginStore): bool + { + return false; + } + + private function can(User $user) + { + if ($user->class >= User::CLASS_SYSOP) { + return true; + } + return false; + } +} diff --git a/app/Support/StaticMake.php b/app/Support/StaticMake.php new file mode 100644 index 00000000..3019bec7 --- /dev/null +++ b/app/Support/StaticMake.php @@ -0,0 +1,14 @@ +getId(); + } } diff --git a/nexus/Plugin/Plugin.php b/nexus/Plugin/Plugin.php index 14ac4a9d..07439547 100644 --- a/nexus/Plugin/Plugin.php +++ b/nexus/Plugin/Plugin.php @@ -5,6 +5,9 @@ class Plugin { private static mixed $providers = null; + /** + * @var BasePlugin[] + */ private static array $plugins = []; // public function __construct() @@ -28,7 +31,7 @@ class Plugin $result = []; //plugins are more exactly foreach (self::$plugins as $id => $plugin) { - $result[$id] = 1; + $result[$id] = $plugin->getVersion(); } return $result; } @@ -60,15 +63,19 @@ class Plugin if ($parts[0] == 'NexusPlugin') { $className = str_replace('ServiceProvider', 'Repository', $provider); if (class_exists($className)) { - $constantName = "$className::COMPATIBLE_VERSION"; + $constantName = "$className::COMPATIBLE_NP_VERSION"; if (defined($constantName) && version_compare(VERSION_NUMBER, constant($constantName), '<')) { continue; } + /** + * @var BasePlugin $className + */ $plugin = new $className; - $pluginIdName = "$className::ID"; - if (defined($pluginIdName)) { - self::$plugins[constant($pluginIdName)] = $plugin; - } +// $pluginIdName = "$className::ID"; +// if (defined($pluginIdName)) { +// self::$plugins[constant($pluginIdName)] = $plugin; +// } + self::$plugins[$plugin->getId()] = $plugin; call_user_func([$plugin, 'boot']); } } diff --git a/resources/lang/en/plugin.php b/resources/lang/en/plugin.php index 620799db..dcc5e8d1 100644 --- a/resources/lang/en/plugin.php +++ b/resources/lang/en/plugin.php @@ -2,17 +2,26 @@ return [ 'actions' => [ - 'install' => 'Install', - 'delete' => 'Remove', - 'update' => 'Upgrade', + 'install' => 'install', + 'delete' => 'delete', + 'update' => 'upgrade', + 'install_or_update' => 'install/upgrade', ], 'labels' => [ - 'display_name' => 'Name', - 'package_name' => 'Package name', - 'remote_url' => 'Repository address', - 'installed_version' => 'Installed version', - 'status' => 'Status', - 'updated_at' => 'Last action at', + 'display_name' => 'name', + 'package_ name' => 'package_name', + 'remote_url' => 'repository_address', + 'installed_version' => 'installed_version', + 'latest_version' => 'latest_version', + 'status' => 'status', + 'updated_at' => 'last_executed_action', + ' release_date' => 'updated at', + 'install_title' => 'Go to the directory: :web_root, and run the following commands in order to install it as the root user: ', + 'introduce' => 'Details', + 'view_on_blog' => 'View on blog', + ' config_plugin_address' => 'Configure plugin address', + 'download_specific_version' => 'Download the extension. The latest version is shown here, if you need to install another version (view on blog to see all versions) replace it yourself', + 'execute_install' => 'Execute installation', ], 'status' => [ \App\Models\Plugin::STATUS_NORMAL => 'Normal', diff --git a/resources/lang/zh_CN/plugin.php b/resources/lang/zh_CN/plugin.php index 7ef18c10..32bff412 100644 --- a/resources/lang/zh_CN/plugin.php +++ b/resources/lang/zh_CN/plugin.php @@ -5,14 +5,23 @@ return [ 'install' => '安装', 'delete' => '删除', 'update' => '升级', + 'install_or_update' => '安装/升级', ], 'labels' => [ 'display_name' => '名称', 'package_name' => '包名', 'remote_url' => '仓库地址', 'installed_version' => '已安装版本', + 'latest_version' => '最新版本', 'status' => '状态', 'updated_at' => '上次执行操作', + 'release_date' => '更新时间', + 'install_title' => '进入目录: :web_root, 以 root 用户的身份依次执行以下命令进行安装: ', + 'introduce' => '详细介绍', + 'view_on_blog' => '在博客上查看', + 'config_plugin_address' => '配置插件地址', + 'download_specific_version' => '下载扩展. 这里展示的最新版本号, 如果需要安装其他版本(打开查看页面底部有显示所有版本)自行替换', + 'execute_install' => '执行安装', ], 'status' => [ \App\Models\Plugin::STATUS_NORMAL => '正常', diff --git a/resources/lang/zh_TW/plugin.php b/resources/lang/zh_TW/plugin.php index a3700e24..2da80f05 100644 --- a/resources/lang/zh_TW/plugin.php +++ b/resources/lang/zh_TW/plugin.php @@ -5,14 +5,23 @@ return [ 'install' => '安裝', 'delete' => '刪除', 'update' => '升級', + 'install_or_update' => '安裝/升級', ], 'labels' => [ 'display_name' => '名稱', 'package_name' => '包名', 'remote_url' => '倉庫地址', 'installed_version' => '已安裝版本', + 'latest_version' => '最新版本', 'status' => '狀態', 'updated_at' => '上次執行操作', + 'release_date' => '更新時間', + 'install_title' => '進入目錄: :web_root, 以 root 用戶的身份依次執行以下命令進行安裝: ', + 'introduce' => '詳細介紹', + 'view_on_blog' => '在博客上查看', + 'config_plugin_address' => '配置插件地址', + 'download_specific_version' => '下載擴展. 這裏展示的最新版本號, 如果需要安裝其他版本(打開查看頁面底部有顯示所有版本)自行替換', + 'execute_install' => '執行安裝', ], 'status' => [ \App\Models\Plugin::STATUS_NORMAL => '正常',