• Как сделать так, чтобы при нажатии не исчезало?

    Метод называется checkbox hack.
    Мы на самом деле используем checkbox и его превосходное свойство работать даже при нажатии на метку, которая с ним ассоциирована. Таким образом мы скрываем сам нативный checkbox, а его метку стилизуем так, как нам надо, хоть в виде кнопки, хоть в виде чего угодно. И вместо :active у кнопки проверяем :checked у чекбокса
    https://css-tricks.com/the-checkbox-hack/

    А вот ещё набор идей
    https://codepen.io/
    В поиске наберите "toggle-switch"

    И вот что-то похожее на ваш случай
    Ответ написан
    7 комментариев
  • Как заставить предупреждение срабатывать только после нажатия "Да/Нет" в диалоговом окне?

    Первое, что вам надо сделать - изучить самые базовые основы Javascript и DOM, и как это всё работает.
    Вы должны понимать, что Javascript - это не PHP. PHP работает только в один поток и выполняет операции исключительно одну за другой по порядку. В Javascript же есть возможность выполнять код асинхронно с использованием такого инструмента, как Event Loop. Ещё обязательно изучить, что такое Promise и как подсластить его использование при помощи сахарка async - await

    Т.е. ваш обычный код, который выполняется по порядку, может выполняться как бы "одновременно" с кодом, который выполняется при нажатии на кнопку. Т.е. пока ваша кнопка ждёт события click, чтобы выполнить какой-то код по нажатию, остальная программа спокойно себе исполняется по порядку. Таким образом, пользователь еще не успел кликнуть, но ваша программа уже создала диалог при помощи функции Myconfirmselect, и спокойно себе приступила к следующей операции. А этой следующей операцией как раз и является тот самый alert("!"). Вот он и появляется на экране.

    Т.е. нам надо как бы подсунуть этот alert внутрь функции Myconfirmselect. Как это сделать? В Javascript функции - это такие же объекты. Т.е. функцию можно назначить какой-то переменной и передать эту переменную в другую функцию. Вот мы и передаём наш callback в функцию Myconfirmselect просто в качестве аргумента, и вызываем этот аргумент как функцию уже в том месте, где обрабатывается событие click на кнопку.

    P.S. В коде постоянно происходит выборка одного и того же элемента. Это не очень хорошо, потому что такая выборка не бесплатна, все операции с DOM - довольно медленные, поэтому я позволил себе переписать часть кода, не меняя самой его структуры, чтобы вам было понятнее. Выбрав один раз наш конкретный dialog нет смысла выбирать его постоянно еще и еще раз. Запихнём его в переменную и будем с ним работать. То же самое касается и textarea и div

    <html>
        <body onload="Load()">
            <dialog></dialog>
            <script>
                function Load() {
                    setTimeout(() => {
                        // Создаём колбэк, функцию, которая будет вызываться только после нажатия на кнопку
                        const callback = function () {
                            alert("!");
                        };
    
                        Myconfirmselect(
                            "Готовы?" +
                                "¤" +
                                "Да" +
                                "¥" +
                                "alert('Да');" +
                                "º" +
                                "Нет" +
                                "¥" +
                                "alert('Нет');",
                            callback, // Передаём колбэк в функцию создания диалога
                        );
                    }, 500);
                }
    
                function Myconfirmselect(VarInput, callback) {
                    const dialog = document.querySelector("dialog");
                    dialog.innerHTML += `
       <div>
        <textarea></textarea>
       </div>
       `;
    
                    const textarea = dialog.querySelector("textarea");
                    const div = dialog.querySelector("div");
                    dialog.style.textAlign = "center";
                    textarea.style.width = "100%";
                    textarea.style.textAlign = "center";
                    textarea.style.resize = "none";
                    textarea.readOnly = true;
                    textarea.value = VarInput.split("¤")[0];
                    for (
                        let i = 0;
                        i < VarInput.split("¤")[1].split("º").length;
                        i++
                    ) {
                        const VarButton = document.createElement("button");
                        VarButton.textContent = VarInput.split("¤")[1]
                            .split("º")
                            [i].split("¥")[0];
                        VarButton.id = VarInput.split("¤")[1]
                            .split("º")
                            [i].split("¥")[1];
                        VarButton.style.marginLeft = "10px";
                        VarButton.style.marginTop = "10px";
                        VarButton.addEventListener("click", () => {
                            eval(event.target.id);
                            // Вызов того самого callback после нажатия на кнопку
                            callback();
                            //ButtonClosealert();
                        });
                        div.appendChild(VarButton);
                    }
                    function ButtonClosealert() {
                        dialog.style.animation = "AnimFormHide 0.1s both";
                        setTimeout(() => {
                            dialog.style.textAlign = "initial";
                            const div = dialog.querySelector("div");
                            dialog.removeChild(div);
                            dialog.close();
                        }, 100);
                    }
                    dialog.style.animation = "AnimFormShow 0.25s both";
                    dialog.showModal();
                }
            </script>
            <style>
                @keyframes AnimFormShow {
                    0% {
                        transform: translateY(-150%);
                    }
                }
    
                @keyframes AnimFormHide {
                    100% {
                        transform: translateY(-150%);
                        display: none;
                    }
                }
    
                dialog,
                #DialogCycle {
                    border: 2px solid black;
                    border-radius: 15px;
                }
    
                dialog div,
                #DialogCycle div {
                    position: initial;
                }
    
                dialog::backdrop,
                #DialogCycle::backdrop {
                    background: rgba(0, 0, 0, 0.6);
                }
            </style>
        </body>
    </html>
    Ответ написан
    Комментировать
  • Какие минусы у данного решения?

    А не надо гадать. Находите какой-то автоматический инструмент, который проверяет все эти параметры, и радуетесь. Есть куча подобных расширений для браузеров.
    Например:

    https://silktide.com/toolbar/screen-reader-simulator/

    https://chromewebstore.google.com/detail/web-disab...

    Ваша цель:
    - сделать так, чтобы устройства для слепых "screen reader" адекватно сообщали пользователю, что от него требуется.
    - чтобы контраст был адекватным, чтобы и слабовидящие могли пользоваться
    Ответ написан
    2 комментария
  • Slog. Нужно ли предавать логгер через параметры или объявить его на уровне модуля и обращаться к нему?

    Насчёт параллельных обращений. slog очень гибкий и продуманный логгер. Не стоит беспокоиться насчёт параллельных обращений, там всё отлично с этим.

    Насчёт того, использовать ли логгер глобально...
    На этот вопрос трудно ответить, не зная, какой у вас проект.

    Лично я предпочитаю пользоваться принципом "Бритвы Оккама" и не вводить новых сущностей без необходимости.

    Сначала надо задаться вопросом: а нужны ли мне разные варианты логгера в одном и том же окружении? Что я такого буду логгировать в разные места и разным образом, чтобы мне надо было иметь кучу разных логгеров и передавать их как зависимость в каждую функцию или в каждый ваш синглтончик. И если не нужны, то зачем городить огород?

    Тем более, что даже для тестов вы можете написать для slog какой-то особый хэндлер, который будет работать так, как надо в тестах.

    Поэтому я последнее время всегда использую глобальный slog. В начале программы загружаю конфиг, переменные окружения, определяю, какой мне в этом окружении будет нужен логгер, и конфигурирую дефолтный slog с нужным мне хендлером.

    Вот вам крайне увлекательное и крайне полезное видео, как из глобального slog можно сделать просто монстра логгирования на все случаи жизни.
    https://www.youtube.com/watch?v=p9XiOOU52Qw

    Вот пример того, как я настраивал логгер в одном из маленьких проектов. Он определяет по конфигу, с какого уровня надо логгировать, в каком формате, и в какой поток (stdout, stderr, файл)

    package app
    
    import (
    	"fmt"
    	"io"
    	"log/slog"
    	"os"
    )
    
    // SetupLogger создаёт логгер на основании конфига
    // Конфигурируем дефолтный slog и возвращаем функцию закрытия файла логов, 
    // если был выбран файл как место логгирования.
    func SetupLogger(cfg config.Config) func() error {
    	closer := func() error { return nil }
    
    	level := cfg.LogLevel
    
    	var w io.Writer
    
    	switch cfg.LogOutput {
    	case config.LogOutputStderr:
    		w = os.Stderr
    	case config.LogOutputStdout:
    		w = os.Stdout
    	case config.LogOutputFile:
    		w, closer = getFileWriter(cfg)
    	}
    
    	var handler slog.Handler
    	switch cfg.LogFormat {
    	case config.LogFormatText:
    		handler = slog.Handler(slog.NewTextHandler(w, &slog.HandlerOptions{Level: level}))
    	case config.LogFormatJSON:
    		handler = slog.Handler(slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level}))
    	}
    
    	slog.SetDefault(slog.New(handler))
    
    	return closer
    }
    
    // getFileWriter возвращает файл, куда писать логи и функцию закрытия этого файла
    func getFileWriter(cfg config.Config) (*os.File, func() error) {
    	logDir := fmt.Sprintf("%s/%s", cfg.VarDir, cfg.LogDir)
    	err := os.MkdirAll(logDir, 0755)
    	if err != nil {
    		panic(fmt.Sprintf("error creating directory: %s;  %v", logDir, err))
    	}
    
    	filename := fmt.Sprintf("%s/%s.log", logDir, cfg.AppEnv)
    	f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    	if err != nil {
    		panic(fmt.Sprintf("error opening file: %s %v", filename, err))
    	}
    	return f, f.Close
    }


    package main
    
    import "app"
    
    func main() {
    
            // .....
    
    	closeLogFile := app.SetupLogger(cfg)
    	defer closeLogFile()
    
            // ...
    }
    Ответ написан
    5 комментариев
  • Как в js, scss, html реализовать tooltip который показывается если навести на объект ниже?

    А зачем такие сложности с такой простой задачей, как тултипы? Их ведь можно реализовать полностью на CSS, просто добавив нужному элементу, например, атрибут "tooltip"



    Анимацию уже сами добавите, если очень нужна

    Если же очень хотите именно при наведении на следующий элемент что-то делать, то могу просто предложить идею селектора, который позволяет таким образом выбирать



    .custom-tooltip:has(+ *:hover) означает, что выбрать надо те элементы .custom-tooltip, у которых на следующем за ними элементе наведена мышь.
    "+" выбор непосредственно следующего элемента
    "+ *"- выбор любого непосредственного следующего элемента
    "+ *:hover" - выбор любого непосредственного следующего элемента, на который наведена мышь.
    has  выбирает элементы .custom-tooltip, с которым связаны элементы, выбранные при помощи уточняющего селектора в скобках.
    Ответ написан
    1 комментарий
  • Как избежать race condition при вставки новой записи в бд SQL, PHP?

    Создайте в таблице какой-то уникальный индекс, основанный на ваших данных, и второй раз одни и те же данные просто не получится вставить.
    Ответ написан
    2 комментария
  • Как обрабатывать данные, сохраняя очередность?

    Пусть хук записывает всю необходимую информацию и, главное, время в специальную таблицу в базе данных. Это всё, что будет делать хук. Таким образом он будет работать всегда быстро и всегда за одно и то же очень малое время.

    Далее по крону или каким-то другим образом совершенно другой скрипт будет выбирать самую старую запись из этой таблицы и обрабатывать её, совершая необходимые действия. Далее эту запись либо удаляем либо помечаем обработанной и берём следующую запись
    Ответ написан
    4 комментария
  • Почему не рендерится ответ fetch в виде DOM?

     Если вам нужно динамическое обновление частей страницы, и вы не можете это сделать сами, то вам не стоит изобретать велосипед, а воспользоваться готовыми библиотеками для этого.

    1. Вы можете вообще взять под контроль весь рендеринг DOM при помощи React, Vue и т.д. Но это вам надо будет серьёзно погрузиться в изучение этих фреймворков, и в классическом варианте вы потеряете поисковую оптимизацию.

    2. Воспользоваться библиотеками, которые как раз для этого предназначены.

    - Для Symfony есть прекраснейший компонент Live Components. Вы пишете всё в Twig, вставляя там особые атрибуты, и компонент возьмёт на себя всю работу по замене частей страницы нужными компонентами.

    - Либо, если вам не понравится почему-то Live Components, можете воспользоваться Htmx

    В результате вам даже не нужно будет особо знать JavaScript, всё будет автоматом работать

    P.S. Если вы уже пользуетесь фреймворком, то пользуйтесь его плюшками, сильно упрощающими жизнь. Я имею ввиду, что не стоит использовать голый curl с простынёй настроек, когда в наличии имеется превосходный HTTP Client https://symfony.com/doc/current/http_client.html
    А для сериализации/десериализации из JSON в PHP и обратно, лучше всего использовать Serializer component https://symfony.com/doc/current/components/seriali...
    Ответ написан
    Комментировать
  • Как отследить нажатие по определенной границе в canvas?

    У canvas есть прекрасный метод isPointInPath

    Документация https://developer.mozilla.org/en-US/docs/Web/API/C...

    Ответ написан
    Комментировать
  • Почему PhpStorm показывает ошибку в строке с SQL запросом?

    Ну так измените ваш диалект SQL. Phpstorm просто не знает, каким именно SQL вы пользуетесь (PostgreSQL, MySQL, SQLite).

    На скрине во всплывашке прямо внизу слева есть синяя ссылка "Change dialect to"

    К тому же, по-моему, если в проекте подключить базу в окне с базами данных, то он должен сам подхватить диалект оттуда
    Ответ написан
    3 комментария
  • Почему такой неадекватный расход памяти?

    У утечки памяти может быть миллион разных причин. Искать их, просто читая код - это искать иголку в стоге сена.

    Поэтому, умные люди придумали профайлинг. Мы собираем статистику и пытаемся её отобразить в более-менее наглядном виде.

    Например, есть вот такой инструмент
    https://github.com/arnaud-lb/php-memory-profiler
    Readme очень подробный, и можно, в принципе, им и обойтись.

    Есть ещё вот такие видео, в которых всё более комплексно и подробно

    https://www.youtube.com/watch?v=NNMp-97rk9c&t=219s

    https://www.youtube.com/watch?v=56I5C0NYjv8&t=293s

    Удачи в поисках!
    Ответ написан
    1 комментарий
  • Нужны ли везде дивы и как должна выглядеть верстка?

    Бри́тва О́ккама (иногда ле́звие О́ккама) — методологический принцип, в кратком виде гласящий: «Не следует множить сущее без необходимости» (либо «Не следует привлекать новые сущности без крайней на то необходимости»).

    Это главный принцип и верстки, и программирования.

    1. Оборачивайте элемент в div (или другой контейнер), если вы точно знаете, зачем вы это делаете, и без этого нельзя обойтись.

    2. Не вставляйте везде бездумно div. Почитайте про семантическую вёрстку, и используйте разные элементы-обёртки для разных типов контента.

    https://htmlacademy.ru/blog/html/semantics
    Ответ написан
    1 комментарий
  • В чем может быть причина сильного увеличения длительности ответа запросов при установке Load Balancer на 2 сервера со стаком MySQL, PHP, Nginx?

    Система слишком сложна для того, чтобы можно было вот так вот взять - и ответить на ваш вопрос, не изучив её. Когда в системе хотя бы несколько компонентов, взаимодействующих между собой, это сигнал к тому, что всё нужно обвесить мониторингом.

    Рекомендую вам найти слабое звено, начиная с самого конца:

    1. Создайте тестовое окружение в тех же облаках, с такими же компонентами, с идентичным конфигом и воспроизведите поведение при помощи какой-нибудь утилиты, которая будет нагружать эту конфигурацию, пуляя в неё запросы с необходимой частотой.

    2. Когда тестовое окружение будет так же тормозить, начните искать слабое звено. Рекомендую начать с конца.

    3. Замените весь ваш прекрасный PHP код на один единственный файл index.php с Hello World. Если проблема уйдёт, значит виновата ваша программа. Чтобы найти место уже в программе, рекомендую запустить Prometheus + Grafana, установить библиотеку в ваш PHP, и писать в мониторинг длительность каждой сложной операции (длительность всего запроса, длительность запросов к базе, длительность запросов во внешние API, длительность каких-то подозрительных циклов, алгоритмов и т.д.) Так вы сможете найти точное место в программе, где происходит затуп. Это надо будет сделать и на проде после решения проблемы, чтобы вы всегда знали, что происходит с вашим проектом.

    4. Если проблема не в программе, проверьте конфиг PHP. Может быть, у вас там тупо мало воркеров включено, может быть не включён OPcache и т.д. Почитайте, как правильно настраивать PHP на проде.

    5. Если проблема не в этом, то... https://youtu.be/bq3HksAwb2Q?si=NsBZeYeHsq69JW6o
    Ответ написан
    Комментировать
  • Как должен выглядеть идеальный контроллер?

    Если хотите идеал, то он должен соответствовать следующим пунктам:

    1. Сериализация/десериализация - это дорогостоящее мероприятие, поэтому оно должно делаться только в двух местах: прямо на входе и прямо на выходе. Вход - это ваш контроллер, Выход - это другой сервис, куда вы передаёте данные, или база данных (тут тоже происходит сериализация, либо явно, либо в ORM). Во всех остальных слоях инфообмен должен совершаться уже при помощи объектов PHP либо нативных типов. Это экономит ресурсы. При передаче между слоями приложения объектов вместо значений либо ассоциативных массивов вы сразу будете видеть очепятки, IDE вам прекрасно поможет при помощи автодополнения, объекты могут иметь какие-то полезные методы.

    2. Очень желательно в каждом из слоёв иметь собственный класс, отвечающий за данные. Например, нам в слой API приходит JSON-чик с новым пользователем.
    - Сериализуем JSON в DTO UserInAPI, сразу валидируем всё то, что мы можем валидировать без слоя бизнес-логики, и либо отдаём клиенту ошибку, либо передаём сам объект UserInAPI в следующий слой: слой бизнес-логики
    - В слое бизнес логики, получаем DTO UserInAPI на входе, преобразуем его в свой бизнес-объект UserInBusiness, валидируем его уже с точки зрения бизнеса, и либо возвращаем ошибку в слой API, либо совершаем над ним действия, и передаём объект класса UserInBusiness в слой работы с базой
    - В слое работы с базой данных получаем на входе объект UserInBusiness, преобразуем его уже в сущность базы данных UserInDB, валидируем всё на предмет корректности данных для базы, и либо возвращаем ошибку в бизнес, либо сохраняем сущность класса UserInDB в базу.

    Зачем такие сложности, вы спросите? А просто обратите внимание на то, что скорость изменения кода в разных слоях разная.
    - API вообще должен меняться раз в сто лет, чтобы не злить клиентов. Поэтому DTO класс UserInAPI будет стабильным и редко будет меняться.
    - Бизнес-логика меняется очень часто. У класса UserInBusiness постоянно будут добавляться поля и методы, тут жизнь будет кипеть.
    - Слой базы данных будет меняться реже, чем слой бизнеса, но чаще, чем слой API, потому что нам нужны будут новые поля в базе, новые таблицы и связанные таблицы.
    - И если мы один тип сущности протащим во все слои, то эта сущность обрастёт таким количеством различной хрени, что нам плохо станет, когда будем на неё смотреть. Либо она обрастёт кучей декораторов в каждом из слоёв. Поэтому лучше всё разделить.

    3. Теперь внимание, казалось бы, что мы слишком сильно связываем наши слои, и нижестоящие слои знают что-то о вышестоящих, а это неправильно. Ведь мы передаём объект UserInAPI в слой бизнеса, т.е. слой бизнеса должен уметь работать с этим объектом. И так же слой базы должен уметь работать с объектом бизнеса UserInBusiness. Как же быть? А очень просто. На входе слоёв использовать интерфейсы. Т.е. в слое бизнеса мы будем принимать не сам класс UserInAPI, а объект, имплементирующий интерфейс UserIncoming, который объявим в бизнес слое, и заставим слой API сделать так, чтобы его класс UserInAPI имплементировал этот интерфейс. Таким образом слой бизнеса ничего не будет знать о слое API, а будет ждать на входе данные по контракту, описанному в интерфейсе. Бизнесу плевать на конкретную реализацию, ему нужны только методы getUsername, getEmail из интерфейса. А какой класс ему их предоставит - пофигу. Таким образом мы практически полностью разделяем слои и в два счёта сможем поменять слой API, где у нас HTTP контроллеры, на слой RabbitMQ, SOAP, Grpc и т.д.
    Ответ написан
    6 комментариев
  • Как правильно делать большие сайты? обязательно ли писать каждую страницу по отдельности?

    Вопрос в том, насколько вы задолбались копипастить и как часто вы меняете дизайн и структуру вашего сайта. Если просто делать всё индивидуальными страничками, то поменять дизайн, даже если изменения совсем небольшие, будет болью в одном месте. Поэтому, что-то бэкендо-подобное вам надо будет изучать. Это вам не повредит, поверьте. React сейчас полным ходом несётся в сторону рендеринга на сервере, поэтому вам придётся это учить.

    Итого, есть три пути:

    1. Беспроигрышный. Это Wordpress. Берете, делаете, все счастливы. Туториалов миллиарды, плагинов ещё больше, всё будет зашибись. Проблема одна: если надо будет тонко настраивать, то надо будет хоть немного изучить PHP.

    2. Когда надо быстро что-то добавить, даже с мобилы или с компьютера девушки, и чтобы оно сразу отобразилось на сайте, то либо смотрим в сторону п.1, либо если не хотите изучать PHP, выбираете себе CMS на базе Javascript. Есть даже так называемые headless cms, которые генерируют вам только JSON, а вы у себя на фронтенде сами его обрабатываете и рисуете всё на основе полученных данных.

    Это самый сложный вариант из трёх

    Вот список некоторых из таких CMS
    https://jamstack.org/headless-cms/

    3. Если вам не нужна молниеносная публикация новых материалов, и вы можете потерпеть до доступа к рабочему ноуту, то можно выбрать генератор статических сайтов.

    Сразу посоветую вам Astro https://astro.build/

    Прелесть Astro в том, что новый контент вы можете писать в Markdown. Это такой простейший язык разметки текстовых документов. Вы сможете его выучить за час, не больше. Там всё настолько интуитивно и просто, что больше времени вам не понадобится. При этом формат мощный, убедиться в этом можно, посмотрев почти любой репозиторий на Гитхабе. Практически каждый из них имеет файл README.MD, где как раз всё написано в Markdown (MD)

    В Astro вам надо будет создать несколько шаблонов astro (html). Шаблон хедера, футера, сайдбара, основного контента, разных типов страниц (статья, список статей, и т.д.). И в этих шаблонах будет указано, где нужно выводить сгенерированный из Markdown контент. И далее новые странички вы будете добавлять путём добавления в соответствующие папки файликов с Markdown разметкой. Это очень просто, быстро и не напряжно.

    После добавления этих файликов вы запустите генерацию сайта, и Astro сгенерирует вам кучу готовых html страничек, которые вы просто зальёте на сервак. Профит!

    Берите youtube, набирайте там "Astro markdown", и изучайте туториалы. Процесс всего этого чрезвычайно прост, вы много времени не потеряете, зато сэкономите себе массу времени на создании новых материалов на сайте

    Можете посмотреть и другие генераторы статических сайтов тут https://jamstack.org/generators/
    Ответ написан
    Комментировать
  • Не находит пакет gitlab-ee на Ubunty. Что делать?

    Не мучайтесь. Берите докер.

    Вот обычная команда для запуска контейнера. Контейнер запустится в фоне и будет автоматически перезапускаться при остановке или перезагрузке хоста.

    docker run --detach \ # Запускаем контейнер в фоновом режиме
      --publish 443:443 --publish 80:80 --publish 22:22 \ # Открываем порты. Можете поставить те, что вам нужны, если эти уже используются где-то
      --name gitlab \ # Имя контейнера
      --restart always \ # Перезапускать контейнер при его остановке или перезагрузке
      --volume gitlab_config:/etc/gitlab \ # Подключаем том для конфигурации
      --volume gitlab_logs:/var/log/gitlab \ # Подключаем том для логов
      --volume gitlab_data:/var/opt/gitlab \ # Подключаем том для данных
      --shm-size 2gb \ # Устанавливаем размер разделяемой памяти
      gitlab/gitlab-ee:latest


    Или же можете использовать docker-compose.yml файл и запускать, находясь в этой же дириктории, через:

    docker compose up -d

    version: '3.6'
    services:
      web:
        image: 'gitlab/gitlab-ee:latest'
        container_name: gitlab
        restart: always
        ports:
          - '80:80'
          - '443:443'
          - '22:22'
        volumes:
          - 'gitlab_config:/etc/gitlab'
          - 'gitlab_logs:/var/log/gitlab'
          - 'gitlab_data:/var/opt/gitlab'
        shm_size: '2gb'
    
    volumes:
      gitlab_config: {}
      gitlab_logs: {}
      gitlab_data: {}


    Если докер у вас свежий и будет ругаться на наличие 'version', то просто удалите строчку с version

    Чтобы узнать начальный пароль, выполните:

    docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
    Ответ написан
    4 комментария
  • Что пытается сделать злоумышленник на моем сайте?

    На первом скрине, похоже, атака через "Stacked Queries". Хацкер надеется, что вы не отфильтровали входящие данные.

    Допустим у вас из бройзера приходит ID товара. И в программе запрос:

    SELECT * FROM products WHERE productid={нефильтрованный ID};


    Хакер отправляет вместо ID товара следующее:

    1; SELECT pg_sleep(25)


    В результате получится запрос:

    SELECT * FROM products WHERE productid=1; SELECT pg_sleep(25);


    И ваш сервер при каждом таком запросе будет спать 25 секунд. И порядочное количество таких запросов в случае удачи атаки смогут повесить вам там всё.

    То, что он продолжает долбиться, ничего не значит. Он, наверняка, использует какого-то бота, где накиданы в кучу различные векторы атаки, и они все применяются, пока ваш сервер не упадёт в результате срабатывания какой-то из них.
    Ответ написан
    Комментировать
  • Проблема SEO во Vue 3?

    Делать рендеринг VUE на сервере, используя Go, это задача не для слабонервных. Вы можете как-то присобачить гошный JavaScript рантайм и пытаться его использовать для рендеринга VUE, но я и врагу такого не посоветую

    Вот вам рантайм, написанный на чистом Go:
    https://github.com/dop251/goja

    Я не специалист в SEO, но где-то слышал, что поисковики научились индексировать SPA.
    НО!!!

    1. Это не точно) И вам надо провести мини исследование. Найти какие-то популярные SPA сайты и поглядеть архив Гугла и Яндекса по ним.

    2. Если это работает, то самое главное, что вам нужно усвоить, это то, что каждый чих должен отражаться в URL. History API должен стать вашим всем. Выбрали какую-то сортировку или фильтр таблицы с данными или списка статей? Всё это должно ОБЯЗАТЕЛЬНО отражаться в URL, как если бы вы писали какой-то традиционный сайт на PHP. Только в этом случае вам можно надеяться, что контент хоть как-то будет проиндексирован. Это, кстати, даст вам ещё одно преимущество: вы сможете без последствий нажать кнопку перезагрузки страницы, и её содержимое не должно измениться.

    3. Умение сказать заказчику НЕТ, одно из важнейших умений программиста. Пусть он взвесит на весах SEO и Go, и примет решение. Запросите у него космические деньги за сервер-рендеринг на Go, либо скажите, что можно гораздо дешевле сделать это на Nuxt.

    Либо можно пойти на компромисс, и сделать на Nuxt только лишь тупую мини-прослойку между фронтендом и основной бизнес-логикой, которая будет реализована в отдельном сервисе на Go
    Ответ написан
    3 комментария
  • Когда лучше использовать cms?

    - Если вы работаете исполнителем на стороннего заказчика, то переложите ответственность за выбор метода на него. Чтобы он сам отвечал, если что-то в будущем пойдет не так. Перед этим его желательно убедить, почему именно самый дорогой метод лучше дешевых.

    - Если вы работаете в команде, все такие решения необходимо принимать, исходя из того, что ваша команда умеет, с чем вам проще и эффективнее работать. И тогда активно убеждать заказчика в выборе этого метода.

    - Во всех остальных случаях я предлагаю сперва задать себе вопрос: "А с какой целью я делаю этот проект?"

    В подавляющем большинстве случаев ответ будет "хочу заработать денег". Это и должно определять все ваши решения.
    1. Чтобы проект зарабатывал деньги, он должен быть на рынке. Т.е. он должен работать. Это значит, что скорость разработки - один из важнейших факторов, ведь ту небольшую нишу, куда вы целитесь, может раньше занять кто-то более расторопный. Тут побеждает CMS с модификацией готовых тем и кучей готовых модулей на все случаи жизни.

    2. Хорошенько распланируйте проект на логические подзадачи. Проанализируйте, какие есть инструменты для решения каждой из этих задач. Возможно, какая-то CMS, фреймворк, библиотека, либо даже язык программирования помогут вам решить данные конкретные задачи наиболее быстро и эффективно.

    3. Не задумывайтесь сильно о качестве кода. Вам всё равно предстоит сильно переделывать проект в будущем. Если вашу задачу по зарабатыванию денег решит один единственный файл index.php, то и наплевать на всё остальное. Совсем говнокодить, конечно, не надо. Ваша задача сделать код максимально простым и понятным. Чтобы через несколько месяцев вы, только взглянув на участок кода, сразу понимали бы, что там происходит. А то бывает, напишешь настолько изящную систему типов, монады, рекурсии, чистые функции, отложенные вычисления, разобьешь код на функции по три строчки каждая, приправишь это всё DDD, чистой архитектурой, SOLID, DRY и прочим, а потом целый день разбираешься, почему не можешь поменять цвет кнопки с красного на синий...

    Мораль: Да, инструмент всегда нужно выбирать под конкретную задачу. Но если вы, забив шуруп в доску микроскопом, обнаружите, что размера вашего кармана не хватает для потолстевшего кошелька, то можете смело плевать в лицо тем, кто будет вас осуждать за такое решение...
    Ответ написан
    2 комментария