mirror of
https://github.com/vitodeploy/vito.git
synced 2025-04-21 10:51:36 +00:00
Compare commits
No commits in common. "1.x" and "1.9.1" have entirely different histories.
11
.github/ISSUE_TEMPLATE/config.yml
vendored
11
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,11 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Feature request
|
|
||||||
url: https://github.com/vitodeploy/vito/discussions/new?category=ideas
|
|
||||||
about: Share ideas for new features
|
|
||||||
- name: Support
|
|
||||||
url: https://github.com/vitodeploy/vito/discussions/new?category=q-a
|
|
||||||
about: Ask the community for help
|
|
||||||
- name: Discord
|
|
||||||
url: https://discord.gg/uZeeHZZnm5
|
|
||||||
about: Join the community
|
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
To request a feature or suggest an idea please add it to the feedback boards
|
||||||
|
|
||||||
|
https://vitodeploy.featurebase.app/
|
@ -34,7 +34,8 @@ ## Useful Links
|
|||||||
- [Documentation](https://vitodeploy.com)
|
- [Documentation](https://vitodeploy.com)
|
||||||
- [Install on Server](https://vitodeploy.com/introduction/installation.html#install-on-vps-recommended)
|
- [Install on Server](https://vitodeploy.com/introduction/installation.html#install-on-vps-recommended)
|
||||||
- [Install via Docker](https://vitodeploy.com/introduction/installation.html#install-via-docker)
|
- [Install via Docker](https://vitodeploy.com/introduction/installation.html#install-via-docker)
|
||||||
- [Roadmap](https://github.com/orgs/vitodeploy/projects/5)
|
- [Feedbacks](https://vitodeploy.featurebase.app)
|
||||||
|
- [Roadmap](https://vitodeploy.featurebase.app/roadmap)
|
||||||
- [Video Demo](https://youtu.be/AbmUOBDOc28)
|
- [Video Demo](https://youtu.be/AbmUOBDOc28)
|
||||||
- [Discord](https://discord.gg/uZeeHZZnm5)
|
- [Discord](https://discord.gg/uZeeHZZnm5)
|
||||||
- [Contribution](/CONTRIBUTING.md)
|
- [Contribution](/CONTRIBUTING.md)
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\Tag;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\Site;
|
|
||||||
use App\Models\Tag;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
class AttachTag
|
|
||||||
{
|
|
||||||
public function attach(User $user, array $input): Tag
|
|
||||||
{
|
|
||||||
$this->validate($input);
|
|
||||||
|
|
||||||
/** @var Server|Site $taggable */
|
|
||||||
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
|
|
||||||
|
|
||||||
$tag = Tag::query()->where('name', $input['name'])->first();
|
|
||||||
if ($tag) {
|
|
||||||
if (! $taggable->tags->contains($tag->id)) {
|
|
||||||
$taggable->tags()->attach($tag->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tag = new Tag([
|
|
||||||
'project_id' => $user->currentProject->id,
|
|
||||||
'name' => $input['name'],
|
|
||||||
'color' => config('core.tag_colors')[array_rand(config('core.tag_colors'))],
|
|
||||||
]);
|
|
||||||
$tag->save();
|
|
||||||
|
|
||||||
$taggable->tags()->attach($tag->id);
|
|
||||||
|
|
||||||
return $tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validate(array $input): void
|
|
||||||
{
|
|
||||||
Validator::make($input, [
|
|
||||||
'name' => [
|
|
||||||
'required',
|
|
||||||
],
|
|
||||||
'taggable_id' => [
|
|
||||||
'required',
|
|
||||||
'integer',
|
|
||||||
],
|
|
||||||
'taggable_type' => [
|
|
||||||
'required',
|
|
||||||
Rule::in(config('core.taggable_types')),
|
|
||||||
],
|
|
||||||
])->validate();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\Tag;
|
|
||||||
|
|
||||||
use App\Models\Tag;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
|
|
||||||
class CreateTag
|
|
||||||
{
|
|
||||||
public function create(User $user, array $input): Tag
|
|
||||||
{
|
|
||||||
$this->validate($input);
|
|
||||||
|
|
||||||
$tag = Tag::query()
|
|
||||||
->where('project_id', $user->current_project_id)
|
|
||||||
->where('name', $input['name'])
|
|
||||||
->first();
|
|
||||||
if ($tag) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'name' => ['Tag with this name already exists.'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tag = new Tag([
|
|
||||||
'project_id' => $user->currentProject->id,
|
|
||||||
'name' => $input['name'],
|
|
||||||
'color' => $input['color'],
|
|
||||||
]);
|
|
||||||
$tag->save();
|
|
||||||
|
|
||||||
return $tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validate(array $input): void
|
|
||||||
{
|
|
||||||
Validator::make($input, [
|
|
||||||
'name' => [
|
|
||||||
'required',
|
|
||||||
],
|
|
||||||
'color' => [
|
|
||||||
'required',
|
|
||||||
Rule::in(config('core.tag_colors')),
|
|
||||||
],
|
|
||||||
])->validate();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\Tag;
|
|
||||||
|
|
||||||
use App\Models\Tag;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
class DeleteTag
|
|
||||||
{
|
|
||||||
public function delete(Tag $tag): void
|
|
||||||
{
|
|
||||||
DB::table('taggables')->where('tag_id', $tag->id)->delete();
|
|
||||||
$tag->delete();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\Tag;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\Site;
|
|
||||||
use App\Models\Tag;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
class DetachTag
|
|
||||||
{
|
|
||||||
public function detach(Tag $tag, array $input): void
|
|
||||||
{
|
|
||||||
$this->validate($input);
|
|
||||||
|
|
||||||
/** @var Server|Site $taggable */
|
|
||||||
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
|
|
||||||
|
|
||||||
$taggable->tags()->detach($tag->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validate(array $input): void
|
|
||||||
{
|
|
||||||
Validator::make($input, [
|
|
||||||
'taggable_id' => [
|
|
||||||
'required',
|
|
||||||
'integer',
|
|
||||||
],
|
|
||||||
'taggable_type' => [
|
|
||||||
'required',
|
|
||||||
Rule::in(config('core.taggable_types')),
|
|
||||||
],
|
|
||||||
])->validate();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\Tag;
|
|
||||||
|
|
||||||
use App\Models\Tag;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
use Illuminate\Validation\ValidationException;
|
|
||||||
|
|
||||||
class EditTag
|
|
||||||
{
|
|
||||||
public function edit(Tag $tag, array $input): void
|
|
||||||
{
|
|
||||||
$this->validate($input);
|
|
||||||
|
|
||||||
$tag->name = $input['name'];
|
|
||||||
$tag->color = $input['color'];
|
|
||||||
|
|
||||||
$tag->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws ValidationException
|
|
||||||
*/
|
|
||||||
private function validate(array $input): void
|
|
||||||
{
|
|
||||||
$rules = [
|
|
||||||
'name' => [
|
|
||||||
'required',
|
|
||||||
],
|
|
||||||
'color' => [
|
|
||||||
'required',
|
|
||||||
Rule::in(config('core.tag_colors')),
|
|
||||||
],
|
|
||||||
];
|
|
||||||
Validator::make($input, $rules)->validate();
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,8 +9,4 @@ final class StorageProvider
|
|||||||
const FTP = 'ftp';
|
const FTP = 'ftp';
|
||||||
|
|
||||||
const LOCAL = 'local';
|
const LOCAL = 'local';
|
||||||
|
|
||||||
const S3 = 's3';
|
|
||||||
|
|
||||||
const WASABI = 'wasabi';
|
|
||||||
}
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Facades;
|
|
||||||
|
|
||||||
use App\Support\Testing\FTPFake;
|
|
||||||
use FTP\Connection;
|
|
||||||
use Illuminate\Support\Facades\Facade;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @method static bool|Connection connect(string $host, string $port, bool $ssl = false)
|
|
||||||
* @method static bool login(string $username, string $password, bool|Connection $connection)
|
|
||||||
* @method static void close(bool|Connection $connection)
|
|
||||||
* @method static bool passive(bool|Connection $connection, bool $passive)
|
|
||||||
* @method static bool delete(bool|Connection $connection, string $path)
|
|
||||||
* @method static void assertConnected(string $host)
|
|
||||||
*/
|
|
||||||
class FTP extends Facade
|
|
||||||
{
|
|
||||||
protected static function getFacadeAccessor(): string
|
|
||||||
{
|
|
||||||
return 'ftp';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function fake(): FTPFake
|
|
||||||
{
|
|
||||||
static::swap($fake = new FTPFake());
|
|
||||||
|
|
||||||
return $fake;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Helpers;
|
|
||||||
|
|
||||||
use FTP\Connection;
|
|
||||||
|
|
||||||
class FTP
|
|
||||||
{
|
|
||||||
public function connect(string $host, string $port, bool $ssl = false): bool|Connection
|
|
||||||
{
|
|
||||||
if ($ssl) {
|
|
||||||
return ftp_ssl_connect($host, $port, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ftp_connect($host, $port, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function login(string $username, string $password, bool|Connection $connection): bool
|
|
||||||
{
|
|
||||||
return ftp_login($connection, $username, $password);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close(bool|Connection $connection): void
|
|
||||||
{
|
|
||||||
ftp_close($connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function passive(bool|Connection $connection, bool $passive): bool
|
|
||||||
{
|
|
||||||
return ftp_pasv($connection, $passive);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(bool|Connection $connection, string $path): bool
|
|
||||||
{
|
|
||||||
return ftp_delete($connection, $path);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Settings;
|
|
||||||
|
|
||||||
use App\Actions\Tag\AttachTag;
|
|
||||||
use App\Actions\Tag\CreateTag;
|
|
||||||
use App\Actions\Tag\DeleteTag;
|
|
||||||
use App\Actions\Tag\DetachTag;
|
|
||||||
use App\Actions\Tag\EditTag;
|
|
||||||
use App\Facades\Toast;
|
|
||||||
use App\Helpers\HtmxResponse;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Tag;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Contracts\View\View;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class TagController extends Controller
|
|
||||||
{
|
|
||||||
public function index(Request $request): View
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'tags' => Tag::getByProjectId(auth()->user()->current_project_id)->get(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($request->has('edit')) {
|
|
||||||
$data['editTag'] = Tag::find($request->input('edit'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return view('settings.tags.index', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function create(Request $request): HtmxResponse
|
|
||||||
{
|
|
||||||
/** @var User $user */
|
|
||||||
$user = $request->user();
|
|
||||||
|
|
||||||
app(CreateTag::class)->create(
|
|
||||||
$user,
|
|
||||||
$request->input(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Toast::success('Tag created.');
|
|
||||||
|
|
||||||
return htmx()->redirect(route('settings.tags'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update(Tag $tag, Request $request): HtmxResponse
|
|
||||||
{
|
|
||||||
app(EditTag::class)->edit(
|
|
||||||
$tag,
|
|
||||||
$request->input(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Toast::success('Tag updated.');
|
|
||||||
|
|
||||||
return htmx()->redirect(route('settings.tags'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function attach(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
/** @var User $user */
|
|
||||||
$user = $request->user();
|
|
||||||
|
|
||||||
app(AttachTag::class)->attach($user, $request->input());
|
|
||||||
|
|
||||||
return back()->with([
|
|
||||||
'status' => 'tag-created',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function detach(Request $request, Tag $tag): RedirectResponse
|
|
||||||
{
|
|
||||||
app(DetachTag::class)->detach($tag, $request->input());
|
|
||||||
|
|
||||||
return back()->with([
|
|
||||||
'status' => 'tag-detached',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(Tag $tag): RedirectResponse
|
|
||||||
{
|
|
||||||
app(DeleteTag::class)->delete($tag);
|
|
||||||
|
|
||||||
Toast::success('Tag deleted.');
|
|
||||||
|
|
||||||
return back();
|
|
||||||
}
|
|
||||||
}
|
|
@ -65,9 +65,4 @@ public function sourceControls(): HasMany
|
|||||||
{
|
{
|
||||||
return $this->hasMany(SourceControl::class);
|
return $this->hasMany(SourceControl::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tags(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(Tag::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
|
||||||
use Illuminate\Filesystem\FilesystemAdapter;
|
use Illuminate\Filesystem\FilesystemAdapter;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
@ -215,11 +214,6 @@ public function sshKeys(): BelongsToMany
|
|||||||
->withTimestamps();
|
->withTimestamps();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tags(): MorphToMany
|
|
||||||
{
|
|
||||||
return $this->morphToMany(Tag::class, 'taggable');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSshUser(): string
|
public function getSshUser(): string
|
||||||
{
|
{
|
||||||
if ($this->ssh_user) {
|
if ($this->ssh_user) {
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,11 +126,6 @@ public function ssls(): HasMany
|
|||||||
return $this->hasMany(Ssl::class);
|
return $this->hasMany(Ssl::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tags(): MorphToMany
|
|
||||||
{
|
|
||||||
return $this->morphToMany(Tag::class, 'taggable');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws SourceControlIsNotConnected
|
* @throws SourceControlIsNotConnected
|
||||||
*/
|
*/
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property int $id
|
|
||||||
* @property int $project_id
|
|
||||||
* @property string $name
|
|
||||||
* @property string $color
|
|
||||||
* @property Carbon $created_at
|
|
||||||
* @property Carbon $updated_at
|
|
||||||
*/
|
|
||||||
class Tag extends Model
|
|
||||||
{
|
|
||||||
use HasFactory;
|
|
||||||
|
|
||||||
protected $fillable = [
|
|
||||||
'project_id',
|
|
||||||
'name',
|
|
||||||
'color',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $casts = [
|
|
||||||
'project_id' => 'int',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function project(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Project::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function servers(): MorphToMany
|
|
||||||
{
|
|
||||||
return $this->morphedByMany(Server::class, 'taggable');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function sites(): MorphToMany
|
|
||||||
{
|
|
||||||
return $this->morphedByMany(Site::class, 'taggable');
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getByProjectId(int $projectId): Builder
|
|
||||||
{
|
|
||||||
return self::query()
|
|
||||||
->where('project_id', $projectId)
|
|
||||||
->orWhereNull('project_id');
|
|
||||||
}
|
|
||||||
}
|
|
@ -56,7 +56,7 @@ private function checkConnection(string $subject, string $text): bool
|
|||||||
'content' => '*'.$subject.'*'."\n".$text,
|
'content' => '*'.$subject.'*'."\n".$text,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $connect->successful();
|
return $connect->ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function send(object $notifiable, NotificationInterface $notification): void
|
public function send(object $notifiable, NotificationInterface $notification): void
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Helpers\FTP;
|
|
||||||
use App\Helpers\Notifier;
|
use App\Helpers\Notifier;
|
||||||
use App\Helpers\SSH;
|
use App\Helpers\SSH;
|
||||||
use App\Helpers\Toast;
|
use App\Helpers\Toast;
|
||||||
@ -37,8 +36,5 @@ public function boot(): void
|
|||||||
$this->app->bind('toast', function () {
|
$this->app->bind('toast', function () {
|
||||||
return new Toast;
|
return new Toast;
|
||||||
});
|
});
|
||||||
$this->app->bind('ftp', function () {
|
|
||||||
return new FTP;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\SSH;
|
|
||||||
|
|
||||||
trait HasS3Storage
|
|
||||||
{
|
|
||||||
private function prepareS3Path(string $path, string $prefix = ''): string
|
|
||||||
{
|
|
||||||
$path = trim($path);
|
|
||||||
$path = ltrim($path, '/');
|
|
||||||
$path = preg_replace('/[^a-zA-Z0-9\-_\.\/]/', '_', $path);
|
|
||||||
$path = preg_replace('/\/+/', '/', $path);
|
|
||||||
|
|
||||||
if ($prefix) {
|
|
||||||
$path = trim($prefix, '/').'/'.$path;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $path;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common curl zip unzip git gcc openssl ufw
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common curl zip unzip git gcc openssl
|
||||||
git config --global user.email "__email__"
|
git config --global user.email "__email__"
|
||||||
git config --global user.name "__name__"
|
git config --global user.name "__name__"
|
||||||
|
|
||||||
# Install Node.js
|
# Install Node.js
|
||||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -;
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y
|
||||||
|
@ -117,7 +117,6 @@ public function deleteUser(string $username, string $host): void
|
|||||||
public function link(string $username, string $host, array $databases): void
|
public function link(string $username, string $host, array $databases): void
|
||||||
{
|
{
|
||||||
$ssh = $this->service->server->ssh();
|
$ssh = $this->service->server->ssh();
|
||||||
$version = $this->service->version;
|
|
||||||
|
|
||||||
foreach ($databases as $database) {
|
foreach ($databases as $database) {
|
||||||
$ssh->exec(
|
$ssh->exec(
|
||||||
@ -125,7 +124,6 @@ public function link(string $username, string $host, array $databases): void
|
|||||||
'username' => $username,
|
'username' => $username,
|
||||||
'host' => $host,
|
'host' => $host,
|
||||||
'database' => $database,
|
'database' => $database,
|
||||||
'version' => $version,
|
|
||||||
]),
|
]),
|
||||||
'link-user-to-database'
|
'link-user-to-database'
|
||||||
);
|
);
|
||||||
@ -134,13 +132,10 @@ public function link(string $username, string $host, array $databases): void
|
|||||||
|
|
||||||
public function unlink(string $username, string $host): void
|
public function unlink(string $username, string $host): void
|
||||||
{
|
{
|
||||||
$version = $this->service->version;
|
|
||||||
|
|
||||||
$this->service->server->ssh()->exec(
|
$this->service->server->ssh()->exec(
|
||||||
$this->getScript($this->getScriptsDir().'/unlink.sh', [
|
$this->getScript($this->getScriptsDir().'/unlink.sh', [
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
'host' => $host,
|
'host' => $host,
|
||||||
'version' => $version,
|
|
||||||
]),
|
]),
|
||||||
'unlink-user-from-databases'
|
'unlink-user-from-databases'
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,5 @@
|
|||||||
USER_TO_LINK='__username__'
|
if ! sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE __database__ TO __username__;"; then
|
||||||
DB_NAME='__database__'
|
|
||||||
DB_VERSION='__version__'
|
|
||||||
|
|
||||||
if ! sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE \"$DB_NAME\" TO $USER_TO_LINK;"; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
echo 'VITO_SSH_ERROR' && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if PostgreSQL version is 15 or greater
|
echo "Linking to __database__ finished"
|
||||||
if [ "$DB_VERSION" -ge 15 ]; then
|
|
||||||
if ! sudo -u postgres psql -d "$DB_NAME" -c "GRANT USAGE, CREATE ON SCHEMA public TO $USER_TO_LINK;"; then
|
|
||||||
echo 'VITO_SSH_ERROR' && exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Linking to $DB_NAME finished"
|
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
USER_TO_REVOKE='__username__'
|
USER_TO_REVOKE='__username__'
|
||||||
DB_VERSION='__version__'
|
|
||||||
|
|
||||||
DATABASES=$(sudo -u postgres psql -t -c "SELECT datname FROM pg_database WHERE datistemplate = false;")
|
DATABASES=$(sudo -u postgres psql -t -c "SELECT datname FROM pg_database WHERE datistemplate = false;")
|
||||||
|
|
||||||
for DB in $DATABASES; do
|
for DB in $DATABASES; do
|
||||||
echo "Revoking privileges in database: $DB"
|
echo "Revoking privileges in database: $DB"
|
||||||
sudo -u postgres psql -d "$DB" -c "REVOKE ALL PRIVILEGES ON DATABASE \"$DB\" FROM $USER_TO_REVOKE;"
|
sudo -u postgres psql -d "$DB" -c "REVOKE ALL PRIVILEGES ON DATABASE \"$DB\" FROM $USER_TO_REVOKE;"
|
||||||
|
|
||||||
# Check if PostgreSQL version is 15 or greater
|
|
||||||
if [ "$DB_VERSION" -ge 15 ]; then
|
|
||||||
sudo -u postgres psql -d "$DB" -c "REVOKE USAGE, CREATE ON SCHEMA public FROM $USER_TO_REVOKE;"
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Privileges revoked from $USER_TO_REVOKE"
|
echo "Privileges revoked from $USER_TO_REVOKE"
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\SSH\Storage;
|
|
||||||
|
|
||||||
use App\Exceptions\SSHCommandError;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StorageProvider;
|
|
||||||
use App\SSH\HasS3Storage;
|
|
||||||
use App\SSH\HasScripts;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class S3 extends S3AbstractStorage
|
|
||||||
{
|
|
||||||
use HasS3Storage, HasScripts;
|
|
||||||
|
|
||||||
public function __construct(Server $server, StorageProvider $storageProvider)
|
|
||||||
{
|
|
||||||
parent::__construct($server, $storageProvider);
|
|
||||||
$this->setBucketRegion($this->storageProvider->credentials['region']);
|
|
||||||
$this->setApiUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SSHCommandError
|
|
||||||
*/
|
|
||||||
public function upload(string $src, string $dest): array
|
|
||||||
{
|
|
||||||
$uploadCommand = $this->getScript('s3/upload.sh', [
|
|
||||||
'src' => $src,
|
|
||||||
'bucket' => $this->storageProvider->credentials['bucket'],
|
|
||||||
'dest' => $this->prepareS3Path($this->storageProvider->credentials['path'].'/'.$dest),
|
|
||||||
'key' => $this->storageProvider->credentials['key'],
|
|
||||||
'secret' => $this->storageProvider->credentials['secret'],
|
|
||||||
'region' => $this->getBucketRegion(),
|
|
||||||
'endpoint' => $this->getApiUrl(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$upload = $this->server->ssh()->exec($uploadCommand, 'upload-to-s3');
|
|
||||||
|
|
||||||
if (str_contains($upload, 'Error') || ! str_contains($upload, 'upload:')) {
|
|
||||||
Log::error('Failed to upload to S3', ['output' => $upload]);
|
|
||||||
throw new SSHCommandError('Failed to upload to S3: '.$upload);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'size' => null, // You can parse the size from the output if needed
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SSHCommandError
|
|
||||||
*/
|
|
||||||
public function download(string $src, string $dest): void
|
|
||||||
{
|
|
||||||
$downloadCommand = $this->getScript('s3/download.sh', [
|
|
||||||
'src' => $this->prepareS3Path($this->storageProvider->credentials['path'].'/'.$src),
|
|
||||||
'dest' => $dest,
|
|
||||||
'bucket' => $this->storageProvider->credentials['bucket'],
|
|
||||||
'key' => $this->storageProvider->credentials['key'],
|
|
||||||
'secret' => $this->storageProvider->credentials['secret'],
|
|
||||||
'region' => $this->getBucketRegion(),
|
|
||||||
'endpoint' => $this->getApiUrl(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$download = $this->server->ssh()->exec($downloadCommand, 'download-from-s3');
|
|
||||||
|
|
||||||
if (! str_contains($download, 'Download successful')) {
|
|
||||||
Log::error('Failed to download from S3', ['output' => $download]);
|
|
||||||
throw new SSHCommandError('Failed to download from S3: '.$download);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @TODO Implement delete method
|
|
||||||
*/
|
|
||||||
public function delete(string $path): void {}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\SSH\Storage;
|
|
||||||
|
|
||||||
abstract class S3AbstractStorage extends AbstractStorage
|
|
||||||
{
|
|
||||||
protected ?string $apiUrl = null;
|
|
||||||
|
|
||||||
protected ?string $bucketRegion = null;
|
|
||||||
|
|
||||||
public function getApiUrl(): string
|
|
||||||
{
|
|
||||||
return $this->apiUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setApiUrl(?string $region = null): void
|
|
||||||
{
|
|
||||||
$this->bucketRegion = $region ?? $this->bucketRegion;
|
|
||||||
$this->apiUrl = "https://s3.{$this->bucketRegion}.amazonaws.com";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getter and Setter for $bucketRegion
|
|
||||||
public function getBucketRegion(): string
|
|
||||||
{
|
|
||||||
return $this->bucketRegion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBucketRegion(string $region): void
|
|
||||||
{
|
|
||||||
$this->bucketRegion = $region;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\SSH\Storage;
|
|
||||||
|
|
||||||
use App\Exceptions\SSHCommandError;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\StorageProvider;
|
|
||||||
use App\SSH\HasS3Storage;
|
|
||||||
use App\SSH\HasScripts;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class Wasabi extends S3AbstractStorage
|
|
||||||
{
|
|
||||||
use HasS3Storage, HasScripts;
|
|
||||||
|
|
||||||
public function __construct(Server $server, StorageProvider $storageProvider)
|
|
||||||
{
|
|
||||||
parent::__construct($server, $storageProvider);
|
|
||||||
$this->setBucketRegion($this->storageProvider->credentials['region']);
|
|
||||||
$this->setApiUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SSHCommandError
|
|
||||||
*/
|
|
||||||
public function upload(string $src, string $dest): array
|
|
||||||
{
|
|
||||||
$uploadCommand = $this->getScript('wasabi/upload.sh', [
|
|
||||||
'src' => $src,
|
|
||||||
'bucket' => $this->storageProvider->credentials['bucket'],
|
|
||||||
'dest' => $this->prepareS3Path($this->storageProvider->credentials['path'].'/'.$dest),
|
|
||||||
'key' => $this->storageProvider->credentials['key'],
|
|
||||||
'secret' => $this->storageProvider->credentials['secret'],
|
|
||||||
'region' => $this->storageProvider->credentials['region'],
|
|
||||||
'endpoint' => $this->getApiUrl(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$upload = $this->server->ssh()->exec($uploadCommand, 'upload-to-wasabi');
|
|
||||||
|
|
||||||
if (str_contains($upload, 'Error') || ! str_contains($upload, 'upload:')) {
|
|
||||||
Log::error('Failed to upload to wasabi', ['output' => $upload]);
|
|
||||||
throw new SSHCommandError('Failed to upload to wasabi: '.$upload);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'size' => null, // You can parse the size from the output if needed
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws SSHCommandError
|
|
||||||
*/
|
|
||||||
public function download(string $src, string $dest): void
|
|
||||||
{
|
|
||||||
$downloadCommand = $this->getScript('wasabi/download.sh', [
|
|
||||||
'src' => $this->prepareS3Path($this->storageProvider->credentials['path'].'/'.$src),
|
|
||||||
'dest' => $dest,
|
|
||||||
'bucket' => $this->storageProvider->credentials['bucket'],
|
|
||||||
'key' => $this->storageProvider->credentials['key'],
|
|
||||||
'secret' => $this->storageProvider->credentials['secret'],
|
|
||||||
'region' => $this->storageProvider->credentials['region'],
|
|
||||||
'endpoint' => $this->getApiUrl(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$download = $this->server->ssh()->exec($downloadCommand, 'download-from-wasabi');
|
|
||||||
|
|
||||||
if (! str_contains($download, 'Download successful')) {
|
|
||||||
Log::error('Failed to download from wasabi', ['output' => $download]);
|
|
||||||
throw new SSHCommandError('Failed to download from wasabi: '.$download);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @TODO Implement delete method
|
|
||||||
*/
|
|
||||||
public function delete(string $path): void {}
|
|
||||||
|
|
||||||
public function setApiUrl(?string $region = null): void
|
|
||||||
{
|
|
||||||
$this->bucketRegion = $region ?? $this->bucketRegion;
|
|
||||||
$this->apiUrl = "https://{$this->storageProvider->credentials['bucket']}.s3.{$this->getBucketRegion()}.wasabisys.com";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Configure AWS CLI with provided credentials
|
|
||||||
/usr/local/bin/aws configure set aws_access_key_id "__key__"
|
|
||||||
/usr/local/bin/aws configure set aws_secret_access_key "__secret__"
|
|
||||||
/usr/local/bin/aws configure set default.region "__region__"
|
|
||||||
|
|
||||||
# Use the provided endpoint in the correct format
|
|
||||||
ENDPOINT="__endpoint__"
|
|
||||||
BUCKET="__bucket__"
|
|
||||||
REGION="__region__"
|
|
||||||
|
|
||||||
# Ensure that DEST does not have a trailing slash
|
|
||||||
SRC="__src__"
|
|
||||||
DEST="__dest__"
|
|
||||||
|
|
||||||
# Download the file from S3
|
|
||||||
echo "Downloading s3://__bucket__/__src__ to __dest__"
|
|
||||||
download_output=$(/usr/local/bin/aws s3 cp "s3://$BUCKET/$SRC" "$DEST" --endpoint-url="$ENDPOINT" --region "$REGION" 2>&1)
|
|
||||||
download_exit_code=$?
|
|
||||||
|
|
||||||
# Log output and exit code
|
|
||||||
echo "Download command output: $download_output"
|
|
||||||
echo "Download command exit code: $download_exit_code"
|
|
||||||
|
|
||||||
# Check if the download was successful
|
|
||||||
if [ $download_exit_code -eq 0 ]; then
|
|
||||||
echo "Download successful"
|
|
||||||
else
|
|
||||||
echo "Download failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
@ -1,59 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Check if AWS CLI is installed
|
|
||||||
if ! command -v aws &> /dev/null
|
|
||||||
then
|
|
||||||
echo "AWS CLI is not installed. Installing..."
|
|
||||||
|
|
||||||
# Detect system architecture
|
|
||||||
ARCH=$(uname -m)
|
|
||||||
if [ "$ARCH" == "x86_64" ]; then
|
|
||||||
CLI_URL="https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"
|
|
||||||
elif [ "$ARCH" == "aarch64" ]; then
|
|
||||||
CLI_URL="https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"
|
|
||||||
else
|
|
||||||
echo "Unsupported architecture: $ARCH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Download and install AWS CLI
|
|
||||||
sudo curl "$CLI_URL" -o "awscliv2.zip"
|
|
||||||
sudo unzip awscliv2.zip
|
|
||||||
sudo ./aws/install --update
|
|
||||||
sudo rm -rf awscliv2.zip aws
|
|
||||||
|
|
||||||
echo "AWS CLI installation completed."
|
|
||||||
else
|
|
||||||
echo "AWS CLI is already installed."
|
|
||||||
/usr/local/bin/aws --version
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Configure AWS CLI with provided credentials
|
|
||||||
/usr/local/bin/aws configure set aws_access_key_id "__key__"
|
|
||||||
/usr/local/bin/aws configure set aws_secret_access_key "__secret__"
|
|
||||||
|
|
||||||
# Use the provided endpoint in the correct format
|
|
||||||
ENDPOINT="__endpoint__"
|
|
||||||
BUCKET="__bucket__"
|
|
||||||
REGION="__region__"
|
|
||||||
|
|
||||||
# Ensure that DEST does not have a trailing slash
|
|
||||||
SRC="__src__"
|
|
||||||
DEST="__dest__"
|
|
||||||
|
|
||||||
# Upload the file
|
|
||||||
echo "Uploading __src__ to s3://$BUCKET/$DEST"
|
|
||||||
upload_output=$(/usr/local/bin/aws s3 cp "$SRC" "s3://$BUCKET/$DEST" --endpoint-url="$ENDPOINT" --region "$REGION" 2>&1)
|
|
||||||
upload_exit_code=$?
|
|
||||||
|
|
||||||
# Log output and exit code
|
|
||||||
echo "Upload command output: $upload_output"
|
|
||||||
echo "Upload command exit code: $upload_exit_code"
|
|
||||||
|
|
||||||
# Check if the upload was successful
|
|
||||||
if [ $upload_exit_code -eq 0 ]; then
|
|
||||||
echo "Upload successful"
|
|
||||||
else
|
|
||||||
echo "Upload failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
@ -1,31 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Configure AWS CLI with provided credentials
|
|
||||||
/usr/local/bin/aws configure set aws_access_key_id "__key__"
|
|
||||||
/usr/local/bin/aws configure set aws_secret_access_key "__secret__"
|
|
||||||
|
|
||||||
# Use the provided endpoint in the correct format
|
|
||||||
ENDPOINT="__endpoint__"
|
|
||||||
BUCKET="__bucket__"
|
|
||||||
REGION="__region__"
|
|
||||||
|
|
||||||
# Ensure that DEST does not have a trailing slash
|
|
||||||
SRC="__src__"
|
|
||||||
DEST="__dest__"
|
|
||||||
|
|
||||||
# Download the file from S3
|
|
||||||
echo "Downloading s3://__bucket____src__ to __dest__"
|
|
||||||
download_output=$(/usr/local/bin/aws s3 cp "s3://$BUCKET/$SRC" "$DEST" --endpoint-url="$ENDPOINT" --region "$REGION" 2>&1)
|
|
||||||
download_exit_code=$?
|
|
||||||
|
|
||||||
# Log output and exit code
|
|
||||||
echo "Download command output: $download_output"
|
|
||||||
echo "Download command exit code: $download_exit_code"
|
|
||||||
|
|
||||||
# Check if the download was successful
|
|
||||||
if [ $download_exit_code -eq 0 ]; then
|
|
||||||
echo "Download successful"
|
|
||||||
else
|
|
||||||
echo "Download failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
@ -1,59 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Check if AWS CLI is installed
|
|
||||||
if ! command -v aws &> /dev/null
|
|
||||||
then
|
|
||||||
echo "AWS CLI is not installed. Installing..."
|
|
||||||
|
|
||||||
# Detect system architecture
|
|
||||||
ARCH=$(uname -m)
|
|
||||||
if [ "$ARCH" == "x86_64" ]; then
|
|
||||||
CLI_URL="https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"
|
|
||||||
elif [ "$ARCH" == "aarch64" ]; then
|
|
||||||
CLI_URL="https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"
|
|
||||||
else
|
|
||||||
echo "Unsupported architecture: $ARCH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Download and install AWS CLI
|
|
||||||
sudo curl "$CLI_URL" -o "awscliv2.zip"
|
|
||||||
sudo unzip awscliv2.zip
|
|
||||||
sudo ./aws/install --update
|
|
||||||
sudo rm -rf awscliv2.zip aws
|
|
||||||
|
|
||||||
echo "AWS CLI installation completed."
|
|
||||||
else
|
|
||||||
echo "AWS CLI is already installed."
|
|
||||||
aws --version
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Configure AWS CLI with provided credentials
|
|
||||||
/usr/local/bin/aws configure set aws_access_key_id "__key__"
|
|
||||||
/usr/local/bin/aws configure set aws_secret_access_key "__secret__"
|
|
||||||
|
|
||||||
# Use the provided endpoint in the correct format
|
|
||||||
ENDPOINT="__endpoint__"
|
|
||||||
BUCKET="__bucket__"
|
|
||||||
REGION="__region__"
|
|
||||||
|
|
||||||
# Ensure that DEST does not have a trailing slash
|
|
||||||
SRC="__src__"
|
|
||||||
DEST="__dest__"
|
|
||||||
|
|
||||||
# Upload the file
|
|
||||||
echo "Uploading __src__ to s3://$BUCKET/$DEST"
|
|
||||||
upload_output=$(/usr/local/bin/aws s3 cp "$SRC" "s3://$BUCKET/$DEST" --endpoint-url="$ENDPOINT" --region "$REGION" 2>&1)
|
|
||||||
upload_exit_code=$?
|
|
||||||
|
|
||||||
# Log output and exit code
|
|
||||||
echo "Upload command output: $upload_output"
|
|
||||||
echo "Upload command exit code: $upload_exit_code"
|
|
||||||
|
|
||||||
# Check if the upload was successful
|
|
||||||
if [ $upload_exit_code -eq 0 ]; then
|
|
||||||
echo "Upload successful"
|
|
||||||
else
|
|
||||||
echo "Upload failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
@ -41,7 +41,7 @@ public function connect(): bool
|
|||||||
$isConnected = $connection && $this->login($connection);
|
$isConnected = $connection && $this->login($connection);
|
||||||
|
|
||||||
if ($isConnected) {
|
if ($isConnected) {
|
||||||
\App\Facades\FTP::close($connection);
|
ftp_close($connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $isConnected;
|
return $isConnected;
|
||||||
@ -58,36 +58,31 @@ public function delete(array $paths): void
|
|||||||
|
|
||||||
if ($connection && $this->login($connection)) {
|
if ($connection && $this->login($connection)) {
|
||||||
if ($this->storageProvider->credentials['passive']) {
|
if ($this->storageProvider->credentials['passive']) {
|
||||||
\App\Facades\FTP::passive($connection, true);
|
ftp_pasv($connection, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($paths as $path) {
|
foreach ($paths as $path) {
|
||||||
\App\Facades\FTP::delete($connection, $this->storageProvider->credentials['path'].'/'.$path);
|
ftp_delete($connection, $this->storageProvider->credentials['path'].'/'.$path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
\App\Facades\FTP::close($connection);
|
ftp_close($connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function connection(): bool|Connection
|
private function connection(): bool|Connection
|
||||||
{
|
{
|
||||||
$credentials = $this->storageProvider->credentials;
|
$credentials = $this->storageProvider->credentials;
|
||||||
|
if ($credentials['ssl']) {
|
||||||
|
return ftp_ssl_connect($credentials['host'], $credentials['port'], 5);
|
||||||
|
}
|
||||||
|
|
||||||
return \App\Facades\FTP::connect(
|
return ftp_connect($credentials['host'], $credentials['port'], 5);
|
||||||
$credentials['host'],
|
|
||||||
$credentials['port'],
|
|
||||||
$credentials['ssl']
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function login(bool|Connection $connection): bool
|
private function login(Connection $connection): bool
|
||||||
{
|
{
|
||||||
$credentials = $this->storageProvider->credentials;
|
$credentials = $this->storageProvider->credentials;
|
||||||
|
|
||||||
return \App\Facades\FTP::login(
|
return ftp_login($connection, $credentials['username'], $credentials['password']);
|
||||||
$credentials['username'],
|
|
||||||
$credentials['password'],
|
|
||||||
$connection
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\StorageProviders;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\SSH\Storage\S3 as S3Storage;
|
|
||||||
use App\SSH\Storage\Storage;
|
|
||||||
use Aws\S3\Exception\S3Exception;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class S3 extends S3AbstractStorageProvider
|
|
||||||
{
|
|
||||||
public function validationRules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'key' => 'required|string',
|
|
||||||
'secret' => 'required|string',
|
|
||||||
'region' => 'required|string',
|
|
||||||
'bucket' => 'required|string',
|
|
||||||
'path' => 'required|string',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function credentialData(array $input): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'key' => $input['key'],
|
|
||||||
'secret' => $input['secret'],
|
|
||||||
'region' => $input['region'],
|
|
||||||
'bucket' => $input['bucket'],
|
|
||||||
'path' => $input['path'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function connect(): bool
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->setBucketRegion($this->storageProvider->credentials['region']);
|
|
||||||
$this->setApiUrl();
|
|
||||||
$this->buildClientConfig();
|
|
||||||
$this->getClient()->listBuckets();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (S3Exception $e) {
|
|
||||||
Log::error('Failed to connect to S3', ['exception' => $e]);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ssh(Server $server): Storage
|
|
||||||
{
|
|
||||||
return new S3Storage($server, $this->storageProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(array $paths): void {}
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\StorageProviders;
|
|
||||||
|
|
||||||
use App\Models\StorageProvider;
|
|
||||||
use Aws\S3\S3Client;
|
|
||||||
|
|
||||||
abstract class S3AbstractStorageProvider extends AbstractStorageProvider implements S3ClientInterface, S3StorageInterface
|
|
||||||
{
|
|
||||||
protected ?string $apiUrl = null;
|
|
||||||
|
|
||||||
protected ?string $bucketRegion = null;
|
|
||||||
|
|
||||||
protected ?S3Client $client = null;
|
|
||||||
|
|
||||||
protected StorageProvider $storageProvider;
|
|
||||||
|
|
||||||
protected array $clientConfig = [];
|
|
||||||
|
|
||||||
public function getApiUrl(): string
|
|
||||||
{
|
|
||||||
return $this->apiUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setApiUrl(?string $region = null): void
|
|
||||||
{
|
|
||||||
$this->bucketRegion = $region ?? $this->bucketRegion;
|
|
||||||
$this->apiUrl = "https://s3.{$this->bucketRegion}.amazonaws.com";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBucketRegion(): string
|
|
||||||
{
|
|
||||||
return $this->bucketRegion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBucketRegion(string $region): void
|
|
||||||
{
|
|
||||||
$this->bucketRegion = $region;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getClient(): S3Client
|
|
||||||
{
|
|
||||||
return new S3Client($this->clientConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the configuration array for the S3 client.
|
|
||||||
* This method can be overridden by child classes to modify the configuration.
|
|
||||||
*/
|
|
||||||
public function buildClientConfig(): array
|
|
||||||
{
|
|
||||||
$this->clientConfig = [
|
|
||||||
'credentials' => [
|
|
||||||
'key' => $this->storageProvider->credentials['key'],
|
|
||||||
'secret' => $this->storageProvider->credentials['secret'],
|
|
||||||
],
|
|
||||||
'region' => $this->getBucketRegion(),
|
|
||||||
'version' => 'latest',
|
|
||||||
'endpoint' => $this->getApiUrl(),
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->clientConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set or update a configuration parameter for the S3 client.
|
|
||||||
*/
|
|
||||||
public function setConfigParam(array $param): void
|
|
||||||
{
|
|
||||||
foreach ($param as $key => $value) {
|
|
||||||
$this->clientConfig[$key] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\StorageProviders;
|
|
||||||
|
|
||||||
use Aws\S3\S3Client;
|
|
||||||
|
|
||||||
interface S3ClientInterface
|
|
||||||
{
|
|
||||||
public function getClient(): S3Client;
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\StorageProviders;
|
|
||||||
|
|
||||||
interface S3StorageInterface
|
|
||||||
{
|
|
||||||
public function getApiUrl(): string;
|
|
||||||
|
|
||||||
public function setApiUrl(?string $region = null): void;
|
|
||||||
|
|
||||||
public function getBucketRegion(): string;
|
|
||||||
|
|
||||||
public function setBucketRegion(string $region): void;
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\StorageProviders;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\SSH\Storage\Storage;
|
|
||||||
use App\SSH\Storage\Wasabi as WasabiStorage;
|
|
||||||
use Aws\S3\Exception\S3Exception;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class Wasabi extends S3AbstractStorageProvider
|
|
||||||
{
|
|
||||||
private const DEFAULT_REGION = 'us-east-1';
|
|
||||||
|
|
||||||
public function validationRules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'key' => 'required|string',
|
|
||||||
'secret' => 'required|string',
|
|
||||||
'region' => 'required|string',
|
|
||||||
'bucket' => 'required|string',
|
|
||||||
'path' => 'required|string',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function credentialData(array $input): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'key' => $input['key'],
|
|
||||||
'secret' => $input['secret'],
|
|
||||||
'region' => $input['region'],
|
|
||||||
'bucket' => $input['bucket'],
|
|
||||||
'path' => $input['path'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function connect(): bool
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->setBucketRegion(self::DEFAULT_REGION);
|
|
||||||
$this->setApiUrl();
|
|
||||||
$this->buildClientConfig();
|
|
||||||
$this->getClient()->listBuckets();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (S3Exception $e) {
|
|
||||||
Log::error('Failed to connect to S3', ['exception' => $e]);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the configuration array for the S3 client.
|
|
||||||
* This method can be overridden by child classes to modify the configuration.
|
|
||||||
*/
|
|
||||||
public function buildClientConfig(): array
|
|
||||||
{
|
|
||||||
$this->clientConfig = [
|
|
||||||
'credentials' => [
|
|
||||||
'key' => $this->storageProvider->credentials['key'],
|
|
||||||
'secret' => $this->storageProvider->credentials['secret'],
|
|
||||||
],
|
|
||||||
'region' => $this->getBucketRegion(),
|
|
||||||
'version' => 'latest',
|
|
||||||
'endpoint' => $this->getApiUrl(),
|
|
||||||
'use_path_style_endpoint' => true,
|
|
||||||
];
|
|
||||||
|
|
||||||
return $this->clientConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ssh(Server $server): Storage
|
|
||||||
{
|
|
||||||
return new WasabiStorage($server, $this->storageProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setApiUrl(?string $region = null): void
|
|
||||||
{
|
|
||||||
$this->bucketRegion = $region ?? $this->bucketRegion;
|
|
||||||
$this->apiUrl = "https://s3.{$this->bucketRegion}.wasabisys.com";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(array $paths): void {}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Support\Testing;
|
|
||||||
|
|
||||||
use FTP\Connection;
|
|
||||||
use PHPUnit\Framework\Assert;
|
|
||||||
|
|
||||||
class FTPFake
|
|
||||||
{
|
|
||||||
protected array $connections = [];
|
|
||||||
|
|
||||||
protected array $logins = [];
|
|
||||||
|
|
||||||
public function connect(string $host, string $port, bool $ssl = false): bool|Connection
|
|
||||||
{
|
|
||||||
$this->connections[] = compact('host', 'port', 'ssl');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function login(string $username, string $password, bool|Connection $connection): bool
|
|
||||||
{
|
|
||||||
$this->logins[] = compact('username', 'password');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function close(bool|Connection $connection): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public function passive(bool|Connection $connection, bool $passive): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(bool|Connection $connection, string $path): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
public function assertConnected(string $host): void
|
|
||||||
{
|
|
||||||
if (! $this->connections) {
|
|
||||||
Assert::fail('No connections are made');
|
|
||||||
}
|
|
||||||
$connected = false;
|
|
||||||
foreach ($this->connections as $connection) {
|
|
||||||
if ($connection['host'] === $host) {
|
|
||||||
$connected = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (! $connected) {
|
|
||||||
Assert::fail('The expected host is not connected');
|
|
||||||
}
|
|
||||||
Assert::assertTrue(true, $connected);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\View\Components;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Contracts\View\View;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\View\Component;
|
|
||||||
|
|
||||||
class Editor extends Component
|
|
||||||
{
|
|
||||||
public string $id;
|
|
||||||
|
|
||||||
public string $name;
|
|
||||||
|
|
||||||
public ?string $value;
|
|
||||||
|
|
||||||
public array $options;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
string $name,
|
|
||||||
?string $value,
|
|
||||||
public string $lang,
|
|
||||||
public bool $readonly = false,
|
|
||||||
public bool $lineNumbers = true,
|
|
||||||
) {
|
|
||||||
$this->id = $name.'-'.Str::random(8);
|
|
||||||
$this->name = $name;
|
|
||||||
$this->value = json_encode($value ?? '');
|
|
||||||
$this->options = $this->getOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getOptions(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'id' => $this->id,
|
|
||||||
'name' => $this->name,
|
|
||||||
'lang' => $this->lang,
|
|
||||||
'value' => $this->value,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render(): View|Closure|string
|
|
||||||
{
|
|
||||||
return view('components.editor');
|
|
||||||
}
|
|
||||||
}
|
|
731
composer.lock
generated
731
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -427,16 +427,11 @@
|
|||||||
\App\Enums\StorageProvider::DROPBOX,
|
\App\Enums\StorageProvider::DROPBOX,
|
||||||
\App\Enums\StorageProvider::FTP,
|
\App\Enums\StorageProvider::FTP,
|
||||||
\App\Enums\StorageProvider::LOCAL,
|
\App\Enums\StorageProvider::LOCAL,
|
||||||
\App\Enums\StorageProvider::S3,
|
|
||||||
\App\Enums\StorageProvider::WASABI,
|
|
||||||
],
|
],
|
||||||
'storage_providers_class' => [
|
'storage_providers_class' => [
|
||||||
\App\Enums\StorageProvider::DROPBOX => \App\StorageProviders\Dropbox::class,
|
\App\Enums\StorageProvider::DROPBOX => \App\StorageProviders\Dropbox::class,
|
||||||
\App\Enums\StorageProvider::FTP => \App\StorageProviders\FTP::class,
|
\App\Enums\StorageProvider::FTP => \App\StorageProviders\Ftp::class,
|
||||||
\App\Enums\StorageProvider::LOCAL => \App\StorageProviders\Local::class,
|
\App\Enums\StorageProvider::LOCAL => \App\StorageProviders\Local::class,
|
||||||
\App\Enums\StorageProvider::S3 => \App\StorageProviders\S3::class,
|
|
||||||
\App\Enums\StorageProvider::WASABI => \App\StorageProviders\Wasabi::class,
|
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'ssl_types' => [
|
'ssl_types' => [
|
||||||
@ -450,30 +445,4 @@
|
|||||||
30,
|
30,
|
||||||
90,
|
90,
|
||||||
],
|
],
|
||||||
|
|
||||||
'tag_colors' => [
|
|
||||||
'slate',
|
|
||||||
'gray',
|
|
||||||
'red',
|
|
||||||
'orange',
|
|
||||||
'amber',
|
|
||||||
'yellow',
|
|
||||||
'lime',
|
|
||||||
'green',
|
|
||||||
'emerald',
|
|
||||||
'teal',
|
|
||||||
'cyan',
|
|
||||||
'sky',
|
|
||||||
'blue',
|
|
||||||
'indigo',
|
|
||||||
'violet',
|
|
||||||
'purple',
|
|
||||||
'fuchsia',
|
|
||||||
'pink',
|
|
||||||
'rose',
|
|
||||||
],
|
|
||||||
'taggable_types' => [
|
|
||||||
\App\Models\Server::class,
|
|
||||||
\App\Models\Site::class,
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
@ -618,8 +618,8 @@
|
|||||||
'images' => [
|
'images' => [
|
||||||
'ubuntu_18' => '112929540',
|
'ubuntu_18' => '112929540',
|
||||||
'ubuntu_20' => '112929454',
|
'ubuntu_20' => '112929454',
|
||||||
'ubuntu_22' => '159651797',
|
'ubuntu_22' => '129211873',
|
||||||
'ubuntu_24' => '160232537',
|
'ubuntu_24' => '155133621',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'vultr' => [
|
'vultr' => [
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Factories;
|
|
||||||
|
|
||||||
use App\Models\Tag;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
|
|
||||||
class TagFactory extends Factory
|
|
||||||
{
|
|
||||||
protected $model = Tag::class;
|
|
||||||
|
|
||||||
public function definition(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'project_id' => 1,
|
|
||||||
'created_at' => Carbon::now(), //
|
|
||||||
'updated_at' => Carbon::now(),
|
|
||||||
'name' => $this->faker->randomElement(['production', 'staging', 'development']),
|
|
||||||
'color' => $this->faker->randomElement(config('core.tag_colors')),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('tags', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->unsignedBigInteger('project_id');
|
|
||||||
$table->string('name');
|
|
||||||
$table->string('color');
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('tags');
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('taggables', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->unsignedBigInteger('tag_id');
|
|
||||||
$table->unsignedBigInteger('taggable_id');
|
|
||||||
$table->string('taggable_type');
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('taggables');
|
|
||||||
}
|
|
||||||
};
|
|
@ -8,12 +8,9 @@
|
|||||||
use App\Models\Site;
|
use App\Models\Site;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
use Illuminate\Foundation\Testing\WithFaker;
|
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
{
|
{
|
||||||
use WithFaker;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seed the application's database.
|
* Seed the application's database.
|
||||||
*/
|
*/
|
||||||
@ -23,12 +20,6 @@ public function run(): void
|
|||||||
'name' => 'Test User',
|
'name' => 'Test User',
|
||||||
'email' => 'user@example.com',
|
'email' => 'user@example.com',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->createResources($user);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createResources(User $user): void
|
|
||||||
{
|
|
||||||
$server = Server::factory()->create([
|
$server = Server::factory()->create([
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'project_id' => $user->currentProject->id,
|
'project_id' => $user->currentProject->id,
|
||||||
|
30
package-lock.json
generated
30
package-lock.json
generated
@ -4,13 +4,13 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "vito",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"alpinejs": "^3.4.2",
|
"alpinejs": "^3.4.2",
|
||||||
"apexcharts": "^3.44.2",
|
"apexcharts": "^3.44.2",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"brace": "^0.11.1",
|
|
||||||
"flowbite": "^2.3.0",
|
"flowbite": "^2.3.0",
|
||||||
"flowbite-datepicker": "^1.2.6",
|
"flowbite-datepicker": "^1.2.6",
|
||||||
"htmx.org": "^1.9.10",
|
"htmx.org": "^1.9.10",
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||||
"tailwindcss": "^3.1.0",
|
"tailwindcss": "^3.1.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"vite": "^4.5.5"
|
"vite": "^4.5.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
@ -685,12 +685,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace": {
|
|
||||||
"version": "0.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz",
|
|
||||||
"integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
@ -1283,12 +1277,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/micromatch": {
|
"node_modules/micromatch": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.3",
|
"braces": "^3.0.2",
|
||||||
"picomatch": "^2.3.1"
|
"picomatch": "^2.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1798,9 +1792,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "3.29.5",
|
"version": "3.29.4",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
|
||||||
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
|
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
@ -2234,9 +2228,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "4.5.5",
|
"version": "4.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||||
"integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==",
|
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.18.10",
|
"esbuild": "^0.18.10",
|
||||||
|
@ -10,11 +10,8 @@
|
|||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/forms": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"alpinejs": "^3.4.2",
|
"alpinejs": "^3.4.2",
|
||||||
"apexcharts": "^3.44.2",
|
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"brace": "^0.11.1",
|
|
||||||
"flowbite": "^2.3.0",
|
"flowbite": "^2.3.0",
|
||||||
"flowbite-datepicker": "^1.2.6",
|
|
||||||
"htmx.org": "^1.9.10",
|
"htmx.org": "^1.9.10",
|
||||||
"laravel-echo": "^1.15.0",
|
"laravel-echo": "^1.15.0",
|
||||||
"laravel-vite-plugin": "^0.7.2",
|
"laravel-vite-plugin": "^0.7.2",
|
||||||
@ -24,6 +21,8 @@
|
|||||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||||
"tailwindcss": "^3.1.0",
|
"tailwindcss": "^3.1.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"vite": "^4.5.5"
|
"vite": "^4.5.3",
|
||||||
|
"apexcharts": "^3.44.2",
|
||||||
|
"flowbite-datepicker": "^1.2.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
732
public/build/assets/app-01264060.js
Normal file
732
public/build/assets/app-01264060.js
Normal file
File diff suppressed because one or more lines are too long
1
public/build/assets/app-7f487305.css
Normal file
1
public/build/assets/app-7f487305.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"resources/css/app.css": {
|
"resources/css/app.css": {
|
||||||
"file": "assets/app-888ea5fa.css",
|
"file": "assets/app-7f487305.css",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "resources/css/app.css"
|
"src": "resources/css/app.css"
|
||||||
},
|
},
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"css": [
|
"css": [
|
||||||
"assets/app-a1ae07b3.css"
|
"assets/app-a1ae07b3.css"
|
||||||
],
|
],
|
||||||
"file": "assets/app-d9a3bf01.js",
|
"file": "assets/app-01264060.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "resources/js/app.js"
|
"src": "resources/js/app.js"
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"><title>AWS Simple Storage Service (S3)</title><defs><linearGradient x1="0%" y1="100%" x2="100%" y2="0%" id="a"><stop stop-color="#1B660F" offset="0%"/><stop stop-color="#6CAE3E" offset="100%"/></linearGradient></defs><g><rect fill="url(#a)" width="256" height="256"/><path d="M194.67488,137.25632 L195.90368,128.60352 C207.23488,135.39072 207.38208,138.19392 207.378964,138.27072 C207.35968,138.28672 205.42688,139.89952 194.67488,137.25632 L194.67488,137.25632 Z M188.45728,135.52832 C168.87328,129.60192 141.59968,117.08992 130.56288,111.87392 C130.56288,111.82912 130.57568,111.78752 130.57568,111.74272 C130.57568,107.50272 127.12608,104.05312 122.88288,104.05312 C118.64608,104.05312 115.19648,107.50272 115.19648,111.74272 C115.19648,115.98272 118.64608,119.43232 122.88288,119.43232 C124.74528,119.43232 126.43488,118.73792 127.76928,117.63392 C140.75488,123.78112 167.81728,136.11072 187.54528,141.93472 L179.74368,196.99392 C179.72128,197.14432 179.71168,197.29472 179.71168,197.44512 C179.71168,202.29312 158.24928,211.19872 123.18048,211.19872 C87.74048,211.19872 66.05088,202.29312 66.05088,197.44512 C66.05088,197.29792 66.04128,197.15392 66.02208,197.00992 L49.72128,77.94752 C63.83008,87.65952 94.17568,92.79872 123.19968,92.79872 C152.17888,92.79872 182.47328,87.67872 196.61088,77.99552 L188.45728,135.52832 Z M47.99968,65.52832 C48.23008,61.31712 72.42848,44.79872 123.19968,44.79872 C173.96448,44.79872 198.16608,61.31392 198.39968,65.52832 L198.39968,66.96512 C195.61568,76.40832 164.25568,86.39872 123.19968,86.39872 C82.07328,86.39872 50.69728,76.37632 47.99968,66.92032 L47.99968,65.52832 Z M204.79968,65.59872 C204.79968,54.51072 173.01088,38.39872 123.19968,38.39872 C73.38848,38.39872 41.59968,54.51072 41.59968,65.59872 L41.90048,68.01152 L59.65408,197.68832 C60.07968,212.19072 98.75488,217.59872 123.18048,217.59872 C153.49088,217.59872 185.69248,210.62912 186.10848,197.69792 L193.77568,143.62752 C198.04128,144.64832 201.55168,145.16992 204.37088,145.16992 C208.15648,145.16992 210.71648,144.24512 212.26848,142.39552 C213.54208,140.87872 214.02848,139.04192 213.66368,137.08672 C212.83488,132.65792 207.57728,127.88352 196.87008,121.77472 L204.47328,68.13632 L204.79968,65.59872 Z" fill="#FFFFFF"/></g></svg>
|
|
Before Width: | Height: | Size: 2.3 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 51 KiB |
@ -1,40 +0,0 @@
|
|||||||
import ace from 'brace';
|
|
||||||
import 'brace/mode/javascript';
|
|
||||||
import 'brace/mode/plain_text';
|
|
||||||
import 'brace/mode/sh';
|
|
||||||
import 'brace/mode/ini';
|
|
||||||
import 'brace/ext/searchbox'
|
|
||||||
import './theme-vito'
|
|
||||||
import './mode-env';
|
|
||||||
import './mode-nginx';
|
|
||||||
|
|
||||||
window.initAceEditor = function (options = {}) {
|
|
||||||
const editorValue = JSON.parse(options.value || '');
|
|
||||||
const editor = ace.edit(options.id);
|
|
||||||
editor.setTheme("ace/theme/vito");
|
|
||||||
editor.getSession().setMode(`ace/mode/${options.lang || 'plain_text'}`);
|
|
||||||
editor.setValue(editorValue, -1);
|
|
||||||
editor.clearSelection();
|
|
||||||
editor.focus();
|
|
||||||
editor.setOptions({
|
|
||||||
enableBasicAutocompletion: true,
|
|
||||||
enableSnippets: true,
|
|
||||||
enableLiveAutocompletion: true,
|
|
||||||
printMargin: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.renderer.setScrollMargin(15, 15, 0, 0)
|
|
||||||
editor.renderer.setPadding(15);
|
|
||||||
|
|
||||||
editor.getSession().on('change', function () {
|
|
||||||
document.getElementById(`textarea-${options.id}`).value = editor.getValue();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('resize', function () {
|
|
||||||
editor.resize();
|
|
||||||
})
|
|
||||||
|
|
||||||
document.getElementById(`textarea-${options.id}`).innerHTML = editorValue;
|
|
||||||
|
|
||||||
return editor;
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
ace.define("ace/mode/env", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/mode/env_highlight_rules", "ace/mode/folding/ini","ace/mode/behaviour"], function (require, exports) {
|
|
||||||
var oop = require("../lib/oop");
|
|
||||||
var TextMode = require("./text").Mode;
|
|
||||||
var Behaviour = require("./behaviour").Behaviour;
|
|
||||||
var envHighlightRules = require("./env_highlight_rules").envHighlightRules;
|
|
||||||
|
|
||||||
var Mode = function () {
|
|
||||||
this.HighlightRules = envHighlightRules;
|
|
||||||
this.$behaviour = new Behaviour
|
|
||||||
};
|
|
||||||
|
|
||||||
oop.inherits(Mode, TextMode);
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
this.lineCommentStart = "#",
|
|
||||||
this.blockComment = null,
|
|
||||||
this.$id = "ace/mode/env"
|
|
||||||
}).call(Mode.prototype),
|
|
||||||
|
|
||||||
exports.Mode = Mode;
|
|
||||||
})
|
|
||||||
ace.define("ace/mode/env_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (require, exports, module) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
var oop = require("../lib/oop");
|
|
||||||
var TextHighlightRules =
|
|
||||||
require("./text_highlight_rules").TextHighlightRules;
|
|
||||||
|
|
||||||
var envHighlightRules = function () {
|
|
||||||
this.$rules = {
|
|
||||||
start: [
|
|
||||||
{
|
|
||||||
token: "punctuation.definition.comment.env",
|
|
||||||
regex: "#.*",
|
|
||||||
push_: [
|
|
||||||
{
|
|
||||||
token: "comment.line.number-sign.env",
|
|
||||||
regex: "$|^",
|
|
||||||
next: "pop",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
defaultToken: "comment.line.number-sign.env",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: "punctuation.definition.comment.env",
|
|
||||||
regex: "#.*",
|
|
||||||
push_: [
|
|
||||||
{
|
|
||||||
token: "comment.line.semicolon.env",
|
|
||||||
regex: "$|^",
|
|
||||||
next: "pop",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
defaultToken: "comment.line.semicolon.env",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: [
|
|
||||||
"keyword.other.definition.env",
|
|
||||||
"text",
|
|
||||||
"punctuation.separator.key-value.env",
|
|
||||||
],
|
|
||||||
regex: "\\b([a-zA-Z0-9_.-]+)\\b(\\s*)(=)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: [
|
|
||||||
"punctuation.definition.entity.env",
|
|
||||||
"constant.section.group-title.env",
|
|
||||||
"punctuation.definition.entity.env",
|
|
||||||
],
|
|
||||||
regex: "^(\\[)(.*?)(\\])",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: "punctuation.definition.string.begin.env",
|
|
||||||
regex: "'",
|
|
||||||
push: [
|
|
||||||
{
|
|
||||||
token: "punctuation.definition.string.end.env",
|
|
||||||
regex: "'",
|
|
||||||
next: "pop",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: "constant.language.escape",
|
|
||||||
regex: "\\\\(?:[\\\\0abtrn;#=:]|x[a-fA-F\\d]{4})",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
defaultToken: "string.quoted.single.env",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: "punctuation.definition.string.begin.env",
|
|
||||||
regex: '"',
|
|
||||||
push: [
|
|
||||||
{
|
|
||||||
token: "constant.language.escape",
|
|
||||||
regex: "\\\\(?:[\\\\0abtrn;#=:]|x[a-fA-F\\d]{4})",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: "support.constant.color",
|
|
||||||
regex: /\${[\w]+}/,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: "punctuation.definition.string.end.env",
|
|
||||||
regex: '"',
|
|
||||||
next: "pop",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
defaultToken: "string.quoted.double.env",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
token: "constant.language.boolean",
|
|
||||||
regex: /(?:true|false)\b/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
this.normalizeRules();
|
|
||||||
};
|
|
||||||
|
|
||||||
envHighlightRules.metaData = {
|
|
||||||
fileTypes: ["env"],
|
|
||||||
keyEquivalent: "^~I",
|
|
||||||
name: "Env",
|
|
||||||
scopeName: "source.env",
|
|
||||||
};
|
|
||||||
|
|
||||||
oop.inherits(envHighlightRules, TextHighlightRules);
|
|
||||||
|
|
||||||
exports.envHighlightRules = envHighlightRules;
|
|
||||||
});
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
@ -1,47 +0,0 @@
|
|||||||
ace.define(
|
|
||||||
"ace/theme/vito",
|
|
||||||
["require", "exports", "module", "ace/lib/dom"],
|
|
||||||
function (require, exports) {
|
|
||||||
(exports.isDark = true),
|
|
||||||
(exports.cssClass = "ace-vito rounded-lg w-full"),
|
|
||||||
(exports.cssText = `
|
|
||||||
.ace-vito .ace_scrollbar::-webkit-scrollbar { width: 12px;}
|
|
||||||
.ace-vito .ace_scrollbar::-webkit-scrollbar-track { background: #111827;}
|
|
||||||
.ace-vito .ace_scrollbar::-webkit-scrollbar-thumb { background: #374151; border-radius: 4px;}
|
|
||||||
.ace-vito .ace_gutter {background: #151c27;color: rgb(128,145,160)}
|
|
||||||
.ace-vito .ace_print-margin {width: 1px;background: #555555}
|
|
||||||
.ace-vito {background-color: #0f172a;color: #F9FAFB}
|
|
||||||
.ace-vito .ace_cursor {color: #F9FAFB}
|
|
||||||
.ace-vito .ace_marker-layer .ace_selection {background: rgba(179, 101, 57, 0.75)}
|
|
||||||
.ace-vito.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #002240;}
|
|
||||||
.ace-vito .ace_marker-layer .ace_step {background: rgb(127, 111, 19)}
|
|
||||||
.ace-vito .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgba(255, 255, 255, 0.15)}
|
|
||||||
.ace-vito .ace_marker-layer .ace_active-line {background: rgba(24, 182, 155, 0.10)}
|
|
||||||
.ace-vito .ace_gutter-active-line {background-color: rgba(0, 0, 0, 0.35)}
|
|
||||||
.ace-vito .ace_marker-layer .ace_selected-word {border: 1px solid rgba(179, 101, 57, 0.75)}
|
|
||||||
.ace-vito .ace_invisible {color: rgba(255, 255, 255, 0.15)}
|
|
||||||
.ace-vito .ace_keyword,.ace-vito .ace_meta {color: #FF9D00}
|
|
||||||
.ace-vito .ace_constant,.ace-vito .ace_constant.ace_character,.ace-vito .ace_constant.ace_character.ace_escape,.ace-vito .ace_constant.ace_other {color: #FF628C}
|
|
||||||
.ace-vito .ace_invalid {color: #F8F8F8;background-color: #800F00}
|
|
||||||
.ace-vito .ace_support {color: #80FFBB}
|
|
||||||
.ace-vito .ace_support.ace_constant {color: #EB939A}
|
|
||||||
.ace-vito .ace_fold {background-color: #FF9D00;border-color: #F9FAFB}
|
|
||||||
.ace-vito .ace_support.ace_function {color: #FFB054}
|
|
||||||
.ace-vito .ace_storage {color: #FFEE80}
|
|
||||||
.ace-vito .ace_entity {color: #FFDD00}
|
|
||||||
.ace-vito .ace_string {color: #7cd827}
|
|
||||||
.ace-vito .ace_string.ace_regexp {color: #80FFC2}
|
|
||||||
.ace-vito .ace_comment {font-style: italic;color: #6B7280}
|
|
||||||
.ace-vito .ace_heading,.ace-vito
|
|
||||||
.ace_markup.ace_heading {color: #C8E4FD;background-color: #001221}
|
|
||||||
.ace-vito .ace_list,.ace-vito .ace_markup.ace_list {background-color: #130D26}
|
|
||||||
.ace-vito .ace_variable {color: #CCCCCC}
|
|
||||||
.ace-vito .ace_variable.ace_language {color: #FF80E1}
|
|
||||||
.ace-vito .ace_meta.ace_tag {color: #9EFFFF}
|
|
||||||
.ace-vito .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNgYGBgYHCLSvkPAAP3AgSDTRd4AAAAAElFTkSuQmCC) right repeat-y}
|
|
||||||
`);
|
|
||||||
|
|
||||||
var dom = require("../lib/dom");
|
|
||||||
dom.importCssString(exports.cssText, exports.cssClass);
|
|
||||||
},
|
|
||||||
);
|
|
@ -1,6 +1,5 @@
|
|||||||
import 'flowbite';
|
import 'flowbite';
|
||||||
import 'flowbite/dist/datepicker.js';
|
import 'flowbite/dist/datepicker.js';
|
||||||
import './ace-editor/ace-editor';
|
|
||||||
|
|
||||||
import Alpine from 'alpinejs';
|
import Alpine from 'alpinejs';
|
||||||
window.Alpine = Alpine;
|
window.Alpine = Alpine;
|
||||||
|
@ -16,8 +16,9 @@ class="p-6"
|
|||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<x-input-label for="script" :value="__('Script')" />
|
<x-input-label for="script" :value="__('Script')" />
|
||||||
@php($value = old("script", $site->deploymentScript?->content))
|
<x-textarea id="script" name="script" class="mt-1 min-h-[400px] w-full font-mono">
|
||||||
<x-editor id="script" name="script" lang="sh" :value="$value" />
|
{{ old("script", $site->deploymentScript?->content) }}
|
||||||
|
</x-textarea>
|
||||||
@error("script")
|
@error("script")
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
@enderror
|
@enderror
|
||||||
|
@ -21,8 +21,9 @@ class="mt-6"
|
|||||||
>
|
>
|
||||||
<x-input-label for="env" :value="__('.env')" />
|
<x-input-label for="env" :value="__('.env')" />
|
||||||
<div id="env-content">
|
<div id="env-content">
|
||||||
@php($envValue = old("env", session()->get("env") ?? "Loading..."))
|
<x-textarea id="env" name="env" rows="10" class="mt-1 block min-h-[400px] w-full font-mono">
|
||||||
<x-editor id="env" name="env" lang="env" :value="$envValue" />
|
{{ old("env", session()->get("env") ?? "Loading...") }}
|
||||||
|
</x-textarea>
|
||||||
</div>
|
</div>
|
||||||
@error("env")
|
@error("env")
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
@props([
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"placeholder" => "Search...",
|
|
||||||
"items" => [],
|
|
||||||
"maxResults" => 5,
|
|
||||||
"value" => "",
|
|
||||||
])
|
|
||||||
|
|
||||||
<script>
|
|
||||||
window['items_' + @js($id)] = @json($items);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
x-data="{
|
|
||||||
q: @js($value),
|
|
||||||
items: window['items_' + @js($id)],
|
|
||||||
resultItems: window['items_' + @js($id)],
|
|
||||||
maxResults: @js($maxResults),
|
|
||||||
init() {
|
|
||||||
this.search()
|
|
||||||
},
|
|
||||||
search() {
|
|
||||||
if (! this.q) {
|
|
||||||
this.resultItems = this.items.slice(0, this.maxResults)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.resultItems = this.items
|
|
||||||
.filter((item) => item.toLowerCase().includes(this.q.toLowerCase()))
|
|
||||||
.slice(0, this.maxResults)
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<input type="hidden" name="{{ $name }}" x-ref="input" x-model="q" />
|
|
||||||
<x-dropdown width="full" :hide-if-empty="true">
|
|
||||||
<x-slot name="trigger">
|
|
||||||
<x-text-input
|
|
||||||
id="$id . '-q"
|
|
||||||
x-model="q"
|
|
||||||
type="text"
|
|
||||||
class="mt-1 w-full"
|
|
||||||
:placeholder="$placeholder"
|
|
||||||
autocomplete="off"
|
|
||||||
x-on:input.debounce.100ms="search"
|
|
||||||
/>
|
|
||||||
</x-slot>
|
|
||||||
<x-slot name="content">
|
|
||||||
<div
|
|
||||||
id="{{ $id }}-items-list"
|
|
||||||
x-bind:class="
|
|
||||||
resultItems.length > 0
|
|
||||||
? 'py-1 border border-gray-200 dark:border-gray-600 rounded-md'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template x-for="item in resultItems">
|
|
||||||
<x-dropdown-link class="cursor-pointer" x-on:click="q = item">
|
|
||||||
<span x-text="item"></span>
|
|
||||||
</x-dropdown-link>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</x-slot>
|
|
||||||
</x-dropdown>
|
|
||||||
</div>
|
|
@ -1,10 +0,0 @@
|
|||||||
<div>
|
|
||||||
<div
|
|
||||||
class="block w-full cursor-pointer rounded-md border border-gray-300 p-2.5 text-sm focus:border-primary-500 focus:ring-primary-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-primary-600 dark:focus:ring-primary-600"
|
|
||||||
>
|
|
||||||
{{ $slot }}
|
|
||||||
</div>
|
|
||||||
<button type="button" class="absolute inset-y-0 right-0 flex items-center pr-2">
|
|
||||||
<x-heroicon name="o-chevron-down" class="h-4 w-4 text-gray-400" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
@ -2,18 +2,12 @@
|
|||||||
"open" => false,
|
"open" => false,
|
||||||
"align" => "right",
|
"align" => "right",
|
||||||
"width" => "48",
|
"width" => "48",
|
||||||
"contentClasses" => "list-none divide-y divide-gray-100 rounded-md bg-white text-base dark:divide-gray-600 dark:bg-gray-700",
|
"contentClasses" => "list-none divide-y divide-gray-100 rounded-md border border-gray-200 bg-white py-1 text-base dark:divide-gray-600 dark:border-gray-600 dark:bg-gray-700",
|
||||||
"search" => false,
|
"search" => false,
|
||||||
"searchUrl" => "",
|
"searchUrl" => "",
|
||||||
"hideIfEmpty" => false,
|
|
||||||
"closeOnClick" => true,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
@php
|
@php
|
||||||
if (! $hideIfEmpty) {
|
|
||||||
$contentClasses .= " py-1 border border-gray-200 dark:border-gray-600";
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($align) {
|
switch ($align) {
|
||||||
case "left":
|
case "left":
|
||||||
$alignmentClasses = "left-0 origin-top-left";
|
$alignmentClasses = "left-0 origin-top-left";
|
||||||
@ -31,9 +25,6 @@
|
|||||||
case "48":
|
case "48":
|
||||||
$width = "w-48";
|
$width = "w-48";
|
||||||
break;
|
break;
|
||||||
case "56":
|
|
||||||
$width = "w-56";
|
|
||||||
break;
|
|
||||||
case "full":
|
case "full":
|
||||||
$width = "w-full";
|
$width = "w-full";
|
||||||
break;
|
break;
|
||||||
@ -55,9 +46,31 @@
|
|||||||
x-transition:leave-end="scale-95 transform opacity-0"
|
x-transition:leave-end="scale-95 transform opacity-0"
|
||||||
class="{{ $width }} {{ $alignmentClasses }} absolute z-50 mt-2 rounded-md"
|
class="{{ $width }} {{ $alignmentClasses }} absolute z-50 mt-2 rounded-md"
|
||||||
style="display: none"
|
style="display: none"
|
||||||
@if ($closeOnClick) @click="open = false" @endif
|
@click="open = false"
|
||||||
>
|
>
|
||||||
<div class="{{ $contentClasses }} max-h-80 overflow-y-auto rounded-md">
|
<div class="{{ $contentClasses }} rounded-md">
|
||||||
|
@if ($search)
|
||||||
|
<div class="p-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-ref="search"
|
||||||
|
x-model="search"
|
||||||
|
x-on:keydown.window.prevent.enter="open = false"
|
||||||
|
x-on:keydown.window.prevent.escape="open = false"
|
||||||
|
x-on:keydown.window.prevent.arrow-up="
|
||||||
|
open = true
|
||||||
|
$refs.search.focus()
|
||||||
|
"
|
||||||
|
x-on:keydown.window.prevent.arrow-down="
|
||||||
|
open = true
|
||||||
|
$refs.search.focus()
|
||||||
|
"
|
||||||
|
class="w-full rounded-md border border-gray-200 p-2"
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
{{ $content }}
|
{{ $content }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
<div>
|
|
||||||
<div
|
|
||||||
id="{{ $id }}"
|
|
||||||
{{ $attributes->merge(["class" => "mt-1 min-h-[400px] w-full"]) }}
|
|
||||||
class="ace-vito ace_dark"
|
|
||||||
></div>
|
|
||||||
<textarea id="textarea-{{ $id }}" name="{{ $name }}" style="display: none"></textarea>
|
|
||||||
<script>
|
|
||||||
if (window.initAceEditor) {
|
|
||||||
window.initAceEditor(@json($options));
|
|
||||||
} else {
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
window.initAceEditor(@json($options));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</div>
|
|
@ -1,14 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
{{ $attributes }}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 556 B |
@ -1,10 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
{{ $attributes }}
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 251 B |
@ -1,15 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
{{ $attributes }}
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z"
|
|
||||||
/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 546 B |
@ -73,7 +73,7 @@ class="fixed inset-0 transform transition-all"
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
x-show="show"
|
x-show="show"
|
||||||
class="{{ $maxWidth }} mb-6 transform overflow-visible rounded-lg bg-white shadow-xl transition-all dark:bg-gray-800 sm:mx-auto sm:w-full"
|
class="{{ $maxWidth }} mb-6 transform overflow-hidden rounded-lg bg-white shadow-xl transition-all dark:bg-gray-800 sm:mx-auto sm:w-full"
|
||||||
x-transition:enter="duration-300 ease-out"
|
x-transition:enter="duration-300 ease-out"
|
||||||
x-transition:enter-start="translate-y-4 opacity-0 sm:translate-y-0 sm:scale-95"
|
x-transition:enter-start="translate-y-4 opacity-0 sm:translate-y-0 sm:scale-95"
|
||||||
x-transition:enter-end="translate-y-0 opacity-100 sm:scale-100"
|
x-transition:enter-end="translate-y-0 opacity-100 sm:scale-100"
|
||||||
|
@ -71,6 +71,5 @@
|
|||||||
</script>
|
</script>
|
||||||
<x-toast />
|
<x-toast />
|
||||||
<x-htmx-error-handler />
|
<x-htmx-error-handler />
|
||||||
@stack("footer")
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -11,12 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@else
|
@else
|
||||||
<div class="flex items-center">
|
<h2 class="text-lg font-semibold">{{ $server->name }}</h2>
|
||||||
<h2 class="text-lg font-semibold">{{ $server->name }}</h2>
|
|
||||||
<div class="ml-2">
|
|
||||||
@include("settings.tags.tags", ["taggable" => $server])
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="flex flex-col items-end">
|
<div class="flex flex-col items-end">
|
||||||
|
@ -161,13 +161,6 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
|||||||
<x-hr />
|
<x-hr />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<li>
|
|
||||||
<x-sidebar-link :href="route('servers')" :active="request()->routeIs('servers')">
|
|
||||||
<x-heroicon name="o-server" class="h-6 w-6" />
|
|
||||||
<span class="ml-2">Servers</span>
|
|
||||||
</x-sidebar-link>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<x-sidebar-link :href="route('scripts.index')" :active="request()->routeIs('scripts.*')">
|
<x-sidebar-link :href="route('scripts.index')" :active="request()->routeIs('scripts.*')">
|
||||||
<x-heroicon name="o-bolt" class="h-6 w-6" />
|
<x-heroicon name="o-bolt" class="h-6 w-6" />
|
||||||
@ -246,12 +239,6 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
|||||||
<span class="ml-2">SSH Keys</span>
|
<span class="ml-2">SSH Keys</span>
|
||||||
</x-sidebar-link>
|
</x-sidebar-link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<x-sidebar-link :href="route('settings.tags')" :active="request()->routeIs('settings.tags')">
|
|
||||||
<x-heroicon name="o-tag" class="h-6 w-6" />
|
|
||||||
<span class="ml-2">Tags</span>
|
|
||||||
</x-sidebar-link>
|
|
||||||
</li>
|
|
||||||
@endif
|
@endif
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +40,7 @@ class="cursor-pointer"
|
|||||||
@foreach ([\App\Enums\PHPIniType::FPM, \App\Enums\PHPIniType::CLI] as $type)
|
@foreach ([\App\Enums\PHPIniType::FPM, \App\Enums\PHPIniType::CLI] as $type)
|
||||||
<x-dropdown-link
|
<x-dropdown-link
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
x-on:click="version = '{{ $php->version }}'; $dispatch('open-modal', 'update-php-ini-{{ $type }}');"
|
x-on:click="version = '{{ $php->version }}'; $dispatch('open-modal', 'update-php-ini-{{ $type }}'); document.getElementById('ini').value = 'Loading...';"
|
||||||
hx-get="{{ route('servers.php.get-ini', ['server' => $server, 'version' => $php->version, 'type' => $type]) }}"
|
hx-get="{{ route('servers.php.get-ini', ['server' => $server, 'version' => $php->version, 'type' => $type]) }}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-target="#update-php-ini-{{ $type }}-form"
|
hx-target="#update-php-ini-{{ $type }}-form"
|
||||||
|
@ -15,8 +15,9 @@ class="p-6"
|
|||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<x-input-label for="ini" value="php.ini" />
|
<x-input-label for="ini" value="php.ini" />
|
||||||
@php($ini = old("ini", session()->get("ini") ?? "Loading..."))
|
<x-textarea id="ini" name="ini" class="mt-1 w-full font-mono" rows="15">
|
||||||
<x-editor id="ini" name="ini" lang="ini" :value="$ini" />
|
{{ old("ini", session()->get("ini")) }}
|
||||||
|
</x-textarea>
|
||||||
@error("ini")
|
@error("ini")
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
@enderror
|
@enderror
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<x-input-label for="content" :value="__('Content')" />
|
<x-input-label for="content" :value="__('Content')" />
|
||||||
<x-editor name="content" lang="sh" :value="$value" />
|
<x-textarea id="content" name="content" class="mt-1 min-h-[400px] w-full font-mono">
|
||||||
|
{{ $value }}
|
||||||
|
</x-textarea>
|
||||||
@error("content")
|
@error("content")
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
@enderror
|
@enderror
|
||||||
|
@ -110,15 +110,4 @@ class="mt-2 md:ml-2 md:mt-0"
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<div class="py-5">
|
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>{{ __("Tags") }}</div>
|
|
||||||
<div>
|
|
||||||
@include("settings.tags.tags", ["taggable" => $server, "edit" => true])
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-card>
|
</x-card>
|
||||||
|
@ -9,73 +9,40 @@
|
|||||||
</x-slot>
|
</x-slot>
|
||||||
</x-card-header>
|
</x-card-header>
|
||||||
|
|
||||||
<div class="space-y-3">
|
<x-live id="live-servers-list">
|
||||||
<x-live id="live-servers-list">
|
@if (count($servers) > 0)
|
||||||
@if (count($servers) > 0)
|
<div class="space-y-3">
|
||||||
<div class="space-y-3">
|
@foreach ($servers as $server)
|
||||||
<x-table>
|
<a href="{{ route("servers.show", ["server" => $server]) }}" class="block">
|
||||||
<x-thead>
|
<x-item-card>
|
||||||
<x-tr>
|
<div class="flex-none">
|
||||||
<x-th>Name</x-th>
|
<img
|
||||||
<x-th>IP</x-th>
|
src="{{ asset("static/images/" . $server->provider . ".svg") }}"
|
||||||
<x-th>Tags</x-th>
|
class="h-10 w-10"
|
||||||
<x-th>Status</x-th>
|
alt=""
|
||||||
<x-th></x-th>
|
/>
|
||||||
</x-tr>
|
</div>
|
||||||
</x-thead>
|
<div class="ml-3 flex flex-grow flex-col items-start justify-center">
|
||||||
<x-tbody>
|
<span class="mb-1">{{ $server->name }}</span>
|
||||||
@foreach ($servers as $server)
|
<span class="text-sm text-gray-400">
|
||||||
<x-tr>
|
{{ $server->ip }}
|
||||||
<x-td>
|
</span>
|
||||||
<div class="flex items-center">
|
</div>
|
||||||
<img
|
<div class="flex items-center">
|
||||||
src="{{ asset("static/images/" . $server->provider . ".svg") }}"
|
<div class="inline">
|
||||||
class="mr-1 h-5 w-5"
|
@include("servers.partials.server-status", ["server" => $server])
|
||||||
alt=""
|
</div>
|
||||||
/>
|
</div>
|
||||||
<a
|
</x-item-card>
|
||||||
href="{{ route("servers.show", ["server" => $server]) }}"
|
</a>
|
||||||
class="hover:underline"
|
@endforeach
|
||||||
>
|
</div>
|
||||||
{{ $server->name }}
|
@else
|
||||||
</a>
|
<x-simple-card>
|
||||||
</div>
|
<div class="text-center">
|
||||||
</x-td>
|
{{ __("You don't have any servers yet!") }}
|
||||||
<x-td>{{ $server->ip }}</x-td>
|
|
||||||
<x-td>
|
|
||||||
@include("settings.tags.tags", ["taggable" => $server, "oobOff" => true])
|
|
||||||
</x-td>
|
|
||||||
<x-td>
|
|
||||||
@include("servers.partials.server-status", ["server" => $server])
|
|
||||||
</x-td>
|
|
||||||
<x-td>
|
|
||||||
<div class="flex items-center justify-end">
|
|
||||||
<x-icon-button
|
|
||||||
:href="route('servers.show', ['server' => $server])"
|
|
||||||
data-tooltip="Show Server"
|
|
||||||
>
|
|
||||||
<x-heroicon name="o-eye" class="h-5 w-5" />
|
|
||||||
</x-icon-button>
|
|
||||||
<x-icon-button
|
|
||||||
:href="route('servers.settings', ['server' => $server])"
|
|
||||||
data-tooltip="Settings"
|
|
||||||
>
|
|
||||||
<x-heroicon name="o-wrench-screwdriver" class="h-5 w-5" />
|
|
||||||
</x-icon-button>
|
|
||||||
</div>
|
|
||||||
</x-td>
|
|
||||||
</x-tr>
|
|
||||||
@endforeach
|
|
||||||
</x-tbody>
|
|
||||||
</x-table>
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
</x-simple-card>
|
||||||
<x-simple-card>
|
@endif
|
||||||
<div class="text-center">
|
</x-live>
|
||||||
{{ __("You don't have any servers yet!") }}
|
|
||||||
</div>
|
|
||||||
</x-simple-card>
|
|
||||||
@endif
|
|
||||||
</x-live>
|
|
||||||
</div>
|
|
||||||
</x-container>
|
</x-container>
|
||||||
|
@ -1,14 +1,3 @@
|
|||||||
@use(App\Enums\StorageProvider)
|
|
||||||
@php
|
|
||||||
$storageProviders = [
|
|
||||||
StorageProvider::DROPBOX,
|
|
||||||
StorageProvider::FTP,
|
|
||||||
StorageProvider::LOCAL,
|
|
||||||
StorageProvider::S3,
|
|
||||||
StorageProvider::WASABI,
|
|
||||||
];
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<x-primary-button x-data="" x-on:click.prevent="$dispatch('open-modal', 'connect-provider')">
|
<x-primary-button x-data="" x-on:click.prevent="$dispatch('open-modal', 'connect-provider')">
|
||||||
{{ __("Connect") }}
|
{{ __("Connect") }}
|
||||||
@ -80,8 +69,136 @@ class="p-6"
|
|||||||
@enderror
|
@enderror
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (in_array($provider, $storageProviders))
|
@if ($provider == \App\Enums\StorageProvider::DROPBOX)
|
||||||
@include("settings.storage-providers.providers.{$provider}")
|
<div class="mt-6">
|
||||||
|
<x-input-label for="token" value="API Key" />
|
||||||
|
<x-text-input value="{{ old('token') }}" id="token" name="token" type="text" class="mt-1 w-full" />
|
||||||
|
@error("token")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="mt-1 text-primary-500"
|
||||||
|
href="https://dropbox.tech/developers/generate-an-access-token-for-your-own-account"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
How to generate?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($provider == \App\Enums\StorageProvider::FTP)
|
||||||
|
<div class="mt-6">
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="host" value="Host" />
|
||||||
|
<x-text-input
|
||||||
|
value="{{ old('host') }}"
|
||||||
|
id="host"
|
||||||
|
name="host"
|
||||||
|
type="text"
|
||||||
|
class="mt-1 w-full"
|
||||||
|
/>
|
||||||
|
@error("host")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="port" value="Port" />
|
||||||
|
<x-text-input
|
||||||
|
value="{{ old('port') }}"
|
||||||
|
id="port"
|
||||||
|
name="port"
|
||||||
|
type="text"
|
||||||
|
class="mt-1 w-full"
|
||||||
|
/>
|
||||||
|
@error("port")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="path" value="Path" />
|
||||||
|
<x-text-input value="{{ old('path') }}" id="path" name="path" type="text" class="mt-1 w-full" />
|
||||||
|
@error("path")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="username" value="Username" />
|
||||||
|
<x-text-input
|
||||||
|
value="{{ old('username') }}"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
type="text"
|
||||||
|
class="mt-1 w-full"
|
||||||
|
/>
|
||||||
|
@error("username")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="password" value="Password" />
|
||||||
|
<x-text-input
|
||||||
|
value="{{ old('password') }}"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="text"
|
||||||
|
class="mt-1 w-full"
|
||||||
|
/>
|
||||||
|
@error("password")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="ssl" :value="__('SSL')" />
|
||||||
|
<x-select-input id="ssl" name="ssl" class="mt-1 w-full">
|
||||||
|
<option value="1" @if(old('ssl')) selected @endif>
|
||||||
|
{{ __("Yes") }}
|
||||||
|
</option>
|
||||||
|
<option value="0" @if(!old('ssl')) selected @endif>
|
||||||
|
{{ __("No") }}
|
||||||
|
</option>
|
||||||
|
</x-select-input>
|
||||||
|
@error("ssl")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="passive" :value="__('Passive')" />
|
||||||
|
<x-select-input id="passive" name="passive" class="mt-1 w-full">
|
||||||
|
<option value="1" @if(old('passive')) selected @endif>
|
||||||
|
{{ __("Yes") }}
|
||||||
|
</option>
|
||||||
|
<option value="0" @if(!old('passive')) selected @endif>
|
||||||
|
{{ __("No") }}
|
||||||
|
</option>
|
||||||
|
</x-select-input>
|
||||||
|
@error("passive")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($provider == \App\Enums\StorageProvider::LOCAL)
|
||||||
|
<div class="mt-6">
|
||||||
|
<x-input-label for="path" value="Absolute Path" />
|
||||||
|
<x-text-input value="{{ old('path') }}" id="path" name="path" type="text" class="mt-1 w-full" />
|
||||||
|
<x-input-help>
|
||||||
|
The absolute path on your server that the database exists. like `/home/vito/db-backups`
|
||||||
|
</x-input-help>
|
||||||
|
<x-input-help>
|
||||||
|
Make sure that the path exists and the `vito` user has permission to write to it.
|
||||||
|
</x-input-help>
|
||||||
|
@error("path")
|
||||||
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="token" value="API Key" />
|
|
||||||
<x-text-input value="{{ old('token') }}" id="token" name="token" type="text" class="mt-1 w-full" />
|
|
||||||
@error("token")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
|
|
||||||
<a
|
|
||||||
class="mt-1 text-primary-500"
|
|
||||||
href="https://dropbox.tech/developers/generate-an-access-token-for-your-own-account"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
How to generate?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
@ -1,71 +0,0 @@
|
|||||||
<div class="mt-6">
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="host" value="Host" />
|
|
||||||
<x-text-input value="{{ old('host') }}" id="host" name="host" type="text" class="mt-1 w-full" />
|
|
||||||
@error("host")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="port" value="Port" />
|
|
||||||
<x-text-input value="{{ old('port') }}" id="port" name="port" type="text" class="mt-1 w-full" />
|
|
||||||
@error("port")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="path" value="Path" />
|
|
||||||
<x-text-input value="{{ old('path') }}" id="path" name="path" type="text" class="mt-1 w-full" />
|
|
||||||
@error("path")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="username" value="Username" />
|
|
||||||
<x-text-input value="{{ old('username') }}" id="username" name="username" type="text" class="mt-1 w-full" />
|
|
||||||
@error("username")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="password" value="Password" />
|
|
||||||
<x-text-input value="{{ old('password') }}" id="password" name="password" type="text" class="mt-1 w-full" />
|
|
||||||
@error("password")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="ssl" :value="__('SSL')" />
|
|
||||||
<x-select-input id="ssl" name="ssl" class="mt-1 w-full">
|
|
||||||
<option value="1" @if(old('ssl')) selected @endif>
|
|
||||||
{{ __("Yes") }}
|
|
||||||
</option>
|
|
||||||
<option value="0" @if(!old('ssl')) selected @endif>
|
|
||||||
{{ __("No") }}
|
|
||||||
</option>
|
|
||||||
</x-select-input>
|
|
||||||
@error("ssl")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="passive" :value="__('Passive')" />
|
|
||||||
<x-select-input id="passive" name="passive" class="mt-1 w-full">
|
|
||||||
<option value="1" @if(old('passive')) selected @endif>
|
|
||||||
{{ __("Yes") }}
|
|
||||||
</option>
|
|
||||||
<option value="0" @if(!old('passive')) selected @endif>
|
|
||||||
{{ __("No") }}
|
|
||||||
</option>
|
|
||||||
</x-select-input>
|
|
||||||
@error("passive")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,9 +0,0 @@
|
|||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="path" value="Absolute Path" />
|
|
||||||
<x-text-input value="{{ old('path') }}" id="path" name="path" type="text" class="mt-1 w-full" />
|
|
||||||
<x-input-help>The absolute path on your server that the database exists. like `/home/vito/db-backups`</x-input-help>
|
|
||||||
<x-input-help>Make sure that the path exists and the `vito` user has permission to write to it.</x-input-help>
|
|
||||||
@error("path")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
@ -1,49 +0,0 @@
|
|||||||
<div class="mt-6">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="path" value="Path" />
|
|
||||||
<x-text-input value="{{ old('path') }}" id="path" name="path" type="text" class="mt-1 w-full" />
|
|
||||||
@error("path")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="key" value="Key" />
|
|
||||||
<x-text-input value="{{ old('key') }}" id="key" name="key" type="text" class="mt-1 w-full" />
|
|
||||||
@error("key")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="secret" value="Secret" />
|
|
||||||
<x-text-input value="{{ old('secret') }}" id="secret" name="secret" type="text" class="mt-1 w-full" />
|
|
||||||
@error("secret")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a
|
|
||||||
class="mt-1 text-primary-500"
|
|
||||||
href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/configuring-bucket-key.html"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
How to generate?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="region" value="Region" />
|
|
||||||
<x-text-input value="{{ old('region') }}" id="region" name="region" type="text" class="mt-1 w-full" />
|
|
||||||
@error("region")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="bucket" value="Bucket" />
|
|
||||||
<x-text-input value="{{ old('bucket') }}" id="bucket" name="bucket" type="text" class="mt-1 w-full" />
|
|
||||||
@error("bucket")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,49 +0,0 @@
|
|||||||
<div class="mt-6">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="path" value="Path" />
|
|
||||||
<x-text-input value="{{ old('path') }}" id="path" name="path" type="text" class="mt-1 w-full" />
|
|
||||||
@error("path")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="key" value="Key" />
|
|
||||||
<x-text-input value="{{ old('key') }}" id="key" name="key" type="text" class="mt-1 w-full" />
|
|
||||||
@error("key")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="secret" value="Secret" />
|
|
||||||
<x-text-input value="{{ old('secret') }}" id="secret" name="secret" type="text" class="mt-1 w-full" />
|
|
||||||
@error("secret")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a
|
|
||||||
class="mt-1 text-primary-500"
|
|
||||||
href="https://docs.wasabi.com/docs/creating-a-user-account-and-access-key"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
How to generate?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="region" value="Region" />
|
|
||||||
<x-text-input value="{{ old('region') }}" id="region" name="region" type="text" class="mt-1 w-full" />
|
|
||||||
@error("region")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<x-input-label for="bucket" value="Bucket" />
|
|
||||||
<x-text-input value="{{ old('bucket') }}" id="bucket" name="bucket" type="text" class="mt-1 w-full" />
|
|
||||||
@error("bucket")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,41 +0,0 @@
|
|||||||
<div x-data="">
|
|
||||||
<button type="button" class="flex items-center" x-on:click="$dispatch('open-modal', 'create-tag-modal')">
|
|
||||||
<x-heroicon name="o-plus" class="h-5 w-5 text-gray-500 dark:text-gray-400" />
|
|
||||||
<span class="text-md">New Tag</span>
|
|
||||||
</button>
|
|
||||||
@push("footer")
|
|
||||||
<x-modal name="create-tag-modal" max-width="sm">
|
|
||||||
<form
|
|
||||||
id="create-tag-form"
|
|
||||||
hx-post="{{ route("tags.attach") }}"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-select="#create-tag-form"
|
|
||||||
class="p-6"
|
|
||||||
>
|
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">New Tag</h2>
|
|
||||||
|
|
||||||
<input type="hidden" name="taggable_type" value="{{ get_class($taggable) }}" />
|
|
||||||
<input type="hidden" name="taggable_id" value="{{ $taggable->id }}" />
|
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
@include("settings.tags.fields.name",["value" => old("name"),"items" => auth()->user()->currentProject->tags()->pluck("name"),])
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6 flex justify-end">
|
|
||||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">Cancel</x-secondary-button>
|
|
||||||
<x-primary-button class="ml-3" hx-disable>Save</x-primary-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (session()->has("status") && session()->get("status") === "tag-created")
|
|
||||||
<script defer>
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent('close-modal', {
|
|
||||||
detail: 'create-tag-modal'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
@endif
|
|
||||||
</form>
|
|
||||||
</x-modal>
|
|
||||||
@endpush
|
|
||||||
</div>
|
|
@ -1,31 +0,0 @@
|
|||||||
@php($id = "color-" . uniqid())
|
|
||||||
<div x-data="{
|
|
||||||
value: '{{ $value }}',
|
|
||||||
}">
|
|
||||||
<x-input-label for="color" :value="__('Color')" />
|
|
||||||
<input x-bind:value="value" id="{{ $id }}" name="color" type="hidden" />
|
|
||||||
<x-dropdown class="relative" align="left">
|
|
||||||
<x-slot name="trigger">
|
|
||||||
<x-dropdown-trigger>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div x-show="value" x-bind:class="`bg-${value}-500 mr-1 h-3 w-3 rounded-full`"></div>
|
|
||||||
<span x-text="value || 'Select a color'"></span>
|
|
||||||
</div>
|
|
||||||
</x-dropdown-trigger>
|
|
||||||
</x-slot>
|
|
||||||
|
|
||||||
<x-slot name="content">
|
|
||||||
<div class="z-50 max-h-[200px] overflow-y-auto">
|
|
||||||
@foreach (config("core.tag_colors") as $color)
|
|
||||||
<x-dropdown-link href="#" x-on:click="value = '{{ $color }}'" class="flex items-center capitalize">
|
|
||||||
<div class="bg-{{ $color }}-500 mr-1 h-3 w-3 rounded-full"></div>
|
|
||||||
{{ $color }}
|
|
||||||
</x-dropdown-link>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</x-slot>
|
|
||||||
</x-dropdown>
|
|
||||||
@error("color")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
@ -1,12 +0,0 @@
|
|||||||
@php
|
|
||||||
$id = "name-" . uniqid();
|
|
||||||
if (! isset($items)) {
|
|
||||||
$items = [];
|
|
||||||
}
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<x-input-label :for="$id" :value="__('Name')" />
|
|
||||||
<x-autocomplete-text id="tag-name" name="name" :items="$items" :value="$value" placeholder="" />
|
|
||||||
@error("name")
|
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
|
||||||
@enderror
|
|
@ -1,5 +0,0 @@
|
|||||||
<x-settings-layout>
|
|
||||||
<x-slot name="pageTitle">{{ __("Tags") }}</x-slot>
|
|
||||||
|
|
||||||
@include("settings.tags.partials.tags-list")
|
|
||||||
</x-settings-layout>
|
|
@ -1,42 +0,0 @@
|
|||||||
<x-modal name="manage-tags-modal">
|
|
||||||
<div class="p-6">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Manage Tags</h2>
|
|
||||||
@include("settings.tags.attach", ["taggable" => $taggable])
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-table id="tags-{{ $taggable->id }}" class="mt-6" hx-swap-oob="outerHTML">
|
|
||||||
<x-thead>
|
|
||||||
<x-tr>
|
|
||||||
<x-th>Name</x-th>
|
|
||||||
<x-th></x-th>
|
|
||||||
</x-tr>
|
|
||||||
</x-thead>
|
|
||||||
<x-tbody>
|
|
||||||
@foreach ($taggable->tags as $tag)
|
|
||||||
<x-tr>
|
|
||||||
<x-td>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="bg-{{ $tag->color }}-500 mr-1 h-3 w-3 rounded-full"></div>
|
|
||||||
{{ $tag->name }}
|
|
||||||
</div>
|
|
||||||
</x-td>
|
|
||||||
<x-td class="text-right">
|
|
||||||
<form
|
|
||||||
id="detach-tag-{{ $tag->id }}"
|
|
||||||
hx-post="{{ route("tags.detach", ["tag" => $tag]) }}"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
>
|
|
||||||
<input type="hidden" name="taggable_type" value="{{ get_class($taggable) }}" />
|
|
||||||
<input type="hidden" name="taggable_id" value="{{ $taggable->id }}" />
|
|
||||||
<x-icon-button>
|
|
||||||
<x-heroicon name="o-trash" class="h-5 w-5 text-gray-500 dark:text-gray-400" />
|
|
||||||
</x-icon-button>
|
|
||||||
</form>
|
|
||||||
</x-td>
|
|
||||||
</x-tr>
|
|
||||||
@endforeach
|
|
||||||
</x-tbody>
|
|
||||||
</x-table>
|
|
||||||
</div>
|
|
||||||
</x-modal>
|
|
@ -1,41 +0,0 @@
|
|||||||
<div>
|
|
||||||
<x-primary-button x-data="" x-on:click.prevent="$dispatch('open-modal', 'create-tag')">
|
|
||||||
{{ __("Create Tag") }}
|
|
||||||
</x-primary-button>
|
|
||||||
|
|
||||||
<x-modal name="create-tag">
|
|
||||||
<form
|
|
||||||
id="create-tag-form"
|
|
||||||
hx-post="{{ route("settings.tags.create") }}"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-select="#create-tag-form"
|
|
||||||
hx-ext="disable-element"
|
|
||||||
hx-disable-element="#btn-create-tag"
|
|
||||||
class="p-6"
|
|
||||||
x-data="{}"
|
|
||||||
>
|
|
||||||
@csrf
|
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{{ __("Create Tag") }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
@include("settings.tags.fields.name", ["value" => old("name")])
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
@include("settings.tags.fields.color", ["value" => old("color")])
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6 flex justify-end">
|
|
||||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
|
||||||
{{ __("Cancel") }}
|
|
||||||
</x-secondary-button>
|
|
||||||
|
|
||||||
<x-primary-button id="btn-create-tag" class="ml-3">
|
|
||||||
{{ __("Save") }}
|
|
||||||
</x-primary-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</x-modal>
|
|
||||||
</div>
|
|
@ -1,18 +0,0 @@
|
|||||||
<x-modal name="delete-tag" :show="$errors->isNotEmpty()">
|
|
||||||
<form id="delete-tag-form" method="post" x-bind:action="deleteAction" class="p-6">
|
|
||||||
@csrf
|
|
||||||
@method("delete")
|
|
||||||
|
|
||||||
<h2 class="text-lg font-medium">Deleting a tag will detach it from all the resources that it has been used</h2>
|
|
||||||
|
|
||||||
<div class="mt-6 flex justify-end">
|
|
||||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
|
||||||
{{ __("Cancel") }}
|
|
||||||
</x-secondary-button>
|
|
||||||
|
|
||||||
<x-danger-button class="ml-3">
|
|
||||||
{{ __("Delete") }}
|
|
||||||
</x-danger-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</x-modal>
|
|
@ -1,39 +0,0 @@
|
|||||||
<x-modal
|
|
||||||
name="edit-tag"
|
|
||||||
:show="true"
|
|
||||||
x-on:modal-edit-tag-closed.window="window.history.pushState('', '', '{{ route('settings.tags') }}');"
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
id="edit-tag-form"
|
|
||||||
hx-post="{{ route("settings.tags.update", ["tag" => $tag->id]) }}"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-select="#edit-tag-form"
|
|
||||||
hx-ext="disable-element"
|
|
||||||
hx-disable-element="#btn-edit-tag"
|
|
||||||
class="p-6"
|
|
||||||
>
|
|
||||||
@csrf
|
|
||||||
|
|
||||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
{{ __("Edit Tag") }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
@include("settings.tags.fields.name", ["value" => old("name", $tag->name)])
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
@include("settings.tags.fields.color", ["value" => old("color", $tag->color)])
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6 flex justify-end">
|
|
||||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
|
||||||
{{ __("Cancel") }}
|
|
||||||
</x-secondary-button>
|
|
||||||
|
|
||||||
<x-primary-button id="btn-edit-tag" class="ml-3">
|
|
||||||
{{ __("Save") }}
|
|
||||||
</x-primary-button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</x-modal>
|
|
@ -1,69 +0,0 @@
|
|||||||
<div>
|
|
||||||
<x-card-header>
|
|
||||||
<x-slot name="title">{{ __("Tags") }}</x-slot>
|
|
||||||
<x-slot name="description">
|
|
||||||
{{ __("You can manage tags here") }}
|
|
||||||
</x-slot>
|
|
||||||
<x-slot name="aside">
|
|
||||||
@include("settings.tags.partials.create-tag")
|
|
||||||
</x-slot>
|
|
||||||
</x-card-header>
|
|
||||||
<div x-data="{ deleteAction: '' }" class="space-y-3">
|
|
||||||
@if (count($tags) > 0)
|
|
||||||
<x-table class="mt-6">
|
|
||||||
<x-thead>
|
|
||||||
<x-tr>
|
|
||||||
<x-th>Name</x-th>
|
|
||||||
<x-th></x-th>
|
|
||||||
</x-tr>
|
|
||||||
</x-thead>
|
|
||||||
<x-tbody>
|
|
||||||
@foreach ($tags as $tag)
|
|
||||||
<x-tr>
|
|
||||||
<x-td>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="bg-{{ $tag->color }}-500 mr-1 size-4 rounded-full"></div>
|
|
||||||
{{ $tag->name }}
|
|
||||||
</div>
|
|
||||||
</x-td>
|
|
||||||
<x-td class="text-right">
|
|
||||||
<div class="inline">
|
|
||||||
<x-icon-button
|
|
||||||
id="edit-{{ $tag->id }}"
|
|
||||||
hx-get="{{ route('settings.tags', ['edit' => $tag->id]) }}"
|
|
||||||
hx-replace-url="true"
|
|
||||||
hx-select="#edit"
|
|
||||||
hx-target="#edit"
|
|
||||||
hx-ext="disable-element"
|
|
||||||
hx-disable-element="#edit-{{ $tag->id }}"
|
|
||||||
>
|
|
||||||
<x-heroicon name="o-pencil" class="h-5 w-5" />
|
|
||||||
</x-icon-button>
|
|
||||||
<x-icon-button
|
|
||||||
x-on:click="deleteAction = '{{ route('settings.tags.delete', ['tag' => $tag]) }}'; $dispatch('open-modal', 'delete-tag')"
|
|
||||||
>
|
|
||||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
|
||||||
</x-icon-button>
|
|
||||||
</div>
|
|
||||||
</x-td>
|
|
||||||
</x-tr>
|
|
||||||
@endforeach
|
|
||||||
</x-tbody>
|
|
||||||
</x-table>
|
|
||||||
|
|
||||||
@include("settings.tags.partials.delete-tag")
|
|
||||||
|
|
||||||
<div id="edit">
|
|
||||||
@if (isset($editTag))
|
|
||||||
@include("settings.tags.partials.edit-tag", ["tag" => $editTag])
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<x-simple-card>
|
|
||||||
<div class="text-center">
|
|
||||||
{{ __("You don't have any tags yet!") }}
|
|
||||||
</div>
|
|
||||||
</x-simple-card>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,30 +0,0 @@
|
|||||||
<div x-data="">
|
|
||||||
<div class="inline-flex gap-1">
|
|
||||||
<div
|
|
||||||
id="tags-list-{{ $taggable->id }}"
|
|
||||||
class="inline-flex gap-1"
|
|
||||||
@if (! isset($oobOff) || ! $oobOff)
|
|
||||||
hx-swap-oob="outerHTML"
|
|
||||||
@endif
|
|
||||||
>
|
|
||||||
@foreach ($taggable->tags as $tag)
|
|
||||||
<div
|
|
||||||
class="border-{{ $tag->color }}-300 bg-{{ $tag->color }}-50 text-{{ $tag->color }}-500 dark:border-{{ $tag->color }}-600 dark:bg-{{ $tag->color }}-500 flex max-w-max items-center rounded-md border px-2 py-1 text-xs dark:bg-opacity-10"
|
|
||||||
>
|
|
||||||
<x-heroicon name="o-tag" class="mr-1 size-4" />
|
|
||||||
{{ $tag->name }}
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (isset($edit) && $edit)
|
|
||||||
<a
|
|
||||||
x-on:click="$dispatch('open-modal', 'manage-tags-modal')"
|
|
||||||
class="flex max-w-max cursor-pointer items-center justify-center rounded-md border border-gray-300 bg-gray-50 px-2 py-1 text-xs text-gray-500 dark:border-gray-600 dark:bg-gray-500 dark:bg-opacity-10"
|
|
||||||
>
|
|
||||||
<x-heroicon name="o-pencil" class="h-3 w-3" />
|
|
||||||
</a>
|
|
||||||
@include("settings.tags.manage")
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,8 +1,6 @@
|
|||||||
<x-site-layout :site="$site">
|
<x-site-layout :site="$site">
|
||||||
<x-slot name="pageTitle">{{ __("Settings") }}</x-slot>
|
<x-slot name="pageTitle">{{ __("Settings") }}</x-slot>
|
||||||
|
|
||||||
@include("site-settings.partials.site-details")
|
|
||||||
|
|
||||||
@include("site-settings.partials.change-php-version")
|
@include("site-settings.partials.change-php-version")
|
||||||
|
|
||||||
@include("site-settings.partials.update-aliases")
|
@include("site-settings.partials.update-aliases")
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
<x-card id="server-details">
|
|
||||||
<x-slot name="title">{{ __("Details") }}</x-slot>
|
|
||||||
<x-slot name="description">
|
|
||||||
{{ __("More details about your site") }}
|
|
||||||
</x-slot>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>{{ __("Created At") }}</div>
|
|
||||||
<div>
|
|
||||||
<x-datetime :value="$site->created_at" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="py-5">
|
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>{{ __("Type") }}</div>
|
|
||||||
<div class="capitalize">{{ $site->type }}</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="py-5">
|
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>{{ __("Site ID") }}</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="rounded-md bg-gray-100 p-1 dark:bg-gray-700">
|
|
||||||
{{ $site->id }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="py-5">
|
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>{{ __("Status") }}</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
@include("sites.partials.site-status")
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="py-5">
|
|
||||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>{{ __("Tags") }}</div>
|
|
||||||
<div>
|
|
||||||
@include("settings.tags.tags", ["taggable" => $site, "edit" => true])
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-card>
|
|
@ -22,8 +22,9 @@ class="space-y-6"
|
|||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
<div id="vhost-container">
|
<div id="vhost-container">
|
||||||
@php($vhost = old("vhost", session()->get("vhost") ?? "Loading..."))
|
<x-textarea id="vhost" name="vhost" rows="10" class="mt-1 block min-h-[400px] w-full font-mono">
|
||||||
<x-editor id="vhost" name="vhost" lang="nginx" :value="$vhost" />
|
{{ session()->has("vhost") ? session()->get("vhost") : "Loading..." }}
|
||||||
|
</x-textarea>
|
||||||
</div>
|
</div>
|
||||||
@error("vhost")
|
@error("vhost")
|
||||||
<x-input-error class="mt-2" :messages="$message" />
|
<x-input-error class="mt-2" :messages="$message" />
|
||||||
|
@ -12,63 +12,30 @@
|
|||||||
<x-live id="live-sites">
|
<x-live id="live-sites">
|
||||||
@if (count($sites) > 0)
|
@if (count($sites) > 0)
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<x-table>
|
@foreach ($sites as $site)
|
||||||
<x-thead>
|
<a href="{{ route("servers.sites.show", ["server" => $server, "site" => $site]) }}" class="block">
|
||||||
<x-tr>
|
<x-item-card>
|
||||||
<x-th>Domain</x-th>
|
<div class="flex-none">
|
||||||
<x-th>Date</x-th>
|
<img
|
||||||
<x-th>Tags</x-th>
|
src="{{ asset("static/images/" . $site->type . ".svg") }}"
|
||||||
<x-th>Status</x-th>
|
class="h-10 w-10"
|
||||||
<x-th></x-th>
|
alt=""
|
||||||
</x-tr>
|
/>
|
||||||
</x-thead>
|
</div>
|
||||||
<x-tbody>
|
<div class="ml-3 flex flex-grow flex-col items-start justify-center">
|
||||||
@foreach ($sites as $site)
|
<span class="mb-1">{{ $site->domain }}</span>
|
||||||
<x-tr>
|
<span class="text-sm text-gray-400">
|
||||||
<x-td>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<img
|
|
||||||
src="{{ asset("static/images/" . $site->type . ".svg") }}"
|
|
||||||
class="mr-1 h-5 w-5"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
href="{{ route("servers.sites.show", ["server" => $server, "site" => $site]) }}"
|
|
||||||
class="hover:underline"
|
|
||||||
>
|
|
||||||
{{ $site->domain }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</x-td>
|
|
||||||
<x-td>
|
|
||||||
<x-datetime :value="$site->created_at" />
|
<x-datetime :value="$site->created_at" />
|
||||||
</x-td>
|
</span>
|
||||||
<x-td>
|
</div>
|
||||||
@include("settings.tags.tags", ["taggable" => $site, "oobOff" => true])
|
<div class="flex items-center">
|
||||||
</x-td>
|
<div class="inline">
|
||||||
<x-td>
|
|
||||||
@include("sites.partials.status", ["status" => $site->status])
|
@include("sites.partials.status", ["status" => $site->status])
|
||||||
</x-td>
|
</div>
|
||||||
<x-td>
|
</div>
|
||||||
<div class="flex items-center justify-end">
|
</x-item-card>
|
||||||
<x-icon-button
|
</a>
|
||||||
:href="route('servers.sites.show', ['server' => $server, 'site' => $site])"
|
@endforeach
|
||||||
data-tooltip="Show Site"
|
|
||||||
>
|
|
||||||
<x-heroicon name="o-eye" class="h-5 w-5" />
|
|
||||||
</x-icon-button>
|
|
||||||
<x-icon-button
|
|
||||||
:href="route('servers.sites.settings', ['server' => $server, 'site' => $site])"
|
|
||||||
data-tooltip="Settings"
|
|
||||||
>
|
|
||||||
<x-heroicon name="o-wrench-screwdriver" class="h-5 w-5" />
|
|
||||||
</x-icon-button>
|
|
||||||
</div>
|
|
||||||
</x-td>
|
|
||||||
</x-tr>
|
|
||||||
@endforeach
|
|
||||||
</x-tbody>
|
|
||||||
</x-table>
|
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<x-simple-card>
|
<x-simple-card>
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
use App\Http\Controllers\Settings\SourceControlController;
|
use App\Http\Controllers\Settings\SourceControlController;
|
||||||
use App\Http\Controllers\Settings\SSHKeyController;
|
use App\Http\Controllers\Settings\SSHKeyController;
|
||||||
use App\Http\Controllers\Settings\StorageProviderController;
|
use App\Http\Controllers\Settings\StorageProviderController;
|
||||||
use App\Http\Controllers\Settings\TagController;
|
|
||||||
use App\Http\Controllers\Settings\UserController;
|
use App\Http\Controllers\Settings\UserController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
@ -65,11 +64,3 @@
|
|||||||
Route::post('add', [SshKeyController::class, 'add'])->name('settings.ssh-keys.add');
|
Route::post('add', [SshKeyController::class, 'add'])->name('settings.ssh-keys.add');
|
||||||
Route::delete('delete/{id}', [SshKeyController::class, 'delete'])->name('settings.ssh-keys.delete');
|
Route::delete('delete/{id}', [SshKeyController::class, 'delete'])->name('settings.ssh-keys.delete');
|
||||||
});
|
});
|
||||||
|
|
||||||
// tags
|
|
||||||
Route::prefix('/tags')->group(function () {
|
|
||||||
Route::get('/', [TagController::class, 'index'])->name('settings.tags');
|
|
||||||
Route::post('/create', [TagController::class, 'create'])->name('settings.tags.create');
|
|
||||||
Route::post('/{tag}', [TagController::class, 'update'])->name('settings.tags.update');
|
|
||||||
Route::delete('/{tag}', [TagController::class, 'delete'])->name('settings.tags.delete');
|
|
||||||
});
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
use App\Http\Controllers\ScriptController;
|
use App\Http\Controllers\ScriptController;
|
||||||
use App\Http\Controllers\SearchController;
|
use App\Http\Controllers\SearchController;
|
||||||
use App\Http\Controllers\Settings\ProjectController;
|
use App\Http\Controllers\Settings\ProjectController;
|
||||||
use App\Http\Controllers\Settings\TagController;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
@ -26,11 +25,6 @@
|
|||||||
require __DIR__.'/settings.php';
|
require __DIR__.'/settings.php';
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('/settings/tags')->group(function () {
|
|
||||||
Route::post('/attach', [TagController::class, 'attach'])->name('tags.attach');
|
|
||||||
Route::post('/{tag}/detach', [TagController::class, 'detach'])->name('tags.detach');
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::prefix('/servers')->middleware('must-have-current-project')->group(function () {
|
Route::prefix('/servers')->middleware('must-have-current-project')->group(function () {
|
||||||
require __DIR__.'/server.php';
|
require __DIR__.'/server.php';
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@ fi
|
|||||||
|
|
||||||
if [[ -z "${V_ADMIN_PASSWORD}" ]]; then
|
if [[ -z "${V_ADMIN_PASSWORD}" ]]; then
|
||||||
echo "Enter a password for Vito's dashboard:"
|
echo "Enter a password for Vito's dashboard:"
|
||||||
read V_ADMIN_PASSWORD
|
read -s V_ADMIN_PASSWORD
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${V_ADMIN_PASSWORD}" ]]; then
|
if [[ -z "${V_ADMIN_PASSWORD}" ]]; then
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user