up
This commit is contained in:
@@ -39,6 +39,17 @@
|
|||||||
Users
|
Users
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
to="/restaurants"
|
||||||
|
class="flex items-center px-4 py-3 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors"
|
||||||
|
:class="{ 'bg-primary-50 text-primary-700': $route.path === '/restaurants' }"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||||
|
</svg>
|
||||||
|
Restaurants
|
||||||
|
</router-link>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
to="/settings"
|
to="/settings"
|
||||||
class="flex items-center px-4 py-3 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors"
|
class="flex items-center px-4 py-3 text-gray-700 rounded-lg hover:bg-gray-100 transition-colors"
|
||||||
@@ -111,28 +122,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, ref} from 'vue'
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const userName = ref('Loading...')
|
||||||
|
const userLogin = ref('')
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/admin/me')
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json()
|
||||||
|
userLogin.value = data.login
|
||||||
|
userName.value = data.login // или можно сделать красивое отображение
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
userName.value = 'User'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const userName = ref('Admin User')
|
|
||||||
const userInitials = computed(() => {
|
const userInitials = computed(() => {
|
||||||
return userName.value.split(' ').map(n => n[0]).join('').toUpperCase()
|
return (userName.value[0] || 'U').toUpperCase()
|
||||||
})
|
})
|
||||||
|
|
||||||
const pageTitle = computed(() => {
|
|
||||||
const titles: Record<string, string> = {
|
|
||||||
'/dashboard': 'Dashboard',
|
|
||||||
'/users': 'Users Management',
|
|
||||||
'/settings': 'Settings'
|
|
||||||
}
|
|
||||||
return titles[route.path] || 'Admin Panel'
|
|
||||||
})
|
|
||||||
|
|
||||||
async function logout() {
|
|
||||||
await fetch('/api/logout', { method: 'POST' })
|
|
||||||
router.push('/login')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,19 +2,29 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||||||
import Login from '../views/auth/Login.vue'
|
import Login from '../views/auth/Login.vue'
|
||||||
import Setup from '../views/auth/Setup.vue'
|
import Setup from '../views/auth/Setup.vue'
|
||||||
import Dashboard from '../views/Dashboard.vue'
|
import Dashboard from '../views/Dashboard.vue'
|
||||||
|
import Users from '../views/Users.vue'
|
||||||
|
import Restaurants from '../views/Restaurants.vue'
|
||||||
import NotFound from '../views/NotFound.vue'
|
import NotFound from '../views/NotFound.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/login', component: Login, meta: { title: 'Login' } },
|
{ path: '/login', component: Login, meta: { title: 'Login' } },
|
||||||
{ path: '/setup', component: Setup, meta: { title: 'Setup' } },
|
{ path: '/setup', component: Setup, meta: { title: 'Setup' } },
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: '/dashboard'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
component: Dashboard,
|
component: Dashboard,
|
||||||
meta: { requiresAuth: true, title: 'Dashboard' }
|
meta: { requiresAuth: true, title: 'Dashboard' }
|
||||||
},
|
},
|
||||||
{
|
{ path: '/users',
|
||||||
path: '/',
|
component: Users,
|
||||||
redirect: '/dashboard'
|
meta: { requiresAuth: true, title: 'Users' }
|
||||||
|
},
|
||||||
|
{ path: '/restaurants',
|
||||||
|
component: Restaurants,
|
||||||
|
meta: { requiresAuth: true, title: 'Restaurants' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
@@ -46,6 +56,18 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
console.error('Failed to check status', e)
|
console.error('Failed to check status', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (to.path === '/login') {
|
||||||
|
try {
|
||||||
|
const meRes = await fetch('/api/admin/me');
|
||||||
|
if (meRes.ok) {
|
||||||
|
next('/dashboard');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// игнорируем ошибку, продолжаем
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check authentication
|
// Check authentication
|
||||||
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
|
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div v-for="service in systemServices" :key="service.name" class="flex items-center justify-between">
|
<div v-for="service in systemServices" :key="service.name" class="flex items-center justify-between">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<div :class="['w-2 h-2 rounded-full', service.status === 'healthy' ? 'bg-green-500' : 'bg-red-500']"></div>
|
<div :class="['w-2 h-2 rounded-full', service.status === 'up' ? 'bg-green-500' : 'bg-red-500']"></div>
|
||||||
<span class="text-gray-700">{{ service.name }}</span>
|
<span class="text-gray-700">{{ service.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-gray-500">{{ service.latency }}ms</span>
|
<span class="text-sm text-gray-500">{{ service.latency }}ms</span>
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
import AppLayout from '../components/Layout/AppLayout.vue'
|
import AppLayout from '../components/Layout/AppLayout.vue'
|
||||||
|
|
||||||
const stats = ref({
|
const stats = ref({
|
||||||
@@ -126,12 +126,34 @@ const stats = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const recentUsers = ref([])
|
const recentUsers = ref([])
|
||||||
const systemServices = ref([
|
const systemServices = ref([])
|
||||||
{ name: 'Database', status: 'healthy', latency: 12 },
|
|
||||||
{ name: 'Redis Cache', status: 'healthy', latency: 3 },
|
async function loadHealth() {
|
||||||
{ name: 'API Gateway', status: 'healthy', latency: 45 },
|
try {
|
||||||
{ name: 'File Storage', status: 'healthy', latency: 28 }
|
const res = await fetch('/api/health')
|
||||||
])
|
const data = await res.json()
|
||||||
|
if (data.checks) {
|
||||||
|
systemServices.value = data.checks.map(check => ({
|
||||||
|
name: check.data?.name || check.id,
|
||||||
|
status: check.status.toLowerCase(),
|
||||||
|
latency: check.data?.latency_ms || 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Health check failed', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let interval: number
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadData()
|
||||||
|
await loadHealth()
|
||||||
|
interval = window.setInterval(loadHealth, 5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (interval) clearInterval(interval)
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadData()
|
await loadData()
|
||||||
|
|||||||
140
frontend/src/views/Restaurants.vue
Normal file
140
frontend/src/views/Restaurants.vue
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<AppLayout>
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900">Restaurants</h1>
|
||||||
|
<button @click="openModal('create')" class="btn-primary">+ Add Restaurant</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<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">Created</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
|
<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 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>
|
||||||
|
<button @click="deleteRestaurant(rest.id)" class="text-red-600 hover:text-red-800">Delete</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div v-if="modalOpen" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<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" />
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
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 modalTitle = ref('');
|
||||||
|
|
||||||
|
async function loadRestaurants() {
|
||||||
|
const res = await fetch('/api/admin/restaurants');
|
||||||
|
restaurants.value = await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateStr: string) {
|
||||||
|
if (!dateStr) return '-';
|
||||||
|
return new Date(dateStr).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal(mode: 'create' | 'edit', rest: any = null) {
|
||||||
|
modalMode.value = mode;
|
||||||
|
if (mode === 'create') {
|
||||||
|
form.value = { id: null, name: '', login: '', password: '', host: '' };
|
||||||
|
modalTitle.value = 'Create Restaurant';
|
||||||
|
} else {
|
||||||
|
form.value = { id: rest.id, name: rest.name, login: rest.login, password: '', host: rest.host };
|
||||||
|
modalTitle.value = 'Edit Restaurant';
|
||||||
|
}
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
modalOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitRestaurant() {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
name: form.value.name,
|
||||||
|
login: form.value.login,
|
||||||
|
host: form.value.host,
|
||||||
|
...(form.value.password ? { password: form.value.password } : {})
|
||||||
|
};
|
||||||
|
if (modalMode.value === 'create') {
|
||||||
|
await fetch('/api/admin/restaurants', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await fetch(`/api/admin/restaurants/${form.value.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await loadRestaurants();
|
||||||
|
closeModal();
|
||||||
|
} catch (e) {
|
||||||
|
alert('Operation failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRestaurant(id: number) {
|
||||||
|
if (confirm('Are you sure?')) {
|
||||||
|
await fetch(`/api/admin/restaurants/${id}`, { method: 'DELETE' });
|
||||||
|
await loadRestaurants();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadRestaurants);
|
||||||
|
</script>
|
||||||
124
frontend/src/views/Users.vue
Normal file
124
frontend/src/views/Users.vue
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<AppLayout>
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900">Users Management</h1>
|
||||||
|
<button @click="openModal('create')" class="btn-primary">+ Add User</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<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">Login</th>
|
||||||
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">IP</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>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
|
<tr v-for="user in users" :key="user.id">
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.id }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.login }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ user.ip || '-' }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ formatDate(user.created) }}</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm space-x-2">
|
||||||
|
<button @click="openModal('edit', user)" class="text-blue-600 hover:text-blue-800">Edit</button>
|
||||||
|
<button @click="deleteUser(user.id)" class="text-red-600 hover:text-red-800">Delete</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div v-if="modalOpen" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<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="submitUser">
|
||||||
|
<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>
|
||||||
|
<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" />
|
||||||
|
<p v-if="modalMode === 'edit'" class="text-xs text-gray-500 mt-1">Leave blank to keep current password</p>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AppLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import AppLayout from '../components/Layout/AppLayout.vue';
|
||||||
|
|
||||||
|
const users = ref([]);
|
||||||
|
const modalOpen = ref(false);
|
||||||
|
const modalMode = ref<'create' | 'edit'>('create');
|
||||||
|
const form = ref({ id: null, login: '', password: '' });
|
||||||
|
const modalTitle = ref('');
|
||||||
|
|
||||||
|
async function loadUsers() {
|
||||||
|
const res = await fetch('/api/admin/users');
|
||||||
|
users.value = await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateStr: string) {
|
||||||
|
if (!dateStr) return '-';
|
||||||
|
return new Date(dateStr).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal(mode: 'create' | 'edit', user: any = null) {
|
||||||
|
modalMode.value = mode;
|
||||||
|
if (mode === 'create') {
|
||||||
|
form.value = { id: null, login: '', password: '' };
|
||||||
|
modalTitle.value = 'Create User';
|
||||||
|
} else {
|
||||||
|
form.value = { id: user.id, login: user.login, password: '' };
|
||||||
|
modalTitle.value = 'Edit User';
|
||||||
|
}
|
||||||
|
modalOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
modalOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitUser() {
|
||||||
|
try {
|
||||||
|
if (modalMode.value === 'create') {
|
||||||
|
await fetch('/api/admin/users', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ login: form.value.login, password: form.value.password })
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await fetch(`/api/admin/users/${form.value.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ login: form.value.login, password: form.value.password || undefined })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await loadUsers();
|
||||||
|
closeModal();
|
||||||
|
} catch (e) {
|
||||||
|
alert('Operation failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteUser(id: number) {
|
||||||
|
if (confirm('Are you sure?')) {
|
||||||
|
await fetch(`/api/admin/users/${id}`, { method: 'DELETE' });
|
||||||
|
await loadUsers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadUsers);
|
||||||
|
</script>
|
||||||
50
src/main/java/su/xserver/iikocon/DateRangeSetup.java
Normal file
50
src/main/java/su/xserver/iikocon/DateRangeSetup.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package su.xserver.iikocon;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
|
||||||
|
public class DateRangeSetup {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Параметры по умолчанию
|
||||||
|
String login = "4444";
|
||||||
|
String password = "4444";
|
||||||
|
String server = "folk-amber-co.iiko.it";
|
||||||
|
String presetId = "7ddc40c3-9d5f-408f-aa1e-652964b36c6c";
|
||||||
|
|
||||||
|
// Вычисление dateFrom и dateTo
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
LocalDate dateFrom = today.minusDays(7);
|
||||||
|
LocalDate dateTo = today;
|
||||||
|
|
||||||
|
// Переопределение из аргументов командной строки
|
||||||
|
if (args.length > 0 && args[0] != null && !args[0].isEmpty()) {
|
||||||
|
try {
|
||||||
|
dateFrom = LocalDate.parse(args[0]);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
System.err.println("Ошибка парсинга dateFrom: " + args[0] + ". Используется значение по умолчанию.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length > 1 && args[1] != null && !args[1].isEmpty()) {
|
||||||
|
try {
|
||||||
|
dateTo = LocalDate.parse(args[1]);
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
System.err.println("Ошибка парсинга dateTo: " + args[1] + ". Используется значение по умолчанию.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Форматирование дат в YYYY-MM-DD
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
String formattedDateFrom = dateFrom.format(formatter);
|
||||||
|
String formattedDateTo = dateTo.format(formatter);
|
||||||
|
|
||||||
|
// Вывод переменных (можно заменить на дальнейшее использование)
|
||||||
|
System.out.println("login=" + login);
|
||||||
|
System.out.println("password=" + password);
|
||||||
|
System.out.println("server=" + server);
|
||||||
|
System.out.println("presetId=" + presetId);
|
||||||
|
System.out.println("dateFrom=" + formattedDateFrom);
|
||||||
|
System.out.println("dateTo=" + formattedDateTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -156,6 +156,42 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
router.post("/api/admin/users").handler(rc -> {
|
||||||
|
JsonObject body = rc.body().asJsonObject();
|
||||||
|
String login = body.getString("login");
|
||||||
|
String password = body.getString("password");
|
||||||
|
String ip = rc.request().remoteAddress().host();
|
||||||
|
if (login == null || password == null) {
|
||||||
|
rc.response().setStatusCode(400).end("Missing login or password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userService.createUser(login, password, ip)
|
||||||
|
.onSuccess(v -> rc.response().setStatusCode(201).end())
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put("/api/admin/users/:id").handler(rc -> {
|
||||||
|
int id = Integer.parseInt(rc.pathParam("id"));
|
||||||
|
JsonObject body = rc.body().asJsonObject();
|
||||||
|
String login = body.getString("login");
|
||||||
|
String password = body.getString("password");
|
||||||
|
String ip = rc.request().remoteAddress().host();
|
||||||
|
if (login == null) {
|
||||||
|
rc.response().setStatusCode(400).end("Missing login");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userService.updateUser(id, login, password, ip)
|
||||||
|
.onSuccess(v -> rc.response().end())
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete("/api/admin/users/:id").handler(rc -> {
|
||||||
|
int id = Integer.parseInt(rc.pathParam("id"));
|
||||||
|
userService.deleteUser(id)
|
||||||
|
.onSuccess(v -> rc.response().end())
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
// Получение текущего пользователя
|
// Получение текущего пользователя
|
||||||
router.get("/api/admin/me").handler(rc -> {
|
router.get("/api/admin/me").handler(rc -> {
|
||||||
Integer userId = rc.session().get("userId");
|
Integer userId = rc.session().get("userId");
|
||||||
@@ -171,6 +207,64 @@ public class MainVerticle extends AbstractVerticle {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/api/admin/restaurants").handler(rc -> restaurantService.getAllRestaurants().onComplete(ar -> {
|
||||||
|
if (ar.succeeded()) {
|
||||||
|
rc.response()
|
||||||
|
.putHeader("Content-Type", "application/json")
|
||||||
|
.end(ar.result().encode());
|
||||||
|
} else {
|
||||||
|
rc.response().setStatusCode(500).end(ar.cause().getMessage());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
router.get("/api/admin/restaurants/:id").handler(rc -> {
|
||||||
|
int id = Integer.parseInt(rc.pathParam("id"));
|
||||||
|
restaurantService.findById(id)
|
||||||
|
.onSuccess(rest -> {
|
||||||
|
if (rest == null) rc.response().setStatusCode(404).end();
|
||||||
|
else rc.response().putHeader("Content-Type", "application/json").end(rest.encode());
|
||||||
|
})
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/api/admin/restaurants").handler(rc -> {
|
||||||
|
JsonObject body = rc.body().asJsonObject();
|
||||||
|
String name = body.getString("name");
|
||||||
|
String login = body.getString("login");
|
||||||
|
String password = body.getString("password");
|
||||||
|
String host = body.getString("host");
|
||||||
|
if (name == null || login == null || password == null || host == null) {
|
||||||
|
rc.response().setStatusCode(400).end("Missing fields");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
restaurantService.createRestaurant(name, login, password, host)
|
||||||
|
.onSuccess(v -> rc.response().setStatusCode(201).end())
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put("/api/admin/restaurants/:id").handler(rc -> {
|
||||||
|
int id = Integer.parseInt(rc.pathParam("id"));
|
||||||
|
JsonObject body = rc.body().asJsonObject();
|
||||||
|
String name = body.getString("name");
|
||||||
|
String login = body.getString("login");
|
||||||
|
String password = body.getString("password");
|
||||||
|
String host = body.getString("host");
|
||||||
|
if (name == null || login == null || host == null) {
|
||||||
|
rc.response().setStatusCode(400).end("Missing required fields");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
restaurantService.updateRestaurant(id, name, login, password, host)
|
||||||
|
.onSuccess(v -> rc.response().end())
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete("/api/admin/restaurants/:id").handler(rc -> {
|
||||||
|
int id = Integer.parseInt(rc.pathParam("id"));
|
||||||
|
restaurantService.deleteRestaurant(id)
|
||||||
|
.onSuccess(v -> rc.response().end())
|
||||||
|
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||||
|
});
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
194
src/main/java/su/xserver/iikocon/ProxyVerticle.java
Normal file
194
src/main/java/su/xserver/iikocon/ProxyVerticle.java
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package su.xserver.iikocon;
|
||||||
|
|
||||||
|
import io.vertx.core.AbstractVerticle;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.core.http.HttpMethod;
|
||||||
|
import io.vertx.core.json.Json;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.Router;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
import io.vertx.ext.web.client.WebClientOptions;
|
||||||
|
import io.vertx.ext.web.codec.BodyCodec;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.HexFormat;
|
||||||
|
|
||||||
|
public class ProxyVerticle extends AbstractVerticle {
|
||||||
|
|
||||||
|
private WebClient webClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Promise<Void> startPromise) {
|
||||||
|
webClient = WebClient.create(vertx, new WebClientOptions()
|
||||||
|
.setSsl(true)
|
||||||
|
.setTrustAll(true)
|
||||||
|
.setVerifyHost(false));
|
||||||
|
|
||||||
|
Router router = Router.router(vertx);
|
||||||
|
router.post("/api/proxy").handler(this::handlePost);
|
||||||
|
router.get("/api/proxy").handler(this::handleGet);
|
||||||
|
|
||||||
|
int port = 8080;
|
||||||
|
vertx.createHttpServer()
|
||||||
|
.requestHandler(router)
|
||||||
|
.listen(port).onComplete(http -> {
|
||||||
|
if (http.succeeded()) {
|
||||||
|
System.out.println("Proxy server started on port " + port);
|
||||||
|
startPromise.complete();
|
||||||
|
} else {
|
||||||
|
startPromise.fail(http.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePost(RoutingContext ctx) {
|
||||||
|
String apiServer = System.getenv("IIKO_API_SERVER");
|
||||||
|
String apiLogin = System.getenv("IIKO_API_LOGIN");
|
||||||
|
String apiPass = System.getenv("IIKO_API_PASS");
|
||||||
|
String externalEndpoint = System.getenv("IIKO_API_ENDPOINT");
|
||||||
|
if (externalEndpoint == null || externalEndpoint.isBlank()) {
|
||||||
|
externalEndpoint = "/your-endpoint";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiServer == null || apiLogin == null || apiPass == null) {
|
||||||
|
fail(ctx, 500, "Missing required environment variables: IIKO_API_SERVER, IIKO_API_LOGIN, IIKO_API_PASS");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject body = ctx.body().asJsonObject();
|
||||||
|
if (body == null) {
|
||||||
|
fail(ctx, 400, "Request body must be JSON");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String signature = sha1(apiPass);
|
||||||
|
String authUrl = "https://" + apiServer + ":443/resto/api/auth?login=" + apiLogin + "&pass=" + signature;
|
||||||
|
String finalExternalEndpoint = externalEndpoint;
|
||||||
|
webClient.getAbs(authUrl)
|
||||||
|
.as(BodyCodec.string())
|
||||||
|
.send()
|
||||||
|
.onSuccess(authResp -> {
|
||||||
|
if (authResp.statusCode() != 200) {
|
||||||
|
fail(ctx, authResp.statusCode(), "Authentication failed: " + authResp.statusMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String token = authResp.body();
|
||||||
|
String targetUrl = "https://" + apiServer + finalExternalEndpoint;
|
||||||
|
webClient.request(HttpMethod.POST, targetUrl)
|
||||||
|
.putHeader("Content-Type", "application/json")
|
||||||
|
.as(BodyCodec.jsonObject())
|
||||||
|
.sendJsonObject(body)
|
||||||
|
.onSuccess(apiResp -> {
|
||||||
|
webClient.getAbs("https://" + apiServer + ":443/resto/api/logout?key=" + token)
|
||||||
|
.send()
|
||||||
|
.onFailure(err -> System.err.println("Logout failed: " + err.getMessage()));
|
||||||
|
if (apiResp.statusCode() == 200) {
|
||||||
|
ctx.response().setStatusCode(200).end(apiResp.body().encode());
|
||||||
|
} else {
|
||||||
|
fail(ctx, apiResp.statusCode(), "External API error: " + apiResp.statusMessage());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onFailure(err -> fail(ctx, 500, "Request to external API failed: " + err.getMessage()));
|
||||||
|
})
|
||||||
|
.onFailure(err -> fail(ctx, 500, "Auth request failed: " + err.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGet(RoutingContext ctx) {
|
||||||
|
String presetId = ctx.queryParam("presetId").stream().findFirst().orElse(null);
|
||||||
|
String dateFrom = ctx.queryParam("dateFrom").stream().findFirst().orElse(null);
|
||||||
|
String dateTo = ctx.queryParam("dateTo").stream().findFirst().orElse(null);
|
||||||
|
String server = ctx.queryParam("server").stream().findFirst().orElse(null);
|
||||||
|
String password = ctx.queryParam("password").stream().findFirst().orElse(null);
|
||||||
|
String login = ctx.queryParam("login").stream().findFirst().orElse(null);
|
||||||
|
String type = ctx.queryParam("type").stream().findFirst().orElse(null);
|
||||||
|
String rootType = ctx.queryParam("rootType").stream().findFirst().orElse(null);
|
||||||
|
|
||||||
|
if (server == null || login == null || password == null) {
|
||||||
|
fail(ctx, 400, "Missing required parameters: server, login, password");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String signature = sha1(password);
|
||||||
|
String authUrl = "https://" + server + ":443/resto/api/auth?login=" + login + "&pass=" + signature;
|
||||||
|
webClient.getAbs(authUrl)
|
||||||
|
.as(BodyCodec.string())
|
||||||
|
.send()
|
||||||
|
.onSuccess(authResp -> {
|
||||||
|
if (authResp.statusCode() != 200) {
|
||||||
|
fail(ctx, authResp.statusCode(), "Authentication failed: " + authResp.statusMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String token = authResp.body();
|
||||||
|
String dataUrl;
|
||||||
|
if ("entity".equals(type)) {
|
||||||
|
dataUrl = "https://" + server + "/resto/api/v2/entities/list?key=" + token;
|
||||||
|
if (rootType != null && !rootType.isBlank()) {
|
||||||
|
dataUrl += "&rootType=" + rootType;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (presetId == null || dateFrom == null || dateTo == null) {
|
||||||
|
fail(ctx, 400, "Missing presetId, dateFrom or dateTo for report request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dataUrl = "https://" + server + "/resto/api/v2/reports/olap/byPresetId/" + presetId +
|
||||||
|
"?key=" + token + "&dateFrom=" + dateFrom + "&dateTo=" + dateTo;
|
||||||
|
}
|
||||||
|
System.out.println("URL: " + dataUrl);
|
||||||
|
webClient.getAbs(dataUrl)
|
||||||
|
.as(BodyCodec.jsonObject())
|
||||||
|
.send()
|
||||||
|
.onSuccess(dataResp -> {
|
||||||
|
// logout (fire and forget)
|
||||||
|
webClient.getAbs("https://" + server + ":443/resto/api/logout?key=" + token)
|
||||||
|
.send()
|
||||||
|
.onFailure(err -> System.err.println("Logout failed: " + err.getMessage()));
|
||||||
|
if (dataResp.statusCode() == 200) {
|
||||||
|
JsonObject responseBody = dataResp.body();
|
||||||
|
if ("entity".equals(type)) {
|
||||||
|
ctx.response().setStatusCode(200).end(responseBody.encode());
|
||||||
|
} else {
|
||||||
|
Object data = responseBody.getValue("data");
|
||||||
|
if (data == null) {
|
||||||
|
ctx.response().setStatusCode(200).end(responseBody.encode());
|
||||||
|
} else {
|
||||||
|
// data может быть массивом, объектом или другим типом
|
||||||
|
ctx.response().setStatusCode(200).end(Json.encode(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail(ctx, dataResp.statusCode(), "External API error: " + dataResp.statusMessage());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onFailure(err -> fail(ctx, 500, "Data request failed: " + err.getMessage()));
|
||||||
|
})
|
||||||
|
.onFailure(err -> fail(ctx, 500, "Auth request failed: " + err.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sha1(String input) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||||
|
byte[] digest = md.digest(input.getBytes());
|
||||||
|
return HexFormat.of().formatHex(digest).toLowerCase();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fail(RoutingContext ctx, int status, String message) {
|
||||||
|
System.err.println("Error: " + message);
|
||||||
|
ctx.response().setStatusCode(status).end(new JsonObject().put("error", message).encode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// > GET /api/proxy?server=folk-amber-co.iiko.it&login=4444&password=4444&presetId=7ddc40c3-9d5f-408f-aa1e-652964b36c6c&dateFrom=2026-04-10&dateTo=2026-04-17 HTTP/1.1
|
||||||
|
// > Host: localhost:8080
|
||||||
|
// > access-token: ddb4ab653b9194ec1ea5448cee2a8a26282b0866c1d4a86e98e9b0f84bc91944
|
||||||
|
// > User-Agent: v2raytun/ios
|
||||||
|
// > X-App-Version: 2.4.3
|
||||||
|
// > X-Device-Model: iPhone 11 Pro
|
||||||
|
// > X-Device-OS: iOS
|
||||||
|
// > X-HWID: HHS8JDJN-F2EB-HFBS-KMWX-234FA7B95JSC
|
||||||
|
// > X-Ver-OS: 26.0
|
||||||
|
// > Accept: */*
|
||||||
@@ -90,4 +90,45 @@ public class RestaurantService {
|
|||||||
return array;
|
return array;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Future<JsonObject> findById(int id) {
|
||||||
|
return SqlTemplate.forQuery(pool,
|
||||||
|
"SELECT id, name, login, password, host, created, updated FROM restaurants WHERE id = #{id}")
|
||||||
|
.mapTo(row -> new JsonObject()
|
||||||
|
.put("id", row.getInteger("id"))
|
||||||
|
.put("name", row.getString("name"))
|
||||||
|
.put("login", row.getString("login"))
|
||||||
|
.put("password", row.getString("password"))
|
||||||
|
.put("host", row.getString("host"))
|
||||||
|
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
|
||||||
|
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null))
|
||||||
|
.execute(Collections.singletonMap("id", id))
|
||||||
|
.map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Void> updateRestaurant(int id, String name, String login, String password, String host) {
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("id", id);
|
||||||
|
params.put("name", name);
|
||||||
|
params.put("login", login);
|
||||||
|
params.put("host", host);
|
||||||
|
|
||||||
|
String sql;
|
||||||
|
if (password != null && !password.isEmpty()) {
|
||||||
|
params.put("password", password);
|
||||||
|
sql = "UPDATE restaurants SET name = #{name}, login = #{login}, password = #{password}, host = #{host} WHERE id = #{id}";
|
||||||
|
} else {
|
||||||
|
sql = "UPDATE restaurants SET name = #{name}, login = #{login}, host = #{host} WHERE id = #{id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return SqlTemplate.forUpdate(pool, sql)
|
||||||
|
.execute(params)
|
||||||
|
.mapEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Void> deleteRestaurant(int id) {
|
||||||
|
return SqlTemplate.forUpdate(pool, "DELETE FROM restaurants WHERE id = #{id}")
|
||||||
|
.execute(Collections.singletonMap("id", id))
|
||||||
|
.mapEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,32 @@ public class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Future<Void> updateUser(int id, String login, String password, String ip) {
|
||||||
|
Map<String, Object> params = new HashMap<>();
|
||||||
|
params.put("id", id);
|
||||||
|
params.put("login", login);
|
||||||
|
params.put("ip", ip);
|
||||||
|
|
||||||
|
String sql;
|
||||||
|
if (password != null && !password.isEmpty()) {
|
||||||
|
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
||||||
|
params.put("password", hash);
|
||||||
|
sql = "UPDATE users SET login = #{login}, password = #{password}, ip = #{ip} WHERE id = #{id}";
|
||||||
|
} else {
|
||||||
|
sql = "UPDATE users SET login = #{login}, ip = #{ip} WHERE id = #{id}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return SqlTemplate.forUpdate(pool, sql)
|
||||||
|
.execute(params)
|
||||||
|
.mapEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<Void> deleteUser(int id) {
|
||||||
|
return SqlTemplate.forUpdate(pool, "DELETE FROM users WHERE id = #{id}")
|
||||||
|
.execute(Collections.singletonMap("id", id))
|
||||||
|
.mapEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean checkPassword(String plain, String hash) {
|
public boolean checkPassword(String plain, String hash) {
|
||||||
try {
|
try {
|
||||||
return BCrypt.checkpw(plain, hash);
|
return BCrypt.checkpw(plain, hash);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public class HealthCheckService {
|
|||||||
long time = System.currentTimeMillis() - start;
|
long time = System.currentTimeMillis() - start;
|
||||||
if ("PONG".equalsIgnoreCase(response.toString())) {
|
if ("PONG".equalsIgnoreCase(response.toString())) {
|
||||||
JsonObject data = new JsonObject()
|
JsonObject data = new JsonObject()
|
||||||
|
.put("name", "redis")
|
||||||
.put("latency_ms", time);
|
.put("latency_ms", time);
|
||||||
future.complete(Status.OK(data));
|
future.complete(Status.OK(data));
|
||||||
} else {
|
} else {
|
||||||
@@ -47,6 +48,7 @@ public class HealthCheckService {
|
|||||||
.onSuccess(rs -> {
|
.onSuccess(rs -> {
|
||||||
long time = System.currentTimeMillis() - start;
|
long time = System.currentTimeMillis() - start;
|
||||||
JsonObject data = new JsonObject()
|
JsonObject data = new JsonObject()
|
||||||
|
.put("name", "database")
|
||||||
.put("latency_ms", time);
|
.put("latency_ms", time);
|
||||||
future.complete(Status.OK(data));
|
future.complete(Status.OK(data));
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user