mirror of
https://github.com/mivodev/mivo.git
synced 2026-01-26 13:31:56 +07:00
Chore: Bump version to v1.1.0 and implement automated release system
This commit is contained in:
@@ -22,9 +22,9 @@ sort($uniqueModes);
|
||||
<a href="/<?= htmlspecialchars($session) ?>/dashboard" class="btn btn-secondary">
|
||||
<i data-lucide="arrow-left" class="w-4 h-4 mr-2"></i> <span data-i18n="common.dashboard">Dashboard</span>
|
||||
</a>
|
||||
<a href="/<?= htmlspecialchars($session) ?>/hotspot/profile/add" class="btn btn-primary">
|
||||
<button onclick="openProfileModal('add')" class="btn btn-primary">
|
||||
<i data-lucide="plus" class="w-4 h-4 mr-2"></i> <span data-i18n="hotspot_profiles.add_profile">Add Profile</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,8 +79,21 @@ sort($uniqueModes);
|
||||
<tbody id="table-body">
|
||||
<?php if (!empty($profiles)): ?>
|
||||
<?php foreach ($profiles as $profile): ?>
|
||||
<tr class="table-row-item"
|
||||
data-name="<?= strtolower($profile['name'] ?? '') ?>"
|
||||
<tr class="table-row-item group-row"
|
||||
data-id="<?= $profile['.id'] ?>"
|
||||
data-name="<?= htmlspecialchars($profile['name'] ?? '') ?>"
|
||||
data-shared-users="<?= htmlspecialchars($profile['shared-users'] ?? '1') ?>"
|
||||
data-rate-limit="<?= htmlspecialchars($profile['rate-limit'] ?? '') ?>"
|
||||
data-address-pool="<?= htmlspecialchars($profile['address-pool'] ?? 'none') ?>"
|
||||
data-parent-queue="<?= htmlspecialchars($profile['parent-queue'] ?? 'none') ?>"
|
||||
data-expired-mode="<?= htmlspecialchars($profile['meta']['expired_mode'] ?? 'none') ?>"
|
||||
data-val-d="<?= htmlspecialchars($profile['val_d'] ?? '') ?>"
|
||||
data-val-h="<?= htmlspecialchars($profile['val_h'] ?? '') ?>"
|
||||
data-val-m="<?= htmlspecialchars($profile['val_m'] ?? '') ?>"
|
||||
data-price="<?= htmlspecialchars($profile['meta']['price'] ?? '') ?>"
|
||||
data-selling-price="<?= htmlspecialchars($profile['meta']['selling_price'] ?? '') ?>"
|
||||
data-lock-user="<?= htmlspecialchars($profile['meta']['lock_user'] ?? 'Disable') ?>"
|
||||
data-search-name="<?= strtolower($profile['name'] ?? '') ?>"
|
||||
data-mode="<?= htmlspecialchars($profile['meta']['expired_mode_formatted'] ?? '') ?>">
|
||||
|
||||
<td>
|
||||
@@ -89,9 +102,9 @@ sort($uniqueModes);
|
||||
<i data-lucide="ticket" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="text-sm font-medium text-foreground">
|
||||
<a href="/<?= htmlspecialchars($session) ?>/hotspot/profile/edit/<?= $profile['.id'] ?>" class="hover:underline hover:text-purple-600 dark:hover:text-purple-400">
|
||||
<button onclick="openProfileModal('edit', this)" class="hover:underline hover:text-purple-600 dark:hover:text-purple-400 text-left">
|
||||
<?= htmlspecialchars($profile['name'] ?? '-') ?>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -129,9 +142,9 @@ sort($uniqueModes);
|
||||
|
||||
<td class="text-right text-sm font-medium">
|
||||
<div class="flex items-center justify-end gap-2 table-actions-reveal">
|
||||
<a href="/<?= htmlspecialchars($session) ?>/hotspot/profile/edit/<?= $profile['.id'] ?>" class="btn bg-blue-50 hover:bg-blue-100 text-blue-600 dark:bg-blue-900/20 dark:hover:bg-blue-900/40 border-transparent h-8 px-2 rounded transition-colors" title="Edit">
|
||||
<button onclick="openProfileModal('edit', this)" class="btn bg-blue-50 hover:bg-blue-100 text-blue-600 dark:bg-blue-900/20 dark:hover:bg-blue-900/40 border-transparent h-8 px-2 rounded transition-colors" title="Edit">
|
||||
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||
</a>
|
||||
</button>
|
||||
<form action="/<?= htmlspecialchars($session) ?>/hotspot/profile/delete" method="POST" onsubmit="event.preventDefault(); Mivo.confirm(window.i18n ? window.i18n.t('hotspot_profiles.title') : 'Delete Profile?', window.i18n ? window.i18n.t('common.confirm_delete') : 'Are you sure you want to delete profile <?= $profile['name'] ?>?', window.i18n ? window.i18n.t('common.delete') : 'Delete', window.i18n ? window.i18n.t('common.cancel') : 'Cancel').then(res => { if(res) this.submit(); });" class="inline">
|
||||
<input type="hidden" name="session" value="<?= htmlspecialchars($session) ?>">
|
||||
<input type="hidden" name="id" value="<?= $profile['.id'] ?>">
|
||||
@@ -238,7 +251,7 @@ sort($uniqueModes);
|
||||
|
||||
update() {
|
||||
this.filteredRows = this.allRows.filter(row => {
|
||||
const name = row.dataset.name || '';
|
||||
const name = row.dataset.searchName || '';
|
||||
const mode = row.dataset.mode || '';
|
||||
|
||||
if (this.filters.search && !name.includes(this.filters.search)) return false;
|
||||
@@ -307,5 +320,201 @@ sort($uniqueModes);
|
||||
|
||||
const rows = document.querySelectorAll('.table-row-item');
|
||||
new TableManager(rows, 10);
|
||||
});
|
||||
});
|
||||
|
||||
function openProfileModal(mode, btn = null) {
|
||||
const template = document.getElementById('profile-form-template').innerHTML;
|
||||
|
||||
let title = window.i18n ? window.i18n.t('hotspot_profiles.form.add_title') : 'Add Profile';
|
||||
let saveBtn = window.i18n ? window.i18n.t('common.save') : 'Save';
|
||||
|
||||
if (mode === 'edit') {
|
||||
title = window.i18n ? window.i18n.t('hotspot_profiles.form.edit_title') : 'Edit Profile';
|
||||
saveBtn = window.i18n ? window.i18n.t('common.forms.save_changes') : 'Save Changes';
|
||||
}
|
||||
|
||||
const preConfirmFn = () => {
|
||||
const form = Swal.getHtmlContainer().querySelector('form');
|
||||
if(form.reportValidity()) {
|
||||
form.submit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const onOpenedFn = (popup) => {
|
||||
const form = popup.querySelector('form');
|
||||
|
||||
// Validity Toggle Logic for Modal
|
||||
const modeSelect = form.querySelector('#expired-mode');
|
||||
const validityGroup = form.querySelector('#validity-group');
|
||||
|
||||
function toggleValidity() {
|
||||
if (!modeSelect || !validityGroup) return;
|
||||
if (modeSelect.value === 'none') {
|
||||
validityGroup.classList.add('hidden');
|
||||
} else {
|
||||
validityGroup.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (modeSelect) {
|
||||
modeSelect.addEventListener('change', toggleValidity);
|
||||
}
|
||||
|
||||
if (mode === 'edit' && btn) {
|
||||
const row = btn.closest('tr');
|
||||
|
||||
form.action = "/<?= htmlspecialchars($session) ?>/hotspot/profile/update";
|
||||
|
||||
// Populate Hidden ID
|
||||
const idInput = form.querySelector('#form-id');
|
||||
idInput.disabled = false;
|
||||
idInput.value = row.dataset.id;
|
||||
|
||||
// Populate Fields
|
||||
form.querySelector('[name="name"]').value = row.dataset.name || '';
|
||||
form.querySelector('[name="shared-users"]').value = row.dataset.sharedUsers || '1';
|
||||
form.querySelector('[name="rate-limit"]').value = row.dataset.rateLimit || '';
|
||||
|
||||
// Selects
|
||||
if(form.querySelector('[name="address-pool"]')) form.querySelector('[name="address-pool"]').value = row.dataset.addressPool;
|
||||
if(form.querySelector('[name="parent-queue"]')) form.querySelector('[name="parent-queue"]').value = row.dataset.parentQueue;
|
||||
if(form.querySelector('[name="expired_mode"]')) form.querySelector('[name="expired_mode"]').value = row.dataset.expiredMode;
|
||||
if(form.querySelector('[name="lock_user"]')) form.querySelector('[name="lock_user"]').value = row.dataset.lockUser;
|
||||
|
||||
// Validity
|
||||
form.querySelector('[name="validity_d"]').value = row.dataset.valD || '';
|
||||
form.querySelector('[name="validity_h"]').value = row.dataset.valH || '';
|
||||
form.querySelector('[name="validity_m"]').value = row.dataset.valM || '';
|
||||
|
||||
// Prices
|
||||
form.querySelector('[name="price"]').value = row.dataset.price || '';
|
||||
form.querySelector('[name="selling_price"]').value = row.dataset.sellingPrice || '';
|
||||
|
||||
// Initial Toggle Check
|
||||
toggleValidity();
|
||||
}
|
||||
};
|
||||
|
||||
Mivo.modal.form(title, template, saveBtn, preConfirmFn, onOpenedFn, 'swal-wide');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template id="profile-form-template">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 text-left">
|
||||
<!-- Form Column -->
|
||||
<div class="lg:col-span-2">
|
||||
<form id="profile-form" action="/<?= htmlspecialchars($session) ?>/hotspot/profile/store" method="POST" class="space-y-4">
|
||||
<input type="hidden" name="session" value="<?= htmlspecialchars($session) ?>">
|
||||
<input type="hidden" name="id" id="form-id" disabled>
|
||||
|
||||
<!-- Name -->
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="common.name">Name</label>
|
||||
<input type="text" name="name" required class="w-full" data-i18n-placeholder="hotspot_profiles.form.name_placeholder" placeholder="e.g. 1Hour-Package">
|
||||
</div>
|
||||
|
||||
<!-- Pools & Shared Users -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.address_pool">Address Pool</label>
|
||||
<select name="address-pool" class="w-full">
|
||||
<option value="none" data-i18n="common.forms.none">none</option>
|
||||
<?php foreach ($pools as $pool): ?>
|
||||
<?php if(isset($pool['name'])): ?>
|
||||
<option value="<?= htmlspecialchars($pool['name']) ?>"><?= htmlspecialchars($pool['name']) ?></option>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.shared_users">Shared Users</label>
|
||||
<input type="number" name="shared-users" value="1" min="1" class="w-full" placeholder="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rate Limit & Parent Queue -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.rate_limit">Rate Limit (Rx/Tx)</label>
|
||||
<input type="text" name="rate-limit" class="w-full" data-i18n-placeholder="hotspot_profiles.form.rate_limit_help" placeholder="e.g. 512k/1M">
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.parent_queue">Parent Queue</label>
|
||||
<select name="parent-queue" class="w-full">
|
||||
<option value="none" data-i18n="common.forms.none">none</option>
|
||||
<?php foreach ($queues as $q): ?>
|
||||
<option value="<?= htmlspecialchars($q) ?>"><?= htmlspecialchars($q) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expired Mode & Validity -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.expired_mode">Expired Mode</label>
|
||||
<select name="expired_mode" id="expired-mode" class="w-full">
|
||||
<option value="none" data-i18n="common.forms.none" selected>none</option>
|
||||
<option value="rem">Remove</option>
|
||||
<option value="ntf">Notice</option>
|
||||
<option value="remc">Remove & Record</option>
|
||||
<option value="ntfc">Notice & Record</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="validity-group" class="hidden space-y-1 transition-all">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.validity">Validity</label>
|
||||
<div class="flex w-full">
|
||||
<input type="number" name="validity_d" min="0" class="w-full text-center rounded-r-none border-r-0" placeholder="0D">
|
||||
<input type="number" name="validity_h" min="0" class="w-full text-center rounded-none border-r-0" placeholder="0H">
|
||||
<input type="number" name="validity_m" min="0" class="w-full text-center rounded-l-none" placeholder="0M">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prices -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.price">Price (Rp)</label>
|
||||
<input type="number" name="price" class="w-full" placeholder="e.g. 5000">
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.selling_price">Selling Price (Rp)</label>
|
||||
<input type="number" name="selling_price" class="w-full" placeholder="e.g. 7000">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lock User -->
|
||||
<div class="space-y-1">
|
||||
<label class="form-label" data-i18n="hotspot_profiles.form.lock_user">Lock User</label>
|
||||
<select name="lock_user" class="w-full">
|
||||
<option value="Disable" data-i18n="common.forms.disabled">Disable</option>
|
||||
<option value="Enable" data-i18n="common.forms.enabled">Enable</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="h-12"></div> <!-- Spacer for selects -->
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tips Column -->
|
||||
<div class="hidden lg:block space-y-4 border-l border-white/10 pl-6">
|
||||
<h3 class="text-sm font-bold text-foreground flex items-center gap-2">
|
||||
<i data-lucide="lightbulb" class="w-4 h-4 text-yellow-500"></i>
|
||||
<span data-i18n="hotspot_profiles.form.quick_tips">Quick Tips</span>
|
||||
</h3>
|
||||
<ul class="text-xs text-accents-5 space-y-3 list-disc pl-4">
|
||||
<li data-i18n="hotspot_profiles.form.tip_rate_limit">
|
||||
<strong>Rate Limit</strong>: Rx/Tx (Upload/Download). Example: <code>512k/1M</code>
|
||||
</li>
|
||||
<li data-i18n="hotspot_profiles.form.tip_expired_mode">
|
||||
<strong>Expired Mode</strong>: Select 'Remove' or 'Notice' to enable Validity.
|
||||
</li>
|
||||
<li data-i18n="hotspot_profiles.form.tip_parent_queue">
|
||||
<strong>Parent Queue</strong>: Assigns users to a specific parent queue for bandwidth management.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user