mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-03 14:10:57 +08:00
Merge remote-tracking branch 'origin/php8' into php8
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\TorrentCustomFields\Pages;
|
||||
|
||||
use App\Filament\Resources\TorrentCustomFields\TorrentCustomFieldResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListTorrentCustomFields extends ListRecords
|
||||
{
|
||||
protected static string $resource = TorrentCustomFieldResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\TorrentCustomFields\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
use Nexus\Field\Field;
|
||||
|
||||
class TorrentCustomFieldForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('label.field.name'))
|
||||
->helperText(__('label.field.name_help'))
|
||||
->alphaDash()
|
||||
->required(),
|
||||
TextInput::make('label')
|
||||
->label(__('label.field.field_label'))
|
||||
->required(),
|
||||
Select::make('type')
|
||||
->options((new Field())->getTypeRadioOptions())
|
||||
->label(__('label.field.type'))
|
||||
->required(),
|
||||
Checkbox::make('required')
|
||||
->label(__('label.field.required')),
|
||||
Textarea::make('help')
|
||||
->label(__('label.field.help'))
|
||||
->rows(3),
|
||||
Textarea::make('options')
|
||||
->label(__('label.field.options'))
|
||||
->rows(3)
|
||||
->hiddenJs("\$get('type') !== 'radio' && \$get('type') !== 'checkbox' && \$get('type') !== 'select'")
|
||||
->helperText(__('label.field.options_help')),
|
||||
Checkbox::make('is_single_row')
|
||||
->label(__('label.field.is_single_row')),
|
||||
TextInput::make('priority')
|
||||
->label(__('label.priority'))
|
||||
->numeric(),
|
||||
Textarea::make('display')
|
||||
->label(__('label.field.display'))
|
||||
->rows(3)
|
||||
->helperText(__('label.search_box.custom_fields_display_help')),
|
||||
|
||||
])
|
||||
->columns(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\TorrentCustomFields\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class TorrentCustomFieldsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('id'),
|
||||
TextColumn::make('name')->label(__('label.field.name')),
|
||||
TextColumn::make('label')->label(__('label.field.field_label')),
|
||||
TextColumn::make('type')->label(__('label.field.type')),
|
||||
IconColumn::make('required')->boolean()->label(__('label.field.required')),
|
||||
IconColumn::make('is_single_row')->boolean()->label(__('label.field.is_single_row')),
|
||||
TextColumn::make('priority')->label(__('label.priority')),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\TorrentCustomFields;
|
||||
|
||||
use App\Filament\Resources\TorrentCustomFields\Pages\ListTorrentCustomFields;
|
||||
use App\Filament\Resources\TorrentCustomFields\Schemas\TorrentCustomFieldForm;
|
||||
use App\Filament\Resources\TorrentCustomFields\Tables\TorrentCustomFieldsTable;
|
||||
use App\Models\TorrentCustomField;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
|
||||
class TorrentCustomFieldResource extends Resource
|
||||
{
|
||||
protected static ?string $model = TorrentCustomField::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
protected static string|null|UnitEnum $navigationGroup = 'Section';
|
||||
|
||||
protected static ?int $navigationSort = 12;
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('label.field.label');
|
||||
}
|
||||
|
||||
public static function getBreadcrumb(): string
|
||||
{
|
||||
return self::getNavigationLabel();
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return TorrentCustomFieldForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return TorrentCustomFieldsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListTorrentCustomFields::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
102
app/Filament/Resources/User/UserPasskeyResource.php
Normal file
102
app/Filament/Resources/User/UserPasskeyResource.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\User;
|
||||
|
||||
use App\Filament\Resources\User\UserPasskeyResource\Pages;
|
||||
use App\Models\Passkey;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class UserPasskeyResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Passkey::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-key';
|
||||
|
||||
protected static ?string $navigationGroup = 'User';
|
||||
|
||||
protected static ?int $navigationSort = 12;
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('passkey.passkey');
|
||||
}
|
||||
|
||||
public static function getBreadcrumb(): string
|
||||
{
|
||||
return self::getNavigationLabel();
|
||||
}
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')->sortable()
|
||||
,
|
||||
Tables\Columns\TextColumn::make('user_id')
|
||||
->formatStateUsing(fn($state) => username_for_admin($state))
|
||||
->label(__('label.username'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('AAGUID')
|
||||
->label("AAGUID")
|
||||
,
|
||||
Tables\Columns\TextColumn::make('credential_id')
|
||||
->label(__('passkey.fields.credential_id'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('counter')
|
||||
->label(__('passkey.fields.counter'))
|
||||
,
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->label(__('label.created_at'))
|
||||
,
|
||||
])
|
||||
->defaultSort('id', 'desc')
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('user_id')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('uid')
|
||||
->label(__('label.username'))
|
||||
->placeholder('UID')
|
||||
,
|
||||
])->query(function (Builder $query, array $data) {
|
||||
return $query->when($data['uid'], fn(Builder $query, $value) => $query->where("user_id", $value));
|
||||
})
|
||||
,
|
||||
Tables\Filters\Filter::make('credential_id')
|
||||
->form([
|
||||
Forms\Components\TextInput::make('credential_id')
|
||||
->label(__('passkey.fields.credential_id'))
|
||||
->placeholder('Credential ID')
|
||||
,
|
||||
])->query(function (Builder $query, array $data) {
|
||||
return $query->when($data['credential_id'], fn(Builder $query, $value) => $query->where("credential_id", $value));
|
||||
})
|
||||
,
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ManageUserPasskey::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\User\UserPasskeyResource\Pages;
|
||||
|
||||
use App\Filament\PageListSingle;
|
||||
use App\Filament\Resources\User\UserPasskeyResource;
|
||||
|
||||
class ManageUserPasskey extends PageListSingle
|
||||
{
|
||||
protected static string $resource = UserPasskeyResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class CalculateUserSeedBonus implements ShouldQueue
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public $timeout = 3600;
|
||||
public $timeout = 120;
|
||||
|
||||
/**
|
||||
* 获取任务时,应该通过的中间件。
|
||||
@@ -72,6 +72,7 @@ class CalculateUserSeedBonus implements ShouldQueue
|
||||
$this->requestId, $this->beginUid, $this->endUid, $this->idStr, $this->idRedisKey
|
||||
);
|
||||
do_log("$logPrefix, job start ...");
|
||||
|
||||
$haremAdditionFactor = Setting::get('bonus.harem_addition');
|
||||
$officialAdditionFactor = Setting::get('bonus.official_addition');
|
||||
$donortimes_bonus = Setting::get('bonus.donortimes');
|
||||
|
||||
32
app/Models/Passkey.php
Normal file
32
app/Models/Passkey.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Passkey extends NexusModel
|
||||
{
|
||||
protected $table = 'user_passkeys';
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = [
|
||||
'id', 'user_id', 'AAGUID', 'credential_id', 'public_key', 'counter',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function AAGUID() {
|
||||
$guid = $this->AAGUID;
|
||||
return sprintf(
|
||||
'%s-%s-%s-%s-%s',
|
||||
substr($guid, 0, 8),
|
||||
substr($guid, 8, 4),
|
||||
substr($guid, 12, 4),
|
||||
substr($guid, 16, 4),
|
||||
substr($guid, 20, 12)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use App\Models\SecondIcon;
|
||||
use App\Models\Source;
|
||||
use App\Models\Standard;
|
||||
use App\Models\Team;
|
||||
use App\Models\TorrentCustomField;
|
||||
use App\Models\User;
|
||||
use App\Policies\CodecPolicy;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
@@ -34,6 +35,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
Category::class => CodecPolicy::class,
|
||||
Icon::class => CodecPolicy::class,
|
||||
SecondIcon::class => CodecPolicy::class,
|
||||
TorrentCustomField::class => CodecPolicy::class,
|
||||
|
||||
Codec::class => CodecPolicy::class,
|
||||
AudioCodec::class => CodecPolicy::class,
|
||||
|
||||
@@ -37,7 +37,7 @@ class CleanupRepository extends BaseRepository
|
||||
|
||||
private static int $oneTaskSeconds = 0;
|
||||
|
||||
private static int $scanSize = 1000;
|
||||
private static int $scanSize = 500;
|
||||
|
||||
public static function recordBatch(\Redis $redis, $uid, $torrentId)
|
||||
{
|
||||
|
||||
252
app/Repositories/UserPasskeyRepository.php
Normal file
252
app/Repositories/UserPasskeyRepository.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Passkey;
|
||||
use Exception;
|
||||
use lbuchs\WebAuthn\WebAuthn;
|
||||
use Nexus\Database\NexusDB;
|
||||
use Nexus\Nexus;
|
||||
use RuntimeException;
|
||||
|
||||
class UserPasskeyRepository extends BaseRepository
|
||||
{
|
||||
|
||||
public static function createWebAuthn()
|
||||
{
|
||||
$formats = ['android-key', 'android-safetynet', 'apple', 'fido-u2f', 'packed', 'tpm', 'none'];
|
||||
return new WebAuthn(get_setting('basic.SITENAME'), nexus()->getRequestHost(), $formats);
|
||||
}
|
||||
|
||||
public static function getCreateArgs($userId, $userName)
|
||||
{
|
||||
$WebAuthn = self::createWebAuthn();
|
||||
|
||||
$passkey = Passkey::query()->where('user_id', '=', $userId)->get();
|
||||
|
||||
$credentialIds = array_map(function ($item) {
|
||||
return hex2bin($item['credential_id']);
|
||||
}, $passkey->toArray());
|
||||
|
||||
$createArgs = $WebAuthn->getCreateArgs(bin2hex($userId), $userName, $userName, 60 * 4, true, 'preferred', null, $credentialIds);
|
||||
|
||||
NexusDB::cache_put("{$userId}_passkey_challenge", $WebAuthn->getChallenge(), 60 * 4);
|
||||
|
||||
return $createArgs;
|
||||
}
|
||||
|
||||
public static function processCreate($userId, $clientDataJSON, $attestationObject)
|
||||
{
|
||||
$WebAuthn = self::createWebAuthn();
|
||||
|
||||
$clientDataJSON = !empty($clientDataJSON) ? base64_decode($clientDataJSON) : null;
|
||||
$attestationObject = !empty($attestationObject) ? base64_decode($attestationObject) : null;
|
||||
$challenge = NexusDB::cache_get("{$userId}_passkey_challenge") ?? null;
|
||||
|
||||
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $challenge, false, true, false);
|
||||
|
||||
self::insertUserPasskey(
|
||||
$userId,
|
||||
bin2hex($data->AAGUID),
|
||||
bin2hex($data->credentialId),
|
||||
$data->credentialPublicKey,
|
||||
$data->signatureCounter
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getGetArgs()
|
||||
{
|
||||
$WebAuthn = self::createWebAuthn();
|
||||
|
||||
$getArgs = $WebAuthn->getGetArgs(null, 60 * 4, true, true, true, true, true, 'preferred');
|
||||
|
||||
return $getArgs;
|
||||
}
|
||||
|
||||
public static function insertUserPasskey($userId, $AAGUID, $credentialId, $publicKey, $counter)
|
||||
{
|
||||
$params = [
|
||||
'user_id' => $userId,
|
||||
'AAGUID' => $AAGUID,
|
||||
'credential_id' => $credentialId,
|
||||
'public_key' => $publicKey,
|
||||
'counter' => $counter,
|
||||
];
|
||||
Passkey::query()->create($params);
|
||||
}
|
||||
|
||||
public static function processGet($challenge, $id, $clientDataJSON, $authenticatorData, $signature, $userHandle)
|
||||
{
|
||||
$WebAuthn = self::createWebAuthn();
|
||||
|
||||
$clientDataJSON = !empty($clientDataJSON) ? base64_decode($clientDataJSON) : null;
|
||||
$id = !empty($id) ? base64_decode($id) : null;
|
||||
$authenticatorData = !empty($authenticatorData) ? base64_decode($authenticatorData) : null;
|
||||
$signature = !empty($signature) ? base64_decode($signature) : null;
|
||||
$userHandle = !empty($userHandle) ? base64_decode($userHandle) : null;
|
||||
$challenge = !empty($challenge) ? base64_decode($challenge) : null;
|
||||
|
||||
$passkey = Passkey::query()->where('credential_id', '=', bin2hex($id))->first();
|
||||
$credentialPublicKey = $passkey->public_key;
|
||||
|
||||
if ($credentialPublicKey === null) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_unknown'));
|
||||
}
|
||||
|
||||
if ($userHandle !== bin2hex($passkey->user_id)) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_invalid'));
|
||||
}
|
||||
|
||||
try {
|
||||
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, 'preferred');
|
||||
} catch (Exception $e) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_error') . "\n" . $e->getMessage());
|
||||
}
|
||||
|
||||
$user = $passkey->user;
|
||||
if (!$user) {
|
||||
throw new RuntimeException(nexus_trans('passkey.passkey_user_not_found'));
|
||||
}
|
||||
$user->checkIsNormal();
|
||||
|
||||
$ip = getip();
|
||||
$userRep = new UserRepository();
|
||||
$userRep->saveLoginLog($user->id, $ip, 'Web', true);
|
||||
|
||||
logincookie($user->id, $user->auth_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function delete($userId, $credentialId)
|
||||
{
|
||||
return Passkey::query()->where('user_id', '=', $userId)->where('credential_id', '=', $credentialId)->delete();
|
||||
}
|
||||
|
||||
public static function getList($userId)
|
||||
{
|
||||
return Passkey::query()->where('user_id', '=', $userId)->get();
|
||||
}
|
||||
|
||||
public static function getAaguids()
|
||||
{
|
||||
return NexusDB::remember("aaguids", 60 * 60 * 24 * 14, function () {
|
||||
return json_decode(file_get_contents("https://raw.githubusercontent.com/passkeydeveloper/passkey-authenticator-aaguids/refs/heads/main/combined_aaguid.json"), true);
|
||||
});
|
||||
}
|
||||
|
||||
private static $passkeyvg = 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20216%20216%22%20xml%3Aspace%3D%22preserve%22%3E%3Cg%2F%3E%3Cpath%20style%3D%22fill%3Anone%22%20d%3D%22M0%200h216v216H0z%22%2F%3E%3Cpath%20d%3D%22M172.32%2096.79c0%2013.78-8.48%2025.5-20.29%2029.78l7.14%2011.83-10.57%2013%2010.57%2012.71-17.04%2022.87-12.01-12.82V125.7c-10.68-4.85-18.15-15.97-18.15-28.91%200-17.4%2013.51-31.51%2030.18-31.51%2016.66%200%2030.17%2014.11%2030.17%2031.51m-30.18%204.82c4.02%200%207.28-3.4%207.28-7.6s-3.26-7.61-7.28-7.61-7.28%203.4-7.28%207.61c-.01%204.2%203.26%207.6%207.28%207.6%22%20style%3D%22fill-rule%3Aevenodd%3Bclip-rule%3Aevenodd%3Bfill%3A%23353535%22%2F%3E%3Cpath%20d%3D%22M172.41%2096.88c0%2013.62-8.25%2025.23-19.83%2029.67l6.58%2011.84-9.73%2013%209.73%2012.71-17.03%2023.05v-85.54c4.02%200%207.28-3.41%207.28-7.6%200-4.2-3.26-7.61-7.28-7.61V65.28c16.73%200%2030.28%2014.15%2030.28%2031.6m-52.17%2034.55c-9.75-8-16.3-20.3-17.2-34.27H50.8c-10.96%200-19.84%209.01-19.84%2020.13v25.17c0%205.56%204.44%2010.07%209.92%2010.07h69.44c5.48%200%209.92-4.51%209.92-10.07z%22%20style%3D%22fill-rule%3Aevenodd%3Bclip-rule%3Aevenodd%22%2F%3E%3Cpath%20d%3D%22M73.16%2091.13c-2.42-.46-4.82-.89-7.11-1.86-8.65-3.63-13.69-10.32-15.32-19.77-1.12-6.47-.59-12.87%202.03-18.92%203.72-8.6%2010.39-13.26%2019.15-14.84%205.24-.94%2010.46-.73%2015.5%201.15%207.59%202.82%2012.68%208.26%2015.03%2016.24%202.38%208.05%202.03%2016.1-1.56%2023.72-3.72%207.96-10.21%2012.23-18.42%2013.9-.68.14-1.37.27-2.05.41-2.41-.03-4.83-.03-7.25-.03%22%20style%3D%22fill%3A%23141313%22%2F%3E%3C%2Fsvg%3E';
|
||||
|
||||
public static function renderLogin()
|
||||
{
|
||||
printf('<p id="passkey_box"><button type="button" id="passkey_login"><img style="width:32px" src="%s" alt="%s"><br>%s</button></p>', self::$passkeyvg, nexus_trans('passkey.passkey'), nexus_trans('passkey.passkey'));
|
||||
?>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
if (Passkey.conditionalSupported()) {
|
||||
Passkey.isCMA().then(async (isCMA) => {
|
||||
if (isCMA) startPasskeyLogin(true);
|
||||
});
|
||||
}
|
||||
document.getElementById('passkey_login').addEventListener('click', () => {
|
||||
if (!Passkey.supported()) {
|
||||
layer.alert('<?php echo nexus_trans('passkey.passkey_not_supported'); ?>');
|
||||
} else {
|
||||
startPasskeyLogin(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
const startPasskeyLogin = (conditional) => {
|
||||
Passkey.checkRegistration(conditional, () => {
|
||||
layer.load(2, {shade: 0.3});
|
||||
}).then(() => {
|
||||
if (location.search) {
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
location.href = searchParams.get('returnto') || '/index.php';
|
||||
} else {
|
||||
location.href = '/index.php';
|
||||
}
|
||||
}).catch((e) => {
|
||||
if (e.name === 'NotAllowedError' || e.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
layer.alert(e.message);
|
||||
}).finally(() => {
|
||||
layer.closeAll('loading');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function renderList($id)
|
||||
{
|
||||
$passkeys = self::getList($id);
|
||||
printf('<button type="button" id="passkey_create">%s</button><br>%s', nexus_trans('passkey.passkey_create'), nexus_trans('passkey.passkey_desc'));
|
||||
?>
|
||||
<table>
|
||||
<?php
|
||||
if (empty($passkeys)) {
|
||||
printf('<tr><td>%s</td></tr>', nexus_trans('passkey.passkey_empty'));
|
||||
} else {
|
||||
$AAGUIDS = self::getAaguids();
|
||||
foreach ($passkeys as $passkey) {
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;padding:4px">
|
||||
<?php
|
||||
$meta = $AAGUIDS[$passkey->AAGUID()];
|
||||
if (isset($meta)) {
|
||||
printf('<img style="width: 32px" src="%s" alt="%s" /><div style="margin-right:4px"><b>%s</b> (%s)', $meta['icon_dark'], $meta['name'], $meta['name'], $passkey->credential_id);
|
||||
} else {
|
||||
printf('<img style="width: 32px" src="%s" alt="%s" /><div style="margin-right:4px"><b>%s</b>', self::$passkeyvg, $passkey->credential_id, $passkey->credential_id);
|
||||
}
|
||||
printf('<br><b>%s</b>%s</div>', nexus_trans('passkey.passkey_created_at'), gettime($passkey->created_at));
|
||||
printf('<button type="button" style="margin-left:auto" data-passkey-id="%s">%s</button>', $passkey->credential_id, nexus_trans('passkey.passkey_delete'))
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
} ?>
|
||||
</table>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.getElementById('passkey_create').addEventListener('click', () => {
|
||||
if (!Passkey.supported()) {
|
||||
layer.alert('<?php echo nexus_trans('passkey.passkey_not_supported'); ?>');
|
||||
} else {
|
||||
layer.load(2, {shade: 0.3});
|
||||
Passkey.createRegistration().then(() => {
|
||||
location.reload();
|
||||
}).catch((e) => {
|
||||
layer.alert(e.message);
|
||||
}).finally(() => {
|
||||
layer.closeAll('loading');
|
||||
});
|
||||
}
|
||||
})
|
||||
document.querySelectorAll('button[data-passkey-id]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const credentialId = button.getAttribute('data-passkey-id');
|
||||
layer.confirm('<?php echo nexus_trans('passkey.passkey_delete_confirm'); ?>', {}, function () {
|
||||
layer.load(2, {shade: 0.3});
|
||||
Passkey.deleteRegistration(credentialId).then(() => {
|
||||
location.reload();
|
||||
}).catch((e) => {
|
||||
layer.alert(e.message);
|
||||
}).finally(() => {
|
||||
layer.closeAll('loading');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
Nexus::js('js/passkey.js', 'footer', true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user