mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 14:06:15 +00:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
e9016737d4 | |||
f34d5eb82b | |||
12c500e125 | |||
2d566b853f | |||
ca93b521ec | |||
a0af4e3e9d |
@ -34,8 +34,6 @@ public function create(Site $site, array $input): void
|
||||
$ssl->status = SslStatus::CREATED;
|
||||
$ssl->save();
|
||||
$site->type()->edit();
|
||||
})->catch(function () use ($ssl) {
|
||||
$ssl->delete();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Enums\SiteStatus;
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Facades\Notifier;
|
||||
use App\Models\Server;
|
||||
@ -19,7 +21,6 @@
|
||||
class CreateSite
|
||||
{
|
||||
/**
|
||||
* @throws SourceControlIsNotConnected
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function create(Server $server, array $input): Site
|
||||
@ -47,7 +48,15 @@ public function create(Server $server, array $input): Site
|
||||
}
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
throw ValidationException::withMessages([
|
||||
'source_control' => __('Source control is not connected'),
|
||||
'source_control' => 'Source control is not connected',
|
||||
]);
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'You do not have permission to access this repository',
|
||||
]);
|
||||
} catch (RepositoryNotFound) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'Repository not found',
|
||||
]);
|
||||
}
|
||||
|
||||
|
49
app/Actions/Site/UpdateSourceControl.php
Normal file
49
app/Actions/Site/UpdateSourceControl.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Models\Site;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class UpdateSourceControl
|
||||
{
|
||||
public function update(Site $site, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$site->source_control_id = $input['source_control'];
|
||||
try {
|
||||
if ($site->sourceControl()) {
|
||||
$site->sourceControl()->getRepo($site->repository);
|
||||
}
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
throw ValidationException::withMessages([
|
||||
'source_control' => 'Source control is not connected',
|
||||
]);
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'You do not have permission to access this repository',
|
||||
]);
|
||||
} catch (RepositoryNotFound) {
|
||||
throw ValidationException::withMessages([
|
||||
'repository' => 'Repository not found',
|
||||
]);
|
||||
}
|
||||
$site->save();
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'source_control' => [
|
||||
'required',
|
||||
Rule::exists('source_controls', 'id'),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SSHAuthenticationError extends Exception
|
||||
class SSHAuthenticationError extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SSLCreationException extends Exception
|
||||
class SSLCreationException extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
$this->log?->write($output);
|
||||
|
||||
if (Str::contains($output, 'VITO_SSH_ERROR')) {
|
||||
throw new Exception('SSH command failed with an error');
|
||||
throw new SSHCommandError('SSH command failed with an error');
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
@ -7,6 +7,8 @@
|
||||
use App\Actions\Site\UpdateDeploymentScript;
|
||||
use App\Actions\Site\UpdateEnv;
|
||||
use App\Exceptions\DeploymentScriptIsEmptyException;
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
@ -24,12 +26,14 @@ public function deploy(Server $server, Site $site): HtmxResponse
|
||||
app(Deploy::class)->run($site);
|
||||
|
||||
Toast::success('Deployment started!');
|
||||
} catch (SourceControlIsNotConnected $e) {
|
||||
Toast::error($e->getMessage());
|
||||
|
||||
return htmx()->redirect(route('source-controls'));
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
Toast::error('Source control is not connected. Check site\'s settings.');
|
||||
} catch (DeploymentScriptIsEmptyException) {
|
||||
Toast::error('Deployment script is empty!');
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
Toast::error('You do not have permission to access this repository!');
|
||||
} catch (RepositoryNotFound) {
|
||||
Toast::error('Repository not found!');
|
||||
}
|
||||
|
||||
return htmx()->back();
|
||||
@ -83,6 +87,12 @@ public function enableAutoDeployment(Server $server, Site $site): HtmxResponse
|
||||
Toast::success('Auto deployment has been enabled.');
|
||||
} catch (SourceControlIsNotConnected) {
|
||||
Toast::error('Source control is not connected. Check site\'s settings.');
|
||||
} catch (DeploymentScriptIsEmptyException) {
|
||||
Toast::error('Deployment script is empty!');
|
||||
} catch (RepositoryPermissionDenied) {
|
||||
Toast::error('You do not have permission to access this repository!');
|
||||
} catch (RepositoryNotFound) {
|
||||
Toast::error('Repository not found!');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\Site\CreateSite;
|
||||
use App\Actions\Site\DeleteSite;
|
||||
use App\Enums\SiteStatus;
|
||||
use App\Enums\SiteType;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
@ -42,14 +43,38 @@ public function create(Server $server): View
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Server $server, Site $site): View
|
||||
public function show(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||
{
|
||||
if (in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||
if ($request->hasHeader('HX-Request')) {
|
||||
return htmx()->redirect(route('servers.sites.installing', [$server, $site]));
|
||||
}
|
||||
|
||||
return redirect()->route('servers.sites.installing', [$server, $site]);
|
||||
}
|
||||
|
||||
return view('sites.show', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
]);
|
||||
}
|
||||
|
||||
public function installing(Server $server, Site $site, Request $request): View|RedirectResponse|HtmxResponse
|
||||
{
|
||||
if (! in_array($site->status, [SiteStatus::INSTALLING, SiteStatus::INSTALLATION_FAILED])) {
|
||||
if ($request->hasHeader('HX-Request')) {
|
||||
return htmx()->redirect(route('servers.sites.show', [$server, $site]));
|
||||
}
|
||||
|
||||
return redirect()->route('servers.sites.show', [$server, $site]);
|
||||
}
|
||||
|
||||
return view('sites.installing', [
|
||||
'server' => $server,
|
||||
'site' => $site,
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Server $server, Site $site): RedirectResponse
|
||||
{
|
||||
app(DeleteSite::class)->delete($site);
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Site\UpdateSourceControl;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
@ -63,4 +64,13 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
public function updateSourceControl(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$site = app(UpdateSourceControl::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Source control updated successfully!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
}
|
||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -20,7 +20,7 @@
|
||||
"tailwindcss": "^3.1.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"toastr": "^2.1.4",
|
||||
"vite": "^4.5.2"
|
||||
"vite": "^4.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
@ -1812,9 +1812,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
@ -3011,9 +3011,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.18.10",
|
||||
|
@ -22,6 +22,6 @@
|
||||
"tailwindcss": "^3.1.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
"toastr": "^2.1.4",
|
||||
"vite": "^4.5.2"
|
||||
"vite": "^4.5.3"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -12,7 +12,7 @@
|
||||
"css": [
|
||||
"assets/app-a1ae07b3.css"
|
||||
],
|
||||
"file": "assets/app-5f99a92f.js",
|
||||
"file": "assets/app-4c0bf3b2.js",
|
||||
"isEntry": true,
|
||||
"src": "resources/js/app.js"
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
ace.define("ace/mode/sh", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/tokenizer", "ace/mode/sh_highlight_rules", "ace/range"], function (e, t, n) { var r = e("../lib/oop"), i = e("./text").Mode, s = e("../tokenizer").Tokenizer, o = e("./sh_highlight_rules").ShHighlightRules, u = e("../range").Range, a = function () { this.$tokenizer = new s((new o).getRules()) }; r.inherits(a, i), function () { this.toggleCommentLines = function (e, t, n, r) { var i = !0, s = /^(\s*)#/; for (var o = n; o <= r; o++)if (!s.test(t.getLine(o))) { i = !1; break } if (i) { var a = new u(0, 0, 0, 0); for (var o = n; o <= r; o++) { var f = t.getLine(o), l = f.match(s); a.start.row = o, a.end.row = o, a.end.column = l[0].length, t.replace(a, l[1]) } } else t.indentRows(n, r, "#") }, this.getNextLineIndent = function (e, t, n) { var r = this.$getIndent(t), i = this.$tokenizer.getLineTokens(t, e), s = i.tokens; if (s.length && s[s.length - 1].type == "comment") return r; if (e == "start") { var o = t.match(/^.*[\{\(\[\:]\s*$/); o && (r += n) } return r }; var e = { pass: 1, "return": 1, raise: 1, "break": 1, "continue": 1 }; this.checkOutdent = function (t, n, r) { if (r !== "\r\n" && r !== "\r" && r !== "\n") return !1; var i = this.$tokenizer.getLineTokens(n.trim(), t).tokens; if (!i) return !1; do var s = i.pop(); while (s && (s.type == "comment" || s.type == "text" && s.value.match(/^\s+$/))); return s ? s.type == "keyword" && e[s.value] : !1 }, this.autoOutdent = function (e, t, n) { n += 1; var r = this.$getIndent(t.getLine(n)), i = t.getTabString(); r.slice(-i.length) == i && t.remove(new u(n, r.length - i.length, n, r.length)) } }.call(a.prototype), t.Mode = a }), ace.define("ace/mode/sh_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (e, t, n) { var r = e("../lib/oop"), i = e("./text_highlight_rules").TextHighlightRules, s = t.reservedKeywords = "!|{|}|case|do|done|elif|else|esac|fi|for|if|in|then|until|while|&|;|export|local|read|typeset|unset|elif|select|set", o = t.languageConstructs = "[|]|alias|bg|bind|break|builtin|cd|command|compgen|complete|continue|dirs|disown|echo|enable|eval|exec|exit|fc|fg|getopts|hash|help|history|jobs|kill|let|logout|popd|printf|pushd|pwd|return|set|shift|shopt|source|suspend|test|times|trap|type|ulimit|umask|unalias|wait", u = function () { var e = this.createKeywordMapper({ keyword: s, "support.function.builtin": o, "invalid.deprecated": "debugger" }, "identifier"), t = "(?:(?:[1-9]\\d*)|(?:0))", n = "(?:\\.\\d+)", r = "(?:\\d+)", i = "(?:(?:" + r + "?" + n + ")|(?:" + r + "\\.))", u = "(?:(?:" + i + "|" + r + ")" + ")", a = "(?:" + u + "|" + i + ")", f = "(?:&" + r + ")", l = "[a-zA-Z][a-zA-Z0-9_]*", c = "(?:(?:\\$" + l + ")|(?:" + l + "=))", h = "(?:\\$(?:SHLVL|\\$|\\!|\\?))", p = "(?:" + l + "\\s*\\(\\))"; this.$rules = { start: [{ token: "comment", regex: "#.*$" }, { token: "string", regex: '"(?:[^\\\\]|\\\\.)*?"' }, { token: "variable.language", regex: h }, { token: "variable", regex: c }, { token: "support.function", regex: p }, { token: "support.function", regex: f }, { token: "string", regex: "'(?:[^\\\\]|\\\\.)*?'" }, { token: "constant.numeric", regex: a }, { token: "constant.numeric", regex: t + "\\b" }, { token: e, regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b" }, { token: "keyword.operator", regex: "\\+|\\-|\\*|\\*\\*|\\/|\\/\\/|~|<|>|<=|=>|=|!=" }, { token: "paren.lparen", regex: "[\\[\\(\\{]" }, { token: "paren.rparen", regex: "[\\]\\)\\}]" }, { token: "text", regex: "\\s+" }] } }; r.inherits(u, i), t.ShHighlightRules = u })
|
@ -1,5 +0,0 @@
|
||||
ace.define("ace/theme/github", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) {
|
||||
t.isDark = !1, t.cssClass = "ace-github", t.cssText = '/* CSS style content from github\'s default pygments highlighter template.Cursor and selection styles from textmate.css. */.ace-github .ace_gutter {background: #e8e8e8;color: #AAA;}.ace-github .ace_scroller {background: #fff;}.ace-github .ace_keyword {font-weight: bold;}.ace-github .ace_string {color: #D14;}.ace-github .ace_variable.ace_class {color: teal;}.ace-github .ace_constant.ace_numeric {color: #099;}.ace-github .ace_constant.ace_buildin {color: #0086B3;}.ace-github .ace_support.ace_function {color: #0086B3;}.ace-github .ace_comment {color: #998;font-style: italic;}.ace-github .ace_variable.ace_language {color: #0086B3;}.ace-github .ace_paren {font-weight: bold;}.ace-github .ace_boolean {font-weight: bold;}.ace-github .ace_string.ace_regexp {color: #009926;font-weight: normal;}.ace-github .ace_variable.ace_instance {color: teal;}.ace-github .ace_constant.ace_language {font-weight: bold;}.ace-github .ace_text-layer {}.ace-github .ace_cursor {border-left: 2px solid black;}.ace-github .ace_overwrite-cursors .ace_cursor {border-left: 0px;border-bottom: 1px solid black;}.ace-github .ace_marker-layer .ace_active-line {background: rgb(255, 255, 204);}.ace-github .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-github.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;border-radius: 2px;}/* bold keywords cause cursor issues for some fonts *//* this disables bold style for editor and keeps for static highlighter */.ace-github.ace_nobold .ace_line > span {font-weight: normal !important;}.ace-github .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-github .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-github .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-github .ace_gutter-active-line {background-color : rgba(0, 0, 0, 0.07);}.ace-github .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-github .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-github .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';
|
||||
var r = e("../lib/dom");
|
||||
r.importCssString(t.cssText, t.cssClass)
|
||||
})
|
@ -1,5 +0,0 @@
|
||||
ace.define("ace/theme/one-dark", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) {
|
||||
t.isDark = !1, t.cssClass = "ace-one-dark", t.cssText = '/* CSS style content from one-dark\'s default pygments highlighter template.Cursor and selection styles from textmate.css. */.ace-one-dark .ace_gutter{background:#282c34;color:#6a6f7a}.ace-one-dark .ace_print-margin{width:1px;background:#e8e8e8}.ace-one-dark{background-color:#282c34;color:#abb2bf}.ace-one-dark .ace_cursor{color:#528bff}.ace-one-dark .ace_marker-layer .ace_selection{background:#3d4350}.ace-one-dark.ace_multiselect .ace_selection.ace_start{box-shadow:0 0 3px 0 #282c34;border-radius:2px}.ace-one-dark .ace_marker-layer .ace_step{background:#c6dbae}.ace-one-dark .ace_marker-layer .ace_bracket{margin:-1px 0 0 -1px;border:1px solid #747369}.ace-one-dark .ace_marker-layer .ace_active-line{background:rgba(76,87,103,.19)}.ace-one-dark .ace_gutter-active-line{background-color:rgba(76,87,103,.19)}.ace-one-dark .ace_marker-layer .ace_selected-word{border:1px solid #3d4350}.ace-one-dark .ace_fold{background-color:#61afef;border-color:#abb2bf}.ace-one-dark .ace_keyword{color:#c678dd}.ace-one-dark .ace_keyword.ace_operator{color:#c678dd}.ace-one-dark .ace_keyword.ace_other.ace_unit{color:#d19a66}.ace-one-dark .ace_constant.ace_language{color:#d19a66}.ace-one-dark .ace_constant.ace_numeric{color:#d19a66}.ace-one-dark .ace_constant.ace_character{color:#56b6c2}.ace-one-dark .ace_constant.ace_other{color:#56b6c2}.ace-one-dark .ace_support.ace_function{color:#61afef}.ace-one-dark .ace_support.ace_constant{color:#d19a66}.ace-one-dark .ace_support.ace_class{color:#e5c07b}.ace-one-dark .ace_support.ace_type{color:#e5c07b}.ace-one-dark .ace_storage{color:#c678dd}.ace-one-dark .ace_storage.ace_type{color:#c678dd}.ace-one-dark .ace_invalid{color:#fff;background-color:#f2777a}.ace-one-dark .ace_invalid.ace_deprecated{color:#272b33;background-color:#d27b53}.ace-one-dark .ace_string{color:#98c379}.ace-one-dark .ace_string.ace_regexp{color:#e06c75}.ace-one-dark .ace_comment{font-style:italic;color:#5c6370}.ace-one-dark .ace_variable{color:#e06c75}.ace-one-dark .ace_variable.ace_parameter{color:#d19a66}.ace-one-dark .ace_meta.ace_tag{color:#e06c75}.ace-one-dark .ace_entity.ace_other.ace_attribute-name{color:#e06c75}.ace-one-dark .ace_entity.ace_name.ace_function{color:#61afef}.ace-one-dark .ace_entity.ace_name.ace_tag{color:#e06c75}.ace-one-dark .ace_markup.ace_heading{color:#98c379}.ace-one-dark .ace_indent-guide{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ09NrYAgMjP4PAAtGAwchHMyAAAAAAElFTkSuQmCC) right repeat-y}';
|
||||
var r = e("../lib/dom");
|
||||
r.importCssString(t.cssText, t.cssClass)
|
||||
})
|
@ -23,12 +23,10 @@ window.htmx.defineExtension('disable-element', {
|
||||
});
|
||||
document.body.addEventListener('htmx:configRequest', (event) => {
|
||||
event.detail.headers['X-CSRF-TOKEN'] = document.head.querySelector('meta[name="csrf-token"]').content;
|
||||
if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
||||
else if (document.selection) { document.selection.empty(); }
|
||||
// if (window.getSelection) { window.getSelection().removeAllRanges(); }
|
||||
// else if (document.selection) { document.selection.empty(); }
|
||||
});
|
||||
let activeElement = null;
|
||||
document.body.addEventListener('htmx:beforeRequest', (event) => {
|
||||
activeElement = document.activeElement;
|
||||
let targetElements = event.target.querySelectorAll('[hx-disable]');
|
||||
for (let i = 0; i < targetElements.length; i++) {
|
||||
targetElements[i].disabled = true;
|
||||
@ -46,11 +44,6 @@ document.body.addEventListener('htmx:afterSwap', (event) => {
|
||||
return reference.getAttribute('data-tooltip');
|
||||
},
|
||||
});
|
||||
if (activeElement) {
|
||||
activeElement.blur();
|
||||
activeElement.focus();
|
||||
activeElement = null;
|
||||
}
|
||||
});
|
||||
|
||||
import toastr from 'toastr';
|
||||
|
@ -4,6 +4,7 @@
|
||||
<x-slot name="trigger">
|
||||
<x-secondary-button>
|
||||
{{ __("Auto Deployment") }}
|
||||
<x-heroicon name="o-chevron-down" class="ml-1 h-4 w-4" />
|
||||
</x-secondary-button>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
|
@ -12,11 +12,13 @@ class="p-6"
|
||||
{{ __("Deployment Script") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">A bash script that will be executed when you run the deployment process.</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="script" :value="__('Script')" />
|
||||
<x-code-editor id="script" name="script" lang="sh" class="mt-1 w-full">
|
||||
<x-textarea id="script" name="script" class="mt-1 min-h-[400px] w-full">
|
||||
{{ old("script", $site->deploymentScript?->content) }}
|
||||
</x-code-editor>
|
||||
</x-textarea>
|
||||
@error("script")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
@ -21,9 +21,9 @@ class="mt-6"
|
||||
>
|
||||
<x-input-label for="env" :value="__('.env')" />
|
||||
<div id="env-content">
|
||||
<x-code-editor id="env" name="env" rows="10" class="mt-1 block w-full">
|
||||
<x-textarea id="env" name="env" rows="10" class="mt-1 block min-h-[400px] w-full">
|
||||
{{ old("env", session()->get("env") ?? "Loading...") }}
|
||||
</x-code-editor>
|
||||
</x-textarea>
|
||||
</div>
|
||||
@error("env")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
|
@ -19,6 +19,7 @@
|
||||
<x-slot name="trigger">
|
||||
<x-secondary-button>
|
||||
{{ __("Manage") }}
|
||||
<x-heroicon name="o-chevron-down" class="ml-1 h-4 w-4" />
|
||||
</x-secondary-button>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
|
@ -1,45 +0,0 @@
|
||||
@props([
|
||||
"id",
|
||||
"name",
|
||||
"disabled" => false,
|
||||
"lang" => "text",
|
||||
])
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
editorId: @js($id),
|
||||
disabled: @js($disabled),
|
||||
lang: @js($lang),
|
||||
init() {
|
||||
document.body.addEventListener('htmx:afterSettle', (event) => {
|
||||
let editor = null
|
||||
let theme =
|
||||
document.documentElement.className === 'dark'
|
||||
? 'one-dark'
|
||||
: 'github'
|
||||
editor = window.ace.edit(this.editorId, {})
|
||||
let contentElement = document.getElementById(
|
||||
`text-${this.editorId}`,
|
||||
)
|
||||
editor.setValue(contentElement.innerText, 1)
|
||||
if (this.disabled) {
|
||||
editor.setReadOnly(true)
|
||||
}
|
||||
editor.getSession().setMode(`ace/mode/${this.lang}`)
|
||||
editor.setTheme(`ace/theme/${theme}`)
|
||||
editor.setFontSize('15px')
|
||||
editor.setShowPrintMargin(false)
|
||||
editor.on('change', () => {
|
||||
contentElement.innerHTML = editor.getValue()
|
||||
})
|
||||
document.body.addEventListener('color-scheme-changed', (event) => {
|
||||
theme = event.detail.theme === 'dark' ? 'one-dark' : 'github'
|
||||
editor.setTheme(`ace/theme/${theme}`)
|
||||
})
|
||||
})
|
||||
},
|
||||
}"
|
||||
>
|
||||
<div id="{{ $id }}" class="min-h-[400px] w-full rounded-md border border-gray-200 dark:border-gray-700"></div>
|
||||
<textarea id="text-{{ $id }}" name="{{ $name }}" class="hidden">{{ $slot }}</textarea>
|
||||
</div>
|
@ -10,7 +10,7 @@
|
||||
runUrl: '{{ route("servers.console.run", ["server" => $server]) }}',
|
||||
async run() {
|
||||
this.running = true
|
||||
this.output = 'Running...\n'
|
||||
this.output = this.command + '\n'
|
||||
const fetchOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -23,6 +23,7 @@
|
||||
}),
|
||||
}
|
||||
|
||||
this.command = ''
|
||||
const response = await fetch(this.runUrl, fetchOptions)
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder('utf-8')
|
||||
|
@ -21,8 +21,6 @@
|
||||
<link rel="preconnect" href="https://fonts.bunny.net" />
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<script src="{{ asset("static/libs/ace/ace.js") }}"></script>
|
||||
|
||||
@include("layouts.partials.favicon")
|
||||
|
||||
<!-- Scripts -->
|
||||
|
@ -38,6 +38,17 @@ class="p-6"
|
||||
@endif
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
<x-input-help>
|
||||
Check
|
||||
<a
|
||||
href="https://vitodeploy.com/settings/source-controls.html"
|
||||
class="text-primary-500"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
to see what permissions you need
|
||||
</x-input-help>
|
||||
@error("provider")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
@ -3,6 +3,10 @@
|
||||
|
||||
@include("site-settings.partials.change-php-version")
|
||||
|
||||
@if ($site->source_control_id)
|
||||
@include("site-settings.partials.update-source-control")
|
||||
@endif
|
||||
|
||||
@include("site-settings.partials.update-v-host")
|
||||
|
||||
<x-card>
|
||||
|
@ -0,0 +1,32 @@
|
||||
<x-card>
|
||||
<x-slot name="title">{{ __("Update Source Control") }}</x-slot>
|
||||
|
||||
<x-slot name="description">
|
||||
{{ __("You can switch the source control profile (token) in case of token expiration. Keep in mind that it must be the same account and provider") }}
|
||||
</x-slot>
|
||||
|
||||
<form
|
||||
id="update-source-control"
|
||||
hx-post="{{ route("servers.sites.settings.source-control", ["server" => $server, "site" => $site]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#update-source-control"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-update-source-control"
|
||||
class="space-y-6"
|
||||
>
|
||||
@include(
|
||||
"sites.partials.create.fields.source-control",
|
||||
[
|
||||
"sourceControls" => \App\Models\SourceControl::query()
|
||||
->where("provider", $site->sourceControl()?->provider)
|
||||
->get(),
|
||||
]
|
||||
)
|
||||
</form>
|
||||
|
||||
<x-slot name="actions">
|
||||
<x-primary-button id="btn-update-source-control" form="update-source-control" hx-disable>
|
||||
{{ __("Save") }}
|
||||
</x-primary-button>
|
||||
</x-slot>
|
||||
</x-card>
|
@ -22,9 +22,9 @@ class="space-y-6"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<div id="vhost-container">
|
||||
<x-code-editor id="vhost" name="vhost" rows="10" class="mt-1 block w-full">
|
||||
<x-textarea id="vhost" name="vhost" rows="10" class="mt-1 block min-h-[400px] w-full">
|
||||
{{ session()->has("vhost") ? session()->get("vhost") : "Loading..." }}
|
||||
</x-code-editor>
|
||||
</x-textarea>
|
||||
</div>
|
||||
@error("vhost")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
|
15
resources/views/sites/installing.blade.php
Normal file
15
resources/views/sites/installing.blade.php
Normal file
@ -0,0 +1,15 @@
|
||||
<x-site-layout :site="$site">
|
||||
<x-slot name="pageTitle">{{ $site->domain }}</x-slot>
|
||||
|
||||
<x-live id="site">
|
||||
@if ($site->status === \App\Enums\SiteStatus::INSTALLING)
|
||||
@include("sites.partials.installing", ["site" => $site])
|
||||
@endif
|
||||
|
||||
@if ($site->status === \App\Enums\SiteStatus::INSTALLATION_FAILED)
|
||||
@include("sites.partials.installation-failed", ["site" => $site])
|
||||
@endif
|
||||
</x-live>
|
||||
|
||||
@include("server-logs.partials.logs-list", ["server" => $site->server, "site" => $site])
|
||||
</x-site-layout>
|
@ -6,7 +6,7 @@
|
||||
@foreach ($sourceControls as $sourceControl)
|
||||
<option
|
||||
value="{{ $sourceControl->id }}"
|
||||
@if($sourceControl->id === old('source_control')) selected @endif
|
||||
@if($sourceControl->id == old('source_control', isset($site) ? $site->source_control_id : null)) selected @endif
|
||||
>
|
||||
{{ $sourceControl->profile }}
|
||||
({{ $sourceControl->provider }})
|
||||
|
@ -1,17 +0,0 @@
|
||||
<div>
|
||||
@if ($site->status === \App\Enums\SiteStatus::INSTALLING)
|
||||
@include("sites.partials.installing", ["site" => $site])
|
||||
|
||||
@include("server-logs.partials.logs-list", ["server" => $site->server, "site" => $site])
|
||||
@endif
|
||||
|
||||
@if ($site->status === \App\Enums\SiteStatus::INSTALLATION_FAILED)
|
||||
@include("sites.partials.installation-failed", ["site" => $site])
|
||||
|
||||
@include("server-logs.partials.logs-list", ["server" => $site->server, "site" => $site])
|
||||
@endif
|
||||
|
||||
@if ($site->status === \App\Enums\SiteStatus::READY)
|
||||
@include("application." . $site->type . "-app", ["site" => $site])
|
||||
@endif
|
||||
</div>
|
@ -1,5 +1,5 @@
|
||||
<x-site-layout :site="$site">
|
||||
<x-slot name="pageTitle">{{ $site->domain }}</x-slot>
|
||||
|
||||
@include("sites.partials.show-site")
|
||||
@include("application." . $site->type . "-app", ["site" => $site])
|
||||
</x-site-layout>
|
||||
|
@ -36,6 +36,7 @@
|
||||
Route::post('/create', [SiteController::class, 'store']);
|
||||
Route::get('/{site}', [SiteController::class, 'show'])->name('servers.sites.show');
|
||||
Route::delete('/{site}', [SiteController::class, 'destroy'])->name('servers.sites.destroy');
|
||||
Route::get('/{site}/installing', [SiteController::class, 'installing'])->name('servers.sites.installing');
|
||||
|
||||
// site application
|
||||
Route::post('/{site}/application/deploy', [ApplicationController::class, 'deploy'])->name('servers.sites.application.deploy');
|
||||
@ -64,6 +65,7 @@
|
||||
Route::get('/{site}/settings/vhost', [SiteSettingController::class, 'getVhost'])->name('servers.sites.settings.vhost');
|
||||
Route::post('/{site}/settings/vhost', [SiteSettingController::class, 'updateVhost']);
|
||||
Route::post('/{site}/settings/php', [SiteSettingController::class, 'updatePHPVersion'])->name('servers.sites.settings.php');
|
||||
Route::post('/{site}/settings/source-control', [SiteSettingController::class, 'updateSourceControl'])->name('servers.sites.settings.source-control');
|
||||
|
||||
// site logs
|
||||
Route::get('/{site}/logs', [SiteLogController::class, 'index'])->name('servers.sites.logs');
|
||||
|
@ -46,6 +46,48 @@ public function test_create_site(array $inputs): void
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider create_failure_data
|
||||
*/
|
||||
public function test_create_site_failed_due_to_source_control(int $status): void
|
||||
{
|
||||
$inputs = [
|
||||
'type' => SiteType::LARAVEL,
|
||||
'domain' => 'example.com',
|
||||
'alias' => 'www.example.com',
|
||||
'php_version' => '8.2',
|
||||
'web_directory' => 'public',
|
||||
'repository' => 'test/test',
|
||||
'branch' => 'main',
|
||||
'composer' => true,
|
||||
];
|
||||
|
||||
SSH::fake();
|
||||
|
||||
Http::fake([
|
||||
'https://api.github.com/repos/*' => Http::response([
|
||||
], $status),
|
||||
]);
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
/** @var \App\Models\SourceControl $sourceControl */
|
||||
$sourceControl = \App\Models\SourceControl::factory()->create([
|
||||
'provider' => SourceControl::GITHUB,
|
||||
]);
|
||||
|
||||
$inputs['source_control'] = $sourceControl->id;
|
||||
|
||||
$this->post(route('servers.sites.create', [
|
||||
'server' => $this->server,
|
||||
]), $inputs)->assertSessionHasErrors();
|
||||
|
||||
$this->assertDatabaseMissing('sites', [
|
||||
'domain' => 'example.com',
|
||||
'status' => SiteStatus::READY,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_see_sites_list(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
@ -103,6 +145,58 @@ public function test_change_php_version(): void
|
||||
$this->assertEquals('8.2', $site->php_version);
|
||||
}
|
||||
|
||||
public function test_update_source_control(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
Http::fake([
|
||||
'https://api.github.com/repos/*' => Http::response([
|
||||
], 201),
|
||||
]);
|
||||
|
||||
/** @var \App\Models\SourceControl $sourceControl */
|
||||
$sourceControl = \App\Models\SourceControl::factory()->create([
|
||||
'provider' => SourceControl::GITHUB,
|
||||
]);
|
||||
|
||||
$this->post(route('servers.sites.settings.source-control', [
|
||||
'server' => $this->server,
|
||||
'site' => $this->site,
|
||||
]), [
|
||||
'source_control' => $sourceControl->id,
|
||||
])->assertSessionDoesntHaveErrors();
|
||||
|
||||
$this->site->refresh();
|
||||
|
||||
$this->assertEquals($sourceControl->id, $this->site->source_control_id);
|
||||
}
|
||||
|
||||
public function test_failed_to_update_source_control(): void
|
||||
{
|
||||
SSH::fake();
|
||||
|
||||
$this->actingAs($this->user);
|
||||
|
||||
Http::fake([
|
||||
'https://api.github.com/repos/*' => Http::response([
|
||||
], 404),
|
||||
]);
|
||||
|
||||
/** @var \App\Models\SourceControl $sourceControl */
|
||||
$sourceControl = \App\Models\SourceControl::factory()->create([
|
||||
'provider' => SourceControl::GITHUB,
|
||||
]);
|
||||
|
||||
$this->post(route('servers.sites.settings.source-control', [
|
||||
'server' => $this->server,
|
||||
'site' => $this->site,
|
||||
]), [
|
||||
'source_control' => $sourceControl->id,
|
||||
])->assertSessionHasErrors();
|
||||
}
|
||||
|
||||
public function test_update_v_host(): void
|
||||
{
|
||||
SSH::fake();
|
||||
@ -170,6 +264,15 @@ public static function create_data(): array
|
||||
];
|
||||
}
|
||||
|
||||
public static function create_failure_data(): array
|
||||
{
|
||||
return [
|
||||
[401],
|
||||
[403],
|
||||
[404],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_see_logs(): void
|
||||
{
|
||||
$this->actingAs($this->user);
|
||||
|
Reference in New Issue
Block a user