mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-14 12:30:49 +08:00
plugin management + user tables and torrents table text column migrate
This commit is contained in:
@@ -16,6 +16,7 @@ use App\Models\Invite;
|
||||
use App\Models\LoginLog;
|
||||
use App\Models\Medal;
|
||||
use App\Models\Peer;
|
||||
use App\Models\Post;
|
||||
use App\Models\SearchBox;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Snatch;
|
||||
@@ -101,11 +102,7 @@ class Test extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$str = "1.abc.de";
|
||||
$ext = "png";
|
||||
$str = "202404/20240403215909f58f38ddd968a0e8a4bdd30690a9e92e.png";
|
||||
$ext = pathinfo($str, PATHINFO_EXTENSION);
|
||||
dd(basename($str), $ext);
|
||||
$this->info("haha");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Upgrade;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class MigrateTorrentsTableTextColumn extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'upgrade:migrate_torrents_table_text_column';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Migrate torrents table text column';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$columns = ["ori_descr", "descr", "nfo", "technical_info", "pt_gen"];
|
||||
$sql = "alter table torrents ";
|
||||
$drops = [];
|
||||
foreach ($columns as $column) {
|
||||
if (Schema::hasColumn("torrents", $column)) {
|
||||
$drops[] = "drop column $column";
|
||||
}
|
||||
}
|
||||
if (!empty($drops)) {
|
||||
$sql .= implode(",", $drops);
|
||||
NexusDB::statement($sql);
|
||||
}
|
||||
if (Schema::hasTable("torrent_extras")) {
|
||||
NexusDB::statement("insert torrent_extras (torrent_id, descr, media_info, nfo, created_at) select id, descr, technical_info, nfo, now() from torrents on duplicate key update torrent_id = values(torrent_id)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Upgrade;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class MigrateUsersTableCommentRelatedColumn extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'upgrade:migrate_users_table_comment_related_column';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Migrate users table comment related column';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('users', 'modcomment')) {
|
||||
$table->dropColumn('modcomment');
|
||||
}
|
||||
if (Schema::hasColumn('users', 'bonuscomment')) {
|
||||
$table->dropColumn('bonuscomment');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,9 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
if (app()->runningInConsole()) {
|
||||
return;
|
||||
}
|
||||
$this->reportable(function (InsufficientPermissionException $e) {
|
||||
if (request()->expectsJson()) {
|
||||
return response()->json(fail($e->getMessage(), request()->all()), 403);
|
||||
@@ -55,9 +58,9 @@ class Handler extends ExceptionHandler
|
||||
});
|
||||
|
||||
//Other Only handle in json request
|
||||
// if (!request()->expectsJson()) {
|
||||
// return;
|
||||
// }
|
||||
if (!request()->expectsJson()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderable(function (AuthenticationException $e) {
|
||||
return response()->json(fail($e->getMessage(), ['guards' => $e->guards()]), 401);
|
||||
|
||||
10
app/Filament/Clusters/Plugin.php
Normal file
10
app/Filament/Clusters/Plugin.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Clusters;
|
||||
|
||||
use Filament\Clusters\Cluster;
|
||||
|
||||
class Plugin extends Cluster
|
||||
{
|
||||
protected static ?string $navigationIcon = 'heroicon-o-squares-2x2';
|
||||
}
|
||||
14
app/Filament/Pages/RunCommand.php
Normal file
14
app/Filament/Pages/RunCommand.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use Filament\Pages\Page;
|
||||
|
||||
class RunCommand extends Page
|
||||
{
|
||||
protected static ?string $navigationIcon = 'heroicon-o-document-text';
|
||||
|
||||
protected static string $view = 'filament.pages.run-command';
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class PluginResource extends Resource
|
||||
return self::getNavigationLabel();
|
||||
}
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
protected static bool $shouldRegisterNavigation = true;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
|
||||
123
app/Filament/Resources/System/PluginStoreResource.php
Normal file
123
app/Filament/Resources/System/PluginStoreResource.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System;
|
||||
|
||||
use App\Filament\Resources\System\PluginStoreResource\Pages;
|
||||
use App\Filament\Resources\System\PluginStoreResource\RelationManagers;
|
||||
use App\Livewire\InstallPluginModal;
|
||||
use App\Models\Plugin;
|
||||
use App\Models\PluginStore;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Support\Enums\FontWeight;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Infolists\Components;
|
||||
use Filament\Infolists;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Filament\Actions\Action;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class PluginStoreResource extends Resource
|
||||
{
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
protected static ?string $navigationGroup = 'System';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\Layout\Stack::make([
|
||||
Tables\Columns\Layout\Stack::make([
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->weight(FontWeight::Bold)
|
||||
,
|
||||
Tables\Columns\TextColumn::make('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')
|
||||
,
|
||||
])
|
||||
])->space(3),
|
||||
])
|
||||
->contentGrid([
|
||||
'md' => 2,
|
||||
'xl' => 3,
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\ViewAction::make()
|
||||
->modalHeading("详细介绍")
|
||||
->modalContent(fn (PluginStore $record) => $record->getFullDescription())
|
||||
->extraModalFooterActions([
|
||||
Action::make("viewOnBlog")
|
||||
->url(fn (PluginStore $record) => $record->getBlogPostUrl())
|
||||
->extraAttributes(['target' => '_blank'])
|
||||
,
|
||||
])
|
||||
,
|
||||
Tables\Actions\Action::make("install")
|
||||
->label("安装")
|
||||
->modalHeading(fn (PluginStore $record) => sprintf("安装插件: %s", $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()))
|
||||
->html(true)
|
||||
->formatStateUsing(function (PluginStore $record) {
|
||||
$user = executeCommand("whoami");
|
||||
$commands = [
|
||||
sprintf("sudo -u %s composer config repositories.%s %s", $user, $record->plugin_id, $record->remote_url),
|
||||
sprintf("sudo -u %s composer require %s:%s", $user, $record->package_name, $record->version),
|
||||
sprintf("sudo -u %s php artisan plugin install %s", $user, $record->package_name),
|
||||
];
|
||||
return implode("<br/>", $commands);
|
||||
})
|
||||
,
|
||||
]);
|
||||
return $infolist;
|
||||
})
|
||||
->modalFooterActions(fn () => [])
|
||||
,
|
||||
])
|
||||
->recordAction(null)
|
||||
->paginated(false)
|
||||
;
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPluginStores::route('/'),
|
||||
// 'create' => Pages\CreatePluginStore::route('/create'),
|
||||
// 'edit' => Pages\EditPluginStore::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
|
||||
|
||||
use App\Filament\Resources\System\PluginStoreResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePluginStore extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PluginStoreResource::class;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
|
||||
|
||||
use App\Filament\Resources\System\PluginStoreResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPluginStore extends EditRecord
|
||||
{
|
||||
protected static string $resource = PluginStoreResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\System\PluginStoreResource\Pages;
|
||||
|
||||
use App\Filament\Resources\System\PluginStoreResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPluginStores extends ListRecords
|
||||
{
|
||||
protected static string $resource = PluginStoreResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -130,8 +130,10 @@ class UserResource extends Resource
|
||||
,
|
||||
Infolists\Components\TextEntry::make('email')
|
||||
->label(__("label.email"))
|
||||
->copyable()
|
||||
->placeholder("点击复制")
|
||||
,
|
||||
Infolists\Components\TextEntry::make('passkey'),
|
||||
Infolists\Components\TextEntry::make('passkey')->limit(10)->copyable(),
|
||||
Infolists\Components\TextEntry::make('added')->label(__("label.added")),
|
||||
Infolists\Components\TextEntry::make('last_access')->label(__("label.last_access")),
|
||||
Infolists\Components\TextEntry::make('inviter.username')->label(__("label.user.invite_by")),
|
||||
@@ -141,7 +143,12 @@ class UserResource extends Resource
|
||||
Infolists\Components\TextEntry::make('uploadedText')->label(__("label.uploaded")),
|
||||
Infolists\Components\TextEntry::make('downloadedText')->label(__("label.downloaded")),
|
||||
Infolists\Components\TextEntry::make('seedbonus')->label(__("label.user.seedbonus")),
|
||||
])->columns(2),
|
||||
Infolists\Components\TextEntry::make('seed_points')->label(__("label.user.seed_points")),
|
||||
])
|
||||
->columns(6)
|
||||
->columnSpan(4)
|
||||
,
|
||||
|
||||
Components\Group::make([
|
||||
Infolists\Components\TextEntry::make('status')
|
||||
->label(__('label.user.status'))
|
||||
@@ -174,7 +181,8 @@ class UserResource extends Resource
|
||||
->hintAction(self::buildActionCancelTwoStepAuthentication())
|
||||
,
|
||||
])
|
||||
]),
|
||||
->columnSpan(1)
|
||||
])->columns(5),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
52
app/Http/Controllers/PluginController.php
Normal file
52
app/Http/Controllers/PluginController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Repositories\PluginRepository;
|
||||
use App\Repositories\ToolRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Telegram\Bot\Api;
|
||||
use Telegram\Bot\Commands\HelpCommand;
|
||||
|
||||
class PluginController extends Controller
|
||||
{
|
||||
private $repository;
|
||||
|
||||
public function __construct(PluginRepository $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
|
||||
public function runCommandSSE(Request $request)
|
||||
{
|
||||
$commands = [
|
||||
['pwd'], // Example command 1
|
||||
['ls', '-la'], // Example command 2
|
||||
['composer', '--version'], // Example command 3
|
||||
['composer', 'why', 'ext-zip']
|
||||
];
|
||||
foreach ($commands as $command) {
|
||||
$process = new Process($command);
|
||||
$process->setTimeout(0); // Set timeout for each process
|
||||
|
||||
try {
|
||||
$process->mustRun(function ($type, $buffer) {
|
||||
if (Process::OUT === $type) {
|
||||
echo "Output: " . $buffer; // 实时输出
|
||||
} else { // Process::ERR === $type
|
||||
echo "Error: " . $buffer;
|
||||
}
|
||||
});
|
||||
} catch (\Symfony\Component\Process\Exception\ProcessFailedException $e) {
|
||||
echo "Command failed: " . implode(' ', $command) . "\n";
|
||||
echo $e->getMessage();
|
||||
break; // Stop executing further commands
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
121
app/Http/Controllers/ServerSendEventController.php
Normal file
121
app/Http/Controllers/ServerSendEventController.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\PluginStore;
|
||||
use App\Repositories\ToolRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class ServerSendEventController extends Controller
|
||||
{
|
||||
public function sse(Request $request)
|
||||
{
|
||||
$this->initSSE();
|
||||
$response = response()->stream(function () use ($request) {
|
||||
$action = $request->input('action');
|
||||
$this->sendData("action: $action");
|
||||
try {
|
||||
if ($action == "install_plugin") {
|
||||
$this->checkComposer();
|
||||
$pluginId = $request->input('plugin_id');
|
||||
$this->sendData("pluginId: $pluginId");
|
||||
$this->doActionInstallPlugin($pluginId);
|
||||
} else {
|
||||
throw new \InvalidArgumentException("Invalid action: $action");
|
||||
}
|
||||
}catch (\Throwable $throwable) {
|
||||
$this->sendData("error: " . $throwable->getMessage());
|
||||
$this->sendData("close");
|
||||
}
|
||||
});
|
||||
$response->headers->set('Content-Type', 'text/event-stream');
|
||||
$response->headers->set('Cache-Control', 'no-cache');
|
||||
$response->headers->set('Connection', 'keep-alive');
|
||||
$response->headers->set('X-Accel-Buffering', 'no');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function initSSE(): void
|
||||
{
|
||||
@set_time_limit(0); // Disable time limit
|
||||
|
||||
// Prevent buffering
|
||||
if(function_exists('apache_setenv')){
|
||||
@apache_setenv('no-gzip', 1);
|
||||
}
|
||||
|
||||
@ini_set('zlib.output_compression', 0);
|
||||
@ini_set('implicit_flush', 1);
|
||||
|
||||
while (ob_get_level() != 0) {
|
||||
ob_end_flush();
|
||||
}
|
||||
ob_implicit_flush(1);
|
||||
}
|
||||
|
||||
private function sendData(string $data): void
|
||||
{
|
||||
echo sprintf("data: %s\n\n", $data);
|
||||
@ob_flush();
|
||||
@flush();
|
||||
}
|
||||
|
||||
private function runCommand(string $command): void
|
||||
{
|
||||
$this->sendData("running $command, this may take a while...");
|
||||
$process = Process::fromShellCommandline($command);
|
||||
$process->setWorkingDirectory(base_path());
|
||||
$process->setTimeout(null);
|
||||
$process->mustRun(function ($type, $buffer) {
|
||||
$this->sendData($buffer);
|
||||
});
|
||||
}
|
||||
|
||||
private function doActionInstallPlugin(string $pluginId): void
|
||||
{
|
||||
$pluginInfo = PluginStore::getInfo($pluginId);
|
||||
if (!$pluginInfo) {
|
||||
throw new \InvalidArgumentException("invalid plugin: $pluginId");
|
||||
}
|
||||
$this->sendData(sprintf("going to install plugin: %s, version: %s", $pluginInfo['title'], $pluginInfo['version']));
|
||||
$commands = [];
|
||||
$commands[] = sprintf("composer config repositories.%s git %s", $pluginInfo['plugin_id'], $pluginInfo['remote_url']);
|
||||
$commands[] = sprintf("composer require %s:%s", $pluginInfo['package_name'], $pluginInfo['version']);
|
||||
$commands[] = sprintf("php artisan plugin install %s", $pluginInfo['package_name']);
|
||||
foreach ($commands as $command) {
|
||||
$this->runCommand($command);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkComposer(): void
|
||||
{
|
||||
$this->sendData("checking composer ...");
|
||||
$composerCacheDir = executeCommand("composer config cache-vcs-dir -f " . base_path("composer.json"));
|
||||
$this->sendData("composer cache-vcs-dir: $composerCacheDir");
|
||||
if (!is_dir($composerCacheDir)) {
|
||||
$this->sendData("going to mkdir: $composerCacheDir");
|
||||
try {
|
||||
mkdir($composerCacheDir, 0755, true);
|
||||
$this->sendData("success create composer cache-vcs-dir: $composerCacheDir");
|
||||
} catch (\Throwable $e) {
|
||||
$this->sendData("fail to mkdir: " . $e->getMessage());
|
||||
$this->sendData("Please execute the following command as root user:");
|
||||
$this->sendData(sprintf("mkdir -p %s", $composerCacheDir));
|
||||
throw new \RuntimeException("stop due to composer cache-vcs-dir: $composerCacheDir not exists");
|
||||
}
|
||||
}
|
||||
if (!is_writable($composerCacheDir)) {
|
||||
$this->sendData("cache directory: $composerCacheDir is not writable");
|
||||
$this->sendData("Please execute the following command as root user:");
|
||||
$user = executeCommand("whoami");
|
||||
$this->sendData(sprintf("chown -R %s:%s %s", $user, $user, $composerCacheDir));
|
||||
throw new \RuntimeException("stop due to composer cache-vcs-dir: $composerCacheDir not writable");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\PluginStore;
|
||||
use App\Repositories\ToolRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Telegram\Bot\Api;
|
||||
use Telegram\Bot\Commands\HelpCommand;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use App\Http\Middleware\Filament;
|
||||
use App\Http\Middleware\Locale;
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
@@ -49,7 +50,8 @@ class Kernel extends HttpKernel
|
||||
],
|
||||
'filament' => [
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Filament\Http\Middleware\Authenticate::class,
|
||||
// \Filament\Http\Middleware\Authenticate::class,
|
||||
Filament::class,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
||||
use App\Models\User;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\UnauthorizedException;
|
||||
|
||||
class Admin
|
||||
{
|
||||
|
||||
62
app/Livewire/InstallPluginModal.php
Normal file
62
app/Livewire/InstallPluginModal.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\PluginStore;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class InstallPluginModal extends Component
|
||||
{
|
||||
// public PluginStore $record;
|
||||
|
||||
public $output = ''; // 存储命令输出
|
||||
|
||||
// public function mount(PluginStore $record)
|
||||
// {
|
||||
// $this->recored = $record;
|
||||
// $this->output = sprintf("点击按钮以开始安装 %s ...", $record->title);
|
||||
// }
|
||||
|
||||
public function executeCommand($command)
|
||||
{
|
||||
$this->output = ''; // 清空之前的输出
|
||||
// $command = "whereis composer";
|
||||
// $process = new Process([$command]);
|
||||
// $process->setTimeout(60); // 设置超时时间(秒)
|
||||
//
|
||||
// $process->start(); // 启动异步进程
|
||||
//
|
||||
// foreach ($process as $type => $data) {
|
||||
// if ($type === Process::OUT) {
|
||||
// $this->output .= $data; // 实时追加标准输出
|
||||
// } elseif ($type === Process::ERR) {
|
||||
// $this->output .= "[ERROR]: $data"; // 实时追加错误输出
|
||||
// }
|
||||
//
|
||||
//// $this->dispatch('updateTextarea', $this->output); // 通知前端更新
|
||||
// }
|
||||
$process = new Process(['/usr/local/bin/composer', 'info']);
|
||||
$process->setTimeout(3600); // 可选,设置超时时间
|
||||
$basePath = base_path();
|
||||
do_log("base path: $basePath");
|
||||
$process->setWorkingDirectory($basePath);
|
||||
try {
|
||||
$process->mustRun(function ($type, $buffer) {
|
||||
if (Process::OUT === $type) {
|
||||
$this->output = $buffer;
|
||||
$this->dispatch('updateTextarea', $this->output);
|
||||
} else { // Process::ERR === $type
|
||||
do_log("executeCommand, ERR: " . $buffer, 'error');
|
||||
}
|
||||
});
|
||||
} catch (\Exception $e) {
|
||||
do_log($e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.install-plugin-modal');
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class BonusLogs extends NexusModel
|
||||
{
|
||||
protected $table = 'bonus_logs';
|
||||
@@ -41,6 +43,7 @@ class BonusLogs extends NexusModel
|
||||
|
||||
const BUSINESS_TYPE_ROLE_WORK_SALARY = 1000;
|
||||
const BUSINESS_TYPE_TORRENT_BE_DOWNLOADED = 1001;
|
||||
const BUSINESS_TYPE_RECEIVE_REWARD = 1002;
|
||||
|
||||
public static array $businessTypes = [
|
||||
self::BUSINESS_TYPE_CANCEL_HIT_AND_RUN => ['text' => 'Cancel H&R'],
|
||||
@@ -65,6 +68,7 @@ class BonusLogs extends NexusModel
|
||||
|
||||
self::BUSINESS_TYPE_ROLE_WORK_SALARY => ['text' => 'Role work salary'],
|
||||
self::BUSINESS_TYPE_TORRENT_BE_DOWNLOADED => ['text' => 'Torrent be downloaded'],
|
||||
self::BUSINESS_TYPE_RECEIVE_REWARD => ['text' => 'Receive reward'],
|
||||
];
|
||||
|
||||
public function getBusinessTypeTextAttribute()
|
||||
@@ -102,5 +106,23 @@ class BonusLogs extends NexusModel
|
||||
return $result ?? self::DEFAULT_BONUS_BUY_CHANGE_USERNAME_CARD;
|
||||
}
|
||||
|
||||
public static function add(int $userId, float $old, float $delta, float $new, string $comment, int $businessType)
|
||||
{
|
||||
if (!isset(self::$businessTypes[$businessType])) {
|
||||
throw new \InvalidArgumentException("Invalid business type: $businessType");
|
||||
}
|
||||
$nowStr = Carbon::now()->toDateTimeString();
|
||||
return self::query()->create([
|
||||
'business_type' => $businessType,
|
||||
'uid' => $userId,
|
||||
'old_total_value' => $old,
|
||||
'value' => $delta,
|
||||
'new_total_value' => $new,
|
||||
'comment' => sprintf("[%s] %s", self::$businessTypes[$businessType]['text'], $comment),
|
||||
'created_at' => $nowStr,
|
||||
'updated_at' => $nowStr,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
57
app/Models/PluginStore.php
Normal file
57
app/Models/PluginStore.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\HtmlString;
|
||||
use Sushi\Sushi;
|
||||
|
||||
class PluginStore extends Model
|
||||
{
|
||||
use Sushi;
|
||||
|
||||
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";
|
||||
|
||||
public function getRows()
|
||||
{
|
||||
return Http::get(self::PLUGIN_LIST_API)->json();
|
||||
}
|
||||
|
||||
public function getBlogPostUrl(): string
|
||||
{
|
||||
return sprintf(self::BLOG_POST_URL, $this->post_id);
|
||||
}
|
||||
|
||||
public function getFullDescription(): Htmlable
|
||||
{
|
||||
$url = $this->getBlogPostInfoUrl($this->post_id);
|
||||
$logPrefix = sprintf("post_id: %s, url: %s", $this->post_id, $url);
|
||||
$defaultContent = "无法获取详细信息 ...";
|
||||
try {
|
||||
$result = Http::get($url)->json();
|
||||
do_log("$logPrefix, result: " . json_encode($result));
|
||||
$content = $result['content']['rendered'] ?? $result['message'] ?? $defaultContent;
|
||||
} catch (\Exception $e) {
|
||||
do_log(sprintf(
|
||||
"%s, error: %s",
|
||||
$logPrefix, $e->getMessage() . $e->getTraceAsString()
|
||||
), 'error');
|
||||
$content = $defaultContent;
|
||||
}
|
||||
return new HtmlString($content);
|
||||
}
|
||||
|
||||
private function getBlogPostInfoUrl(int $postId): string
|
||||
{
|
||||
return sprintf(self::BLOG_POST_INFO_API, $postId);
|
||||
}
|
||||
|
||||
public static function getInfo(string $id)
|
||||
{
|
||||
return Http::get(self::PLUGIN_LIST_API . "/plugin/$id")->json();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace App\Models;
|
||||
|
||||
class Post extends NexusModel
|
||||
{
|
||||
public function user()
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'userid');
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
class Torrent extends NexusModel
|
||||
{
|
||||
protected $fillable = [
|
||||
'name', 'filename', 'save_as', 'descr', 'small_descr', 'ori_descr',
|
||||
'name', 'filename', 'save_as', 'small_descr',
|
||||
'category', 'source', 'medium', 'codec', 'standard', 'processing', 'team', 'audiocodec',
|
||||
'size', 'added', 'type', 'numfiles', 'owner', 'nfo', 'sp_state', 'promotion_time_type',
|
||||
'promotion_until', 'anonymous', 'url', 'pos_state', 'cache_stamp', 'picktype', 'picktime',
|
||||
'last_reseed', 'pt_gen', 'technical_info', 'leechers', 'seeders', 'cover', 'last_action',
|
||||
'last_reseed', 'leechers', 'seeders', 'cover', 'last_action',
|
||||
'times_completed', 'approval_status', 'banned', 'visible', 'pos_state_until', 'price',
|
||||
];
|
||||
|
||||
@@ -25,7 +25,6 @@ class Torrent extends NexusModel
|
||||
|
||||
protected $casts = [
|
||||
'added' => 'datetime',
|
||||
'pt_gen' => 'array',
|
||||
'promotion_until' => 'datetime',
|
||||
'pos_state_until' => 'datetime',
|
||||
];
|
||||
@@ -241,7 +240,7 @@ class Torrent extends NexusModel
|
||||
|
||||
public static function getFieldsForList($appendTableName = false): array|bool
|
||||
{
|
||||
$fields = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, pt_gen, hr, approval_status, cover, price';
|
||||
$fields = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, hr, approval_status, cover, price';
|
||||
$fields = preg_split('/[,\s]+/', $fields);
|
||||
if ($appendTableName) {
|
||||
foreach ($fields as &$value) {
|
||||
@@ -513,4 +512,9 @@ class Torrent extends NexusModel
|
||||
{
|
||||
return $this->hasMany(TorrentOperationLog::class, 'torrent_id');
|
||||
}
|
||||
|
||||
public function extra(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(TorrentExtra::class, 'torrent_id');
|
||||
}
|
||||
}
|
||||
|
||||
18
app/Models/TorrentExtra.php
Normal file
18
app/Models/TorrentExtra.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Nexus\Database\NexusDB;
|
||||
|
||||
class TorrentExtra extends NexusModel
|
||||
{
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = ['torrent_id', 'descr', 'ori_descr', 'media_info'];
|
||||
|
||||
public function torrent()
|
||||
{
|
||||
return $this->belongsTo(Torrent::class, 'torrent_id');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -182,9 +182,9 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'modcomment', 'enabled', 'status',
|
||||
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'enabled', 'status',
|
||||
'leechwarn', 'leechwarnuntil', 'page', 'class', 'uploaded', 'downloaded', 'clientselect', 'showclienterror', 'last_home',
|
||||
'seedbonus', 'bonuscomment', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
|
||||
'seedbonus', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
|
||||
'seed_points_per_hour', 'passkey',
|
||||
];
|
||||
|
||||
@@ -229,7 +229,7 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
'uploaded', 'downloaded', 'seedbonus', 'seedtime', 'leechtime',
|
||||
'invited_by', 'enabled', 'seed_points', 'last_access', 'invites',
|
||||
'lang', 'attendance_card', 'privacy', 'noad', 'downloadpos', 'donoruntil', 'donor',
|
||||
'bonuscomment', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
|
||||
'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
|
||||
'seed_points_per_hour'
|
||||
];
|
||||
|
||||
@@ -535,6 +535,11 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
return $this->examAndTasks()->wherePivot("status", ExamUser::STATUS_NORMAL);
|
||||
}
|
||||
|
||||
public function modifyLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(UserModifyLog::class, "user_id");
|
||||
}
|
||||
|
||||
public function getAvatarAttribute($value)
|
||||
{
|
||||
if ($value) {
|
||||
@@ -560,10 +565,17 @@ class User extends Authenticatable implements FilamentUser, HasName
|
||||
throw new \RuntimeException('This method only works when user exists !');
|
||||
}
|
||||
//@todo how to do prepare bindings here ?
|
||||
$comment = addslashes($comment);
|
||||
do_log("update: " . json_encode($update) . ", $commentField: $comment", 'notice');
|
||||
$update[$commentField] = NexusDB::raw("if($commentField = '', '$comment', concat_ws('\n', '$comment', $commentField))");
|
||||
return $this->update($update);
|
||||
// $comment = addslashes($comment);
|
||||
// do_log("update: " . json_encode($update) . ", $commentField: $comment", 'notice');
|
||||
// $update[$commentField] = NexusDB::raw("if($commentField = '', '$comment', concat_ws('\n', '$comment', $commentField))");
|
||||
|
||||
if ($commentField != "modcomment") {
|
||||
throw new \RuntimeException("unsupported commentField: $commentField !");
|
||||
}
|
||||
return NexusDB::transaction(function () use ($update, $comment) {
|
||||
$this->modifyLogs()->create(['content' => $comment]);
|
||||
return $this->update($update);
|
||||
});
|
||||
}
|
||||
|
||||
public function canAccessAdmin(): bool
|
||||
|
||||
16
app/Models/UserModifyLog.php
Normal file
16
app/Models/UserModifyLog.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class UserModifyLog extends NexusModel
|
||||
{
|
||||
protected $fillable = ['user_id', 'content', ];
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, "user_id");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,8 @@ use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Filament\Tables\Enums\FiltersLayout;
|
||||
use Filament\Tables\Table;
|
||||
use Livewire\Livewire;
|
||||
use NexusPlugin\TelegramBot\Filament\TelegramBotBindsResource;
|
||||
use NexusPlugin\TelegramBot\Filament\TelegramBotResource;
|
||||
|
||||
class AppPanelProvider extends PanelProvider
|
||||
{
|
||||
@@ -29,7 +31,7 @@ class AppPanelProvider extends PanelProvider
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('app')
|
||||
->id('admin')
|
||||
->homeUrl("/")
|
||||
->sidebarWidth("15rem")
|
||||
->topbar(true)
|
||||
@@ -40,6 +42,10 @@ class AppPanelProvider extends PanelProvider
|
||||
->colors([
|
||||
'primary' => Color::Amber,
|
||||
])
|
||||
->resources([
|
||||
// TelegramBotResource::class,
|
||||
// TelegramBotBindsResource::class
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
@@ -51,6 +57,7 @@ class AppPanelProvider extends PanelProvider
|
||||
// Widgets\AccountWidget::class,
|
||||
// Widgets\FilamentInfoWidget::class,
|
||||
])
|
||||
->discoverClusters(app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters')
|
||||
->middleware([
|
||||
// EncryptCookies::class,
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Http\Middleware\Filament;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -47,14 +48,15 @@ class RouteServiceProvider extends ServiceProvider
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web.php'));
|
||||
|
||||
Route::prefix('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/tracker.php'));
|
||||
|
||||
Route::prefix('api')
|
||||
->namespace($this->namespace)
|
||||
->middleware('throttle:third-party')
|
||||
->group(base_path('routes/third-party.php'));
|
||||
|
||||
Route::prefix('admin')
|
||||
->namespace($this->namespace)
|
||||
->middleware([Filament::class])
|
||||
->group(base_path('routes/admin.php'));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -381,6 +381,14 @@ class TorrentRepository extends BaseRepository
|
||||
return md5($passkey . date('Ymd') . $user['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param $id
|
||||
* @param $uid
|
||||
* @param $initializeIfNotExists
|
||||
* @return string
|
||||
* @throws NexusException
|
||||
*/
|
||||
public function getTrackerReportAuthKey($id, $uid, $initializeIfNotExists = false): string
|
||||
{
|
||||
$key = $this->getTrackerReportAuthKeySecret($id, $uid, $initializeIfNotExists);
|
||||
@@ -389,6 +397,8 @@ class TorrentRepository extends BaseRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* check tracker report authkey
|
||||
* if valid, the result will be the date the key generate, else if will be empty string
|
||||
*
|
||||
|
||||
@@ -227,6 +227,13 @@ class TrackerRepository extends BaseRepository
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @param $authkey
|
||||
* @return array
|
||||
* @throws TrackerException
|
||||
*/
|
||||
protected function checkAuthkey($authkey)
|
||||
{
|
||||
$arr = explode('|', $authkey);
|
||||
|
||||
Reference in New Issue
Block a user