This commit is contained in:
2026-04-20 13:42:41 +03:00
parent fd3cbb019f
commit ec0671c5e8
16 changed files with 465 additions and 117 deletions

View File

@@ -11,8 +11,9 @@
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Login</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Host</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">HTTPS</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Login</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Created</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
</tr>
@@ -21,8 +22,16 @@
<tr v-for="rest in restaurants" :key="rest.id">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ rest.id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ rest.name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ rest.login }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ rest.host }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<input
type="checkbox"
:checked="rest.https"
@change="toggleHttps(rest)"
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500 w-4 h-4"
/>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ rest.login }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ formatDate(rest.created) }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm space-x-2">
<button @click="openModal('edit', rest)" class="text-blue-600 hover:text-blue-800">Edit</button>
@@ -38,23 +47,38 @@
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<h2 class="text-xl font-bold mb-4">{{ modalTitle }}</h2>
<form @submit.prevent="submitRestaurant">
<!-- 1. Имя ресторана -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700">Name</label>
<input v-model="form.name" type="text" required class="input-field mt-1" />
</div>
<!-- 2. Хост -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700">Host</label>
<input v-model="form.host" type="text" required class="input-field mt-1" />
</div>
<!-- 3. HTTPS чекбокс -->
<div class="mb-4 flex items-center">
<input type="checkbox" v-model="form.https" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500 w-4 h-4 mr-2" />
<label class="text-sm font-medium text-gray-700">HTTPS</label>
</div>
<!-- 4. Логин -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700">Login</label>
<input v-model="form.login" type="text" required class="input-field mt-1" />
</div>
<!-- 5. Пароль (отключаем автозаполнение) -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700">Password</label>
<input v-model="form.password" :required="modalMode === 'create'" type="password" class="input-field mt-1" />
<input
v-model="form.password"
:required="modalMode === 'create'"
type="password"
<!-- autocomplete="new-password" -->
class="input-field mt-1"
/>
<p v-if="modalMode === 'edit'" class="text-xs text-gray-500 mt-1">Leave blank to keep current password</p>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700">Host</label>
<input v-model="form.host" type="text" required class="input-field mt-1" />
</div>
<div class="flex justify-end space-x-2">
<button type="button" @click="closeModal" class="btn-secondary">Cancel</button>
<button type="submit" class="btn-primary">Save</button>
@@ -72,7 +96,7 @@ import AppLayout from '../components/Layout/AppLayout.vue';
const restaurants = ref([]);
const modalOpen = ref(false);
const modalMode = ref<'create' | 'edit'>('create');
const form = ref({ id: null, name: '', login: '', password: '', host: '' });
const form = ref({ id: null, name: '', login: '', password: '', host: '', https: false });
const modalTitle = ref('');
async function loadRestaurants() {
@@ -88,10 +112,17 @@ function formatDate(dateStr: string) {
function openModal(mode: 'create' | 'edit', rest: any = null) {
modalMode.value = mode;
if (mode === 'create') {
form.value = { id: null, name: '', login: '', password: '', host: '' };
form.value = { id: null, name: '', login: '', password: '', host: '', https: false };
modalTitle.value = 'Create Restaurant';
} else {
form.value = { id: rest.id, name: rest.name, login: rest.login, password: '', host: rest.host };
form.value = {
id: rest.id,
name: rest.name,
login: rest.login,
password: '',
host: rest.host,
https: rest.https || false
};
modalTitle.value = 'Edit Restaurant';
}
modalOpen.value = true;
@@ -101,31 +132,65 @@ function closeModal() {
modalOpen.value = false;
}
async function toggleHttps(rest: any) {
const newHttps = !rest.https;
const payload = {
name: rest.name,
host: rest.host,
login: rest.login,
https: newHttps
// пароль не передаём, он останется прежним
};
try {
const res = await fetch(`/api/admin/restaurants/${rest.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.ok) {
// Обновляем локальное состояние или перезагружаем список
rest.https = newHttps;
// Альтернатива: await loadRestaurants();
} else {
alert('Failed to update HTTPS status');
}
} catch (e) {
alert('Network error');
}
}
async function submitRestaurant() {
try {
const payload = {
name: form.value.name,
login: form.value.login,
host: form.value.host,
https: form.value.https,
login: form.value.login,
...(form.value.password ? { password: form.value.password } : {})
};
if (modalMode.value === 'create') {
await fetch('/api/admin/restaurants', {
if (!form.value.password) {
alert('Password is required');
return;
}
const res = await fetch('/api/admin/restaurants', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error('Create failed');
} else {
await fetch(`/api/admin/restaurants/${form.value.id}`, {
const res = await fetch(`/api/admin/restaurants/${form.value.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error('Update failed');
}
await loadRestaurants();
closeModal();
} catch (e) {
alert('Operation failed');
alert('Operation failed: ' + e.message);
}
}

View File

@@ -24,13 +24,13 @@
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.login }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.email }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button
<input
v-if="user.id !== currentUserId"
@click="toggleActive(user)"
:class="user.active ? 'text-green-600' : 'text-red-600'"
>
{{ user.active ? 'Active' : 'Inactive' }}
</button>
type="checkbox"
:checked="user.active"
@change="toggleActive(user)"
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500 w-4 h-4"
/>
<span v-else class="text-gray-400 text-sm">(You)</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">{{ user.ip || '-' }}</td>

View File

@@ -67,7 +67,11 @@
<input type="checkbox" class="rounded border-gray-300 text-primary-600 focus:ring-primary-500" />
<span class="ml-2 text-sm text-gray-600">Remember me</span>
</label>
<router-link to="/register" class="text-sm text-primary-600 hover:text-primary-700">
<router-link
v-if="settings.enableRegistration"
to="/register"
class="text-sm text-primary-600 hover:text-primary-700"
>
Create account
</router-link>
</div>
@@ -99,6 +103,9 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useSettingsStore } from '../../stores/settings'
const settings = useSettingsStore()
const router = useRouter()
const form = ref({ login: '', password: '' })