Русский
Русский
English
Статистика
Реклама

Таблицы

Поиск данных в столбцах таблицы с пагинацией (front-часть)

26.02.2021 16:11:32 | Автор: admin

Введение

Всем добрый день. Меня зовут Александр. Сейчас я работаю в Мегафон front-end разработчиком. Проблемы поиска данных всегда отличались особенной сложностью и зачастую нестандартностью в подходах. Сегодня я бы хотел остановиться на одной интересной задаче, которую мне пришлось решать совсем недавно во время разработки платформы Интернета вещей. Впрочем, такая задача, может встретиться и на любом другом проекте, где есть динамическая подгрузка данных по REST API. Будь то подгрузка во время пагинации, или во время скроллинга, или как то иначе

Проблематика

Казалось бы: в чем может быть трудность. Особенно когда речь касается только фронта? Ведь все алгоритмы поиска реализуются в основном на бэке. На самом деле и да, и нет. Давайте представим простую таблицу, у которой есть пагинация из нескольких страниц и фильтр на каждом столбце. См. ниже.

Таблица с фильтрами в столбцахТаблица с фильтрами в столбцах

В данной табличке открыт фильтр по столбцу Номер БС. Простыми словами: вводя любые символы в поле ввода фильтра, вы получаете подходящие опции в дропдауне. Кликнув на любую из них, вы отфильтруете данные в таблице по этому элементу.

Каким образом можно вывести подобный дропдаун с нужными опциям?

Варианты решения

  1. Составить массив из всех элементов столбца таблицы и с помощью методов filter и includes (или им подобных) показать нужные опции в дропдауне. Проблема в том, что компонент таблицы не знает о элементах, которые находятся на других страницах. Они подгружаются только при переходе на эти страницы. А значит поиск будет осуществляться не по всем элементам.

  2. Сформировать массив как в 1-м варианте, но запрашивая его с бэка. Хороший вариант, но производительность такого подхода близится к 0. Что если записей 1 млн? В таблице именно для этого и используется пагинация.

  3. Отправлять строку фильтра на бэк, и уже с бэка получать нужные опции для дропдауна. Бэк знает обо всех записях и на нем можно использовать всевозможные алгоритмы поиска в базах данных, которые будут работать достаточно быстро.

    Отлично. Именно этот вариант мы и рассмотрим. На рисунке выше изображен именно он: мы ищем записи, которых нет на текущей странице, однако в дропдауне видим подходящие варианты со всех страниц.

Алгоритм

  1. Для каждого столбца должен существовать запрос на бэк и поле, которое мы будем брать из ответа, чтобы заполнить опции. Нужен config.

  2. После ввода данных их нужно отправлять на бэк. Можно по кнопке, но лучше использовать периодическую отправку в функции debounce.

  3. Во время процесса отправки/получения данных показывать loader

  4. Получения ответа от бэка и заполнение опций для дропдауна из заданного поля ответа

Решение

Я предполагаю, что читатели знакомы с JS и React и в данной статье нет смысла рассказывать о том, как отправлять запросы, фильтровать массивы и другие основы JS. Я буду останавливаться лишь на важных составных частях алгоритма.

Итак, начнем по порядку. Как мы выяснили, для начала нужен конфиг для таблицы (для каждого столбца) и ее фильтров. Разделим его на 2 файла.

Пример конфига столбца таблицы:

{  id: 'address',  title: 'Адрес объекта',  filter: filters.address,  checked: true,  minWidth: 160}

Пример конфига фильтра:

address: {  type: 'includes',  name: 'addrFilter',  options: {    default: {      values: 'objectsList',      fetchFunc: 'fetchObjectsList',      calcFunc: 'address'    }  }}

Как можно заметить конфиги связаны по типу один-ко многим. То есть один и тот же фильтр может соответствовать нескольким столбцам таблицы.

Поле options содержит как раз поля для конфигурации опций фильтра. Например:

  • fetchFunc - имя thunk функции, которая загружает данные для этого фильтра

  • values - поле из ответа сервера, в котором содержатся опции

  • calcFunc - имя функции для трансформирования опций

Остановимся подробнее на calcFunс. При таком алгоритме, что был описан в опции попадают данные из определенного поля ответа. Но что если поле составное, например в каждую опцию фильтра должно попасть сразу несколько полей. Пример с адресом: опция фильтра = город + улица + дом

Фильтр адресаФильтр адреса

В этом нам и поможет функция предварительного обсчета опции. Вот пример реализации:

//object includes calc functionsconst calculatedData = useMemo(() => (  {    default: (values) =>    {      //default calculate    },    address: (values) =>    {      //calculate with generateAddress function, for example    },    ...}), [...]);//using this object (calcFunc from config):const data = calculatedData[calcFunc || 'default'](values)

Далее. Как уже говорилось лучше всего отправлять запросы через определенный интервал задержки. То есть пользователь вводит данные в поле, приостановился на полсекунды - произошла отправка. Реализуется это примерно так:

// debounce functionconst debounce = (fn, ms = 0) => {  let timeoutId;  return function(...args) {    clearTimeout(timeoutId);    timeoutId = setTimeout(() => fn.apply(this, args), ms);  };};//debounceFetch functionconst debounceFetch = debounce(async (func, args) =>   typeof func === 'function' && (await func(args)), 500);//sending requestuseEffect(() => {  debounceFetch(actions[fetchFunc], {     filter: { [filterName]: filterValue || null }   });}, [filter]);

Продолжаем. В текущей задаче может понадобиться определить грузится ли в данный момент какой-либо фильтр, например, чтобы показать лоадер. У нас в команде приняты некоторые стандарты для именования переменных, например, все переменные, показывающие процесс загрузки начинаются с isLoading. Например, isLoadingObjects. Поэтому идеальным решением будет создать selector для kea, который = true если хотя бы в одном стейте, содержащем в названии isLoading, содержится значение true.

Если вы используете не kea, а обычный redux или какую-то другую библиотеку - уверен, вы адаптируете этот код под свои нужды. Если нет - пишите мне. А лучше используйте kea =)

Далее вы просто показываете лоадер пока anyLoading===true, все как обычно.

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

Всю логику лучше всего будет объединить в один хук и назвать его, скажем, useFiltersOptions.

Выводы

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

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

Подробнее..

Адаптация таблиц под мобильные устройства

15.03.2021 10:06:02 | Автор: admin

Для кого эта статья

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

Проблема

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

Данная проблема встречается повсеместно, и усугубить ее может пожалуй лишь контент ячейки, который не переносится построчно, увеличивая тем самым ширину колонки.

Подопытный набор данных

Для того чтобы понимать лучше суть проблемы, мы придумаем себе таблицу, и будем ее адаптировать.

Колонки:

  1. Номер

  2. Фото

  3. ФИО

  4. Телефон

  5. Email

  6. Дата

  7. Текст описание

  8. Статус

  9. Действия

Результатом станет довольно обширная таблица, которая имеет все нужные нам виды колонок. Признаться даже список сверху вызывает у меня вопросы справа тратится слишком много места.

  1. С фиксированной шириной и переносом строк

  2. С шириной по контенту

Анонс следующей статьи О списках в интерфейсах и как их применять по феншую.

Десктоп

В окне браузера наша таблица будет довольно комфортно себя чувствовать. Мы можем контролировать отображение контента так, как нам нужно. Даже если таблица перестанет помещаться по ширине то мы всегда можем либо скрыть пару колонок с самым низким приоритетом, либо в крайнем случае сделать скроллинг по горизонтали.

Варианты адаптации

Проблема наша талица по ширине не влезает в телефон.

Ошибочные решения

  1. Уменьшать шрифт

  2. Убирать колонки

  3. Делать растровую картинку с таблицей и вставлять ее в макет

Возможные верные решения по убыванию

  1. Каждую строку таблицы делать блоком

  2. Горизонтальный скроллинг

Первый вариант адаптации - сделать каждую строку таблицу отдельным блоком, и вывести ее на экране телефона.

Проблема с данным методом: Удлиняется вертикальный скроллинг, данные повторяются. (частично решается добавлением фильтров для поиска)

Возьмём за пример таблицу, которая отображена сверху. В таком виде она не поместится по ширине в мобильный экран. Для решения этой проблемы мы будем каждую строку таблицы преобразовывать в блок.

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

Второй вариант - горизонтальный скролинг таблицы.

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

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

Вывод

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

Если вы заметили ошибки, или вам есть что дополнить - дайте мне знать, я обязательно это сделаю.Спасибо за внимание!

Подробнее..

Перевод 10 постулатов по улучшению таблиц

06.04.2021 20:05:58 | Автор: admin

Короткое резюме 10 постулатов по улучшению таблиц, опубликованных в Journal of Benefit Cost Analysis экономистом Jon Schwabish.

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

Итак, перейдем к правилам.

1. Отделяйте заголовки от тела таблицы

Делайте заголовки своей таблицы понятными. Старайтесь использовать жирный шрифт или линии, чтобы отделить заголовки от тела таблицы - чисел и текста.

Без заголовковБез заголовковС заголовкамиС заголовками

2. Используйте ненавязчивые разделители вместо толстой сетки

Облегчите или вообще удалите большинство тяжеловесных краев и разделителей в Вашей таблице. Редко бывает, что границы необходимы для всех ячеек.

Без разделителейБез разделителейВыделение цветомВыделение цветом

3. Выравнивайте числа и заголовки по правому краю

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

Выравнивание чисел и заголовков по левому краюВыравнивание чисел и заголовков по левому краю

4. Выравнивайте текст и заголовки по левому краю

Как только мы выровняли числа по правому краю, нужно выровнять текст по левому краю. Обратите внимание, насколько названия стран в самом правом столбце лучше воспринимаются по сравнению с первыми двумя столбцами.

Различные варианты выравнивания текстаРазличные варианты выравнивания текста

5. Выбирайте адекватный уровень точности

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

Различные варианты округленийРазличные варианты округлений

6. Направляйте читателя с помощью расстояний между строками и столбцами

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

Расстояние между столбцамиРасстояние между столбцамиРасстояние между строкамиРасстояние между строками

7. Избегайте повтора единиц измерения

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

Излишек единиц измеренияИзлишек единиц измеренияПравильное оформление единиц измеренияПравильное оформление единиц измерения

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

8. Выделяйте выбросы

Некоторые читатели будут подробно изучать все значения в таблице, потому что им нужна специфическая информация, но большинство людей интересуют только самые значимые значения.

Выделение выбросов цветом текстаВыделение выбросов цветом текстаВыделение выбросов заливкойВыделение выбросов заливкой

9. Группируйте похожие данные и отделяйте их пробелами

Уменьшайте количество повторов за счет объединения похожих данных или подписей. Аналогично повторам единиц измерения, мы можем сделать таблицу чище за счет группировки данных по смыслу.

Без группировки по регионамБез группировки по регионамС группировкой по регионамС группировкой по регионам

10. Добавляйте визуализацию при необходимости

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

Голая таблицаГолая таблицаОтформатированная таблицаОтформатированная таблица

Эпилог

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

Разница в наглядности очевидна. Проведите ревизию своих ежедневных и финансовых отчетов - а у Вас все правила соблюдаются? ;)

Подробнее..

Экстракция данных из SAP HCM в non-SAP хранилища данных

23.09.2020 16:18:12 | Автор: admin
Как известно, компания SAP предлагает полный спектр программного обеспечения, как для ведения транзакционных данных, так и для обработки этих данных в системах анализа и отчетности. В частности платформа SAP Business Warehouse (SAP BW) представляет собой инструментарий для хранения и анализа данных, обладающий широкими техническими возможностями. При всех своих объективных преимуществах система SAP BW обладает одним значительным недостатком. Это высокая стоимость хранения и обработки данных, особенно заметная при использовании облачной SAP BW on Hana.

А что если в качестве хранилища начать использовать какой-нибудь non-SAP и желательно OpenSource продукт? Мы в Х5 Retail Group остановили свой выбор на GreenPlum. Это конечно решает вопрос стоимости, но при этом сразу появляются вопросы, которые при использовании SAP BW решались практически по умолчанию.



В частности, каким образом забирать данные из систем источников, которые в большинстве своем являются решениями SAP?

HR-метрики стал первым проектом, в котором необходимо было решить эту проблему. Нашей целью было создание хранилища HR-данных и построение аналитической отчетности по направлению работы с сотрудниками. При этом основным источником данных является транзакционная система SAP HCM, в которой ведутся все кадровые, организационные и зарплатные мероприятия.

Экстракция данных


В SAP BW для SAP-систем существуют стандартные экстракторы данных. Эти экстракторы могут автоматически собирать необходимые данные, отслеживать их целостность, определять дельты изменений. Вот, например, стандартный источник данных по атрибутам сотрудника 0EMPLOYEE_ATTR:



Результат экстракции данных из него по одному сотруднику:



При необходимости такой экстрактор может быть модифицирован под собственные требования или может быть создан свой собственный экстрактор.

Первой возникла идея о возможности их переиспользования. К сожалению, это оказалось неосуществимой задачей. Большая часть логики реализована на стороне SAP BW, и безболезненно отделить экстрактор на источнике от SAP BW не получилось.

Стало очевидно, что потребуется разработка собственного механизма извлечения данных из SAP систем.

Структура хранения данных в SAP HCM


Для понимания требований к такому механизму, первоначально нужно определить какие именно данные нам потребуются.

Большинство данных в SAP HCM хранится в плоских SQL таблицах. На основании этих данных приложения SAP визуализируют пользователю оргструктуры, сотрудников и прочую HR информацию. Например, вот так в SAP HCM выглядит оргструктура:



Физически такое дерево хранится в двух таблицах в hrp1000 объекты и в hrp1001 связи между этими объектами.

Объекты Департамент 1 и Управление 1:



Связь между объектами:



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

Отображение руководителя в SAP:



Хранение в таблице БД:



Данные по сотрудникам хранятся в таблицах pa*. Например, данные о кадровых мероприятиях по сотруднику хранятся в таблице pa0000



Мы приняли решение, что GreenPlum будет забирать сырые данные, т.е. просто копировать их из SAP таблиц. И уже непосредственно в GreenPlum они будут обрабатываться и преобразовываться в физические объекты (например, Отдел или Сотрудник) и метрики (например, среднесписочная численность).

Было определено порядка 70 таблиц, данные из которых необходимо передавать в GreenPlum. После чего мы приступили к проработке способа передачи этих данных.

SAP предлагает достаточно большое количество механизмов интеграции. Но самый простой способ прямой доступ к базе данных запрещен из-за лицензионных ограничений. Таким образом, все интеграционные потоки должны быть реализованы на уровне сервера приложений.
Следующей проблемой было отсутствие данных об удаленных записях в БД SAP. При удалении строки в БД, она удаляется физически. Т.е. формирование дельты изменений по времени изменения не представлялось возможным.

Конечно, в SAP HCM есть механизмы фиксация изменений данных. Например, для последующей передачи в системы получатели существуют указатели изменений(change pointer), которые фиксируют любые изменения и на основании которых формируются Idoc (объект для передачи во внешние системы).

Пример IDoc изменения инфотипа 0302 у сотрудника с табельным номером 1251445:



Или ведение логов изменений данных в таблице DBTABLOG.

Пример лога удаления записи с ключом QK53216375 из таблицы hrp1000:



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

Следующей серьезной проблемой были кластерные таблицы. Данные оценки времени и расчета зарплаты в RDBMS версии SAP HCM хранятся в виде набора логических таблиц по каждому сотруднику за каждый расчет. Эти логические таблицы в виде двоичных данных хранятся в таблице pcl2.

Кластер расчета заработной платы:



Данные из кластерных таблиц невозможно считать SQL командой, а требуется использование макрокоманд SAP HCM или специальных функциональных модулей. Соответственно, скорость считывания таких таблиц будет достаточно низка. С другой стороны, в таких кластерах хранятся данные, которые необходимы только раз в месяц итоговый расчет заработной платы и оценка времени. Так что скорость в этом случае не столь критична.

Оценивая варианты с формированием дельты изменения данных, решили так же рассмотреть вариант с полной выгрузкой. Вариант каждый день передавать гигабайты неизменных данных между системами не может выглядеть красиво. Однако он имеет и ряд преимуществ нет необходимости как реализации дельты на стороне источника, так и реализация встраивания этой дельты на стороне приемника. Соответственно, уменьшается стоимость и сроки реализации, и повышается надежность интеграции. При этом было определено, что практически все изменения в SAP HR происходят в горизонте трех месяцев до текущей даты. Таким образом, было принято решение остановиться на ежедневной полной выгрузке данных из SAP HR за N месяцев до текущей даты и на ежемесячной полной выгрузке. Параметр N зависит от конкретной таблицы
и колеблется от 1 до 15.

Для экстракции данных была предложена следующая схема:



Внешняя система формирует запрос и отправляет его в SAP HCM, там этот запрос проверяется на полноту данных и полномочия на доступ к таблицам. В случае успешной проверки, в SAP HCM отрабатывает программа, собирающая необходимые данные и передающая их в интеграционное решение Fuse. Fuse определяет необходимый топик в Kafka и передает данные туда. Далее данные из Kafka передаются в Stage Area GP.

Нас в данной цепочке интересует вопрос извлечения данных из SAP HCM. Остановимся на нем подробнее.

Схема взаимодействия SAP HCM-FUSE.



Внешняя система определяет время последнего успешного запроса в SAP.
Процесс может быть запущен по таймеру или иному событию, в том числе может быть установлен таймаут ожидания ответа с данными от SAP и инициация повторного запроса. После чего формирует запрос дельты и отправляет его в SAP.

Данные запроса передаются в body в формате json.
Метод http: POST.
Пример запроса:



Сервис SAP выполняет контроль запроса на полноту, соответствие текущей структуре SAP, наличие разрешения доступа к запрошенной таблице.

В случае ошибок сервис возвращает ответ с соответствующим кодом и описанием. В случае успешного контроля он создает фоновый процесс для формирования выборки, генерирует и синхронно возвращает уникальный id сессии.

Внешняя система в случае ошибки регистрирует ее в журнале. В случае успешного ответа передает id сессии и имя таблицы по которой был сделан запрос.

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

Фоновое задание SAP формирует курсор по заданным параметрам и пакет данных заданного размера. Размер пакета максимальное количество записей, которые процесс читает из БД. По умолчанию принимается равным 2000. Если в выборке БД больше записей, чем используемый размер пакета после передачи первого пакета формируется следующий блок с соответствующим offset и инкрементированным номером пакета. Номера инкрементируются на 1 и отправляются строго последовательно.

Далее SAP передает пакет на вход web-сервису внешней системы. А она система выполняет контроли входящего пакета. В системе должна быть зарегистрирована сессия с полученным id и она должна находиться в открытом статусе. Если номер пакета > 1 в системе должно быть зарегистрировано успешное получение предыдущего пакета (package_id-1).

В случае успешного контроля внешняя система парсит и сохраняет данные таблицы.

Дополнительно, если в пакете присутствует флаг final и сериализация прошла успешно, происходит уведомление модуля интеграции об успешном завершении обработки сессии и модуль обновляет статус сессии.

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

Так же и в обратном случае, когда внешняя система возвращает ошибку, она логируется и прекращается передача пакетов.

Для запроса данных на стороне SAP HСM был реализован интеграционный сервис. Сервис реализован на фреймворке ICF (SAP Internet Communication Framework help.sap.com/viewer/6da7259a6c4b1014b7d5e759cc76fd22/7.01.22/en-US/488d6e0ea6ed72d5e10000000a42189c.html). Он позволяет производить запрос данных из системы SAP HCM по определенным таблицам. При формировании запроса данных есть возможность задавать перечень конкретных полей и параметры фильтрации с целью получения необходимых данных. При этом реализация сервиса не предполагает какой-либо бизнес-логики. Алгоритмы расчёта дельты, параметров запроса, контроля целостности, и пр. также реализуются на стороне внешней системы.

Данный механизм позволяет собирать и передавать все необходимые данные за несколько часов. Такая скорость находится на грани приемлемой, поэтому это решение рассматривается нами как временное, позволившее закрыть потребность в инструменте экстракции на проекте.
В целевой картине для решения задачи экстракции данных прорабатываются варианты использования CDC систем типа Oracle Golden Gate или ETL инструментов типа SAP DS.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru