Архитектура системы
Рекомендации
Небольшой набор правил для обеспечения целостности интерфейса как для конечных пользователей, так и для разработчиков.
Конфигурация
Не надо плодить лишних параметров в .conf файлах. По возможности избегать path для внутренних целей. Если нужно поменять расположение какого-либо из файлов панели, его всегда можно заменить символической ссылкой.
XML
Все функции панели работают с XML документом, который используется для формирования ответа пользователю (этот XML — единственный источник данных для формирования ответа). При работе с XML следует соблюдать следующие правила:
- корневой элемент должен называться doc
- при обработке XML избегайте использования XPath начинающихся с "//" — вы можете найти больше узлов, чем вам было нужно.
Общие рекомендации
- Избегать ситуаций, ставящих пользователя в тупик; при возникновении ошибки он всегда должен точно знать, что делать, чтобы устранить ошибку. При возникновении ситуации, требующей дополнительных настроек, он всегда должен знать, где, как и какие настройки он должен произвести. В идеале, автоматом перенаправлять его на заполнение соответствующих настроек и потом автоматом возвращать обратно. При возникновении вопросов о поведении системы он должен быстро найти ответ в документации (по возможности в хинте)
- По возможности пытаться решать проблемы автоматически, не напрягая при этом пользователя. Например, если обнаружено несоответствие каких-то данных, и его можно однозначно устранить (rotate уже существует), то устраняем. Если отсутствует нужная нам дира или файл и мы знаем с какими правами они должны быть и знаем каким должно быть дефолтное содержимое файла, то автоматом создаем их. И т.д.
- В случае появления потенциально неправильных действий (warning) пишем сообщение не только в лог, но и отображаем их пользователю, посредством баннеров.
- Всегда разрешать переход с вышестоящих уровней даже на отключенные учетные записи.
- Все разделы в dashboard'е должны быть выстроены в ровные колонки и выглядеть органично.
- Весь код должен быть многопоточным
- Временные данные запроса должны храниться в XML сессии.
- Изолировать функции панели
- Отдельный функционал должен быть оформлен в отдельный файл, а не размазан по всему коду.
- Использовать неименованные namespace для сокрытия структуры данных модуля.
- Функции, используемые для проверки значения (check в метаданных), должны пропускать пустое значение параметра
- Пустое значение можно запретить, указав атрибут required.
- Значение может оказаться пустым в результате внутренних преобразований. В этом случае оно должно быть признано верным.
- Все групповые операции (удаление, включение, выключение и т.д.) вызываются для каждого отдельного элемента, если операцию выполнить невозможно, обязательно генерируем эксепшен. На более высоком уровне он будет перехвачен, и продолжится выполнение операции для других элементов. В конечном итоге, в случае ошибок пользователь увидит список элементов, над которыми не удалось произвести операцию.
- Игнорировать попытки удаления несуществующих объектов (не генерировать исключения).
- Избегать дублирования кода
- Избегать случаев, когда несколько функций панели выполняют одни и те же действия. Такие действия следует объединять в отдельные функции и вызывать их посредством 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 групп кнопок у списка.
- При описании колонки типа, содержащей состояние, обязательно указывается цвет текста.
- 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
Рекомендации по расположению разделов меню
Разделы меню во всех панелях располагаются в следующем порядке:
- Разделы продукта
- Состояние системы
- Интеграция
- Настройки
- Справка. При этом сам модуль "Справка" должен занимать самую верхнюю позицию раздела "Справка"
Рекомендации по содержанию текстовых сообщений
Будет пополняться прецедентно.
- Придерживаться единого стиля при написании сообщений
- Хинты писать с большой буквы и без точки в конце
- Для подписей к кнопкам и имен полей стараться использовать сообщения из секции 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 Во время загрузки библиотека должна зарегистрировать в системе все необходимые ей компоненты . Кроме того, она может подгружать дополнительные библиотеки. Если загрузка какой-либо библиотеки завершается с ошибкой, все зарегистрированные ей компоненты автоматически удаляются из системы.
Обработка запросов
На каждом этапе обработки запроса вы можете добавлять собственные обработчики.
- получение запроса, чтение и разбор параметров запроса Обрабатываются данные GET и POST, формируются внутреннее представление данных для удобного и быстрого доступа
- аутентификация пользователя (попытка определить имя и уровень доступа пользователя отправившего запрос)
- поиск функции по имени (имя функции берется из параметра func, если параметр отсутствует, предполагается, что он равен desktop )
- авторизация пользователя
- создание Xml для сессии (lang, func, binary, host)
- вызов глобальных обработчиков событий (тех, что висят на действии "*" ) before="yes"
- вызов обработчиков событий для выбранной функции before="yes"
- вызов обработчика функции
- вызов обработчиков событий для выбранной функции after="yes"
- вызов глобальных обработчиков событий (тех, что висят на действии "*" ) after="yes"
- завершение транзакций (запись файлов, удаление временных данных, вызов commit для баз данных)
- формирование ответа в требуемом формате (формат задается через параметр запроса out)
Аутентификация пользователя
На этом этапе происходит попытка определить имя пользователя отправившего запрос и уровень его доступа. За аутентификацию пользователей, и их проверку отвечает базовая функция authenticate . Проверка пользователя необходима, чтобы определить существует ли такой пользователь в системе на данный момент.
- проверка COOKIE (<имя панели> + "5") COOKIE должен содержать следующие поля разделенные двоеточием: имя темы, аббревиатуру языка, код сессии .
- загрузка сохраненных сессий из файла var/<имя панели>.ses (если он еще не был загружен) Этот файл записывается каждый раз, когда создается код сессии , на случай непредвиденного завершения работы панели.
- проверка пользователя (вызов authenticate.<имя метода авторизации> )
- авторизация пользователя исходя из данных полученных от клиента (вызов authenticate )
- авторизация по IP В данном, случае имя пользователя нам уже известно (задано в конфиге), но все равно необходим вызов authenticate , чтобы определить дополнительные параметры.
- авторизация пользователей пришедших с доверенного источника соединений Все аналогично предыдущему пункту, только имя пользователя — это имя локального администратора .
Если ни одна из предпринятых попыток не завершилась успешно, сессии присваивается нулевой уровень доступа.