Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dist/js/card.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions resources/js/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import MaintenanceModeCard from "./components/MaintenanceMode/MaintenanceModeCar
import MaintenanceModeWarningCard from "./components/MaintenanceMode/MaintenanceModeWarningCard.vue";
import DatabaseBackupCard from "./components/DatabaseBackup/DatabaseBackupCard.vue";
import HorizonClearCard from "./components/HorizonClear/HorizonClearCard.vue";
import ArtisanCallCard from "./components/ArtisanCall/ArtisanCallCard.vue";
import DatabaseSeedCard from './components/DatabaseSeed/DatabaseSeedCard.vue'

Nova.booting((app, store) => {
Expand All @@ -11,5 +12,6 @@ Nova.booting((app, store) => {
app.component('nova-artisan-maintenance-mode-warning-card', MaintenanceModeWarningCard)
app.component('nova-artisan-database-backup-card', DatabaseBackupCard)
app.component('nova-artisan-horizon-clear-card', HorizonClearCard)
app.component('nova-artisan-artisan-call-card', ArtisanCallCard)
app.component('nova-artisan-database-seed-card', DatabaseSeedCard)
})
78 changes: 78 additions & 0 deletions resources/js/components/ArtisanCall/ArtisanCallCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<template>
<Card class="flex flex-col justify-center min-h-8">
<div class="px-4 py-4 flex justify-between items-center">
<div class="flex-grow pr-2">
<h3 class="text-lg font-bold">
Artisan Call
</h3>
<p>This will run the specified artisan command.</p>
</div>
<div class="shrink-0 ml-4">
<Button
@click="showModal = true"
state="default"
>
Run Command
</Button>
</div>
</div>
<ArtisanCallModal
:show="showModal"
title="Artisan Call"
message="This will run the artisan command."
@confirm="handleConfirm"
confirmButtonState="danger"
confirmButtonText="Run Command"
@close="handleClose"
/>
</Card>
</template>

<script>

import ArtisanCallModal from "./Modals/ArtisanCallModal.vue";
import {Button} from "laravel-nova-ui";

export default {
components: {
Button,
ArtisanCallModal,
},
props: [
'card',
// The following props are only available on resource detail cards...
// 'resource',
// 'resourceId',
// 'resourceName',
],
data() {
return {
loading: false,
showModal: false,
};
},
methods: {
async handleConfirm(command) {
this.loading = true;
try {
const response = await Nova.request()
.post('/nova-vendor/nova-artisan-cards/artisan/artisan-call', {
'command': command,
});
// Redirect user back to /
alert(response.data.output);
} catch (error) {
console.error('Error running the command', error);
// Handle error here
alert('Artisan call experienced an error');
} finally {
this.showModal = false;
this.loading = false;
}
},
handleClose() {
this.showModal = false;
},
},
};
</script>
110 changes: 110 additions & 0 deletions resources/js/components/ArtisanCall/Modals/ArtisanCallModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<template>
<Modal :show="show" size="sm">
<form
@submit.prevent="handleConfirm"
class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden"
style="width: 460px"
>
<slot>
<ModalHeader v-text="title"/>
<ModalContent>
<p class="leading-normal">
{{ message }}
</p>

<div class="action">
<div
class="space-y-2 md:flex @md/modal:flex md:flex-row @md/modal:flex-row md:space-y-0 @md/modal:space-y-0 py-5"
>
<div class="w-full px-6 md:mt-2 @md/modal:mt-2 md:px-8 @md/modal:px-8 md:w-1/5 @md/modal:w-1/5">
<label for="test-default-text-field" class="inline-block leading-tight space-x-1">
<span>Artisan Command</span>
</label>
</div>
<div class="w-full space-y-2 md:w-3/5 @md/modal:w-3/5">
<div class="space-y-1">
<input type="text" placeholder="Enter Command"
v-model="command"
class="w-full form-control form-input form-control-bordered"
id="test-default-text-field" maxlength="-1">
</div>
</div>
</div>
</div>
</ModalContent>
</slot>

<ModalFooter>
<div class="ml-auto">
<LinkButton
type="button"
dusk="cancel-restore-button"
@click.prevent="handleClose"
class="mr-3"
>
{{ __('Cancel') }}
</LinkButton>

<Button
type="submit"
ref="confirmButton"
:loading="working"
:state="confirmButtonState"
>
{{ confirmButtonText }}
</Button>
</div>
</ModalFooter>
</form>
</Modal>
</template>

<script>
import {Button} from 'laravel-nova-ui'

export default {
components: {
Button,
},

emits: ['confirm', 'close'],

props: {
show: {type: Boolean, default: false},
message: {type: String, default: 'Are you sure you want to do this action?'},
title: {type: String, default: 'Confirm Action'},
confirmButtonText: {type: String, default: 'Confirm'},
confirmButtonState: {type: Button.ButtonState, default: 'default'},
},

data: () => ({
working: false,
command: "",
}),

watch: {
show(showing) {
if (showing === false) {
this.working = false
}
},
},

methods: {
handleClose() {
this.$emit('close')
this.working = false
},

handleConfirm() {
// If the secret is blank, show an alert
if (this.command === '') {
alert('Command cannot be null')
return
}
this.$emit('confirm', this.command)
this.working = true
},
},
}
</script>
3 changes: 3 additions & 0 deletions routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Coreproc\NovaArtisanCards\HorizonClear\HorizonClearController;
use Coreproc\NovaArtisanCards\MaintenanceMode\MaintenanceModeController;
use Coreproc\NovaArtisanCards\MigrateFresh\MigrateFreshController;
use Coreproc\NovaArtisanCards\ArtisanCall\ArtisanCallController;
use Illuminate\Support\Facades\Route;

/*
Expand All @@ -28,4 +29,6 @@

Route::post('/artisan/horizon-clear', [HorizonClearController::class, 'clear']);

Route::post('/artisan/artisan-call', [ArtisanCallController::class, 'run']);

Route::post('/artisan/database-seed', [DatabaseSeedController::class, 'seed']);
26 changes: 26 additions & 0 deletions src/ArtisanCall/ArtisanCallCard.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Coreproc\NovaArtisanCards\ArtisanCall;

use Illuminate\Support\Facades\Cache;
use Laravel\Nova\Card;

class ArtisanCallCard extends Card
{
/**
* The width of the card (1/3, 1/2, or full).
*
* @var string
*/
public $width = 'full';

/**
* Get the component name for the element.
*
* @return string
*/
public function component()
{
return 'nova-artisan-artisan-call-card';
}
}
41 changes: 41 additions & 0 deletions src/ArtisanCall/ArtisanCallController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Coreproc\NovaArtisanCards\ArtisanCall;

use Coreproc\NovaArtisanCards\ArtisanCall\Requests\ArtisanCallRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Controller;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

class ArtisanCallController extends Controller
{
public function run(ArtisanCallRequest $artisanCallRequest): JsonResponse
{
$artisanCall = $artisanCallRequest->get('command');

$command = ['php', base_path('artisan')];
if (isset($artisanCall)) {
$command = array_merge($command, explode(' ', $artisanCall));
}

$process = new Process($command);

try {
$process->mustRun();

return response()->json([
'success' => true,
'message' => 'Artisan call ran successfully.',
'output' => $process->getOutput()
]);
} catch (ProcessFailedException $exception) {
return response()->json([
'success' => false,
'message' => 'Failed to run artisan command.',
'error' => $exception->getMessage(),
'output' => $process->getOutput()
], 500);
}
}
}
20 changes: 20 additions & 0 deletions src/ArtisanCall/Requests/ArtisanCallRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Coreproc\NovaArtisanCards\ArtisanCall\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ArtisanCallRequest extends FormRequest
{
public function rules(): array
{
return [
'command' => ['required', 'string', 'max:255']
];
}

public function authorize(): bool
{
return true;
}
}