Compare commits

...

8 Commits

133 changed files with 4319 additions and 1016 deletions

View File

@ -2,10 +2,14 @@
namespace App\Actions\Site; namespace App\Actions\Site;
use App\Exceptions\SSHUploadFailed;
use App\Models\Site; use App\Models\Site;
class UpdateEnv class UpdateEnv
{ {
/**
* @throws SSHUploadFailed
*/
public function update(Site $site, array $input): void public function update(Site $site, array $input): void
{ {
$site->server->os()->editFile( $site->server->os()->editFile(

View File

@ -0,0 +1,58 @@
<?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();
}
}

View File

@ -0,0 +1,49 @@
<?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();
}
}

View File

@ -0,0 +1,15 @@
<?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();
}
}

View File

@ -0,0 +1,36 @@
<?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();
}
}

View File

@ -0,0 +1,38 @@
<?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();
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Exceptions;
class SSHUploadFailed extends SSHError
{
//
}

30
app/Facades/FTP.php Normal file
View File

@ -0,0 +1,30 @@
<?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;
}
}

View File

@ -16,6 +16,8 @@
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false) * @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false)
* @method static string assertExecuted(array|string $commands) * @method static string assertExecuted(array|string $commands)
* @method static string assertExecutedContains(string $command) * @method static string assertExecutedContains(string $command)
* @method static string assertFileUploaded(string $toPath, ?string $content = null)
* @method static string getUploadedLocalPath()
* @method static disconnect() * @method static disconnect()
*/ */
class SSH extends FacadeAlias class SSH extends FacadeAlias

37
app/Helpers/FTP.php Normal file
View File

@ -0,0 +1,37 @@
<?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);
}
}

View File

@ -10,6 +10,7 @@
use App\Exceptions\RepositoryNotFound; use App\Exceptions\RepositoryNotFound;
use App\Exceptions\RepositoryPermissionDenied; use App\Exceptions\RepositoryPermissionDenied;
use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SourceControlIsNotConnected;
use App\Exceptions\SSHUploadFailed;
use App\Facades\Toast; use App\Facades\Toast;
use App\Helpers\HtmxResponse; use App\Helpers\HtmxResponse;
use App\Models\Deployment; use App\Models\Deployment;
@ -81,9 +82,12 @@ public function updateEnv(Server $server, Site $site, Request $request): Redirec
{ {
$this->authorize('manage', $server); $this->authorize('manage', $server);
app(UpdateEnv::class)->update($site, $request->input()); try {
app(UpdateEnv::class)->update($site, $request->input());
Toast::success('Env updated!'); Toast::success('Env updated!');
} catch (SSHUploadFailed) {
Toast::error('Failed to update .env file!');
}
return back(); return back();
} }

View File

@ -0,0 +1,90 @@
<?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();
}
}

View File

@ -65,4 +65,9 @@ public function sourceControls(): HasMany
{ {
return $this->hasMany(SourceControl::class); return $this->hasMany(SourceControl::class);
} }
public function tags(): HasMany
{
return $this->hasMany(Tag::class);
}
} }

View File

@ -14,6 +14,7 @@
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;
@ -214,6 +215,11 @@ 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) {

View File

@ -11,6 +11,7 @@
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;
/** /**
@ -126,6 +127,11 @@ 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
*/ */

55
app/Models/Tag.php Normal file
View File

@ -0,0 +1,55 @@
<?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');
}
}

View File

@ -2,6 +2,7 @@
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;
@ -36,5 +37,8 @@ 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;
});
} }
} }

View File

@ -2,9 +2,14 @@
namespace App\SSH\OS; namespace App\SSH\OS;
use App\Exceptions\SSHUploadFailed;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerLog; use App\Models\ServerLog;
use App\SSH\HasScripts; use App\SSH\HasScripts;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Throwable;
class OS class OS
{ {
@ -109,14 +114,25 @@ public function reboot(): void
); );
} }
/**
* @throws SSHUploadFailed
*/
public function editFile(string $path, ?string $content = null): void public function editFile(string $path, ?string $content = null): void
{ {
$this->server->ssh()->exec( $tmpName = Str::random(10).strtotime('now');
$this->getScript('edit-file.sh', [ try {
'path' => $path, /** @var FilesystemAdapter $storageDisk */
'content' => $content ?? '', $storageDisk = Storage::disk('local');
]), $storageDisk->put($tmpName, $content);
); $this->server->ssh()->upload(
$storageDisk->path($tmpName),
$path
);
} catch (Throwable) {
throw new SSHUploadFailed();
} finally {
$this->deleteTempFile($tmpName);
}
} }
public function readFile(string $path): string public function readFile(string $path): string
@ -200,4 +216,11 @@ public function resourceInfo(): array
'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(), 'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(),
]; ];
} }
private function deleteTempFile(string $name): void
{
if (Storage::disk('local')->exists($name)) {
Storage::disk('local')->delete($name);
}
}
} }

View File

@ -1,3 +0,0 @@
if ! echo "__content__" | tee __path__; then
echo 'VITO_SSH_ERROR' && exit 1
fi

View File

@ -41,7 +41,7 @@ public function connect(): bool
$isConnected = $connection && $this->login($connection); $isConnected = $connection && $this->login($connection);
if ($isConnected) { if ($isConnected) {
ftp_close($connection); \App\Facades\FTP::close($connection);
} }
return $isConnected; return $isConnected;
@ -58,31 +58,36 @@ 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']) {
ftp_pasv($connection, true); \App\Facades\FTP::passive($connection, true);
} }
foreach ($paths as $path) { foreach ($paths as $path) {
ftp_delete($connection, $this->storageProvider->credentials['path'].'/'.$path); \App\Facades\FTP::delete($connection, $this->storageProvider->credentials['path'].'/'.$path);
} }
} }
ftp_close($connection); \App\Facades\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 ftp_connect($credentials['host'], $credentials['port'], 5); return \App\Facades\FTP::connect(
$credentials['host'],
$credentials['port'],
$credentials['ssl']
);
} }
private function login(Connection $connection): bool private function login(bool|Connection $connection): bool
{ {
$credentials = $this->storageProvider->credentials; $credentials = $this->storageProvider->credentials;
return ftp_login($connection, $credentials['username'], $credentials['password']); return \App\Facades\FTP::login(
$credentials['username'],
$credentials['password'],
$connection
);
} }
} }

View File

@ -0,0 +1,60 @@
<?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);
}
}

View File

@ -17,6 +17,12 @@ class SSHFake extends SSH
protected bool $connectionWillFail = false; protected bool $connectionWillFail = false;
protected string $uploadedLocalPath;
protected string $uploadedRemotePath;
protected string $uploadedContent;
public function __construct(?string $output = null) public function __construct(?string $output = null)
{ {
$this->output = $output; $this->output = $output;
@ -63,6 +69,9 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
public function upload(string $local, string $remote): void public function upload(string $local, string $remote): void
{ {
$this->uploadedLocalPath = $local;
$this->uploadedRemotePath = $remote;
$this->uploadedContent = file_get_contents($local);
$this->log = null; $this->log = null;
} }
@ -105,4 +114,22 @@ public function assertExecutedContains(string $command): void
} }
Assert::assertTrue(true, $executed); Assert::assertTrue(true, $executed);
} }
public function assertFileUploaded(string $toPath, ?string $content = null): void
{
if (! $this->uploadedLocalPath || ! $this->uploadedRemotePath) {
Assert::fail('File is not uploaded');
}
Assert::assertEquals($toPath, $this->uploadedRemotePath);
if ($content) {
Assert::assertEquals($content, $this->uploadedContent);
}
}
public function getUploadedLocalPath(): string
{
return $this->uploadedLocalPath;
}
} }

View File

@ -0,0 +1,47 @@
<?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');
}
}

View File

@ -430,7 +430,7 @@
], ],
'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,
], ],
@ -445,4 +445,30 @@
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,
],
]; ];

View File

@ -618,8 +618,8 @@
'images' => [ 'images' => [
'ubuntu_18' => '112929540', 'ubuntu_18' => '112929540',
'ubuntu_20' => '112929454', 'ubuntu_20' => '112929454',
'ubuntu_22' => '129211873', 'ubuntu_22' => '159651797',
'ubuntu_24' => '155133621', 'ubuntu_24' => '160232537',
], ],
], ],
'vultr' => [ 'vultr' => [

View File

@ -0,0 +1,23 @@
<?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')),
];
}
}

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->string('profile_photo_path', 2048)->nullable();
$table->text('two_factor_secret')->nullable();
$table->text('two_factor_recovery_codes')->nullable();
$table->string('timezone')->default('UTC')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
}

View File

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePasswordResetsTable extends Migration
{
public function up(): void
{
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
public function down(): void
{
Schema::dropIfExists('password_reset_tokens');
}
}

View File

@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Get the migration connection name.
*/
public function getConnection(): ?string
{
return config('telescope.storage.database.connection');
}
/**
* Run the migrations.
*/
public function up(): void
{
$schema = Schema::connection($this->getConnection());
$schema->create('telescope_entries', function (Blueprint $table) {
$table->bigIncrements('sequence');
$table->uuid('uuid');
$table->uuid('batch_id');
$table->string('family_hash')->nullable();
$table->boolean('should_display_on_index')->default(true);
$table->string('type', 20);
$table->longText('content');
$table->dateTime('created_at')->nullable();
$table->unique('uuid');
$table->index('batch_id');
$table->index('family_hash');
$table->index('created_at');
$table->index(['type', 'should_display_on_index']);
});
$schema->create('telescope_entries_tags', function (Blueprint $table) {
$table->uuid('entry_uuid');
$table->string('tag');
$table->primary(['entry_uuid', 'tag']);
$table->index('tag');
$table->foreign('entry_uuid')
->references('uuid')
->on('telescope_entries')
->onDelete('cascade');
});
$schema->create('telescope_monitoring', function (Blueprint $table) {
$table->string('tag')->primary();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$schema = Schema::connection($this->getConnection());
$schema->dropIfExists('telescope_entries_tags');
$schema->dropIfExists('telescope_entries');
$schema->dropIfExists('telescope_monitoring');
}
};

View File

@ -0,0 +1,26 @@
<?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('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
public function down(): void
{
Schema::dropIfExists('failed_jobs');
}
};

View File

@ -0,0 +1,27 @@
<?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('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@ -0,0 +1,25 @@
<?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('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity')->index();
});
}
public function down(): void
{
Schema::dropIfExists('sessions');
}
};

View File

@ -0,0 +1,38 @@
<?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('servers', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('name')->index();
$table->string('ssh_user')->nullable();
$table->ipAddress('ip')->index()->nullable();
$table->ipAddress('local_ip')->nullable();
$table->unsignedInteger('provider_id')->nullable();
$table->integer('port')->default(22);
$table->string('os');
$table->string('type');
$table->json('type_data')->nullable();
$table->string('provider');
$table->json('provider_data')->nullable();
$table->longText('authentication')->nullable();
$table->longText('public_key')->nullable();
$table->string('status')->default('installing');
$table->integer('progress')->default(0);
$table->string('progress_step')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('servers');
}
};

View File

@ -0,0 +1,30 @@
<?php
use App\Enums\ServiceStatus;
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('services', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->string('type');
$table->json('type_data')->nullable();
$table->string('name');
$table->string('version');
$table->string('status')->default(ServiceStatus::INSTALLING);
$table->boolean('is_default')->default(1);
$table->string('unit')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('services');
}
};

View File

@ -0,0 +1,26 @@
<?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('jobs', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('queue')->index();
$table->longText('payload');
$table->integer('attempts');
$table->integer('reserved_at')->nullable();
$table->integer('available_at');
$table->integer('created_at');
});
}
public function down(): void
{
Schema::dropIfExists('jobs');
}
};

View File

@ -0,0 +1,26 @@
<?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('server_logs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->unsignedBigInteger('site_id')->nullable();
$table->string('type');
$table->string('name');
$table->string('disk');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('server_logs');
}
};

View File

@ -0,0 +1,35 @@
<?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('sites', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id')->index();
$table->string('type');
$table->json('type_data')->nullable();
$table->string('domain')->index();
$table->json('aliases')->nullable();
$table->string('web_directory')->nullable();
$table->string('path');
$table->string('php_version')->nullable();
$table->string('source_control')->nullable();
$table->string('repository')->nullable();
$table->string('branch')->nullable();
$table->integer('port')->nullable();
$table->string('status')->default('installing');
$table->integer('progress')->default(0)->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('sites');
}
};

View File

@ -0,0 +1,24 @@
<?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('source_controls', function (Blueprint $table) {
$table->id();
$table->string('provider');
$table->json('provider_data')->nullable();
$table->longText('access_token')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('source_controls');
}
};

View File

@ -0,0 +1,27 @@
<?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('deployments', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->unsignedBigInteger('deployment_script_id');
$table->unsignedInteger('log_id')->nullable();
$table->json('commit_data')->nullable();
$table->string('commit_id')->nullable();
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('deployments');
}
};

View File

@ -0,0 +1,25 @@
<?php
use App\Enums\DatabaseStatus;
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('databases', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->string('name');
$table->string('status')->default(DatabaseStatus::CREATING);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('databases');
}
};

View File

@ -0,0 +1,28 @@
<?php
use App\Enums\DatabaseUserStatus;
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('database_users', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->string('username');
$table->longText('password')->nullable();
$table->json('databases')->nullable();
$table->string('host')->default('localhost');
$table->string('status')->default(DatabaseUserStatus::CREATING);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('database_users');
}
};

View File

@ -0,0 +1,29 @@
<?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('firewall_rules', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->string('type');
$table->string('protocol');
$table->integer('port');
$table->ipAddress('source')->default('0.0.0.0');
$table->string('mask')->nullable();
$table->text('note')->nullable();
$table->string('status')->default('creating');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('firewall_rules');
}
};

View File

@ -0,0 +1,27 @@
<?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('cron_jobs', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->text('command');
$table->string('user');
$table->string('frequency');
$table->boolean('hidden')->default(0);
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('cron_jobs');
}
};

View File

@ -0,0 +1,24 @@
<?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('deployment_scripts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->string('name')->nullable();
$table->longText('content')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('deployment_scripts');
}
};

View File

@ -0,0 +1,29 @@
<?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('ssls', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->string('type')->default('letsencrypt');
$table->string('domains')->nullable();
$table->longText('certificate')->nullable();
$table->longText('pk')->nullable();
$table->longText('ca')->nullable();
$table->timestamp('expires_at');
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('ssls');
}
};

View File

@ -0,0 +1,26 @@
<?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('redirects', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->integer('mode');
$table->text('from');
$table->text('to');
$table->string('status')->default('creating');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('redirects');
}
};

View File

@ -0,0 +1,31 @@
<?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('queues', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('server_id')->nullable();
$table->unsignedBigInteger('site_id');
$table->text('command');
$table->string('user');
$table->boolean('auto_start')->default(1);
$table->boolean('auto_restart')->default(1);
$table->integer('numprocs')->default(8);
$table->boolean('redirect_stderr')->default(1);
$table->string('stdout_logfile')->nullable();
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('queues');
}
};

View File

@ -0,0 +1,24 @@
<?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('ssh_keys', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('name');
$table->longText('public_key');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('ssh_keys');
}
};

View File

@ -0,0 +1,24 @@
<?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('server_ssh_keys', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->unsignedBigInteger('ssh_key_id');
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('server_ssh_keys');
}
};

View File

@ -0,0 +1,28 @@
<?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('git_hooks', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('site_id');
$table->unsignedBigInteger('source_control_id');
$table->string('secret')->unique()->index();
$table->json('events');
$table->json('actions');
$table->string('hook_id')->nullable();
$table->json('hook_response')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('git_hooks');
}
};

View File

@ -0,0 +1,26 @@
<?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('server_providers', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('profile')->nullable();
$table->string('provider');
$table->longText('credentials');
$table->boolean('connected')->default(1);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('server_providers');
}
};

View File

@ -0,0 +1,24 @@
<?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('scripts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('name');
$table->longText('content');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('scripts');
}
};

View File

@ -0,0 +1,25 @@
<?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('script_executions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('script_id');
$table->unsignedBigInteger('server_id');
$table->string('user')->nullable();
$table->timestamp('finished_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('script_executions');
}
};

View File

@ -0,0 +1,26 @@
<?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('notification_channels', function (Blueprint $table) {
$table->id();
$table->string('provider');
$table->string('label');
$table->json('data')->nullable();
$table->boolean('connected')->default(false);
$table->boolean('is_default')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('notification_channels');
}
};

View File

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateStorageProvidersTable extends Migration
{
public function up(): void
{
Schema::create('storage_providers', function (Blueprint $table) {
$table->id();
$table->string('provider');
$table->string('label')->nullable();
$table->string('token', 1000)->nullable();
$table->string('refresh_token', 1000)->nullable();
$table->boolean('connected')->default(1);
$table->timestamp('token_expires_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('storage_providers');
}
}

View File

@ -0,0 +1,29 @@
<?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('backups', function (Blueprint $table) {
$table->id();
$table->string('type');
$table->string('name');
$table->unsignedBigInteger('server_id');
$table->unsignedBigInteger('storage_id');
$table->unsignedBigInteger('database_id')->nullable();
$table->string('interval');
$table->bigInteger('keep_backups');
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('backups');
}
};

View File

@ -0,0 +1,26 @@
<?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('backup_files', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('backup_id');
$table->string('name');
$table->bigInteger('size')->nullable();
$table->string('status');
$table->timestamp('restored_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('backup_files');
}
};

View File

@ -0,0 +1,19 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
if (DB::getDriverName() === 'mysql') {
DB::statement('ALTER TABLE firewall_rules MODIFY mask varchar(10) null');
}
}
public function down(): void
{
//
}
};

View File

@ -0,0 +1,22 @@
<?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::table('sites', function (Blueprint $table) {
$table->longText('ssh_key')->nullable()->after('repository');
});
}
public function down(): void
{
Schema::table('sites', function (Blueprint $table) {
$table->dropColumn('ssh_key');
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('source_controls', function (Blueprint $table) {
$table->string('url')->nullable()->after('provider');
});
}
public function down(): void
{
Schema::table('source_controls', function (Blueprint $table) {
$table->dropColumn('url');
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('source_controls', function (Blueprint $table) {
$table->string('profile')->after('provider')->nullable();
});
}
public function down(): void
{
Schema::table('source_controls', function (Blueprint $table) {
$table->dropColumn('profile');
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('sites', function (Blueprint $table) {
$table->unsignedBigInteger('source_control_id')->nullable()->after('source_control');
});
}
public function down(): void
{
Schema::table('sites', function (Blueprint $table) {
$table->dropColumn('source_control_id');
});
}
};

View File

@ -0,0 +1,33 @@
<?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::table('storage_providers', function (Blueprint $table) {
$table->unsignedBigInteger('user_id')->after('id');
$table->string('profile')->after('user_id');
$table->longText('credentials')->nullable()->after('provider');
});
Schema::table('storage_providers', function (Blueprint $table) {
$table->dropColumn(['token', 'refresh_token', 'token_expires_at', 'label', 'connected']);
});
}
public function down(): void
{
Schema::table('storage_providers', function (Blueprint $table) {
$table->string('token')->nullable();
$table->string('refresh_token')->nullable();
$table->string('token_expires_at')->nullable();
$table->string('label')->nullable();
});
Schema::table('storage_providers', function (Blueprint $table) {
$table->dropColumn(['user_id', 'profile', 'credentials']);
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('backups', function (Blueprint $table) {
$table->dropColumn('name');
});
}
public function down(): void
{
Schema::table('backups', function (Blueprint $table) {
$table->string('name')->nullable();
});
}
};

View File

@ -0,0 +1,22 @@
<?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::table('backup_files', function (Blueprint $table) {
$table->string('restored_to')->after('status')->nullable();
});
}
public function down(): void
{
Schema::table('backup_files', function (Blueprint $table) {
$table->dropColumn('restored_to');
});
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('two_factor_confirmed_at')
->after('two_factor_recovery_codes')
->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'two_factor_confirmed_at',
]);
});
}
};

View File

@ -0,0 +1,23 @@
<?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('projects', function (Blueprint $table) {
$table->id();
$table->bigInteger('user_id');
$table->string('name');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('projects');
}
};

View File

@ -0,0 +1,22 @@
<?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::table('servers', function (Blueprint $table) {
$table->unsignedBigInteger('project_id')->nullable()->after('id');
});
}
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('project_id');
});
}
};

View File

@ -0,0 +1,27 @@
<?php
use App\Models\User;
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::table('users', function (Blueprint $table) {
$table->unsignedBigInteger('current_project_id')->nullable()->after('timezone');
});
User::query()->each(function (User $user) {
$project = $user->createDefaultProject();
$user->servers()->update(['project_id' => $project->id]);
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('current_project_id');
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('server_logs', function (Blueprint $table) {
$table->boolean('is_remote')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('server_logs', function (Blueprint $table) {
$table->dropColumn('is_remote');
});
}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('metrics', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('server_id');
$table->decimal('load', 5, 2);
$table->decimal('memory_total', 15, 0);
$table->decimal('memory_used', 15, 0);
$table->decimal('memory_free', 15, 0);
$table->decimal('disk_total', 15, 0);
$table->decimal('disk_used', 15, 0);
$table->decimal('disk_free', 15, 0);
$table->timestamps();
$table->index(['server_id', 'created_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('metrics');
}
};

View File

@ -0,0 +1,31 @@
<?php
use App\Enums\UserRole;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('role')->default(UserRole::USER);
});
User::query()->update(['role' => UserRole::ADMIN]);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};

View File

@ -0,0 +1,38 @@
<?php
use App\Models\Project;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_project', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('project_id');
$table->timestamps();
});
Project::all()->each(function (Project $project) {
$project->users()->attach($project->user_id);
});
User::all()->each(function (User $user) {
$user->current_project_id = $user->projects()->first()?->id;
$user->save();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_project');
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('projects', function (Blueprint $table) {
$table->dropColumn('user_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('projects', function (Blueprint $table) {
$table->bigInteger('user_id')->nullable();
});
}
};

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('source_controls', function (Blueprint $table) {
$table->unsignedBigInteger('project_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('source_controls', function (Blueprint $table) {
$table->dropColumn('project_id');
});
}
};

View File

@ -0,0 +1,24 @@
<?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::table('servers', function (Blueprint $table) {
$table->integer('updates')->default(0);
$table->timestamp('last_update_check')->nullable();
});
}
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('updates');
$table->dropColumn('last_update_check');
});
}
};

View File

@ -0,0 +1,28 @@
<?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::dropIfExists('script_executions');
Schema::create('script_executions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('script_id');
$table->unsignedBigInteger('server_log_id')->nullable();
$table->string('user');
$table->json('variables')->nullable();
$table->string('status');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('script_executions');
}
};

View File

@ -0,0 +1,24 @@
<?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');
}
};

View File

@ -0,0 +1,24 @@
<?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');
}
};

View File

@ -1,108 +0,0 @@
CREATE TABLE IF NOT EXISTS "migrations" ("id" integer primary key autoincrement not null, "migration" varchar not null, "batch" integer not null);
CREATE TABLE IF NOT EXISTS "users" ("id" integer primary key autoincrement not null, "name" varchar not null, "email" varchar not null, "password" varchar not null, "remember_token" varchar, "profile_photo_path" varchar, "two_factor_secret" text, "two_factor_recovery_codes" text, "timezone" varchar default 'UTC', "created_at" datetime, "updated_at" datetime, "two_factor_confirmed_at" datetime, "current_project_id" integer, "role" varchar not null default 'user');
CREATE UNIQUE INDEX "users_email_unique" on "users" ("email");
CREATE TABLE IF NOT EXISTS "password_reset_tokens" ("email" varchar not null, "token" varchar not null, "created_at" datetime);
CREATE INDEX "password_reset_tokens_email_index" on "password_reset_tokens" ("email");
CREATE TABLE IF NOT EXISTS "telescope_entries" ("sequence" integer primary key autoincrement not null, "uuid" varchar not null, "batch_id" varchar not null, "family_hash" varchar, "should_display_on_index" tinyint(1) not null default '1', "type" varchar not null, "content" text not null, "created_at" datetime);
CREATE UNIQUE INDEX "telescope_entries_uuid_unique" on "telescope_entries" ("uuid");
CREATE INDEX "telescope_entries_batch_id_index" on "telescope_entries" ("batch_id");
CREATE INDEX "telescope_entries_family_hash_index" on "telescope_entries" ("family_hash");
CREATE INDEX "telescope_entries_created_at_index" on "telescope_entries" ("created_at");
CREATE INDEX "telescope_entries_type_should_display_on_index_index" on "telescope_entries" ("type", "should_display_on_index");
CREATE TABLE IF NOT EXISTS "telescope_entries_tags" ("entry_uuid" varchar not null, "tag" varchar not null, foreign key("entry_uuid") references "telescope_entries"("uuid") on delete cascade, primary key ("entry_uuid", "tag"));
CREATE INDEX "telescope_entries_tags_tag_index" on "telescope_entries_tags" ("tag");
CREATE TABLE IF NOT EXISTS "telescope_monitoring" ("tag" varchar not null, primary key ("tag"));
CREATE TABLE IF NOT EXISTS "failed_jobs" ("id" integer primary key autoincrement not null, "uuid" varchar not null, "connection" text not null, "queue" text not null, "payload" text not null, "exception" text not null, "failed_at" datetime not null default CURRENT_TIMESTAMP);
CREATE UNIQUE INDEX "failed_jobs_uuid_unique" on "failed_jobs" ("uuid");
CREATE TABLE IF NOT EXISTS "personal_access_tokens" ("id" integer primary key autoincrement not null, "tokenable_type" varchar not null, "tokenable_id" integer not null, "name" varchar not null, "token" varchar not null, "abilities" text, "last_used_at" datetime, "expires_at" datetime, "created_at" datetime, "updated_at" datetime);
CREATE INDEX "personal_access_tokens_tokenable_type_tokenable_id_index" on "personal_access_tokens" ("tokenable_type", "tokenable_id");
CREATE UNIQUE INDEX "personal_access_tokens_token_unique" on "personal_access_tokens" ("token");
CREATE TABLE IF NOT EXISTS "sessions" ("id" varchar not null, "user_id" integer, "ip_address" varchar, "user_agent" text, "payload" text not null, "last_activity" integer not null, primary key ("id"));
CREATE INDEX "sessions_user_id_index" on "sessions" ("user_id");
CREATE INDEX "sessions_last_activity_index" on "sessions" ("last_activity");
CREATE TABLE IF NOT EXISTS "servers" ("id" integer primary key autoincrement not null, "user_id" integer not null, "name" varchar not null, "ssh_user" varchar, "ip" varchar, "local_ip" varchar, "provider_id" integer, "port" integer not null default '22', "os" varchar not null, "type" varchar not null, "type_data" text, "provider" varchar not null, "provider_data" text, "authentication" text, "public_key" text, "status" varchar not null default 'installing', "progress" integer not null default '0', "progress_step" varchar, "created_at" datetime, "updated_at" datetime, "project_id" integer, "updates" integer not null default '0', "last_update_check" datetime);
CREATE INDEX "servers_name_index" on "servers" ("name");
CREATE INDEX "servers_ip_index" on "servers" ("ip");
CREATE TABLE IF NOT EXISTS "services" ("id" integer primary key autoincrement not null, "server_id" integer not null, "type" varchar not null, "type_data" text, "name" varchar not null, "version" varchar not null, "status" varchar not null default 'installing', "is_default" tinyint(1) not null default '1', "unit" varchar, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "jobs" ("id" integer primary key autoincrement not null, "queue" varchar not null, "payload" text not null, "attempts" integer not null, "reserved_at" integer, "available_at" integer not null, "created_at" integer not null);
CREATE INDEX "jobs_queue_index" on "jobs" ("queue");
CREATE TABLE IF NOT EXISTS "server_logs" ("id" integer primary key autoincrement not null, "server_id" integer not null, "site_id" integer, "type" varchar not null, "name" varchar not null, "disk" varchar not null, "created_at" datetime, "updated_at" datetime, "is_remote" tinyint(1) not null default '0');
CREATE TABLE IF NOT EXISTS "sites" ("id" integer primary key autoincrement not null, "server_id" integer not null, "type" varchar not null, "type_data" text, "domain" varchar not null, "aliases" text, "web_directory" varchar, "path" varchar not null, "php_version" varchar, "source_control" varchar, "repository" varchar, "branch" varchar, "port" integer, "status" varchar not null default 'installing', "progress" integer default '0', "created_at" datetime, "updated_at" datetime, "ssh_key" text, "source_control_id" integer);
CREATE INDEX "sites_server_id_index" on "sites" ("server_id");
CREATE INDEX "sites_domain_index" on "sites" ("domain");
CREATE TABLE IF NOT EXISTS "source_controls" ("id" integer primary key autoincrement not null, "provider" varchar not null, "provider_data" text, "access_token" text, "created_at" datetime, "updated_at" datetime, "url" varchar, "profile" varchar, "project_id" integer);
CREATE TABLE IF NOT EXISTS "deployments" ("id" integer primary key autoincrement not null, "site_id" integer not null, "deployment_script_id" integer not null, "log_id" integer, "commit_data" text, "commit_id" varchar, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "databases" ("id" integer primary key autoincrement not null, "server_id" integer not null, "name" varchar not null, "status" varchar not null default 'creating', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "database_users" ("id" integer primary key autoincrement not null, "server_id" integer not null, "username" varchar not null, "password" text, "databases" text, "host" varchar not null default 'localhost', "status" varchar not null default 'creating', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "firewall_rules" ("id" integer primary key autoincrement not null, "server_id" integer not null, "type" varchar not null, "protocol" varchar not null, "port" integer not null, "source" varchar not null default '0.0.0.0', "mask" varchar, "note" text, "status" varchar not null default 'creating', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "cron_jobs" ("id" integer primary key autoincrement not null, "server_id" integer not null, "command" text not null, "user" varchar not null, "frequency" varchar not null, "hidden" tinyint(1) not null default '0', "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "deployment_scripts" ("id" integer primary key autoincrement not null, "site_id" integer not null, "name" varchar, "content" text, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "ssls" ("id" integer primary key autoincrement not null, "site_id" integer not null, "type" varchar not null default 'letsencrypt', "domains" varchar, "certificate" text, "pk" text, "ca" text, "expires_at" datetime not null, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "redirects" ("id" integer primary key autoincrement not null, "site_id" integer not null, "mode" integer not null, "from" text not null, "to" text not null, "status" varchar not null default 'creating', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "queues" ("id" integer primary key autoincrement not null, "server_id" integer, "site_id" integer not null, "command" text not null, "user" varchar not null, "auto_start" tinyint(1) not null default '1', "auto_restart" tinyint(1) not null default '1', "numprocs" integer not null default '8', "redirect_stderr" tinyint(1) not null default '1', "stdout_logfile" varchar, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "ssh_keys" ("id" integer primary key autoincrement not null, "user_id" integer not null, "name" varchar not null, "public_key" text not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "server_ssh_keys" ("id" integer primary key autoincrement not null, "server_id" integer not null, "ssh_key_id" integer not null, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "git_hooks" ("id" integer primary key autoincrement not null, "site_id" integer not null, "source_control_id" integer not null, "secret" varchar not null, "events" text not null, "actions" text not null, "hook_id" varchar, "hook_response" text, "created_at" datetime, "updated_at" datetime);
CREATE UNIQUE INDEX "git_hooks_secret_unique" on "git_hooks" ("secret");
CREATE TABLE IF NOT EXISTS "server_providers" ("id" integer primary key autoincrement not null, "user_id" integer not null, "profile" varchar, "provider" varchar not null, "credentials" text not null, "connected" tinyint(1) not null default '1', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "scripts" ("id" integer primary key autoincrement not null, "user_id" integer not null, "name" varchar not null, "content" text not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "notification_channels" ("id" integer primary key autoincrement not null, "provider" varchar not null, "label" varchar not null, "data" text, "connected" tinyint(1) not null default '0', "is_default" tinyint(1) not null default '0', "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "storage_providers" ("id" integer primary key autoincrement not null, "provider" varchar not null, "created_at" datetime, "updated_at" datetime, "user_id" integer not null, "profile" varchar not null, "credentials" text);
CREATE TABLE IF NOT EXISTS "backups" ("id" integer primary key autoincrement not null, "type" varchar not null, "server_id" integer not null, "storage_id" integer not null, "database_id" integer, "interval" varchar not null, "keep_backups" integer not null, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "backup_files" ("id" integer primary key autoincrement not null, "backup_id" integer not null, "name" varchar not null, "size" integer, "status" varchar not null, "restored_at" datetime, "created_at" datetime, "updated_at" datetime, "restored_to" varchar);
CREATE TABLE IF NOT EXISTS "projects" ("id" integer primary key autoincrement not null, "name" varchar not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "metrics" ("id" integer primary key autoincrement not null, "server_id" integer not null, "load" numeric not null, "memory_total" numeric not null, "memory_used" numeric not null, "memory_free" numeric not null, "disk_total" numeric not null, "disk_used" numeric not null, "disk_free" numeric not null, "created_at" datetime, "updated_at" datetime);
CREATE INDEX "metrics_server_id_created_at_index" on "metrics" ("server_id", "created_at");
CREATE TABLE IF NOT EXISTS "user_project" ("id" integer primary key autoincrement not null, "user_id" integer not null, "project_id" integer not null, "created_at" datetime, "updated_at" datetime);
CREATE TABLE IF NOT EXISTS "script_executions" ("id" integer primary key autoincrement not null, "script_id" integer not null, "server_log_id" integer, "user" varchar not null, "variables" text, "status" varchar not null, "created_at" datetime, "updated_at" datetime);
INSERT INTO migrations VALUES(147,'2014_10_12_000000_create_users_table',1);
INSERT INTO migrations VALUES(148,'2014_10_12_100000_create_password_resets_table',1);
INSERT INTO migrations VALUES(149,'2018_08_08_100000_create_telescope_entries_table',1);
INSERT INTO migrations VALUES(150,'2019_08_19_000000_create_failed_jobs_table',1);
INSERT INTO migrations VALUES(151,'2019_12_14_000001_create_personal_access_tokens_table',1);
INSERT INTO migrations VALUES(152,'2021_06_23_192743_create_sessions_table',1);
INSERT INTO migrations VALUES(153,'2021_06_23_211827_create_servers_table',1);
INSERT INTO migrations VALUES(154,'2021_06_23_214143_create_services_table',1);
INSERT INTO migrations VALUES(155,'2021_06_25_102220_create_jobs_table',1);
INSERT INTO migrations VALUES(156,'2021_06_25_124831_create_server_logs_table',1);
INSERT INTO migrations VALUES(157,'2021_06_26_211903_create_sites_table',1);
INSERT INTO migrations VALUES(158,'2021_06_28_085814_create_source_controls_table',1);
INSERT INTO migrations VALUES(159,'2021_07_02_065815_create_deployments_table',1);
INSERT INTO migrations VALUES(160,'2021_07_03_133319_create_databases_table',1);
INSERT INTO migrations VALUES(161,'2021_07_03_133327_create_database_users_table',1);
INSERT INTO migrations VALUES(162,'2021_07_15_090830_create_firewall_rules_table',1);
INSERT INTO migrations VALUES(163,'2021_07_30_204454_create_cron_jobs_table',1);
INSERT INTO migrations VALUES(164,'2021_08_13_213657_create_deployment_scripts_table',1);
INSERT INTO migrations VALUES(165,'2021_08_14_165326_create_ssls_table',1);
INSERT INTO migrations VALUES(166,'2021_08_26_055643_create_redirects_table',1);
INSERT INTO migrations VALUES(167,'2021_08_27_064512_create_queues_table',1);
INSERT INTO migrations VALUES(168,'2021_08_29_210204_create_ssh_keys_table',1);
INSERT INTO migrations VALUES(169,'2021_08_30_174511_create_server_ssh_keys_table',1);
INSERT INTO migrations VALUES(170,'2021_11_12_093030_create_git_hooks_table',1);
INSERT INTO migrations VALUES(171,'2021_11_14_190808_create_server_providers_table',1);
INSERT INTO migrations VALUES(172,'2021_12_09_062430_create_scripts_table',1);
INSERT INTO migrations VALUES(173,'2021_12_10_204458_create_script_executions_table',1);
INSERT INTO migrations VALUES(174,'2021_12_24_151835_create_notification_channels_table',1);
INSERT INTO migrations VALUES(175,'2022_01_29_183900_create_storage_providers_table',1);
INSERT INTO migrations VALUES(176,'2022_02_11_085718_create_backups_table',1);
INSERT INTO migrations VALUES(177,'2022_02_11_085815_create_backup_files_table',1);
INSERT INTO migrations VALUES(178,'2023_07_21_210213_update_firewall_rules_table',1);
INSERT INTO migrations VALUES(179,'2023_07_23_143530_add_ssh_key_field_to_sites_table',1);
INSERT INTO migrations VALUES(180,'2023_07_30_163805_add_url_to_source_controls_table',1);
INSERT INTO migrations VALUES(181,'2023_07_30_200348_add_profile_to_source_controls_table',1);
INSERT INTO migrations VALUES(182,'2023_07_30_205328_add_source_control_id_to_sites_table',1);
INSERT INTO migrations VALUES(183,'2023_08_13_095440_update_storage_providers_table',1);
INSERT INTO migrations VALUES(184,'2023_08_17_231824_update_backups_table',1);
INSERT INTO migrations VALUES(185,'2023_08_25_183201_update_backup_files_table',1);
INSERT INTO migrations VALUES(186,'2023_09_10_185414_add_two_factor_fields_to_users_table',1);
INSERT INTO migrations VALUES(187,'2023_10_01_120250_create_projects_table',1);
INSERT INTO migrations VALUES(188,'2024_01_01_232932_update_servers_table',1);
INSERT INTO migrations VALUES(189,'2024_01_01_235900_update_users_table',1);
INSERT INTO migrations VALUES(190,'2024_04_07_204001_add_is_remote_to_server_logs_table',1);
INSERT INTO migrations VALUES(191,'2024_04_08_212940_create_metrics_table',1);
INSERT INTO migrations VALUES(192,'2024_04_24_213204_add_role_to_users_table',1);
INSERT INTO migrations VALUES(193,'2024_04_26_122230_create_user_project_table',1);
INSERT INTO migrations VALUES(194,'2024_04_26_123326_drop_user_id_from_projects_table',1);
INSERT INTO migrations VALUES(195,'2024_05_07_184201_add_project_id_to_source_controls_table',2);
INSERT INTO migrations VALUES(197,'2024_05_10_212155_add_updates_field_to_servers_table',3);
INSERT INTO migrations VALUES(198,'2024_06_06_093350_create_script_executions_table',4);

View File

@ -8,9 +8,12 @@
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.
*/ */
@ -20,6 +23,12 @@ 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,

8
package-lock.json generated
View File

@ -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",
@ -685,6 +685,12 @@
"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",

View File

@ -10,8 +10,11 @@
"@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",
@ -21,8 +24,6 @@
"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.3", "vite": "^4.5.3"
"apexcharts": "^3.44.2",
"flowbite-datepicker": "^1.2.6"
} }
} }

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

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"resources/css/app.css": { "resources/css/app.css": {
"file": "assets/app-7f487305.css", "file": "assets/app-888ea5fa.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-01264060.js", "file": "assets/app-d9a3bf01.js",
"isEntry": true, "isEntry": true,
"src": "resources/js/app.js" "src": "resources/js/app.js"
} }

View File

@ -0,0 +1,40 @@
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;
}

View File

@ -0,0 +1,136 @@
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

View File

@ -0,0 +1,47 @@
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);
},
);

View File

@ -1,5 +1,6 @@
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;

View File

@ -16,9 +16,8 @@ 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')" />
<x-textarea id="script" name="script" class="mt-1 min-h-[400px] w-full font-mono"> @php($value = old("script", $site->deploymentScript?->content))
{{ old("script", $site->deploymentScript?->content) }} <x-editor id="script" name="script" lang="sh" :value="$value" />
</x-textarea>
@error("script") @error("script")
<x-input-error class="mt-2" :messages="$message" /> <x-input-error class="mt-2" :messages="$message" />
@enderror @enderror

View File

@ -21,9 +21,8 @@ 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">
<x-textarea id="env" name="env" rows="10" class="mt-1 block min-h-[400px] w-full font-mono"> @php($envValue = old("env", session()->get("env") ?? "Loading..."))
{{ old("env", session()->get("env") ?? "Loading...") }} <x-editor id="env" name="env" lang="env" :value="$envValue" />
</x-textarea>
</div> </div>
@error("env") @error("env")
<x-input-error class="mt-2" :messages="$message" /> <x-input-error class="mt-2" :messages="$message" />

View File

@ -0,0 +1,64 @@
@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>

View File

@ -0,0 +1,10 @@
<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>

View File

@ -2,12 +2,18 @@
"open" => false, "open" => false,
"align" => "right", "align" => "right",
"width" => "48", "width" => "48",
"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", "contentClasses" => "list-none divide-y divide-gray-100 rounded-md bg-white text-base dark:divide-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";
@ -25,6 +31,9 @@
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;
@ -46,31 +55,9 @@
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"
@click="open = false" @if ($closeOnClick) @click="open = false" @endif
> >
<div class="{{ $contentClasses }} 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>

View File

@ -0,0 +1,17 @@
<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>

View File

@ -0,0 +1,14 @@
<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>

After

Width:  |  Height:  |  Size: 556 B

Some files were not shown because too many files have changed in this diff Show More