Рекомендации

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

Конфигурация

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

XML

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

  • корневой элемент должен называться doc
  • при обработке XML избегайте использования XPath начинающихся с "//" — вы можете найти больше узлов, чем вам было нужно.

Общие рекомендации

  • Избегать ситуаций, ставящих пользователя в тупик; при возникновении ошибки он всегда должен точно знать, что делать, чтобы устранить ошибку. При возникновении ситуации, требующей дополнительных настроек, он всегда должен знать, где, как и какие настройки он должен произвести. В идеале, автоматом перенаправлять его на заполнение соответствующих настроек и потом автоматом возвращать обратно. При возникновении вопросов о поведении системы он должен быстро найти ответ в документации (по возможности в хинте)
  • По возможности пытаться решать проблемы автоматически, не напрягая при этом пользователя. Например, если обнаружено несоответствие каких-то данных, и его можно однозначно устранить (rotate уже существует), то устраняем. Если отсутствует нужная нам дира или файл и мы знаем с какими правами они должны быть и знаем каким должно быть дефолтное содержимое файла, то автоматом создаем их. И т.д.
  • В случае появления потенциально неправильных действий (warning) пишем сообщение не только в лог, но и отображаем их пользователю, посредством баннеров.
  • Всегда разрешать переход с вышестоящих уровней даже на отключенные учетные записи.
  • Все разделы в dashboard'е должны быть выстроены в ровные колонки и выглядеть органично.
  • Весь код должен быть многопоточным
    1. Временные данные запроса должны храниться в XML сессии
  • Изолировать функции панели
    1. Отдельный функционал должен быть оформлен в отдельный файл, а не размазан по всему коду
    2. Использовать неименованные namespace для сокрытия структуры данных модуля
  • Функции, используемые для проверки значения (check в метаданных), должны пропускать пустое значение параметра
    1. Пустое значение можно запретить, указав атрибут required
    2. Значение может оказаться пустым в результате внутренних преобразований. В этом случае оно должно быть признано верным
  • Все групповые операции (удаление, включение, выключение и т.д.) вызываются для каждого отдельного элемента, если операцию выполнить невозможно, обязательно генерируем эксепшен. На более высоком уровне он будет перехвачен, и продолжится выполнение операции для других элементов. В конечном итоге, в случае ошибок пользователь увидит список элементов, над которыми не удалось произвести операцию.
  • Игнорировать попытки удаления несуществующих объектов (не генерировать исключения)
  • Избегать дублирования кода
    1. Избегать случаев, когда несколько функций панели выполняют одни и те же действия. Такие действия следует объединять в отдельные функции и вызывать их посредством InternalCall, чтобы обеспечить возможность централизованно менять поведение. Есть возможность защитить такие функции от непосредственного вызова извне.
  • Все функции создающие какие либо сущности, должны возвращать их идентификатор (по которому можно обратится к нему) в теге elid. если создаваемых объектов несколько, возвращается несколько тегов elid

Рекомендации по работе с формами

Подробную информацию о формате xml документа описывающего форму вы можете найти в статье Описание форм

  • Функции редактирования/создания новых элементов, по возможности, должны возвращать новый идентификатор элемента в узле id и новое имя, если оно отличается от идентификатора, в узле name.
  • Параметр confirm (подтверждение пароля) в коде нигде не обрабатывается, следовательно, при вызовах по API его можно не указывать.
  • Если поле на форме подразумевает неограниченное значение, то оно должно иметь placeholder с сообщением "не ограничено" и обрабатывать пустое значение, выставляя большое значение самостоятельно.
  • Если проверка доступа к элементу реализована в Get, её можно не делать в Set (Get всегда вызывается перед обработкой Set)
  • Все проверки вводимых параметров должны быть объявлены в XML (@check и @checkargs). Значения полей с типами slider, checkbox и select проверяются автоматически, для чего во время set запросов выполняется соответствующий get запрос.
    • slider — целое число, лежащее в промежутке [min,max]. Если не указано — считается min.
    • checkbox — допустимые значения: on, off. Если не указано — считается off.
    • select — одно из значений в соответствующем slist. Если не указано — берется первое значение из списка slist после его сортировки
  • Значения, возвращаемые в get запросах, должны удовлетворять условиям, заданным в XML.
    • Если разработчик в get запросе не вернул значения для полей с типами slider, checkbox или select, значение будет подставлено автоматически (см. предыдущий пункт).
    • Значения параметров, переданные в get запрос, заменят собой значения одноименных полей формы. Таким образом можно показать пользователю форму с заранее заполнеными полями.
  • Значения для полей ввода в новых записях (запрос с пустым elid), заполняемые через setvalues, должны формироваться автоматически при Get запросе, если они небыли переданы в качестве параметров. Например, если вы заполняете некое поле Email после изменения поля Name, код может выглядеть следующим образом:

    if (ses.Has("Name") && !ses.Has("Email"))
      ses.NewNode("Email", ses.Param("Name") + "@host.com");
  • Поля для ввода пароля при интеграции панели с внешними системами (другие панели: ip/dnsmanager, сервера баз данных и т.п.) должны иметь тип password. При этом, во время отображения формы (get запрос) должно отдаваться текущее значение запомненное в панели.

Рекомендации по оформлению списков

Подробную информацию о формате xml документа описывающего список вы можете найти в статье Описание списков

  • Заголовок списка должен совпадать с именем списка в главном меню (если не использовалось сокращение)
  • Использование подписи с prop'а только по согласованию с руководителем разработки.
  • В статистике все цифры должны подытоживаться. Все индикаторы должны отображать обе цифры аля "3/40", а не "4/". По колонке с prop'ами итог должен подводиться по тому, чего меньше (если речь идёт о нескольких возможных состояниях объекта, аля "вкл/выкл").
  • Не должно быть колонок, в которой нет ни одного значения.
  • Если есть prop'ы в списке, то комментарии, примечания и т.п. должны быть хинтами к соответствующему изображению. Если prop'ов нет, то в отдельной колонке в виде текста.
  • Если среди prop/xprop есть лампочка, она должна стоять первой
  • Не должно быть больше 8 групп кнопок у списка.
  • При описании колонки типа <col type="msg" ... /> содержащей состояние, обязательно указывается цвет текста.
    • red — ошибка
    • yellow — предупреждение
    • green — все хорошо

Рекомендации по расположению кнопок на toolbar

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

Кнопки везде располагаются в следующем порядке (разумеется, если у вас чего-то нет, пропускаем):

  • Создать
  • Изменить. При наличии этой кнопки default="yes" должен быть на ней. (Пользователи, которым такое поведение не удобно, могут изменить его в настройках таблицы)
  • Удалить
  • --разделитель--
  • Включить
  • Выключить
  • --разделитель--
  • любые другие кнопки не описанные ниже, по необходимости допускается ставить разделители между смысловыми группами кнопок
  • --разделитель--
  • кнопки отчетов
  • --разделитель--
  • кнопки фильтров
  • --разделитель--
  • кнопки переходов (на другой уровень, в другие панели и т.д.)

Рекомендации по работе с базами данных

  • Не использовать Зарезервированные_слова_СУБД в качестве имен таблиц и полей
  • Ключевые слова языка SQL должны быть в верхнем регистре (ну проще так запросы читать)
  • Разделение слов внутри имени символом '_'
  • Таблицы
    • Имена функций выводящих содержимое списков должны соответствовать именам таблиц (если это возможно)
    • Имена таблиц и полей маленькими буквами
    • Имена таблиц писать в единственном числе (Например: server, а не servers). Иначе внешние ссылки будут некрасиво выглядеть (см. выше)
    • Таблица со списком пользователей — users (user — зарезервированное слово в некоторых СУБД)
    • many2many Таблицы именуем через двойку в единственном числе слева и справа от двойки
  • Поля
    • Префиксы имен полей (обозначающие тип, размер и прочее) не использовать.
    • Тип boolean — StringField длины 3. Значения on/off (для совместимости с checkbox). Class_mgr_db::BoolField
    • Не использовать autoincrement поля (из-за проблем с репликацией данных MySQL). Используем Class_mgr_db::AutoIncrement
    • Поле, ссылающееся на другую таблицу, должно иметь такое же имя, как и таблица, на которую оно ссылается (если ссылок много, то имя таблицы следует использовать в качестве префикса)
    • Поле, содержащее уникальный идентификатор записи, должно иметь имя id Class_mgr_db::AutoIncrement
    • Поле, содержащее уникальное имя записи, должно иметь имя name
    • Поле, содержащее состояние объекта типа: включено/выключено, должно иметь имя active с возможными значениями on/off. Class_mgr_db::BoolField
    • Поле, содержащее уровень доступа, должно называться level быть целочисленным и принимать значения согласно Namespace_mgr_access
    • Поля, содержащие пароли, должны иметь тип Class_mgr_db::CryptedField. Это позволяет увеличить защиту данных (в частности от SQL инъекций)

SQLite

  • Базы храним в каталоге etc
  • Имя файла базы данных должно иметь расширение .db

Рекомендации по расположению разделов меню

Разделы меню во всех панелях располагаются в следующем порядке:

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

Рекомендации по содержанию текстовых сообщений

Будет пополняться прецедентно.

  • Придерживаться единого стиля при написании сообщений
    1. Хинты писать с большой буквы и без точки в конце
    2. Для подписей к кнопкам и имен полей стараться использовать сообщения из секции common
  • Id — писать так.
  • Краткие подписи под кнопками. В идеале, подписи должны вмещаться в ширину кнопки. Максимум в 1.5 ширины кнопки. В особо трудных случаях допускается сокращать слова точкой. Развёрнутая информация о предназначении кнопки должна быть в подсказке(hint) к ней.
  • IP и другие адреса следует писать так: "IP-адрес" "MAC-адрес". Аббревиатура большими буквами через дефис.
  • Наименования модулей. Текст должен быть такой длины, чтобы вмещался в одну строчку в главном (боковом) меню. Максимальная длина названия также зависит от ширины тулбара, т.е. название и тулбар должны вмещаться на экране шириной 1280px
  • Хинт должен раскрывать суть, а не констатировать то, что и так указано в подписи поля. Например, если на форме есть чекбокс "Включить Spamassassin для домена" — то в хинте должно быть примерно следующее содержание "Разрешать ли фильтрацию спама с помощью Spamassassin для данного домена". Человек, которому понятие не знакомо, может сразу вникнуть о чём идёт речь. Если сообщение очевидно и пояснения не требуются, то нужно написать, где и для чего используется данное поле или перефразировать его название другими словами.
  • Если все что хочется написать в хинте не вмещается в 5 строк, то пишем техническую статью (даже если в ней всего будет пара абзацев) и добавляем на нее ссылку в "Полезные ссылки"

Рекомендации по обработке лимитов на различные ресурсы

  • В качестве безлимита использовать пустое значение. Ноль — это ноль (запрет на использование ресурса), а не анлим. Для того, чтобы клиенту было понятно, что пусто — это безлимит, используем placeholder. Пример использования:

     <field name="domainlimit">
         <input type="text" name="domainlimit" check="int" unlimit="" checkargs="0,"/>
     </field>
     ...
     <msg name="placeholder_domainlimit">не ограничено</msg>
  • Если цифра 0 в лимите бессмысленна, делайте проверку в валидатор, на минимальное значение 1, поможет пользователям избежать недопонимания.
  • Уменьшение лимитов. Если у пользователя использовано ресурсов больше, чем вы хотите задать (уменьшить) лимит. Значит отказываем в этой операции, генерим ошибку.
  • Оверселинг по умолчанию у нас разрешен. Т.е. реселлер может в сумме раздать своим пользователям больше лимитов, чем его собственный лимит. При этом при выделении какого либо ресурса пользователю нужно проверять оба лимита, лимит пользователя и лимит реселлера (сложив использование ресурса всеми его пользователями)
  • Если лимит не задан явно — доступ к ресурсу должен быть закрыт

Новые возможности COREmanager, которые нужно использовать в панелях

  • Notify bar (уведомления теперь отображаются в панели оповещений в левом верхнем углу).
  • Навигация из списков (nestedlist). Возможность связывания списков. Можно задать в какой список и с каким фильтром следует перейти при нажатии на отдельные значения таблицы.
  • Перегрузка веб-интерфейса при изменениях в меню.
  • Кнопки New в дашборде
  • Подсказки для кнопок при первой авторизации пользователя. Нужны для того, чтобы сориентировать пользователя как пользоваться панелью.

Загрузка системы

  • создание обработчиков базовых функций
  • загрузка дополнительной библиотеки <имя панели>.so / <имя панели>.dll Во время загрузки библиотека должна зарегистрировать в системе все необходимые ей компоненты . Кроме того, она может подгружать дополнительные библиотеки. Если загрузка какой-либо библиотеки завершается с ошибкой, все зарегистрированные ей компоненты автоматически удаляются из системы.

Обработка запросов

На каждом этапе обработки запроса вы можете добавлять собственные обработчики.

  1. получение запроса, чтение и разбор параметров запроса Обрабатываются данные GET и POST, формируются внутреннее представление данных для удобного и быстрого доступа
  2. аутентификация пользователя (попытка определить имя и уровень доступа пользователя отправившего запрос)
  3. поиск функции по имени (имя функции берется из параметра func, если параметр отсутствует, предполагается, что он равен desktop )
  4. авторизация пользователя
  5. создание Xml для сессии (lang, func, binary, host)
  6. вызов глобальных обработчиков событий (тех, что висят на действии "*" ) before="yes"
  7. вызов обработчиков событий для выбранной функции before="yes"
  8. вызов обработчика функции
  9. вызов обработчиков событий для выбранной функции after="yes"
  10. вызов глобальных обработчиков событий (тех, что висят на действии "*" ) after="yes"
  11. завершение транзакций (запись файлов, удаление временных данных, вызов commit для баз данных)
  12. формирование ответа в требуемом формате (формат задается через параметр запроса out)

Аутентификация пользователя

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

  1. проверка COOKIE (<имя панели> + "5") COOKIE должен содержать следующие поля разделенные двоеточием: имя темы, аббревиатуру языка, код сессии .
    • загрузка сохраненных сессий из файла var/<имя панели>.ses (если он еще не был загружен) Этот файл записывается каждый раз, когда создается код сессии , на случай непредвиденного завершения работы панели.
    • проверка пользователя (вызов authenticate.<имя метода авторизации> )
  2. авторизация пользователя исходя из данных полученных от клиента (вызов authenticate )
  3. авторизация по IP В данном, случае имя пользователя нам уже известно (задано в конфиге), но все равно необходим вызов authenticate , чтобы определить дополнительные параметры.
  4. авторизация пользователей пришедших с доверенного источника соединений Все аналогично предыдущему пункту, только имя пользователя — это имя локального администратора .

Если ни одна из предпринятых попыток не завершилась успешно, сессии присваивается нулевой уровень доступа.