ваше сообщение коммита
This commit is contained in:
@@ -76,9 +76,16 @@ async function reinitPoolFromDbSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// При старте приложения — сразу пробуем инициализировать из db_settings
|
// При старте приложения — убираем автоматический вызов reinitPoolFromDbSettings
|
||||||
if (process.env.NODE_ENV !== 'migration') {
|
// if (process.env.NODE_ENV !== 'migration') {
|
||||||
reinitPoolFromDbSettings();
|
// reinitPoolFromDbSettings();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Экспортируем функцию для явной инициализации пула
|
||||||
|
async function initDbPool() {
|
||||||
|
if (process.env.NODE_ENV !== 'migration') {
|
||||||
|
await reinitPoolFromDbSettings();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для сохранения гостевого сообщения в базе данных
|
// Функция для сохранения гостевого сообщения в базе данных
|
||||||
@@ -99,4 +106,4 @@ async function saveGuestMessageToDatabase(message, language, guestId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Экспортируем функции для работы с базой данных
|
// Экспортируем функции для работы с базой данных
|
||||||
module.exports = { query, getQuery, pool, getPool, setPoolChangeCallback };
|
module.exports = { query, getQuery, pool, getPool, setPoolChangeCallback, initDbPool };
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const db = require('../db');
|
const db = require('../db');
|
||||||
const { requireAuth } = require('../middleware/auth');
|
const { requireAuth } = require('../middleware/auth');
|
||||||
|
const vectorSearchClient = require('../services/vectorSearchClient');
|
||||||
|
|
||||||
router.use((req, res, next) => {
|
router.use((req, res, next) => {
|
||||||
console.log('Tables router received:', req.method, req.originalUrl);
|
console.log('Tables router received:', req.method, req.originalUrl);
|
||||||
@@ -79,6 +80,13 @@ router.post('/:id/rows', async (req, res, next) => {
|
|||||||
'INSERT INTO user_rows (table_id) VALUES ($1) RETURNING *',
|
'INSERT INTO user_rows (table_id) VALUES ($1) RETURNING *',
|
||||||
[tableId]
|
[tableId]
|
||||||
);
|
);
|
||||||
|
// Получаем все строки и значения для upsert
|
||||||
|
const rows = (await db.getQuery()('SELECT r.id as row_id, c.value as text, c2.value as answer FROM user_rows r LEFT JOIN user_cell_values c ON c.row_id = r.id AND c.column_id = 1 LEFT JOIN user_cell_values c2 ON c2.row_id = r.id AND c2.column_id = 2 WHERE r.table_id = $1', [tableId])).rows;
|
||||||
|
const upsertRows = rows.filter(r => r.row_id && r.text).map(r => ({ row_id: r.row_id, text: r.text, metadata: { answer: r.answer } }));
|
||||||
|
console.log('[DEBUG][upsertRows]', upsertRows);
|
||||||
|
if (upsertRows.length > 0) {
|
||||||
|
await vectorSearchClient.upsert(tableId, upsertRows);
|
||||||
|
}
|
||||||
res.json(result.rows[0]);
|
res.json(result.rows[0]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
@@ -94,6 +102,24 @@ router.patch('/cell/:cellId', async (req, res, next) => {
|
|||||||
'UPDATE user_cell_values SET value = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
|
'UPDATE user_cell_values SET value = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
|
||||||
[value, cellId]
|
[value, cellId]
|
||||||
);
|
);
|
||||||
|
// Получаем row_id и table_id
|
||||||
|
const row = (await db.getQuery()('SELECT row_id FROM user_cell_values WHERE id = $1', [cellId])).rows[0];
|
||||||
|
if (row) {
|
||||||
|
const rowId = row.row_id;
|
||||||
|
const table = (await db.getQuery()('SELECT table_id FROM user_rows WHERE id = $1', [rowId])).rows[0];
|
||||||
|
if (table) {
|
||||||
|
const tableId = table.table_id;
|
||||||
|
// Получаем всю строку для upsert
|
||||||
|
const rowData = (await db.getQuery()('SELECT r.id as row_id, c.value as text, c2.value as answer FROM user_rows r LEFT JOIN user_cell_values c ON c.row_id = r.id AND c.column_id = 1 LEFT JOIN user_cell_values c2 ON c2.row_id = r.id AND c2.column_id = 2 WHERE r.id = $1', [rowId])).rows[0];
|
||||||
|
if (rowData) {
|
||||||
|
const upsertRows = [{ row_id: rowData.row_id, text: rowData.text, metadata: { answer: rowData.answer } }].filter(r => r.row_id && r.text);
|
||||||
|
console.log('[DEBUG][upsertRows]', upsertRows);
|
||||||
|
if (upsertRows.length > 0) {
|
||||||
|
await vectorSearchClient.upsert(tableId, upsertRows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
res.json(result.rows[0]);
|
res.json(result.rows[0]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
@@ -110,6 +136,20 @@ router.post('/cell', async (req, res, next) => {
|
|||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[row_id, column_id, value]
|
[row_id, column_id, value]
|
||||||
);
|
);
|
||||||
|
// Получаем table_id
|
||||||
|
const table = (await db.getQuery()('SELECT table_id FROM user_rows WHERE id = $1', [row_id])).rows[0];
|
||||||
|
if (table) {
|
||||||
|
const tableId = table.table_id;
|
||||||
|
// Получаем всю строку для upsert
|
||||||
|
const rowData = (await db.getQuery()('SELECT r.id as row_id, c.value as text, c2.value as answer FROM user_rows r LEFT JOIN user_cell_values c ON c.row_id = r.id AND c.column_id = 1 LEFT JOIN user_cell_values c2 ON c2.row_id = r.id AND c2.column_id = 2 WHERE r.id = $1', [row_id])).rows[0];
|
||||||
|
if (rowData) {
|
||||||
|
const upsertRows = [{ row_id: rowData.row_id, text: rowData.text, metadata: { answer: rowData.answer } }].filter(r => r.row_id && r.text);
|
||||||
|
console.log('[DEBUG][upsertRows]', upsertRows);
|
||||||
|
if (upsertRows.length > 0) {
|
||||||
|
await vectorSearchClient.upsert(tableId, upsertRows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
res.json(result.rows[0]);
|
res.json(result.rows[0]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
@@ -120,7 +160,19 @@ router.post('/cell', async (req, res, next) => {
|
|||||||
router.delete('/row/:rowId', async (req, res, next) => {
|
router.delete('/row/:rowId', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const rowId = req.params.rowId;
|
const rowId = req.params.rowId;
|
||||||
|
// Получаем table_id
|
||||||
|
const table = (await db.getQuery()('SELECT table_id FROM user_rows WHERE id = $1', [rowId])).rows[0];
|
||||||
await db.getQuery()('DELETE FROM user_rows WHERE id = $1', [rowId]);
|
await db.getQuery()('DELETE FROM user_rows WHERE id = $1', [rowId]);
|
||||||
|
if (table) {
|
||||||
|
const tableId = table.table_id;
|
||||||
|
// Получаем все строки для rebuild
|
||||||
|
const rows = (await db.getQuery()('SELECT r.id as row_id, c.value as text, c2.value as answer FROM user_rows r LEFT JOIN user_cell_values c ON c.row_id = r.id AND c.column_id = 1 LEFT JOIN user_cell_values c2 ON c2.row_id = r.id AND c2.column_id = 2 WHERE r.table_id = $1', [tableId])).rows;
|
||||||
|
const rebuildRows = rows.filter(r => r.row_id && r.text).map(r => ({ row_id: r.row_id, text: r.text, metadata: { answer: r.answer } }));
|
||||||
|
console.log('[DEBUG][rebuildRows]', rebuildRows);
|
||||||
|
if (rebuildRows.length > 0) {
|
||||||
|
await vectorSearchClient.rebuild(tableId, rebuildRows);
|
||||||
|
}
|
||||||
|
}
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
@@ -206,6 +258,51 @@ router.patch('/:id', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Получить id колонок с purpose 'question' и 'answer'
|
||||||
|
async function getQuestionAnswerColumnIds(tableId) {
|
||||||
|
const { rows } = await db.getQuery()(
|
||||||
|
`SELECT id, options FROM user_columns WHERE table_id = $1`, [tableId]
|
||||||
|
);
|
||||||
|
let questionCol = null, answerCol = null;
|
||||||
|
for (const col of rows) {
|
||||||
|
if (col.options && col.options.purpose === 'question') questionCol = col.id;
|
||||||
|
if (col.options && col.options.purpose === 'answer') answerCol = col.id;
|
||||||
|
}
|
||||||
|
return { questionCol, answerCol };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пересобрать векторный индекс для таблицы (только для админа)
|
||||||
|
router.post('/:id/rebuild-index', requireAuth, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
if (!req.session.isAdmin) {
|
||||||
|
return res.status(403).json({ error: 'Доступ только для администратора' });
|
||||||
|
}
|
||||||
|
const tableId = req.params.id;
|
||||||
|
const { questionCol, answerCol } = await getQuestionAnswerColumnIds(tableId);
|
||||||
|
if (!questionCol || !answerCol) {
|
||||||
|
return res.status(400).json({ error: 'Не найдены колонки с вопросами и ответами' });
|
||||||
|
}
|
||||||
|
const rows = (await db.getQuery()(
|
||||||
|
`SELECT r.id as row_id, c.value as text, c2.value as answer
|
||||||
|
FROM user_rows r
|
||||||
|
LEFT JOIN user_cell_values c ON c.row_id = r.id AND c.column_id = $2
|
||||||
|
LEFT JOIN user_cell_values c2 ON c2.row_id = r.id AND c2.column_id = $3
|
||||||
|
WHERE r.table_id = $1`,
|
||||||
|
[tableId, questionCol, answerCol]
|
||||||
|
)).rows;
|
||||||
|
const rebuildRows = rows.filter(r => r.row_id && r.text).map(r => ({ row_id: r.row_id, text: r.text, metadata: { answer: r.answer } }));
|
||||||
|
console.log('[DEBUG][rebuildRows]', rebuildRows);
|
||||||
|
if (rebuildRows.length > 0) {
|
||||||
|
await vectorSearchClient.rebuild(tableId, rebuildRows);
|
||||||
|
res.json({ success: true, count: rebuildRows.length });
|
||||||
|
} else {
|
||||||
|
res.status(400).json({ error: 'Нет валидных строк для индексации' });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// DELETE: удалить таблицу и каскадно все связанные строки/столбцы/ячейки (доступно всем)
|
// DELETE: удалить таблицу и каскадно все связанные строки/столбцы/ячейки (доступно всем)
|
||||||
router.delete('/:id', requireAuth, async (req, res, next) => {
|
router.delete('/:id', requireAuth, async (req, res, next) => {
|
||||||
const dbModule = require('../db');
|
const dbModule = require('../db');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const { initWSS } = require('./wsHub');
|
|||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
const { getBot } = require('./services/telegramBot');
|
const { getBot } = require('./services/telegramBot');
|
||||||
const EmailBotService = require('./services/emailBot');
|
const EmailBotService = require('./services/emailBot');
|
||||||
|
const { initDbPool } = require('./db');
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8000;
|
const PORT = process.env.PORT || 8000;
|
||||||
|
|
||||||
@@ -44,10 +45,15 @@ async function initServices() {
|
|||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
initWSS(server);
|
initWSS(server);
|
||||||
|
|
||||||
|
async function startServer() {
|
||||||
|
await initDbPool(); // Дождаться пересоздания пула!
|
||||||
|
await initServices(); // Только теперь запускать сервисы
|
||||||
|
console.log(`Server is running on port ${PORT}`);
|
||||||
|
}
|
||||||
|
|
||||||
server.listen(PORT, async () => {
|
server.listen(PORT, async () => {
|
||||||
try {
|
try {
|
||||||
await initServices();
|
await startServer();
|
||||||
console.log(`Server is running on port ${PORT}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting server:', error);
|
console.error('Error starting server:', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const identityService = require('./identity-service');
|
|||||||
const aiAssistant = require('./ai-assistant');
|
const aiAssistant = require('./ai-assistant');
|
||||||
const { checkAdminRole } = require('./admin-role');
|
const { checkAdminRole } = require('./admin-role');
|
||||||
const { broadcastContactsUpdate } = require('../wsHub');
|
const { broadcastContactsUpdate } = require('../wsHub');
|
||||||
|
const aiAssistantSettingsService = require('./aiAssistantSettingsService');
|
||||||
|
const { ragAnswer, generateLLMResponse } = require('./ragService');
|
||||||
|
|
||||||
let botInstance = null;
|
let botInstance = null;
|
||||||
let telegramSettingsCache = null;
|
let telegramSettingsCache = null;
|
||||||
@@ -335,8 +337,34 @@ async function getBot() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Получить ответ от ИИ
|
// 3. Получить ответ от ИИ (RAG + LLM)
|
||||||
const aiResponse = await aiAssistant.getResponse(content, 'auto');
|
const aiSettings = await aiAssistantSettingsService.getSettings();
|
||||||
|
let ragTableId = null;
|
||||||
|
if (aiSettings && aiSettings.selected_rag_tables) {
|
||||||
|
ragTableId = Array.isArray(aiSettings.selected_rag_tables)
|
||||||
|
? aiSettings.selected_rag_tables[0]
|
||||||
|
: aiSettings.selected_rag_tables;
|
||||||
|
}
|
||||||
|
let aiResponse;
|
||||||
|
if (ragTableId) {
|
||||||
|
// Сначала ищем ответ через RAG
|
||||||
|
const ragResult = await ragAnswer({ tableId: ragTableId, userQuestion: content });
|
||||||
|
if (ragResult && ragResult.answer) {
|
||||||
|
aiResponse = ragResult.answer;
|
||||||
|
} else {
|
||||||
|
aiResponse = await generateLLMResponse({
|
||||||
|
userQuestion: content,
|
||||||
|
context: ragResult && ragResult.context ? ragResult.context : '',
|
||||||
|
answer: ragResult && ragResult.answer ? ragResult.answer : '',
|
||||||
|
systemPrompt: aiSettings ? aiSettings.system_prompt : '',
|
||||||
|
history: null,
|
||||||
|
model: aiSettings ? aiSettings.model : undefined,
|
||||||
|
language: aiSettings && aiSettings.languages && aiSettings.languages.length > 0 ? aiSettings.languages[0] : 'ru'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
aiResponse = await aiAssistant.getResponse(content, 'auto');
|
||||||
|
}
|
||||||
// 4. Сохранить ответ в БД с conversation_id
|
// 4. Сохранить ответ в БД с conversation_id
|
||||||
await db.getQuery()(
|
await db.getQuery()(
|
||||||
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at)
|
`INSERT INTO messages (user_id, conversation_id, sender_type, content, channel, role, direction, created_at)
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
const VECTOR_SEARCH_URL = process.env.VECTOR_SEARCH_URL || 'http://vector-search:8001';
|
const VECTOR_SEARCH_URL = process.env.VECTOR_SEARCH_URL || 'http://vector-search:8001';
|
||||||
|
|
||||||
async function upsert(tableId, rows) {
|
async function upsert(tableId, rows) {
|
||||||
|
logger.info(`[VectorSearch] upsert: tableId=${tableId}, rows=${rows.length}`);
|
||||||
|
try {
|
||||||
const res = await axios.post(`${VECTOR_SEARCH_URL}/upsert`, {
|
const res = await axios.post(`${VECTOR_SEARCH_URL}/upsert`, {
|
||||||
table_id: String(tableId),
|
table_id: String(tableId),
|
||||||
rows: rows.map(r => ({
|
rows: rows.map(r => ({
|
||||||
@@ -11,27 +14,48 @@ async function upsert(tableId, rows) {
|
|||||||
metadata: r.metadata || {}
|
metadata: r.metadata || {}
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
logger.info(`[VectorSearch] upsert result:`, res.data);
|
||||||
return res.data;
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[VectorSearch] upsert error:`, error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function search(tableId, query, topK = 3) {
|
async function search(tableId, query, topK = 3) {
|
||||||
|
logger.info(`[VectorSearch] search: tableId=${tableId}, query="${query}", topK=${topK}`);
|
||||||
|
try {
|
||||||
const res = await axios.post(`${VECTOR_SEARCH_URL}/search`, {
|
const res = await axios.post(`${VECTOR_SEARCH_URL}/search`, {
|
||||||
table_id: String(tableId),
|
table_id: String(tableId),
|
||||||
query,
|
query,
|
||||||
top_k: topK
|
top_k: topK
|
||||||
});
|
});
|
||||||
|
logger.info(`[VectorSearch] search result:`, res.data.results);
|
||||||
return res.data.results;
|
return res.data.results;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[VectorSearch] search error:`, error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove(tableId, rowIds) {
|
async function remove(tableId, rowIds) {
|
||||||
const res = await axios.post(`${VECTOR_SEARCH_URL}/delete`, {
|
logger.info(`[VectorSearch] remove: tableId=${tableId}, rowIds=${rowIds}`);
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${VECTOR_SEARCH_URL}/remove`, {
|
||||||
table_id: String(tableId),
|
table_id: String(tableId),
|
||||||
row_ids: rowIds.map(id => String(id))
|
row_ids: rowIds.map(String)
|
||||||
});
|
});
|
||||||
|
logger.info(`[VectorSearch] remove result:`, res.data);
|
||||||
return res.data;
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[VectorSearch] remove error:`, error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rebuild(tableId, rows) {
|
async function rebuild(tableId, rows) {
|
||||||
|
logger.info(`[VectorSearch] rebuild: tableId=${tableId}, rows=${rows.length}`);
|
||||||
|
try {
|
||||||
const res = await axios.post(`${VECTOR_SEARCH_URL}/rebuild`, {
|
const res = await axios.post(`${VECTOR_SEARCH_URL}/rebuild`, {
|
||||||
table_id: String(tableId),
|
table_id: String(tableId),
|
||||||
rows: rows.map(r => ({
|
rows: rows.map(r => ({
|
||||||
@@ -40,18 +64,26 @@ async function rebuild(tableId, rows) {
|
|||||||
metadata: r.metadata || {}
|
metadata: r.metadata || {}
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
logger.info(`[VectorSearch] rebuild result:`, res.data);
|
||||||
return res.data;
|
return res.data;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[VectorSearch] rebuild error:`, error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function health() {
|
async function health() {
|
||||||
|
logger.info(`[VectorSearch] health check`);
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(`${VECTOR_SEARCH_URL}/health`, { timeout: 5000 });
|
const res = await axios.get(`${VECTOR_SEARCH_URL}/health`, { timeout: 5000 });
|
||||||
|
logger.info(`[VectorSearch] health result:`, res.data);
|
||||||
return {
|
return {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
url: VECTOR_SEARCH_URL,
|
url: VECTOR_SEARCH_URL,
|
||||||
response: res.data
|
response: res.data
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error(`[VectorSearch] health error:`, error.message);
|
||||||
return {
|
return {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
url: VECTOR_SEARCH_URL,
|
url: VECTOR_SEARCH_URL,
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
<div class="user-table-header" v-if="tableMeta">
|
<div class="user-table-header" v-if="tableMeta">
|
||||||
<h2>{{ tableMeta.name }}</h2>
|
<h2>{{ tableMeta.name }}</h2>
|
||||||
<div class="table-desc">{{ tableMeta.description }}</div>
|
<div class="table-desc">{{ tableMeta.description }}</div>
|
||||||
|
<button v-if="isAdmin" class="rebuild-btn" @click="rebuildIndex" :disabled="rebuilding">
|
||||||
|
{{ rebuilding ? 'Пересборка...' : 'Пересобрать индекс' }}
|
||||||
|
</button>
|
||||||
|
<span v-if="rebuildStatus" :class="['rebuild-status', rebuildStatus.success ? 'success' : 'error']">
|
||||||
|
{{ rebuildStatus.message }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="notion-table-wrapper">
|
<div class="notion-table-wrapper">
|
||||||
<table class="notion-table">
|
<table class="notion-table">
|
||||||
@@ -97,6 +103,11 @@
|
|||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import tablesService from '../../services/tablesService';
|
import tablesService from '../../services/tablesService';
|
||||||
import TableCell from './TableCell.vue';
|
import TableCell from './TableCell.vue';
|
||||||
|
import { useAuthContext } from '@/composables/useAuth';
|
||||||
|
import axios from 'axios';
|
||||||
|
const { isAdmin } = useAuthContext();
|
||||||
|
const rebuilding = ref(false);
|
||||||
|
const rebuildStatus = ref(null);
|
||||||
|
|
||||||
const props = defineProps({ tableId: Number });
|
const props = defineProps({ tableId: Number });
|
||||||
const columns = ref([]);
|
const columns = ref([]);
|
||||||
@@ -252,6 +263,22 @@ function deleteColumn(col) {
|
|||||||
tablesService.deleteColumn(col.id).then(fetchTable);
|
tablesService.deleteColumn(col.id).then(fetchTable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function rebuildIndex() {
|
||||||
|
rebuilding.value = true;
|
||||||
|
rebuildStatus.value = null;
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post(`/tables/${props.tableId}/rebuild-index`);
|
||||||
|
if (data.success) {
|
||||||
|
rebuildStatus.value = { success: true, message: `Индекс пересобран (${data.count || 0} строк)` };
|
||||||
|
} else {
|
||||||
|
rebuildStatus.value = { success: false, message: data.message || 'Ошибка пересборки' };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
rebuildStatus.value = { success: false, message: e.response?.data?.error || e.message || 'Ошибка запроса' };
|
||||||
|
}
|
||||||
|
rebuilding.value = false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -448,4 +475,30 @@ tr:hover .delete-row-btn {
|
|||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
z-index: 1999;
|
z-index: 1999;
|
||||||
}
|
}
|
||||||
|
.rebuild-btn {
|
||||||
|
margin-top: 1em;
|
||||||
|
background: #2ecc40;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.rebuild-btn:disabled {
|
||||||
|
background: #b2e6c2;
|
||||||
|
color: #fff;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.rebuild-status {
|
||||||
|
margin-left: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.rebuild-status.success {
|
||||||
|
color: #2ecc40;
|
||||||
|
}
|
||||||
|
.rebuild-status.error {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user