diff --git a/app/Core/Admin/Http/Controllers/Api/DatabasesController.php b/app/Core/Admin/Http/Controllers/Api/DatabasesController.php index 2508acd..4e31e18 100644 --- a/app/Core/Admin/Http/Controllers/Api/DatabasesController.php +++ b/app/Core/Admin/Http/Controllers/Api/DatabasesController.php @@ -21,7 +21,7 @@ public function __construct(DatabasesService $databasesService) public function store(FluteRequest $request): Response { try { - $this->validate($request); + $this->validation($request); $this->databasesService->store( $request->mod, @@ -50,7 +50,7 @@ public function delete(FluteRequest $request, $id): Response public function update(FluteRequest $request, $id): Response { try { - $this->validate($request); + $this->validation($request); $this->databasesService->update( (int) $id, @@ -66,7 +66,7 @@ public function update(FluteRequest $request, $id): Response } } - protected function validate( FluteRequest $request ) + protected function validation( FluteRequest $request ) { if( empty( $request->input('mod') ) || empty( $request->input('dbname') ) ) throw new \Exception(__('admin.databases.params_empty')); diff --git a/app/Core/Admin/Http/Controllers/Api/IconsController.php b/app/Core/Admin/Http/Controllers/Api/IconsController.php new file mode 100644 index 0000000..f02c471 --- /dev/null +++ b/app/Core/Admin/Http/Controllers/Api/IconsController.php @@ -0,0 +1,17 @@ +json([ + 'icons' => app(PhosphorIconsParser::class)->getAll() + ]); + } +} \ No newline at end of file diff --git a/app/Core/Admin/Http/Controllers/Api/NavigationController.php b/app/Core/Admin/Http/Controllers/Api/NavigationController.php index c705264..4036c8f 100644 --- a/app/Core/Admin/Http/Controllers/Api/NavigationController.php +++ b/app/Core/Admin/Http/Controllers/Api/NavigationController.php @@ -31,7 +31,7 @@ public function saveOrder(FluteRequest $request) foreach ($order as $value) { $item = rep(NavbarItem::class)->select()->load('roles')->where(['id' => (int) $value['id']])->fetchOne(); - if ($item && navbar()->hasAccess($item)) { + if ($item && navbar()->hasAccess($item, true)) { $item->position = $value['position']; if ($value['parent_id'] == null) { @@ -39,7 +39,7 @@ public function saveOrder(FluteRequest $request) } else { $parent = rep(NavbarItem::class)->select()->load('roles')->where(['id' => (int) $value['parent_id']])->fetchOne(); - if ($parent && navbar()->hasAccess($parent)) + if ($parent && navbar()->hasAccess($parent, true)) $item->parent = $parent; } @@ -151,7 +151,7 @@ protected function getItem(string $id) if (!$navigation) return $this->error(__('admin.navigation.not_found'), 404); - if (!navbar()->hasAccess($navigation)) + if (!navbar()->hasAccess($navigation, true)) return $this->error(__('def.no_access')); return $navigation; diff --git a/app/Core/Admin/Http/Controllers/Api/ServersController.php b/app/Core/Admin/Http/Controllers/Api/ServersController.php index e2024a9..bb8aea8 100644 --- a/app/Core/Admin/Http/Controllers/Api/ServersController.php +++ b/app/Core/Admin/Http/Controllers/Api/ServersController.php @@ -136,13 +136,15 @@ public function checkRcon(FluteRequest $request) try { $query = new SourceQuery(); - $query->Connect($ip, $port, 5, ((int) $game === 10) ? SourceQuery::GOLDSOURCE : SourceQuery::SOURCE); + $query->Connect($ip, $port, 3, ((int) $game === 10) ? SourceQuery::GOLDSOURCE : SourceQuery::SOURCE); $query->SetRconPassword($rcon); $rcon = $query->Rcon($command); - return $this->success($rcon); + return $this->json([ + 'result' => $rcon + ]); } catch (\Exception $e) { return $this->error($e->getMessage()); } finally { diff --git a/app/Core/Admin/Http/Controllers/Views/NavbarView.php b/app/Core/Admin/Http/Controllers/Views/NavbarView.php index 767fee3..e7634f2 100644 --- a/app/Core/Admin/Http/Controllers/Views/NavbarView.php +++ b/app/Core/Admin/Http/Controllers/Views/NavbarView.php @@ -41,7 +41,7 @@ public function edit(FluteRequest $request, string $id) { $navigation = rep(NavbarItem::class)->select()->load('roles')->where('id', (int) $id)->fetchOne(); - if (!navbar()->hasAccess($navigation)) + if (!navbar()->hasAccess($navigation, true)) return $this->error(__('def.no_access')); if (!$navigation) diff --git a/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php b/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php index e0e43b5..9fdccd9 100644 --- a/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php +++ b/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php @@ -173,11 +173,11 @@ public function edit(FluteRequest $request, string $id): Response return view("Core/Admin/Http/Views/pages/payments/edit", [ 'gateway' => $payment, 'additional' => \Nette\Utils\Json::decode($payment->additional), - 'drivers' => $this->getAllDrivers() + 'drivers' => $this->getAllDrivers($payment->adapter) ]); } - protected function getAllDrivers() + protected function getAllDrivers(?string $currentGateway = null) { $namespaceMap = app()->getLoader()->getPrefixesPsr4(); $result = []; @@ -215,6 +215,15 @@ protected function getAllDrivers() } } + foreach ($result as $key => $val) { + $find = rep(PaymentGateway::class)->findOne([ + "adapter" => $val['name'] + ]); + + if ($find && $currentGateway !== $val['name']) + unset($result[$key]); + } + return $result; } diff --git a/app/Core/Admin/Http/Controllers/Views/SocialsView.php b/app/Core/Admin/Http/Controllers/Views/SocialsView.php index 0444997..2199e79 100644 --- a/app/Core/Admin/Http/Controllers/Views/SocialsView.php +++ b/app/Core/Admin/Http/Controllers/Views/SocialsView.php @@ -138,7 +138,7 @@ public function edit(FluteRequest $request, string $id): Response return view("Core/Admin/Http/Views/pages/socials/edit", [ 'social' => $social, - 'drivers' => $this->getAllDrivers() + 'drivers' => $this->getAllDrivers($social->key) ]); } @@ -147,7 +147,7 @@ protected function getSocialNetwork(int $id): ?SocialNetwork return rep(SocialNetwork::class)->findByPK($id); } - protected function getAllDrivers() + protected function getAllDrivers(?string $currentDriver = null) { $namespaceMap = app()->getLoader()->getPrefixesPsr4(); $result = []; @@ -175,9 +175,16 @@ protected function getAllDrivers() return Strings::startsWith($item, "Hybridauth\\Provider"); }), $result); - foreach( $result as $key => $item ) { + foreach ($result as $key => $item) { $ex = explode('\\', $item); - $result[$key] = $ex[array_key_last($ex)]; + $driver = $ex[array_key_last($ex)]; + + $find = rep(SocialNetwork::class)->findOne([ + "key" => $driver + ]); + + if (!$find || ($driver === $currentDriver)) + $result[$key] = $driver; } return $result; diff --git a/app/Core/Admin/Http/Views/assets/js/components/tabs.js b/app/Core/Admin/Http/Views/assets/js/components/tabs.js index e30c9d8..dfcd6ef 100644 --- a/app/Core/Admin/Http/Views/assets/js/components/tabs.js +++ b/app/Core/Admin/Http/Views/assets/js/components/tabs.js @@ -74,6 +74,7 @@ const contentCache = {}; constructor() { this.draggabillies = []; this.contentContainers = {}; + this.abortControllers = {}; } init(el) { @@ -425,6 +426,12 @@ const contentCache = {}; // Получаем ID вкладки const tabId = tabEl.getAttribute('data-tab-id'); + // Отмена загрузки контента при закрытии вкладки + if (this.abortControllers[tabId]) { + this.abortControllers[tabId].abort(); + delete this.abortControllers[tabId]; + } + // Удаляем контейнер контента, связанный с этой вкладкой const contentContainer = document.getElementById( `content-${tabId}`, @@ -758,27 +765,34 @@ function refreshCurrentPage() { } function fetchContent(url, container, tabEl, reload = false, title = null) { - fetch(appendGet(url, 'loadByTab', '1')) + const tabId = tabEl.getAttribute('data-tab-id'); + const abortController = new AbortController(); + chromeTabs.abortControllers[tabId] = abortController; + + fetch(appendGet(url, 'loadByTab', '1'), { signal: abortController.signal }) .then((response) => { if (!response.ok) { throw new Error('Not 2xx response', { cause: response }); } - return response.text(); }) .then((html) => { - const containerId = container.getAttribute('id'); + if (!container) return; // Check if container is still in DOM + const containerId = container.getAttribute('id'); container.classList.remove('loading'); container.innerHTML = html; loadScriptsFromContainer(containerId); - recoverContainerIDS(containerId); }) .catch((error) => { - console.error('Error loading the page: ', error); - showErrorPage(true); + if (error.name === 'AbortError') { + console.log('Content load aborted:', url); + } else { + console.error('Error loading the page: ', error); + showErrorPage(true); + } }) .finally(() => { setTimeout(() => { @@ -793,6 +807,7 @@ function fetchContent(url, container, tabEl, reload = false, title = null) { }, 700); }); } + function displayLoading(show) { const loadingElement = document.getElementById('loading'); loadingElement.style.setProperty('--animate-duration', '.3s'); @@ -1048,6 +1063,7 @@ function removeScriptsByContainerId(containerId) { function initEditor(container) { container.find('.editor-ace').each(function () { let editor = ace.edit(this); + let mode = $(this).data('editor-lang') || 'json'; let unformattedContent = editor.getSession().getValue(); let formattedContent = js_beautify(unformattedContent, { indent_size: 4, @@ -1055,7 +1071,7 @@ function initEditor(container) { }); editor.getSession().setValue(formattedContent); editor.setTheme('ace/theme/solarized_dark'); - editor.session.setMode('ace/mode/json'); + editor.session.setMode(`ace/mode/${mode}`); }); } diff --git a/app/Core/Admin/Http/Views/assets/js/layout.js b/app/Core/Admin/Http/Views/assets/js/layout.js index b873cb4..ea83d07 100644 --- a/app/Core/Admin/Http/Views/assets/js/layout.js +++ b/app/Core/Admin/Http/Views/assets/js/layout.js @@ -81,7 +81,13 @@ function transformUrl(url) { } } -function sendRequest(data, path = null, method = 'POST', callback) { +function sendRequest( + data, + path = null, + method = 'POST', + callback = null, + needToRefresh = true, +) { toast({ type: 'async', message: translate('admin.is_loading'), @@ -93,27 +99,31 @@ function sendRequest(data, path = null, method = 'POST', callback) { data: data, success: function (response) { callback && callback(response); - Modals.clear(); - if (method === 'DELETE') { - tryAndDeleteTab(transformUrl(path)); - refreshCurrentPage(); - } else { - refreshCurrentPage(); - if (!path.includes('admin/api/settings')) { - if ( - path.includes('edit') || - path.includes('add') || - path.includes('delete') - ) - fetchContentAndAddTab( - replaceURLForTab( - window.location.pathname, - ), - ); + if (needToRefresh) { + Modals.clear(); + + if (method === 'DELETE') { + tryAndDeleteTab(transformUrl(path)); refreshCurrentPage(); + } else { + refreshCurrentPage(); + if (!path.includes('admin/api/settings')) { + if ( + path.includes('edit') || + path.includes('add') || + path.includes('delete') + ) + fetchContentAndAddTab( + replaceURLForTab( + window.location.pathname, + ), + ); + refreshCurrentPage(); + } } } + resolve(response?.success || translate('def.success')); }, error: function (jqXHR, textStatus, errorThrown) { @@ -398,6 +408,76 @@ $(function () { container.addEventListener('mouseleave', function () { container.style.width = '35px'; }); + + let icons = []; + + const $iconMenu = $( + '
', + ).appendTo('body'); + const $iconMenuHeader = $(` + + `).appendTo($iconMenu); + const $iconList = $( + '', + ).appendTo($iconMenu); + + function fetchIcons() { + $.getJSON(u('admin/api/get-icons'), function (data) { + icons = data.icons; + updateIconList(); + }); + } + + function updateIconList() { + const searchValue = $('#icon-search').val().toLowerCase(); + const style = $('#icon-style').val(); + $iconList.empty(); + icons + .filter((icon) => icon.includes(searchValue)) + .forEach((icon) => { + const iconElement = ``; + $iconList.append(iconElement); + }); + } + + $(document).on('focus', '#icon', function () { + const inputOffset = $(this).offset(); + const inputHeight = $(this).outerHeight() + 10; + $iconMenu + .css({ + top: inputOffset.top + inputHeight, + left: inputOffset.left, + }) + .slideDown(300); + }); + + $(document).on('click', function (event) { + if (!$(event.target).closest('#icon-menu, #icon').length) { + $iconMenu.slideUp(300); + } + }); + + $iconMenuHeader.on('input', '#icon-search', updateIconList); + $iconMenuHeader.on('change', '#icon-style', updateIconList); + + $iconList.on('click', 'i', function () { + const iconClass = $(this).attr('class'); + $('#icon').val(``).trigger('input'); + $iconMenu.slideUp(); + }); + + fetchIcons(); }); window.defaultEditorData = {}; diff --git a/app/Core/Admin/Http/Views/assets/js/pages/navigation/add.js b/app/Core/Admin/Http/Views/assets/js/pages/navigation/add.js index 2a8d911..3a41ab1 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/navigation/add.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/navigation/add.js @@ -15,7 +15,16 @@ $(function () { $(document).on('input', '#icon', function () { let val = $(this).val().trim(); - $('#icon-output').html(``); + let className = val; + + if (val.includes('`); }); // Обработчик события изменения текста в поле URL diff --git a/app/Core/Admin/Http/Views/assets/js/pages/servers/edit.js b/app/Core/Admin/Http/Views/assets/js/pages/servers/edit.js index 4009aed..ac6a90d 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/servers/edit.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/servers/edit.js @@ -52,3 +52,84 @@ $(document).on('click', '#check_ip', (ev) => { }), }); }); + +$(document).on('submit', '#commandInputForm', (e) => sendCommand(e)); + +$(document).on('click', '#openModal', () => { + Modals.open({ + title: translate('admin.servers.rcon_command_placeholder'), + content: { + form: createCommandInput(), + }, + buttons: [ + { + text: translate('def.close'), + class: 'cancel', + id: 'closeRconBtn', + callback: (modal) => modal.clear(), + }, + { + text: translate('def.submit'), + class: 'primary', + id: 'sendBtn', + callback: () => sendCommand(null), + }, + ], + }); +}); + +function createCommandInput() { + return { + id: 'commandInputForm', + fields: [ + { + type: 'text', + id: 'commandInput', + label: translate('admin.servers.rcon_command'), + placeholder: translate( + 'admin.servers.rcon_command_placeholder', + ), + helpText: translate('admin.servers.rcon_command_desc'), + }, + ], + }; +} + +async function sendCommand(e) { + if (e) { + e.preventDefault(); + } + + sendRequest( + { + command: $('#commandInput').val(), + ip: $('#serverIp').val(), + port: Number($('#serverPort').val()), + rcon: $('#serverRcon').val(), + game: $('#gameSelect').val(), + }, + u('admin/api/servers/check-rcon'), + 'POST', + function (res) { + if (res?.result) { + $('#commandInputForm') + .parent() + .html(` `); + $('#sendBtn').remove(); + $('#closeRconBtn').removeClass('cancel').addClass('primary'); + } else { + if ($('#commandInputForm > div > .error-message').length > 0) { + $('#commandInputForm > div > .error-message').html( + res.responseJSON?.error, + ); + $('#commandInputForm > div > .error-message').remove(); + } else { + $('#commandInputForm > div').append( + ` `, + ); + } + } + }, + false, + ); +} diff --git a/app/Core/Admin/Http/Views/assets/js/pages/socials/add.js b/app/Core/Admin/Http/Views/assets/js/pages/socials/add.js index 63ac105..d7f7e3e 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/socials/add.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/socials/add.js @@ -1,12 +1,32 @@ $(function () { + hideSteam(); + $(document).on('change', '#key', function () { let socialPlatform = $(this).val(); let redirectUri1 = u(`social/${socialPlatform}`); let redirectUri2 = u(`profile/social/bind/${socialPlatform}`); $('#redirectUri1').val(redirectUri1).attr('data-copy', redirectUri1); $('#redirectUri2').val(redirectUri2).attr('data-copy', redirectUri2); + + hideSteam(); }); - + + function hideSteam() { + let socialPlatform = $('#key').val(); + + if (socialPlatform === 'Steam' || socialPlatform === 'HttpsSteam') { + $('#driverSettings').hide(); + } else { + $('#driverSettings').show(); + } + } + + document + .querySelector('.chrome-tabs') + .addEventListener('contentRender', ({ detail }) => { + hideSteam(); + }); + $(document).on('submit', '#socialAdd, #socialEdit', (ev) => { let $form = $(ev.currentTarget); @@ -28,7 +48,9 @@ $(function () { { ...form, ...{ - settings: ace.edit($form.find('.editor-ace')[0]).getValue(), + settings: ace + .edit($form.find('.editor-ace')[0]) + .getValue(), }, }, url, diff --git a/app/Core/Admin/Http/Views/assets/styles/components/_inputs.scss b/app/Core/Admin/Http/Views/assets/styles/components/_inputs.scss index dd34e4c..ff27a5d 100644 --- a/app/Core/Admin/Http/Views/assets/styles/components/_inputs.scss +++ b/app/Core/Admin/Http/Views/assets/styles/components/_inputs.scss @@ -621,4 +621,61 @@ $input-placeholder-color: $color-inactive; &:hover { color: $color-primary; } +} + +.icon-menu { + display: none; + position: absolute; + background: $color-disabled; + border: 1px solid $color-white-5; + border-radius: 6px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + z-index: 1000; + width: 305px; + max-height: 400px; + overflow-y: auto; + z-index: 10; +} + +.icon-menu-header { + display: flex; + padding: 10px; + border-bottom: 1px solid $color-white-5; + position: sticky; + top: 0; + background-color: $color-disabled; + + .form-group { + width: 100%; + margin: 0; + padding: 0; + } +} + +#icon-search { + padding: 5px; + color: $color-text; + width: 160px; +} + +#icon-style {} + +.icon-list { + padding: 10px; + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.icon-list i { + cursor: pointer; + font-size: 22px; + color: $color-text; + padding: 10px; + border-radius: 5px; + transition: $transition; + + &:hover { + background-color: $color-white-5; + } } \ No newline at end of file diff --git a/app/Core/Admin/Http/Views/assets/styles/pages/servers.scss b/app/Core/Admin/Http/Views/assets/styles/pages/servers.scss index 3ff98b1..5253647 100644 --- a/app/Core/Admin/Http/Views/assets/styles/pages/servers.scss +++ b/app/Core/Admin/Http/Views/assets/styles/pages/servers.scss @@ -9,6 +9,18 @@ text-shadow: none; } +.error-message { + color: $color-error; + font-size: 14px; + margin-top: 20px; +} + +.success-message { + color: $color-success; + font-size: 14px; + white-space: break-spaces; +} + .servers-action-buttons { display: flex; gap: 5px; diff --git a/app/Core/Admin/Http/Views/assets/styles/pages/users.scss b/app/Core/Admin/Http/Views/assets/styles/pages/users.scss index e3e949f..240cce4 100644 --- a/app/Core/Admin/Http/Views/assets/styles/pages/users.scss +++ b/app/Core/Admin/Http/Views/assets/styles/pages/users.scss @@ -344,6 +344,8 @@ svg, i { width: 30px; height: 30px; - font-size: 25px; + font-size: 24px; + fill: $color-primary; + color: $color-primary; } } \ No newline at end of file diff --git a/app/Core/Admin/Http/Views/pages/footer/list.blade.php b/app/Core/Admin/Http/Views/pages/footer/list.blade.php index 80f7f96..9c0d2e7 100644 --- a/app/Core/Admin/Http/Views/pages/footer/list.blade.php +++ b/app/Core/Admin/Http/Views/pages/footer/list.blade.php @@ -84,6 +84,6 @@ @endpush @push('footer') - + @at('https://SortableJS.github.io/Sortable/Sortable.js') @at('Core/Admin/Http/Views/assets/js/pages/footer/list.js') @endpush diff --git a/app/Core/Admin/Http/Views/pages/footer/social/add.blade.php b/app/Core/Admin/Http/Views/pages/footer/social/add.blade.php deleted file mode 100644 index 946b5ca..0000000 --- a/app/Core/Admin/Http/Views/pages/footer/social/add.blade.php +++ /dev/null @@ -1,71 +0,0 @@ -@extends('Core.Admin.Http.Views.layout', [ - 'title' => __('admin.title', ['name' => __('admin.footer.social_add')]), -]) - -@push('header') - @at('Core/Admin/Http/Views/assets/styles/pages/footer.scss') -@endpush - -@push('content') -@t('admin.footer.social_add_description')
-@t('admin.footer.social_edit_description')
-@t('admin.footer.social_description')
-