Compare commits

...

31 Commits
0.0.1 ... main

Author SHA1 Message Date
JonatanRek
0b29730e7f Merge branch 'main' of https://git.steelants.cz/JonatanRek/LAR_Maintenance 2024-09-25 22:09:17 +02:00
JonatanRek
1cc854ae1f Add Host Groups 2024-09-25 22:09:13 +02:00
Jonatan Rek
8d408c4704 Fixed 2024-09-10 15:40:53 +02:00
Jonatan Rek
eaccd5224a Fixes 2024-09-10 15:33:34 +02:00
Jonatan Rek
0ed83d7bc2 Fixes 2024-09-10 15:31:16 +02:00
Jonatan Rek
881c89bfc1 Fixes 2024-09-10 15:17:13 +02:00
Jonatan Rek
79cb5ceb19 Fixes 2024-09-10 13:31:06 +02:00
Jonatan Rek
c7ce9ac177 Rename Inputs 2024-09-10 13:23:26 +02:00
JonatanRek
b749cdf591 Zabbix integration 2024-08-16 23:37:42 +02:00
JonatanRek
f3a3dcb566 Fix 2024-08-16 18:23:04 +02:00
JonatanRek
2f4f917032 Proxxgers + ZBX hosts Sync 2024-08-16 18:20:45 +02:00
JonatanRek
84ef42382a Progress 2024-08-16 13:28:14 +02:00
JonatanRek
2d5cb8cdc6 Password change error 2024-08-09 09:17:23 +02:00
JonatanRek
558d49a986 SH fix 2024-08-09 09:17:06 +02:00
JonatanRek
c1802dfb52 Progress 2024-08-09 08:37:00 +02:00
Jonatan Rek
17616bad17 Fixes and preparation for history detail 2024-08-08 10:33:21 +02:00
JonatanRek
6ad2ddeb4a Fixes 2024-08-07 21:56:13 +02:00
JonatanRek
790f595f0f Fix 2024-08-07 21:50:09 +02:00
JonatanRek
84d20bd23c Fix 2024-08-07 21:47:40 +02:00
JonatanRek
8e1b00a6c7 Fix 2024-08-07 21:46:38 +02:00
JonatanRek
c7ae8e0f3f Fixes and TODO 2024-08-07 21:45:21 +02:00
702a1e7d46 Update database/seeders/DatabaseSeeder.php 2024-08-07 19:22:55 +00:00
JonatanRek
9abc4c4961 Progression 2024-08-07 11:50:10 +02:00
JonatanRek
065655ca7e Additional Feedback 2024-08-07 11:13:25 +02:00
JonatanRek
1254f7015b Performance Tweaks 2024-08-07 10:30:51 +02:00
JonatanRek
b486cad1ad Progress 2024-08-07 09:54:20 +02:00
JonatanRek
7951b8c84c Additional Progress 2024-08-07 07:52:07 +02:00
Jonatan Rek
13756de3a8 Fix Maintenance Creation 2024-08-06 17:02:14 +02:00
Jonatan Rek
e166932809 Progress Fix Scheduling 2024-08-06 16:57:48 +02:00
Jonatan Rek
425630ad44 Example Commands 2024-08-06 16:33:38 +02:00
Jonatan Rek
d2ea5ab548 Docker 2024-08-06 16:30:02 +02:00
65 changed files with 1715 additions and 822 deletions

74
Dockerfile Normal file
View File

@ -0,0 +1,74 @@
# Use PHP with Apache as the base image
FROM php:8.3-apache
# Timezone
ENV TZ="Europe/Prague"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN printf '[Date]\ndate.timezone="%s"\n', $TZ > /usr/local/etc/php/conf.d/tzone.ini
# PHP
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN sed -i "s|memory_limit = 128M|memory_limit = 256M |g" /etc/sysctl.conf
RUN sed -i "s|upload_max_filesize = 2M|upload_max_filesize = 100M |g" /etc/sysctl.conf
RUN sed -i "s|post_max_size = 8M|post_max_size = 100M |g" /etc/sysctl.conf
# Install Additional System Dependencies
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y \
libzip-dev \
zip \
nodejs \
npm \
cron \
nano \
default-mysql-client
# Clear cache
RUN apt-get autoremove
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Enable Apache mod_rewrite for URL rewriting
RUN a2enmod rewrite
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql zip
# Configure Apache DocumentRoot to point to Laravel's public directory
# and update Apache configuration files
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
# Copy the application code
COPY . /var/www/html
# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Install project dependencies
RUN composer install --quiet --prefer-dist --no-dev -o
RUN npm install && npm run build
# Setup Crone
RUN mkdir -p /var/log/cron
RUN echo "* * * * * www-data cd /var/www/html && /usr/local/bin/php artisan schedule:run >> /dev/null 2>&1" >> /etc/crontab
RUN echo "* * * * * www-data cd /var/www/html && /usr/local/bin/php artisan queue:work --queue=default --stop-when-empty >> /dev/null 2>&1" >> /etc/crontab
RUN echo "#" >> /etc/crontab
# Add a command to base-image entrypont scritp
RUN sed -i 's/^exec /printenv > \/etc\/environment\n\nexec /' /usr/local/bin/apache2-foreground
RUN sed -i 's/^exec /service cron start\n\nexec /' /usr/local/bin/apache2-foreground
#Start Container
RUN chmod +x /var/www/html/start.sh
CMD ["/var/www/html/start.sh"]
# Set the working directory
VOLUME /var/www/html/storage/app/app
RUN mkdir -p /config
VOLUME /config
# Start Apache
WORKDIR /var/www/html/

View File

@ -1,66 +1,19 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
# Maintenance
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
#### Docker Build - Powershell (One Line)
```powershell
$tag = ("0.0.1").Trim(); Remove-Item -Path "./database/database.sqlite" -Force; Remove-Item -Path ".env" -Force; $tag | set-content -path "./public/version.txt" -Force; git reset --hard; git pull origin master; git tag $tag; git push --tags; docker build -t git.steelants.cz/jonatanrek/lar_maintenance:$tag .; docker push git.steelants.cz/jonatanrek/lar_maintenance:$tag; docker build -t git.steelants.cz/jonatanrek/lar_maintenance:latest .; docker push git.steelants.cz/jonatanrek/lar_maintenance:latest
```
## About Laravel
#### Docker Build - Bash (One Line)
```bash
export TAG="0.0.1" && echo "$TAG" > "./public/version.txt" && sudo docker build -t git.steelants.cz/jonatanrek/lar_maintenance:"$TAG" . && sudo docker push git.steelants.cz/jonatanrek/lar_maintenance:"$TAG" && sudo docker build -t git.steelants.cz/jonatanrek/lar_maintenance:latest . && sudo docker push git.steelants.cz/jonatanrek/lar_maintenance:latest
```
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[WebReinvent](https://webreinvent.com/)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Jump24](https://jump24.co.uk)**
- **[Redberry](https://redberry.international/laravel/)**
- **[Active Logic](https://activelogic.com)**
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
### TODO
* Maintenance Relations
* Shippable servers in maintenance
* Zakazníci
* Možnost comentáže pokud skipneš host/task require comment
* zodpovědná osoba za maintenance
* maintenance doba trvání

View File

@ -4,8 +4,14 @@ namespace App\Http\Controllers;
use SteelAnts\LaravelAuth\Traits\Authentication;
class AuthController
class AuthController extends Controller
{
use Authentication;
protected string $redirectTo = "maintenance.planned";
public function __construct()
{
$this->middleware('auth')->only(['logout','root']);
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Http\Controllers;
class HomeController extends BaseController
{
public function index()
{
return view('home');
}
}

View File

@ -2,10 +2,31 @@
namespace App\Http\Controllers;
use App\Models\Host;
use App\Services\ZabbixService;
use Carbon\Carbon;
class HostController extends BaseController
{
public function index()
{
return view('hosts.index');
}
public function sync()
{
$zabbix = new ZabbixService(config('zabbix.url'));
$zabbix->connect(config('zabbix.username'), config('zabbix.password'));
foreach ($zabbix->hosts() as $host) {
Host::updateOrCreate([
"hostname" => $host['host'],
], [
"display_name" => $host['name'],
"external_id" => $host['hostid'],
]);
}
return redirect()->back();
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers;
use App\Models\Host;
use App\Services\ZabbixService;
use Carbon\Carbon;
class HostGroupController extends BaseController
{
public function index()
{
return view('host_groups.index');
}
public function sync()
{
$zabbix = new ZabbixService(config('zabbix.url'));
$zabbix->connect(config('zabbix.username'), config('zabbix.password'));
return redirect()->back();
}
}

View File

@ -3,6 +3,9 @@
namespace App\Http\Controllers;
use App\Models\MaintenanceHistory;
use App\Services\ZabbixService;
use Carbon\Carbon;
use Illuminate\Http\Request;
class MaintenanceController extends BaseController
{
@ -18,8 +21,152 @@ class MaintenanceController extends BaseController
public function plannedDetail(MaintenanceHistory $maintenance_history)
{
if (!empty($maintenance_history->finished_at)) {
abort(404);
}
return view('maintenance.planned-detail', [
'maintenance_history' => $maintenance_history,
'maintenance_history' => $maintenance_history ?? [],
]);
}
public function plannedDetailPut(Request $request, MaintenanceHistory $maintenance_history)
{
if (!empty($maintenance_history->finished_at)) {
abort(404);
}
return view('maintenance.planned-detail-done', [
'maintenance_history' => $maintenance_history,
'maintenance_task_status' => $request->input('maintenance_task_status') ?? [],
'maintenance_host_skipped' => $request->input('maintenance_host_skipped') ?? [],
]);
}
public function plannedDetailFinishPost(Request $request, MaintenanceHistory $maintenance_history)
{
if (!empty($maintenance_history->finished_at)) {
abort(404);
}
$request->validate([
"skippedHostsSummary.*.*" => 'required|string'
]);
// if ($request->has('skippedHostsSummary') || $request->has('skippedHostTasksSummary'))
// {
// return redirect()->back()->with('error', __('boilerplate::ui.incorect.old.password'));
// }
if ($request->has('skippedHostsSummary')) {
foreach ($request->input('skippedHostsSummary') as $history_host_id => $summary) {
$maintenance_history->historyHosts->find($history_host_id)->update([
'summary' => $summary,
]);
}
}
if ($request->has('skippedHostTasksSummary')) {
foreach ($maintenance_history->historyHosts as $history_host) {
if (!isset($request->input('skippedHostTasksSummary')[$history_host->id]))
continue;
foreach ($request->input('skippedHostTasksSummary')[$history_host->id] as $history_task_id => $task_summary) {
$history_host->historyTasks->find($history_task_id)->update([
'summary' => $task_summary,
]);
}
}
}
$maintenance_history->finished_at = Carbon::now();
$maintenance_history->save();
return redirect()->route('maintenance.history.detail', $maintenance_history->id);
}
public function history(Request $request)
{
return view('maintenance.history');
}
public function historyDetail(Request $request, MaintenanceHistory $maintenance_history)
{
return view('maintenance.history-detail-done', [
'maintenance_history' => $maintenance_history ?? []
]);
}
public function start(Request $request, MaintenanceHistory $maintenance_history)
{
$hostsForMaintenance = [];
foreach ($maintenance_history->historyHosts as $historyHost) {
if (empty($historyHost->host->external_id)) {
continue;
}
$hostsForMaintenance[] = [
"hostid" => (int) $historyHost->host->external_id,
];
}
$zabbix = new ZabbixService(config('zabbix.url'));
$zabbix->connect(config('zabbix.username'), config('zabbix.password'));
if (empty($maintenance_history->maintenance->external_id)) {
$maintenance_history->maintenance->external_id = $zabbix->maintenancesCreate([
"name" => $maintenance_history->maintenance->name . ' - ' . $maintenance_history->start_at->format('Y'),
"description" => strip_tags($maintenance_history->maintenance->description),
"active_since" => $maintenance_history->start_at->startofyear()->timestamp,
"active_till" => $maintenance_history->start_at->endofyear()->timestamp,
"tags_evaltype" => 0,
"hosts" => $hostsForMaintenance,
"timeperiods" => [[
"timeperiod_type" => 0,
"start_date" => $maintenance_history->start_at->timestamp,
"period" => (($maintenance_history->maintenance->duration * 60) * 60),
]],
])['maintenanceids'][0];
$maintenance_history->maintenance->save();
} else {
$createPeriode = true;
$timePeriodes = $zabbix->maintenances([
"selectTimeperiods" => "extend",
'maintenanceids' => $maintenance_history->maintenance->external_id,
])[0]['timeperiods'];
foreach ($timePeriodes as $periode) {
if ((int) $periode['start_date'] === $maintenance_history->start_at->timestamp) {
$createPeriode = false;
}
}
if ($createPeriode) {
for ($i = 0; $i < count($timePeriodes); $i++) {
unset($timePeriodes[$i]['every']);
unset($timePeriodes[$i]['month']);
unset($timePeriodes[$i]['dayofweek']);
unset($timePeriodes[$i]['day']);
unset($timePeriodes[$i]['start_time']);
}
$timePeriodes[] = [
"timeperiod_type" => 0,
"start_date" => $maintenance_history->start_at->timestamp,
"period" => (($maintenance_history->maintenance->duration * 60) * 60),
];
$zabbix->maintenancesUpdate([
"maintenanceid" => $maintenance_history->maintenance->external_id,
"timeperiods" => $timePeriodes,
]);
}
}
return redirect()->back();
}
}

View File

@ -17,7 +17,7 @@ class GenerateMenus
public function handle(Request $request, Closure $next): Response
{
if ($request->route()->getName() === 'livewire.message') {
return $next($request);
return $next($request);
}
if (!auth()->check()) {
@ -26,8 +26,8 @@ class GenerateMenus
Menu::make('main-menu', function ($menu) {
$systemRoutes = [
'boilerplate::ui.home' => [' fas fa-home', 'home'],
'boilerplate::ui.planned' => [' fas fa-home', 'maintenance.planned'],
'ui.planned' => [' fas fa-home', 'maintenance.planned'],
'ui.history' => [' fas fa-home', 'maintenance.history'],
];
foreach ($systemRoutes as $title => $route_data) {
@ -45,8 +45,9 @@ class GenerateMenus
Menu::make('settings-menu', function ($menu) {
$systemRoutes = [
'Host' => [' fas fa-server', 'host'],
'Host Groups' => [' fas fa-server', 'host_groups'],
'Maintenance' => [' fas fa-calendar', 'maintenance'],
'Tasks' => [' fas fa-calendar', 'tasks'],
'Tasks' => [' fas fa-list', 'tasks'],
];
foreach ($systemRoutes as $title => $route_data) {

View File

@ -23,6 +23,7 @@ class UpdateUserRequest extends FormRequest
{
return [
'email' => ['sometimes', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['sometimes', 'nullable', 'string'],
'newPassword' => ['sometimes', 'nullable', 'string', 'min:8', 'confirmed'],
];
}

View File

@ -5,12 +5,14 @@ namespace App\Jobs;
use App\Models\Maintenance;
use App\Models\MaintenanceHistory;
use App\Models\MaintenanceTaskHistory;
use Carbon\Carbon;
use Cron\CronExpression;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Poliander\Cron\CronExpression as CronCronExpression;
class ScheduleNextMaintenance implements ShouldQueue
{
@ -31,16 +33,18 @@ class ScheduleNextMaintenance implements ShouldQueue
{
$maintenances = Maintenance::all();
foreach ($maintenances as $maintenance) {
$valid = CronExpression::isValidExpression($maintenance->schedule);
if (!$valid) {
dd($maintenance->schedule);
return;
}
$cron = new CronCronExpression($maintenance->schedule);
$nextRunTime = Carbon::createFromTimestamp($cron->getNext());
if(MaintenanceHistory::where('hash', md5($maintenance->id . $nextRunTime))->first() !== null){
continue;
};
$cron = new CronExpression($maintenance->schedule);
$maintenancePlanned = $maintenance->history()->create([
'start_at' => $cron->getNextRunDate(null, 2)
'start_at' => $nextRunTime,
'guestor_id' => $maintenance->guestor_id,
]);
$maintenancePlanned->refresh();
$hosts = $maintenance->hosts;
@ -49,7 +53,7 @@ class ScheduleNextMaintenance implements ShouldQueue
'host_id' => $host->id
]);
$tasks = $maintenance->tasks;
$tasks = $maintenance->hosts->find($host->id)->tasks;
foreach ($tasks as $key => $task) {
$maintenancePlannedHost->historyTasks()->create([
'maintenance_task_id' => $task->id,
@ -58,6 +62,5 @@ class ScheduleNextMaintenance implements ShouldQueue
}
}
}
die();
}
}

View File

@ -2,6 +2,7 @@
namespace App\Livewire\Host;
use App\Models\Host;
use App\Services\ZabbixService;
use SteelAnts\DataTable\Livewire\DataTableComponent;
use Illuminate\Database\Eloquent\Builder;
@ -12,15 +13,20 @@ class DataTable extends DataTableComponent
'closeModal' => '$refresh',
];
public bool $searchable = true;
public array $searchableColumns = ['display_name', 'hostname'];
public function query(): Builder
{
return Host::query();
return Host::query()->with(['hostGroups']);
}
public function headers(): array
{
return [
'display_name' => 'display_name',
'hostname' => 'hostname',
'host_groups' => 'host_groups',
];
}
@ -28,6 +34,12 @@ class DataTable extends DataTableComponent
Host::find($host_id)->delete();
}
public function columnHostGroups(mixed $column): mixed
{
return implode(", " , $column->pluck('name')->toArray());
}
public function actions($item)
{
return [

View File

@ -3,12 +3,16 @@ namespace App\Livewire\Host;
use Livewire\Component;
use App\Models\Host;
use App\Models\HostGroup;
class Form extends Component
{
public $model;
public string $hostname;
public $host_groups = [];
public $host_groups_available = [];
public $action = 'store';
protected function rules()
@ -19,12 +23,15 @@ class Form extends Component
}
public function mount ($model = null){
$this->host_groups_available = HostGroup::pluck('name', 'id')->toArray();
if (!empty($model)) {
$host = Host::find($model);
$this->model = $model;
$this->hostname = $host->hostname;
$this->host_groups = $host->hostGroups()->pluck('host_groups.id')->toArray();
$this->action = 'update';
}
@ -33,7 +40,8 @@ class Form extends Component
public function store()
{
$validatedData = $this->validate();
Host::create($validatedData);
$host = Host::create($validatedData);
$host->hostGroups()->sync($this->host_groups);
$this->dispatch('closeModal');
}
@ -43,6 +51,7 @@ class Form extends Component
$host = Host::find($this->model);
if (!empty($host)) {
$host->update($validatedData);
$host->hostGroups()->sync($this->host_groups);
}
$this->dispatch('closeModal');
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Livewire\HostGroup;
use App\Models\HostGroup;
use SteelAnts\DataTable\Livewire\DataTableComponent;
use Illuminate\Database\Eloquent\Builder;
class DataTable extends DataTableComponent
{
public $listeners = [
'hostgroupAdded' => '$refresh',
'closeModal' => '$refresh',
];
public function query(): Builder
{
return HostGroup::query()->with(['hosts:id']);
}
public function headers(): array
{
return [
'name' => 'name',
'hosts_count' => 'hosts_count',
];
}
public function row($row){
$row['hosts_count'] = $row->hosts->count();
return $row;
}
public function remove($hostgroup_id){
HostGroup::find($hostgroup_id)->delete();
}
public function actions($item)
{
return [
[
'type' => "livewire",
'action' => "edit",
'text' => "edit",
'parameters' => $item['id']
],
[
'type' => "livewire",
'action' => "remove",
'text' => "remove",
'parameters' => $item['id']
]
];
}
public function edit($host_id)
{
$this->dispatch('openModal', 'host.form', __('boilerplate::host.edit'), ['model' => $host_id]);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Livewire\HostGroup;
use Livewire\Component;
use App\Models\HostGroup;
class Form extends Component
{
public $model;
public string $name;
public $action = 'store';
protected function rules()
{
return [
'name' => 'required',
];
}
public function mount ($model = null){
if (!empty($model)) {
$hostGroup = HostGroup::find($model);
$this->model = $model;
$this->name = $hostGroup->name;
$this->action = 'update';
}
}
public function store()
{
$validatedData = $this->validate();
HostGroup::create($validatedData);
$this->dispatch('closeModal');
}
public function update()
{
$validatedData = $this->validate();
$hostGroup = HostGroup::find($this->model);
if (!empty($hostGroup)) {
$hostGroup->update($validatedData);
}
$this->dispatch('closeModal');
}
public function render()
{
return view('livewire.host-group.form');
}
}

View File

@ -22,10 +22,20 @@ class DataTable extends DataTableComponent
return [
'name' => 'name',
'description' => 'description',
'schedule' => 'schedule',
'schedule' => 'schedule/offset',
];
}
public function renderColumnSchedule($value, $row)
{
return e(!empty($value) ? $value : $row['blocking_maintenance_offset']);
}
public function renderColumnDescription($value, $row)
{
return e(!empty($value) ? mb_strimwidth(strip_tags($value), 0, 50, "...") : "");
}
public function remove($maintenance_id){
Maintenance::find($maintenance_id)->delete();
}

View File

@ -7,6 +7,8 @@ use Livewire\Component;
use App\Models\Maintenance;
use App\Models\MaintenanceTask;
use App\Models\Task;
use App\Models\User;
use Illuminate\Http\Request;
class Form extends Component
{
@ -23,19 +25,37 @@ class Form extends Component
public $hosts_tasks = [];
public $hosts_tasks_available = [];
public int $guestor_id;
public $guestor_available = [];
public $blocking_maintenance_id = null;
public $maintenances_available = [];
public $blocking_maintenance_offset = null;
public int $duration = 0;
protected function rules()
{
return [
'name' => 'required',
'guestor_id' => 'required|exists:users,id',
'blocking_maintenance_id' => 'exists:maintenances,id|nullable',
'blocking_maintenance_offset' => 'nullable',
'description' => 'required',
'schedule' => 'required',
'schedule' => 'nullable',
'duration' => 'required|max:24',
];
}
public function mount($model = null)
public function mount(Request $request, $model = null)
{
$this->hosts_available = Host::all()->pluck('hostname', 'id')->toArray();
$this->hosts_tasks_available = Task::all()->pluck('name', 'id')->toArray();
$this->guestor_available = User::all()->pluck('name', 'id')->toArray();
$this->maintenances_available = Maintenance::where('id', '!=', $model)->pluck('name', 'id')->toArray();
$this->maintenances_available = [null => 'Select blocking maintenance...'] + $this->maintenances_available;
if (!empty($model)) {
$maintenance = Maintenance::find($model);
@ -45,6 +65,11 @@ class Form extends Component
$this->name = $maintenance->name;
$this->description = $maintenance->description;
$this->schedule = $maintenance->schedule;
$this->guestor_id = $maintenance->guestor_id;
$this->blocking_maintenance_id = $maintenance->blocking_maintenance_id ?? null;
$this->blocking_maintenance_offset = $maintenance->blocking_maintenance_offset ?? null;
$this->duration = $maintenance->duration ;
$this->hosts = $maintenance->hosts()->pluck('hosts.id')->toArray();
foreach ($maintenance->tasks as $task) {
@ -52,6 +77,19 @@ class Form extends Component
}
$this->action = 'update';
} else {
$this->guestor_id = $request->user()->id;
}
}
public function updatedHosts($value)
{
foreach ($this->hosts_tasks as $host_id => $tasks) {
if (in_array($host_id, $this->hosts)) {
continue;
}
unset($this->hosts_tasks[$host_id]);
}
}
@ -60,8 +98,16 @@ class Form extends Component
$validatedData = $this->validate();
$maintenance = Maintenance::create($validatedData);
$hosts = Host::whereIn('id', $this->hosts)->get();
foreach ($hosts as $key => $host) {
$maintenance->hosts()->attach($host);
$tasks = Task::whereIn('id', $this->hosts_tasks[$host->id])->get();
foreach ($tasks as $task) {
$maintenance->tasks()->create([
'task_id' => $task->id,
'host_id' => $host->id,
]);
}
}
$this->dispatch('closeModal');
}

View File

@ -0,0 +1,133 @@
<?php
namespace App\Livewire\Maintenance;
use App\Models\MaintenanceHistory;
use App\Models\MaintenanceHostHistory;
use App\Models\MaintenanceTaskHistory;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
class ProgressForm extends Component
{
public $maintenanceHistory;
//INPUTS
public array $maintenance_task_status;
public array $maintenance_task_status_old;
public array $maintenance_host_skipped;
public function mount(int $maintenanceHistory)
{
$this->maintenanceHistory = self::getMaintenanceHistory($maintenanceHistory, true);
if (!empty($this->maintenanceHistory->finished_at)) {
abort(404);
}
foreach ($this->maintenanceHistory->historyHosts as $maintenanceHost) {
$this->maintenance_host_skipped[$maintenanceHost->id] = (bool)$maintenanceHost->is_skipped;
foreach ($maintenanceHost->historyTasks as $maintenanceTask) {
$this->maintenance_task_status[$maintenanceHost->id][$maintenanceTask->id] = (!empty($maintenanceTask->finished_at) ? true : false);
}
}
}
public function updatedMaintenanceHostSkipped($value)
{
$skipped = [];
$notSkipped = [];
foreach ($this->maintenance_host_skipped as $host_history_id => $is_skipped) {
if ($is_skipped === true) {
$skipped[] = $host_history_id;
continue;
}
$notSkipped[] = $host_history_id;
}
if (count($skipped) != 0) {
MaintenanceHostHistory::whereIn('id', $skipped)->update([
'is_skipped' => true,
]);
}
if (count($notSkipped) != 0) {
MaintenanceHostHistory::whereIn('id', $notSkipped)->update([
'is_skipped' => false,
]);
}
}
public function updatedMaintenanceTaskStatus($value)
{
foreach ($this->maintenance_task_status as $host_history_id => $tasks) {
$done = [];
$notDone = [];
foreach ($tasks as $task_history_id => $state) {
if (isset($this->maintenance_task_status_old[$host_history_id][$task_history_id]) && $this->maintenance_task_status_old[$host_history_id][$task_history_id] === $state) {
continue;
}
if ($state === true) {
$done[] = $task_history_id;
continue;
}
$notDone[] = $task_history_id;
}
if (count($done) != 0) {
MaintenanceTaskHistory::where('maintenance_task_histories.maintenance_host_history_id', $host_history_id)
->where('maintenance_task_histories.status', '!=', 2)
->whereIn('maintenance_task_histories.id', $done)
->update([
'maintenance_task_histories.status' => 2,
'maintenance_task_histories.finished_at' => Carbon::now(),
]);
}
if (count($notDone) != 0) {
MaintenanceTaskHistory::where('maintenance_task_histories.maintenance_host_history_id', $host_history_id)
->where('maintenance_task_histories.status', '!=', 1)
->whereIn('maintenance_task_histories.id', $notDone)
->update([
'maintenance_task_histories.status' => 1,
'maintenance_task_histories.finished_at' => null,
]);
}
}
$this->maintenance_task_status_old = $this->maintenance_task_status;
}
public function render()
{
$this->maintenanceHistory = self::getMaintenanceHistory($this->maintenanceHistory->id);
return view('livewire.maintenance.progress-form');
}
private static function getMaintenanceHistory(int $maintenanceHistoryId, bool $force = false)
{
if ($force){
Cache::forget("maintenance_history_{$maintenanceHistoryId}");
}
return Cache::remember("maintenance_history_{$maintenanceHistoryId}", 60, function () use ($maintenanceHistoryId) {
return MaintenanceHistory::with([
'maintenance.tasks',
'maintenance.hosts',
'historyHosts',
'historyHosts.host',
'historyHosts.historyTasks',
'historyHosts.historyTasks.maintenanceTask',
'historyHosts.historyTasks.maintenanceTask.task'
])->find($maintenanceHistoryId);
});
}
}

View File

@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Builder;
class DataTable extends DataTableComponent
{
public string $type = "planned";
public $listeners = [
'maintenanceHistoryAdded' => '$refresh',
'closeModal' => '$refresh',
@ -15,16 +17,31 @@ class DataTable extends DataTableComponent
public function query(): Builder
{
return MaintenanceHistory::query();
$query = MaintenanceHistory::query();
if ($this->type == "planned") {
$query->whereNull('finished_at');
} elseif ($this->type == "history") {
$query->whereNotNull('finished_at');
}
return $query;
}
public function headers(): array
{
return [
$headers = [
'maintenance.name' => 'maintenance.name',
'start_at' => 'start_at',
'finished_at' => 'finished_at',
];
if ($this->type == "planned") {
$headers['guestor.name'] = 'guestor';
} else {
$headers['finished_at'] = 'finished_at';
}
return $headers;
}
public function remove($maintenancehistory_id)
@ -34,7 +51,24 @@ class DataTable extends DataTableComponent
public function renderColumnMaintenanceName($val, $row)
{
$ret = '<a href="' . route('maintenance.planned.detail', $row['id']) . '">' . e($val) . '</a>';
if ($this->type == "planned") {
$ret = '<a href="' . route('maintenance.planned.detail', $row['id']) . '">' . e($val) . '</a>';
} else {
$ret = '<a href="' . route('maintenance.history.detail', $row['id']) . '">' . e($val) . '</a>';
}
return $ret;
}
public function actions($item)
{
return [
[
'type' => "livewire",
'action' => "remove",
'text' => "remove",
'parameters' => $item['id']
]
];
}
}

View File

@ -24,13 +24,13 @@ class Form extends Component
public function mount ($model = null){
if (!empty($model)) {
$maintenance-history = MaintenanceHistory::find($model);
$maintenanceHistory = MaintenanceHistory::find($model);
$this->model = $model;
$this->maintenance_id = $maintenance-history->maintenance_id;
$this->start_at = $maintenance-history->start_at;
$this->finished_at = $maintenance-history->finished_at;
$this->maintenance_id = $maintenanceHistory->maintenance_id;
$this->start_at = $maintenanceHistory->start_at;
$this->finished_at = $maintenanceHistory->finished_at;
$this->action = 'update';
}
@ -46,9 +46,9 @@ class Form extends Component
public function update()
{
$validatedData = $this->validate();
$maintenance-history = MaintenanceHistory::find($this->model);
if (!empty($maintenance-history)) {
$maintenance-history->update($validatedData);
$maintenanceHistory = MaintenanceHistory::find($this->model);
if (!empty($maintenanceHistory)) {
$maintenanceHistory->update($validatedData);
}
$this->dispatch('closeModal');
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Livewire;
use Livewire\Component;
class MaintenanceProgressForm extends Component
{
public function render()
{
return view('livewire.maintenance-progress-form');
}
}

View File

@ -25,6 +25,11 @@ class DataTable extends DataTableComponent
];
}
public function renderColumnDescription($value, $row)
{
return e(!empty($value) ? mb_strimwidth(strip_tags($value), 0, 50, "...") : "");
}
public function remove($task_id){
Task::find($task_id)->delete();
}

View File

@ -11,10 +11,17 @@ class Host extends Model
protected $fillable = [
'hostname',
'display_name',
'external_id',
];
public function tasks()
{
return $this->hasMany(MaintenanceTask::class,);
return $this->hasMany(MaintenanceTask::class);
}
public function hostGroups()
{
return $this->belongsToMany(HostGroup::class)->using(HostHostGroup::class);
}
}

21
app/Models/HostGroup.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class HostGroup extends Model
{
use HasFactory;
protected $fillable = [
'name',
'external_id',
];
public function hosts()
{
return $this->belongsToMany(Host::class)->using(HostHostGroup::class);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class HostHostGroup extends Pivot
{
//
}

View File

@ -13,6 +13,11 @@ class Maintenance extends Model
'name',
'description',
'schedule',
'guestor_id',
'blocking_maintenance_id',
'duration',
'blocking_maintenance_offset',
'external_id',
];
public function hosts()
@ -29,4 +34,9 @@ class Maintenance extends Model
{
return $this->hasMany(MaintenanceTask::class,);
}
public function guestor()
{
return $this->BelongsTo(User::class, 'guestor_id');
}
}

View File

@ -2,6 +2,7 @@
namespace App\Models;
use App\Observers\MaintenanceHistoryObserver;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -13,8 +14,19 @@ class MaintenanceHistory extends Model
'maintenance_id',
'start_at',
'finished_at',
'guestor_id',
];
protected $casts = [
'start_at' => 'datetime',
'finished_at' => 'datetime',
];
protected static function booted()
{
MaintenanceHistory::observe(MaintenanceHistoryObserver::class);
}
public function maintenance()
{
return $this->BelongsTo(Maintenance::class);
@ -24,4 +36,9 @@ class MaintenanceHistory extends Model
{
return $this->hasMany(MaintenanceHostHistory::class);
}
public function guestor()
{
return $this->BelongsTo(User::class, 'guestor_id');
}
}

View File

@ -12,6 +12,12 @@ class MaintenanceHostHistory extends Model
protected $fillable = [
'maintenance_history_id',
'host_id',
'summary',
'is_skipped',
];
protected $cast = [
'is_skipped' => 'bool'
];
public function host()

View File

@ -8,11 +8,12 @@ use Illuminate\Database\Eloquent\Model;
class MaintenanceTaskHistory extends Model
{
use HasFactory;
protected $fillable = [
'maintenance_task_id',
'maintenance_history_id',
'maintenance_host_history_id',
'summary',
];
public function maintenance()
@ -24,4 +25,5 @@ class MaintenanceTaskHistory extends Model
{
return $this->belongsTo(MaintenanceTask::class);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Observers;
use App\Models\MaintenanceHistory;
class MaintenanceHistoryObserver
{
public function creating(MaintenanceHistory $maintenanceHistory): void
{
$maintenanceHistory->hash = md5($maintenanceHistory->maintenance_id . $maintenanceHistory->start_at);
}
public function updating(MaintenanceHistory $maintenanceHistory): void
{
$maintenanceHistory->hash = md5($maintenanceHistory->maintenance_id . $maintenanceHistory->start_at);
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace App\Services;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Http;
class ZabbixService
{
public string $token = "";
public string $url = "";
public int $id = 1;
function __construct($url)
{
$this->url = $url;
}
public function connect($username, $password)
{
$this->token = $this->request([
"jsonrpc" => "2.0",
"method" => "user.login",
"params" => [
"username" => $username,
"password" => $password,
],
"id" => $this->id,
"auth" => null,
]);
return $this->token;
}
public function hosts($params = [])
{
return $this->request([
"jsonrpc" => "2.0",
"method" => "host.get",
"params" => $params,
"id" => $this->id,
"auth" => $this->token,
]);
}
public function hostGroups($params = [])
{
return $this->request([
"jsonrpc" => "2.0",
"method" => "hostgroup.get",
"params" => $params,
"id" => $this->id,
"auth" => $this->token,
]);
}
public function maintenances($params = [])
{
return $this->request([
"jsonrpc" => "2.0",
"method" => "maintenance.get",
"params" => $params,
"id" => $this->id,
"auth" => $this->token,
]);
}
public function maintenancesCreate($params = [])
{
return $this->request([
"jsonrpc" => "2.0",
"method" => "maintenance.create",
"params" => $params,
"id" => $this->id,
"auth" => $this->token,
]);
}
public function maintenancesUpdate( $params = [])
{
return $this->request([
"jsonrpc" => "2.0",
"method" => "maintenance.update",
"params" => $params,
"id" => $this->id,
"auth" => $this->token,
]);
}
/*Helpers*/
private function request($body = [])
{
if (empty($this->token) && $body['auth'] != null) {
throw new Exception("you need to connect first", 1);
}
$response = Http::withoutVerifying()->post($this->url . "/api_jsonrpc.php", $body);
if (!$response->successful()) {
throw new Exception("Unable To Request", 1);
}
$responseObject = $response->json();
if (isset($responseObject['error'])) {
throw new Exception($responseObject['error']['data'], $responseObject['error']['code']);
}
$this->id++;
return is_array($responseObject["result"]) ? collect($responseObject["result"]) : $responseObject["result"];
}
}

View File

@ -6,10 +6,10 @@
"license": "MIT",
"require": {
"php": "^8.2",
"dragonmantank/cron-expression": "^3.3",
"laravel/framework": "^11.9",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9",
"poliander/cron": "^3.1",
"steelants/laravel-boilerplate": "^1.2"
},
"require-dev": {

603
composer.lock generated

File diff suppressed because it is too large Load Diff

7
config/zabbix.php Normal file
View File

@ -0,0 +1,7 @@
<?php
return [
'url' => env('ZBX_URL', ''),
'username' => env('ZBX_USERNAME', ''),
'password' => env('ZBX_PASSWORD', ''),
];

View File

@ -22,6 +22,7 @@ return new class extends Migration
$table->foreignIdFor(MaintenanceHostHistory::class);
$table->string('status')->default(1);
$table->datetime('finished_at')->nullable();
$table->text('summary')->nullable();
$table->timestamps();
});
}

View File

@ -19,6 +19,8 @@ return new class extends Migration
$table->id();
$table->foreignIdFor(MaintenanceHistory::class);
$table->foreignIdFor(Host::class);
$table->text('summary')->nullable();
$table->boolean('is_skipped')->default(false);
$table->timestamps();
});
}

View File

@ -0,0 +1,33 @@
<?php
use App\Models\Maintenance;
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('maintenances', function (Blueprint $table) {
$table->foreignIdFor(User::class, 'guestor_id');
$table->foreignIdFor(Maintenance::class, 'blocking_maintenance_id')->nullable();
$table->string('blocking_maintenance_offset')->nullable();
$table->integer('duration');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('maintenances', function (Blueprint $table) {
//
});
}
};

View File

@ -0,0 +1,28 @@
<?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
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('maintenance_histories', function (Blueprint $table) {
$table->foreignIdFor(User::class, 'guestor_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('maintenance_histories', function (Blueprint $table) {
});
}
};

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
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('maintenance_histories', function (Blueprint $table) {
$table->string('hash');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};

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
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('hosts', function (Blueprint $table) {
$table->string('display_name')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
}
};

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('hosts', function (Blueprint $table) {
$table->string('external_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('hosts', function (Blueprint $table) {
});
}
};

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('maintenances', function (Blueprint $table) {
$table->string('external_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('maintenances', function (Blueprint $table) {
//
});
}
};

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('maintenance_histories', function (Blueprint $table) {
$table->string('external_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('maintenance_histories', function (Blueprint $table) {
//
});
}
};

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
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('host_groups', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('external_id')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('host_groups');
}
};

View File

@ -0,0 +1,29 @@
<?php
use App\Models\HostGroup;
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('hosts', function (Blueprint $table) {
$table->foreignIdFor(HostGroup::class, 'host_group_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('hosts', function (Blueprint $table) {
//
});
}
};

View File

@ -0,0 +1,30 @@
<?php
use App\Models\Host;
use App\Models\HostGroup;
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('host_host_group', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Host::class, 'host_id')->nullable();
$table->foreignIdFor(HostGroup::class, 'host_group_id')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('host_host_group');
}
};

View File

@ -14,7 +14,7 @@ class DatabaseSeeder extends Seeder
public function run(): void
{
// User::factory(10)->create();
if (empty(User::where('email', 'test@example.co')->first())){
if (empty(User::where('email', 'test@example.com')->first())){
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',

7
lang/en/ui.php Normal file
View File

@ -0,0 +1,7 @@
<?php
return [
'dashboard' => 'Dashboard',
'planned' => 'Planned',
'history' => 'History',
];

1
public/version.txt Normal file
View File

@ -0,0 +1 @@
0.0.1

View File

@ -1,189 +0,0 @@
<x-layout-app>
<div class="container-xl">
<div class="page-header">
<h1>Welcolm Back !</h1>
<a class="btn btn-primary" href="{{ url('home') }}"><i class="fa fa-plus me-2"></i> Page Action</a>
</div>
</div>
<div class="container-xl">
<h1>head 1</h1>
<h2>head 2</h2>
<h3>head 3</h3>
<h4>head 4</h4>
<h5>head 5</h5>
<h6>head 6</h6>
<div class="my-4">
<h4>Tabs</h4>
<ul class="nav nav-tabs mb-4">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Active</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
</li>
</ul>
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Active</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
</li>
</ul>
</div>
<div class="my-4 d-flex">
<div class="app-nav-profile random-bg-1">
PS
</div>
<div class="app-nav-profile random-bg-2">
PS
</div>
<div class="app-nav-profile random-bg-3">
PS
</div>
<div class="app-nav-profile random-bg-4">
PS
</div>
<div class="app-nav-profile random-bg-5">
PS
</div>
<div class="app-nav-profile random-bg-6">
PS
</div>
</div>
<div class="my-4">
<h4>Buttons</h4>
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-secondary">Secondary</button>
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-danger">Danger</button>
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-light">Light</button>
<button type="button" class="btn btn-dark">Dark</button>
<button type="button" class="btn">Button</button>
<button type="button" class="btn btn-link">Link</button>
</div>
<div class="my-4">
<h4>Badges</h4>
<span class="badge text-bg-primary">Primary</span>
<span class="badge text-bg-secondary">Secondary</span>
<span class="badge text-bg-success">Success</span>
<span class="badge text-bg-danger">Danger</span>
<span class="badge text-bg-warning">Warning</span>
<span class="badge text-bg-info">Info</span>
<span class="badge text-bg-light">Light</span>
<span class="badge text-bg-dark">Dark</span>
<br>
<span class="badge rounded-pill text-bg-primary">Primary</span>
<span class="badge rounded-pill text-bg-secondary">Secondary</span>
<span class="badge rounded-pill text-bg-success">Success</span>
<span class="badge rounded-pill text-bg-danger">Danger</span>
<span class="badge rounded-pill text-bg-warning">Warning</span>
<span class="badge rounded-pill text-bg-info">Info</span>
<span class="badge rounded-pill text-bg-light">Light</span>
<span class="badge rounded-pill text-bg-dark">Dark</span>
<br>
</div>
<div class="my-4">
<h4>Breadcrumbs</h4>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item active" aria-current="page">Home</li>
</ol>
</nav>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">Library</li>
</ol>
</nav>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item"><a href="#">Library</a></li>
<li class="breadcrumb-item active" aria-current="page">Data</li>
</ol>
</nav>
</div>
<div class="my-4">
<h4>Alerts</h4>
<div class="alert alert-primary" role="alert">
A simple primary alert—check it out!
</div>
<div class="alert alert-secondary" role="alert">
A simple secondary alert—check it out!
</div>
<div class="alert alert-success" role="alert">
A simple success alert—check it out!
</div>
<div class="alert alert-danger" role="alert">
A simple danger alert—check it out!
</div>
<div class="alert alert-warning" role="alert">
A simple warning alert—check it out!
</div>
<div class="alert alert-info" role="alert">
A simple info alert—check it out!
</div>
<div class="alert alert-light" role="alert">
A simple light alert—check it out!
</div>
<div class="alert alert-dark" role="alert">
A simple dark alert—check it out!
</div>
</div>
<div class="my-4">
<h4>Pagination</h4>
<nav aria-label="...">
<ul class="pagination">
<li class="page-item disabled">
<a class="page-link">Previous</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item active" aria-current="page">
<a class="page-link" href="#">2</a>
</li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#">Next</a>
</li>
</ul>
</nav>
</div>
<div class="my-4">
<h4>Colors</h4>
<div class="text-bg-primary p-3">Primary with contrasting color</div>
<div class="text-bg-secondary p-3">Secondary with contrasting color</div>
<div class="text-bg-success p-3">Success with contrasting color</div>
<div class="text-bg-danger p-3">Danger with contrasting color</div>
<div class="text-bg-warning p-3">Warning with contrasting color</div>
<div class="text-bg-info p-3">Info with contrasting color</div>
<div class="text-bg-light p-3">Light with contrasting color</div>
<div class="text-bg-dark p-3">Dark with contrasting color</div>
</div>
</div>
</x-layout-app>

View File

@ -0,0 +1,20 @@
<x-layout-app>
<div class="container-xl">
<div class="page-header">
<h1>{{ __('boilerplate::host_groups.title') }}</h1>
<div>
<a class="btn btn-primary" href="{{ route('host.sync') }}">
<i class="me-2 fas fa-sync-alt"></i><span>{{ __('boilerplate::ui.sync') }}</span>
</a>
<button class="btn btn-primary"
onclick="Livewire.dispatch('openModal', {livewireComponents: 'host_group.form', title: '{{ __('boilerplate::host_group.create') }}'})">
<i class="me-2 fas fa-plus"></i><span>{{ __('boilerplate::ui.add') }}</span>
</button>
</div>
</div>
@livewire('host_group.data-table', [], key('data-table'))
</div>
</x-layout-app>

View File

@ -3,9 +3,16 @@
<div class="page-header">
<h1>{{ __('boilerplate::hosts.title') }}</h1>
<button class="btn btn-primary" onclick="Livewire.dispatch('openModal', {livewireComponents: 'host.form', title: '{{ __('boilerplate::host.create') }}'})">
<i class="me-2 fas fa-plus"></i><span>{{ __('boilerplate::ui.add') }}</span>
</button>
<div>
<a class="btn btn-primary" href="{{ route('host.sync') }}">
<i class="me-2 fas fa-sync-alt"></i><span>{{ __('boilerplate::ui.sync') }}</span>
</a>
<button class="btn btn-primary"
onclick="Livewire.dispatch('openModal', {livewireComponents: 'host.form', title: '{{ __('boilerplate::host.create') }}'})">
<i class="me-2 fas fa-plus"></i><span>{{ __('boilerplate::ui.add') }}</span>
</button>
</div>
</div>
@livewire('host.data-table', [], key('data-table'))

View File

@ -0,0 +1,6 @@
<div>
<x-form::form wire:submit.prevent="{{$action}}">
<x-form::input group-class="mb-3" type="text" wire:model="name" id="name" label="name"/>
<x-form::button class="btn-primary" type="submit">Create</x-form::button>
</x-form::form>
</div>

View File

@ -1,6 +1,7 @@
<div>
<x-form::form wire:submit.prevent="{{$action}}">
<x-form::input group-class="mb-3" type="text" wire:model="hostname" id="hostname" label="hostname"/>
<x-form::select group-class="mb-3" wire:model.live="host_groups" label="Select groups for Host" :options="$host_groups_available" placeholder="Select value..." multiple />
<x-form::button class="btn-primary" type="submit">Create</x-form::button>
</x-form::form>
</div>
</div>

View File

@ -1,15 +1,23 @@
<div>
<x-form::form wire:submit.prevent="{{ $action }}">
<x-form::input group-class="mb-3" type="text" wire:model="name" id="name" label="name" />
<x-form::select group-class="mb-3" wire:model="guestor_id" label="Responsible User" :options="$guestor_available" placeholder="Select guestor..." />
<x-form::select group-class="mb-3" wire:model.live="blocking_maintenance_id" label="Blocking Maintenance" :options="$maintenances_available" placeholder="Select blocking maintenance..." />
<x-form::quill group-class="mb-3" type="text" wire:model="description" id="description" label="description" />
@if(empty($blocking_maintenance_id))
<x-form::input group-class="mb-3" type="text" wire:model="schedule" id="schedule" label="schedule" />
<x-form::select group-class="mb-3" wire:model.live="hosts" label="Livewire Select" :options="$hosts_available" placeholder="Select value..." multiple />
@else
<x-form::input group-class="mb-3" type="text" wire:model="blocking_maintenance_offset" id="blocking_maintenance_offset" label="blocking_maintenance_offset" help="you can use foloving shortcodes (w - weeks, m - months, d - days)" />
@endif
<x-form::input group-class="mb-3" type="text" wire:model="duration" id="duration" label="duration" help="in hours (60 minutes) maximum 24h" />
<x-form::select group-class="mb-3" wire:model.live="hosts" label="Hosts in Maintenance" :options="$hosts_available" placeholder="Select value..." multiple />
@foreach($hosts as $key => $host_id)
<x-form::select group-class="mb-3" wire:model="hosts_tasks.{{ $host_id }}" label="Select Tasks for Host {{ $hosts_available[$host_id] }}" :options="$hosts_tasks_available" placeholder="Select value..." multiple />
<x-form::select group-class="mb-3" wire:key="{{ $host_id }}" wire:model="hosts_tasks.{{ $host_id }}" label="Select Tasks for Host {{ $hosts_available[$host_id] }}" :options="$hosts_tasks_available" placeholder="Select value..." multiple />
@endforeach
<x-form::button class="btn-primary" type="submit">Create</x-form::button>
@dump($hosts_tasks)
</x-form::form>
</div>
</div>

View File

@ -0,0 +1,22 @@
<div>
@foreach ($maintenanceHistory->historyHosts as $historyHost)
<div class="mb-3">
<div class="position-relative">
<div class="btn-toggle btn py-3 bg-primary w-100 text-start" style="--bs-bg-opacity: .2;" data-bs-toggle="collapse" data-bs-target="#collapse{{ $historyHost->id }}">
<b>{{ $historyHost->host->hostname }}</b>
</div>
<div class="position-absolute top-50 end-0 translate-middle-y px-3 py-2">
<x-form::checkbox groupClass="ms-auto" wire:key="{{ $historyHost->id . 'skip' }}" wire:model.live.debounce.250ms="maintenance_host_skipped.{{ $historyHost->id }}" name="maintenance_host_skipped[{{ $historyHost->id }}]" id="maintenance_host_skipped-{{ $historyHost->id }}" label="{{ __('Skip') }}" />
</div>
</div>
<div class="collapse" id="collapse{{ $historyHost->id }}" wire:ignore.self>
<div class="card card-body mt-2">
@foreach ($historyHost->historyTasks as $historyTask)
<x-form::checkbox wire:key="{{ $historyHost->id . $historyTask->id }}" wire:model.live.debounce.250ms="maintenance_task_status.{{ $historyHost->id }}.{{ $historyTask->id }}" name="maintenance_task_status[{{ $historyHost->id }}][{{ $historyTask->id }}]" id="maintenance_task_status-{{ $historyHost->id }}-{{ $historyTask->id }}" label="{{ $historyTask->maintenanceTask->task->name }}" />
<p>{!! $historyTask->maintenanceTask->task->description !!}</p>
@endforeach
</div>
</div>
</div>
@endforeach
</div>

View File

@ -0,0 +1,37 @@
<x-layout-app>
<div class="container-xl">
<div class="page-header">
<h1>{{ $maintenance_history->maintenance->name }}</h1>
</div>
<p>{!! $maintenance_history->maintenance->description !!}</p>
@foreach ($maintenance_history->historyHosts as $historyHost)
<h2>
@if (empty($historyHost->summary))
<i class="fas fa-check text-success"></i>
@else
<i class="fas fa-times text-danger"></i>
@endif{{ $historyHost->host->hostname }}
</h2>
@if (!empty($historyHost->summary))
<b>{{ __('Oduvodnění') }}</b>
<p>{!! $historyHost->summary !!}</p>
@endif
@foreach ($historyHost->historyTasks as $historyTask)
<h3>
@if (empty($historyTask->summary))
<i class="fas fa-check text-success"></i>
@else
<i class="fas fa-times text-danger"></i>
@endif
{{ $historyTask->maintenanceTask->task->name }}
</h3>
@if (!empty($historyTask->summary))
<b>{{ __('Oduvodnění') }}</b>
<p>{!! $historyTask->summary !!}</p>
@endif
@endforeach
<hr>
@endforeach
</div>
</x-layout-app>

View File

@ -0,0 +1,9 @@
<x-layout-app>
<div class="container-xl">
<div class="page-header">
<h1>{{ __('History') }}</h1>
</div>
@livewire('maintenance-history.data-table', ['type' => 'history'], key('data-table'))
</div>
</x-layout-app>

View File

@ -0,0 +1,24 @@
<x-layout-app>
<div class="container-xl">
<div class="page-header">
<h1>{{ __('Planned Maintenance') }}</h1>
</div>
<p>{!! $maintenance_history->maintenance->description !!}</p>
<x-form::form action="{{ route('maintenance.planned.finished', ['maintenance_history' => $maintenance_history->id]) }}" method="POST">
@foreach ($maintenance_history->historyHosts as $historyHost)
@if (in_array($historyHost->id, array_keys($maintenance_host_skipped)))
<x-form::quill required label="{{ __('Reason for Skipping Host: ') . $historyHost->host->hostname}}" id="skipped-host-{{ $historyHost->id }}-summary" name="skippedHostsSummary[{{ $historyHost->id }}]" help="{{ __('Describe why did you skipped the host') }}" />
@else
@foreach ($historyHost->historyTasks as $historyTask)
@if (!isset($maintenance_task_status[$historyHost->id][$historyTask->id]))
<x-form::quill required label="{{ __('Reason for Skipping Task: ') . $historyTask->maintenanceTask->name}}" id="skipped-host-{{ $historyHost->id }}-task-{{ $historyTask->id }}-summary" name="skippedHostTasksSummary[{{ $historyHost->id }}][{{ $historyTask->id }}]" help="{{ __('Describe why did you not finished the task') }}" />
@else
<x-form::checkbox onclick="return false" checked="{{ isset($maintenance_task_status[$historyHost->id][$historyTask->id]) ? 'checked' : '' }}" label="{{ $historyTask->maintenanceTask->task->name }}" name="test-{{ $historyHost->id }}-{{ $historyTask->id }}" />
@endif
@endforeach
@endif
@endforeach
<x-form::button class="btn-primary" type="submit">{{ __('Ukončit Maintenance') }}</x-form::button>
</x-form::form>
</div>
</x-layout-app>

View File

@ -2,25 +2,14 @@
<div class="container-xl">
<div class="page-header">
<h1>{{ __('Planned Maintenance') }}</h1>
<a class="btn btn-primary" href="{{ route('maintenance.start', ['maintenance_history' => $maintenance_history->id]) }}" disabled>
<i class="me-2 fas fa-play"></i><span>{{ __('Zahájit') }}</span>
</a>
</div>
<p>{!! $maintenance_history->maintenance->description !!}</p>
<x-form::form action="save" method="PUT">
@foreach ($maintenance_history->historyHosts as $historyHost)
<div class="mb-3">
<div class="btn-toggle btn py-3 w-100 bg-primary text-start" style="--bs-bg-opacity: .2;" data-bs-toggle="collapse" data-bs-target="#collapse{{ $historyHost->id }}">
<b>{{ $historyHost->host->hostname }}</b>
</div>
<div class="collapse" id="collapse{{ $historyHost->id }}">
<div class="card card-body mt-2">
@foreach ($historyHost->historyTasks as $historyTasks)
<x-form::checkbox wire:model="test" label="{{$historyTasks->maintenanceTask->task->name}}" />
<p>{!! $historyTasks->maintenanceTask->task->description !!}</p>
@endforeach
</div>
</div>
</div>
@endforeach
<x-form::button class="btn-primary" type="submit">{{ __('Ukončit')}}</x-form::button>
<x-form::form action="{{ route('maintenance.planned.detail.put', ['maintenance_history' => $maintenance_history->id]) }}" method="PUT">
@livewire('maintenance.progress-form', ['maintenanceHistory' => $maintenance_history->id], key('progress-form'))
<x-form::button class="btn-primary" type="submit">{{ __('Pokračovat') }}</x-form::button>
</x-form::form>
</div>
</x-layout-app>
</x-layout-app>

File diff suppressed because one or more lines are too long

View File

@ -1,18 +1,29 @@
<?php
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
Route::auth();
Route::get('/', function () {
return view('welcome');
if (Auth::check()){
return redirect()->route('maintenance.planned');
}
return redirect()->route('login');
});
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
Route::get('/maintenance/planned', [App\Http\Controllers\MaintenanceController::class, 'planned'])->name('maintenance.planned');
Route::get('/maintenance/planned/{maintenance_history}', [App\Http\Controllers\MaintenanceController::class, 'plannedDetail'])->name('maintenance.planned.detail');
Route::put('/maintenance/planned/{maintenance_history}', [App\Http\Controllers\MaintenanceController::class, 'plannedDetailPut'])->name('maintenance.planned.detail.put');
Route::get('/maintenance/planned/{maintenance_history}/start', [App\Http\Controllers\MaintenanceController::class, 'start'])->name('maintenance.start');
Route::post('/maintenance/planned/{maintenance_history}/finished', [App\Http\Controllers\MaintenanceController::class, 'plannedDetailFinishPost'])->name('maintenance.planned.finished');
Route::get('/maintenance/history/', [App\Http\Controllers\MaintenanceController::class, 'history'])->name('maintenance.history');
Route::get('/maintenance/history/{maintenance_history}', [App\Http\Controllers\MaintenanceController::class, 'historyDetail'])->name('maintenance.history.detail');
Route::get('/host', [App\Http\Controllers\HostController::class, 'index'])->name('host');
Route::get('/host/sync', [App\Http\Controllers\HostController::class, 'sync'])->name('host.sync');
Route::get('/host_groups', [App\Http\Controllers\HostGroupController::class, 'index'])->name('host_groups');
Route::get('/maintenance', [App\Http\Controllers\MaintenanceController::class, 'index'])->name('maintenance');
Route::get('/tasks', [App\Http\Controllers\TaskController::class, 'index'])->name('tasks');

55
start.sh Normal file
View File

@ -0,0 +1,55 @@
# Install project dependencies
init=false
if ! test -f /config/.env; then
# Create Env File
cp .env.example .env
php artisan key:generate
cp .env /config/.env
init=true
echo "Initialization"
else
cp /config/.env .env
fi
if [ $(grep -ic "DB_CONNECTION=sqlite" /var/www/html/.env) -gt 0 ] || [ "${DEPLOY_ENV}" == "sqlite"]; then
if ! test -f "/var/www/html/database/database.sqlite" ; then
echo " DB Initialization"
init=true
fi
fi
# Migrate Database
php artisan migrate --force --no-interaction
# Link storage and create default user
if [ "$init" = true ]; then
echo "Initialization of Laravel"
# Install project build dependencies
composer install --quiet --prefer-dist-o
php artisan db:seed --force --class=DatabaseSeeder
php artisan storage:link
#clear build dependencies and install production dependencies
rm -rf vendor/*
composer install --quiet --prefer-dist --no-dev -o
fi
# Clear Cashes
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
# fix permissions
chown -R www-data:www-data /var/www/html
find /var/www/html -type f -not -name "*.sh" -exec chmod 664 {} +
find /var/www/html -type d -exec chmod 775 {} +
# Start Apache
apache2-foreground

View File

@ -1,3 +0,0 @@
*
!public/
!.gitignore

View File

@ -1,2 +0,0 @@
*
!.gitignore

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB