diff --git a/application/controllers/GroupController.php b/application/controllers/GroupController.php index 88b2b754c..c91c033a2 100644 --- a/application/controllers/GroupController.php +++ b/application/controllers/GroupController.php @@ -46,7 +46,7 @@ class GroupController extends AuthBackendController } $this->view->backendSelection = new Form(); - $this->view->backendSelection->setAttrib('class', 'backend-selection'); + $this->view->backendSelection->setAttrib('class', 'backend-selection icinga-controls'); $this->view->backendSelection->setUidDisabled(); $this->view->backendSelection->setMethod('GET'); $this->view->backendSelection->setTokenDisabled(); @@ -128,6 +128,7 @@ class GroupController extends AuthBackendController if ($this->hasPermission('config/authentication/groups/edit') && $backend instanceof Reducible) { $removeForm = new Form(); $removeForm->setUidDisabled(); + $removeForm->setAttrib('class', 'inline'); $removeForm->setAction( Url::fromPath('group/removemember', array('backend' => $backend->getName(), 'group' => $groupName)) ); diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php index e7088ce4e..6bcdee76a 100644 --- a/application/controllers/NavigationController.php +++ b/application/controllers/NavigationController.php @@ -167,6 +167,7 @@ class NavigationController extends Controller $removeForm = new Form(); $removeForm->setUidDisabled(); + $removeForm->setAttrib('class', 'inline'); $removeForm->addElement('hidden', 'name', array( 'decorators' => array('ViewHelper') )); diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php index 7ef07534d..cdf2b77da 100644 --- a/application/controllers/UserController.php +++ b/application/controllers/UserController.php @@ -45,7 +45,7 @@ class UserController extends AuthBackendController } $this->view->backendSelection = new Form(); - $this->view->backendSelection->setAttrib('class', 'backend-selection'); + $this->view->backendSelection->setAttrib('class', 'backend-selection icinga-controls'); $this->view->backendSelection->setUidDisabled(); $this->view->backendSelection->setMethod('GET'); $this->view->backendSelection->setTokenDisabled(); @@ -141,6 +141,7 @@ class UserController extends AuthBackendController if ($this->hasPermission('config/authentication/groups/edit')) { $removeForm = new Form(); $removeForm->setUidDisabled(); + $removeForm->setAttrib('class', 'inline'); $removeForm->addElement('hidden', 'user_name', array( 'isArray' => true, 'value' => $userName, diff --git a/application/forms/AcknowledgeApplicationStateMessageForm.php b/application/forms/AcknowledgeApplicationStateMessageForm.php index 6fe0dd37c..61f5824f6 100644 --- a/application/forms/AcknowledgeApplicationStateMessageForm.php +++ b/application/forms/AcknowledgeApplicationStateMessageForm.php @@ -13,7 +13,7 @@ class AcknowledgeApplicationStateMessageForm extends Form public function init() { $this->setAction(Url::fromPath('application-state/acknowledge-message')); - $this->setAttrib('class', 'form-inline application-state-acknowledge-message-control'); + $this->setAttrib('class', 'application-state-acknowledge-message-control'); $this->setRedirectUrl('application-state/summary'); } diff --git a/application/forms/Announcement/AcknowledgeAnnouncementForm.php b/application/forms/Announcement/AcknowledgeAnnouncementForm.php index 4dce5df7a..85fecdc0b 100644 --- a/application/forms/Announcement/AcknowledgeAnnouncementForm.php +++ b/application/forms/Announcement/AcknowledgeAnnouncementForm.php @@ -17,7 +17,7 @@ class AcknowledgeAnnouncementForm extends Form public function init() { $this->setAction(Url::fromPath('announcements/acknowledge')); - $this->setAttrib('class', 'form-inline acknowledge-announcement-control'); + $this->setAttrib('class', 'acknowledge-announcement-control'); $this->setRedirectUrl('layout/announcements'); } diff --git a/application/forms/Announcement/AnnouncementForm.php b/application/forms/Announcement/AnnouncementForm.php index 2ac3b5023..4da47e2f1 100644 --- a/application/forms/Announcement/AnnouncementForm.php +++ b/application/forms/Announcement/AnnouncementForm.php @@ -92,6 +92,7 @@ class AnnouncementForm extends RepositoryForm { $this->setTitle(sprintf($this->translate('Remove announcement %s?'), $this->getIdentifier())); $this->setSubmitLabel($this->translate('Yes')); + $this->setAttrib('class', 'icinga-controls'); } /** diff --git a/application/forms/Authentication/LoginForm.php b/application/forms/Authentication/LoginForm.php index e8f053846..868302f96 100644 --- a/application/forms/Authentication/LoginForm.php +++ b/application/forms/Authentication/LoginForm.php @@ -16,6 +16,8 @@ use Icinga\Web\Url; */ class LoginForm extends Form { + const DEFAULT_CLASSES = 'icinga-controls'; + /** * Redirect URL */ diff --git a/application/forms/Config/User/UserForm.php b/application/forms/Config/User/UserForm.php index 6decec52f..e0cffb519 100644 --- a/application/forms/Config/User/UserForm.php +++ b/application/forms/Config/User/UserForm.php @@ -114,6 +114,7 @@ class UserForm extends RepositoryForm { $this->setTitle(sprintf($this->translate('Remove user %s?'), $this->getIdentifier())); $this->setSubmitLabel($this->translate('Yes')); + $this->setAttrib('class', 'icinga-controls'); } /** diff --git a/application/forms/Config/UserGroup/UserGroupForm.php b/application/forms/Config/UserGroup/UserGroupForm.php index 3214a3495..a590524de 100644 --- a/application/forms/Config/UserGroup/UserGroupForm.php +++ b/application/forms/Config/UserGroup/UserGroupForm.php @@ -64,6 +64,7 @@ class UserGroupForm extends RepositoryForm . ' have their membership cleared automatically.' )); $this->setSubmitLabel($this->translate('Yes')); + $this->setAttrib('class', 'icinga-form icinga-controls'); } /** diff --git a/application/forms/ConfirmRemovalForm.php b/application/forms/ConfirmRemovalForm.php index a1cd75f34..39fc66154 100644 --- a/application/forms/ConfirmRemovalForm.php +++ b/application/forms/ConfirmRemovalForm.php @@ -10,6 +10,8 @@ use Icinga\Web\Form; */ class ConfirmRemovalForm extends Form { + const DEFAULT_CLASSES = 'icinga-controls'; + /** * {@inheritdoc} */ diff --git a/application/forms/Control/LimiterControlForm.php b/application/forms/Control/LimiterControlForm.php index bd90ed16d..dee834696 100644 --- a/application/forms/Control/LimiterControlForm.php +++ b/application/forms/Control/LimiterControlForm.php @@ -15,7 +15,7 @@ class LimiterControlForm extends Form * * @var string */ - const CSS_CLASS_LIMITER = 'limiter-control'; + const CSS_CLASS_LIMITER = 'limiter-control icinga-controls'; /** * Default limit @@ -37,6 +37,11 @@ class LimiterControlForm extends Form 500 => '500' ); + public static $defaultElementDecorators = [ + ['Label', ['tag' => 'span', 'separator' => '']], + ['ViewHelper', ['separator' => '']], + ]; + /** * Default limit for this instance * diff --git a/application/forms/PreferenceForm.php b/application/forms/PreferenceForm.php index 836befadb..2ceb44973 100644 --- a/application/forms/PreferenceForm.php +++ b/application/forms/PreferenceForm.php @@ -302,7 +302,8 @@ class PreferenceForm extends Form array( 'ignore' => true, 'label' => $this->translate('Save to the Preferences'), - 'decorators' => array('ViewHelper') + 'decorators' => array('ViewHelper'), + 'class' => 'btn-primary' ) ); } @@ -335,7 +336,7 @@ class PreferenceForm extends Form array( 'decorators' => array( 'FormElements', - array('HtmlTag', array('tag' => 'div', 'class' => 'control-group')) + array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls')) ) ) ); diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index 829c78389..cb517f178 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -37,39 +37,50 @@ class RoleForm extends RepositoryForm public function init() { + $this->setAttrib('class', self::DEFAULT_CLASSES . ' role-form'); + $helper = new Zend_Form_Element('bogus'); + $view = $this->getView(); $this->providedPermissions['application'] = [ $helper->filterName('application/share/navigation') => [ - 'label' => $this->translate('Allow to share navigation items'), - 'name' => 'application/share/navigation' + 'name' => 'application/share/navigation', + 'description' => $this->translate('Allow to share navigation items') ], - $helper->filterName('application/stacktraces') => [ - 'label' => $this->translate('Allow to adjust in the preferences whether to show stacktraces'), - 'name' => 'application/stacktraces' + $helper->filterName('application/stacktraces') => [ + 'name' => 'application/stacktraces', + 'description' => $this->translate( + 'Allow to adjust in the preferences whether to show stacktraces' + ) ], - $helper->filterName('application/log') => [ - 'label' => $this->translate('Allow to view the application log'), - 'name' => 'application/log' + $helper->filterName('application/log') => [ + 'name' => 'application/log', + 'description' => $this->translate('Allow to view the application log') ], - $helper->filterName('admin') => [ - 'label' => $this->translate('Grant admin permissions, e.g. manage announcements'), - 'name' => 'admin' + $helper->filterName('admin') => [ + 'name' => 'admin', + 'description' => $this->translate( + 'Grant admin permissions, e.g. manage announcements' + ) ], - $helper->filterName('config/*') => [ - 'label' => $this->translate('Allow config access'), - 'name' => 'config/*' + $helper->filterName('config/*') => [ + 'name' => 'config/*', + 'description' => $this->translate('Allow config access') ] ]; $this->providedRestrictions['application'] = [ $helper->filterName('application/share/users') => [ - 'label' => $this->translate('Restrict which users this role can share items and information with'), - 'name' => 'application/share/users' + 'name' => 'application/share/users', + 'description' => $this->translate( + 'Restrict which users this role can share items and information with' + ) ], $helper->filterName('application/share/groups') => [ - 'label' => $this->translate('Restrict which groups this role can share items and information with'), - 'name' => 'application/share/groups' + 'name' => 'application/share/groups', + 'description' => $this->translate( + 'Restrict which groups this role can share items and information with' + ) ] ]; @@ -77,34 +88,43 @@ class RoleForm extends RepositoryForm foreach ($mm->listInstalledModules() as $moduleName) { $modulePermission = Manager::MODULE_PERMISSION_NS . $moduleName; $this->providedPermissions[$moduleName][$helper->filterName($modulePermission)] = [ - 'label' => sprintf($this->translate('Allow access to module %s'), $moduleName), + 'isUsagePerm' => true, 'name' => $modulePermission, - 'isUsagePerm' => true + 'label' => $view->escape($this->translate('General Module Access')), + 'description' => sprintf($this->translate('Allow access to module %s'), $moduleName) ]; $module = $mm->getModule($moduleName, false); $permissions = $module->getProvidedPermissions(); - if (count($permissions) > 1) { - $this->providedPermissions[$moduleName][$helper->filterName($moduleName . '/*')] = [ - 'label' => $this->translate('Full Access'), - 'name' => $moduleName . '/*', - 'isFullPerm' => true - ]; - } + $this->providedPermissions[$moduleName][$helper->filterName($moduleName . '/*')] = [ + 'isFullPerm' => true, + 'name' => $moduleName . '/*', + 'label' => $view->escape($this->translate('Full Module Access')) + ]; foreach ($permissions as $permission) { /** @var object $permission */ $this->providedPermissions[$moduleName][$helper->filterName($permission->name)] = [ - 'label' => $permission->description, - 'name' => $permission->name + 'name' => $permission->name, + 'label' => preg_replace( + '~^(\w+)(\/.*)~', + '$1$2', + $view->escape($permission->name) + ), + 'description' => $permission->description ]; } foreach ($module->getProvidedRestrictions() as $restriction) { $this->providedRestrictions[$moduleName][$helper->filterName($restriction->name)] = [ - 'label' => $restriction->description, - 'name' => $restriction->name + 'name' => $restriction->name, + 'label' => preg_replace( + '~^(\w+)(\/.*)~', + '$1$2', + $view->escape($restriction->name) + ), + 'description' => $restriction->description ]; } } @@ -156,7 +176,24 @@ class RoleForm extends RepositoryForm foreach ($this->providedPermissions as $moduleName => $permissionList) { $this->sortPermissions($permissionList); - $elements = []; + $elements = [$moduleName . '_header']; + $this->addElement( + 'note', + $moduleName . '_header', + [ + 'decorators' => ['ViewHelper'], + 'value' => '

' . ($moduleName !== 'application' + ? sprintf('%s %s', $moduleName, $this->translate('Module')) + : 'Icinga Web 2') . '

' + ] + ); + + $elements[] = 'permission_header'; + $this->addElement('note', 'permission_header', [ + 'value' => '

' . $this->translate('Permissions') . '

', + 'decorators' => ['ViewHelper'] + ]); + $hasFullPerm = false; foreach ($permissionList as $name => $spec) { $elements[] = $name; @@ -168,36 +205,49 @@ class RoleForm extends RepositoryForm 'autosubmit' => isset($spec['isFullPerm']), 'disabled' => $hasFullPerm ?: null, 'value' => $hasFullPerm, - 'label' => $spec['name'], - 'description' => $spec['label'] + 'label' => isset($spec['label']) ? $spec['label'] : $spec['name'], + 'description' => isset($spec['description']) ? $spec['description'] : $spec['name'] ] - ); + ) + ->getElement($name) + ->getDecorator('Label') + ->setOption('escape', false); if (isset($spec['isFullPerm'])) { $hasFullPerm = isset($formData[$name]) && $formData[$name]; } } if (isset($this->providedRestrictions[$moduleName])) { + $elements[] = 'restriction_header'; + $this->addElement('note', 'restriction_header', [ + 'value' => '

' . $this->translate('Restrictions') . '

', + 'decorators' => ['ViewHelper'] + ]); + foreach ($this->providedRestrictions[$moduleName] as $name => $spec) { $elements[] = $name; $this->addElement( 'text', $name, [ - 'label' => $spec['name'], - 'description' => $spec['label'] + 'label' => isset($spec['label']) ? $spec['label'] : $spec['name'], + 'description' => $spec['description'] ] - ); + ) + ->getElement($name) + ->getDecorator('Label') + ->setOption('escape', false); } } $this->addDisplayGroup($elements, $moduleName . '_elements', [ - 'legend' => $moduleName !== 'application' - ? sprintf($this->translate('Module: %s'), $moduleName) - : 'Icinga Web 2', 'decorators' => [ 'FormElements', - ['Fieldset', ['class' => 'collapsible']] + ['Fieldset', [ + 'class' => 'collapsible', + 'data-toggle-element' => 'h3', + 'data-visible-height' => 0 + ]] ] ]); } diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml index 0394fd2f5..f64e1bae4 100644 --- a/application/views/scripts/group/show.phtml +++ b/application/views/scripts/group/show.phtml @@ -40,7 +40,7 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan compact): ?>

translate('Members'); ?>

-
+
limiter; ?> paginator; ?> sortBox; ?> diff --git a/application/views/scripts/layout/menu.phtml b/application/views/scripts/layout/menu.phtml index 5177c35de..c82d5cc84 100644 --- a/application/views/scripts/layout/menu.phtml +++ b/application/views/scripts/layout/menu.phtml @@ -7,7 +7,7 @@ $searchDashboard->setUser($this->Auth()->getUser()); if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?>
' . $content; } } diff --git a/library/Icinga/Web/Form/Element/Checkbox.php b/library/Icinga/Web/Form/Element/Checkbox.php new file mode 100644 index 000000000..d4499a038 --- /dev/null +++ b/library/Icinga/Web/Form/Element/Checkbox.php @@ -0,0 +1,9 @@ +getAttrib('rows') === null) { + $this->setAttrib('rows', 3); + } + } +} diff --git a/library/Icinga/Web/View.php b/library/Icinga/Web/View.php index 2d5bb4cc3..bec17b3ed 100644 --- a/library/Icinga/Web/View.php +++ b/library/Icinga/Web/View.php @@ -4,6 +4,7 @@ namespace Icinga\Web; use Closure; +use Icinga\Application\Icinga; use Zend_View_Abstract; use Icinga\Authentication\Auth; use Icinga\Exception\ProgrammingError; @@ -92,6 +93,8 @@ class View extends Zend_View_Abstract } } + $config['helperPath']['Icinga\\Web\\View\\Helper\\'] = Icinga::app()->getLibraryDir('Icinga/Web/View/Helper'); + parent::__construct($config); } diff --git a/library/Icinga/Web/View/Helper/IcingaCheckbox.php b/library/Icinga/Web/View/Helper/IcingaCheckbox.php new file mode 100644 index 000000000..88b6c7c43 --- /dev/null +++ b/library/Icinga/Web/View/Helper/IcingaCheckbox.php @@ -0,0 +1,28 @@ +view->protectId('icingaCheckbox_' . $name); + } + + $html = parent::formCheckbox($name, $value, $attribs, $checkedOptions); + + $class = 'toggle-switch'; + if (isset($attribs['disabled'])) { + $class .= ' disabled'; + } + + return $html + . ''; + } +} diff --git a/library/Icinga/Web/Widget/Announcements.php b/library/Icinga/Web/Widget/Announcements.php index 78d73228a..5944eb44f 100644 --- a/library/Icinga/Web/Widget/Announcements.php +++ b/library/Icinga/Web/Widget/Announcements.php @@ -36,11 +36,11 @@ class Announcements extends AbstractWidget $announcements = $repo->findActive(); $announcements->applyFilter($acked); if ($announcements->hasResult()) { - $html = '
'; diff --git a/library/Icinga/Web/Widget/FilterEditor.php b/library/Icinga/Web/Widget/FilterEditor.php index ce43e2c82..9eb969c63 100644 --- a/library/Icinga/Web/Widget/FilterEditor.php +++ b/library/Icinga/Web/Widget/FilterEditor.php @@ -768,12 +768,12 @@ class FilterEditor extends AbstractWidget return ''; } if (! $this->preservedUrl()->getParam('modifyFilter')) { - return '
' + return '
' . $this->renderSearch() . $this->view()->escape($this->shorten($this->filter, 50)) . '
'; } - return '
' + return '
' . $this->renderSearch() . '' - . '' - . '' + . '' + . '' . '
' . '' . '' diff --git a/library/Icinga/Web/Widget/SortBox.php b/library/Icinga/Web/Widget/SortBox.php index 8bec80cec..037e95995 100644 --- a/library/Icinga/Web/Widget/SortBox.php +++ b/library/Icinga/Web/Widget/SortBox.php @@ -187,7 +187,7 @@ class SortBox extends AbstractWidget $columnForm = new Form(); $columnForm->setTokenDisabled(); $columnForm->setName($this->name . '-column'); - $columnForm->setAttrib('class', 'inline'); + $columnForm->setAttrib('class', 'icinga-controls'); $columnForm->addElement( 'select', 'sort', diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php index d8fff893e..a0b27ce13 100644 --- a/library/Icinga/Web/Wizard.php +++ b/library/Icinga/Web/Wizard.php @@ -605,7 +605,7 @@ class Wizard 'button', static::BTN_NEXT, array( - 'class' => 'control-button', + 'class' => 'control-button btn-primary', 'type' => 'submit', 'value' => $pages[1]->getName(), 'label' => t('Next'), @@ -629,7 +629,7 @@ class Wizard 'button', static::BTN_NEXT, array( - 'class' => 'control-button', + 'class' => 'control-button btn-primary', 'type' => 'submit', 'value' => $pages[$index + 1]->getName(), 'label' => t('Next'), @@ -653,7 +653,7 @@ class Wizard 'button', static::BTN_NEXT, array( - 'class' => 'control-button', + 'class' => 'control-button btn-primary', 'type' => 'submit', 'value' => $page->getName(), 'label' => t('Finish'), diff --git a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php index bc3c01f39..8b0139966 100644 --- a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php +++ b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php @@ -26,7 +26,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm public function init() { $this->setUseFormAutosubmit(); - $this->setAttrib('class', 'inline instance-features'); + $this->setAttrib('class', self::DEFAULT_CLASSES . ' instance-features'); } /** diff --git a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php index cc76327b5..3a97d173f 100644 --- a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php @@ -32,7 +32,7 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm public function init() { $this->setUseFormAutosubmit(); - $this->setAttrib('class', 'inline object-features'); + $this->setAttrib('class', self::DEFAULT_CLASSES . ' object-features'); $features = array( ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS => array( 'label' => $this->translate('Active Checks'), diff --git a/modules/monitoring/application/forms/Config/BackendConfigForm.php b/modules/monitoring/application/forms/Config/BackendConfigForm.php index 3e4a789d0..c311bda1c 100644 --- a/modules/monitoring/application/forms/Config/BackendConfigForm.php +++ b/modules/monitoring/application/forms/Config/BackendConfigForm.php @@ -216,8 +216,6 @@ class BackendConfigForm extends ConfigForm ) ); - $decorators = static::$defaultElementDecorators; - array_pop($decorators); // Removes the HtmlTag decorator $this->addElement( 'select', 'resource', @@ -227,7 +225,6 @@ class BackendConfigForm extends ConfigForm 'description' => $this->translate('The resource to use'), 'multiOptions' => $this->resources[$resourceType], 'value' => current($this->resources[$resourceType]), - 'decorators' => $decorators, 'autosubmit' => true ) ); @@ -237,7 +234,6 @@ class BackendConfigForm extends ConfigForm 'resource_note', array( 'escape' => false, - 'decorators' => $decorators, 'value' => sprintf( '%3$s', $this->getView()->url('config/editresource', array('resource' => $resourceName)), @@ -246,16 +242,6 @@ class BackendConfigForm extends ConfigForm ) ) ); - $this->addDisplayGroup( - array('resource', 'resource_note'), - 'resource-group', - array( - 'decorators' => array( - 'FormElements', - array('HtmlTag', array('tag' => 'div', 'class' => 'control-group')) - ) - ) - ); if (isset($formData['skip_validation']) && $formData['skip_validation']) { // In case another error occured and the checkbox was displayed before diff --git a/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php index cb55324c4..48b98ac5c 100644 --- a/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php +++ b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php @@ -96,7 +96,7 @@ class SelectBox extends AbstractWidget public function render() { $form = new Form(); - $form->setAttrib('class', 'inline'); + $form->setAttrib('class', Form::DEFAULT_CLASSES . ' inline'); $form->setMethod('GET'); $form->setUidDisabled(); $form->setTokenDisabled(); diff --git a/modules/monitoring/public/css/module.less b/modules/monitoring/public/css/module.less index 8ba1b29d6..a7e2bd544 100644 --- a/modules/monitoring/public/css/module.less +++ b/modules/monitoring/public/css/module.less @@ -590,8 +590,7 @@ form.instance-features span.description, form.object-features span.description { .object-features { .control-label-group { text-align: left; - padding: @table-column-padding; - padding-left: 0; + margin-right: 0; width: @name-value-table-name-width; label { @@ -599,9 +598,8 @@ form.instance-features span.description, form.object-features span.description { } } - input[type="checkbox"], - input[type="radio"] { - margin: @table-column-padding; + .toggle-switch { + margin-left: @table-column-padding; } } diff --git a/modules/setup/application/views/scripts/form/setup-modules.phtml b/modules/setup/application/views/scripts/form/setup-modules.phtml index 108218441..51a8c2a51 100644 --- a/modules/setup/application/views/scripts/form/setup-modules.phtml +++ b/modules/setup/application/views/scripts/form/setup-modules.phtml @@ -9,6 +9,7 @@ use Icinga\Web\Wizard; enctype="getEncType(); ?>" method="getMethod(); ?>" action="getAction(); ?>" + class="icinga-controls" data-progress-element="" >

translate('Modules', 'setup.page.title'); ?>

@@ -16,9 +17,13 @@ use Icinga\Web\Wizard; getElements() as $element): ?> getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, Wizard::PROGRESS_ELEMENT, $form->getTokenElementName(), $form->getUidElementName()))): ?>
-

- - +
+

+
+ +
+
+
diff --git a/modules/setup/application/views/scripts/form/setup-welcome.phtml b/modules/setup/application/views/scripts/form/setup-welcome.phtml index 09810bce5..0284aa5d8 100644 --- a/modules/setup/application/views/scripts/form/setup-welcome.phtml +++ b/modules/setup/application/views/scripts/form/setup-welcome.phtml @@ -63,7 +63,7 @@ if (! (false === ($distro = Platform::getLinuxDistro(1)) || $distro === 'linux') . ' finished you are able to log in and to explore all the new and stunning features!' ); ?>

-
+ getElement('token'); ?> getElement($form->getTokenElementName()); ?> getElement($form->getUidElementName()); ?> diff --git a/public/css/icinga/controls.less b/public/css/icinga/controls.less index d38598e25..c2142de47 100644 --- a/public/css/icinga/controls.less +++ b/public/css/icinga/controls.less @@ -2,9 +2,9 @@ // TODO(el): Rename .filter to .filter-control -// Hide auto sumbit info in controls but keep information for screen readers +// Hide auto sumbit info in controls .controls .autosubmit-info { - .sr-only(); + display: none; } @@ -12,13 +12,34 @@ .backend-selection { float: left; - > .control-group { - padding: 0; + .control-label-group, select { + display: inline-block; + } - > .control-label-group { - text-align: left; - width: auto; - } + select { + margin-left: .5em; + } +} + +.controls input.search, +input.search { + .transition(border 0.3s ease); + .transition(background-image 0.2s ease); + + background-image: url(../img/icons/search.png); + background-repeat: no-repeat; + background-size: 1em 1em; + background-position: .25em center; + outline: none; + padding-left: 1.5em; + width: 20em; + + &:focus { + background-image: url(../img/icons/search_icinga_blue.png); + } + + &:focus:not([readonly]) { + border-color: @icinga-blue; } } @@ -29,41 +50,37 @@ margin-bottom: 0.5em; } -.backend-selection, -.pagination-control, -.sort-controls-container { - // Select controls may not respect font-size thus leading to improperly vertical alignment w/o big enough line-height - line-height: 2em; -} - .filter { // Display filter control on a new line clear: both; -} + margin: .5em 0; -.limiter-control > .control-group { - padding: 0; - // Note that the sort-control form does not have padding as it's utilizing different decorators - - > .control-label-group { - text-align: left; - padding: 0; - width: 1em; + > a { + color: @icinga-blue; + padding: .5em; + line-height: 1; } - > select { - width: 4.5em; + > a > i { + text-align: center; + &:before { + margin-right: 0; + } } - > i { - .sr-only(); + .form input { + padding: @vertical-padding @vertical-padding; } } -.limiter-control, -.sort-control { - // Display limiter and sort control both on the same line; floating could be used here too to achieve the same - display: inline-block; +.controls .filter { + form .search { + height: 2em; + } +} + +.limiter-control > select { + margin-left: .5em; } .pagination-control { @@ -72,12 +89,18 @@ float: left; li { + line-height: 1; + &.active { + border-bottom: 2px solid @icinga-blue; + > a, > a:hover { - border-bottom: 2px solid @icinga-blue; - color: @icinga-blue + color: @icinga-blue; + /* Compensate border-bottom: 2px */ + margin-bottom: -2px; } + > a:hover { background: none; cursor: default; @@ -92,7 +115,7 @@ > a, > span { - padding: 0 0.5em 0.25em 0.5em; + padding: 0.5em; } > a:hover { background-color: @gray-lighter; @@ -120,8 +143,6 @@ } .sort-control { - margin-left: 0.25em; - label { width: auto; margin-right: 0.5em; @@ -141,15 +162,20 @@ .sort-controls-container { clear: right; float: right; + display: flex; - > * { - vertical-align: middle; + > *:not(:last-child) { + margin-right: .5em; } } .sort-direction-control { margin-left: 0.25em; width: 1em; + + .spinner { + line-height: 1; + } } html.no-js .sort-control form { @@ -157,3 +183,28 @@ html.no-js .sort-control form { margin-left: auto; margin-top: 0.25em; } + +.controls { + .control-label-group { + margin-top: 0; + margin-bottom: 0; + line-height: 1.5em; + padding-top: 0.25em; + padding-bottom: 0.25em; + } + + input, + select { + padding: .5em; + max-width: 16em; + } + + select { + padding-right: 1.526em; + margin-top: 0; + margin-bottom: 0; + /* compensate inconsistent select height calculations */ + line-height: 1; + max-height: 2em; + } +} diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less index 89211c165..4c3a6ff42 100644 --- a/public/css/icinga/forms.less +++ b/public/css/icinga/forms.less @@ -1,618 +1,494 @@ -/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */ +/*! Icinga Web 2 | (c) 2019 Icinga Development Team | GPLv2+ */ -input:not([type]), -input[type=text], -input[type=date], -input[type=datetime-local], -input[type=email], -input[type=number], -input[type=password], -input[type=search], -input[type=tel], -input[type=time], -input[type=url] { - .box-shadow(0 1px 0 0 @gray-light); - .transition(border 0.3s ease); +/** + Rules found in here are structured with two layers: - border: none; - border-bottom: 1px solid @gray-light; - outline: none; - padding: @vertical-padding / 2 @horizontal-padding / 2; + 1) form.icinga-form, that's what defines the general structure of our single/individual forms. It's not + supposed to be used for any other forms that are not the only content on the page (e.g. inline-forms) + 2) .icinga-controls, this defines the design of our controls. Any input that's part of a container with + this class gets our design applied + */ - &:focus:not([readonly]) { - border-color: @icinga-blue; - } +// Used colors - width: 20em; -} +@disabled-gray: #9a9a9a; +@low-sat-blue: #dae3e6; +@low-sat-blue-dark: #becbcf; +@icinga-blue-light: #a5c4cd; +@icinga-blue-dark: #0081a6; -select, textarea { - width: 20em; -} +// General form layout -.control-button, -input[type="submit"] { - .button(); -} +form.icinga-form { + max-width: 70em; + width: 80%; -input:-moz-placeholder { // FF 18- - color: @gray-light; - opacity: 1; -} - -input::-moz-placeholder { // FF 19+ - color: @gray-light; - opacity: 1; -} - -input:-ms-input-placeholder { - color: @gray-light; -} - -input::-webkit-input-placeholder { - color: @gray-light; -} - -input.search { - background: transparent url("../img/icons/search.png") no-repeat scroll 0.1em center; - background-size: 1em 1em; - padding-left: 1.5em; - - &:focus { - background-color: @body-bg-color; - background-image: url('../img/icons/search_icinga_blue.png'); - } -} - -// TODO(el): .form-controls-inline control-group { display: inline-block; } -form.inline, -.form-inline { .control-group { - padding: 0; + display: flex; + flex-wrap: wrap; + align-items: flex-start; + + // Negative margin-right because every child gets 1em right but we can't exclude + // the last element as it's impossible to identify the last *visible* element + margin: 1em -1em 1em 0; + + > * { + margin-right: 1em; + } } + .form-controls { - margin: 0; - } - display: inline-block; -} - -.form-controls { - margin-left: 10em; -} - -.autosubmit-info { - margin-left: 0.5em; -} - -button:hover .icon-cancel { - color: @color-critical; -} - -.control-info { - color: @text-color-light; - margin-left: 0.2em; - - &:hover { - .opacity(0.6); + display: flex; + justify-content: flex-end; } } -.control-label { - display: inline-block; +// Minimal form layout + +#layout.minimal-layout, +#layout.twocols:not(.wide-layout) { + form.icinga-form { + width: 100%; + + .control-label-group { + text-align: left; + padding-bottom: 0; + padding-left: 0; + margin-bottom: 0; + } + + .toggle-switch ~ .control-info:before { + margin-left: 0; + } + + .control-info { + opacity: .5; + } + + .errors > li { + margin-left: .5em; + } + } } -label { - color: @text-color-light; - font-size: @font-size-small; -} +// Label styles -.control-label-group { - display: inline-block; - padding-right: @horizontal-padding; +form.icinga-form .control-label-group { + display: flex; + flex-direction: column; + justify-content: center; + line-height: 1.1em; + padding: .5625em .5625em .5625em 0; + max-height: 2.5em; text-align: right; - width: 10em; + width: 14em; } -.control-group { - padding: @vertical-padding @horizontal-padding; +.icinga-controls fieldset { + margin: 0; + padding: 0; + border: none; + border-top: 1px solid @gray-light; - .errors { - color: @color-critical; + legend, .description { + margin-left: .7em; } } +.icinga-controls .control-info { + line-height: 2.25em; + opacity: .6; + + &:before { + margin-right: 0; + } + + &:hover:before { + color: black; + } +} + +form.icinga-form .control-group .control-info { + margin-left: -.5em; +} +form.icinga-form .control-group .toggle-switch ~ .control-info { + margin-left: 0; +} + +form.inline { + display: inline-block; + + .control-group .spinner { + line-height: 1.5; + margin: 0 auto; + padding: 0 .5em; + } +} + +// General input styles + +.icinga-controls { + input[type="text"], + input[type="password"], + input[type="number"], + input[type="datetime-local"], + input[type="date"], + input[type="time"], + textarea, + select { + background-color: @low-sat-blue; + } +} + +form.icinga-form { + input[type="text"], + input[type="password"], + input[type="number"], + input[type="datetime-local"], + input[type="date"], + input[type="time"], + textarea, + select { + flex: 1; + width: 0; + } +} + +.icinga-controls { + input:not([type="checkbox"]), + .toggle-switch, + button, + select, + textarea { + border: none; + .rounded-corners(.25em); + .appearance(none); + } +} + +.icinga-controls { + input:not([type="checkbox"]), + .toggle-switch, + select, + textarea, + button, + .toggle-switch { + font-size: inherit; + padding: @vertical-padding; + } +} + +form.icinga-form { + .control-group .toggle-switch, + .form-controls .toggle-switch { + margin-top: 0.5em*0.666666667; + margin-bottom: 0.5em*0.666666667; + } +} + +form.icinga-form select:not([multiple]) { + // Compensate inconsistent select height calculations + line-height: 1em; + height: 2.25em; +} + +// Remove native dropdown arrow in IE10+ +.icinga-controls select::-ms-expand { + display: none; + opacity: 0; +} + +.icinga-controls select:not([multiple]) { + padding-right: 1.5625em; + background-image: url(../img/select-icon.svg); + background-repeat: no-repeat; + background-position: right center; + background-size: contain; +} + +form.icinga-form select { + width: 0; // Prevent selects with long option values from exceeding the container +} + +form.inline select { + width: auto; +} + + +// Specific input styles + .link-button { .action-link(); // Reset defaults background: none; border: none; + display: inline-block; padding: 0; text-align: left; +} - &:hover { - text-decoration: underline; +.icinga-controls { + input ~ .spinner, + button ~ .spinner, + select ~ .spinner, + textarea ~ .spinner { + line-height: normal; + padding: .5em 0; + + &:before { + vertical-align: middle; + margin-left: .5em; + opacity: 0.4; + } } } -// @TODO(el): Fix that -.form-description, .form-description ul, -.form-info, .form-info ul, -.form-notifications, .form-notifications ul, -.form-errors, .form-errors ul, -form .errors, form .errors ul { - // Reset defaults - margin: 5 0; +/* selects get their spinner specifically placed */ +.icinga-controls select:not([multiple]) + .spinner { + height: 2.25em; + margin: 0; + + &:before { + margin-left: -3.75em; + } +} + +form.icinga-form .form-controls { + .spinner { + order: -1; + } + + .btn-primary { + order: 99; + } +} + +// Button styles + +.icinga-controls { + input[type="submit"], + input[type="submit"].btn-confirm { + .button(); + } + + input[type="submit"].btn-remove { + .button(@body-bg-color, @color-critical, darken(@color-critical, 10%)); + } + + input[type="submit"].btn-cancel { + .button(@body-bg-color, @gray, @black); + } +} + +// Toggle styles + +.icinga-controls .toggle-switch { + cursor: pointer; + position: relative; + display: inline-block; + height: 1.5em; + width: 2.625em; +} + +// Hide default checkbox +.icinga-controls input[type="checkbox"] { + opacity: 0; + width: 0; + height: 0; + margin: 0; padding: 0; - list-style-type: none; } -.form-errors { - background-color: @color-critical; - color: @text-color-inverted; +.icinga-controls .toggle-switch .toggle-slider { + position: absolute; + left: 0; + top: 0; + + display: inline-block; + background: @low-sat-blue; + border: 1px solid @low-sat-blue; + box-sizing: content-box; + border-radius: 1em; + height: 4/3em; + width: 8/3em; + vertical-align: middle; } -.form-info { +.icinga-controls .toggle-switch .toggle-slider:before { + position: absolute; + top: 0; + left: 0; + + background: @text-color-inverted; + border-radius: 1em; + border: 1px solid @low-sat-blue; + box-sizing: border-box; + content: ""; + display: block; + height: 4/3em; + margin-left: 0; + width: 4/3em; + + @transition: left .2s ease, margin .2s ease; + -webkit-transition: @transition; + -moz-transition: @transition; + -o-transition: @transition; + transition: @transition; +} + +.icinga-controls input[type="checkbox"]:checked + .toggle-switch .toggle-slider { + background-color: @icinga-blue; + border: 1px solid @icinga-blue; +} + +.icinga-controls input[type="checkbox"]:focus + .toggle-switch .toggle-slider { + box-shadow: 0 0 0 2px @body-bg-color, 0 0 0 4px fade(@icinga-blue, 40); +} + +.icinga-controls input[type="checkbox"]:checked + .toggle-switch .toggle-slider:before { + border: 1px solid @icinga-blue; + left: 100%; + margin-left: -4/3em; +} + +// Disabled inputs + +.icinga-controls .toggle-switch.disabled { + cursor: default; + + & > .toggle-slider { + background-color: @gray-light; + border-color: @gray-light; + + &:before { + background-color: @gray-lighter; + border-color: @gray-light; + } + } +} + +form.icinga-form .control-group.disabled .control-label-group { + color: @disabled-gray; +} + +.icinga-controls { + input[disabled], + select[disabled] { + background: @gray-lighter; + border-color: transparent; + } +} + +// Errors and additional information + +form.icinga-form { + .form-notifications, + .form-description { + border-radius: .25em; + display: flex; + list-style: none; + align-items: center; + margin: 0 0 1em 0; + padding: .25em .5em; + + ul { + list-style: none; + margin: 0; + padding: 0 .5em; + } + + li { + list-style: none; + } + + & .form-notification-icon, + & .form-description-icon { + font-size: 2em; + margin-left: .25em; + opacity: .4; + align-self: flex-start; + } + } + + .form-notifications { + &.info { + background-color: fade(@color-ok, 20%); + } + + &.error { + background-color: fade(@color-critical, 30%); + } + + &.warning { + background-color: fade(@color-warning, 40%); + } + } + + .form-description { + background-color: fade(@gray, 5%); + } + + .errors { + list-style: none; + position: relative; + margin: 0; + padding: 0; + display: block; + width: 100%; + + & > li { + margin: -0.5em 0 0.5em 16.5em; + color: #f56; + } + } +} + +form.icinga-form .form-info { color: @text-color-light; font-size: @font-size-small; + list-style: none; + padding-left: 0; } -.form-notifications { - .notification-error { - background-color: @color-critical; - color: @text-color-inverted; - li { - padding: @vertical-padding @horizontal-padding; - } +// Placeholder styles + +.icinga-controls { + input:-moz-placeholder { // FF 18- + color: @gray; + opacity: 1; } - .notification-info { - background-color: @color-pending; - color: @text-color-inverted; - li { - padding: @vertical-padding @horizontal-padding; - } + + input::-moz-placeholder { // FF 19+ + color: @gray; + opacity: 1; } - .notification-warning { - background-color: @color-warning; - color: @text-color-inverted; - li { - padding: @vertical-padding @horizontal-padding; - } + + input:-ms-input-placeholder { + color: @gray; + } + + input::-webkit-input-placeholder { + color: @gray; } } -button.animated.active { - &.move-up i:before { - .animate(move-vertical 500ms infinite linear); - } +// Specific form styles - &.move-down i:before { - .animate(move-vertical 500ms infinite linear reverse); - } +.search.inline { + display: inline-block; } -textarea { - height: 8em; +.sort-control > * { + display: inline-block; } -select.grant-permissions { - height: 20em; +/* Flyover form styles */ + +.flyover-content form:not(.inline):not([role="search"]) { width: auto; } -//div.config-form-buttons { -// margin-top: 5px; -//} -// -//table.configTable { -// border-spacing: 15px; -// border-collapse: separate; -//} -// -//td.configTable { -// border: solid; -// border-width: thin; -// padding: 10px; -// min-width: 300px; -//} -// -//.form-element { -// margin-bottom: 1em; -//} -// -//label { -// display: block; -// font-weight: bold; -//} -// -//input, select, textarea { -// box-sizing: border-box; -// -moz-box-sizing: border-box; -// -webkit-box-sizing: border-box; -// border: 1px solid #ddd; -// font-size: 0.9em; -// padding: 0.2em; -// background: #fff; -// font-family: Calibri, Helvetica, sans-serif; -//} -// -//input[type=checkbox] { -// margin-top: 0.3em; -// margin-bottom: 0.1em; -//} -// -//input:focus, select:focus { -// border-color: #333; -//} -// -//input[type=submit] { -// font-weight: bold; -// text-align: center; -// color: #fff; -// border: 1px solid; -// border-color: @colorPetrol; -// background: @colorPetrol; -// outline: 0; -// -// &[disabled] { -// background-color: #666; -// border-color: black; -// } -// -// &:hover[disabled], &:active[disabled], &:focus[disabled] { -// background-color: #666; -// } -//} -// -//input[type=submit]:hover, a.button:hover, input[type=submit]:focus { -// background-color: #333; -// border-color: #333; -//} -// -//input[type=submit]:hover, a.button:hover { -// cursor: pointer; -// color: white; -//} -// -//input:focus, select:focus { -// background: white; -// outline: 0; -//} -// -//input::-moz-placeholder { -// color: #333; -//} -// -//input::-webkit-input-placeholder { -// color: #333; -//} -// -//input:-ms-input-placeholder { -// color: #333; -//} -// -//input::-moz-focus-inner { -// border: 0; -// outline: 0; -//} -// -//button::-moz-focus-inner { -// border: 0; -// outline: 0; -//} -// -//select::-moz-focus-inner { -// border: 0; -// outline: 0; -//} -// -//form.inline { -// margin: 0; -// padding: 0; -// display: inline; -//} -// -//div.spinner { -// display: inline-block; -// margin-top: 0.2em; -// margin-left: 0.25em; -// -// i { -// visibility: hidden; -// -// &.active { -// visibility: visible; -// -// &:before { -// .animate(spin 2s infinite linear); -// } -// } -// -// &:before { -// margin: 0; // Disables wobbling -// } -// } -//} -// -//button.animated.active { -// &.move-up i:before { -// .animate(move-vertical 500ms infinite linear); -// } -// -// &.move-down i:before { -// .animate(move-vertical 500ms infinite linear reverse); -// } -//} -// -//button, .button-like { -// font-size: 0.9em; -// font-weight: bold; -// outline: 0; -// color: #fff; -// padding: 0.2em; -// border: 1px solid; -// border-color: @colorPetrol; -// background: @colorPetrol; -// -// &[disabled] { -// background-color: #666; -// border-color: black; -// } -// -// &:hover, &:focus, &:active { -// background-color: #333; -// border-color: #333; -// cursor: pointer; -// -// &[disabled] { -// background-color: #666; -// } -// } -//} -// -//.button-like { -// display: inline-block; -//} -// -//a.button-like { -// cursor: default; -// text-decoration: none; -//} -// -//form.link-like input[type="submit"], form.link-like button[type="submit"], input.link-like, button.link-like { -// color: @colorLinkDefault; -// font-weight: normal; -// border: none; -// background: none; -// padding: 0; -// font-size: 1em; -// cursor: pointer; -// -// &.icon-only { -// color: inherit; -// font-size: 1.5em; -// -// &:hover, &:focus, &:active { -// color: #666; -// } -// } -//} -// -//form.link-like input[type="submit"]:hover, -//form.link-like input[type="submit"]:focus, -//form.link-like input[type="submit"]:active, -//form.link-like button[type="submit"]:hover, -//form.link-like button[type="submit"]:focus, -//form.link-like button[type="submit"]:active, -//input.link-like:hover, -//input.link-like:focus, -//input.link-like:active, -//button.link-like:hover, -//button.link-like:focus, -//button.link-like:active { -// text-decoration: underline; -// background: none; -// color: @colorLinkDefault; -//} -// -//.non-list-like-list { -// list-style-type: none; -// margin: 0; -// padding: 0.5em 0.5em 0; -// -// li { -// padding-bottom: 0.5em; -// } -//} -// -//form div.element ul.errors { -// .non-list-like-list; -// margin: 0.3em 0 0 0.6em; -// -// li { -// color: @colorCritical; -// font-weight: bold; -// } -//} -// -//form ul.form-errors { -// .non-list-like-list; -// margin-bottom: 1em; -// background-color: @colorCritical; -// -// ul.errors { -// .non-list-like-list; -// padding: 0; -// -// li:last-child { -// padding-bottom: 0; -// } -// } -// -// li { -// color: white; -// font-weight: bold; -// } -//} -// -//form ul.form-notifications { -// .non-list-like-list; -// margin-bottom: 1em; -// padding: 0; -// -// ul { -// .non-list-like-list; -// -// &.info { -// background-color: @colorFormNotificationInfo; -// } -// -// &.warning { -// background-color: @colorFormNotificationWarning; -// } -// -// &.error { -// background-color: @colorFormNotificationError; -// } -// } -// -// li { -// color: white; -// font-weight: bold; -// } -//} -// -//form div.element { -// margin-top: 0.5em; -// margin-bottom: 0.5em; -//} -// -//form label { -// display: inline-block; -// vertical-align: top; -// margin-top: 0.25em; -// margin-right: 1em; -// font-size: 0.9em; -// width: 10em; -//} -// -//form div.element i.help:before { -// margin-top: 0.25em; -//} -// -//label ~ * { -// vertical-align: top; -//} -// -//form dt { -// vertical-align: top; -//} -// -//select, input[type=text], textarea { -// width: 20em; -// display: inline-block; -//} -// -//textarea { -// height: 4em; -//} -// -//textarea.resource { -// &.ssh-identity { -// width: 50%; -// height: 25em; -// } -//} -// -//form .description { -// font-size: 0.8em; -// margin: 0.3em 0 0 0.6em; -//} -// -//.description { -// display: block; -//} -// -//select.grant-permissions { -// height: 20em; -// width: auto; -//} -// -//label ~ input, label ~ select, label ~ textarea { -// margin-left: 1.3em; -//} -// -//label + i ~ input, label + i ~ select, label + i ~ textarea { -// margin-left: 0; -//} -// -//button.noscript-apply { -// margin-left: 0.5em; -//} -// -//i.autosubmit-warning { -// display: inline-block; -// margin-left: 0.2em; -// margin-top: 0.25em; -// -// &:before { -// margin: 0; // Disables wobbling -// } -// -// &.spinning:before { -// content: '\e874'; // icon-spin6 -// .animate(spin 2s infinite linear); -// } -//} -// -//input[type=checkbox] + i.autosubmit-warning { -// margin-top: 0.15em; -//} -// -//html.no-js i.autosubmit-warning { -// .sr-only; -//} -// -//form ul.descriptions { -// .info-box; -// padding: 0.5em 0.5em 0 1.8em; -// -// li { -// padding-bottom: 0.5em; -// -// &:only-child { -// margin-left: -1.3em; -// list-style-type: none; -// } -// } -//} -// -//form ul.hints { -// .non-list-like-list; -// padding: 0.5em 0.5em 0 0.5em; -// -// li { -// font-size: 0.8em; -// padding-bottom: 0.5em; -// } -//} -// -//.control-group { -// & > * { -// float: left; -// margin-right: 0.5em; -// } -// -// div.element { -// margin-top: 0; -// margin-bottom: 0; -// } -// -// &:after { -// content: "."; -// visibility: hidden; -// display: block; -// height: 0; -// clear: both; -// } -//} +.flyover-content .control-label-group { + text-align: left; +} diff --git a/public/css/icinga/main.less b/public/css/icinga/main.less index 39bc13d0f..770bccd49 100644 --- a/public/css/icinga/main.less +++ b/public/css/icinga/main.less @@ -304,8 +304,11 @@ a:hover > .icon-cancel { // Collapsibles .collapsible.collapsed { - position: relative; overflow: hidden; +} + +.collapsible.collapsed:not([data-toggle-element]) { + position: relative; &:before, &:after { content: ""; @@ -328,7 +331,7 @@ a:hover > .icon-cancel { } } -.impact .collapsible.collapsed { +.impact .collapsible.collapsed:not([data-toggle-element]) { &:before { opacity: 0; } diff --git a/public/css/icinga/menu.less b/public/css/icinga/menu.less index d4c21f4ed..a7b0ff6bc 100644 --- a/public/css/icinga/menu.less +++ b/public/css/icinga/menu.less @@ -55,6 +55,10 @@ white-space: nowrap; } +#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item { + overflow: hidden; +} + #layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item > a { // Clip overflowing content overflow: hidden; @@ -208,13 +212,13 @@ ul:not(.nav-level-2) > .selected > a { } #menu input.search { - background: transparent url('../img/icons/search_white.png') no-repeat 0.7em center; + background: transparent url('../img/icons/search_white.png') no-repeat 1em center; background-size: 1em auto; border: none; - border-left: 5px solid transparent; color: @menu-color; line-height: 2.167em; - padding-left: @icon-width + 0.5em; + padding: .25em; + padding-left: @icon-width + .75em; width: 100%; &.active { @@ -411,10 +415,13 @@ input[type=text].search-input { color: @gray-light; } +.search-input ~ .search-reset { + opacity: 0; +} + .search-input:valid ~ .search-reset { - animation-duration: .4s; - animation-name: search-reset-in; display: block; + opacity: 1; } .search-input:invalid, @@ -424,17 +431,6 @@ input[type=text].search-input { box-shadow: none; } -@keyframes search-reset-in { - 0% { - opacity: 0; - transform: translate3d(-20%, 0, 0); - } - 100% { - opacity: 1; - transform: none; - } -} - // Toggle sidebar button #toggle-sidebar { // Reset button styles diff --git a/public/css/icinga/mixins.less b/public/css/icinga/mixins.less index dbda4a0f6..6cd08df04 100644 --- a/public/css/icinga/mixins.less +++ b/public/css/icinga/mixins.less @@ -6,26 +6,32 @@ box-shadow: @arguments; } -.button(@background-color: @body-bg-color, @color: @icinga-blue) { +.button(@background-color: @body-bg-color, @border-font-color: @icinga-blue, @color-dark: darken(@border-font-color, 10%)) { .rounded-corners(3px); background-color: @background-color; - border: 2px solid @color; - color: @color; + border: 2px solid @border-font-color; + color: @border-font-color; cursor: pointer; line-height: normal; outline: none; padding: @vertical-padding @horizontal-padding; - // Transition mixin does not work w/ comma-separated transitions - -webkit-transition: background 0.3s ease, color 0.3s ease; - -moz-transition: background 0.3s ease, color 0.3s ease; - -o-transition: background 0.3s ease, color 0.3s ease; - transition: background 0.3s ease, color 0.3s ease; + @duration: 0.2s; + // The trailing semicolon is needed to be able to pass this as a css list + .transition(background @duration, border @duration ease, color @duration ease;); &:focus, - &:hover { - background-color: @color; + &:hover, + &.btn-primary { + background-color: @border-font-color; + color: @background-color; + } + + &.btn-primary:focus, + &.btn-primary:hover { + background-color: @color-dark; + border-color: @color-dark; color: @background-color; } @@ -46,6 +52,13 @@ opacity: @opacity; } +.appearance(@appearance) { + -webkit-appearance: @appearance; + -moz-appearance: @appearance; + -ms-appearance: @appearance; + appearance: @appearance; +} + .transform(@transform) { -webkit-transform: @transform; -moz-transform: @transform; diff --git a/public/css/icinga/setup.less b/public/css/icinga/setup.less index 864ac55b6..a348c3144 100644 --- a/public/css/icinga/setup.less +++ b/public/css/icinga/setup.less @@ -6,11 +6,11 @@ #setup { - .header { + > .header { width: 100%; height: 5.5em; background-color: @icinga-blue; - border-bottom: 1px solid #d9d9d9d; + border-bottom: 1px solid #d9d9d9; text-align: center; @@ -124,6 +124,11 @@ } } +.setup-content .control-group > * { + display: inline-block; + margin-right: 1em; +} + #setup div.buttons { margin-top: 1.5em; // Yes, -top and -bottom, keep it like that... margin-bottom: 1.5em; @@ -133,9 +138,18 @@ left: -1337px; } + .control-button, + input[type="submit"] { + .button(); + } + button.finish, a.button-like.login { min-width: 25em; } + + .spinner { + margin-left: 1em; + } } #setup div.buttons + ul.hints { @@ -391,30 +405,35 @@ form#setup_requirements { #setup_modules { div.module { float: left; - //width: 15em; + width: 15em; height: 15em; margin: 1em; padding: 0.3em; border: 1px solid #ccc; background-color: snow; + .header { + height: 2.5em; + display: flex; + justify-content: space-between; + } + h3 { + margin: 0; border: none; - margin: 0.5em 0; - text-align: center; + overflow: hidden; + text-overflow: ellipsis; label { - margin: 0; - width: 15em; cursor: pointer; } } - h3 + label { + label.description { display: inline-block; - width: 13.5em; - height: 13.9em; - overflow: visible; + width: 14.4em; + height: 12em; + overflow: auto; cursor: pointer; font-weight: normal; } diff --git a/public/css/icinga/widgets.less b/public/css/icinga/widgets.less index b8c478a24..5dc5b2ebd 100644 --- a/public/css/icinga/widgets.less +++ b/public/css/icinga/widgets.less @@ -33,6 +33,8 @@ .message { display: inline-block; vertical-align: middle; + padding-right: 1.5em; + font-size: 7/6em; } } } @@ -45,7 +47,7 @@ margin-top: -0.75em; position: absolute; - right: 1em; + right: .75em; top: 50%; } @@ -186,41 +188,105 @@ table.multiselect tr[href] td { #main div.filter { form.editor { + max-width: 37em; + input[type=text], select { width: 12em; + height: 2em; + line-height: 1; } ul.tree li.active { - background-color: #eee; - } - - div.buttons { - float: right; + background-color: @gray-lightest; } button { border: none; background: none; } + + .buttons { + padding: .25em 0; + text-align: right; + } + + .buttons input { + .button(); + } + + .buttons input:not(:last-child) { + margin-right:.5em; + } } } -ul.tree select { /* ?? */ +form.role-form { + &.icinga-form .control-label-group { + width: 20em; + } + + .control-label-group em { + color: @text-color-light; + font-style: normal; + } + + fieldset.collapsible { + border: none; + padding: 0; + margin: 0; + + h3 em { + font-size: .857em; + font-weight: normal; + color: @text-color-light; + } + + h4 { + margin-top: 1.5em; + } + + .collapsible-control { + border-bottom: 1px solid @gray-light; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .collapsible-control:after { + content: "\f103"; + display: inline-block; + font-family: 'ifont'; + font-weight: normal; + padding: 0 .25em; + margin-right: .25em; + width: 1em; + opacity: .6; + float: right; + } + + &.collapsed .collapsible-control:after { + content: "\e87a"; + } + } +} + +ul.tree select:first-of-type { /* ?? */ margin-bottom: 0.3em; - margin-left: 1em; + margin-left: 2em; } ul.tree { padding: 0; margin: 0; -} - -ul.tree, ul.tree ul { - padding-left: 1.5em; + padding-top: .5em; } ul.tree ul { - padding-left: 1.5em; + padding-left: 1em; } ul.tree li { @@ -235,10 +301,10 @@ ul.tree li .handle { background-repeat: no-repeat; display: inline-block; position: absolute; - width: 1em; + width: 1.5em; height: 2em; - left: -0.6em; - background-position: -3px 3px; + left: 0em; + background-position: center center; z-index: 1; cursor: pointer; } @@ -264,9 +330,9 @@ ul.tree li::before, ul.tree li::after { /* This is the left vertical line */ ul.tree li::before { border-left-width: 1px; - top: 0; + top: -.5em; width: 1em; - height: 100%; + height: 2.5em; bottom: 1em; } @@ -274,13 +340,13 @@ ul.tree li::before { ul.tree li::after { border-top-width: 1px; top: 1em; - width: 1em; + width: 2em; height: 1em; } /* Stop left vertical line at "mid-height" after last nodes (at each level) */ ul.tree li:last-child::before { - height: 1em; + height: 1.5em; } /* No border for the root element - there must be only ONE root */ @@ -295,8 +361,8 @@ ul.tree > ul > li::before, ul.tree > ul > li::after { ul.tree li a { display: inline-block; - padding-left: 1em; line-height: 2em; + padding: 0 .5em; text-decoration: none; color: @gray; background-repeat: no-repeat; @@ -356,6 +422,8 @@ ul.tree li a.error:hover { border: 1px solid @gray-lighter; box-shadow: 0 0 .5em 0 rgba(0, 0, 0, 0.2); position: absolute; + padding: @vertical-padding @horizontal-padding; + .rounded-corners(); } &.flyover-arrow-top .flyover-content:before { diff --git a/public/img/select-icon-2x.png b/public/img/select-icon-2x.png new file mode 100644 index 000000000..8d24b106d Binary files /dev/null and b/public/img/select-icon-2x.png differ diff --git a/public/img/select-icon.png b/public/img/select-icon.png new file mode 100644 index 000000000..0cf513289 Binary files /dev/null and b/public/img/select-icon.png differ diff --git a/public/img/select-icon.svg b/public/img/select-icon.svg new file mode 100644 index 000000000..a0cfa17a0 --- /dev/null +++ b/public/img/select-icon.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/public/img/textarea-corner-2x.png b/public/img/textarea-corner-2x.png new file mode 100644 index 000000000..ee9cb50bd Binary files /dev/null and b/public/img/textarea-corner-2x.png differ diff --git a/public/img/textarea-corner.png b/public/img/textarea-corner.png new file mode 100644 index 000000000..3a2242c56 Binary files /dev/null and b/public/img/textarea-corner.png differ diff --git a/public/js/helpers.js b/public/js/helpers.js index 9747335ff..a7eae0a9c 100644 --- a/public/js/helpers.js +++ b/public/js/helpers.js @@ -5,6 +5,16 @@ 'use strict'; + /* Get data value or default */ + $.fn.getData = function (name, fallback) { + var value = this.data(name); + if (typeof value !== 'undefined') { + return value; + } + + return fallback; + }; + /* Whether a HTML tag has a specific attribute */ $.fn.hasAttr = function(name) { // We have inconsistent behaviour across browsers (false VS undef) diff --git a/public/js/icinga/behavior/collapsible.js b/public/js/icinga/behavior/collapsible.js index 205ec8d1a..1aa4c13d3 100644 --- a/public/js/icinga/behavior/collapsible.js +++ b/public/js/icinga/behavior/collapsible.js @@ -16,7 +16,8 @@ this.on('layout-change', this.onLayoutChange, this); this.on('rendered', '#layout', this.onRendered, this); - this.on('click', '.collapsible + .collapsible-control', this.onControlClicked, this); + this.on('click', '.collapsible + .collapsible-control, .collapsible > .collapsible-control', + this.onControlClicked, this); this.icinga = icinga; this.defaultVisibleRows = 2; @@ -45,7 +46,19 @@ // Assumes that any newly rendered elements are expanded if (_this.canCollapse($collapsible)) { - $collapsible.after($('#collapsible-control-ghost').clone().removeAttr('id')); + var toggleElement = $collapsible.data('toggleElement'); + if (!! toggleElement) { + var $toggle = $collapsible.children(toggleElement).first(); + if (! $toggle.length) { + _this.icinga.logger.error( + '[Collapsible] Control `' + toggleElement + '` not found in .collapsible', $collapsible); + } else if (! $toggle.is('.collapsible-control')) { + $toggle.addClass('collapsible-control'); + } + } else { + $collapsible.after($('#collapsible-control-ghost').clone().removeAttr('id')); + } + $collapsible.addClass('can-collapse'); if (! _this.state.has(_this.icinga.utils.getCSSPath($collapsible))) { @@ -119,7 +132,11 @@ Collapsible.prototype.onControlClicked = function(event) { var _this = event.data.self; var $target = $(event.currentTarget); + var $collapsible = $target.prev('.collapsible'); + if (! $collapsible.length) { + $collapsible = $target.parent('.collapsible'); + } if (! $collapsible.length) { _this.icinga.logger.error('[Collapsible] Collapsible control has no associated .collapsible: ', $target); @@ -150,7 +167,7 @@ if ($collapsible.is('table')) { return '> tbody > tr'; } else if ($collapsible.is('ul, ol')) { - return '> li'; + return '> li:not(.collapsible-control)'; } return ''; @@ -166,12 +183,12 @@ Collapsible.prototype.canCollapse = function($collapsible) { var rowSelector = this.getRowSelector($collapsible); if (!! rowSelector) { - var visibleRows = $collapsible.data('visibleRows') || this.defaultVisibleRows; + var visibleRows = $collapsible.getData('visibleRows', this.defaultVisibleRows); return $(rowSelector, $collapsible).length > visibleRows * 2; } else { - var actualHeight = $collapsible[0].scrollHeight; - var maxHeight = $collapsible.data('visibleHeight') || this.defaultVisibleHeight; + var actualHeight = $collapsible[0].scrollHeight - parseFloat($collapsible.css('padding-top')); + var maxHeight = $collapsible.getData('visibleHeight', this.defaultVisibleHeight); return actualHeight >= maxHeight * 2; } @@ -183,21 +200,40 @@ * @param $collapsible jQuery The given collapsible container element */ Collapsible.prototype.collapse = function($collapsible) { - $collapsible.addClass('collapsed'); + var height; var rowSelector = this.getRowSelector($collapsible); if (!! rowSelector) { - var $rows = $(rowSelector, $collapsible).slice(0, $collapsible.data('visibleRows') || this.defaultVisibleRows); + height = $collapsible[0].scrollHeight; + height -= parseFloat($collapsible.css('padding-bottom')); - var totalHeight = $rows.offset().top - $collapsible.offset().top; - $rows.outerHeight(function(_, height) { - totalHeight += height; + var $rows = $(rowSelector, $collapsible).slice( + $collapsible.getData('visibleRows', this.defaultVisibleRows) + ); + $rows.outerHeight(function (i, contentHeight) { + var $el = $(this); + var $prev = $el.prev(); + + if (i === 0 && ! $prev.length) { // very first element + height -= parseFloat($el.css('margin-top')) + contentHeight; + } else if (i < $rows.length - 1) { // every element but the last one + var prevBottomOffset = $prev.offset().top + $prev.outerHeight(); + height -= ($el.offset().top - prevBottomOffset) + contentHeight; + } else { // the last element + height -= $el.outerHeight(true); + } }); - - $collapsible.css({display: 'block', height: totalHeight}); } else { - $collapsible.css({display: 'block', height: $collapsible.data('visibleHeight') || this.defaultVisibleHeight}); + height = $collapsible.getData('visibleHeight', this.defaultVisibleHeight); + height += parseFloat($collapsible.css('padding-top')); + + if (!! $collapsible.data('toggleElement')) { + height += $collapsible.children($collapsible.data('toggleElement')).first().outerHeight(true); + } } + + $collapsible.css({display: 'block', height: height, paddingBottom: 0}); + $collapsible.addClass('collapsed'); }; /** @@ -207,7 +243,7 @@ */ Collapsible.prototype.expand = function($collapsible) { $collapsible.removeClass('collapsed'); - $collapsible.css({display: '', height: ''}); + $collapsible.css({display: '', height: '', paddingBottom: ''}); }; Icinga.Behaviors.Collapsible = Collapsible; diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index 5678b66e4..363ef5045 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -348,8 +348,8 @@ } // Show a spinner depending on how the form is being submitted - if (autosubmit && typeof $el !== 'undefined' && $el.next().hasClass('spinner')) { - $el.next().addClass('active'); + if (autosubmit && typeof $el !== 'undefined' && $el.siblings('.spinner').length) { + $el.siblings('.spinner').first().addClass('active'); } else if ($button.length && $button.is('button') && $button.hasClass('animated')) { $button.addClass('active'); } else if ($button.length && $button.attr('data-progress-label')) {