Files
ERP-upgrade/resources/views/servers/index.blade.php
2026-09-22 08:06:31 +00:00

496 lines
25 KiB
PHP

@extends('layouts.masterbeta')
@section('title', 'Click ERP - Server Infrastructure')
@section('breadcrumbs')
<a href="{{ url('/') }}" class="text-secondary text-decoration-none"><i class="bi bi-house me-2"></i></a>
<i class="bi bi-chevron-right text-secondary me-2" style="font-size: 0.8rem;"></i>
<span class="fw-bold" style="font-size: 0.95rem;">Servers</span>
@endsection
@section('content')
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="fw-bold mb-1">Infrastructure Management</h4>
<p class="text-secondary mb-0" style="font-size: 0.9rem;">Server list</p>
</div>
<button class="btn text-white fw-bold d-flex align-items-center" style="background-color: #5c4df0; border-radius: 8px;" data-bs-toggle="modal" data-bs-target="#addServerModal">
<i class="bi bi-plus-lg me-2"></i> Add Server
</button>
</div>
<div class="content-card shadow-sm bg-white rounded-3 overflow-hidden">
<!-- Filters Header -->
<div class="p-3 border-bottom bg-light bg-opacity-50">
<div class="row g-3 align-items-center">
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text bg-white border-end-0 text-secondary"><i class="bi bi-search"></i></span>
<input type="text" class="form-control bg-white border-start-0 ps-0" id="searchServer" placeholder="Search by name, IP, or OS ...">
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light text-secondary" style="font-size: 0.85rem;">
<tr>
<th class="ps-4">Server / IP</th>
<th>OS Version</th>
<th>Services</th>
<th>Status</th>
<th class="text-end pe-4">Actions</th>
</tr>
</thead>
<tbody>
@forelse($servers as $server)
<tr>
<td class="ps-4">
<div class="fw-bold text-dark">{{ $server->name }}</div>
<div class="text-secondary font-monospace small"><i class="bi bi-hdd-network me-1"></i>{{ $server->ip_address }}</div>
</td>
<td class="text-secondary small">{{ $server->os_version ?? 'Unknown' }}</td>
<td>
@if(is_array($server->services))
@foreach($server->services as $service)
<span class="badge bg-secondary bg-opacity-10 text-secondary border me-1">{{ $service }}</span>
@endforeach
@else
<span class="text-muted small">None</span>
@endif
</td>
<td>
<span class="badge {{ $server->status == 'Active' ? 'bg-success' : 'bg-warning' }} bg-opacity-10 text-{{ $server->status == 'Active' ? 'success' : 'warning' }}">
{{ $server->status }}
</span>
</td>
<td class="text-end pe-4">
<!-- Secure Access Button -->
<button class="btn btn-sm btn-danger fw-bold btn-access-credentials me-2" data-id="{{ $server->id }}" data-name="{{ $server->name }}">
<i class="bi bi-shield-lock-fill me-1"></i> View Password
</button>
<button class="btn btn-sm btn-light text-primary btn-edit-server" data-id="{{ $server->id }}" title="Edit">
<i class="bi bi-pencil"></i>
</button>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-5 text-secondary">
<i class="bi bi-server fs-1 text-muted mb-2 d-block"></i>
No servers added yet.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endsection
@push('modals')
<!-- OTP VERIFICATION MODAL -->
<div class="modal fade" id="otpModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content">
<div class="modal-header bg-dark text-white border-bottom-0">
<h6 class="modal-title fw-bold"><i class="bi bi-shield-lock me-2"></i>Security Check</h6>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="verifyOtpForm">
@csrf
<div class="modal-body p-4 text-center">
<input type="hidden" id="auth_server_id" name="server_id">
<i class="bi bi-envelope-check text-success" style="font-size: 2.5rem;"></i>
<p class="text-secondary small mt-2 mb-3">An authorization code has been sent to your email. Enter it below to access credentials for <strong id="auth_server_name" class="text-dark"></strong>.</p>
<div class="mb-3">
<input type="text" class="form-control form-control-lg text-center fw-bold letter-spacing-3" name="otp" id="otp_input" placeholder="000000" maxlength="6" required autocomplete="off" style="letter-spacing: 5px;">
</div>
</div>
<div class="modal-footer bg-light border-top-0 d-flex justify-content-between">
<button type="button" class="btn btn-sm btn-link text-secondary text-decoration-none" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnVerifyOtp" class="btn btn-dark btn-sm fw-bold px-4">
Unlock
</button>
</div>
</form>
</div>
</div>
</div>
<!-- ADD SERVER MODAL -->
<div class="modal fade" id="addServerModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-light border-bottom-0">
<h5 class="modal-title fw-bold">Add New Server</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="addServerForm">
@csrf
<div class="modal-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Server Name *</label>
<input type="text" class="form-control" name="name" required placeholder="e.g. Main PBX Engine">
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">IP Address *</label>
<input type="text" class="form-control font-monospace" name="ip_address" required placeholder="192.168.x.x">
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">OS Version</label>
<input type="text" class="form-control" name="os_version" placeholder="e.g. AlmaLinux 9, Debian 12">
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Status *</label>
<select class="form-select" name="status" required>
<option value="Active" selected>Active</option>
<option value="Maintenance">Maintenance</option>
<option value="Inactive">Inactive</option>
</select>
</div>
<div class="col-12 mt-4 mb-2 border-bottom pb-2">
<h6 class="fw-bold text-dark mb-0"><i class="bi bi-shield-lock me-2"></i>Access Credentials</h6>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Username *</label>
<input type="text" class="form-control" name="username" required value="root">
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Password *</label>
<input type="password" class="form-control" name="password" required>
</div>
<div class="col-12 mt-3">
<label class="form-label text-secondary fw-semibold small">Running Services</label>
<select class="form-select select2-services" name="services[]" multiple="multiple" style="width: 100%;">
<!-- Services will be entered dynamically as tags -->
</select>
<div class="form-text text-muted" style="font-size: 0.75rem;">Type a service (e.g., Docker, Asterisk, PostGIS) and press enter.</div>
</div>
</div>
</div>
<div class="modal-footer bg-light border-top-0">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnSaveServer" class="btn text-white btn-sm fw-bold px-4" style="background-color: #5c4df0;">
Save Server
</button>
</div>
</form>
</div>
</div>
</div>
<!-- EDIT SERVER MODAL -->
<div class="modal fade" id="editServerModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-light border-bottom-0">
<h5 class="modal-title fw-bold">Edit Server Settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="editServerForm">
@csrf
@method('PUT')
<input type="hidden" id="edit_server_id">
<div class="modal-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Server Name *</label>
<input type="text" class="form-control" name="name" id="edit_name" required>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">IP Address *</label>
<input type="text" class="form-control font-monospace" name="ip_address" id="edit_ip_address" required>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">OS Version</label>
<input type="text" class="form-control" name="os_version" id="edit_os_version">
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Status *</label>
<select class="form-select" name="status" id="edit_status" required>
<option value="Active">Active</option>
<option value="Maintenance">Maintenance</option>
<option value="Inactive">Inactive</option>
</select>
</div>
<div class="col-12 mt-4 mb-2 border-bottom pb-2">
<h6 class="fw-bold text-dark mb-0"><i class="bi bi-shield-lock me-2"></i>Access Credentials</h6>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Username *</label>
<input type="text" class="form-control" name="username" id="edit_username" required>
</div>
<div class="col-md-6">
<label class="form-label text-secondary fw-semibold small">Update Password</label>
<input type="password" class="form-control" name="password" placeholder="Leave blank to keep current password">
</div>
<div class="col-12 mt-3">
<label class="form-label text-secondary fw-semibold small">Running Services</label>
<select class="form-select select2-services" name="services[]" id="edit_services" multiple="multiple" style="width: 100%;"></select>
</div>
</div>
</div>
<div class="modal-footer bg-light border-top-0">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="submit" id="btnUpdateServer" class="btn text-white btn-sm fw-bold px-4" style="background-color: #5c4df0;">
Update Server
</button>
</div>
</form>
</div>
</div>
</div>
@endpush
@push('scripts')
<script>
$(document).ready(function() {
// 1. Request OTP when clicking "View Password"
$('.btn-access-credentials').on('click', function(e) {
e.preventDefault();
let $btn = $(this);
let serverId = $btn.data('id');
let serverName = $btn.data('name');
let originalHtml = $btn.html();
// Set loading state on button
$btn.html('<span class="spinner-border spinner-border-sm"></span>').prop('disabled', true);
$.ajax({
url: `${base_url}/servers/${serverId}/request-otp`,
type: 'POST',
data: { _token: '{{ csrf_token() }}' },
success: function(response) {
if (response.success) {
// Setup and show modal
$('#auth_server_id').val(serverId);
$('#auth_server_name').text(serverName);
$('#otp_input').val('');
$('#otpModal').modal('show');
// Auto-focus input after modal opens
setTimeout(() => $('#otp_input').focus(), 500);
}
},
error: function() {
Swal.fire('Error', 'Failed to send OTP. Please check your email configuration.', 'error');
},
complete: function() {
$btn.html(originalHtml).prop('disabled', false);
}
});
});
// 2. Submit OTP and Display Credentials
$('#verifyOtpForm').on('submit', function(e) {
e.preventDefault();
let $form = $(this);
let $btn = $('#btnVerifyOtp');
let serverId = $('#auth_server_id').val();
let originalText = $btn.html();
$btn.html('<span class="spinner-border spinner-border-sm me-2"></span>Verifying...').prop('disabled', true);
$form.find('.is-invalid').removeClass('is-invalid');
$.ajax({
url: `${base_url}/servers/${serverId}/verify-otp`,
type: 'POST',
data: $form.serialize(),
success: function(response) {
if (response.success) {
$('#otpModal').modal('hide');
// Display credentials securely in a SweetAlert
Swal.fire({
icon: 'success',
title: 'Access Granted',
html: `
<div class="text-start mt-3 p-3 bg-light border rounded">
<div class="mb-2"><span class="text-muted small">Username:</span><br> <strong class="fs-5">${response.username}</strong></div>
<div><span class="text-muted small">Password:</span><br> <strong class="fs-5 text-primary font-monospace" style="user-select: all;">${response.password}</strong></div>
</div>
<div class="text-muted small mt-3"><i class="bi bi-info-circle me-1"></i>This event has been logged.</div>
`,
confirmButtonColor: '#212529',
confirmButtonText: 'Close & Clear'
});
}
},
error: function(xhr) {
if (xhr.status === 422) {
$('#otp_input').addClass('is-invalid');
// Shake animation for incorrect code
$('#otpModal .modal-content').addClass('animate__animated animate__headShake');
setTimeout(() => $('#otpModal .modal-content').removeClass('animate__animated animate__headShake'), 500);
} else {
Swal.fire('Error', 'A server error occurred.', 'error');
}
},
complete: function() {
$btn.html(originalText).prop('disabled', false);
}
});
});
// Initialize Select2 for dynamic tagging
$('.select2-services').select2({
theme: 'bootstrap-5',
tags: true, // Allows typing custom services
tokenSeparators: [',', ' '],
dropdownParent: function() {
if ($('#addServerModal').hasClass('show')) return $('#addServerModal');
if ($('#editServerModal').hasClass('show')) return $('#editServerModal');
return $(document.body);
}
});
// Handle Add Server Submission
$('#addServerForm').on('submit', function(e) {
e.preventDefault();
let $form = $(this);
let $btn = $('#btnSaveServer');
let originalText = $btn.html();
$btn.html('<span class="spinner-border spinner-border-sm me-2"></span>Saving...').prop('disabled', true);
$form.find('.is-invalid').removeClass('is-invalid');
$.ajax({
url: "{{ route('servers.store') }}",
type: 'POST',
data: $form.serialize(),
success: function(response) {
$('#addServerModal').modal('hide');
Swal.fire({
icon: 'success',
title: 'Added!',
text: response.message,
timer: 1500,
showConfirmButton: false
}).then(() => location.reload());
},
error: function(xhr) {
if (xhr.status === 422) {
$.each(xhr.responseJSON.errors, function(key, value) {
let $input = $form.find('[name="' + key + '"]');
if (key === 'services') $input = $form.find('[name="services[]"]');
if ($input.hasClass('select2-hidden-accessible')) {
$input.next('.select2-container').addClass('is-invalid');
} else {
$input.addClass('is-invalid');
}
});
} else {
Swal.fire('Error', 'An unexpected error occurred.', 'error');
}
},
complete: function() {
$btn.html(originalText).prop('disabled', false);
}
});
});
// Handle Edit Modal Trigger
$(document).on('click', '.btn-edit-server', function(e) {
e.preventDefault();
let serverId = $(this).data('id');
$.ajax({
url: `${base_url}/servers/${serverId}`,
type: 'GET',
success: function(server) {
$('#edit_server_id').val(server.id);
$('#edit_name').val(server.name);
$('#edit_ip_address').val(server.ip_address);
$('#edit_os_version').val(server.os_version);
$('#edit_username').val(server.username);
$('#edit_status').val(server.status);
// Populate Select2 tags
let $services = $('#edit_services');
$services.val(null).trigger('change');
if (server.services && Array.isArray(server.services)) {
server.services.forEach(function(val) {
if ($services.find("option[value='" + val + "']").length === 0) {
$services.append(new Option(val, val, true, true));
}
});
$services.val(server.services).trigger('change');
}
$('#editServerModal').modal('show');
}
});
});
// Handle Edit Server Submission
$('#editServerForm').on('submit', function(e) {
e.preventDefault();
let $form = $(this);
let serverId = $('#edit_server_id').val();
let $btn = $('#btnUpdateServer');
let originalText = $btn.html();
$btn.html('<span class="spinner-border spinner-border-sm me-2"></span>Updating...').prop('disabled', true);
$form.find('.is-invalid').removeClass('is-invalid');
$.ajax({
url: `${base_url}/servers/${serverId}`,
type: 'POST',
data: $form.serialize(),
success: function(response) {
$('#editServerModal').modal('hide');
Swal.fire({
icon: 'success',
title: 'Updated!',
text: response.message,
timer: 1500,
showConfirmButton: false
}).then(() => location.reload());
},
error: function(xhr) {
// Same error handling logic as Add Server
if (xhr.status === 422) {
$.each(xhr.responseJSON.errors, function(key, value) {
let $input = $form.find('[name="' + key + '"]');
if (key === 'services') $input = $form.find('[name="services[]"]');
if ($input.hasClass('select2-hidden-accessible')) {
$input.next('.select2-container').addClass('is-invalid');
} else {
$input.addClass('is-invalid');
}
});
}
},
complete: function() {
$btn.html(originalText).prop('disabled', false);
}
});
});
// Clean up modals on close
$('#addServerModal, #editServerModal').on('hidden.bs.modal', function () {
$(this).find('form')[0].reset();
$(this).find('.select2-services').val(null).trigger('change');
$(this).find('.is-invalid').removeClass('is-invalid');
});
});
</script>
@endpush