From 0ed78e2202866a3e578385bf5e837603d02a736398f79cba1e610ffcc45fcd19 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 28 Jul 2026 16:20:29 +0400 Subject: [PATCH] =?UTF-8?q?UI=20=D0=B8=20API:=20=D1=8D=D0=BA=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B0=20=D0=B8=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D1=82=D1=80=20SearchUsers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/shine/db/dao/CurrentUsersDAO.java | 20 +- .../java/shine/db/sql/CurrentUsersSql.java | 3 +- .../tempToTest/Net_SearchUsers_Handler.java | 5 +- .../entyties/Net_SearchUsers_Request.java | 5 +- VERSION.properties | 4 +- docs/API/01_User_Registration_API.md | 10 +- shine-UI/js/app.js | 2 + shine-UI/js/pages/access-servers-view.js | 380 ++++++++++++++++++ shine-UI/js/pages/server-settings-view.js | 17 +- shine-UI/js/pages/settings-view.js | 16 +- shine-UI/js/router.js | 3 + shine-UI/js/services/auth-service.js | 8 +- 12 files changed, 457 insertions(+), 16 deletions(-) create mode 100644 shine-UI/js/pages/access-servers-view.js diff --git a/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/CurrentUsersDAO.java b/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/CurrentUsersDAO.java index c68d6aae..ec61b084 100644 --- a/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/CurrentUsersDAO.java +++ b/SHiNE-server/shine-server-db/src/main/java/shine/db/dao/CurrentUsersDAO.java @@ -154,6 +154,11 @@ public final class CurrentUsersDAO { /** Поиск по префиксу с внешним соединением. Соединение НЕ закрывает. */ public List searchByLoginPrefix(Connection c, String prefix) throws SQLException { + return searchByLoginPrefix(c, prefix, null); + } + + /** Поиск по префиксу с optional-фильтром server PDA. Соединение НЕ закрывает. */ + public List searchByLoginPrefix(Connection c, String prefix, Boolean isServer) throws SQLException { String sql = """ SELECT login, @@ -163,6 +168,7 @@ public final class CurrentUsersDAO { client_key FROM %s WHERE LOWER(login) LIKE ? + AND (? IS NULL OR is_server = ?) ORDER BY login LIMIT 5 """.formatted(CurrentUsersSql.usersSubquery("su")); @@ -171,6 +177,13 @@ public final class CurrentUsersDAO { try (PreparedStatement ps = c.prepareStatement(sql)) { ps.setString(1, prefix.toLowerCase() + "%"); + if (isServer == null) { + ps.setNull(2, Types.BOOLEAN); + ps.setNull(3, Types.BOOLEAN); + } else { + ps.setBoolean(2, isServer); + ps.setBoolean(3, isServer); + } try (ResultSet rs = ps.executeQuery()) { while (rs.next()) result.add(mapRow(rs)); } @@ -181,8 +194,13 @@ public final class CurrentUsersDAO { /** Поиск по префиксу без внешнего соединения. Сам открывает/закрывает. */ public List searchByLoginPrefix(String prefix) throws SQLException { + return searchByLoginPrefix(prefix, null); + } + + /** Поиск по префиксу без внешнего соединения с optional-фильтром server PDA. */ + public List searchByLoginPrefix(String prefix, Boolean isServer) throws SQLException { try (Connection c = db.getConnection()) { - return searchByLoginPrefix(c, prefix); + return searchByLoginPrefix(c, prefix, isServer); } } diff --git a/SHiNE-server/shine-server-db/src/main/java/shine/db/sql/CurrentUsersSql.java b/SHiNE-server/shine-server-db/src/main/java/shine/db/sql/CurrentUsersSql.java index 6dad44b3..837e5aec 100644 --- a/SHiNE-server/shine-server-db/src/main/java/shine/db/sql/CurrentUsersSql.java +++ b/SHiNE-server/shine-server-db/src/main/java/shine/db/sql/CurrentUsersSql.java @@ -15,7 +15,8 @@ public final class CurrentUsersSql { current_users.blockchain_name AS blockchain_name, current_users.client_key AS solana_key, current_users.blockchain_key AS blockchain_key, - current_users.client_key AS client_key + current_users.client_key AS client_key, + current_users.is_server AS is_server FROM solana_user_pda_current current_users ) %s """.formatted(alias); diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_SearchUsers_Handler.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_SearchUsers_Handler.java index ebc86f24..afe7356c 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_SearchUsers_Handler.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/Net_SearchUsers_Handler.java @@ -35,10 +35,11 @@ public class Net_SearchUsers_Handler implements JsonMessageHandler { } String prefix = req.getPrefix().trim(); + Boolean isServer = req.getIsServer(); try { CurrentUsersDAO dao = CurrentUsersDAO.getInstance(); - List users = dao.searchByLoginPrefix(prefix); // case-insensitive + LIMIT 5 + List users = dao.searchByLoginPrefix(prefix, isServer); // case-insensitive + LIMIT 5 List logins = new ArrayList<>(); for (CurrentUserEntry u : users) { @@ -53,7 +54,7 @@ public class Net_SearchUsers_Handler implements JsonMessageHandler { resp.setStatus(WireCodes.Status.OK); resp.setLogins(logins); - log.info("✅ SearchUsers ok: prefix='{}' -> {}", prefix, logins.size()); + log.info("✅ SearchUsers ok: prefix='{}', isServer={} -> {}", prefix, isServer, logins.size()); return resp; } catch (SQLException e) { diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_SearchUsers_Request.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_SearchUsers_Request.java index 1e974d52..3f2e9990 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_SearchUsers_Request.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/tempToTest/entyties/Net_SearchUsers_Request.java @@ -18,7 +18,10 @@ import server.logic.ws_protocol.JSON.entyties.Net_Request; public class Net_SearchUsers_Request extends Net_Request { private String prefix; + private Boolean isServer; public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } -} \ No newline at end of file + public Boolean getIsServer() { return isServer; } + public void setIsServer(Boolean isServer) { this.isServer = isServer; } +} diff --git a/VERSION.properties b/VERSION.properties index 82b49763..e8fae79c 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.355 -server.version=1.2.340 +client.version=1.2.356 +server.version=1.2.341 diff --git a/docs/API/01_User_Registration_API.md b/docs/API/01_User_Registration_API.md index dcd086de..ba411334 100644 --- a/docs/API/01_User_Registration_API.md +++ b/docs/API/01_User_Registration_API.md @@ -5,7 +5,7 @@ Сейчас здесь два метода: - `GetUser` — временная серверная проверка существования пользователя и чтение его базовых данных; -- `SearchUsers` — dev/test поиск логинов по префиксу. +- `SearchUsers` — dev/test поиск логинов по префиксу, при необходимости с фильтром только по server PDA. Регистрация выполняется только через Solana. @@ -111,6 +111,7 @@ ### Назначение Поиск пользователей по префиксу логина. Операция зарегистрирована в серверном API и используется как вспомогательная dev/test операция. +При необходимости можно включить фильтр только по server PDA. ### Запрос @@ -119,11 +120,16 @@ "op": "SearchUsers", "requestId": "search-001", "payload": { - "prefix": "an" + "prefix": "an", + "isServer": true } } ``` +- `prefix` — обязательный префикс логина. +- `isServer` — необязательный boolean-флаг; если `true`, сервер вернёт только логины, + у которых в `solana_user_pda_current` стоит `is_server = true`. + ### Успешный ответ ```json diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index 173978d9..403b7b2c 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -56,6 +56,7 @@ import * as profileView from './pages/profile-view.js?v=202607150910'; import * as profileEditView from './pages/profile-edit-view.js'; import * as walletView from './pages/wallet-view.js?v=202606281930'; import * as settingsView from './pages/settings-view.js'; +import * as accessServersView from './pages/access-servers-view.js'; import * as developerSettingsView from './pages/developer-settings-view.js'; import * as serverSettingsView from './pages/server-settings-view.js?v=202606161240'; import * as remoteAddBlockSessionView from './pages/remote-addblock-session-view.js?v=202606281300'; @@ -116,6 +117,7 @@ const routes = { 'profile-edit-view': profileEditView, 'wallet-view': walletView, 'settings-view': settingsView, + 'access-servers-view': accessServersView, 'developer-settings-view': developerSettingsView, 'server-settings-view': serverSettingsView, 'remote-addblock-session-view': remoteAddBlockSessionView, diff --git a/shine-UI/js/pages/access-servers-view.js b/shine-UI/js/pages/access-servers-view.js new file mode 100644 index 00000000..653a4a4e --- /dev/null +++ b/shine-UI/js/pages/access-servers-view.js @@ -0,0 +1,380 @@ +import { renderHeader } from '../components/header.js'; +import { authService, state } from '../state.js'; +import { readShineUserPda } from '../services/shine-user-pda-service.js'; +import { resolveShineServerByServerLogin } from '../services/shine-server-resolver.js'; + +export const pageMeta = { id: 'access-servers-view', title: 'Серверы доступа' }; + +function escapeHtml(value) { + return String(value || '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function normalizeLogin(value) { + return String(value || '').trim().toLowerCase(); +} + +function createConfirmModal() { + const root = document.getElementById('modal-root'); + if (!(root instanceof HTMLElement)) return null; + + const modal = document.createElement('div'); + modal.className = 'modal'; + modal.hidden = true; + modal.innerHTML = ` + + `; + root.append(modal); + + const titleEl = modal.querySelector('#access-servers-dialog-title'); + const textEl = modal.querySelector('#access-servers-dialog-text'); + const noteEl = modal.querySelector('#access-servers-dialog-note'); + const cancelBtn = modal.querySelector('#access-servers-dialog-cancel'); + const confirmBtn = modal.querySelector('#access-servers-dialog-confirm'); + + let onConfirm = null; + let onCancel = null; + + const close = () => { + modal.hidden = true; + onConfirm = null; + onCancel = null; + }; + + modal.addEventListener('click', (event) => { + if (event.target === modal) close(); + }); + cancelBtn?.addEventListener('click', async () => { + const handler = onCancel; + close(); + if (typeof handler === 'function') await handler(); + }); + confirmBtn?.addEventListener('click', async () => { + const handler = onConfirm; + close(); + if (typeof handler === 'function') await handler(); + }); + + return { + open({ + title, + text, + note = '', + confirmLabel = 'Да', + cancelLabel = 'Нет', + onConfirm: confirmHandler, + onCancel: cancelHandler, + }) { + titleEl.textContent = String(title || 'Подтверждение'); + textEl.textContent = String(text || ''); + if (note) { + noteEl.hidden = false; + noteEl.textContent = String(note); + } else { + noteEl.hidden = true; + noteEl.textContent = ''; + } + confirmBtn.textContent = String(confirmLabel || 'Да'); + cancelBtn.textContent = String(cancelLabel || 'Нет'); + onConfirm = confirmHandler || null; + onCancel = cancelHandler || null; + modal.hidden = false; + }, + destroy() { + modal.remove(); + }, + }; +} + +export function render({ navigate }) { + const screen = document.createElement('section'); + screen.className = 'stack'; + + const sessionLogin = normalizeLogin(state.session.login); + const solanaEndpoint = String(state.entrySettings.solanaServer || '').trim(); + let disposed = false; + let currentAccessServers = []; + let selectedCandidate = null; + let suggestionsLoading = false; + const confirmModal = createConfirmModal(); + + const introCard = document.createElement('div'); + introCard.className = 'card stack'; + introCard.innerHTML = ` +

Где хранятся личные данные

+

+ Серверы доступа хранят зашифрованную личную переписку пользователя и участвуют в звонках. + Всё, что публикуется в блокчейне SHiNE, доступно через любой сервер Сияния, + а доступ пользователя и приватная переписка хранятся только на этих серверах. +

+ `; + + const listCard = document.createElement('div'); + listCard.className = 'card stack'; + const listTitle = document.createElement('p'); + listTitle.className = 'field-label'; + listTitle.textContent = 'Текущий список серверов доступа'; + const listHint = document.createElement('p'); + listHint.className = 'meta-muted'; + listHint.textContent = sessionLogin + ? `Пользователь: @${sessionLogin}` + : 'В текущей сессии не найден логин пользователя.'; + const listBody = document.createElement('div'); + listBody.className = 'stack'; + const listStatus = document.createElement('p'); + listStatus.className = 'meta-muted'; + listStatus.textContent = 'Загрузка данных из PDA...'; + listCard.append(listTitle, listHint, listBody, listStatus); + + const addCard = document.createElement('div'); + addCard.className = 'card stack'; + const addTitle = document.createElement('p'); + addTitle.className = 'field-label'; + addTitle.textContent = 'Добавить сервер доступа'; + const addHint = document.createElement('p'); + addHint.className = 'meta-muted'; + addHint.textContent = 'Введите логин сервера или несколько первых букв, затем выберите сервер из подсказок.'; + const addInput = document.createElement('input'); + addInput.className = 'input'; + addInput.type = 'text'; + addInput.autocomplete = 'off'; + addInput.placeholder = 'Например: shineup'; + const suggestEl = document.createElement('div'); + suggestEl.className = 'profile-relative-search-suggest'; + suggestEl.hidden = true; + const addStatus = document.createElement('p'); + addStatus.className = 'meta-muted'; + addStatus.textContent = 'Добавление сервера пока работает как подтверждающая заглушка.'; + const addButton = document.createElement('button'); + addButton.className = 'primary-btn'; + addButton.type = 'button'; + addButton.textContent = 'Добавить сервер'; + addButton.disabled = true; + addCard.append(addTitle, addHint, addInput, suggestEl, addStatus, addButton); + + const setSelectedCandidate = (candidate) => { + selectedCandidate = candidate; + addButton.disabled = !candidate; + if (candidate) { + addInput.value = candidate.login; + addStatus.textContent = `Выбран сервер @${candidate.login}${candidate.url ? ` (${candidate.url})` : ''}`; + } else { + addStatus.textContent = 'Добавление сервера пока работает как подтверждающая заглушка.'; + } + }; + + const renderServerList = () => { + listBody.innerHTML = ''; + if (!currentAccessServers.length) { + const empty = document.createElement('p'); + empty.className = 'meta-muted'; + empty.textContent = 'Список серверов доступа пока пуст.'; + listBody.append(empty); + return; + } + + currentAccessServers.forEach((server, index) => { + const button = document.createElement('button'); + button.className = 'text-btn'; + button.type = 'button'; + button.innerHTML = ` + + @${escapeHtml(server.login)} + ${escapeHtml(server.url || 'URL не указан')} + + `; + button.addEventListener('click', () => { + const isLast = currentAccessServers.length <= 1; + const openStub = (noteText = '') => { + confirmModal?.open({ + title: isLast ? 'Последний сервер доступа' : 'Отключить сервер доступа?', + text: isLast + ? 'Это последний сервер доступа пользователя. Если его отключить, личная переписка пользователя на серверах доступа будет удалена.' + : `Хотите изменить запись в блокчейне Solana и отключить сервер доступа @${server.login}?`, + note: noteText, + confirmLabel: isLast ? 'Понимаю' : 'Да', + cancelLabel: 'Нет', + onConfirm: () => window.alert('Отключение сервера доступа пока не реализовано.'), + }); + }; + + if (isLast) { + confirmModal?.open({ + title: 'Отключить последний сервер?', + text: `У пользователя остался только один сервер доступа: @${server.login}.`, + note: 'После отключения последнего сервера доступа личная переписка пользователя на серверах доступа будет удалена.', + confirmLabel: 'Продолжить', + cancelLabel: 'Отмена', + onConfirm: () => openStub('Это повторное предупреждение. Сейчас действие остаётся заглушкой.'), + }); + return; + } + + openStub(); + }); + listBody.append(button); + if (index < currentAccessServers.length - 1) { + const divider = document.createElement('div'); + divider.style.height = '1px'; + divider.style.background = 'rgba(255,255,255,0.08)'; + listBody.append(divider); + } + }); + }; + + const loadCurrentServers = async () => { + if (!sessionLogin) { + currentAccessServers = []; + renderServerList(); + listStatus.textContent = 'Нет активной пользовательской сессии.'; + return; + } + if (!solanaEndpoint) { + currentAccessServers = []; + renderServerList(); + listStatus.textContent = 'Не задан Solana RPC endpoint.'; + return; + } + + listStatus.textContent = 'Читаем серверы доступа из Solana PDA...'; + try { + const parsed = await readShineUserPda({ login: sessionLogin, solanaEndpoint }); + const logins = Array.isArray(parsed?.accessServers) + ? [...new Set(parsed.accessServers.map(normalizeLogin).filter(Boolean))] + : []; + const rows = []; + for (const login of logins) { + try { + const resolved = await resolveShineServerByServerLogin({ serverLogin: login, solanaEndpoint }); + rows.push({ login: resolved.serverLogin, url: resolved.httpBase }); + } catch { + rows.push({ login, url: '' }); + } + } + currentAccessServers = rows; + renderServerList(); + listStatus.textContent = rows.length + ? `Найдено серверов доступа: ${rows.length}` + : 'В PDA пользователя пока нет серверов доступа.'; + } catch (error) { + currentAccessServers = []; + renderServerList(); + listStatus.textContent = error?.message || 'Не удалось прочитать список серверов доступа.'; + } + }; + + const renderSuggestions = (items) => { + suggestEl.innerHTML = ''; + if (!items.length) { + suggestEl.hidden = true; + return; + } + suggestEl.hidden = false; + suggestEl.innerHTML = items.map((item) => ( + `` + )).join(''); + }; + + const loadSuggestions = async () => { + const prefix = normalizeLogin(addInput.value); + if (suggestionsLoading || prefix.length < 2) { + suggestEl.hidden = true; + suggestEl.innerHTML = ''; + return; + } + suggestionsLoading = true; + try { + const logins = await authService.searchUsers(prefix, { isServer: true }); + const items = []; + for (const login of logins.slice(0, 6)) { + try { + const resolved = await resolveShineServerByServerLogin({ serverLogin: login, solanaEndpoint }); + items.push({ login: resolved.serverLogin, url: resolved.httpBase }); + } catch { + // если server PDA битая, пропускаем подсказку + } + } + if (!disposed) renderSuggestions(items); + } catch (error) { + if (!disposed) { + suggestEl.hidden = true; + suggestEl.innerHTML = ''; + addStatus.textContent = error?.message || 'Не удалось получить список серверов.'; + } + } finally { + suggestionsLoading = false; + } + }; + + addInput.addEventListener('input', () => { + setSelectedCandidate(null); + void loadSuggestions(); + }); + addInput.addEventListener('focus', () => { + void loadSuggestions(); + }); + suggestEl.addEventListener('click', (event) => { + const target = event.target instanceof HTMLElement ? event.target.closest('[data-login]') : null; + if (!(target instanceof HTMLElement)) return; + setSelectedCandidate({ + login: String(target.dataset.login || ''), + url: String(target.dataset.url || ''), + }); + suggestEl.hidden = true; + suggestEl.innerHTML = ''; + }); + + addButton.addEventListener('click', async () => { + const login = normalizeLogin(selectedCandidate?.login || addInput.value); + if (!login) { + setSelectedCandidate(null); + addStatus.textContent = 'Сначала укажите логин сервера доступа.'; + return; + } + try { + const resolved = await resolveShineServerByServerLogin({ serverLogin: login, solanaEndpoint }); + confirmModal?.open({ + title: 'Добавить сервер доступа?', + text: `Вы хотите добавить сервер доступа @${resolved.serverLogin}?`, + note: resolved.httpBase ? `Адрес сервера: ${resolved.httpBase}` : '', + onConfirm: () => window.alert('Добавление сервера доступа пока не реализовано.'), + }); + } catch (error) { + addStatus.textContent = error?.message || 'Не удалось проверить выбранный сервер.'; + } + }); + + screen.append( + renderHeader({ + title: 'Серверы доступа', + leftAction: { label: '←', onClick: () => navigate('settings-view') }, + }), + introCard, + listCard, + addCard, + ); + + void loadCurrentServers(); + + screen.cleanup = () => { + disposed = true; + confirmModal?.destroy(); + }; + + return screen; +} diff --git a/shine-UI/js/pages/server-settings-view.js b/shine-UI/js/pages/server-settings-view.js index 13c17c11..e0cbb70b 100644 --- a/shine-UI/js/pages/server-settings-view.js +++ b/shine-UI/js/pages/server-settings-view.js @@ -2,7 +2,7 @@ import { renderHeader } from '../components/header.js'; import { saveEntrySettings, state } from '../state.js'; import { checkServerAvailabilityByKey, resolveAndCheckShineServerLogin } from '../services/server-health-service.js'; -export const pageMeta = { id: 'server-settings-view', title: 'Настройки серверов' }; +export const pageMeta = { id: 'server-settings-view', title: 'Серверы блокчейнов' }; const SERVER_FIELDS = [ { key: 'solanaServer', label: 'Адрес Solana сервера' }, @@ -26,6 +26,16 @@ export function render({ navigate }) { const timers = new Map(); + const introCard = document.createElement('div'); + introCard.className = 'card stack'; + introCard.innerHTML = ` +

Серверы блокчейнов и публичных данных

+

+ Здесь настраиваются Solana, SHiNE и Arweave для чтения публичных данных и доступа к блокчейну. + Эти настройки не меняют список личных серверов доступа пользователя. +

+ `; + const body = document.createElement('div'); body.className = 'card stack'; @@ -168,7 +178,7 @@ export function render({ navigate }) { await saveEntrySettings(draft); navigate('settings-view'); } catch (error) { - window.alert(error?.message || 'Не удалось сохранить настройки серверов.'); + window.alert(error?.message || 'Не удалось сохранить настройки серверов блокчейнов.'); } }); @@ -186,9 +196,10 @@ export function render({ navigate }) { screen.append( renderHeader({ - title: 'Настройки серверов', + title: 'Серверы блокчейнов', leftAction: { label: '←', onClick: () => navigate('settings-view') }, }), + introCard, body, actions, help, diff --git a/shine-UI/js/pages/settings-view.js b/shine-UI/js/pages/settings-view.js index a66c16ec..815688e2 100644 --- a/shine-UI/js/pages/settings-view.js +++ b/shine-UI/js/pages/settings-view.js @@ -42,14 +42,26 @@ export function render({ navigate }) { card.innerHTML = ` - + + `; card.querySelector('#settings-device').addEventListener('click', () => navigate('device-view')); card.querySelector('#settings-remote-addblock').addEventListener('click', () => navigate('remote-addblock-session-view')); - card.querySelector('#settings-servers').addEventListener('click', () => navigate('server-settings-view')); + card.querySelector('#settings-access-servers').addEventListener('click', () => navigate('access-servers-view')); + card.querySelector('#settings-blockchain-servers').addEventListener('click', () => navigate('server-settings-view')); card.querySelector('#settings-language').addEventListener('click', () => navigate('language-view')); const signOutBtn = card.querySelector('#settings-signout'); diff --git a/shine-UI/js/router.js b/shine-UI/js/router.js index fa65c242..46466cd2 100644 --- a/shine-UI/js/router.js +++ b/shine-UI/js/router.js @@ -29,6 +29,7 @@ const PRETTY_PATHS = new Map([ ['network-view', 'network'], ['notifications-view', 'notifications'], ['settings-view', 'settings'], + ['access-servers-view', 'settings/access-servers'], ['server-settings-view', 'settings/servers'], ['developer-settings-view', 'settings/developer'], ['trusted-device-login-settings-view', 'settings/device-login'], @@ -258,6 +259,7 @@ export function getRoute() { if (pageId === 'settings') { const sub = decodePart(segments[1] || '').toLowerCase(); + if (sub === 'access-servers') return { pageId: 'access-servers-view', params: {} }; if (sub === 'servers') return { pageId: 'server-settings-view', params: {} }; if (sub === 'developer') return { pageId: 'developer-settings-view', params: {} }; if (sub === 'device-login') return { pageId: 'trusted-device-login-settings-view', params: {} }; @@ -368,6 +370,7 @@ export function resolveToolbarActive(pageId) { pageId === 'profile-edit-view' || pageId === 'wallet-view' || pageId === 'settings-view' || + pageId === 'access-servers-view' || pageId === 'developer-settings-view' || pageId === 'server-settings-view' || pageId === 'remote-addblock-session-view' || diff --git a/shine-UI/js/services/auth-service.js b/shine-UI/js/services/auth-service.js index cb73438c..4a19d578 100644 --- a/shine-UI/js/services/auth-service.js +++ b/shine-UI/js/services/auth-service.js @@ -2603,8 +2603,12 @@ export class AuthService { return response.payload || {}; } - async searchUsers(prefix) { - const response = await this.ws.request('SearchUsers', { prefix }); + async searchUsers(prefix, options = {}) { + const payload = { prefix }; + if (typeof options?.isServer === 'boolean') { + payload.isServer = options.isServer; + } + const response = await this.ws.request('SearchUsers', payload); if (response.status !== 200) throw opError('SearchUsers', response); return response.payload?.logins || []; }