mirror of
https://github.com/pelican-dev/panel.git
synced 2026-07-23 23:53:58 +03:00
Compare commits
7 Commits
v1.0.0-bet
...
lance/impr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d7675caca | ||
|
|
f0dfadda98 | ||
|
|
d1b7ac026b | ||
|
|
476ce81ebc | ||
|
|
07f1dfe8b8 | ||
|
|
2fd3833e95 | ||
|
|
234d4f0c0d |
41
.github/workflows/docker-publish.yml
vendored
41
.github/workflows/docker-publish.yml
vendored
@@ -7,8 +7,6 @@ on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
@@ -77,29 +75,8 @@ jobs:
|
||||
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
|
||||
if: "!contains(github.ref, 'main') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||
steps:
|
||||
# Fetch the latest release tag from GitHub API before checkout and metadata so we can check out the correct ref and tag the image
|
||||
- name: Get latest release tag (scheduled)
|
||||
id: release_info
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
LATEST_TAG=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -w "\n%{http_code}" https://api.github.com/repos/${{ github.repository }}/releases/latest | tail -1 | head -1)
|
||||
HTTP_CODE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -w "%{http_code}" -o /dev/null https://api.github.com/repos/${{ github.repository }}/releases/latest)
|
||||
if [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "Failed to fetch latest release (HTTP $HTTP_CODE)"
|
||||
exit 1
|
||||
fi
|
||||
LATEST_TAG=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r '.tag_name')
|
||||
if [ "$LATEST_TAG" = "null" ] || [ -z "$LATEST_TAG" ]; then
|
||||
echo "No release found"
|
||||
exit 1
|
||||
fi
|
||||
echo "version_tag=${LATEST_TAG#v}" >> $GITHUB_OUTPUT
|
||||
echo "schedule_tag=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ steps.release_info.outputs.schedule_tag || github.ref }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: docker_meta
|
||||
@@ -109,8 +86,7 @@ jobs:
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ (github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false) || github.event_name == 'schedule' }}
|
||||
type=raw,value=${{ steps.release_info.outputs.version_tag }},enable=${{ github.event_name == 'schedule' }}
|
||||
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.action == 'published' && github.event.release.prerelease == false }}
|
||||
type=ref,event=tag
|
||||
type=ref,event=branch
|
||||
|
||||
@@ -133,7 +109,7 @@ jobs:
|
||||
- name: Get Build Information
|
||||
id: build_info
|
||||
run: |
|
||||
echo "version_tag=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||
echo "version_tag=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_OUTPUT
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
# Download the base PHP image AMD64
|
||||
@@ -157,25 +133,20 @@ jobs:
|
||||
rm base-php-arm64.tar base-php-amd64.tar
|
||||
|
||||
- name: Update version in config/app.php (tag)
|
||||
if: "(github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'schedule'"
|
||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||
run: |
|
||||
VERSION="${{ github.event_name == 'schedule' && steps.release_info.outputs.version_tag || steps.build_info.outputs.version_tag }}"
|
||||
if [ -z "$VERSION" ] || [ "$VERSION" = "refs/heads/main" ]; then
|
||||
echo "Invalid version tag, refusing to update config"
|
||||
exit 1
|
||||
fi
|
||||
sed -i "s|'version' => 'canary',|'version' => '$VERSION',|" config/app.php
|
||||
sed -i "s|'version' => 'canary',|'version' => '${{ steps.build_info.outputs.version_tag }}',|" config/app.php
|
||||
|
||||
- name: Build and Push (tag)
|
||||
uses: docker/build-push-action@v7
|
||||
if: "(github.event_name == 'release' && github.event.action == 'published') || github.event_name == 'schedule'"
|
||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: 'linux/amd64,linux/arm64'
|
||||
build-args: |
|
||||
VERSION=${{ github.event_name == 'schedule' && steps.release_info.outputs.version_tag || steps.build_info.outputs.version_tag }}
|
||||
VERSION=${{ steps.build_info.outputs.version_tag }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
cache-from: type=gha,scope=tagged${{ matrix.os }}
|
||||
|
||||
30
.github/workflows/download-translations.yml
vendored
30
.github/workflows/download-translations.yml
vendored
@@ -16,8 +16,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download Translations from Crowdin
|
||||
uses: crowdin/github-action@v2
|
||||
@@ -26,29 +24,13 @@ jobs:
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
export_only_approved: true
|
||||
push_translations: false
|
||||
create_pull_request: false
|
||||
localization_branch_name: i18n_crowdin_translations
|
||||
create_pull_request: true
|
||||
pull_request_title: 'New Crowdin Translations'
|
||||
pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)'
|
||||
pull_request_base_branch_name: 'main'
|
||||
crowdin_branch_name: main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: Run Laravel Pint
|
||||
uses: aglipanci/laravel-pint-action@8eedec46a1856977c41667f9c66d5562fa3f9c08 # 2.4
|
||||
|
||||
# The Crowdin and Pint steps run in Docker as root, so new translation
|
||||
# files land owned by root. Reclaim them so the host-side git operations
|
||||
# in create-pull-request can manage the working tree.
|
||||
- name: Fix file ownership
|
||||
run: sudo chown -R "$(id -u):$(id -g)" "${{ github.workspace }}"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: update translations and format with pint'
|
||||
title: 'New Crowdin Translations'
|
||||
body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action), formatted by Laravel Pint.'
|
||||
branch: 'i18n_crowdin_translations'
|
||||
base: 'main'
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
@@ -1,152 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands\Egg;
|
||||
|
||||
use App\Enums\EggFormat;
|
||||
use App\Exceptions\Service\InvalidFileUploadException;
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class NormalizeEggCommand extends Command
|
||||
{
|
||||
protected $description = 'Upgrades egg json/yaml to latest format as yaml';
|
||||
|
||||
protected $signature = 'p:egg:normalize {file} {--delete-original}';
|
||||
|
||||
public function __construct(
|
||||
private EggExporterService $exporter,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(EggImporterService $importer): int
|
||||
{
|
||||
$inputFile = $this->argument('file');
|
||||
$unparsed = file_get_contents($inputFile);
|
||||
if ($unparsed === false) {
|
||||
$this->error("Failed to read file: {$inputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$extension = strtolower(pathinfo($inputFile, PATHINFO_EXTENSION));
|
||||
|
||||
$this->info("Importing {$inputFile}");
|
||||
|
||||
$format = match ($extension) {
|
||||
'yaml', 'yml' => EggFormat::YAML,
|
||||
'json' => EggFormat::JSON,
|
||||
default => null,
|
||||
};
|
||||
if (is_null($format)) {
|
||||
$this->error(" -> unsupported extension {$extension} for {$inputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
$eggArray = $importer->parse($unparsed, $format);
|
||||
} catch (InvalidFileUploadException) {
|
||||
$this->error(" -> unsupported file version, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (
|
||||
!array_key_exists('meta', $eggArray) ||
|
||||
!is_array($eggArray['meta'])
|
||||
) {
|
||||
$this->error(" -> does not contain existing meta or meta is not array, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (!array_key_exists('exported_at', $eggArray)) {
|
||||
$this->error(" -> does not contain existing exported_at, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (
|
||||
!array_key_exists('update_url', $eggArray['meta']) ||
|
||||
!is_string($eggArray['meta']['update_url'])
|
||||
) {
|
||||
$this->error(" -> does not contain existing meta.update_url or is not a string, is it actually an egg? ({$inputFile})");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// We upgraded our in-memory array when we imported, so set to latest version before export
|
||||
$eggArray['meta']['version'] = Egg::EXPORT_VERSION;
|
||||
$eggArray['meta']['update_url'] = self::replaceExtension($eggArray['meta']['update_url']);
|
||||
self::fixVariableRules($eggArray);
|
||||
|
||||
$outputFile = self::replaceExtension($inputFile);
|
||||
|
||||
if ($outputFile === $inputFile && !$this->hasFileChanged($unparsed, $eggArray)) {
|
||||
$this->info(' -> no changes required');
|
||||
|
||||
return Command::SUCCESS;
|
||||
} else {
|
||||
$eggArray['exported_at'] = Carbon::now()->toAtomString();
|
||||
}
|
||||
|
||||
$this->info(" -> exporting to {$outputFile}");
|
||||
$yaml = $this->eggToYaml($eggArray);
|
||||
if (file_put_contents($outputFile, $yaml) === false) {
|
||||
$this->error(" -> failed to write output file: {$outputFile}");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if ($this->option('delete-original') && $outputFile !== $inputFile) {
|
||||
$this->info(' -> deleting input file as requested');
|
||||
if (!unlink($inputFile)) {
|
||||
$this->warn(" -> failed to delete original file: {$inputFile}");
|
||||
}
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private static function replaceExtension(string $path): string
|
||||
{
|
||||
return preg_replace('/^(.*\.)(?:yml|json|yaml)$/', '$1yaml', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private static function fixVariableRules(array &$eggArray): void
|
||||
{
|
||||
if (!array_key_exists('variables', $eggArray)) {
|
||||
return;
|
||||
}
|
||||
foreach ($eggArray['variables'] as &$var) {
|
||||
unset($var['field_type']);
|
||||
if (!array_key_exists('rules', $var)) {
|
||||
continue;
|
||||
}
|
||||
$var['rules'] = is_array($var['rules']) ? $var['rules'] : explode('|', $var['rules']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private function hasFileChanged(string $unparsed, array $eggArray): bool
|
||||
{
|
||||
$yaml = $this->eggToYaml($eggArray);
|
||||
|
||||
return $unparsed !== $yaml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $eggArray
|
||||
*/
|
||||
private function eggToYaml(array $eggArray): string
|
||||
{
|
||||
return Yaml::dump($this->exporter->yamlExport($eggArray), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK | Yaml::DUMP_OBJECT_AS_MAP);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum NodeJwtScope: string
|
||||
{
|
||||
case BackupDownload = 'backup-download';
|
||||
case FileDownload = 'file-download';
|
||||
case FileUpload = 'file-upload';
|
||||
case ServerTransfer = 'transfer';
|
||||
case Websocket = 'websocket';
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum WebhookScope: string
|
||||
{
|
||||
case Global = 'global';
|
||||
case Server = 'server';
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Global => 'Global',
|
||||
self::Server => 'Server',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,6 @@ class FileSizeTooLargeException extends DisplayException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(trans('exceptions.server.file_too_large'));
|
||||
parent::__construct('The file you are attempting to open is too large to view in the file editor.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,17 @@ class ServerStateConflictException extends ConflictHttpException
|
||||
*/
|
||||
public function __construct(Server $server, ?Throwable $previous = null)
|
||||
{
|
||||
$message = trans('exceptions.server.state_conflict');
|
||||
$message = 'This server is currently in an unsupported state, please try again later.';
|
||||
if ($server->isSuspended()) {
|
||||
$message = trans('exceptions.server.suspended');
|
||||
$message = 'This server is currently suspended and the functionality requested is unavailable.';
|
||||
} elseif ($server->node->isUnderMaintenance()) {
|
||||
$message = trans('exceptions.server.maintenance');
|
||||
$message = 'The node of this server is currently under maintenance and the functionality requested is unavailable.';
|
||||
} elseif (!$server->isInstalled()) {
|
||||
$message = trans('exceptions.server.marked_as_failed');
|
||||
$message = 'This server has not yet completed its installation process, please try again later.';
|
||||
} elseif ($server->status === ServerState::RestoringBackup) {
|
||||
$message = trans('exceptions.server.restoring_backup');
|
||||
$message = 'This server is currently restoring from a backup, please try again later.';
|
||||
} elseif (!is_null($server->transfer)) {
|
||||
$message = trans('exceptions.server.transferring');
|
||||
$message = 'This server is currently being transferred to a new machine, please try again later.';
|
||||
}
|
||||
|
||||
parent::__construct($message, $previous);
|
||||
|
||||
@@ -52,13 +52,13 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading(trans('server/feature.gsl_token.heading'))
|
||||
->modalDescription(trans('server/feature.gsl_token.description'))
|
||||
->modalSubmitActionLabel(trans('server/feature.gsl_token.submit'))
|
||||
->modalHeading('Invalid GSL token')
|
||||
->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.')
|
||||
->modalSubmitActionLabel('Update GSL Token')
|
||||
->disabledSchema(fn () => !user()?->can(SubuserPermission::StartupUpdate, $server))
|
||||
->schema([
|
||||
TextEntry::make('info')
|
||||
->label(new HtmlString(Blade::render(trans('server/feature.gsl_token.info')))),
|
||||
->label(new HtmlString(Blade::render('You can either <x-filament::link href="https://steamcommunity.com/dev/managegameservers" target="_blank">generate a new one</x-filament::link> and enter it below or leave the field blank to remove it completely.'))),
|
||||
TextInput::make('gsltoken')
|
||||
->label('GSL Token')
|
||||
->rules([
|
||||
@@ -104,13 +104,13 @@ class GSLTokenSchema implements FeatureSchemaInterface
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/feature.gsl_token.updated'))
|
||||
->body(trans('server/feature.restart_now'))
|
||||
->title('GSL Token updated')
|
||||
->body('Server will restart now.')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title(trans('server/feature.gsl_token.failed'))
|
||||
->title('Could not update GSL Token')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -41,15 +41,15 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading(trans('server/feature.java_version.heading'))
|
||||
->modalDescription(trans('server/feature.java_version.description'))
|
||||
->modalSubmitActionLabel(trans('server/feature.java_version.submit'))
|
||||
->modalHeading('Unsupported Java Version')
|
||||
->modalDescription('This server is currently running an unsupported version of Java and cannot be started.')
|
||||
->modalSubmitActionLabel('Update Docker Image')
|
||||
->disabledSchema(fn () => !user()?->can(SubuserPermission::StartupDockerImage, $server))
|
||||
->schema([
|
||||
TextEntry::make('java')
|
||||
->label(trans('server/feature.java_version.select_version')),
|
||||
->label('Please select a supported version from the list below to continue starting the server.'),
|
||||
Select::make('image')
|
||||
->label(trans('server/feature.java_version.docker_image'))
|
||||
->label('Docker Image')
|
||||
->disabled(fn () => !in_array($server->image, $server->egg->docker_images))
|
||||
->options(fn () => collect($server->egg->docker_images)->mapWithKeys(fn ($key, $value) => [$key => $value]))
|
||||
->selectablePlaceholder(false)
|
||||
@@ -73,13 +73,13 @@ class JavaVersionSchema implements FeatureSchemaInterface
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/feature.java_version.updated'))
|
||||
->body(trans('server/feature.restart_now'))
|
||||
->title('Docker image updated')
|
||||
->body('Server will restart now.')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title(trans('server/feature.java_version.failed'))
|
||||
->title('Could not update docker image')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -32,9 +32,9 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||
{
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading(trans('server/feature.eula.heading'))
|
||||
->modalDescription(new HtmlString(Blade::render(trans('server/feature.eula.description'))))
|
||||
->modalSubmitActionLabel(trans('server/feature.eula.accept'))
|
||||
->modalHeading('Minecraft EULA')
|
||||
->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.')))
|
||||
->modalSubmitActionLabel('I Accept')
|
||||
->action(function (DaemonFileRepository $fileRepository, DaemonServerRepository $serverRepository) {
|
||||
try {
|
||||
/** @var Server $server */
|
||||
@@ -45,13 +45,13 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
|
||||
$serverRepository->setServer($server)->power('restart');
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/feature.eula.accepted'))
|
||||
->body(trans('server/feature.restart_now'))
|
||||
->title('Minecraft EULA accepted')
|
||||
->body('Server will restart now.')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title(trans('server/feature.eula.failed'))
|
||||
->title('Could not accept Minecraft EULA')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Extensions\Features\Schemas;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class PIDLimitSchema implements FeatureSchemaInterface
|
||||
@@ -32,9 +33,35 @@ class PIDLimitSchema implements FeatureSchemaInterface
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->icon(TablerIcon::AlertTriangle)
|
||||
->modalHeading(fn () => user()?->isAdmin() ? trans('server/feature.pid_limit.heading_admin') : trans('server/feature.pid_limit.heading_user'))
|
||||
->modalDescription(new HtmlString(user()?->isAdmin() ? trans('server/feature.pid_limit.description_admin') : trans('server/feature.pid_limit.description_user')))
|
||||
->modalCancelActionLabel(trans('server/feature.close'))
|
||||
->modalHeading(fn () => user()?->isAdmin() ? 'Memory or process limit reached...' : 'Possible resource limit reached...')
|
||||
->modalDescription(new HtmlString(Blade::render(
|
||||
user()?->isAdmin() ? <<<'HTML'
|
||||
<p>
|
||||
This server has reached the maximum process or memory limit.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
Increasing <code>container_pid_limit</code> in the wings
|
||||
configuration, <code>config.yml</code>, might help resolve
|
||||
this issue.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
<b>Note: Wings must be restarted for the configuration file changes to take effect</b>
|
||||
</p>
|
||||
HTML
|
||||
:
|
||||
<<<'HTML'
|
||||
<p>
|
||||
This server is attempting to use more resources than allocated. Please contact the administrator
|
||||
and give them the error below.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
<code>
|
||||
pthread_create failed, Possibly out of memory or process/resource limits reached
|
||||
</code>
|
||||
</p>
|
||||
HTML
|
||||
)))
|
||||
->modalCancelActionLabel('Close')
|
||||
->action(fn () => null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Extensions\Features\Schemas;
|
||||
|
||||
use App\Extensions\Features\FeatureSchemaInterface;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class SteamDiskSpaceSchema implements FeatureSchemaInterface
|
||||
@@ -26,9 +27,28 @@ class SteamDiskSpaceSchema implements FeatureSchemaInterface
|
||||
{
|
||||
return Action::make($this->getId())
|
||||
->requiresConfirmation()
|
||||
->modalHeading(trans('server/feature.steam_disk_space.heading'))
|
||||
->modalDescription(new HtmlString(user()?->isAdmin() ? trans('server/feature.steam_disk_space.description_admin') : trans('server/feature.steam_disk_space.description_user')))
|
||||
->modalCancelActionLabel(trans('server/feature.close'))
|
||||
->modalHeading('Out of available disk space...')
|
||||
->modalDescription(new HtmlString(Blade::render(
|
||||
user()?->isAdmin() ? <<<'HTML'
|
||||
<p>
|
||||
This server has run out of available disk space and cannot complete the install or update
|
||||
process.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
Ensure the machine has enough disk space by typing{' '}
|
||||
<code class="rounded py-1 px-2">df -h</code> on the machine hosting
|
||||
this server. Delete files or increase the available disk space to resolve the issue.
|
||||
</p>
|
||||
HTML
|
||||
:
|
||||
<<<'HTML'
|
||||
<p>
|
||||
This server has run out of available disk space and cannot complete the install or update
|
||||
process. Please get in touch with the administrator(s) and inform them of disk space issues.
|
||||
</p>
|
||||
HTML
|
||||
)))
|
||||
->modalCancelActionLabel('Close')
|
||||
->action(fn () => null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ class DatabaseHostResource extends Resource
|
||||
->counts('databases')
|
||||
->label(trans('admin/databasehost.databases')),
|
||||
TextColumn::make('nodes.name')
|
||||
->label(trans('admin/databasehost.nodes'))
|
||||
->badge()
|
||||
->placeholder(trans('admin/databasehost.no_nodes')),
|
||||
])
|
||||
|
||||
@@ -66,7 +66,6 @@ class DatabasesRelationManager extends RelationManager
|
||||
DateTimeColumn::make('created_at')
|
||||
->label(trans('admin/databasehost.table.created_at')),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/databasehost.no_databases'))
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->color('primary'),
|
||||
|
||||
@@ -303,11 +303,7 @@ class EditEgg extends EditRecord
|
||||
->tooltip(fn (Egg $egg): string => $egg->servers()->count() <= 0 ? trans('filament-actions::delete.single.label') : trans('admin/egg.in_use')),
|
||||
ExportEggAction::make(),
|
||||
ImportEggAction::make()
|
||||
->multiple(false)
|
||||
->after(function () {
|
||||
$this->record->refresh();
|
||||
$this->refreshForm();
|
||||
}),
|
||||
->multiple(false),
|
||||
Action::make('save')
|
||||
->hiddenLabel()
|
||||
->action('save')
|
||||
@@ -320,8 +316,6 @@ class EditEgg extends EditRecord
|
||||
public function refreshForm(): void
|
||||
{
|
||||
$this->fillForm();
|
||||
|
||||
$this->dispatch('setContent', content: $this->record->script_install ?? '');
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
|
||||
@@ -665,7 +665,6 @@ class EditNode extends EditRecord
|
||||
->disabled(fn (Get $get) => $get('pulled'))
|
||||
->headerActions([
|
||||
Action::make('pull')
|
||||
->label(trans('admin/node.diagnostics.pull'))
|
||||
->tooltip(trans('admin/node.diagnostics.pull'))
|
||||
->icon(TablerIcon::CloudDownload)
|
||||
->hidden(fn (Get $get) => $get('pulled'))
|
||||
@@ -765,20 +764,17 @@ class EditNode extends EditRecord
|
||||
])
|
||||
->schema([
|
||||
ToggleButtons::make('include_endpoints')
|
||||
->label(trans('admin/node.diagnostics.include_endpoints'))
|
||||
->hintIcon(TablerIcon::QuestionMark)->inline()
|
||||
->hintIconTooltip(trans('admin/node.diagnostics.include_endpoints_hint'))
|
||||
->formatStateUsing(fn () => 1)
|
||||
->boolean(),
|
||||
ToggleButtons::make('include_logs')
|
||||
->label(trans('admin/node.diagnostics.include_logs'))
|
||||
->live()
|
||||
->hintIcon(TablerIcon::QuestionMark)->inline()
|
||||
->hintIconTooltip(trans('admin/node.diagnostics.include_logs_hint'))
|
||||
->formatStateUsing(fn () => 1)
|
||||
->boolean(),
|
||||
Slider::make('log_lines')
|
||||
->label(trans('admin/node.diagnostics.log_lines'))
|
||||
->columnSpan(2)
|
||||
->hiddenLabel()
|
||||
->live()
|
||||
|
||||
@@ -83,12 +83,10 @@ class AllocationsRelationManager extends RelationManager
|
||||
->searchable()
|
||||
->label(trans('admin/node.table.ip')),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/node.no_allocations'))
|
||||
->toolbarActions([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => user()?->can('update', $this->getOwnerRecord())),
|
||||
Action::make('create new allocation')
|
||||
->label(trans('admin/node.create_allocation'))
|
||||
->tooltip(trans('admin/node.create_allocation'))
|
||||
->icon(TablerIcon::WorldPlus)
|
||||
->schema(fn () => [
|
||||
|
||||
@@ -67,7 +67,6 @@ class ServersRelationManager extends RelationManager
|
||||
->label(trans('admin/node.backups'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/server.no_servers'));
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,8 +242,6 @@ class PluginResource extends Resource
|
||||
])
|
||||
->headerActions([
|
||||
Action::make('import_from_file')
|
||||
->label(trans('admin/plugin.import_from_file'))
|
||||
->modalHeading(trans('admin/plugin.import_from_file'))
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/plugin.import_from_file'))
|
||||
->authorize(fn () => user()?->can('create', Plugin::class))
|
||||
@@ -251,7 +249,6 @@ class PluginResource extends Resource
|
||||
->schema([
|
||||
// TODO: switch to new file upload
|
||||
FileUpload::make('file')
|
||||
->label(trans('admin/plugin.file'))
|
||||
->required()
|
||||
->acceptedFileTypes(['application/zip', 'application/zip-compressed', 'application/x-zip-compressed'])
|
||||
->preserveFilenames()
|
||||
@@ -288,8 +285,6 @@ class PluginResource extends Resource
|
||||
}
|
||||
}),
|
||||
Action::make('import_from_url')
|
||||
->label(trans('admin/plugin.import_from_url'))
|
||||
->modalHeading(trans('admin/plugin.import_from_url'))
|
||||
->hiddenLabel()
|
||||
->tooltip(trans('admin/plugin.import_from_url'))
|
||||
->authorize(fn () => user()?->can('create', Plugin::class))
|
||||
|
||||
@@ -123,9 +123,7 @@ class RoleResource extends Resource
|
||||
$options = [];
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
$options[$permission . ' ' . $model] = trans()->has("admin/role.permissions_list.$permission")
|
||||
? trans("admin/role.permissions_list.$permission")
|
||||
: Str::headline($permission);
|
||||
$options[$permission . ' ' . $model] = Str::headline($permission);
|
||||
}
|
||||
|
||||
$permissionSections[] = self::makeSection($model, $options);
|
||||
@@ -168,9 +166,7 @@ class RoleResource extends Resource
|
||||
*/
|
||||
private static function makeSection(string $model, array $options): Section
|
||||
{
|
||||
return Section::make(trans()->has("admin/role.models.$model")
|
||||
? trans("admin/role.models.$model")
|
||||
: Str::headline($model))
|
||||
return Section::make(Str::headline($model))
|
||||
->columnSpan(1)
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
|
||||
@@ -69,7 +69,6 @@ class AllocationsRelationManager extends RelationManager
|
||||
->trueIcon(TablerIcon::Lock)
|
||||
->falseIcon(TablerIcon::LockOpen),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/server.no_allocations'))
|
||||
->recordActions([
|
||||
Action::make('make-primary')
|
||||
->label(trans('admin/server.make_primary'))
|
||||
|
||||
@@ -79,7 +79,6 @@ class DatabasesRelationManager extends RelationManager
|
||||
DateTimeColumn::make('created_at')
|
||||
->label(trans('admin/databasehost.table.created_at')),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/server.no_databases'))
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->color('primary'),
|
||||
|
||||
@@ -26,8 +26,7 @@ class ServersRelationManager extends RelationManager
|
||||
->searchable(false)
|
||||
->heading(trans('admin/user.servers'))
|
||||
->headerActions([
|
||||
Action::make('toggle_suspend')
|
||||
->authorize(fn () => user()?->can('update server'))
|
||||
Action::make('toggleSuspend')
|
||||
->hidden(fn () => $user->servers()
|
||||
->whereNot('status', ServerState::Suspended)
|
||||
->orWhereNull('status')
|
||||
@@ -39,8 +38,7 @@ class ServersRelationManager extends RelationManager
|
||||
collect($user->servers)->filter(fn ($server) => !$server->isSuspended())
|
||||
->each(fn ($server) => $suspensionService->handle($server, SuspendAction::Suspend));
|
||||
}),
|
||||
Action::make('toggle_unsuspend')
|
||||
->authorize(fn () => user()?->can('update server'))
|
||||
Action::make('toggleUnsuspend')
|
||||
->hidden(fn () => $user->servers()->where('status', ServerState::Suspended)->count() === 0)
|
||||
->label(trans('admin/server.unsuspend_all'))
|
||||
->color('primary')
|
||||
@@ -89,7 +87,6 @@ class ServersRelationManager extends RelationManager
|
||||
->label(trans('admin/server.backups'))
|
||||
->numeric()
|
||||
->sortable(),
|
||||
])
|
||||
->emptyStateHeading(trans('admin/server.no_servers'));
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Filament\Admin\Resources\Webhooks\Pages;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Enums\WebhookType;
|
||||
use App\Filament\Admin\Resources\Webhooks\WebhookResource;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
@@ -11,7 +10,6 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CreateWebhookConfiguration extends CreateRecord
|
||||
{
|
||||
@@ -46,15 +44,6 @@ class CreateWebhookConfiguration extends CreateRecord
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
// Ensure name is set (required field)
|
||||
if (empty($data['name'] ?? null)) {
|
||||
throw ValidationException::withMessages(['name' => 'Webhook name is required']);
|
||||
}
|
||||
|
||||
// Set scope to GLOBAL by default for admin webhooks
|
||||
$data['scope'] = WebhookScope::Global;
|
||||
unset($data['server_id']);
|
||||
|
||||
if (($data['type'] ?? null) === WebhookType::Discord->value) {
|
||||
$embeds = data_get($data, 'embeds', []);
|
||||
|
||||
|
||||
@@ -2,16 +2,10 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Webhooks\Pages;
|
||||
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Filament\Admin\Resources\Webhooks\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListWebhookConfigurations extends ListRecords
|
||||
{
|
||||
@@ -19,24 +13,4 @@ class ListWebhookConfigurations extends ListRecords
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
/** @return array<Action> */
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->hidden(fn () => $this->activeTab === 'server-webhooks'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTabs(): array
|
||||
{
|
||||
return collect(WebhookScope::cases())
|
||||
->mapWithKeys(fn (WebhookScope $scope) => [
|
||||
$scope->value . '-webhooks' => Tab::make($scope->getLabel() . ' Webhooks')
|
||||
->modifyQueryUsing(fn (Builder $query) => $query->where('scope', $scope))
|
||||
->badge(WebhookConfiguration::where('scope', $scope)->count()),
|
||||
])
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
namespace App\Filament\Admin\Resources\Webhooks;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Enums\WebhookType;
|
||||
use App\Filament\Admin\Resources\Webhooks\Pages\CreateWebhookConfiguration;
|
||||
use App\Filament\Admin\Resources\Webhooks\Pages\EditWebhookConfiguration;
|
||||
use App\Filament\Admin\Resources\Webhooks\Pages\ListWebhookConfigurations;
|
||||
use App\Filament\Admin\Resources\Webhooks\Pages\ViewWebhookConfiguration;
|
||||
use App\Livewire\AlertBanner;
|
||||
use App\Models\Server;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
@@ -18,6 +16,7 @@ use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use BackedEnum;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
@@ -27,19 +26,14 @@ use Filament\Actions\ViewAction;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\ColorPicker;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
@@ -48,6 +42,7 @@ use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Livewire\Component as Livewire;
|
||||
use Livewire\Features\SupportEvents\HandlesEvents;
|
||||
|
||||
class WebhookResource extends Resource
|
||||
@@ -62,7 +57,7 @@ class WebhookResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = TablerIcon::Webhook;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
protected static ?string $recordTitleAttribute = 'description';
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
@@ -92,26 +87,17 @@ class WebhookResource extends Resource
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->groups([
|
||||
'server.name',
|
||||
])
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/webhook.name')),
|
||||
TextColumn::make('description')
|
||||
->label(trans('admin/webhook.table.description')),
|
||||
IconColumn::make('type')
|
||||
->label(trans('admin/webhook.type')),
|
||||
TextColumn::make('server.name')
|
||||
->label('Server')
|
||||
->placeholder('—')
|
||||
->icon('tabler-server')
|
||||
->iconColor('info'),
|
||||
IconColumn::make('type'),
|
||||
TextColumn::make('endpoint')
|
||||
->label(trans('admin/webhook.endpoint'))
|
||||
->label(trans('admin/webhook.table.endpoint'))
|
||||
->formatStateUsing(fn (string $state) => str($state)->after('://'))
|
||||
->limit(60)
|
||||
->wrap(),
|
||||
TextColumn::make('description')
|
||||
->label(trans('admin/webhook.table.description')),
|
||||
TextColumn::make('endpoint')
|
||||
->label(trans('admin/webhook.table.endpoint')),
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
@@ -122,7 +108,7 @@ class WebhookResource extends Resource
|
||||
->tooltip(trans('filament-actions::replicate.single.label'))
|
||||
->modal(false)
|
||||
->excludeAttributes(['created_at', 'updated_at'])
|
||||
->beforeReplicaSaved(fn (WebhookConfiguration $replica) => $replica->name .= ' Copy ' . now()->format('Y-m-d H:i:s'))
|
||||
->beforeReplicaSaved(fn (WebhookConfiguration $replica) => $replica->description .= ' Copy ' . now()->format('Y-m-d H:i:s'))
|
||||
->successRedirectUrl(fn (WebhookConfiguration $replica) => EditWebhookConfiguration::getUrl(['record' => $replica])),
|
||||
])
|
||||
->toolbarActions([
|
||||
@@ -139,9 +125,6 @@ class WebhookResource extends Resource
|
||||
SelectFilter::make('type')
|
||||
->options(WebhookType::class)
|
||||
->attribute('type'),
|
||||
SelectFilter::make('server_id')
|
||||
->label('Server')
|
||||
->options(Server::query()->pluck('name', 'id')->toArray()),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -149,74 +132,50 @@ class WebhookResource extends Resource
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Tabs::make('webhook_tabs')
|
||||
->persistTab()
|
||||
ToggleButtons::make('type')
|
||||
->live()
|
||||
->inline()
|
||||
->options(WebhookType::class)
|
||||
->default(WebhookType::Regular),
|
||||
TextInput::make('description')
|
||||
->label(trans('admin/webhook.description'))
|
||||
->required(),
|
||||
TextInput::make('endpoint')
|
||||
->label(trans('admin/webhook.endpoint'))
|
||||
->required()
|
||||
->columnSpanFull()
|
||||
->tabs([
|
||||
Tab::make(trans('admin/webhook.information'))
|
||||
->icon(TablerIcon::InfoCircle)
|
||||
->schema([
|
||||
Grid::make()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/webhook.name'))
|
||||
->required(),
|
||||
Select::make('server_id')
|
||||
->label(trans('admin/webhook.server'))
|
||||
->relationship('server', 'id')
|
||||
->preload()
|
||||
->disabled(),
|
||||
]),
|
||||
TextInput::make('description')
|
||||
->label(trans('admin/webhook.description'))
|
||||
->required(),
|
||||
Grid::make()
|
||||
->schema([
|
||||
ToggleButtons::make('type')
|
||||
->label(trans('admin/webhook.type'))
|
||||
->live()
|
||||
->inline()
|
||||
->options(WebhookType::class)
|
||||
->default(WebhookType::Regular),
|
||||
Hidden::make('scope')
|
||||
->formatStateUsing(fn (Get $get) => $get('server_id') ? WebhookScope::Server : WebhookScope::Global),
|
||||
TextInput::make('endpoint')
|
||||
->label(trans('admin/webhook.endpoint'))
|
||||
->required()
|
||||
->afterStateUpdated(fn (?string $state, Set $set) => $set('type', $state && str($state)->contains('discord.com') ? WebhookType::Discord : WebhookType::Regular)),
|
||||
]),
|
||||
]),
|
||||
Tab::make(trans('admin/webhook.payload'))
|
||||
->icon(TablerIcon::FileCode)
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema(fn (Get $get) => $get('type') === WebhookType::Discord
|
||||
? self::getDiscordFields()
|
||||
: self::getRegularFields()
|
||||
),
|
||||
]),
|
||||
Tab::make(trans('admin/webhook.events'))
|
||||
->icon(TablerIcon::Star)
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
CheckboxList::make('events')
|
||||
->live()
|
||||
->options(function (Get $get) {
|
||||
$scope = $get('scope');
|
||||
if (!$scope instanceof WebhookScope) {
|
||||
$scope = WebhookScope::from($scope ?? 'global');
|
||||
}
|
||||
|
||||
return WebhookConfiguration::filamentCheckboxList($scope);
|
||||
})
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->required(),
|
||||
]),
|
||||
]),
|
||||
->afterStateUpdated(fn (string $state, Set $set) => $set('type', str($state)->contains('discord.com') ? WebhookType::Discord : WebhookType::Regular)),
|
||||
Section::make(trans('admin/webhook.regular'))
|
||||
->hidden(fn (Get $get) => $get('type') === WebhookType::Discord)
|
||||
->schema(fn () => self::getRegularFields())
|
||||
->headerActions([
|
||||
Action::make('reset_headers')
|
||||
->tooltip(trans('admin/webhook.reset_headers'))
|
||||
->color('danger')
|
||||
->icon(TablerIcon::Restore)
|
||||
->action(fn (Get $get, Set $set) => $set('headers', [
|
||||
'X-Webhook-Event' => '{{event}}',
|
||||
])),
|
||||
])
|
||||
->formBefore(),
|
||||
Section::make(trans('admin/webhook.discord'))
|
||||
->hidden(fn (Get $get) => $get('type') === WebhookType::Regular)
|
||||
->afterStateUpdated(fn (Livewire $livewire) => $livewire->dispatch('refresh-widget'))
|
||||
->schema(fn () => self::getDiscordFields())
|
||||
->view('filament.components.webhooksection')
|
||||
->aside()
|
||||
->formBefore()
|
||||
->columnSpanFull(),
|
||||
Section::make(trans('admin/webhook.events'))
|
||||
->schema([
|
||||
CheckboxList::make('events')
|
||||
->live()
|
||||
->options(fn () => WebhookConfiguration::filamentCheckboxList())
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->required(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -241,150 +200,134 @@ class WebhookResource extends Resource
|
||||
private static function getDiscordFields(): array
|
||||
{
|
||||
return [
|
||||
Grid::make()
|
||||
Section::make(trans('admin/webhook.discord_message.profile'))
|
||||
->collapsible()
|
||||
->schema([
|
||||
Section::make()
|
||||
->columnSpanFull()
|
||||
->poll('15s')
|
||||
->view('filament.components.webhooksection'),
|
||||
Grid::make()
|
||||
->columnSpan(8)
|
||||
TextInput::make('username')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_message.username')),
|
||||
TextInput::make('avatar_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_message.avatar_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_message.message'))
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('content')
|
||||
->label(trans('admin/webhook.discord_message.message'))
|
||||
->live(debounce: 500)
|
||||
->required(fn (Get $get) => empty($get('embeds'))),
|
||||
TextInput::make('thread_name')
|
||||
->label(trans('admin/webhook.discord_message.forum_thread')),
|
||||
CheckboxList::make('flags')
|
||||
->label(trans('admin/webhook.discord_embed.flags'))
|
||||
->options([
|
||||
(1 << 2) => trans('admin/webhook.discord_message.supress_embeds'),
|
||||
(1 << 12) => trans('admin/webhook.discord_message.supress_notifications'),
|
||||
])
|
||||
->descriptions([
|
||||
(1 << 2) => trans('admin/webhook.discord_message.supress_embeds_text'),
|
||||
(1 << 12) => trans('admin/webhook.discord_message.supress_notifications_text'),
|
||||
]),
|
||||
CheckboxList::make('allowed_mentions')
|
||||
->label(trans('admin/webhook.discord_embed.allowed_mentions'))
|
||||
->options([
|
||||
'roles' => trans('admin/webhook.discord_embed.roles'),
|
||||
'users' => trans('admin/webhook.discord_embed.users'),
|
||||
'everyone' => trans('admin/webhook.discord_embed.everyone'),
|
||||
]),
|
||||
]),
|
||||
Repeater::make('embeds')
|
||||
->live(debounce: 500)
|
||||
->itemLabel(fn (array $state) => $state['title'])
|
||||
->addActionLabel(trans('admin/webhook.discord_embed.add_embed'))
|
||||
->required(fn (Get $get) => empty($get('content')))
|
||||
->reorderable()
|
||||
->collapsible()
|
||||
->maxItems(10)
|
||||
->schema([
|
||||
Section::make(trans('admin/webhook.discord_embed.author'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
Section::make(trans('admin/webhook.discord_message.profile'))
|
||||
->collapsible()
|
||||
->columnSpanFull()
|
||||
|
||||
->schema([
|
||||
TextInput::make('username')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_message.username')),
|
||||
TextInput::make('avatar_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_message.avatar_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_message.message'))
|
||||
->columnSpanFull()
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('content')
|
||||
->label(trans('admin/webhook.discord_message.message'))
|
||||
->live(debounce: 500)
|
||||
->required(fn (Get $get) => empty($get('embeds'))),
|
||||
TextInput::make('thread_name')
|
||||
->label(trans('admin/webhook.discord_message.forum_thread')),
|
||||
CheckboxList::make('flags')
|
||||
->label(trans('admin/webhook.discord_embed.flags'))
|
||||
->options([
|
||||
(1 << 2) => trans('admin/webhook.discord_message.supress_embeds'), // Discord flag: SUPPRESS_EMBEDS (4)
|
||||
(1 << 12) => trans('admin/webhook.discord_message.supress_notifications'), // Discord flag: SUPPRESS_NOTIFICATIONS (4096)
|
||||
])
|
||||
->descriptions([
|
||||
(1 << 2) => trans('admin/webhook.discord_message.supress_embeds_text'),
|
||||
(1 << 12) => trans('admin/webhook.discord_message.supress_notifications_text'),
|
||||
]),
|
||||
CheckboxList::make('allowed_mentions')
|
||||
->label(trans('admin/webhook.discord_embed.allowed_mentions'))
|
||||
->options([
|
||||
'roles' => trans('admin/webhook.discord_embed.roles'),
|
||||
'users' => trans('admin/webhook.discord_embed.users'),
|
||||
'everyone' => trans('admin/webhook.discord_embed.everyone'),
|
||||
]),
|
||||
]),
|
||||
Repeater::make('embeds')
|
||||
TextInput::make('author.name')
|
||||
->live(debounce: 500)
|
||||
->itemLabel(fn (array $state) => $state['title'] ?? '')
|
||||
->addActionLabel(trans('admin/webhook.discord_embed.add_embed'))
|
||||
->required(fn (Get $get) => empty($get('content')))
|
||||
->label(trans('admin/webhook.discord_embed.author'))
|
||||
->required(fn (Get $get) => filled($get('author.url')) || filled($get('author.icon_url'))),
|
||||
TextInput::make('author.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.author_url')),
|
||||
TextInput::make('author.icon_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.author_icon_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.body'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.title'))
|
||||
->required(fn (Get $get) => $get('description') === null),
|
||||
Textarea::make('description')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.body'))
|
||||
->required(fn (Get $get) => $get('title') === null),
|
||||
ColorPicker::make('color')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.color'))
|
||||
->hex(),
|
||||
TextInput::make('url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.images'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('image.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.image_url')),
|
||||
TextInput::make('thumbnail.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.image_thumbnail')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.footer'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('footer.text')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.footer')),
|
||||
Checkbox::make('has_timestamp')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.has_timestamp')),
|
||||
TextInput::make('footer.icon_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.footer_icon_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.fields'))
|
||||
->collapsible()->collapsed()
|
||||
->schema([
|
||||
Repeater::make('fields')
|
||||
->reorderable()
|
||||
->columnSpanFull()
|
||||
->addActionLabel(trans('admin/webhook.discord_embed.add_field'))
|
||||
->collapsible()
|
||||
->maxItems(10)
|
||||
->schema([
|
||||
Section::make(trans('admin/webhook.discord_embed.author'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('author.name')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.author'))
|
||||
->required(fn (Get $get) => filled($get('author.url')) || filled($get('author.icon_url'))),
|
||||
TextInput::make('author.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.author_url')),
|
||||
TextInput::make('author.icon_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.author_icon_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.body'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.title'))
|
||||
->required(fn (Get $get) => $get('description') === null),
|
||||
Textarea::make('description')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.body'))
|
||||
->required(fn (Get $get) => $get('title') === null),
|
||||
ColorPicker::make('color')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.color'))
|
||||
->hex(),
|
||||
TextInput::make('url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.images'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('image.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.image_url')),
|
||||
TextInput::make('thumbnail.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.image_thumbnail')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.footer'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('footer.text')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.footer')),
|
||||
Checkbox::make('has_timestamp')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.has_timestamp')),
|
||||
TextInput::make('footer.icon_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.footer_icon_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.fields'))
|
||||
->collapsible()->collapsed()
|
||||
->schema([
|
||||
Repeater::make('fields')
|
||||
->reorderable()
|
||||
->addActionLabel(trans('admin/webhook.discord_embed.add_field'))
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.field_name'))
|
||||
->required(),
|
||||
Textarea::make('value')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.field_value'))
|
||||
->rows(4)
|
||||
->required(),
|
||||
Checkbox::make('inline')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.inline_field')),
|
||||
]),
|
||||
]),
|
||||
TextInput::make('name')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.field_name'))
|
||||
->required(),
|
||||
Textarea::make('value')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.field_value'))
|
||||
->rows(4)
|
||||
->required(),
|
||||
Checkbox::make('inline')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.inline_field')),
|
||||
]),
|
||||
]),
|
||||
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
163
app/Filament/Admin/Widgets/DiscordPreview.php
Normal file
163
app/Filament/Admin/Widgets/DiscordPreview.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Widgets;
|
||||
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class DiscordPreview extends Widget
|
||||
{
|
||||
protected string $view = 'filament.admin.widgets.discord-preview';
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected $listeners = [
|
||||
'refresh-widget' => '$refresh',
|
||||
];
|
||||
|
||||
protected static bool $isDiscovered = false; // Without this its shown on every Admin Pages
|
||||
|
||||
protected int|string|array $columnSpan = 1;
|
||||
|
||||
public ?WebhookConfiguration $record = null;
|
||||
|
||||
/** @var string|array<string, mixed>|null */
|
||||
public string|array|null $payload = null;
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* link: callable,
|
||||
* content: mixed,
|
||||
* sender: array{name: string, avatar: string},
|
||||
* embeds: array<int, mixed>,
|
||||
* getTime: mixed
|
||||
* }
|
||||
*/
|
||||
public function getViewData(): array
|
||||
{
|
||||
if (!$this->record || !$this->record->payload) {
|
||||
return [
|
||||
'link' => fn ($href, $child) => $href ? "<a href=\"$href\" target=\"_blank\" class=\"link\">$child</a>" : $child,
|
||||
'content' => null,
|
||||
'sender' => [
|
||||
'name' => 'Pelican',
|
||||
'avatar' => 'https://raw.githubusercontent.com/pelican-dev/panel/refs/heads/main/public/pelican.ico',
|
||||
],
|
||||
'embeds' => [],
|
||||
'getTime' => 'Today at ' . Carbon::now()->format('h:i A'),
|
||||
];
|
||||
}
|
||||
|
||||
$data = $this->getWebhookSampleData();
|
||||
|
||||
if (is_string($this->record->payload)) {
|
||||
$payload = $this->replaceVarsInStringPayload($this->record->payload, $data);
|
||||
} else {
|
||||
$payload = $this->replaceVarsInArrayPayload($this->record->payload, $data);
|
||||
}
|
||||
|
||||
$embeds = data_get($payload, 'embeds', []);
|
||||
foreach ($embeds as &$embed) {
|
||||
if (data_get($embed, 'has_timestamp')) {
|
||||
unset($embed['has_timestamp']);
|
||||
$embed['timestamp'] = 'Today at ' . Carbon::now()->format('h:i A');
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'link' => fn ($href, $child) => $href ? sprintf('<a href="%s" target="_blank" class="link">%s</a>', $href, $child) : $child,
|
||||
'content' => data_get($payload, 'content'),
|
||||
'sender' => [
|
||||
'name' => data_get($payload, 'username', 'Pelican'),
|
||||
'avatar' => data_get($payload, 'avatar_url', 'https://raw.githubusercontent.com/pelican-dev/panel/refs/heads/main/public/pelican.ico'),
|
||||
],
|
||||
'embeds' => $embeds,
|
||||
'getTime' => 'Today at ' . Carbon::now()->format('h:i A'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
private function replaceVarsInStringPayload(?string $payload, array $data): ?string
|
||||
{
|
||||
if ($payload === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return preg_replace_callback('/{{\s*([\w\.]+)\s*}}/', fn ($m) => data_get($data, $m[1], $m[0]),
|
||||
$payload
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $payload
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function replaceVarsInArrayPayload(?array $payload, array $data): ?array
|
||||
{
|
||||
if ($payload === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($payload as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$payload[$key] = $this->replaceVarsInStringPayload($value, $data);
|
||||
} elseif (is_array($value)) {
|
||||
$payload[$key] = $this->replaceVarsInArrayPayload($value, $data);
|
||||
}
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getWebhookSampleData(): array
|
||||
{
|
||||
return [
|
||||
'event' => 'updated: server',
|
||||
'id' => 2,
|
||||
'external_id' => 10,
|
||||
'uuid' => '651fgbc1-dee6-4250-814e-10slda13f1e',
|
||||
'uuid_short' => '651fgbc1',
|
||||
'node_id' => 1,
|
||||
'name' => 'Example Server',
|
||||
'description' => 'This is an example server description.',
|
||||
'status' => 'running',
|
||||
'skip_scripts' => false,
|
||||
'owner_id' => 1,
|
||||
'memory' => 512,
|
||||
'swap' => 128,
|
||||
'disk' => 10240,
|
||||
'io' => 500,
|
||||
'cpu' => 500,
|
||||
'threads' => '1, 3, 5',
|
||||
'oom_killer' => false,
|
||||
'allocation_id' => 4,
|
||||
'egg_id' => 2,
|
||||
'startup' => 'This is a example startup command.',
|
||||
'image' => 'Image here',
|
||||
'allocation_limit' => 5,
|
||||
'database_limit' => 1,
|
||||
'backup_limit' => 3,
|
||||
'created_at' => '2025-03-17T15:20:32.000000Z',
|
||||
'updated_at' => '2025-05-12T17:53:12.000000Z',
|
||||
'installed_at' => '2025-04-27T21:06:01.000000Z',
|
||||
'docker_labels' => [],
|
||||
'allocation' => [
|
||||
'id' => 4,
|
||||
'node_id' => 1,
|
||||
'ip' => '192.168.0.3',
|
||||
'ip_alias' => null,
|
||||
'port' => 25567,
|
||||
'server_id' => 2,
|
||||
'notes' => null,
|
||||
'created_at' => '2025-03-17T15:20:09.000000Z',
|
||||
'updated_at' => '2025-03-17T15:20:32.000000Z',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ class ListServers extends ListRecords
|
||||
->contentGrid($usingGrid ? ['default' => 1, 'md' => 2] : null)
|
||||
->emptyStateIcon(TablerIcon::BrandDocker)
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(fn () => $this->activeTab === 'my' ? trans('server/dashboard.empty_own') : trans('server/dashboard.empty_other'))
|
||||
->emptyStateHeading(fn () => $this->activeTab === 'my' ? 'You don\'t own any servers!' : 'You don\'t have access to any servers!')
|
||||
->persistFiltersInSession()
|
||||
->filters([
|
||||
SelectFilter::make('egg')
|
||||
|
||||
@@ -21,8 +21,6 @@ class UpdateNodeAllocations extends Action
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(trans('admin/node.bulk_update_ip'));
|
||||
|
||||
$this->tooltip(trans('admin/node.bulk_update_ip'));
|
||||
|
||||
$this->icon(TablerIcon::Replace);
|
||||
|
||||
@@ -465,12 +465,13 @@ class EditProfile extends BaseEditProfile
|
||||
'mixed' => trans('profile.mixed'),
|
||||
]),
|
||||
ToggleButtons::make('button_style')
|
||||
->label(trans('profile.button_style'))
|
||||
->label('Button Style')
|
||||
->inline()
|
||||
->default('icon')
|
||||
|
||||
->options([
|
||||
true => trans('profile.icon'),
|
||||
false => trans('profile.icon_button'),
|
||||
true => 'Icon',
|
||||
false => 'Icon Button',
|
||||
]),
|
||||
]),
|
||||
Section::make(trans('profile.admin'))
|
||||
|
||||
@@ -56,7 +56,6 @@ class Startup extends ServerFormPage
|
||||
->disabled(fn (Server $server) => !user()?->can(SubuserPermission::StartupUpdate, $server))
|
||||
->formatStateUsing(fn (Server $server) => $server->startup)
|
||||
->afterStateUpdated(function ($state, Server $server, Set $set) {
|
||||
abort_unless(user()?->can(SubuserPermission::StartupUpdate, $server), 403);
|
||||
$original = $server->startup;
|
||||
$server->forceFill(['startup' => $state])->saveOrFail();
|
||||
|
||||
@@ -90,7 +89,6 @@ class Startup extends ServerFormPage
|
||||
->visible(fn (Server $server) => in_array($server->image, $server->egg->docker_images))
|
||||
->disabled(fn (Server $server) => !user()?->can(SubuserPermission::StartupDockerImage, $server))
|
||||
->afterStateUpdated(function ($state, Server $server) {
|
||||
abort_unless(user()?->can(SubuserPermission::StartupDockerImage, $server), 403);
|
||||
$original = $server->image;
|
||||
$server->forceFill(['image' => $state])->saveOrFail();
|
||||
|
||||
@@ -153,8 +151,6 @@ class Startup extends ServerFormPage
|
||||
|
||||
public function update(null|string|bool $state, ServerVariable $serverVariable): void
|
||||
{
|
||||
abort_unless(user()?->can(SubuserPermission::StartupUpdate, $this->getRecord()), 403);
|
||||
|
||||
if (!$serverVariable->variable->user_editable) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Filament\Server\Resources\Files\Pages;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\Files\FileResource;
|
||||
@@ -40,7 +39,6 @@ class DownloadFiles extends Page
|
||||
|
||||
$token = $this->nodeJWTService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setScopes(NodeJwtScope::FileDownload)
|
||||
->setUser(user())
|
||||
->setClaims([
|
||||
'file_path' => rawurldecode($path),
|
||||
|
||||
@@ -95,7 +95,6 @@ class TasksRelationManager extends RelationManager
|
||||
->label(trans('server/schedule.tasks.continue_on_failure'))
|
||||
->boolean(),
|
||||
])
|
||||
->emptyStateHeading(trans('server/schedule.no_tasks'))
|
||||
->recordActions([
|
||||
EditAction::make()
|
||||
->schema($this->getTaskForm($schedule))
|
||||
|
||||
@@ -93,10 +93,9 @@ class ScheduleResource extends Resource
|
||||
->required()
|
||||
->default(1),
|
||||
ToggleButtons::make('status')
|
||||
->label(trans('server/schedule.status'))
|
||||
->enum(ScheduleStatus::class)
|
||||
->formatStateUsing(fn (?Schedule $schedule) => $schedule?->status->value ?? 'new')
|
||||
->options(fn (?Schedule $schedule) => [$schedule?->status->value ?? 'new' => $schedule?->status->getLabel() ?? trans('server/schedule.schedule_status.new')])
|
||||
->options(fn (?Schedule $schedule) => [$schedule?->status->value ?? 'new' => $schedule?->status->getLabel() ?? 'New'])
|
||||
->visibleOn('view'),
|
||||
Section::make(trans('server/schedule.cron'))
|
||||
->description(function (Get $get) {
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Server\Resources\Webhooks\Pages;
|
||||
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Filament\Server\Resources\Webhooks\WebhookResource;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateWebhook extends CreateRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCancelFormAction()->formId('form')->icon('tabler-cancel'),
|
||||
$this->getCreateFormAction()->formId('form')->icon('tabler-plus'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
$server = Filament::getTenant();
|
||||
abort_unless($server instanceof Server, 403);
|
||||
|
||||
$data['server_id'] = $server->id;
|
||||
$data['scope'] = WebhookScope::Server;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return EditWebhook::getUrl(['record' => $this->getRecord()]);
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
parent::mount();
|
||||
WebhookResource::sendHelpBanner();
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Server\Resources\Webhooks\Pages;
|
||||
|
||||
use App\Filament\Server\Resources\Webhooks\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditWebhook extends EditRecord
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->icon('tabler-trash'),
|
||||
Action::make('test_now')
|
||||
->label(trans('admin/webhook.test_now'))
|
||||
->color('primary')
|
||||
->icon('tabler-send')
|
||||
->action(fn (WebhookConfiguration $record) => $record->run())
|
||||
->tooltip(trans('admin/webhook.test_now_help')),
|
||||
$this->getSaveFormAction()->formId('form')->icon('tabler-device-floppy'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Server\Resources\Webhooks\Pages;
|
||||
|
||||
use App\Filament\Server\Resources\Webhooks\WebhookResource;
|
||||
use App\Models\Server;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
|
||||
class ListWebhooks extends ListRecords
|
||||
{
|
||||
use CanCustomizeHeaderActions;
|
||||
use CanCustomizeHeaderWidgets;
|
||||
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
/** @return array<Action|ActionGroup> */
|
||||
protected function getDefaultHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->icon('tabler-plus')
|
||||
->hiddenLabel()
|
||||
->iconButton()
|
||||
->iconSize(IconSize::ExtraLarge)
|
||||
->hidden(function () {
|
||||
$server = Filament::getTenant();
|
||||
|
||||
return !$server instanceof Server || $server->webhookConfigurations()->count() <= 0;
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Server\Resources\Webhooks\Pages;
|
||||
|
||||
use App\Filament\Server\Resources\Webhooks\WebhookResource;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewWebhook extends ViewRecord
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
EditAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Server\Resources\Webhooks;
|
||||
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Enums\WebhookType;
|
||||
use App\Filament\Server\Resources\Webhooks\Pages\CreateWebhook;
|
||||
use App\Filament\Server\Resources\Webhooks\Pages\EditWebhook;
|
||||
use App\Filament\Server\Resources\Webhooks\Pages\ListWebhooks;
|
||||
use App\Filament\Server\Resources\Webhooks\Pages\ViewWebhook;
|
||||
use App\Livewire\AlertBanner;
|
||||
use App\Models\Server;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Traits\Filament\CanCustomizePages;
|
||||
use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ReplicateAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\ColorPicker;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Components\Component;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Livewire\Component as Livewire;
|
||||
use Livewire\Features\SupportEvents\HandlesEvents;
|
||||
|
||||
class WebhookResource extends Resource
|
||||
{
|
||||
use CanCustomizePages;
|
||||
use CanCustomizeRelations;
|
||||
use CanModifyForm;
|
||||
use CanModifyTable;
|
||||
use HandlesEvents;
|
||||
|
||||
protected static ?string $model = WebhookConfiguration::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'tabler-webhook';
|
||||
|
||||
protected static ?int $navigationSort = 11;
|
||||
|
||||
protected static ?string $slug = 'webhook';
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return trans('admin/webhook.nav_title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return trans('admin/webhook.model_label');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return trans('admin/webhook.model_label_plural');
|
||||
}
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
/** @var Server|null $server */
|
||||
$server = Filament::getTenant();
|
||||
if (!$server instanceof Server) {
|
||||
return null;
|
||||
}
|
||||
$count = static::getModel()::where('server_id', $server->id)->count();
|
||||
|
||||
return $count > 0 ? (string) $count : null;
|
||||
}
|
||||
|
||||
public static function defaultTable(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
IconColumn::make('type')
|
||||
->label(trans('admin/webhook.type')),
|
||||
TextColumn::make('name')
|
||||
->label(trans('admin/webhook.name')),
|
||||
TextColumn::make('endpoint')
|
||||
->label(trans('admin/webhook.endpoint'))
|
||||
->formatStateUsing(fn (string $state) => str($state)->after('://'))
|
||||
->limit(60)
|
||||
->wrap(),
|
||||
])
|
||||
->recordActions([
|
||||
ViewAction::make()
|
||||
->hidden(fn (WebhookConfiguration $record) => static::canEdit($record)),
|
||||
EditAction::make(),
|
||||
ReplicateAction::make()
|
||||
->iconButton()
|
||||
->tooltip(trans('filament-actions::replicate.single.label'))
|
||||
->modal(false)
|
||||
->excludeAttributes(['created_at', 'updated_at']),
|
||||
])
|
||||
->groupedBulkActions([
|
||||
DeleteBulkAction::make(),
|
||||
])
|
||||
->emptyStateIcon('tabler-webhook')
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading(trans('admin/webhook.no_webhooks'))
|
||||
->emptyStateActions([
|
||||
CreateAction::make(),
|
||||
])
|
||||
->persistFiltersInSession()
|
||||
->filters([
|
||||
SelectFilter::make('type')
|
||||
->options(WebhookType::class)
|
||||
->attribute('type'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function defaultForm(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
ToggleButtons::make('type')
|
||||
->label(trans('admin/webhook.type'))
|
||||
->live()
|
||||
->inline()
|
||||
->options(WebhookType::class)
|
||||
->default(WebhookType::Regular),
|
||||
TextInput::make('endpoint')
|
||||
->label(trans('admin/webhook.endpoint'))
|
||||
->required()
|
||||
->afterStateUpdated(fn (?string $state, Set $set) => $set('type', $state && str($state)->contains('discord.com') ? WebhookType::Discord : WebhookType::Regular)),
|
||||
TextInput::make('name')
|
||||
->label(trans('admin/webhook.name'))
|
||||
->columnSpanFull()
|
||||
->required(),
|
||||
Section::make(trans('admin/webhook.regular'))
|
||||
->hidden(fn (Get $get) => $get('type') === WebhookType::Discord)
|
||||
->schema(fn () => self::getRegularFields())
|
||||
->headerActions([
|
||||
Action::make('reset_headers')
|
||||
->label(trans('admin/webhook.reset_headers'))
|
||||
->color('danger')
|
||||
->icon('heroicon-o-trash')
|
||||
->action(fn (Set $set) => $set('headers', [
|
||||
'X-Webhook-Event' => '{{event}}',
|
||||
])),
|
||||
])
|
||||
->formBefore(),
|
||||
Section::make(trans('admin/webhook.discord'))
|
||||
->hidden(fn (Get $get) => $get('type') === WebhookType::Regular)
|
||||
->afterStateUpdated(fn (Livewire $livewire) => $livewire->dispatch('refresh-widget'))
|
||||
->schema(fn () => self::getDiscordFields())
|
||||
->poll('15s')
|
||||
->view('filament.components.webhooksection')
|
||||
->aside()
|
||||
->formBefore()
|
||||
->columnSpanFull(),
|
||||
Section::make(trans('admin/webhook.events'))
|
||||
->schema([
|
||||
CheckboxList::make('events')
|
||||
->live()
|
||||
->options(fn () => WebhookConfiguration::filamentCheckboxList(WebhookScope::Server))
|
||||
->searchable()
|
||||
->bulkToggleable()
|
||||
->columns(3)
|
||||
->columnSpanFull()
|
||||
->required(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return Component[]
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getRegularFields(): array
|
||||
{
|
||||
return [
|
||||
KeyValue::make('headers')
|
||||
->label(trans('admin/webhook.headers'))
|
||||
->default(fn () => [
|
||||
'X-Webhook-Event' => '{{event}}',
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
/** @return Component[]
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getDiscordFields(): array
|
||||
{
|
||||
return [
|
||||
Section::make(trans('admin/webhook.discord_message.profile'))
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('username')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_message.username')),
|
||||
TextInput::make('avatar_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_message.avatar_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_message.message'))
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('content')
|
||||
->label(trans('admin/webhook.discord_message.message'))
|
||||
->live(debounce: 500)
|
||||
->required(fn (Get $get) => empty($get('embeds'))),
|
||||
TextInput::make('thread_name')
|
||||
->label(trans('admin/webhook.discord_message.forum_thread')),
|
||||
CheckboxList::make('flags')
|
||||
->label(trans('admin/webhook.discord_embed.flags'))
|
||||
->options([
|
||||
(1 << 2) => trans('admin/webhook.discord_message.supress_embeds'), // Discord flag: SUPPRESS_EMBEDS (4)
|
||||
(1 << 12) => trans('admin/webhook.discord_message.supress_notifications'), // Discord flag: SUPPRESS_NOTIFICATIONS (4096)
|
||||
])
|
||||
->descriptions([
|
||||
(1 << 2) => trans('admin/webhook.discord_message.supress_embeds_text'),
|
||||
(1 << 12) => trans('admin/webhook.discord_message.supress_notifications_text'),
|
||||
]),
|
||||
CheckboxList::make('allowed_mentions')
|
||||
->label(trans('admin/webhook.discord_embed.allowed_mentions'))
|
||||
->options([
|
||||
'roles' => trans('admin/webhook.discord_embed.roles'),
|
||||
'users' => trans('admin/webhook.discord_embed.users'),
|
||||
'everyone' => trans('admin/webhook.discord_embed.everyone'),
|
||||
]),
|
||||
]),
|
||||
Repeater::make('embeds')
|
||||
->live(debounce: 500)
|
||||
->itemLabel(fn (array $state) => $state['title'] ?? '')
|
||||
->addActionLabel(trans('admin/webhook.discord_embed.add_embed'))
|
||||
->required(fn (Get $get) => empty($get('content')))
|
||||
->reorderable()
|
||||
->collapsible()
|
||||
->maxItems(10)
|
||||
->schema([
|
||||
Section::make(trans('admin/webhook.discord_embed.author'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('author.name')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.author'))
|
||||
->required(fn (Get $get) => filled($get('author.url')) || filled($get('author.icon_url'))),
|
||||
TextInput::make('author.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.author_url')),
|
||||
TextInput::make('author.icon_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.author_icon_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.body'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.title'))
|
||||
->required(fn (Get $get) => $get('name') === null),
|
||||
Textarea::make('name')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.body'))
|
||||
->required(fn (Get $get) => $get('title') === null),
|
||||
ColorPicker::make('color')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.color'))
|
||||
->hex(),
|
||||
TextInput::make('url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.images'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('image.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.image_url')),
|
||||
TextInput::make('thumbnail.url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.image_thumbnail')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.footer'))
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->schema([
|
||||
TextInput::make('footer.text')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.footer')),
|
||||
Checkbox::make('has_timestamp')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.has_timestamp')),
|
||||
TextInput::make('footer.icon_url')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.footer_icon_url')),
|
||||
]),
|
||||
Section::make(trans('admin/webhook.discord_embed.fields'))
|
||||
->collapsible()->collapsed()
|
||||
->schema([
|
||||
Repeater::make('fields')
|
||||
->reorderable()
|
||||
->addActionLabel(trans('admin/webhook.discord_embed.add_field'))
|
||||
->collapsible()
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.field_name'))
|
||||
->required(),
|
||||
Textarea::make('value')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.field_value'))
|
||||
->rows(4)
|
||||
->required(),
|
||||
Checkbox::make('inline')
|
||||
->live(debounce: 500)
|
||||
->label(trans('admin/webhook.discord_embed.inline_field')),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
public static function sendHelpBanner(): void
|
||||
{
|
||||
AlertBanner::make('discord_webhook_help')
|
||||
->title(trans('admin/webhook.help'))
|
||||
->body(trans('admin/webhook.help_text'))
|
||||
->icon('tabler-question-mark')
|
||||
->info()
|
||||
->send();
|
||||
}
|
||||
|
||||
/** @return array<string, PageRegistration> */
|
||||
public static function getDefaultPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListWebhooks::route('/'),
|
||||
'create' => CreateWebhook::route('/create'),
|
||||
'view' => ViewWebhook::route('/{record}'),
|
||||
'edit' => EditWebhook::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Filament\Server\Widgets;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Exceptions\Http\HttpForbiddenException;
|
||||
use App\Livewire\AlertBanner;
|
||||
@@ -55,7 +54,6 @@ class ServerConsole extends Widget
|
||||
|
||||
return $this->nodeJWTService
|
||||
->setExpiresAt(now()->addMinutes(10)->toImmutable())
|
||||
->setScopes(NodeJwtScope::Websocket)
|
||||
->setUser($this->user)
|
||||
->setClaims([
|
||||
'server_uuid' => $this->server->uuid,
|
||||
|
||||
@@ -6,14 +6,10 @@ use App\Exceptions\Model\DataValidationException;
|
||||
use App\Exceptions\Service\HasActiveServersException;
|
||||
use App\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
use App\Http\Requests\Api\Application\Eggs\GetEggsRequest;
|
||||
use App\Http\Requests\Api\Application\Mounts\DeleteMountRelationRequest;
|
||||
use App\Http\Requests\Api\Application\Mounts\DeleteMountRequest;
|
||||
use App\Http\Requests\Api\Application\Mounts\GetMountRequest;
|
||||
use App\Http\Requests\Api\Application\Mounts\StoreMountRequest;
|
||||
use App\Http\Requests\Api\Application\Mounts\UpdateMountEggsRequest;
|
||||
use App\Http\Requests\Api\Application\Mounts\UpdateMountNodesRequest;
|
||||
use App\Http\Requests\Api\Application\Mounts\UpdateMountRequest;
|
||||
use App\Http\Requests\Api\Application\Mounts\UpdateMountServersRequest;
|
||||
use App\Http\Requests\Api\Application\Nodes\GetNodesRequest;
|
||||
use App\Http\Requests\Api\Application\Servers\GetServerRequest;
|
||||
use App\Models\Mount;
|
||||
@@ -22,6 +18,7 @@ use App\Transformers\Api\Application\MountTransformer;
|
||||
use App\Transformers\Api\Application\NodeTransformer;
|
||||
use App\Transformers\Api\Application\ServerTransformer;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Throwable;
|
||||
@@ -167,9 +164,14 @@ class MountController extends ApplicationApiController
|
||||
*
|
||||
* @return array<array-key, mixed>
|
||||
*/
|
||||
public function addEggs(UpdateMountEggsRequest $request, Mount $mount): array
|
||||
public function addEggs(Request $request, Mount $mount): array
|
||||
{
|
||||
$mount->eggs()->attach($request->validated('eggs'));
|
||||
$validatedData = $request->validate([
|
||||
'eggs' => 'required|array|exists:eggs,id',
|
||||
'eggs.*' => 'integer',
|
||||
]);
|
||||
|
||||
$mount->eggs()->attach($validatedData['eggs']);
|
||||
|
||||
return $this->fractal->item($mount)
|
||||
->transformWith($this->getTransformer(MountTransformer::class))
|
||||
@@ -183,9 +185,14 @@ class MountController extends ApplicationApiController
|
||||
*
|
||||
* @return array<array-key, mixed>
|
||||
*/
|
||||
public function addNodes(UpdateMountNodesRequest $request, Mount $mount): array
|
||||
public function addNodes(Request $request, Mount $mount): array
|
||||
{
|
||||
$mount->nodes()->attach($request->validated('nodes'));
|
||||
$validatedData = $request->validate([
|
||||
'nodes' => 'required|array|exists:nodes,id',
|
||||
'nodes.*' => 'integer',
|
||||
]);
|
||||
|
||||
$mount->nodes()->attach($validatedData['nodes']);
|
||||
|
||||
return $this->fractal->item($mount)
|
||||
->transformWith($this->getTransformer(MountTransformer::class))
|
||||
@@ -199,9 +206,14 @@ class MountController extends ApplicationApiController
|
||||
*
|
||||
* @return array<array-key, mixed>
|
||||
*/
|
||||
public function addServers(UpdateMountServersRequest $request, Mount $mount): array
|
||||
public function addServers(Request $request, Mount $mount): array
|
||||
{
|
||||
$mount->servers()->attach($request->validated('servers'));
|
||||
$validatedData = $request->validate([
|
||||
'servers' => 'required|array|exists:servers,id',
|
||||
'servers.*' => 'integer',
|
||||
]);
|
||||
|
||||
$mount->servers()->attach($validatedData['servers']);
|
||||
|
||||
return $this->fractal->item($mount)
|
||||
->transformWith($this->getTransformer(MountTransformer::class))
|
||||
@@ -213,7 +225,7 @@ class MountController extends ApplicationApiController
|
||||
*
|
||||
* Deletes an egg from the mount's many-to-many relation.
|
||||
*/
|
||||
public function deleteEgg(DeleteMountRelationRequest $request, Mount $mount, int $egg_id): JsonResponse
|
||||
public function deleteEgg(Mount $mount, int $egg_id): JsonResponse
|
||||
{
|
||||
$mount->eggs()->detach($egg_id);
|
||||
|
||||
@@ -225,7 +237,7 @@ class MountController extends ApplicationApiController
|
||||
*
|
||||
* Deletes a node from the mount's many-to-many relation.
|
||||
*/
|
||||
public function deleteNode(DeleteMountRelationRequest $request, Mount $mount, int $node_id): JsonResponse
|
||||
public function deleteNode(Mount $mount, int $node_id): JsonResponse
|
||||
{
|
||||
$mount->nodes()->detach($node_id);
|
||||
|
||||
@@ -237,7 +249,7 @@ class MountController extends ApplicationApiController
|
||||
*
|
||||
* Deletes a server from the mount's many-to-many relation.
|
||||
*/
|
||||
public function deleteServer(DeleteMountRelationRequest $request, Mount $mount, int $server_id): JsonResponse
|
||||
public function deleteServer(Mount $mount, int $server_id): JsonResponse
|
||||
{
|
||||
$mount->servers()->detach($server_id);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Client\Servers;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Facades\Activity;
|
||||
use App\Http\Controllers\Api\Client\ClientApiController;
|
||||
use App\Http\Requests\Api\Client\Servers\Files\ChmodFilesRequest;
|
||||
@@ -94,7 +93,6 @@ class FileController extends ClientApiController
|
||||
{
|
||||
$token = $this->jwtService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setScopes(NodeJwtScope::FileDownload)
|
||||
->setUser($request->user())
|
||||
->setClaims([
|
||||
'file_path' => rawurldecode($request->get('file')),
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Client\Servers;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Http\Controllers\Api\Client\ClientApiController;
|
||||
use App\Http\Requests\Api\Client\Servers\Files\UploadFileRequest;
|
||||
use App\Models\Server;
|
||||
@@ -46,7 +45,6 @@ class FileUploadController extends ClientApiController
|
||||
{
|
||||
$token = $this->jwtService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setScopes(NodeJwtScope::FileUpload)
|
||||
->setUser($user)
|
||||
->setClaims(['server_uuid' => $server->uuid])
|
||||
->handle($server->node, $user->id . $server->uuid);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Client\Servers;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Enums\SubuserPermission;
|
||||
use App\Exceptions\Http\HttpForbiddenException;
|
||||
use App\Http\Controllers\Api\Client\ClientApiController;
|
||||
@@ -59,7 +58,6 @@ class WebsocketController extends ClientApiController
|
||||
|
||||
$token = $this->jwtService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(10))
|
||||
->setScopes(NodeJwtScope::Websocket)
|
||||
->setUser($request->user())
|
||||
->setClaims([
|
||||
'server_uuid' => $server->uuid,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers\Api\Remote;
|
||||
|
||||
use App\Events\ActivityLogged;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\Remote\ActivityEventRequest;
|
||||
use App\Models\ActivityLog;
|
||||
@@ -12,7 +11,6 @@ use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use DateTimeInterface;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ActivityProcessingController extends Controller
|
||||
@@ -79,7 +77,6 @@ class ActivityProcessingController extends Controller
|
||||
'subject_id' => $server->id,
|
||||
'subject_type' => $server->getMorphClass(),
|
||||
]);
|
||||
Event::dispatch(new ActivityLogged($activityLog));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Application\Mounts;
|
||||
|
||||
use App\Http\Requests\Api\Application\ApplicationApiRequest;
|
||||
use App\Models\Mount;
|
||||
use App\Services\Acl\Api\AdminAcl;
|
||||
|
||||
class DeleteMountRelationRequest extends ApplicationApiRequest
|
||||
{
|
||||
protected ?string $resource = Mount::RESOURCE_NAME;
|
||||
|
||||
protected int $permission = AdminAcl::WRITE;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Application\Mounts;
|
||||
|
||||
class UpdateMountEggsRequest extends StoreMountRequest
|
||||
{
|
||||
/**
|
||||
* @param array<string, string|string[]>|null $rules
|
||||
* @return array<string, string|string[]>
|
||||
*/
|
||||
public function rules(?array $rules = null): array
|
||||
{
|
||||
return [
|
||||
'eggs' => 'required|array|exists:eggs,id',
|
||||
'eggs.*' => 'integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Application\Mounts;
|
||||
|
||||
class UpdateMountNodesRequest extends StoreMountRequest
|
||||
{
|
||||
/**
|
||||
* @param array<string, string|string[]>|null $rules
|
||||
* @return array<string, string|string[]>
|
||||
*/
|
||||
public function rules(?array $rules = null): array
|
||||
{
|
||||
return [
|
||||
'nodes' => 'required|array|exists:nodes,id',
|
||||
'nodes.*' => 'integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Application\Mounts;
|
||||
|
||||
class UpdateMountServersRequest extends StoreMountRequest
|
||||
{
|
||||
/**
|
||||
* @param array<string, string|string[]>|null $rules
|
||||
* @return array<string, string|string[]>
|
||||
*/
|
||||
public function rules(?array $rules = null): array
|
||||
{
|
||||
return [
|
||||
'servers' => 'required|array|exists:servers,id',
|
||||
'servers.*' => 'integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use App\Models\WebhookConfiguration;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
@@ -34,7 +35,15 @@ class ProcessWebhook implements ShouldQueue
|
||||
$data = reset($data);
|
||||
}
|
||||
|
||||
$data = $this->normalizeData($data);
|
||||
if ($data instanceof Arrayable) {
|
||||
$data = $data->toArray();
|
||||
} elseif (is_object($data)) {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
if (is_string($data)) {
|
||||
$data = Arr::wrap(json_decode($data, true) ?? []);
|
||||
}
|
||||
$data['event'] = $this->webhookConfiguration->transformClassName($this->eventName);
|
||||
|
||||
if ($this->webhookConfiguration->type === WebhookType::Discord) {
|
||||
@@ -58,7 +67,7 @@ class ProcessWebhook implements ShouldQueue
|
||||
$headers = [];
|
||||
|
||||
if ($this->webhookConfiguration->type === WebhookType::Regular) {
|
||||
foreach ($this->webhookConfiguration->headers as $key => $value) {
|
||||
foreach ($this->webhookConfiguration->headers ?? [] as $key => $value) {
|
||||
$headers[$key] = $this->webhookConfiguration->replaceVars($data, $value);
|
||||
}
|
||||
}
|
||||
@@ -76,18 +85,4 @@ class ProcessWebhook implements ShouldQueue
|
||||
'endpoint' => $this->webhookConfiguration->endpoint,
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
private function normalizeData(mixed $data): array
|
||||
{
|
||||
if (is_string($data)) {
|
||||
return Arr::wrap(json_decode($data, true) ?? []);
|
||||
}
|
||||
|
||||
if (is_object($data)) {
|
||||
return Arr::wrap($data->toArray());
|
||||
}
|
||||
|
||||
return Arr::wrap($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,207 +2,35 @@
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Events\ActivityLogged;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class DispatchWebhooks
|
||||
{
|
||||
/** @param array<mixed>|string|null $action */
|
||||
public function handle(mixed $event, array|string|null $action = null): void
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
*/
|
||||
public function handle(string $eventName, array $data): void
|
||||
{
|
||||
if (is_string($event) && is_array($action)) {
|
||||
if (str_starts_with($event, 'eloquent.')) {
|
||||
$this->handleEloquentEvent($action[0], str($event)->between('eloquent.', ':'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event !== ActivityLogged::class) {
|
||||
$this->handleGenericClassEvent($event, $action);
|
||||
}
|
||||
|
||||
if (!$this->eventIsWatched($eventName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event instanceof ActivityLogged) {
|
||||
$this->handleActivityLogged($event);
|
||||
$this->handleGlobalWebhooks($event);
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleEloquentEvent(Model $model, string $action): void
|
||||
{
|
||||
$modelClass = $model::class;
|
||||
$eventName = "eloquent.$action: $modelClass";
|
||||
|
||||
$webhooks = WebhookConfiguration::query()
|
||||
->where('scope', WebhookScope::Global)
|
||||
->whereJsonContains('events', $eventName)
|
||||
->get();
|
||||
|
||||
if ($webhooks->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$webhookData = [
|
||||
'event' => $eventName,
|
||||
'data' => $model->toArray(),
|
||||
'timestamp' => now()->toIso8601String(),
|
||||
];
|
||||
|
||||
if (!$this->hasPayloadContent($webhookData)) {
|
||||
return;
|
||||
}
|
||||
$matchingHooks = cache()->rememberForever("webhooks.$eventName", function () use ($eventName) {
|
||||
return WebhookConfiguration::query()->whereJsonContains('events', $eventName)->get();
|
||||
});
|
||||
|
||||
/** @var WebhookConfiguration $webhookConfig */
|
||||
foreach ($webhooks as $webhookConfig) {
|
||||
$webhookConfig->run($eventName, [$webhookData]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<mixed> $payload */
|
||||
protected function handleGenericClassEvent(string $eventName, array $payload): void
|
||||
{
|
||||
if (!$this->eventIsWatched($eventName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$matchingHooks = cache()->rememberForever("webhooks.$eventName", function () use ($eventName) {
|
||||
return WebhookConfiguration::query()
|
||||
->where('scope', WebhookScope::Global)
|
||||
->whereJsonContains('events', $eventName)
|
||||
->get();
|
||||
});
|
||||
|
||||
if ($matchingHooks->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$obj = $payload[0] ?? null;
|
||||
$webhookData = ['event' => $eventName, 'timestamp' => now()->toIso8601String()];
|
||||
if (is_object($obj)) {
|
||||
$webhookData['data'] = $obj->toArray();
|
||||
} elseif (is_array($obj)) {
|
||||
$webhookData['data'] = $obj;
|
||||
}
|
||||
|
||||
foreach ($matchingHooks as $webhookConfig) {
|
||||
$webhookConfig->run($eventName, [$webhookData]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleActivityLogged(ActivityLogged $activityLogged): void
|
||||
{
|
||||
$eventName = $activityLogged->model->event;
|
||||
|
||||
if (!$activityLogged->isServerEvent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$server = null;
|
||||
$morphClass = (new Server())->getMorphClass();
|
||||
foreach ($activityLogged->model->subjects as $subject) {
|
||||
if ($subject->subject_type === $morphClass && $subject->subject instanceof Server) {
|
||||
$server = $subject->subject;
|
||||
break;
|
||||
if (in_array($eventName, $webhookConfig->events)) {
|
||||
$webhookConfig->run($eventName, $data);
|
||||
}
|
||||
}
|
||||
if (!$server && isset($activityLogged->model->properties['server'])) {
|
||||
$server = Server::find($activityLogged->model->properties['server']['id'] ?? null);
|
||||
}
|
||||
|
||||
if (!$server) {
|
||||
return;
|
||||
}
|
||||
|
||||
$webhooks = $server->webhookConfigurations()
|
||||
->whereJsonContains('events', $eventName)
|
||||
->get();
|
||||
|
||||
if ($webhooks->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$webhookData = $this->buildActivityPayload($activityLogged);
|
||||
|
||||
if (!$this->hasPayloadContent($webhookData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($webhooks as $webhookConfig) {
|
||||
$webhookConfig->run($eventName, [$webhookData]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
protected function buildActivityPayload(ActivityLogged $activityLogged): array
|
||||
{
|
||||
$webhookData = [
|
||||
'event' => $activityLogged->model->event,
|
||||
'description' => $activityLogged->model->description,
|
||||
'ip' => $activityLogged->model->ip,
|
||||
'timestamp' => $activityLogged->model->timestamp->toIso8601String(),
|
||||
];
|
||||
|
||||
if ($activityLogged->model->actor_id) {
|
||||
$actor = $activityLogged->model->actor;
|
||||
$webhookData['actor'] = [
|
||||
'id' => $activityLogged->model->actor_id,
|
||||
'type' => $activityLogged->model->actor_type,
|
||||
'username' => $actor instanceof User ? $actor->username : null,
|
||||
];
|
||||
}
|
||||
|
||||
if ($activityLogged->model->properties->isNotEmpty()) {
|
||||
$webhookData['properties'] = $activityLogged->model->properties->toArray();
|
||||
}
|
||||
|
||||
if ($activityLogged->model->subjects->isNotEmpty()) {
|
||||
$webhookData['subjects'] = $activityLogged->model->subjects->map(fn ($subject) => [
|
||||
'id' => $subject->subject_id,
|
||||
'type' => $subject->subject_type,
|
||||
])->toArray();
|
||||
}
|
||||
|
||||
return $webhookData;
|
||||
}
|
||||
|
||||
protected function handleGlobalWebhooks(ActivityLogged $activityLogged): void
|
||||
{
|
||||
$eventName = $activityLogged->model->event;
|
||||
|
||||
if (!$this->eventIsWatched($eventName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$matchingHooks = cache()->rememberForever("webhooks.$eventName", function () use ($eventName) {
|
||||
return WebhookConfiguration::query()
|
||||
->where('scope', WebhookScope::Global)
|
||||
->whereJsonContains('events', $eventName)
|
||||
->get();
|
||||
});
|
||||
|
||||
$webhookData = $this->buildActivityPayload($activityLogged);
|
||||
|
||||
if (!$this->hasPayloadContent($webhookData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($matchingHooks as $webhookConfig) {
|
||||
$webhookConfig->run($eventName, [$webhookData]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function eventIsWatched(string $eventName): bool
|
||||
{
|
||||
$watchedEvents = cache()->rememberForever('watchedWebhooks', function () {
|
||||
return WebhookConfiguration::where('scope', WebhookScope::Global)
|
||||
->pluck('events')
|
||||
return WebhookConfiguration::pluck('events')
|
||||
->flatten()
|
||||
->unique()
|
||||
->values()
|
||||
@@ -211,17 +39,4 @@ class DispatchWebhooks
|
||||
|
||||
return in_array($eventName, $watchedEvents);
|
||||
}
|
||||
|
||||
/** @param array<mixed> $webhookData */
|
||||
protected function hasPayloadContent(array $webhookData): bool
|
||||
{
|
||||
return collect($webhookData)
|
||||
->except('event')
|
||||
->reject(fn (mixed $value) => match (true) {
|
||||
is_array($value) => empty($value),
|
||||
$value instanceof Collection => $value->isEmpty(),
|
||||
default => $value === null || $value === '',
|
||||
})
|
||||
->isNotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Schemas\Components\Concerns\CanPoll;
|
||||
use Filament\Support\Concerns\EvaluatesClosures;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class DiscordPreview extends Component
|
||||
{
|
||||
use CanPoll;
|
||||
use EvaluatesClosures;
|
||||
|
||||
public ?WebhookConfiguration $record = null;
|
||||
|
||||
/** @var array<string, mixed>|null */
|
||||
public ?array $formPayload = null;
|
||||
|
||||
#[On('discord-form-changed')]
|
||||
public function onFormChanged(
|
||||
string $content = '',
|
||||
string $username = '',
|
||||
string $avatar_url = '',
|
||||
mixed $embeds = [],
|
||||
): void {
|
||||
$this->formPayload = [
|
||||
'content' => $content,
|
||||
'username' => $username,
|
||||
'avatar_url' => $avatar_url,
|
||||
'embeds' => is_array($embeds) ? $embeds : [],
|
||||
];
|
||||
}
|
||||
|
||||
private function safeUrl(?string $url): ?string
|
||||
{
|
||||
return ($url && preg_match('/^https?:\/\//i', $url)) ? $url : null;
|
||||
}
|
||||
|
||||
/** @return array<int, array<string, mixed>> */
|
||||
private function processEmbeds(mixed $embeds): array
|
||||
{
|
||||
return collect($embeds)
|
||||
->filter(fn (mixed $embed) => is_array($embed))
|
||||
->take(10)
|
||||
->map(function (array $embed): array {
|
||||
$color = $embed['color'] ?? null;
|
||||
$embed['color'] = match (true) {
|
||||
is_int($color) => '#' . str_pad(dechex($color), 6, '0', STR_PAD_LEFT),
|
||||
is_string($color) => $color,
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (!isset($embed['timestamp']) && !empty($embed['has_timestamp'])) {
|
||||
$embed['timestamp'] = now()->toIso8601String();
|
||||
}
|
||||
|
||||
if (isset($embed['timestamp'])) {
|
||||
try {
|
||||
$embed['timestamp'] = Carbon::parse($embed['timestamp'])->format('M j, Y H:i');
|
||||
} catch (\Throwable) {
|
||||
unset($embed['timestamp']);
|
||||
}
|
||||
}
|
||||
|
||||
$embed['view'] = [
|
||||
'author_name' => $embed['author']['name'] ?? null,
|
||||
'author_url' => $this->safeUrl($embed['author']['url'] ?? null),
|
||||
'author_icon' => $this->safeUrl($embed['author']['icon_url'] ?? null),
|
||||
'title' => $embed['title'] ?? null,
|
||||
'title_url' => $this->safeUrl($embed['url'] ?? null),
|
||||
'description' => $embed['description'] ?? null,
|
||||
'fields' => $embed['fields'] ?? [],
|
||||
'image' => $this->safeUrl($embed['image']['url'] ?? null),
|
||||
'thumbnail' => $this->safeUrl($embed['thumbnail']['url'] ?? null),
|
||||
'footer_text' => $embed['footer']['text'] ?? null,
|
||||
'footer_icon' => $this->safeUrl($embed['footer']['icon_url'] ?? null),
|
||||
'timestamp' => $embed['timestamp'] ?? null,
|
||||
'color_style' => $embed['color']
|
||||
? 'border-left-color: ' . $embed['color']
|
||||
: 'border-left-color: #1e1f22',
|
||||
];
|
||||
|
||||
return $embed;
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.discord-preview', $this->getViewData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* link: callable,
|
||||
* content: mixed,
|
||||
* sender: array{name: string, avatar: string},
|
||||
* embeds: array<int, mixed>,
|
||||
* getTime: mixed
|
||||
* }
|
||||
*/
|
||||
public function getViewData(): array
|
||||
{
|
||||
$default = [
|
||||
'link' => fn ($href, $child) => $href ? "<a href=\"$href\" target=\"_blank\" class=\"link\">$child</a>" : $child,
|
||||
'content' => null,
|
||||
'sender' => [
|
||||
'name' => 'Pelican',
|
||||
'avatar' => asset('pelican.svg'),
|
||||
],
|
||||
'embeds' => [],
|
||||
'getTime' => fn () => now()->format('H:i'),
|
||||
];
|
||||
|
||||
$payloadArray = $this->formPayload;
|
||||
|
||||
if ($payloadArray === null) {
|
||||
if (!$this->record || !$this->record->payload) {
|
||||
return $default;
|
||||
}
|
||||
$payloadArray = $this->record->payload;
|
||||
}
|
||||
|
||||
$scope = $this->record !== null ? $this->record->scope : WebhookScope::Global;
|
||||
$sampleData = $scope === WebhookScope::Server
|
||||
? WebhookConfiguration::getServerWebhookSampleData()
|
||||
: WebhookConfiguration::getWebhookSampleData();
|
||||
|
||||
$payloadJson = json_encode($payloadArray) ?: '{}';
|
||||
$replacedPayload = (new WebhookConfiguration())->replaceVars($sampleData, $payloadJson);
|
||||
$data = json_decode($replacedPayload, true) ?? [];
|
||||
|
||||
return [
|
||||
'link' => fn ($href, $child) => $href ? "<a href=\"$href\" target=\"_blank\" class=\"link\">$child</a>" : $child,
|
||||
'content' => data_get($data, 'content'),
|
||||
'sender' => [
|
||||
'name' => filled(data_get($data, 'username')) ? data_get($data, 'username') : 'Pelican',
|
||||
'avatar' => filled(data_get($data, 'avatar_url')) ? data_get($data, 'avatar_url') : asset('pelican.svg'),
|
||||
],
|
||||
'embeds' => $this->processEmbeds(data_get($data, 'embeds', [])),
|
||||
'getTime' => fn () => now()->format('H:i'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Models\Node;
|
||||
use App\Services\Nodes\NodeJWTService;
|
||||
@@ -48,7 +47,6 @@ class NodeClientConnectivity extends Component
|
||||
|
||||
$wsToken = $this->nodeJWTService
|
||||
->setExpiresAt(now()->addMinute()->toImmutable())
|
||||
->setScopes(NodeJwtScope::Websocket)
|
||||
->setUser($user)
|
||||
->setClaims([
|
||||
'server_uuid' => $server->uuid,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\TablerIcon;
|
||||
use App\Events\ActivityLogged;
|
||||
use App\Traits\HasValidation;
|
||||
use BackedEnum;
|
||||
use Filament\Facades\Filament;
|
||||
@@ -17,6 +18,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Str;
|
||||
use LogicException;
|
||||
|
||||
@@ -138,6 +140,9 @@ class ActivityLog extends Model implements HasIcon, HasLabel
|
||||
$model->timestamp = Carbon::now();
|
||||
});
|
||||
|
||||
static::created(function (self $model) {
|
||||
Event::dispatch(new ActivityLogged($model));
|
||||
});
|
||||
}
|
||||
|
||||
public function getIcon(): BackedEnum
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Contracts\Validatable;
|
||||
use App\Enums\ContainerStatus;
|
||||
use App\Enums\ServerResourceType;
|
||||
use App\Enums\ServerState;
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Exceptions\Http\Server\ServerStateConflictException;
|
||||
use App\Models\Traits\HasIcon;
|
||||
use App\Repositories\Daemon\DaemonServerRepository;
|
||||
@@ -382,15 +381,6 @@ class Server extends Model implements HasAvatar, Validatable
|
||||
return $this->morphToMany(Mount::class, 'mountable');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany<WebhookConfiguration, $this>
|
||||
*/
|
||||
public function webhookConfigurations(): HasMany
|
||||
{
|
||||
return $this->hasMany(WebhookConfiguration::class, 'server_id', 'id')
|
||||
->where('scope', WebhookScope::Server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the activity log entries where the server is the subject.
|
||||
*/
|
||||
|
||||
@@ -2,17 +2,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Enums\WebhookType;
|
||||
use App\Jobs\ProcessWebhook;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Livewire\Features\SupportEvents\HandlesEvents;
|
||||
|
||||
@@ -21,7 +18,6 @@ use Livewire\Features\SupportEvents\HandlesEvents;
|
||||
* @property string $endpoint
|
||||
* @property string $description
|
||||
* @property string[] $events
|
||||
* @property WebhookScope $scope
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
@@ -59,9 +55,6 @@ class WebhookConfiguration extends Model
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'scope',
|
||||
'server_id',
|
||||
'type',
|
||||
'payload',
|
||||
'endpoint',
|
||||
@@ -74,7 +67,6 @@ class WebhookConfiguration extends Model
|
||||
* Default values for specific fields in the database.
|
||||
*/
|
||||
protected $attributes = [
|
||||
'scope' => WebhookScope::Global,
|
||||
'type' => WebhookType::Regular,
|
||||
'payload' => null,
|
||||
];
|
||||
@@ -82,7 +74,6 @@ class WebhookConfiguration extends Model
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'scope' => WebhookScope::class,
|
||||
'events' => 'array',
|
||||
'payload' => 'array',
|
||||
'type' => WebhookType::class,
|
||||
@@ -109,23 +100,10 @@ class WebhookConfiguration extends Model
|
||||
private static function updateCache(Collection $eventList): void
|
||||
{
|
||||
$eventList->each(function (string $event) {
|
||||
cache()->forget("webhooks.$event");
|
||||
cache()->forever("webhooks.$event", WebhookConfiguration::query()->whereJsonContains('events', $event)->get());
|
||||
});
|
||||
|
||||
$eventList->each(function (string $event) {
|
||||
cache()->forever("webhooks.$event", static::query()
|
||||
->where('scope', WebhookScope::Global)
|
||||
->whereJsonContains('events', $event)
|
||||
->get());
|
||||
});
|
||||
|
||||
cache()->forget('watchedWebhooks');
|
||||
cache()->forever('watchedWebhooks', static::where('scope', WebhookScope::Global)
|
||||
->pluck('events')
|
||||
->flatten()
|
||||
->unique()
|
||||
->values()
|
||||
->all());
|
||||
cache()->forever('watchedWebhooks', WebhookConfiguration::pluck('events')->flatten()->unique()->values()->all());
|
||||
}
|
||||
|
||||
public function webhooks(): HasMany
|
||||
@@ -133,11 +111,6 @@ class WebhookConfiguration extends Model
|
||||
return $this->hasMany(Webhook::class);
|
||||
}
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public static function allPossibleEvents(): array
|
||||
{
|
||||
@@ -148,211 +121,18 @@ class WebhookConfiguration extends Model
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $filterList
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function eventList(array $filterList): array
|
||||
{
|
||||
return collect(static::allPossibleEvents())
|
||||
->filter(function ($event) use ($filterList) {
|
||||
foreach ($filterList as $filter) {
|
||||
$eventLower = strtolower($event);
|
||||
$filterLower = strtolower($filter);
|
||||
|
||||
if ($eventLower === $filterLower) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$pattern = '/(?:\\\\|\\.)' . preg_quote($filterLower) . '(?:\\\\|:|$)/i';
|
||||
if (preg_match($pattern, $eventLower)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public static function adminEvents(): array
|
||||
{
|
||||
return static::eventList([
|
||||
'User',
|
||||
'Role',
|
||||
'SSHKey',
|
||||
'ApiKey',
|
||||
'Token',
|
||||
'HasAccessTokens',
|
||||
'Node',
|
||||
'Allocation',
|
||||
'DatabaseHost',
|
||||
'Mount',
|
||||
'NodeRole',
|
||||
'Egg',
|
||||
'EggVariable',
|
||||
'Plugin',
|
||||
'WebhookConfiguration',
|
||||
'Webhook',
|
||||
'Captcha',
|
||||
'Authentication',
|
||||
'ActivityLogged',
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public static function globalServerEvents(): array
|
||||
{
|
||||
return static::eventList([
|
||||
'Server',
|
||||
'ServerTransfer',
|
||||
'ServerVariable',
|
||||
'Allocation',
|
||||
'Backup',
|
||||
'Database',
|
||||
'File',
|
||||
'Schedule',
|
||||
'Task',
|
||||
'Subuser',
|
||||
'Installed',
|
||||
'SubUserAdded',
|
||||
'SubUserRemoved',
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public static function allPossibleServerEvents(): array
|
||||
{
|
||||
$events = [
|
||||
'server:file.read',
|
||||
'server:file.write',
|
||||
'server:file.rename',
|
||||
'server:file.copy',
|
||||
'server:file.compress',
|
||||
'server:file.decompress',
|
||||
'server:file.delete',
|
||||
'server:file.create-directory',
|
||||
'server:file.uploaded',
|
||||
'server:file.pull',
|
||||
'server:file.download',
|
||||
|
||||
'server:power.start',
|
||||
'server:power.stop',
|
||||
'server:power.restart',
|
||||
'server:power.kill',
|
||||
|
||||
'server:console.command',
|
||||
|
||||
'server:startup.edit',
|
||||
'server:startup.image',
|
||||
'server:settings.rename',
|
||||
'server:settings.description',
|
||||
'server:settings.reinstall',
|
||||
|
||||
'server:allocation.notes',
|
||||
'server:allocation.primary',
|
||||
'server:allocation.create',
|
||||
'server:allocation.delete',
|
||||
|
||||
'server:schedule.create',
|
||||
'server:schedule.update',
|
||||
'server:schedule.execute',
|
||||
'server:schedule.delete',
|
||||
|
||||
'server:task.create',
|
||||
'server:task.update',
|
||||
'server:task.delete',
|
||||
|
||||
'server:backup.start',
|
||||
'server:backup.delete',
|
||||
'server:backup.download',
|
||||
'server:backup.rename',
|
||||
'server:backup.restore',
|
||||
'server:backup.restore-complete',
|
||||
'server:backup.restore-failed',
|
||||
|
||||
'server:database.create',
|
||||
'server:database.rotate-password',
|
||||
'server:database.delete',
|
||||
|
||||
'server:subuser.create',
|
||||
'server:subuser.update',
|
||||
'server:subuser.delete',
|
||||
|
||||
'server:sftp.denied',
|
||||
];
|
||||
|
||||
Event::dispatch('server:webhook.events', [&$events]);
|
||||
|
||||
return array_unique($events);
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public static function allPossibleAdminOnlyEvents(): array
|
||||
{
|
||||
$events = static::eventList([
|
||||
'User',
|
||||
'Role',
|
||||
'SSHKey',
|
||||
'ApiKey',
|
||||
'Token',
|
||||
'HasAccessTokens',
|
||||
'Node',
|
||||
'Allocation',
|
||||
'DatabaseHost',
|
||||
'Mount',
|
||||
'NodeRole',
|
||||
'Egg',
|
||||
'EggVariable',
|
||||
'Plugin',
|
||||
'WebhookConfiguration',
|
||||
'Webhook',
|
||||
'Captcha',
|
||||
'Authentication',
|
||||
'ActivityLogged',
|
||||
]);
|
||||
|
||||
$serverEvents = collect(static::discoverCustomEvents())
|
||||
->merge(static::allModelEvents())
|
||||
->unique()
|
||||
->filter(fn ($event) => str($event)->contains('App\\Models\\Server') && !str($event)->contains('Subuser'))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return array_values(array_unique(array_merge($events, $serverEvents)));
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public static function filamentCheckboxList(WebhookScope $scope): array
|
||||
public static function filamentCheckboxList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
if ($scope === WebhookScope::Server) {
|
||||
$events = static::allPossibleServerEvents();
|
||||
foreach ($events as $event) {
|
||||
$list[$event] = static::transformServerEventName($event);
|
||||
}
|
||||
} else {
|
||||
$events = static::allPossibleAdminOnlyEvents();
|
||||
foreach ($events as $event) {
|
||||
$list[$event] = static::transformClassName($event);
|
||||
}
|
||||
$events = static::allPossibleEvents();
|
||||
foreach ($events as $event) {
|
||||
$list[$event] = static::transformClassName($event);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public static function transformServerEventName(string $event): string
|
||||
{
|
||||
return str($event)
|
||||
->after('server:')
|
||||
->replace('.', ' → ')
|
||||
->title()
|
||||
->toString();
|
||||
}
|
||||
|
||||
public static function transformClassName(string $event): string
|
||||
{
|
||||
return str($event)
|
||||
@@ -434,16 +214,10 @@ class WebhookConfiguration extends Model
|
||||
/** @param array<mixed, mixed> $eventData */
|
||||
public function run(?string $eventName = null, ?array $eventData = null): void
|
||||
{
|
||||
if ($this->scope === WebhookScope::Server) {
|
||||
$eventName ??= 'server:file.write';
|
||||
$eventData ??= static::getServerWebhookSampleData();
|
||||
}
|
||||
|
||||
$eventName ??= 'eloquent.created: '.Server::class;
|
||||
$eventData ??= static::getWebhookSampleData();
|
||||
|
||||
$payload = array_is_list($eventData) ? $eventData : [$eventData];
|
||||
ProcessWebhook::dispatch($this, $eventName, $payload);
|
||||
ProcessWebhook::dispatch($this, $eventName, [$eventData]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -646,29 +420,4 @@ class WebhookConfiguration extends Model
|
||||
'event' => 'updated: Server',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function getServerWebhookSampleData(): array
|
||||
{
|
||||
return [
|
||||
'user' => [
|
||||
'uuid' => '12345678-1234-5678-9012-123456789012',
|
||||
'username' => 'admin',
|
||||
'email' => 'admin@example.com',
|
||||
'image' => 'https://www.gravatar.com/avatar/default',
|
||||
'admin' => true,
|
||||
'language' => 'en',
|
||||
'created_at' => '2025-06-01T12:31:50.000000Z',
|
||||
'updated_at' => '2025-06-01T12:31:50.000000Z',
|
||||
],
|
||||
'server' => [
|
||||
'uuid' => '87654321-4321-8765-2109-876543210987',
|
||||
'name' => 'Example Server',
|
||||
'node' => 'node1.example.com',
|
||||
'description' => 'Sample Minecraft server',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Events\ActivityLogged;
|
||||
use App\Listeners\DispatchWebhooks;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
@@ -12,8 +11,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
* The event to listener mappings for the application.
|
||||
*/
|
||||
protected $listen = [
|
||||
ActivityLogged::class => [DispatchWebhooks::class],
|
||||
'App\\Events\\*' => [DispatchWebhooks::class],
|
||||
'App\\*' => [DispatchWebhooks::class],
|
||||
'eloquent.created*' => [DispatchWebhooks::class],
|
||||
'eloquent.deleted*' => [DispatchWebhooks::class],
|
||||
'eloquent.updated*' => [DispatchWebhooks::class],
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Services\Activity;
|
||||
|
||||
use App\Events\ActivityLogged;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
@@ -13,7 +12,6 @@ use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Throwable;
|
||||
use Webmozart\Assert\Assert;
|
||||
@@ -246,8 +244,6 @@ class ActivityLogService
|
||||
return $this->activity;
|
||||
});
|
||||
|
||||
Event::dispatch(new ActivityLogged($response));
|
||||
|
||||
$this->activity = null;
|
||||
$this->subjects = [];
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Services\Backups;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Extensions\Backups\BackupManager;
|
||||
use App\Extensions\Filesystem\S3Filesystem;
|
||||
use App\Models\Backup;
|
||||
@@ -29,7 +28,6 @@ class DownloadLinkService
|
||||
|
||||
$token = $this->jwtService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setScopes(NodeJwtScope::BackupDownload)
|
||||
->setUser($user)
|
||||
->setClaims([
|
||||
'backup_uuid' => $backup->uuid,
|
||||
|
||||
@@ -78,7 +78,7 @@ class EggExporterService
|
||||
return null;
|
||||
}
|
||||
|
||||
public function yamlExport(mixed $data): mixed
|
||||
protected function yamlExport(mixed $data): mixed
|
||||
{
|
||||
if ($data instanceof Collection) {
|
||||
$data = $data->all();
|
||||
|
||||
@@ -113,7 +113,6 @@ class EggImporterService
|
||||
'uuid' => $uuid,
|
||||
'author' => Arr::get($parsed, 'author'),
|
||||
'copy_script_from' => null,
|
||||
'config_from' => null,
|
||||
]);
|
||||
|
||||
for ($i = 0; $i < count($parsed['variables']); $i++) {
|
||||
@@ -147,7 +146,7 @@ class EggImporterService
|
||||
*
|
||||
* @throws InvalidFileUploadException|JsonException
|
||||
*/
|
||||
public function parse(string $content, EggFormat $format): array
|
||||
protected function parse(string $content, EggFormat $format): array
|
||||
{
|
||||
try {
|
||||
$parsed = match ($format) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Services\Nodes;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Extensions\Lcobucci\JWT\Encoding\TimestampDates;
|
||||
use App\Models\Node;
|
||||
use App\Models\User;
|
||||
@@ -13,7 +12,6 @@ use Lcobucci\JWT\Configuration;
|
||||
use Lcobucci\JWT\Signer\Hmac\Sha256;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class NodeJWTService
|
||||
{
|
||||
@@ -26,9 +24,6 @@ class NodeJWTService
|
||||
|
||||
private ?string $subject = null;
|
||||
|
||||
/** @var NodeJwtScope[] */
|
||||
private array $scopes;
|
||||
|
||||
/**
|
||||
* Set the claims to include in this JWT.
|
||||
*
|
||||
@@ -69,11 +64,9 @@ class NodeJWTService
|
||||
/**
|
||||
* Generate a new JWT for a given node.
|
||||
*/
|
||||
public function handle(Node $node, ?string $identifiedBy): UnencryptedToken
|
||||
public function handle(Node $node, ?string $identifiedBy, string $algo = 'sha256'): UnencryptedToken
|
||||
{
|
||||
Assert::notEmpty($this->scopes, 'Cannot generate a JWT without providing at least one scope.');
|
||||
|
||||
$identifier = hash('sha256', $identifiedBy);
|
||||
$identifier = hash($algo, $identifiedBy);
|
||||
$config = Configuration::forSymmetricSigner(new Sha256(), InMemory::plainText($node->daemon_token));
|
||||
|
||||
$builder = $config->builder(new TimestampDates())
|
||||
@@ -100,17 +93,8 @@ class NodeJWTService
|
||||
$builder = $builder->withClaim('user_uuid', $this->user->uuid);
|
||||
}
|
||||
|
||||
$builder = $builder->withClaim('scope', implode(' ', array_map(fn ($scope) => $scope->value, $this->scopes)));
|
||||
|
||||
return $builder
|
||||
->withClaim('unique_id', Str::random())
|
||||
->getToken($config->signer(), $config->signingKey());
|
||||
}
|
||||
|
||||
public function setScopes(NodeJwtScope ...$scopes): self
|
||||
{
|
||||
$this->scopes = $scopes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Services\Servers;
|
||||
|
||||
use App\Enums\NodeJwtScope;
|
||||
use App\Models\Allocation;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Node;
|
||||
@@ -100,9 +99,8 @@ class TransferServerService
|
||||
// Generate a token for the destination node that the source node can use to authenticate with.
|
||||
$token = $this->nodeJWTService
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setScopes(NodeJwtScope::ServerTransfer)
|
||||
->setSubject($server->uuid)
|
||||
->handle($transfer->newNode, $server->uuid);
|
||||
->handle($transfer->newNode, $server->uuid, 'sha256');
|
||||
|
||||
// Notify the source node of the pending outgoing transfer.
|
||||
$this->notify($transfer, $token, $backup_uuid);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\WebhookScope;
|
||||
use App\Jobs\ProcessWebhook;
|
||||
use App\Models\Server;
|
||||
use App\Models\WebhookConfiguration;
|
||||
|
||||
class WebhookService
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $contextualData
|
||||
*/
|
||||
public function dispatch(string $eventName, array $contextualData, ?Server $server = null): void
|
||||
{
|
||||
if ($server) {
|
||||
$webhooks = $server->webhookConfigurations()
|
||||
->whereJsonContains('events', $eventName)
|
||||
->get();
|
||||
|
||||
foreach ($webhooks as $webhook) {
|
||||
ProcessWebhook::dispatch($webhook, $eventName, [$contextualData]);
|
||||
}
|
||||
}
|
||||
|
||||
$globalWebhooks = WebhookConfiguration::query()
|
||||
->where('scope', WebhookScope::Global)
|
||||
->whereJsonContains('events', $eventName)
|
||||
->get();
|
||||
|
||||
foreach ($globalWebhooks as $webhook) {
|
||||
ProcessWebhook::dispatch($webhook, $eventName, [$contextualData]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getAllEvents(WebhookScope $scope = WebhookScope::Global): array
|
||||
{
|
||||
return WebhookConfiguration::filamentCheckboxList($scope);
|
||||
}
|
||||
}
|
||||
243
composer.lock
generated
243
composer.lock
generated
@@ -62,16 +62,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.385.0",
|
||||
"version": "3.384.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "994e3408fb963ead9e1ba8ff1a751aa039bfbdf1"
|
||||
"reference": "c7d34f2d60515bd0c307e462268f75877842da4a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/994e3408fb963ead9e1ba8ff1a751aa039bfbdf1",
|
||||
"reference": "994e3408fb963ead9e1ba8ff1a751aa039bfbdf1",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c7d34f2d60515bd0c307e462268f75877842da4a",
|
||||
"reference": "c7d34f2d60515bd0c307e462268f75877842da4a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -82,7 +82,7 @@
|
||||
"guzzlehttp/guzzle": "^7.4.5",
|
||||
"guzzlehttp/promises": "^2.0",
|
||||
"guzzlehttp/psr7": "^2.4.5",
|
||||
"mtdowling/jmespath.php": "^2.9.1",
|
||||
"mtdowling/jmespath.php": "^2.8.0",
|
||||
"php": ">=8.1",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0"
|
||||
@@ -153,9 +153,9 @@
|
||||
"support": {
|
||||
"forum": "https://github.com/aws/aws-sdk-php/discussions",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.385.0"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.384.5"
|
||||
},
|
||||
"time": "2026-06-16T23:06:51+00:00"
|
||||
"time": "2026-06-08T18:25:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "blade-ui-kit/blade-heroicons",
|
||||
@@ -806,16 +806,16 @@
|
||||
},
|
||||
{
|
||||
"name": "dedoc/scramble",
|
||||
"version": "v0.13.28",
|
||||
"version": "v0.13.26",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dedoc/scramble.git",
|
||||
"reference": "e50927c732a341bb743671066892eec6bd2e958b"
|
||||
"reference": "5ca42b5e23b9d5c120607138f790b51e22d8b4a1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dedoc/scramble/zipball/e50927c732a341bb743671066892eec6bd2e958b",
|
||||
"reference": "e50927c732a341bb743671066892eec6bd2e958b",
|
||||
"url": "https://api.github.com/repos/dedoc/scramble/zipball/5ca42b5e23b9d5c120607138f790b51e22d8b4a1",
|
||||
"reference": "5ca42b5e23b9d5c120607138f790b51e22d8b4a1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -874,7 +874,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dedoc/scramble/issues",
|
||||
"source": "https://github.com/dedoc/scramble/tree/v0.13.28"
|
||||
"source": "https://github.com/dedoc/scramble/tree/v0.13.26"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -882,7 +882,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-14T18:21:12+00:00"
|
||||
"time": "2026-06-02T14:43:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
@@ -1793,16 +1793,16 @@
|
||||
},
|
||||
{
|
||||
"name": "firebase/php-jwt",
|
||||
"version": "v7.1.0",
|
||||
"version": "v7.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/googleapis/php-jwt.git",
|
||||
"reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0"
|
||||
"reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0",
|
||||
"reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0",
|
||||
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/47ad26bab5e7c70ae8a6f08ed25ff83631121380",
|
||||
"reference": "47ad26bab5e7c70ae8a6f08ed25ff83631121380",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1811,7 +1811,6 @@
|
||||
"require-dev": {
|
||||
"guzzlehttp/guzzle": "^7.4",
|
||||
"phpfastcache/phpfastcache": "^9.2",
|
||||
"phpseclib/phpseclib": "~3.0",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psr/cache": "^2.0||^3.0",
|
||||
@@ -1820,8 +1819,7 @@
|
||||
},
|
||||
"suggest": {
|
||||
"ext-sodium": "Support EdDSA (Ed25519) signatures",
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
|
||||
"phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures"
|
||||
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1846,16 +1844,16 @@
|
||||
}
|
||||
],
|
||||
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
|
||||
"homepage": "https://github.com/googleapis/php-jwt",
|
||||
"homepage": "https://github.com/firebase/php-jwt",
|
||||
"keywords": [
|
||||
"jwt",
|
||||
"php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/googleapis/php-jwt/issues",
|
||||
"source": "https://github.com/googleapis/php-jwt/tree/v7.1.0"
|
||||
"source": "https://github.com/googleapis/php-jwt/tree/v7.0.5"
|
||||
},
|
||||
"time": "2026-06-11T17:54:14+00:00"
|
||||
"time": "2026-04-01T20:38:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fruitcake/php-cors",
|
||||
@@ -2055,22 +2053,22 @@
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.12.0",
|
||||
"version": "7.11.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "eaa81598031cf57a9e36258c8546defffc994cba"
|
||||
"reference": "5af96f374e0ab4ebd747b8310888c99d3adb0a8c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/eaa81598031cf57a9e36258c8546defffc994cba",
|
||||
"reference": "eaa81598031cf57a9e36258c8546defffc994cba",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/5af96f374e0ab4ebd747b8310888c99d3adb0a8c",
|
||||
"reference": "5af96f374e0ab4ebd747b8310888c99d3adb0a8c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^2.5",
|
||||
"guzzlehttp/psr7": "^2.12",
|
||||
"guzzlehttp/psr7": "^2.11",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.5 || ^3.0",
|
||||
@@ -2163,7 +2161,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.12.0"
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.11.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2179,7 +2177,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-16T22:11:48+00:00"
|
||||
"time": "2026-06-07T22:54:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
@@ -2267,16 +2265,16 @@
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.12.0",
|
||||
"version": "2.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/psr7.git",
|
||||
"reference": "9b38012e7b54f594707e6db52c684dc0a74b3a43"
|
||||
"reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/9b38012e7b54f594707e6db52c684dc0a74b3a43",
|
||||
"reference": "9b38012e7b54f594707e6db52c684dc0a74b3a43",
|
||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/bbb5e61349fa5cb822b3e87842b951088b76b81f",
|
||||
"reference": "bbb5e61349fa5cb822b3e87842b951088b76b81f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2366,7 +2364,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/psr7/issues",
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.12.0"
|
||||
"source": "https://github.com/guzzle/psr7/tree/2.11.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2382,20 +2380,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-16T21:50:11+00:00"
|
||||
"time": "2026-06-02T12:30:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/uri-template",
|
||||
"version": "v1.0.7",
|
||||
"version": "v1.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/uri-template.git",
|
||||
"reference": "7fe811c23a9e3cd712b4389eaeb50b5456d8c529"
|
||||
"reference": "eef7f87bab6f204eba3c39224d8075c70c637946"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/uri-template/zipball/7fe811c23a9e3cd712b4389eaeb50b5456d8c529",
|
||||
"reference": "7fe811c23a9e3cd712b4389eaeb50b5456d8c529",
|
||||
"url": "https://api.github.com/repos/guzzle/uri-template/zipball/eef7f87bab6f204eba3c39224d8075c70c637946",
|
||||
"reference": "eef7f87bab6f204eba3c39224d8075c70c637946",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2452,7 +2450,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/uri-template/issues",
|
||||
"source": "https://github.com/guzzle/uri-template/tree/v1.0.7"
|
||||
"source": "https://github.com/guzzle/uri-template/tree/v1.0.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2468,7 +2466,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-12T21:33:43+00:00"
|
||||
"time": "2026-05-23T22:00:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "kirschbaum-development/eloquent-power-joins",
|
||||
@@ -2535,16 +2533,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v13.16.1",
|
||||
"version": "v13.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "6135650d69bd9442e470bb1b343422081b076f1e"
|
||||
"reference": "7e23b2aa4e1133a43835c93a810b4bedc40e425b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/6135650d69bd9442e470bb1b343422081b076f1e",
|
||||
"reference": "6135650d69bd9442e470bb1b343422081b076f1e",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/7e23b2aa4e1133a43835c93a810b4bedc40e425b",
|
||||
"reference": "7e23b2aa4e1133a43835c93a810b4bedc40e425b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2755,7 +2753,7 @@
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2026-06-16T16:07:50+00:00"
|
||||
"time": "2026-06-09T13:45:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/helpers",
|
||||
@@ -2999,16 +2997,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/socialite",
|
||||
"version": "v5.28.0",
|
||||
"version": "v5.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/socialite.git",
|
||||
"reference": "4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26"
|
||||
"reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/socialite/zipball/4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26",
|
||||
"reference": "4c131ff4b24d8881a9c8fe4eecb5ffeff9803f26",
|
||||
"url": "https://api.github.com/repos/laravel/socialite/zipball/40e0757a75637c7b2dff05d3286b0d8fc25e5c0e",
|
||||
"reference": "40e0757a75637c7b2dff05d3286b0d8fc25e5c0e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3067,7 +3065,7 @@
|
||||
"issues": "https://github.com/laravel/socialite/issues",
|
||||
"source": "https://github.com/laravel/socialite"
|
||||
},
|
||||
"time": "2026-06-12T03:24:05+00:00"
|
||||
"time": "2026-04-24T14:05:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
@@ -4505,16 +4503,16 @@
|
||||
},
|
||||
{
|
||||
"name": "mtdowling/jmespath.php",
|
||||
"version": "2.9.1",
|
||||
"version": "2.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jmespath/jmespath.php.git",
|
||||
"reference": "9c208ba27ae7d90853c288b3795d6702eb251d34"
|
||||
"reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9c208ba27ae7d90853c288b3795d6702eb251d34",
|
||||
"reference": "9c208ba27ae7d90853c288b3795d6702eb251d34",
|
||||
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc",
|
||||
"reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4523,7 +4521,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"composer/xdebug-handler": "^3.0.3",
|
||||
"phpunit/phpunit": "^8.5.52"
|
||||
"phpunit/phpunit": "^8.5.33"
|
||||
},
|
||||
"bin": [
|
||||
"bin/jp.php"
|
||||
@@ -4531,7 +4529,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.9-dev"
|
||||
"dev-master": "2.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -4565,9 +4563,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/jmespath/jmespath.php/issues",
|
||||
"source": "https://github.com/jmespath/jmespath.php/tree/2.9.1"
|
||||
"source": "https://github.com/jmespath/jmespath.php/tree/2.8.0"
|
||||
},
|
||||
"time": "2026-06-11T10:43:56+00:00"
|
||||
"time": "2024-09-04T18:46:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
@@ -4631,16 +4629,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "3.12.3",
|
||||
"version": "3.11.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CarbonPHP/carbon.git",
|
||||
"reference": "6e7853a668c3107294aff38d42bf760ec02126b6"
|
||||
"reference": "e890471a3494740f7d9326d72ce6a8c559ffee60"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6e7853a668c3107294aff38d42bf760ec02126b6",
|
||||
"reference": "6e7853a668c3107294aff38d42bf760ec02126b6",
|
||||
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/e890471a3494740f7d9326d72ce6a8c559ffee60",
|
||||
"reference": "e890471a3494740f7d9326d72ce6a8c559ffee60",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4732,7 +4730,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-14T20:41:42+00:00"
|
||||
"time": "2026-04-07T09:57:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/php-generator",
|
||||
@@ -5650,16 +5648,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.55",
|
||||
"version": "3.0.52",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "db9744e6d47e742b1f974e965ad49bdd041105af"
|
||||
"reference": "2adaefc83df2ec548558307690f376dd7d4f4fce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db9744e6d47e742b1f974e965ad49bdd041105af",
|
||||
"reference": "db9744e6d47e742b1f974e965ad49bdd041105af",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce",
|
||||
"reference": "2adaefc83df2ec548558307690f376dd7d4f4fce",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5740,7 +5738,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.55"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.52"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5756,7 +5754,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-14T23:24:10+00:00"
|
||||
"time": "2026-04-27T07:02:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpdoc-parser",
|
||||
@@ -7846,16 +7844,16 @@
|
||||
},
|
||||
{
|
||||
"name": "spatie/php-structure-discoverer",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spatie/php-structure-discoverer.git",
|
||||
"reference": "fa2b7dae8e8a22c0306154c4b052420e054f7e2b"
|
||||
"reference": "10cd4e0018450d23e2bd8f8472569ad0c445c0fc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/fa2b7dae8e8a22c0306154c4b052420e054f7e2b",
|
||||
"reference": "fa2b7dae8e8a22c0306154c4b052420e054f7e2b",
|
||||
"url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/10cd4e0018450d23e2bd8f8472569ad0c445c0fc",
|
||||
"reference": "10cd4e0018450d23e2bd8f8472569ad0c445c0fc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7913,7 +7911,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/spatie/php-structure-discoverer/issues",
|
||||
"source": "https://github.com/spatie/php-structure-discoverer/tree/2.4.4"
|
||||
"source": "https://github.com/spatie/php-structure-discoverer/tree/2.4.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7921,7 +7919,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-15T07:14:32+00:00"
|
||||
"time": "2026-04-28T06:26:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/regex",
|
||||
@@ -11308,16 +11306,16 @@
|
||||
},
|
||||
{
|
||||
"name": "ueberdosis/tiptap-php",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ueberdosis/tiptap-php.git",
|
||||
"reference": "74bfb7be1c8c6102b240f3879b7f984a6ab87b97"
|
||||
"reference": "6ea321fa665080e1a72ac5f52dfab19f6a292e2d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ueberdosis/tiptap-php/zipball/74bfb7be1c8c6102b240f3879b7f984a6ab87b97",
|
||||
"reference": "74bfb7be1c8c6102b240f3879b7f984a6ab87b97",
|
||||
"url": "https://api.github.com/repos/ueberdosis/tiptap-php/zipball/6ea321fa665080e1a72ac5f52dfab19f6a292e2d",
|
||||
"reference": "6ea321fa665080e1a72ac5f52dfab19f6a292e2d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11357,7 +11355,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ueberdosis/tiptap-php/issues",
|
||||
"source": "https://github.com/ueberdosis/tiptap-php/tree/2.1.1"
|
||||
"source": "https://github.com/ueberdosis/tiptap-php/tree/2.1.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11373,7 +11371,7 @@
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-12T18:19:46+00:00"
|
||||
"time": "2026-01-10T16:40:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
@@ -11904,29 +11902,28 @@
|
||||
},
|
||||
{
|
||||
"name": "composer/pcre",
|
||||
"version": "3.4.0",
|
||||
"version": "3.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/pcre.git",
|
||||
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed"
|
||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed",
|
||||
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed",
|
||||
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpstan/phpstan": "<2.2.2"
|
||||
"phpstan/phpstan": "<1.11.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2",
|
||||
"phpstan/phpstan-deprecation-rules": "^2",
|
||||
"phpstan/phpstan-strict-rules": "^2",
|
||||
"phpunit/phpunit": "^9"
|
||||
"phpstan/phpstan": "^1.12 || ^2",
|
||||
"phpstan/phpstan-strict-rules": "^1 || ^2",
|
||||
"phpunit/phpunit": "^8 || ^9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
@@ -11964,7 +11961,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/composer/pcre/issues",
|
||||
"source": "https://github.com/composer/pcre/tree/3.4.0"
|
||||
"source": "https://github.com/composer/pcre/tree/3.3.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11974,9 +11971,13 @@
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-07T11:47:49+00:00"
|
||||
"time": "2024-11-12T16:29:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
@@ -12563,16 +12564,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/pint",
|
||||
"version": "v1.29.3",
|
||||
"version": "v1.29.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/pint.git",
|
||||
"reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14"
|
||||
"reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/da1d1111a6aa2e082d2a388b194afe1ba0a05d14",
|
||||
"reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/0770e9b7fafd50d4586881d456d6eb41c9247a80",
|
||||
"reference": "0770e9b7fafd50d4586881d456d6eb41c9247a80",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -12583,14 +12584,14 @@
|
||||
"php": "^8.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.95.8",
|
||||
"illuminate/view": "^12.62.0",
|
||||
"larastan/larastan": "^3.10.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.95.1",
|
||||
"illuminate/view": "^12.56.0",
|
||||
"larastan/larastan": "^3.9.6",
|
||||
"laravel-zero/framework": "^12.1.0",
|
||||
"laravel/agent-detector": "^2.0.2",
|
||||
"mockery/mockery": "^1.6.12",
|
||||
"nunomaduro/termwind": "^2.4.0",
|
||||
"pestphp/pest": "^3.8.6"
|
||||
"pestphp/pest": "^3.8.6",
|
||||
"shipfastlabs/agent-detector": "^1.1.3"
|
||||
},
|
||||
"bin": [
|
||||
"builds/pint"
|
||||
@@ -12627,7 +12628,7 @@
|
||||
"issues": "https://github.com/laravel/pint/issues",
|
||||
"source": "https://github.com/laravel/pint"
|
||||
},
|
||||
"time": "2026-06-16T15:34:04+00:00"
|
||||
"time": "2026-04-20T15:26:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sail",
|
||||
@@ -12873,16 +12874,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pestphp/pest",
|
||||
"version": "v4.7.3",
|
||||
"version": "v4.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pestphp/pest.git",
|
||||
"reference": "87882a8561bf3ddf230b9a6b764f367f687d5b2f"
|
||||
"reference": "40b88b62ef8a7c6fcae5fc28f1fa747f601c131b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pestphp/pest/zipball/87882a8561bf3ddf230b9a6b764f367f687d5b2f",
|
||||
"reference": "87882a8561bf3ddf230b9a6b764f367f687d5b2f",
|
||||
"url": "https://api.github.com/repos/pestphp/pest/zipball/40b88b62ef8a7c6fcae5fc28f1fa747f601c131b",
|
||||
"reference": "40b88b62ef8a7c6fcae5fc28f1fa747f601c131b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -12895,12 +12896,12 @@
|
||||
"pestphp/pest-plugin-mutate": "^4.0.1",
|
||||
"pestphp/pest-plugin-profanity": "^4.2.1",
|
||||
"php": "^8.3.0",
|
||||
"phpunit/phpunit": "^12.5.29",
|
||||
"phpunit/phpunit": "^12.5.28",
|
||||
"symfony/process": "^7.4.13|^8.1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"filp/whoops": "<2.18.3",
|
||||
"phpunit/phpunit": ">12.5.29",
|
||||
"phpunit/phpunit": ">12.5.28",
|
||||
"sebastian/exporter": "<7.0.0",
|
||||
"webmozart/assert": "<1.11.0"
|
||||
},
|
||||
@@ -12976,7 +12977,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/pestphp/pest/issues",
|
||||
"source": "https://github.com/pestphp/pest/tree/v4.7.3"
|
||||
"source": "https://github.com/pestphp/pest/tree/v4.7.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -12988,7 +12989,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-12T05:57:27+00:00"
|
||||
"time": "2026-06-01T06:08:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pestphp/pest-plugin",
|
||||
@@ -13922,16 +13923,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "12.5.29",
|
||||
"version": "12.5.28",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "9aa66a47db3ea70f1a468e66dd969f67e594945a"
|
||||
"reference": "5895d05f5bf421ed230fbd76e1277e4b8955def4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9aa66a47db3ea70f1a468e66dd969f67e594945a",
|
||||
"reference": "9aa66a47db3ea70f1a468e66dd969f67e594945a",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5895d05f5bf421ed230fbd76e1277e4b8955def4",
|
||||
"reference": "5895d05f5bf421ed230fbd76e1277e4b8955def4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -13945,7 +13946,7 @@
|
||||
"phar-io/manifest": "^2.0.4",
|
||||
"phar-io/version": "^3.2.1",
|
||||
"php": ">=8.3",
|
||||
"phpunit/php-code-coverage": "^12.5.7",
|
||||
"phpunit/php-code-coverage": "^12.5.6",
|
||||
"phpunit/php-file-iterator": "^6.0.1",
|
||||
"phpunit/php-invoker": "^6.0.0",
|
||||
"phpunit/php-text-template": "^5.0.0",
|
||||
@@ -13955,7 +13956,7 @@
|
||||
"sebastian/diff": "^7.0.0",
|
||||
"sebastian/environment": "^8.1.2",
|
||||
"sebastian/exporter": "^7.0.3",
|
||||
"sebastian/global-state": "^8.0.3",
|
||||
"sebastian/global-state": "^8.0.2",
|
||||
"sebastian/object-enumerator": "^7.0.0",
|
||||
"sebastian/recursion-context": "^7.0.1",
|
||||
"sebastian/type": "^6.0.4",
|
||||
@@ -14000,7 +14001,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.29"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.28"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -14008,7 +14009,7 @@
|
||||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2026-06-04T06:14:42+00:00"
|
||||
"time": "2026-05-27T14:01:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
@@ -15098,5 +15099,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.3"
|
||||
},
|
||||
"plugin-api-version": "2.9.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
|
||||
11
crowdin.yml
11
crowdin.yml
@@ -6,8 +6,9 @@ preserve_hierarchy: true
|
||||
files:
|
||||
- source: "/lang/en/**/*.php"
|
||||
translation: "/lang/%two_letters_code%/**/%original_file_name%"
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
'zh-CN': 'zh_CN'
|
||||
'zh-TW': 'zh_TW'
|
||||
'pt-BR': 'pt_BR'
|
||||
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
'zh-CN': 'zh_CN'
|
||||
'zh-TW': 'zh_TW'
|
||||
'pt-BR': 'pt_BR'
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class PermissionFactory extends Factory
|
||||
{
|
||||
protected $model = Permission::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('webhook_configurations', function (Blueprint $table) {
|
||||
$table->string('scope')->default('global')->after('id');
|
||||
$table->unsignedInteger('server_id')->nullable()->after('scope');
|
||||
$table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade');
|
||||
|
||||
$table->index(['scope', 'server_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('webhook_configurations', function (Blueprint $table) {
|
||||
$table->dropForeign(['server_id']);
|
||||
$table->dropIndex(['scope', 'server_id']);
|
||||
$table->dropColumn(['scope', 'server_id']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('webhook_configurations', function (Blueprint $table) {
|
||||
$table->renameColumn('description', 'name');
|
||||
$table->text('description')->nullable()->after('name');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('webhook_configurations', function (Blueprint $table) {
|
||||
$table->dropColumn('description');
|
||||
$table->renameColumn('name', 'description');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -5,7 +5,7 @@
|
||||
if [ -f /pelican-data/.env ]; then
|
||||
echo ".env vars exist."
|
||||
# load specific env vars from .env used in the entrypoint and they are not already set
|
||||
for VAR in "APP_KEY" "APP_INSTALLED" "DB_CONNECTION" "DB_HOST" "DB_PORT" "TRUSTED_PROXIES"; do
|
||||
for VAR in "APP_KEY" "APP_INSTALLED" "DB_CONNECTION" "DB_HOST" "DB_PORT"; do
|
||||
echo "checking for ${VAR}"
|
||||
## skip if it looks like it might try to execute code
|
||||
if (grep "${VAR}" .env | grep -qE "\$\(|=\`|\$#"); then echo "var in .env may be executable or a comment, skipping"; continue; fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'heading' => 'مرحبا بك في :app!',
|
||||
'heading' => 'مرحبًا بك في Pelican!',
|
||||
'version' => 'الإصدار: :version',
|
||||
'advanced' => 'متقدم',
|
||||
'server' => 'الخادم',
|
||||
@@ -27,7 +27,7 @@ return [
|
||||
'heading' => 'لم يتم اكتشاف أي عقد',
|
||||
'content' => 'يبدو أنه لم يتم إعداد أي عقد حتى الآن، لكن لا تقلق، يمكنك النقر على زر الإجراء لإنشاء العقدة الأولى!',
|
||||
'extra_note' => 'إذا واجهت أي مشاكل، يرجى الإبلاغ عنها على GitHub.',
|
||||
'button_label' => 'إنشاء العقدة الأولى',
|
||||
'button_label' => 'إنشاء العقدة الأولى في Pelican',
|
||||
],
|
||||
'intro-support' => [
|
||||
'heading' => 'دعم Pelican',
|
||||
|
||||
@@ -20,8 +20,6 @@ return [
|
||||
'never_used' => 'Nikdy nepoužito',
|
||||
],
|
||||
'permissions' => [
|
||||
'all' => 'Nastavit všechna oprávnění',
|
||||
'all_description' => 'Rychle nastavit všechna níže uvedená oprávnění na stejnou úroveň.',
|
||||
'none' => 'Žádný',
|
||||
'read' => 'Přečteno',
|
||||
'read_write' => 'Čtení / zápis',
|
||||
|
||||
@@ -109,5 +109,6 @@ return [
|
||||
'migration' => 'Migrace nelze spustit',
|
||||
'create_user' => 'Nelze vytvořit admin uživatele',
|
||||
],
|
||||
'next_step' => 'Další krok',
|
||||
'finish' => 'Dokončit',
|
||||
];
|
||||
|
||||
@@ -11,7 +11,6 @@ return [
|
||||
'username' => 'Uživatelské jméno',
|
||||
'password' => 'Heslo',
|
||||
'remote' => 'Vzdálené',
|
||||
'unlimited' => 'Neomezeně',
|
||||
'created_at' => 'Vytvořeno v',
|
||||
'name' => 'Název databáze',
|
||||
'name_hint' => 'Ponecháním tohoto prázdného bude automaticky generováno náhodné jméno',
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Applikations API-nøgler',
|
||||
'empty' => 'Ingen API nøgler',
|
||||
'whitelist' => 'Hvidlistede IPv4-adresser',
|
||||
'whitelist_help' => 'API-nøgler kan begrænses til kun at fungere fra bestemte IPv4-adresser. Angiv hver adresse på en ny linje.',
|
||||
'whitelist_placeholder' => 'Eksempel: 127.0.0.1 eller 192.168.1.1',
|
||||
'description' => 'Beskriveslse',
|
||||
'description_help' => 'En kort beskrivelse af, hvad denne nøgle er til.',
|
||||
'nav_title' => 'API-nøgler',
|
||||
'model_label' => 'Applikations API-nøgle',
|
||||
'model_label_plural' => 'Applikations API-nøgler',
|
||||
'table' => [
|
||||
'key' => 'Nøgle',
|
||||
'description' => 'Beskriveslse',
|
||||
'last_used' => 'Sidst brugt',
|
||||
'created' => 'Oprettet',
|
||||
'created_by' => 'Oprettet af',
|
||||
'never_used' => 'Aldrig brugt',
|
||||
],
|
||||
'permissions' => [
|
||||
'all' => 'Sæt Alle Tilladelser',
|
||||
'all_description' => 'Sæt hurtigt alle tilladelser nedenfor til det samme niveau.',
|
||||
'none' => 'Ingen',
|
||||
'read' => 'Læse',
|
||||
'read_write' => 'Læse og skrive',
|
||||
],
|
||||
];
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'empty_table' => 'Yay! Ingen Fejl!',
|
||||
'total_logs' => 'Logfiler I Alt',
|
||||
'error' => 'Fejl',
|
||||
'warning' => 'Advarsel',
|
||||
'notice' => 'Bemærk',
|
||||
'info' => 'Info',
|
||||
'debug' => 'Fejlfind',
|
||||
'navigation' => [
|
||||
'panel_logs' => 'Panel Logs',
|
||||
],
|
||||
'actions' => [
|
||||
'upload_logs' => 'Overfør logfiler?',
|
||||
'upload_logs_description' => 'Dette vil uploade :file til :url Er du sikker på, at du ønsker at gøre dette?',
|
||||
'view_logs' => 'Vis Logs',
|
||||
'log_not_found' => 'Log ikke fundet!',
|
||||
'log_not_found_description' => 'Kunne ikke finde log for :filnavn',
|
||||
'failed_to_upload' => 'Overførsel mislykkedes.',
|
||||
'failed_to_upload_description' => 'HTTP Status: :status',
|
||||
'log_upload' => 'Log Uploadet!',
|
||||
'log_upload_action' => 'Vis Log',
|
||||
'upload_tooltip' => 'Upload til :url',
|
||||
],
|
||||
];
|
||||
@@ -120,9 +120,6 @@ return [
|
||||
'update' => 'Unterbenutzer-Berechtigungen für <b>:email</b> aktualisiert',
|
||||
'delete' => 'Unterbenutzer <b>:email</b> entfernt',
|
||||
],
|
||||
'mount' => [
|
||||
'update' => 'Mounts für den Server aktualisiert',
|
||||
],
|
||||
'crashed' => 'Server abgestürzt',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -20,8 +20,6 @@ return [
|
||||
'never_used' => 'Noch nicht verwendet',
|
||||
],
|
||||
'permissions' => [
|
||||
'all' => 'Alle Berechtigungen festlegen',
|
||||
'all_description' => 'Setze alle Berechtigungen unten schnell auf das gleiche Level.',
|
||||
'none' => 'Keine',
|
||||
'read' => 'Lesen',
|
||||
'read_write' => 'Lesen & Schreiben',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'heading' => 'Willkommen bei :app!',
|
||||
'heading' => 'Willkommen bei Pelican!',
|
||||
'version' => 'Version: :version',
|
||||
'advanced' => 'Erweitert',
|
||||
'server' => 'Server',
|
||||
@@ -27,7 +27,7 @@ return [
|
||||
'heading' => 'Keine Nodes gefunden',
|
||||
'content' => 'Es sieht so aus, als hätten Sie noch keine Nodes eingerichtet, aber keine Sorge, klicken Sie auf die Schaltfläche, um Ihre erste Node zu erstellen!',
|
||||
'extra_note' => 'Wenn Sie auf Probleme stoßen, melden Sie diese bitte auf GitHub.',
|
||||
'button_label' => 'Erste Node erstellen',
|
||||
'button_label' => 'Erste Node in Pelican erstellen',
|
||||
],
|
||||
'intro-support' => [
|
||||
'heading' => 'Pelican unterstützen',
|
||||
|
||||
@@ -13,28 +13,23 @@ return [
|
||||
'import' => [
|
||||
'file' => 'Datei',
|
||||
'url' => 'URL',
|
||||
'icon_url' => 'Icon URL',
|
||||
'icon_error' => 'Icon konnte nicht abgerufen werden',
|
||||
'image_url' => 'Bild URL',
|
||||
'image_error' => 'Bild konnte nicht abgerufen werden',
|
||||
'image_too_large' => 'Bild zu groß. Limit ist 1024KB',
|
||||
'egg_help' => 'Dies sollte die unveränderte .json-Datei sein ( egg-minecraft.json )',
|
||||
'url_help' => 'URLs müssen direkt auf die unveränderte .json-Datei zeigen',
|
||||
'add_url' => 'Neue URL',
|
||||
'import_failed' => 'Import fehlgeschlagen',
|
||||
'import_success' => 'Import erfolgreich',
|
||||
'import_result' => 'Es wurden :success von insgesamt :total Eggs importiert',
|
||||
'imported_eggs' => 'Importiert: :eggs',
|
||||
'failed_import_eggs' => 'Fehlgeschlagen: :eggs',
|
||||
'github' => 'Aus GitHub importieren',
|
||||
'refresh' => 'Aktualisieren',
|
||||
'import_icon' => 'Icon importieren',
|
||||
'delete_icon' => 'Icon löschen',
|
||||
'import_image' => 'Importiere Bilder',
|
||||
'no_local_ip' => 'Lokale IP-Adressen sind nicht erlaubt',
|
||||
'unsupported_format' => 'Nicht unterstütztes Format. Unterstützte Formate: :formats',
|
||||
'invalid_url' => 'Die angegebene URL ist ungültig',
|
||||
'unknown_extension' => 'Unbekannte Icon-Erweiterung (:extension)',
|
||||
'could_not_write' => 'Konnte das Icon nicht auf die Festplatte schreiben',
|
||||
'icon_deleted' => 'Icon gelöscht',
|
||||
'no_icon' => 'Kein Icon angegeben',
|
||||
'icon_updated' => 'Icon aktualisiert',
|
||||
'image_deleted' => 'Bild wurde gelöscht',
|
||||
'no_image' => 'Kein Bild angegeben',
|
||||
'image_updated' => 'Bild aktualisiert',
|
||||
],
|
||||
'export' => [
|
||||
'modal' => 'Wie möchten Sie exportieren?',
|
||||
@@ -109,18 +104,7 @@ Das Aktivieren dieser Option deaktiviert das interne Netzwerk für alle Server,
|
||||
'updated' => 'Egg aktualisiert|:count/:total Eggs aktualisiert',
|
||||
'updated_failed' => ':count fehlgeschlagen',
|
||||
'updated_skipped' => ':count übersprungen',
|
||||
'update_success' => ':egg erfolgreich aktualisiert',
|
||||
'update_failed' => 'Aktualisierung fehlgeschlagen :egg',
|
||||
'update_question' => 'Bist du dir sicher, dass du das Egg aktualisieren möchtest?|Bist du dir sicher, dass du die ausgewählten Eggs aktualisieren möchtest?',
|
||||
'update_description' => 'Wenn du Änderungen am Egg vorgenommen hast, werden diese überschieben!|Wenn du Änderungen an den Eggs vorgenommen hast, werden diese überschrieben',
|
||||
'no_updates' => 'Keine Aktualisierungen für die ausgewählten Eggs verfügbar',
|
||||
'cannot_update' => ':count Egg(s) können nicht aktualisiert werden',
|
||||
'no_update_url' => 'Die folgenden Eggs haben keine funktionierende Update-URL-Adresse: :eggs',
|
||||
'cannot_delete' => ':count Egg(s) können nicht gelöscht werden',
|
||||
'eggs_have_servers' => 'Folgende Eggs haben Server und können nicht gelöscht werden: :eggs',
|
||||
'updated_from' => 'Erfolgreich aktualisiert von: :url',
|
||||
'update_error' => 'Fehler: :error',
|
||||
'updated_eggs' => 'Aktualisiert: :eggs',
|
||||
'failed_eggs' => 'Fehlgeschlagen: :eggs',
|
||||
'skipped_eggs' => 'Übersprungen: :eggs',
|
||||
];
|
||||
|
||||
@@ -17,19 +17,14 @@ return [
|
||||
'no_mounts' => 'Keine Mounts',
|
||||
'eggs' => 'Eggs',
|
||||
'nodes' => 'Nodes',
|
||||
'user_mountable' => 'Vom Nutzer mountbar',
|
||||
'user_mountable_help' => 'Sollen Benutzer dieses Mounten für ihre Server ein- oder ausschalten können?',
|
||||
'toggles' => [
|
||||
'writable' => 'Schreibend',
|
||||
'read_only' => 'Nur lesend',
|
||||
'user_mountable' => 'Vom Benutzer einbindbar',
|
||||
'not_user_mountable' => 'Nur Admins',
|
||||
],
|
||||
'table' => [
|
||||
'name' => 'Name',
|
||||
'all_eggs' => 'Alle Eggs',
|
||||
'all_nodes' => 'Alle Nodes',
|
||||
'read_only' => 'Schreibgeschützt?',
|
||||
'user_mountable' => 'Vom Benutzer einbindbar',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -116,12 +116,8 @@ return [
|
||||
'mail_notifications' => [
|
||||
'title' => 'E-Mail Benachrichtigungen',
|
||||
'helper' => 'Stelle ein, welche E-Mail Benachrichtigung zum Benutzer geschickt werden sollen.',
|
||||
'account_created' => 'Konto erstellt',
|
||||
'added_to_server' => 'Zum Server hinzugefügt',
|
||||
'removed_from_server' => 'Vom Server entfernt',
|
||||
'server_installed' => 'Server installiert',
|
||||
'server_reinstalled' => 'Server reinstalliert',
|
||||
'backup_completed' => 'Backup abgeschlossen',
|
||||
],
|
||||
'connections' => [
|
||||
'title' => 'Verbindungen',
|
||||
@@ -139,29 +135,10 @@ return [
|
||||
],
|
||||
'api' => [
|
||||
'title' => 'API',
|
||||
'helper' => 'Legt die Ratenbegrenzung für die Anzahl der Anfragen fest, die pro konfiguriertem Zeitraum ausgeführt werden können.',
|
||||
'requests_per_period' => 'Anfragen pro Zeitraum',
|
||||
'requests' => 'Anfragen',
|
||||
'minutes' => 'Minuten',
|
||||
'period_length' => 'Dauer des Zeitraums',
|
||||
'client_title' => 'Clientseitige API Rate-Limitierung',
|
||||
'client_helper' => 'Die Rate-Limitierung gilt pro Benutzer oder pro IP-Adresse, falls kein Benutzer angemeldet ist.',
|
||||
'application_title' => 'Anwendungsseitige API Rate-Limitierung',
|
||||
'application_helper' => 'Die Rate-Limitierung gilt pro Benutzer oder pro IP-Adresse, falls kein Benutzer angemeldet ist.',
|
||||
'password_reset_title' => 'Passwort-Reset Limitierung',
|
||||
'password_reset_helper' => 'Rate-Limitierung gilt pro IP',
|
||||
'websocket_title' => 'Websocket Rate-Limitierung',
|
||||
'websocket_helper' => 'Rate-Limitierung gilt pro Server-Instanz',
|
||||
'backup_restore_title' => 'Backup und Wiederherstellung Rate-Limitierung',
|
||||
'backup_restore_helper' => 'Rate-Limitierung gilt pro Server-Instanz',
|
||||
'database_create_title' => 'Rate-Limitierung für Datenbank-Erstellung',
|
||||
'database_create_helper' => 'Rate-Limitierung gilt pro Server-Instanz',
|
||||
'subuser_create_title' => 'Rate-Limitierung der Unterbenutzer-Erstellung',
|
||||
'subuser_create_helper' => 'Rate-Limitierung gilt pro Server-Instanz',
|
||||
'file_pull_title' => 'Rate-Limit für Datei-Abrufe',
|
||||
'file_pull_helper' => 'Rate-Limitierung gilt pro Server-Instanz',
|
||||
'default_title' => 'Standard Rate-Limitierung',
|
||||
'default_helper' => 'Rate-Limitierung gilt pro Server-Instanz',
|
||||
'helper' => 'Definiert das Limit von Anfragen pro Minute, die ausgeführt werden können.',
|
||||
'client_rate' => 'Nutzer API Anfragen Limit',
|
||||
'app_rate' => 'Applikation API Anfragen Limit',
|
||||
'rpm' => 'Anfragen pro Minute',
|
||||
],
|
||||
'server' => [
|
||||
'title' => 'Servers',
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'nav_title' => 'Benutzer',
|
||||
'model_label' => 'Benutzer',
|
||||
'model_label_plural' => 'Benutzer',
|
||||
'self_delete' => 'Sie können sich nicht selbst löschen',
|
||||
'has_servers' => 'Benutzer hat Server',
|
||||
'email' => 'E-Mail',
|
||||
'username' => 'Benutzername',
|
||||
'password' => 'Passwort',
|
||||
'external_id' => 'Externe ID',
|
||||
'is_managed_externally' => 'Wird extern verwaltet?',
|
||||
'is_managed_externally_helper' => 'Wenn Ihre Benutzer durch eine externe Software (z. B. eine Abrechnungssoftware) verwaltet werden, können Sie diese Option aktivieren, um zu verhindern, dass die Benutzer ihren Benutzernamen, ihre E-Mail-Adresse und ihr Passwort über das Panel ändern können.',
|
||||
'password_help' => 'Die Angabe eines Benutzerpassworts ist optional. In der E-Mail für neue Benutzer werden diese aufgefordert, bei der ersten Anmeldung ein Passwort zu erstellen.',
|
||||
'admin_roles' => 'Admin Rollen',
|
||||
'roles' => 'Rollen',
|
||||
'no_roles' => 'Keine Rollen',
|
||||
'servers' => 'Server',
|
||||
'subusers' => 'Subuser',
|
||||
'password_reset' => 'Passwort zurücksetzen',
|
||||
'password_reset_sent' => 'E-Mail zum Zurücksetzen des Passworts gesendet',
|
||||
'password_reset_failed' => 'Fehler beim Senden der E-Mail zum Zurücksetzen des Passworts',
|
||||
];
|
||||
@@ -20,6 +20,5 @@ return [
|
||||
'password' => 'Das angegebene Passwort ist falsch.',
|
||||
'throttle' => 'Zu viele Anmeldeversuche. Bitte versuchen Sie es in :seconds Sekunden erneut.',
|
||||
'2fa_must_be_enabled' => 'Der Administrator verlangt, dass die 2-Faktor-Authentifizierung für Deinen Account aktiviert sein muss, damit Du das Panel nutzen kannst.',
|
||||
'password_login_disabled' => 'Passwort Login ist deaktiviert. Bitte verwende einen OAuth Provider.',
|
||||
|
||||
];
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Panel-Installation',
|
||||
'requirements' => [
|
||||
'title' => 'Server Anforderungen',
|
||||
'sections' => [
|
||||
'version' => [
|
||||
'title' => 'PHP Version',
|
||||
'or_newer' => ':version oder neuer',
|
||||
'content' => 'Ihre PHP Version ist :version.',
|
||||
],
|
||||
'extensions' => [
|
||||
'title' => 'PHP Erweiterungen',
|
||||
'good' => 'Alle erforderlichen PHP-Erweiterungen sind installiert.',
|
||||
'bad' => 'Die folgenden PHP-Erweiterungen fehlen: :extensions',
|
||||
],
|
||||
'permissions' => [
|
||||
'title' => 'Ordnerberechtigungen',
|
||||
'good' => 'Alle Ordner haben die richtigen Berechtigungen.',
|
||||
'bad' => 'Die folgenden Ordner haben falsche Berechtigungen: :folders',
|
||||
],
|
||||
],
|
||||
'exception' => 'Einige Anforderungen fehlen.',
|
||||
],
|
||||
'environment' => [
|
||||
'title' => 'Umgebung',
|
||||
'fields' => [
|
||||
'app_name' => 'App Name',
|
||||
'app_name_help' => 'Dies wird der Name Ihres Panels sein.',
|
||||
'app_url' => 'App URL',
|
||||
'app_url_help' => 'Dies wird die URL sein, über die Sie auf Ihr Panel zugreifen können.',
|
||||
'account' => [
|
||||
'section' => 'Administrator',
|
||||
'email' => 'E-Mail',
|
||||
'username' => 'Benutzername',
|
||||
'password' => 'Passwort',
|
||||
],
|
||||
],
|
||||
],
|
||||
'database' => [
|
||||
'title' => 'Datenbank',
|
||||
'driver' => 'Datenbank Treiber',
|
||||
'driver_help' => 'Der für die Panel-Datenbank verwendete Treiber. Wir empfehlen „SQLite“.',
|
||||
'fields' => [
|
||||
'host' => 'Datenbank Host',
|
||||
'host_help' => 'Der Host Ihrer Datenbank. Stellen Sie sicher, dass er erreichbar ist.',
|
||||
'port' => 'Datenbank-Port',
|
||||
'port_help' => 'Der Port Ihrer Datenbank.',
|
||||
'path' => 'Datenbankpfad',
|
||||
'path_help' => 'Der Pfad Ihrer .sqlite-Datei relativ zum Datenbankordner.',
|
||||
'name' => 'Datenbank Name',
|
||||
'name_help' => 'Der Name der Panel-Datenbank.',
|
||||
'username' => 'Datenbank Benutzername',
|
||||
'username_help' => 'Der Name Ihres Datenbankbenutzers.',
|
||||
'password' => 'Datenbank Passwort',
|
||||
'password_help' => 'Das Passwort Ihres Datenbankbenutzers. Kann leer sein.',
|
||||
],
|
||||
'exceptions' => [
|
||||
'connection' => 'Datenbankverbindung fehlgeschlagen',
|
||||
'migration' => 'Migrationen fehlgeschlagen',
|
||||
],
|
||||
],
|
||||
'egg' => [
|
||||
'title' => 'Eggs',
|
||||
'no_eggs' => 'Keine Eggs verfügbar.',
|
||||
'background_install_started' => 'Egg-Installation wurde gestartet.',
|
||||
'background_install_description' => 'Die Installation von :count Eggs wurde in die Warteschlange aufgenommen und wird im Hintergrund fortgesetzt.',
|
||||
'exceptions' => [
|
||||
'failed_to_update' => 'Aktualisierung des Egg-Index fehlgeschlagen.',
|
||||
'no_eggs' => 'Derzeit sind keine Eggs zur Installation verfügbar.',
|
||||
'installation_failed' => 'Die Installation der ausgewählten Eggs ist fehlgeschlagen. Bitte importieren Sie diese nach der Installation über die Egg-Liste.',
|
||||
],
|
||||
],
|
||||
'session' => [
|
||||
'title' => 'Sitzung',
|
||||
'driver' => 'Sitzungstreiber',
|
||||
'driver_help' => 'Der für die Speicherung von Sitzungen verwendete Treiber. Wir empfehlen „Dateisystem“ oder „Datenbank“.',
|
||||
],
|
||||
'cache' => [
|
||||
'title' => 'Cache',
|
||||
'driver' => 'Cache-Treiber',
|
||||
'driver_help' => 'Der für das Caching verwendete Treiber. Wir empfehlen „Filesystem“.',
|
||||
'fields' => [
|
||||
'host' => 'Redis-Host',
|
||||
'host_help' => 'Der Host Ihres Redis-Servers. Stellen Sie sicher, dass er erreichbar ist.',
|
||||
'port' => 'Redis-Port',
|
||||
'port_help' => 'Der Port Ihres Redis-Servers.',
|
||||
'username' => 'Redis Benutzername',
|
||||
'username_help' => 'Der Name Ihres Redis-Benutzers. Kann leer bleiben.',
|
||||
'password' => 'Redis-Passwort',
|
||||
'password_help' => 'Das Passwort für Ihren Redis-Benutzer. Kann leer sein.',
|
||||
],
|
||||
'exception' => 'Redis connection failed',
|
||||
],
|
||||
'queue' => [
|
||||
'title' => 'Warteschlange',
|
||||
'driver' => 'Warteschlangentreiber',
|
||||
'driver_help' => 'Der für die Verwaltung von Warteschlangen verwendete Treiber. Wir empfehlen „Datenbank“.',
|
||||
'fields' => [
|
||||
'done' => 'Ich habe beide unten aufgeführten Schritte durchgeführt.',
|
||||
'done_validation' => 'Sie müssen beide Schritte ausführen, bevor Sie fortfahren können!',
|
||||
'crontab' => 'Führen Sie den folgenden Befehl aus, um Ihre crontab einzurichten. Beachten Sie, dass <code>www-data</code> Ihr Webserver-Benutzer ist. Auf einigen Systemen kann dieser Benutzername abweichen!',
|
||||
'service' => 'Um den Queue-Worker-Dienst einzurichten, müssen Sie lediglich den folgenden Befehl ausführen.',
|
||||
],
|
||||
],
|
||||
'exceptions' => [
|
||||
'write_env' => '',
|
||||
'migration' => 'Migrationen konnten nicht ausgeführt werden',
|
||||
'create_user' => 'Admin-Benutzer konnte nicht erstellt werden',
|
||||
],
|
||||
'finish' => 'Fertigstellen',
|
||||
];
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'greeting' => 'Hallo :namen!',
|
||||
|
||||
'account_created' => [
|
||||
'body' => 'Sie erhalten diese E-Mail, weil für Sie ein Konto erstellt wurde für :app.',
|
||||
'username' => 'Benutzername: :username',
|
||||
'email' => 'E-Mail: :email',
|
||||
'action' => 'Richte dein Konto ein',
|
||||
],
|
||||
|
||||
'added_to_server' => [
|
||||
'body' => 'Sie wurden als Unterbenutzer für den folgenden Server hinzugefügt, wodurch Sie bestimmte Kontrollrechte über den Server erhalten.',
|
||||
'server_name' => 'Server Name: :name',
|
||||
'action' => 'Server ansehen',
|
||||
],
|
||||
|
||||
'removed_from_server' => [
|
||||
'body' => 'Sie wurden als Unterbenutzer für folgende Server entfernt.',
|
||||
'server_name' => 'Server Name: :name',
|
||||
'action' => 'Panel ansehen',
|
||||
],
|
||||
|
||||
'server_installed' => [
|
||||
'body' => 'Die Installation für den Server wurde abgeschlossen und ist nun bereit.',
|
||||
'server_name' => 'Server Name: :name',
|
||||
'action' => 'Anmelden und loslegen',
|
||||
],
|
||||
|
||||
'backup_completed' => [
|
||||
'body_success' => 'Das Backup wurde erfolgreich erstellt.',
|
||||
'body_failed' => 'Erstellen das Backup ist fehlgeschlagen.',
|
||||
'backup_name' => 'Backup-Name: :name',
|
||||
'server_name' => 'Server Name: :name',
|
||||
'action' => 'Backups anzeigen',
|
||||
],
|
||||
|
||||
'mail_tested' => [
|
||||
'subject' => 'Panel Test Nachricht',
|
||||
'body' => 'Das ist ein Test vom Panel E-Mail System. Sie können loslegen!',
|
||||
],
|
||||
];
|
||||
@@ -6,10 +6,6 @@ return [
|
||||
'installation_failed' => 'Serverinstallation fehlgeschlagen',
|
||||
'reinstallation_completed' => 'Server Neuinstallation abgeschlossen',
|
||||
'reinstallation_failed' => 'Server Neuinstallation fehlgeschlagen',
|
||||
'backup_completed' => 'Backup abgeschlossen',
|
||||
'backup_failed' => 'Backup fehlgeschlagen',
|
||||
'backup_body' => 'Backup ":name" für Server ":server"',
|
||||
'view_backups' => 'Backups anzeigen',
|
||||
'failed' => 'Fehlgeschlagen',
|
||||
'user_added' => [
|
||||
'title' => 'Zum Server hinzugefügt',
|
||||
|
||||
@@ -11,7 +11,6 @@ return [
|
||||
'username' => 'Benutzername',
|
||||
'password' => 'Passwort',
|
||||
'remote' => 'Fernzugriff',
|
||||
'unlimited' => 'Unbegrenzt',
|
||||
'created_at' => 'Erstellt am',
|
||||
'name' => 'Datenbank Name',
|
||||
'name_hint' => 'Leer lassen, um einen zufälligen Namen generieren zu lassen',
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Mounts',
|
||||
'description' => 'Verwalten Sie die Mounts, die Ihrem Server angehängt sind:',
|
||||
'no_mounts' => 'Für diesen Server sind keine vom Benutzer einhängbaren Mounts verfügbar.',
|
||||
'notification_updated' => 'Mounts erfolgreich aktualisiert',
|
||||
'notification_updated_body' => 'Starte deinen Server neu, um die neuen Mounts anzuwenden',
|
||||
'notification_failed' => 'Fehler beim Aktualisieren der Mounts',
|
||||
];
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'title' => 'Κλειδιά API Εφαρμογής',
|
||||
'empty' => 'Δεν υπάρχουν κλειδιά API',
|
||||
'whitelist' => 'Εγκεκριμένες Διευθύνσεις IPv4',
|
||||
'whitelist_help' => 'Τα κλειδιά API μπορούν να χρησιμοποιηθούν μόνο από συγκεκριμένες διευθύνσεις IPv4. Εισάγετε κάθε διεύθυνση σε μια νέα γραμμή.',
|
||||
'whitelist_placeholder' => 'Παράδειγμα: 127.0.0.1 ή 192.168.1.1',
|
||||
'description' => 'Περιγραφή',
|
||||
'description_help' => 'Μια σύντομη περιγραφή του τι είναι αυτό το κλειδί.',
|
||||
'nav_title' => 'Κλειδιά API',
|
||||
'model_label' => 'Κλειδί API Εφαρμογής',
|
||||
'model_label_plural' => 'Κλειδιά API Εφαρμογής',
|
||||
'table' => [
|
||||
'key' => 'Κλειδί',
|
||||
'description' => 'Περιγραφή',
|
||||
'last_used' => 'Χρησιμοποιήθηκε τελευταία',
|
||||
'created' => 'Δημιουργήθηκε',
|
||||
'created_by' => 'Δημιουργήθηκε από',
|
||||
'never_used' => 'Δεν έχει χρησιμοποιηθεί ποτέ',
|
||||
],
|
||||
'permissions' => [
|
||||
'all' => 'Ορισμός Όλων Των Δικαιωμάτων',
|
||||
'all_description' => 'Ορίστε γρήγορα όλα τα δικαιώματα κάτω από το ίδιο επίπεδο.',
|
||||
'none' => 'Κανένα',
|
||||
'read' => 'Ανάγνωση',
|
||||
'read_write' => 'Ανάγνωση & Εγγραφή',
|
||||
],
|
||||
];
|
||||
@@ -1,124 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'nav_title' => 'Eggs',
|
||||
'model_label' => 'Egg',
|
||||
'model_label_plural' => 'Eggs',
|
||||
'tabs' => [
|
||||
'configuration' => 'Διαμόρφωση',
|
||||
'process_management' => 'Διαχείριση Διεργασιών',
|
||||
'egg_variables' => 'Μεταβλητές των Egg',
|
||||
'install_script' => 'Script Εγκατάστασης',
|
||||
],
|
||||
'import' => [
|
||||
'file' => 'Αρχείο',
|
||||
'url' => 'URL Διεύθυνση',
|
||||
'icon_url' => 'URL Εικονιδίου',
|
||||
'icon_error' => 'Αδυναμία λήψης εικονιδίου',
|
||||
'egg_help' => 'Αυτό θα πρέπει να είναι το ακατέργαστο αρχείο .json/.yaml',
|
||||
'url_help' => 'Τα URL πρέπει να αναφέρονται απευθείας στο αρχείο .json/.yaml',
|
||||
'add_url' => 'Νέα διεύθυνση URL',
|
||||
'import_failed' => 'Η Εισαγωγή Απέτυχε',
|
||||
'import_success' => 'Επιτυχής Εισαγωγή',
|
||||
'import_result' => 'Εισήχθησαν :success από τα :total eggs',
|
||||
'imported_eggs' => 'Εισήχθησαν: :eggs',
|
||||
'failed_import_eggs' => 'Απέτυχε: :eggs',
|
||||
'github' => 'GitHub',
|
||||
'refresh' => 'Ανανέωση',
|
||||
'import_icon' => 'Εισαγωγή εικονιδίου',
|
||||
'delete_icon' => 'Διαγραφή εικονιδίου',
|
||||
'no_local_ip' => 'Τοπικές διευθύνσεις IP δεν επιτρέπονται',
|
||||
'unsupported_format' => 'Μη υποστηριζόμενη μορφή. Υποστηριζόμενες μορφές: :formats',
|
||||
'invalid_url' => 'Η παρεχόμενη διεύθυνση URL δεν είναι έγκυρη.',
|
||||
'unknown_extension' => 'Άγνωστη επέκταση εικονιδίου (:extension)',
|
||||
'could_not_write' => 'Αδυναμία εγγραφής εικονιδίου στο δίσκο',
|
||||
'icon_deleted' => 'Το Εικονίδιο Διαγράφηκε',
|
||||
'no_icon' => 'Δεν Παρέχεται Εικονίδιο',
|
||||
'icon_updated' => 'Το Εικονίδιο Ενημερώθηκε',
|
||||
],
|
||||
'export' => [
|
||||
'modal' => 'Πώς θα θέλατε να εξάγετε το :egg ?',
|
||||
'as' => 'Ως .:format',
|
||||
],
|
||||
'in_use' => 'Σε Χρήση',
|
||||
'servers' => 'Διακομιστές',
|
||||
'name' => 'Όνομα',
|
||||
'egg_uuid' => 'Egg UUID',
|
||||
'egg_id' => 'Egg ID',
|
||||
'name_help' => 'Ένα απλό, ευανάγνωστο όνομα για χρήση ως αναγνωριστικό για αυτό το Egg.',
|
||||
'author' => 'Δημιουργός',
|
||||
'uuid_help' => 'Αυτό είναι το μοναδικό αναγνωριστικό για αυτό το Egg, το οποίο χρησιμοποιεί το Wings ως αναγνωριστικό.',
|
||||
'author_help' => 'Ο δημιουργός αυτής της έκδοσης του Egg.',
|
||||
'author_help_edit' => 'Ο δημιουργός αυτής της έκδοσης του Egg. Η μεταφόρτωση μιας νέας ρύθμισης από διαφορετικό δημιουργό θα το αλλάξει αυτό.',
|
||||
'description' => 'Περιγραφή',
|
||||
'description_help' => 'Μια περιγραφή αυτού του Egg που θα εμφανίζεται στο Panel όπου χρειάζεται.',
|
||||
'add_startup' => 'Προσθήκη Εντολής Εκκίνησης',
|
||||
'startup_command' => 'Εντολή',
|
||||
'startup_commands' => 'Προσθήκη Εντολής Εκκίνησης',
|
||||
'startup_name' => 'Εμφανιζόμενο Όνομα',
|
||||
'startup_help' => 'Οι διαθέσιμες εντολές εκκίνησης για τους servers που χρησιμοποιούν αυτό το Egg. Η πρώτη είναι η προεπιλεγμένη',
|
||||
'file_denylist' => 'Denylist Αρχείων',
|
||||
'file_denylist_help' => 'Μια λίστα αρχείων που ο τελικός χρήστης δεν επιτρέπεται να επεξεργαστεί.',
|
||||
'features' => 'Λειτουργίες',
|
||||
'force_ip' => 'Αναγκαστική Εξερχόμενη IP',
|
||||
'force_ip_help' => 'Εξαναγκάζει όλη το network traffic να έχει τη Source IP NAT στην IP του κύριου allocation του server. Απαιτείται για να λειτουργούν σωστά ορισμένα παιχνίδια όταν ο Node έχει πολλαπλές δημόσιες IP διευθύνσεις. Η ενεργοποίηση αυτής της επιλογής θα απενεργοποιήσει το internal networking για οποιουσδήποτε servers χρησιμοποιούν αυτό το Egg, με αποτέλεσμα να μην μπορούν να έχουν εσωτερική πρόσβαση σε άλλους servers στον ίδιο node.',
|
||||
'tags' => 'Ετικέτες',
|
||||
'update_url' => 'Ενημέρωση URL',
|
||||
'update_url_help' => 'Τα URL πρέπει να αναφέρονται απευθείας στο αρχείο .json/.yaml',
|
||||
'add_image' => 'Προσθήκη Docker Image',
|
||||
'docker_images' => 'Docker Images',
|
||||
'docker_name' => 'Όνομα εικόνας',
|
||||
'docker_uri' => 'URI Εικόνας',
|
||||
'docker_help' => 'Οι διαθέσιμες εικόνες Docker για τους servers που χρησιμοποιούν αυτό το Egg. Η πρώτη είναι η προεπιλεγμένη.',
|
||||
|
||||
'stop_command' => 'Διακοπή Εντολής',
|
||||
'stop_command_help' => 'Η εντολή που πρέπει να αποστέλλεται στις διεργασίες του server για να τερματίζονται ομαλά. Αν χρειάζεται να σταλεί SIGINT, θα πρέπει να εισάγετε εδώ το ^C.',
|
||||
'copy_from' => 'Αντιγραφή Ρυθμίσεων Από',
|
||||
'copy_from_help' => 'Αν θέλετε να χρησιμοποιήσετε ως προεπιλογή ρυθμίσεις από κάποιο άλλο Egg, επιλέξτε το από το παραπάνω μενού.',
|
||||
'none' => 'Κανένα',
|
||||
'start_config' => 'Έναρξη διαμόρφωσης',
|
||||
'start_config_help' => 'Λίστα τιμών που θα πρέπει να ψάχνει το daemon κατά την εκκίνηση ενός διακομιστή για να καθορίσει την ολοκλήρωση.',
|
||||
'config_files' => 'Διαμορφώσεις',
|
||||
'config_files_help' => 'Αυτό θα πρέπει να είναι μια αναπαράσταση JSON των αρχείων ρυθμίσεων που πρέπει να τροποποιηθούν και των τμημάτων που πρέπει να αλλάξουν.',
|
||||
'log_config' => 'Log Διαμορφώσεων',
|
||||
'log_config_help' => 'Αυτό θα πρέπει να είναι μια αναπαράσταση JSON του πού αποθηκεύονται τα αρχεία καταγραφής και αν ο daemon πρέπει να δημιουργεί προσαρμοσμένα logs.',
|
||||
|
||||
'environment_variable' => 'Μεταβλητή Περιβάλλοντος',
|
||||
'default_value' => 'Προεπιλεγμένη τιμή',
|
||||
'user_permissions' => 'Δικαιώματα Χρήστη',
|
||||
'viewable' => 'Ορατό',
|
||||
'editable' => 'Επεξεργάσιμο',
|
||||
'rules' => 'Κανόνες',
|
||||
'add_new_variable' => 'Προσθήκη Νέας Μεταβλητής',
|
||||
|
||||
'error_unique' => 'Υπάρχει ήδη μια μεταβλητή με το όνομα αυτό!',
|
||||
'error_required' => 'Το πεδίο μεταβλητής περιβάλλοντος είναι υποχρεωτικό.',
|
||||
'error_reserved' => 'Αυτή η μεταβλητή περιβάλλοντος είναι δεσμευμένη και δεν μπορεί να χρησιμοποιηθεί.',
|
||||
|
||||
'script_from' => 'Script Από',
|
||||
'script_container' => 'Script Container',
|
||||
'script_entry' => 'Είσοδος Script',
|
||||
'script_install' => 'Script Εγκατάστασης',
|
||||
'no_eggs' => 'Δεν υπάρχουν Eggs',
|
||||
'no_servers' => 'Δεν υπάρχουν Διακομιστές',
|
||||
'no_servers_help' => 'Δεν έχουν ανατεθεί διακομιστές σε αυτό το Egg',
|
||||
|
||||
'update' => 'Ενημέρωση|Ενημέρωση επιλεγμένων',
|
||||
'updated' => 'Egg ενημερώθηκε|:count/:total Eggs ενημερώθηκαν',
|
||||
'updated_failed' => ':count απέτυχαν',
|
||||
'updated_skipped' => ':count παραλείφθηκαν',
|
||||
'update_success' => ':egg ενημερώθηκε με επιτυχία',
|
||||
'update_failed' => 'Αποτυχία ενημέρωσης :egg',
|
||||
'update_question' => 'Είστε βέβαιοι ότι θέλετε να ενημερώσετε αυτό το egg?|Eίστε σίγουροι ότι θέλετε να ενημερώσετε τα επιλεγμένα eggs?',
|
||||
'update_description' => 'Αν κάνατε οποιεσδήποτε αλλαγές στο egg θα αντικατασταθούν!|Αν κάνατε οποιεσδήποτε αλλαγές στα eggs θα αντικατασταθούν!',
|
||||
'no_updates' => 'Δεν υπάρχουν διαθέσιμες ενημερώσεις για τα επιλεγμένα eggs',
|
||||
'cannot_update' => 'Αδυναμία ενημέρωσης :count egg(s)',
|
||||
'no_update_url' => 'Τα παρακάτω eggs δεν έχουν ορισμένο λειτουργικό URL ενημέρωσης: :eggs',
|
||||
'cannot_delete' => 'Δεν είναι δυνατή η διαγραφή :count egg(s)',
|
||||
'eggs_have_servers' => 'Τα παρακάτω eggs έχουν servers και δεν μπορούν να διαγραφούν: :eggs',
|
||||
'updated_from' => 'Επιτυχής ενημέρωση από: :url',
|
||||
'update_error' => 'Σφάλμα: :error',
|
||||
'updated_eggs' => 'Ενημερώθηκαν :eggs',
|
||||
'failed_eggs' => 'Απέτυχαν :eggs',
|
||||
'skipped_eggs' => 'Παραλείφθηκαν: :eggs',
|
||||
];
|
||||
@@ -35,9 +35,7 @@ return [
|
||||
'linked_nodes_help' => 'This setting only defaults to this database host when adding a database to a server on the selected Node.',
|
||||
'connection_error' => 'Error connecting to database host',
|
||||
'no_database_hosts' => 'No Database Hosts',
|
||||
'no_databases' => 'No Databases',
|
||||
'no_nodes' => 'No Nodes',
|
||||
'nodes' => 'Nodes',
|
||||
'delete_help' => 'Database Host Has Databases',
|
||||
'unlimited' => 'Unlimited',
|
||||
'anywhere' => 'Anywhere',
|
||||
|
||||
@@ -99,7 +99,6 @@ return [
|
||||
'reset_help' => 'Resetting the daemon token will void any request coming from the old token. This token is used for all sensitive operations on the daemon including server creation and deletion. We suggest changing this token regularly for security.',
|
||||
|
||||
'no_nodes' => 'No Nodes',
|
||||
'no_allocations' => 'No Allocations',
|
||||
'none' => 'None',
|
||||
'cpu_chart' => 'CPU - :cpu% of :max%',
|
||||
'memory_chart' => 'Memory - :used of :total',
|
||||
@@ -123,7 +122,6 @@ return [
|
||||
'include_endpoints_hint' => 'Including endpoints will show panel urls within the logs and NOT obscure them.',
|
||||
'include_logs' => 'Include Logs',
|
||||
'include_logs_hint' => 'Including logs will show recent logs and help track down possible issues.',
|
||||
'log_lines' => 'Log Lines',
|
||||
'run_diagnostics' => 'Run Diagnostics',
|
||||
'upload_to_pelican' => 'Upload Logs',
|
||||
'logs_pulled' => 'Logs Pulled!',
|
||||
|
||||
@@ -20,7 +20,6 @@ return [
|
||||
'disable' => 'Disable',
|
||||
'import_from_file' => 'Import from File',
|
||||
'import_from_url' => 'Import from URL',
|
||||
'file' => 'File',
|
||||
'no_plugins' => 'No Plugins',
|
||||
'all' => 'All',
|
||||
'change_load_order' => 'Change load order',
|
||||
|
||||
@@ -14,36 +14,4 @@ return [
|
||||
'users' => 'Users',
|
||||
'nodes' => 'Nodes',
|
||||
'nodes_hint' => 'Leave empty to allow access to all nodes.',
|
||||
|
||||
// Permission section headings (RolePermissionModels enum + special permission groups)
|
||||
'models' => [
|
||||
'apiKey' => 'API Key',
|
||||
'allocation' => 'Allocation',
|
||||
'databaseHost' => 'Database Host',
|
||||
'database' => 'Database',
|
||||
'egg' => 'Egg',
|
||||
'mount' => 'Mount',
|
||||
'node' => 'Node',
|
||||
'role' => 'Role',
|
||||
'server' => 'Server',
|
||||
'user' => 'User',
|
||||
'webhook' => 'Webhook',
|
||||
'settings' => 'Settings',
|
||||
'health' => 'Health',
|
||||
'activityLog' => 'Activity Log',
|
||||
'panelLog' => 'Panel Log',
|
||||
'plugin' => 'Plugin',
|
||||
],
|
||||
|
||||
// Permission checkbox labels (RolePermissionPrefixes enum + model-specific/special prefixes)
|
||||
'permissions_list' => [
|
||||
'viewList' => 'View List',
|
||||
'view' => 'View',
|
||||
'create' => 'Create',
|
||||
'update' => 'Update',
|
||||
'delete' => 'Delete',
|
||||
'import' => 'Import',
|
||||
'export' => 'Export',
|
||||
'seeIps' => 'See IPs',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -5,7 +5,6 @@ return [
|
||||
'model_label' => 'Server',
|
||||
'model_label_plural' => 'Servers',
|
||||
'no_servers' => 'No Servers',
|
||||
'no_allocations' => 'No Allocations',
|
||||
'create' => 'Create Server',
|
||||
'ip_address' => 'IP Address',
|
||||
'import_icon' => 'Import Icon',
|
||||
|
||||
@@ -6,22 +6,16 @@ return [
|
||||
'model_label_plural' => 'Webhooks',
|
||||
'endpoint' => 'Endpoint',
|
||||
'description' => 'Description',
|
||||
'name' => 'Name',
|
||||
'type' => 'Type',
|
||||
'server' => 'Server',
|
||||
'information' => 'Information',
|
||||
'payload' => 'Payload',
|
||||
'events' => 'Events',
|
||||
'no_webhooks' => 'No Webhooks',
|
||||
'help' => 'Help',
|
||||
'help_text' => 'You have to wrap variable name in between {{ }} for example if you want to get the name from the api you can use {{name}}.',
|
||||
'test_now' => 'Test Now',
|
||||
'test_now_help' => 'This will fire a `created: Server` event',
|
||||
'test_now' => 'Test now',
|
||||
'table' => [
|
||||
'description' => 'Description',
|
||||
'endpoint' => 'Endpoint',
|
||||
],
|
||||
'headers' => 'Headers',
|
||||
'events' => 'Events',
|
||||
'regular' => 'Regular',
|
||||
'reset_headers' => 'Reset Headers',
|
||||
'discord' => 'Discord',
|
||||
|
||||
@@ -60,11 +60,5 @@ return [
|
||||
],
|
||||
'server' => [
|
||||
'marked_as_failed' => 'This server has not yet completed its installation process, please try again later.',
|
||||
'file_too_large' => 'The file you are attempting to open is too large to view in the file editor.',
|
||||
'state_conflict' => 'This server is currently in an unsupported state, please try again later.',
|
||||
'suspended' => 'This server is currently suspended and the functionality requested is unavailable.',
|
||||
'maintenance' => 'The node of this server is currently under maintenance and the functionality requested is unavailable.',
|
||||
'restoring_backup' => 'This server is currently restoring from a backup, please try again later.',
|
||||
'transferring' => 'This server is currently being transferred to a new machine, please try again later.',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -64,9 +64,6 @@ return [
|
||||
'sidebar' => 'Sidebar',
|
||||
'topbar' => 'Topbar',
|
||||
'mixed' => 'Mixed',
|
||||
'button_style' => 'Button Style',
|
||||
'icon' => 'Icon',
|
||||
'icon_button' => 'Icon Button',
|
||||
'redirect_to_admin' => 'Redirect to Admin on Login',
|
||||
'redirect_to_admin_help' => 'When enabled, you will be redirected to the admin area after logging in instead of the server list.',
|
||||
'no_oauth' => 'No Accounts Linked',
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'restart_now' => 'Server will restart now.',
|
||||
'close' => 'Close',
|
||||
|
||||
'eula' => [
|
||||
'heading' => 'Minecraft EULA',
|
||||
'description' => 'By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.',
|
||||
'accept' => 'I Accept',
|
||||
'accepted' => 'Minecraft EULA accepted',
|
||||
'failed' => 'Could not accept Minecraft EULA',
|
||||
],
|
||||
|
||||
'gsl_token' => [
|
||||
'heading' => 'Invalid GSL token',
|
||||
'description' => 'It seems like your Gameserver Login Token (GSL token) is invalid or has expired.',
|
||||
'submit' => 'Update GSL Token',
|
||||
'info' => 'You can either <x-filament::link href="https://steamcommunity.com/dev/managegameservers" target="_blank">generate a new one</x-filament::link> and enter it below or leave the field blank to remove it completely.',
|
||||
'updated' => 'GSL Token updated',
|
||||
'failed' => 'Could not update GSL Token',
|
||||
],
|
||||
|
||||
'java_version' => [
|
||||
'heading' => 'Unsupported Java Version',
|
||||
'description' => 'This server is currently running an unsupported version of Java and cannot be started.',
|
||||
'submit' => 'Update Docker Image',
|
||||
'select_version' => 'Please select a supported version from the list below to continue starting the server.',
|
||||
'docker_image' => 'Docker Image',
|
||||
'updated' => 'Docker image updated',
|
||||
'failed' => 'Could not update docker image',
|
||||
],
|
||||
|
||||
'pid_limit' => [
|
||||
'heading_admin' => 'Memory or process limit reached...',
|
||||
'heading_user' => 'Possible resource limit reached...',
|
||||
'description_admin' => '<p>This server has reached the maximum process or memory limit.</p><p class="mt-4">Increasing <code>container_pid_limit</code> in the wings configuration, <code>config.yml</code>, might help resolve this issue.</p><p class="mt-4"><b>Note: Wings must be restarted for the configuration file changes to take effect</b></p>',
|
||||
'description_user' => '<p>This server is attempting to use more resources than allocated. Please contact the administrator and give them the error below.</p><p class="mt-4"><code>pthread_create failed, Possibly out of memory or process/resource limits reached</code></p>',
|
||||
],
|
||||
|
||||
'steam_disk_space' => [
|
||||
'heading' => 'Out of available disk space...',
|
||||
'description_admin' => '<p>This server has run out of available disk space and cannot complete the install or update process.</p><p class="mt-4">Ensure the machine has enough disk space by typing <code class="rounded py-1 px-2">df -h</code> on the machine hosting this server. Delete files or increase the available disk space to resolve the issue.</p>',
|
||||
'description_user' => '<p>This server has run out of available disk space and cannot complete the install or update process. Please get in touch with the administrator(s) and inform them of disk space issues.</p>',
|
||||
],
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user