В данной статье мы рассмотрим основной механизм локализации интерфейса приложений Splunk (в т.ч. стандартных элементов приложения Search) — gettext internationalization and localization (i18n).
Возможности для перевода:
- интерфейс Splunk;
- дашборды (заголовки панелей и полей ввода);
- выбор статических ресурсов (картинки, CSS и т.д.) на основе текущей локали, например: logo-ru_RU.gif или logo-en_GB.gif.
Для локализации необходимо проделать несколько шагов:
- Создать новую локаль в Splunk (при необходимости).
- Сформировать файлы-словари перевода.
- При помощи JavaScript подгрузить переведённые поля.
Также мы дополнительно переведём результаты поиска на примере уязвимостей OpenVAS, что хоть и не относится к самой системе перевода, однако хорошо дополняет общую картину.
Все, кого интересуют актуальные вопросы по Splunk, мы приглашаем присоединиться к нашему каналу в Telegram (@SplunkRU).
Итак, перейдём к практике.
Создание новой локали в Splunk
Напомню, по умолчанию в Splunk используется en-US локаль, в этом можно убедиться в строке браузера: MYSERVER:8000/en-US/app/search (hint: если вручную поменять en-US на en-GB, то время и дата будут отображаться в более привычном виде).
Для начала сделаем новую локаль для России. Копируем существующую локаль:
sudo cp /opt/splunk/lib/python2.7/site-packages/splunk/appserver/mrsparkle/locale/en_GB /opt/splunk/lib/python2.7/site-packages/splunk/appserver/mrsparkle/locale/ru_RU -ru
Перезапускаем Splunk:
sudo /opt/splunk/bin/splunk restart
И проверяем:
MYSERVER:8000/ru-RU/app/launcher/home
Создание нового приложения
Создаём новое приложение с названием testapp (Name\Folder name: testapp):
MYSERVER:8000/ru-RU/manager/launcher/apps/local/_new?action=edit&ns=launcher
Создание файлов перевода
Теперь перейдём непосредственно к формированию файлов перевода.
Мы можем перевести существующее приложение, сделаем это для только что созданного:
sudo /opt/splunk/bin/splunk extract i18n -app testapp
После этого у нас появляется файл
/opt/splunk/etc/apps/testapp/locale/messages.pot
, который содержит в себе все поля, которые используются в приложении, т.к. у нас приложение новое, там будет только один элемент — ссылка на имя приложения.Регистрируемся на сайте poeditor.com.
Добавляем русский язык (Add Language):
Создаём новый проект и импортируем (import terms) наш файл
/opt/splunk/etc/apps/testapp/locale/messages.pot
:Добавим несколько полей при помощи кнопки Add Term:
Справа от каждого из них есть кнопка добавления перевода:
Воспользуемся и переведём наши поля:
Переходим в проект, выбираем русский язык и экспортируем в ДВА формата: .po и .mo:
Создаём папку с соответствующей локалью и копируем туда наши файлы, переименовав их в messages.po и messages.mo соответственно:
sudo mkdir /opt/splunk/etc/apps/testapp/locale/ru_RU/LC_MESSAGES
suco cp messages.* /opt/splunk/etc/apps/testapp/locale/ru_RU/LC_MESSAGES
Перезагружаем Splunk:
sudo /opt/splunk/bin/splunk restart.
Заходим в наше приложение и убеждаемся, что отображаемое имя приложения переведено:
localhost:8000/ru-RU/app/testapp/search
JS скрипт
Следующим шагом будет написание JS-скрипта, который будет заменять токены на соответствующие локали поля. Сразу заложим возможность перевода на несколько языков: русский и немецкий.
Создаём папку, откуда будет загружаться скрипт:
sudo mkdir /opt/splunk/etc/apps/testapp/appserver
sudo mkdir /opt/splunk/etc/apps/testapp/appserver/static
Пишем сам скрипт в /opt/splunk/etc/apps/testapp/appserver/static/dashTranslate.js:
require([
'jquery',
'underscore',
'splunk.i18n',
'splunkjs/mvc',
'splunkjs/mvc/simplexml/ready!'
], function ($, _, i18n, mvc) {
var defaultTokens = mvc.Components.get("default");
var envTokenModel = mvc.Components.getInstance("env");
if (envTokenModel.get("locale") != "ru-RU" || envTokenModel.get("locale") != "de-DE") {
defaultTokens.set("form.t_locale", "ru-RU"); // если текущая локаль не из поддерживаемого списка, будем переводить на стандартный язык (русский), это сделано только для перевода результатов поиска (забегая вперёд, скажу, что это будет сделано при помощи lookup полей, которые будут иметь суффикс в виде локали, в противном случае можно напрямую использовать $env::locale$ токен)
} else {
defaultTokens.set("form.t_locale", envTokenModel.get("locale")); // set locale token
}
// если перевода для текущей локали нет, то будем использовать стандартное значение
if (i18n._("openvasTitle") == "openvasTitle") {
defaultTokens.set("form.t_openvasTitle", "OpenVAS Events"); // default value
} else {
defaultTokens.set("form.t_openvasTitle", i18n._("openvasTitle")); // translated value
}
//перевод названий столбцов. значение в виде имени переменной нас вполне устроит как стандартное значение, поэтому не будем задавать его отдельно
defaultTokens.set("form.t_signature", i18n._("signature"));
defaultTokens.set("form.t_description", i18n._("description"));
defaultTokens.set("form.t_count", i18n._("count"));
});
Делаем дашборд
Создаём новый дашборд и добавляем в него (исходники дашборда для ленивых в конце):
- Input text – Name: count, Token: t_count
- Input text – Name: openvasTitle, Token: t_openvasTitle
- Input text – Name: signature, Token: t_signature
- Input text – Name: description, Token: t_description
Без инпутов не будут работать form.* токены, что не позволит передавать полноценное состояние дашборда в URL. Чтобы они не мозолили глаза, добавляем «depends="$nothing$"» в каждый из них.
Panel, statistic table – Title: $t_openvasTitle$
index=openvas
| stats count by "NVT Name" Summary
| rename "NVT Name" AS $form.t_signature$ Summary AS $form.t_description$ count AS $form.t_count$
Panel, pie chart – Title: $t_openvasTitle$
index=openvas
| stats count by "NVT Name"
| rename "NVT Name" AS $form.t_signature$ count AS $form.t_count$
Добавляем наш скрипт на дашборд – меняем первую строку на:
<form script="dashTranslate.js">
Тут есть небольшая особенность – Splunk переводит известные ему поля (count, signature в нашем случае) в текстовых полях инпутов или заголовках.
Режим редактирования:
Обычный режим:
Однако надо держать в голове то, что при повторном редактировании панели выводиться будет перевод, и если случайно, например, поставить пробел, то в исходниках вместо переменной (openvasTitle или description в нашем примере) будет сохранён текст («События OpenVAS_» или «Описание_» в нашем примере). Плюс при редактировании не ясно, что это, на самом деле, перевод, поэтому лучше использовать токены ($form.t_openvasTitle$ и $form.t_description$ в нашем примере).
В режиме повторного редактирования: верхний заголовок – токен $form.t_openvasTitle$, нижний – текст openvasTitle.
Как итог, у нас получился дашборд
Ограничения
Есть два ограничения, о которых стоит знать:
- Нельзя переводить заголовки дашбордов.
- Нельзя задавать начальные значения токенов в init\set, т.к. выполняются после скрипта-перевода (можно вынести JS на более позднюю загрузку, но тогда придётся конвертировать в HTML).
Перевод результатов поиска
Для вывода конечным пользователям текстовых данных предлагается использовать lookup с переводом на любое количество языков. В этом примере мы как раз используем глобальный токен Splunk $env::local$. Идентификатором уязвимости является хеш, по которому мы подключаем перевод. Также отмечу, что если перевод будет пустой, то будет использовано начальное значение. Добавим новый static table в наш дашборд:
index="openvas"
| eval signature=replace(signature, "[\n\r]", " ")
| eval description=replace(description, "[\n\r]", " ")
| eval hash=md5(signature.description)
| stats count by signature description hash
| lookup OpenVAS_translate hash AS hash OUTPUT signature_$env:locale$ AS signature_$env:locale$ description_$env:locale$ AS description_$env:locale$
| eval signature=coalesce('signature_$env:locale$', signature)
| eval description=coalesce('description_$env:locale$', description)
| fields - signature_$env:locale$ description_$env:locale$ count hash
| rename signature AS $form.t_signature$ description AS $form.t_description$
Файл OpenVAS_translate.csv выглядит следующим образом (для работы с лукапами советую использовать приложение Lookup Editor):
Таким образом, при наличии перевода он будет произведён исходя из текущей локали.
ru-RU:
de-DE:
Перевод стандартного интерфейса Splunk
Перевод интерфейса Splunk осуществляется аналогичным образом -необходимо перевести в poeditor файл $SPLUNK_HOME/lib/python2.7/site-packages/splunk/appserver/mrsparkle/locale/messages.pot и скопировать два получившихся файла (messages.mo и messages.po) в папку:$SPLUNK_HOME/lib/python2.7/site-packages/splunk/appserver/mrsparkle/locale/ru_RU/LC_MESSAGES/
Список используемых источников:
docs.splunk.com/Documentation/Splunk/6.4.1/AdvancedDev/TranslateSplunk
docs.splunk.com/Documentation/Splunk/7.2.6/Viz/tokens
splunkonbigdata.com/2018/11/01/creating-a-splunk-locale
answers.splunk.com
by: Дмитрий Головня tg:@GolovnyaD
Итоговый код дашборда:
<form script="d.js">
<label>i18n description</label>
<description>description</description>
<fieldset submitButton="false" autoRun="false">
<input type="text" token="t_count" depends="$nothing$">
<label>count</label>
</input>
<input type="text" token="t_openvasTitle" depends="$nothing$">
<label>openvasTitle</label>
</input>
<input type="text" token="t_signature" depends="$nothing$">
<label>signature</label>
</input>
<input type="text" token="t_description" depends="$nothing$">
<label>description</label>
</input>
</fieldset>
<row>
<panel>
<title>$form.t_openvasTitle$</title>
<table>
<title>openvasTitle</title>
<search>
<query>index=openvas
| stats count by "NVT Name" Summary
| rename "NVT Name" AS $form.t_signature$ Summary AS $form.t_description$ count AS $form.t_count$</query>
<earliest>0</earliest>
<latest></latest>
</search>
<option name="drilldown">none</option>
<option name="refresh.display">progressbar</option>
</table>
</panel>
</row>
<row>
<panel>
<chart>
<title>$form.t_openvasTitle$</title>
<search>
<query>index=openvas
| stats count by "NVT Name"
| rename "NVT Name" AS $form.t_signature$ count AS $form.t_count$</query>
<earliest>0</earliest>
<latest></latest>
</search>
<option name="charting.chart">pie</option>
<option name="charting.drilldown">none</option>
<option name="refresh.display">progressbar</option>
</chart>
</panel>
</row>
<row>
<panel>
<table>
<search>
<query>index="openvas"
| eval signature=replace(signature, "[\n\r]", " ")
| eval description=replace(description, "[\n\r]", " ")
| eval hash=md5(signature.description)
| stats count by signature description hash
| lookup OpenVAS_translate hash AS hash OUTPUT signature_$env:locale$ AS signature_$env:locale$ description_$env:locale$ AS description_$env:locale$
| eval signature=coalesce('signature_$env:locale$', signature)
| eval description=coalesce('description_$env:locale$', description)
| fields - signature_$env:locale$ description_$env:locale$ count hash
| rename signature AS $form.t_signature$ description AS $form.t_description$</query>
<earliest>0</earliest>
<latest></latest>
</search>
<option name="count">10</option>
<option name="drilldown">none</option>
<option name="refresh.display">progressbar</option>
</table>
</panel>
</row>
</form>