summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2017-10-11 13:39:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2017-10-11 13:39:08 +0000
commit0b88c682bba4f832641e3609be9c80691217e96d (patch)
tree53e9b18b2765ba089adb99a800541ac55b573e71
parentInitial commit. (diff)
downloadicingaweb2-module-director-0b88c682bba4f832641e3609be9c80691217e96d.zip
icingaweb2-module-director-0b88c682bba4f832641e3609be9c80691217e96d.tar.xz
Adding upstream version 1.3.0.upstream/1.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.gitignore7
-rw-r--r--.gitlab-ci.yml125
-rw-r--r--.travis.yml34
-rw-r--r--LICENSE339
-rw-r--r--README.md62
-rw-r--r--application/clicommands/BenchmarkCommand.php53
-rw-r--r--application/clicommands/CommandCommand.php15
-rw-r--r--application/clicommands/CommandsCommand.php14
-rw-r--r--application/clicommands/ConfigCommand.php131
-rw-r--r--application/clicommands/CoreCommand.php16
-rw-r--r--application/clicommands/EndpointCommand.php15
-rw-r--r--application/clicommands/HostCommand.php15
-rw-r--r--application/clicommands/HostgroupCommand.php15
-rw-r--r--application/clicommands/HostsCommand.php14
-rw-r--r--application/clicommands/HousekeepingCommand.php73
-rw-r--r--application/clicommands/JobsCommand.php78
-rw-r--r--application/clicommands/KickstartCommand.php87
-rw-r--r--application/clicommands/MigrationCommand.php66
-rw-r--r--application/clicommands/NotificationCommand.php15
-rw-r--r--application/clicommands/ServiceCommand.php41
-rw-r--r--application/clicommands/ServicesetCommand.php14
-rw-r--r--application/clicommands/TestCommand.php246
-rw-r--r--application/clicommands/TimeperiodCommand.php15
-rw-r--r--application/clicommands/UserCommand.php15
-rw-r--r--application/clicommands/UsergroupCommand.php15
-rw-r--r--application/clicommands/ZoneCommand.php15
-rw-r--r--application/controllers/ApiuserController.php9
-rw-r--r--application/controllers/ApiusersController.php9
-rw-r--r--application/controllers/CommandController.php45
-rw-r--r--application/controllers/CommandsController.php9
-rw-r--r--application/controllers/ConfigController.php399
-rw-r--r--application/controllers/DashboardController.php40
-rw-r--r--application/controllers/DataController.php188
-rw-r--r--application/controllers/DatafieldController.php46
-rw-r--r--application/controllers/DeploymentController.php45
-rw-r--r--application/controllers/EndpointController.php23
-rw-r--r--application/controllers/EndpointsController.php9
-rw-r--r--application/controllers/HostController.php410
-rw-r--r--application/controllers/HostgroupController.php9
-rw-r--r--application/controllers/HostgroupsController.php9
-rw-r--r--application/controllers/HostsController.php19
-rw-r--r--application/controllers/ImportrunController.php36
-rw-r--r--application/controllers/ImportsourceController.php185
-rw-r--r--application/controllers/IndexController.php57
-rw-r--r--application/controllers/InspectController.php130
-rw-r--r--application/controllers/JobController.php91
-rw-r--r--application/controllers/JobsController.php29
-rw-r--r--application/controllers/KickstartController.php21
-rw-r--r--application/controllers/ListController.php42
-rw-r--r--application/controllers/NotificationController.php61
-rw-r--r--application/controllers/NotificationsController.php13
-rw-r--r--application/controllers/SchemaController.php64
-rw-r--r--application/controllers/ScreenshotController.php38
-rw-r--r--application/controllers/ServiceController.php241
-rw-r--r--application/controllers/ServicegroupController.php9
-rw-r--r--application/controllers/ServicegroupsController.php14
-rw-r--r--application/controllers/ServicesController.php24
-rw-r--r--application/controllers/ServicesetController.php119
-rw-r--r--application/controllers/SettingsController.php19
-rw-r--r--application/controllers/ShowController.php250
-rw-r--r--application/controllers/SuggestController.php140
-rw-r--r--application/controllers/SyncpropertyController.php57
-rw-r--r--application/controllers/SyncruleController.php244
-rw-r--r--application/controllers/TimeperiodController.php48
-rw-r--r--application/controllers/TimeperiodsController.php9
-rw-r--r--application/controllers/UserController.php13
-rw-r--r--application/controllers/UsergroupController.php9
-rw-r--r--application/controllers/UsergroupsController.php9
-rw-r--r--application/controllers/UsersController.php13
-rw-r--r--application/controllers/ZoneController.php9
-rw-r--r--application/controllers/ZonesController.php9
-rw-r--r--application/forms/ApplyMigrationsForm.php54
-rw-r--r--application/forms/CustomvarForm.php26
-rw-r--r--application/forms/DeployConfigForm.php128
-rw-r--r--application/forms/DirectorDatafieldForm.php224
-rw-r--r--application/forms/DirectorDatalistForm.php45
-rw-r--r--application/forms/DirectorDatalistentryForm.php52
-rw-r--r--application/forms/DirectorJobForm.php141
-rw-r--r--application/forms/IcingaApiUserForm.php25
-rw-r--r--application/forms/IcingaCloneObjectForm.php70
-rw-r--r--application/forms/IcingaCommandArgumentForm.php170
-rw-r--r--application/forms/IcingaCommandForm.php98
-rw-r--r--application/forms/IcingaDeleteObjectForm.php41
-rw-r--r--application/forms/IcingaEndpointForm.php61
-rw-r--r--application/forms/IcingaHostForm.php231
-rw-r--r--application/forms/IcingaHostGroupForm.php38
-rw-r--r--application/forms/IcingaHostVarForm.php36
-rw-r--r--application/forms/IcingaImportObjectForm.php54
-rw-r--r--application/forms/IcingaMultiEditForm.php279
-rw-r--r--application/forms/IcingaNotificationForm.php259
-rw-r--r--application/forms/IcingaObjectFieldForm.php206
-rw-r--r--application/forms/IcingaServiceForm.php486
-rw-r--r--application/forms/IcingaServiceGroupForm.php38
-rw-r--r--application/forms/IcingaServiceSetForm.php138
-rw-r--r--application/forms/IcingaServiceVarForm.php36
-rw-r--r--application/forms/IcingaTimePeriodForm.php59
-rw-r--r--application/forms/IcingaTimePeriodRangeForm.php80
-rw-r--r--application/forms/IcingaUserForm.php161
-rw-r--r--application/forms/IcingaUserGroupForm.php40
-rw-r--r--application/forms/IcingaZoneForm.php43
-rw-r--r--application/forms/ImportCheckForm.php51
-rw-r--r--application/forms/ImportRowModifierForm.php149
-rw-r--r--application/forms/ImportRunForm.php51
-rw-r--r--application/forms/ImportSourceForm.php139
-rw-r--r--application/forms/KickstartForm.php405
-rw-r--r--application/forms/RestoreObjectForm.php58
-rw-r--r--application/forms/SettingsForm.php181
-rw-r--r--application/forms/SyncCheckForm.php66
-rw-r--r--application/forms/SyncPropertyForm.php401
-rw-r--r--application/forms/SyncRuleForm.php82
-rw-r--r--application/forms/SyncRunForm.php48
-rw-r--r--application/locale/de_DE/LC_MESSAGES/director.mobin0 -> 92674 bytes
-rw-r--r--application/locale/de_DE/LC_MESSAGES/director.po4468
-rw-r--r--application/locale/translateMe.php12
-rw-r--r--application/tables/ActivityLogTable.php205
-rw-r--r--application/tables/ConfigFileDiffTable.php151
-rw-r--r--application/tables/DatafieldTable.php121
-rw-r--r--application/tables/DatalistEntryTable.php64
-rw-r--r--application/tables/DatalistTable.php46
-rw-r--r--application/tables/DeploymentLogTable.php99
-rw-r--r--application/tables/GeneratedConfigFileTable.php101
-rw-r--r--application/tables/IcingaApiUserTable.php42
-rw-r--r--application/tables/IcingaAppliedServiceTable.php68
-rw-r--r--application/tables/IcingaCommandArgumentTable.php68
-rw-r--r--application/tables/IcingaCommandTable.php49
-rw-r--r--application/tables/IcingaCommandTemplateTable.php13
-rw-r--r--application/tables/IcingaEndpointTable.php74
-rw-r--r--application/tables/IcingaHostAppliedForServiceTable.php95
-rw-r--r--application/tables/IcingaHostAppliedServicesTable.php125
-rw-r--r--application/tables/IcingaHostGroupTable.php44
-rw-r--r--application/tables/IcingaHostServiceSetTable.php115
-rw-r--r--application/tables/IcingaHostServiceTable.php100
-rw-r--r--application/tables/IcingaHostTable.php71
-rw-r--r--application/tables/IcingaHostTemplateTable.php95
-rw-r--r--application/tables/IcingaHostVarTable.php49
-rw-r--r--application/tables/IcingaNotificationTable.php48
-rw-r--r--application/tables/IcingaNotificationTemplateTable.php7
-rw-r--r--application/tables/IcingaObjectDatafieldTable.php85
-rw-r--r--application/tables/IcingaServiceGroupTable.php44
-rw-r--r--application/tables/IcingaServiceSetHostTable.php71
-rw-r--r--application/tables/IcingaServiceSetServiceTable.php135
-rw-r--r--application/tables/IcingaServiceSetTable.php97
-rw-r--r--application/tables/IcingaServiceTable.php136
-rw-r--r--application/tables/IcingaServiceTemplateTable.php7
-rw-r--r--application/tables/IcingaServiceVarTable.php49
-rw-r--r--application/tables/IcingaTimePeriodRangeTable.php61
-rw-r--r--application/tables/IcingaTimePeriodTable.php43
-rw-r--r--application/tables/IcingaUserGroupTable.php44
-rw-r--r--application/tables/IcingaUserTable.php59
-rw-r--r--application/tables/IcingaUserTemplateTable.php13
-rw-r--r--application/tables/IcingaZoneTable.php54
-rw-r--r--application/tables/ImportedrowsTable.php78
-rw-r--r--application/tables/ImportrunTable.php78
-rw-r--r--application/tables/ImportsourceHookTable.php94
-rw-r--r--application/tables/ImportsourceTable.php61
-rw-r--r--application/tables/JobTable.php75
-rw-r--r--application/tables/PropertymodifierTable.php54
-rw-r--r--application/tables/SyncRunTable.php74
-rw-r--r--application/tables/SyncpropertyTable.php62
-rw-r--r--application/tables/SyncruleTable.php61
-rw-r--r--application/views/helpers/FormDataFilter.php475
-rw-r--r--application/views/helpers/FormExtensibleSet.php266
-rw-r--r--application/views/helpers/FormSimpleNote.php15
-rw-r--r--application/views/helpers/RenderPlainObject.php14
-rw-r--r--application/views/scripts/command/arguments.phtml11
-rw-r--r--application/views/scripts/config/diff.phtml40
-rw-r--r--application/views/scripts/config/file.phtml66
-rw-r--r--application/views/scripts/config/filediff.phtml11
-rw-r--r--application/views/scripts/config/files.phtml44
-rw-r--r--application/views/scripts/config/show.phtml38
-rw-r--r--application/views/scripts/config/store.phtml7
-rw-r--r--application/views/scripts/dashboard/index.phtml31
-rw-r--r--application/views/scripts/dashlets/default.phtml5
-rw-r--r--application/views/scripts/deployment/index.phtml81
-rw-r--r--application/views/scripts/host/agent.phtml47
-rw-r--r--application/views/scripts/host/services.phtml17
-rw-r--r--application/views/scripts/importrun/index.phtml16
-rw-r--r--application/views/scripts/importsource/history.phtml16
-rw-r--r--application/views/scripts/importsource/index.phtml44
-rw-r--r--application/views/scripts/inspect/object.phtml9
-rw-r--r--application/views/scripts/inspect/type.phtml31
-rw-r--r--application/views/scripts/inspect/types.phtml51
-rw-r--r--application/views/scripts/job/index.phtml44
-rw-r--r--application/views/scripts/kickstart/index.phtml15
-rw-r--r--application/views/scripts/list/importrun.phtml38
-rw-r--r--application/views/scripts/list/table.phtml21
-rw-r--r--application/views/scripts/object/deploymentLink.phtml44
-rw-r--r--application/views/scripts/object/fields.phtml12
-rw-r--r--application/views/scripts/object/form.phtml16
-rw-r--r--application/views/scripts/object/history.phtml9
-rw-r--r--application/views/scripts/object/show.phtml29
-rw-r--r--application/views/scripts/objects/form.phtml11
-rw-r--r--application/views/scripts/objects/table.phtml21
-rw-r--r--application/views/scripts/objects/tree.phtml70
-rw-r--r--application/views/scripts/schema/schema.phtml10
-rw-r--r--application/views/scripts/service/index.phtml22
-rw-r--r--application/views/scripts/settings/index.phtml7
-rw-r--r--application/views/scripts/show/activitylog.phtml90
-rw-r--r--application/views/scripts/suggest/index.phtml3
-rw-r--r--application/views/scripts/syncrule/history.phtml19
-rw-r--r--application/views/scripts/syncrule/index.phtml57
-rw-r--r--application/views/scripts/syncrule/runSummary.phtml55
-rw-r--r--application/views/scripts/syncrule/syncRunDetails.phtml14
-rw-r--r--configuration.php78
-rwxr-xr-xcontrib/docker-test.sh51
-rw-r--r--contrib/linux-agent-installer/Icinga2Agent.bash122
-rw-r--r--contrib/linux-agent-installer/Icinga2AgentHead.bash6
-rw-r--r--contrib/systemd/director-jobs.service10
-rw-r--r--contrib/windows-agent-installer/Icinga2Agent.psm1881
-rw-r--r--doc/01-Introduction.md51
-rw-r--r--doc/02-Installation.md74
-rw-r--r--doc/03-Automation.md134
-rw-r--r--doc/04-Getting-started.md60
-rw-r--r--doc/05-Upgrading.md144
-rw-r--r--doc/10-How-it-works.md107
-rw-r--r--doc/12-Handling-custom-variables.md13
-rw-r--r--doc/14-Fields-example-interfaces-array.md31
-rw-r--r--doc/15-Service-apply-for-example.md44
-rw-r--r--doc/16-Fields-example-SNMP.md104
-rw-r--r--doc/24-Working-with-agents.md80
-rw-r--r--doc/60-CLI.md431
-rw-r--r--doc/70-Import-and-Sync.md88
-rw-r--r--doc/70-REST-API.md510
-rw-r--r--doc/79-Jobs.md86
-rw-r--r--doc/80-FAQ.md75
-rw-r--r--doc/82-Changelog.md196
-rw-r--r--doc/91-Want-more.md19
-rw-r--r--doc/93-Testing.md304
-rw-r--r--doc/screenshot/director/08_import-and-sync/081_director_import_source.pngbin0 -> 77206 bytes
-rw-r--r--doc/screenshot/director/08_import-and-sync/082_director_import_modifier_lowercase.pngbin0 -> 27649 bytes
-rw-r--r--doc/screenshot/director/08_import-and-sync/083_director_import_modifier_sid.pngbin0 -> 31388 bytes
-rw-r--r--doc/screenshot/director/08_import-and-sync/084_director_import_modifier_regex.pngbin0 -> 40729 bytes
-rw-r--r--doc/screenshot/director/08_import-and-sync/085_director_import_preview.pngbin0 -> 32010 bytes
-rw-r--r--doc/screenshot/director/08_import-and-sync/086_director_sync_rule_ad_hosts.pngbin0 -> 17516 bytes
-rw-r--r--doc/screenshot/director/08_import-and-sync/087_director_sync_properties_ad_host.pngbin0 -> 49400 bytes
-rw-r--r--doc/screenshot/director/14_fields-for-interfaces/141_define_datafields.pngbin0 -> 36931 bytes
-rw-r--r--doc/screenshot/director/14_fields-for-interfaces/142_add_datafield.pngbin0 -> 22196 bytes
-rw-r--r--doc/screenshot/director/14_fields-for-interfaces/143_add_host_template.pngbin0 -> 41883 bytes
-rw-r--r--doc/screenshot/director/14_fields-for-interfaces/144_add_template_field.pngbin0 -> 38304 bytes
-rw-r--r--doc/screenshot/director/14_fields-for-interfaces/145_create_host.pngbin0 -> 45254 bytes
-rw-r--r--doc/screenshot/director/14_fields-for-interfaces/146_config_preview.pngbin0 -> 28049 bytes
-rw-r--r--doc/screenshot/director/15_apply-for-services/151_monitored_services.pngbin0 -> 58184 bytes
-rw-r--r--doc/screenshot/director/15_apply-for-services/152_add_service_template.pngbin0 -> 33134 bytes
-rw-r--r--doc/screenshot/director/15_apply-for-services/153_add_service_template_field.pngbin0 -> 53038 bytes
-rw-r--r--doc/screenshot/director/15_apply-for-services/154_create_apply_rule.pngbin0 -> 13927 bytes
-rw-r--r--doc/screenshot/director/15_apply-for-services/155_configure_apply_for.pngbin0 -> 35789 bytes
-rw-r--r--doc/screenshot/director/15_apply-for-services/156_config_preview.pngbin0 -> 34882 bytes
-rw-r--r--doc/screenshot/director/16_fields_snmp/161_snmp_versions_create_list.pngbin0 -> 27497 bytes
-rw-r--r--doc/screenshot/director/16_fields_snmp/162_snmp_versions_fill_list.pngbin0 -> 21504 bytes
-rw-r--r--doc/screenshot/director/16_fields_snmp/163_snmp_version_create_field.pngbin0 -> 44404 bytes
-rw-r--r--doc/screenshot/director/16_fields_snmp/164_snmp_fields_on_template.pngbin0 -> 88704 bytes
-rw-r--r--doc/screenshot/director/16_fields_snmp/165_host_snmp_choose.pngbin0 -> 50798 bytes
-rw-r--r--doc/screenshot/director/16_fields_snmp/166_host_snmp_v2c.pngbin0 -> 29789 bytes
-rw-r--r--doc/screenshot/director/16_fields_snmp/167_host_snmp_v3.pngbin0 -> 31781 bytes
-rw-r--r--doc/screenshot/director/16_fields_snmp/168_assign_snmp_check.pngbin0 -> 52955 bytes
-rw-r--r--doc/screenshot/director/24-agents/2401_agent_template.pngbin0 -> 40241 bytes
-rw-r--r--doc/screenshot/director/24-agents/2402_create_agent_based_host.pngbin0 -> 41833 bytes
-rw-r--r--doc/screenshot/director/24-agents/2403_show_agent_instructions_1.pngbin0 -> 85405 bytes
-rw-r--r--doc/screenshot/director/24-agents/2404_show_agent_instructions_2.pngbin0 -> 59309 bytes
-rw-r--r--doc/screenshot/director/24-agents/2405_agent_preview.pngbin0 -> 37837 bytes
-rw-r--r--doc/screenshot/director/24-agents/2406_agent_based_service.pngbin0 -> 41596 bytes
-rw-r--r--doc/screenshot/director/24-agents/2407_create_agent_based_load_check.pngbin0 -> 47195 bytes
-rw-r--r--doc/screenshot/director/24-agents/2409_agent_based_service_rendered_for_host.pngbin0 -> 22576 bytes
-rw-r--r--doc/screenshot/director/24-agents/2410_agent_based_service_rendered_for_host_template.pngbin0 -> 22440 bytes
-rw-r--r--doc/screenshot/director/24-agents/2411_assign_agent_based_service.pngbin0 -> 48232 bytes
-rw-r--r--doc/screenshot/director/93_testing/931_director_testing_duration.pngbin0 -> 58659 bytes
-rw-r--r--doc/screenshot/director/93_testing/932_director_testing_output_testdox.pngbin0 -> 148209 bytes
-rw-r--r--doc/screenshot/director/93_testing/933_director_testing_history.pngbin0 -> 102245 bytes
-rw-r--r--doc/screenshot/director/readme/director_main_screen.pngbin0 -> 144739 bytes
-rw-r--r--library/Director/Acl.php90
-rw-r--r--library/Director/Cli/Command.php94
-rw-r--r--library/Director/Cli/ObjectCommand.php423
-rw-r--r--library/Director/Cli/ObjectsCommand.php113
-rw-r--r--library/Director/ConfigDiff.php88
-rw-r--r--library/Director/Core/CoreApi.php682
-rw-r--r--library/Director/Core/DeploymentApiInterface.php75
-rw-r--r--library/Director/Core/LegacyDeploymentApi.php451
-rw-r--r--library/Director/Core/RestApiClient.php277
-rw-r--r--library/Director/Core/RestApiResponse.php127
-rw-r--r--library/Director/CoreBeta/ApiStream.php57
-rw-r--r--library/Director/CoreBeta/Stream.php17
-rw-r--r--library/Director/CoreBeta/StreamContext.php89
-rw-r--r--library/Director/CoreBeta/StreamContextSslOptions.php52
-rw-r--r--library/Director/CustomVariable/CustomVariable.php277
-rw-r--r--library/Director/CustomVariable/CustomVariableArray.php100
-rw-r--r--library/Director/CustomVariable/CustomVariableBoolean.php53
-rw-r--r--library/Director/CustomVariable/CustomVariableDictionary.php129
-rw-r--r--library/Director/CustomVariable/CustomVariableNull.php52
-rw-r--r--library/Director/CustomVariable/CustomVariableNumber.php73
-rw-r--r--library/Director/CustomVariable/CustomVariableString.php59
-rw-r--r--library/Director/CustomVariable/CustomVariables.php433
-rw-r--r--library/Director/Dashboard/Dashboard.php199
-rw-r--r--library/Director/Dashboard/Dashlet/ActivityLogDashlet.php35
-rw-r--r--library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php25
-rw-r--r--library/Director/Dashboard/Dashlet/CommandObjectDashlet.php25
-rw-r--r--library/Director/Dashboard/Dashlet/Dashlet.php295
-rw-r--r--library/Director/Dashboard/Dashlet/DatafieldDashlet.php30
-rw-r--r--library/Director/Dashboard/Dashlet/DatalistDashlet.php30
-rw-r--r--library/Director/Dashboard/Dashlet/DeploymentDashlet.php114
-rw-r--r--library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php63
-rw-r--r--library/Director/Dashboard/Dashlet/HostObjectDashlet.php20
-rw-r--r--library/Director/Dashboard/Dashlet/ImportSourceDashlet.php65
-rw-r--r--library/Director/Dashboard/Dashlet/InfrastructureDashlet.php30
-rw-r--r--library/Director/Dashboard/Dashlet/JobDashlet.php65
-rw-r--r--library/Director/Dashboard/Dashlet/KickstartDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/NotificationApplyDashlet.php38
-rw-r--r--library/Director/Dashboard/Dashlet/NotificationTemplateDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/NotificationsDashlet.php33
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php25
-rw-r--r--library/Director/Dashboard/Dashlet/SyncDashlet.php65
-rw-r--r--library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php28
-rw-r--r--library/Director/Dashboard/Dashlet/UserObjectDashlet.php23
-rw-r--r--library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php25
-rw-r--r--library/Director/Dashboard/DataDashboard.php18
-rw-r--r--library/Director/Dashboard/DeploymentDashboard.php18
-rw-r--r--library/Director/Dashboard/InfrastructureDashboard.php32
-rw-r--r--library/Director/Dashboard/NotificationsDashboard.php39
-rw-r--r--library/Director/Dashboard/ObjectsDashboard.php18
-rw-r--r--library/Director/Data/Db/DbConnection.php24
-rw-r--r--library/Director/Data/Db/DbObject.php1141
-rw-r--r--library/Director/Data/Db/DbObjectWithSettings.php133
-rw-r--r--library/Director/Data/Db/IcingaObjectFilterRenderer.php133
-rw-r--r--library/Director/Data/Db/IcingaObjectQuery.php261
-rw-r--r--library/Director/Data/PropertiesFilter.php25
-rw-r--r--library/Director/Data/PropertiesFilter/ArrayCustomVariablesFilter.php13
-rw-r--r--library/Director/Data/PropertiesFilter/CustomVariablesFilter.php13
-rw-r--r--library/Director/Data/ValueFilter.php10
-rw-r--r--library/Director/Data/ValueFilter/FilterBoolean.php19
-rw-r--r--library/Director/Data/ValueFilter/FilterInt.php21
-rw-r--r--library/Director/DataType/DataTypeArray.php14
-rw-r--r--library/Director/DataType/DataTypeBoolean.php30
-rw-r--r--library/Director/DataType/DataTypeDatalist.php47
-rw-r--r--library/Director/DataType/DataTypeDirectorObject.php79
-rw-r--r--library/Director/DataType/DataTypeNumber.php19
-rw-r--r--library/Director/DataType/DataTypeSqlQuery.php76
-rw-r--r--library/Director/DataType/DataTypeString.php16
-rw-r--r--library/Director/DataType/DataTypeTime.php16
-rw-r--r--library/Director/Db.php724
-rw-r--r--library/Director/Db/Cache/CustomVariableCache.php80
-rw-r--r--library/Director/Db/Cache/GroupMembershipCache.php101
-rw-r--r--library/Director/Db/Cache/PrefetchCache.php146
-rw-r--r--library/Director/Db/Housekeeping.php192
-rw-r--r--library/Director/Db/Migration.php60
-rw-r--r--library/Director/Db/Migrations.php161
-rw-r--r--library/Director/Exception/DuplicateKeyException.php9
-rw-r--r--library/Director/Exception/NestingError.php9
-rw-r--r--library/Director/Hook/DataTypeHook.php59
-rw-r--r--library/Director/Hook/ImportSourceHook.php111
-rw-r--r--library/Director/Hook/JobHook.php108
-rw-r--r--library/Director/Hook/PropertyModifierHook.php99
-rw-r--r--library/Director/Hook/ShipConfigFilesHook.php11
-rw-r--r--library/Director/IcingaConfig/AgentWizard.php261
-rw-r--r--library/Director/IcingaConfig/AssignRenderer.php192
-rw-r--r--library/Director/IcingaConfig/ExtensibleSet.php568
-rw-r--r--library/Director/IcingaConfig/IcingaConfig.php827
-rw-r--r--library/Director/IcingaConfig/IcingaConfigFile.php168
-rw-r--r--library/Director/IcingaConfig/IcingaConfigHelper.php285
-rw-r--r--library/Director/IcingaConfig/IcingaConfigRendered.php34
-rw-r--r--library/Director/IcingaConfig/IcingaConfigRenderer.php10
-rw-r--r--library/Director/IcingaConfig/IcingaLegacyConfigHelper.php108
-rw-r--r--library/Director/IcingaConfig/StateFilterSet.php31
-rw-r--r--library/Director/IcingaConfig/TypeFilterSet.php39
-rw-r--r--library/Director/Import/Import.php464
-rw-r--r--library/Director/Import/ImportSourceCoreApi.php95
-rw-r--r--library/Director/Import/ImportSourceLdap.php87
-rw-r--r--library/Director/Import/ImportSourceSql.php43
-rw-r--r--library/Director/Import/PurgeStrategy/ImportRunBasedPurgeStrategy.php92
-rw-r--r--library/Director/Import/PurgeStrategy/PurgeNothingPurgeStrategy.php11
-rw-r--r--library/Director/Import/PurgeStrategy/PurgeStrategy.php31
-rw-r--r--library/Director/Import/Sync.php624
-rw-r--r--library/Director/Import/SyncUtils.php130
-rw-r--r--library/Director/Job/ConfigJob.php221
-rw-r--r--library/Director/Job/HousekeepingJob.php39
-rw-r--r--library/Director/Job/ImportJob.php88
-rw-r--r--library/Director/Job/JobRunner.php51
-rw-r--r--library/Director/Job/SyncJob.php97
-rw-r--r--library/Director/KickstartHelper.php387
-rw-r--r--library/Director/Monitoring.php71
-rw-r--r--library/Director/Objects/DirectorActivityLog.php144
-rw-r--r--library/Director/Objects/DirectorDatafield.php119
-rw-r--r--library/Director/Objects/DirectorDatalist.php20
-rw-r--r--library/Director/Objects/DirectorDatalistEntry.php59
-rw-r--r--library/Director/Objects/DirectorDeploymentLog.php106
-rw-r--r--library/Director/Objects/DirectorJob.php112
-rw-r--r--library/Director/Objects/HostApplyMatches.php141
-rw-r--r--library/Director/Objects/IcingaApiUser.php28
-rw-r--r--library/Director/Objects/IcingaArguments.php401
-rw-r--r--library/Director/Objects/IcingaCommand.php200
-rw-r--r--library/Director/Objects/IcingaCommandArgument.php198
-rw-r--r--library/Director/Objects/IcingaCommandField.php17
-rw-r--r--library/Director/Objects/IcingaEndpoint.php104
-rw-r--r--library/Director/Objects/IcingaFlatVar.php56
-rw-r--r--library/Director/Objects/IcingaHost.php376
-rw-r--r--library/Director/Objects/IcingaHostField.php17
-rw-r--r--library/Director/Objects/IcingaHostGroup.php123
-rw-r--r--library/Director/Objects/IcingaHostGroupAssignment.php20
-rw-r--r--library/Director/Objects/IcingaHostVar.php29
-rw-r--r--library/Director/Objects/IcingaNotification.php175
-rw-r--r--library/Director/Objects/IcingaNotificationField.php17
-rw-r--r--library/Director/Objects/IcingaObject.php2673
-rw-r--r--library/Director/Objects/IcingaObjectField.php28
-rw-r--r--library/Director/Objects/IcingaObjectGroup.php57
-rw-r--r--library/Director/Objects/IcingaObjectGroups.php384
-rw-r--r--library/Director/Objects/IcingaObjectImports.php394
-rw-r--r--library/Director/Objects/IcingaObjectLegacyAssignments.php79
-rw-r--r--library/Director/Objects/IcingaObjectMultiRelations.php425
-rw-r--r--library/Director/Objects/IcingaRelatedObject.php211
-rw-r--r--library/Director/Objects/IcingaService.php490
-rw-r--r--library/Director/Objects/IcingaServiceAssignment.php20
-rw-r--r--library/Director/Objects/IcingaServiceField.php17
-rw-r--r--library/Director/Objects/IcingaServiceGroup.php13
-rw-r--r--library/Director/Objects/IcingaServiceSet.php263
-rw-r--r--library/Director/Objects/IcingaServiceSetAssignment.php20
-rw-r--r--library/Director/Objects/IcingaServiceVar.php29
-rw-r--r--library/Director/Objects/IcingaTemplateResolver.php436
-rw-r--r--library/Director/Objects/IcingaTimePeriod.php72
-rw-r--r--library/Director/Objects/IcingaTimePeriodRange.php88
-rw-r--r--library/Director/Objects/IcingaTimePeriodRanges.php311
-rw-r--r--library/Director/Objects/IcingaUser.php43
-rw-r--r--library/Director/Objects/IcingaUserField.php17
-rw-r--r--library/Director/Objects/IcingaUserGroup.php23
-rw-r--r--library/Director/Objects/IcingaVar.php67
-rw-r--r--library/Director/Objects/IcingaZone.php80
-rw-r--r--library/Director/Objects/ImportRowModifier.php42
-rw-r--r--library/Director/Objects/ImportRun.php139
-rw-r--r--library/Director/Objects/ImportSource.php233
-rw-r--r--library/Director/Objects/SyncProperty.php25
-rw-r--r--library/Director/Objects/SyncRule.php373
-rw-r--r--library/Director/Objects/SyncRun.php39
-rw-r--r--library/Director/PlainObjectRenderer.php130
-rw-r--r--library/Director/PropertyModifier/PropertyModifierBitmask.php35
-rw-r--r--library/Director/PropertyModifier/PropertyModifierDnsRecords.php108
-rw-r--r--library/Director/PropertyModifier/PropertyModifierExtractFromDN.php99
-rw-r--r--library/Director/PropertyModifier/PropertyModifierFromAdSid.php32
-rw-r--r--library/Director/PropertyModifier/PropertyModifierFromLatin1.php19
-rw-r--r--library/Director/PropertyModifier/PropertyModifierGetHostByName.php50
-rw-r--r--library/Director/PropertyModifier/PropertyModifierJoin.php34
-rw-r--r--library/Director/PropertyModifier/PropertyModifierJsonDecode.php76
-rw-r--r--library/Director/PropertyModifier/PropertyModifierLowercase.php13
-rw-r--r--library/Director/PropertyModifier/PropertyModifierMakeBoolean.php90
-rw-r--r--library/Director/PropertyModifier/PropertyModifierMap.php88
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRegexReplace.php41
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRegexSplit.php51
-rw-r--r--library/Director/PropertyModifier/PropertyModifierReplace.php32
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSplit.php51
-rw-r--r--library/Director/PropertyModifier/PropertyModifierStripDomain.php34
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSubstring.php38
-rw-r--r--library/Director/PropertyModifier/PropertyModifierToInt.php24
-rw-r--r--library/Director/PropertyModifier/PropertyModifierUppercase.php13
-rw-r--r--library/Director/ProvidedHook/CubeLinks.php65
-rw-r--r--library/Director/ProvidedHook/Monitoring/HostActions.php59
-rw-r--r--library/Director/ProvidedHook/Monitoring/ServiceActions.php65
-rw-r--r--library/Director/Settings.php150
-rw-r--r--library/Director/StartupLogRenderer.php97
-rw-r--r--library/Director/Test/BaseTestCase.php103
-rw-r--r--library/Director/Test/Bootstrap.php28
-rw-r--r--library/Director/Test/IcingaObjectTestCase.php92
-rw-r--r--library/Director/Test/TestProcess.php116
-rw-r--r--library/Director/Test/TestSuite.php68
-rw-r--r--library/Director/Test/TestSuiteLint.php56
-rw-r--r--library/Director/Test/TestSuiteStyle.php66
-rw-r--r--library/Director/Test/TestSuiteUnit.php26
-rw-r--r--library/Director/Test/Web/Form/TestDirectorObjectForm.php19
-rw-r--r--library/Director/Util.php192
-rw-r--r--library/Director/Web/Controller/ActionController.php411
-rw-r--r--library/Director/Web/Controller/NewObjectsController.php295
-rw-r--r--library/Director/Web/Controller/ObjectController.php485
-rw-r--r--library/Director/Web/Controller/ObjectsController.php288
-rw-r--r--library/Director/Web/Form/CsrfToken.php53
-rw-r--r--library/Director/Web/Form/Decorator/ViewHelperRaw.php14
-rw-r--r--library/Director/Web/Form/DirectorObjectForm.php1414
-rw-r--r--library/Director/Web/Form/Element/Boolean.php80
-rw-r--r--library/Director/Web/Form/Element/DataFilter.php337
-rw-r--r--library/Director/Web/Form/Element/ExtensibleSet.php85
-rw-r--r--library/Director/Web/Form/Element/FormElement.php9
-rw-r--r--library/Director/Web/Form/Element/OptionalYesNo.php22
-rw-r--r--library/Director/Web/Form/Element/SimpleNote.php22
-rw-r--r--library/Director/Web/Form/Element/YesNo.php14
-rw-r--r--library/Director/Web/Form/FormLoader.php37
-rw-r--r--library/Director/Web/Form/IcingaObjectFieldLoader.php536
-rw-r--r--library/Director/Web/Form/IconHelper.php86
-rw-r--r--library/Director/Web/Form/QuickBaseForm.php166
-rw-r--r--library/Director/Web/Form/QuickForm.php499
-rw-r--r--library/Director/Web/Form/QuickSubForm.php35
-rw-r--r--library/Director/Web/Navigation/Renderer/ConfigHealthItemRenderer.php122
-rw-r--r--library/Director/Web/Table/IcingaObjectTable.php27
-rw-r--r--library/Director/Web/Table/QuickTable.php545
-rw-r--r--library/Director/Web/Table/TableLoader.php34
-rw-r--r--library/vendor/php-diff/README.md65
-rw-r--r--library/vendor/php-diff/SOURCE9
-rw-r--r--library/vendor/php-diff/lib/Diff.php179
-rw-r--r--library/vendor/php-diff/lib/Diff/Renderer/Abstract.php82
-rw-r--r--library/vendor/php-diff/lib/Diff/Renderer/Html/Array.php231
-rw-r--r--library/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php143
-rw-r--r--library/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php163
-rw-r--r--library/vendor/php-diff/lib/Diff/Renderer/Text/Context.php128
-rw-r--r--library/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php87
-rw-r--r--library/vendor/php-diff/lib/Diff/SequenceMatcher.php742
-rw-r--r--module.info6
-rw-r--r--phpunit.xml18
-rw-r--r--public/css/module.less1453
-rw-r--r--public/img/globe.pngbin0 -> 882 bytes
-rw-r--r--public/img/leaf.gifbin0 -> 945 bytes
-rw-r--r--public/img/script.pngbin0 -> 471 bytes
-rw-r--r--public/img/server.pngbin0 -> 423 bytes
-rw-r--r--public/img/service.pngbin0 -> 601 bytes
-rw-r--r--public/img/tree.pngbin0 -> 524 bytes
-rw-r--r--public/js/module.js732
-rw-r--r--run.php47
-rw-r--r--schema/mysql-legacy-changes/upgrade_1.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_10.sql14
-rw-r--r--schema/mysql-legacy-changes/upgrade_11.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_12.sql17
-rw-r--r--schema/mysql-legacy-changes/upgrade_13.sql18
-rw-r--r--schema/mysql-legacy-changes/upgrade_14.sql18
-rw-r--r--schema/mysql-legacy-changes/upgrade_15.sql17
-rw-r--r--schema/mysql-legacy-changes/upgrade_16.sql17
-rw-r--r--schema/mysql-legacy-changes/upgrade_17.sql17
-rw-r--r--schema/mysql-legacy-changes/upgrade_18.sql17
-rw-r--r--schema/mysql-legacy-changes/upgrade_19.sql17
-rw-r--r--schema/mysql-legacy-changes/upgrade_2.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_20.sql17
-rw-r--r--schema/mysql-legacy-changes/upgrade_21.sql15
-rw-r--r--schema/mysql-legacy-changes/upgrade_22.sql43
-rw-r--r--schema/mysql-legacy-changes/upgrade_23.sql51
-rw-r--r--schema/mysql-legacy-changes/upgrade_24.sql91
-rw-r--r--schema/mysql-legacy-changes/upgrade_25.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_26.sql4
-rw-r--r--schema/mysql-legacy-changes/upgrade_27.sql58
-rw-r--r--schema/mysql-legacy-changes/upgrade_28.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_29.sql16
-rw-r--r--schema/mysql-legacy-changes/upgrade_3.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_30.sql8
-rw-r--r--schema/mysql-legacy-changes/upgrade_31.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_32.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_33.sql11
-rw-r--r--schema/mysql-legacy-changes/upgrade_34.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_35.sql6
-rw-r--r--schema/mysql-legacy-changes/upgrade_36.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_37.sql17
-rw-r--r--schema/mysql-legacy-changes/upgrade_38.sql4
-rw-r--r--schema/mysql-legacy-changes/upgrade_39.sql5
-rw-r--r--schema/mysql-legacy-changes/upgrade_4.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_40.sql9
-rw-r--r--schema/mysql-legacy-changes/upgrade_41.sql19
-rw-r--r--schema/mysql-legacy-changes/upgrade_42.sql7
-rw-r--r--schema/mysql-legacy-changes/upgrade_43.sql13
-rw-r--r--schema/mysql-legacy-changes/upgrade_44.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_45.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_46.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_47.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_48.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_49.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_5.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_50.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_51.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_52.sql5
-rw-r--r--schema/mysql-legacy-changes/upgrade_53.sql9
-rw-r--r--schema/mysql-legacy-changes/upgrade_54.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_55.sql8
-rw-r--r--schema/mysql-legacy-changes/upgrade_56.sql13
-rw-r--r--schema/mysql-legacy-changes/upgrade_57.sql20
-rw-r--r--schema/mysql-legacy-changes/upgrade_58.sql5
-rw-r--r--schema/mysql-legacy-changes/upgrade_59.sql3
-rw-r--r--schema/mysql-legacy-changes/upgrade_6.sql5
-rw-r--r--schema/mysql-legacy-changes/upgrade_60.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_61.sql2
-rw-r--r--schema/mysql-legacy-changes/upgrade_62.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_7.sql1
-rw-r--r--schema/mysql-legacy-changes/upgrade_8.sql36
-rw-r--r--schema/mysql-legacy-changes/upgrade_9.sql1
-rw-r--r--schema/mysql-migrations/upgrade_100.sql6
-rw-r--r--schema/mysql-migrations/upgrade_101.sql7
-rw-r--r--schema/mysql-migrations/upgrade_102.sql13
-rw-r--r--schema/mysql-migrations/upgrade_103.sql12
-rw-r--r--schema/mysql-migrations/upgrade_104.sql19
-rw-r--r--schema/mysql-migrations/upgrade_105.sql6
-rw-r--r--schema/mysql-migrations/upgrade_107.sql9
-rw-r--r--schema/mysql-migrations/upgrade_108.sql18
-rw-r--r--schema/mysql-migrations/upgrade_109.sql16
-rw-r--r--schema/mysql-migrations/upgrade_110.sql104
-rw-r--r--schema/mysql-migrations/upgrade_112.sql6
-rw-r--r--schema/mysql-migrations/upgrade_114.sql55
-rw-r--r--schema/mysql-migrations/upgrade_115.sql23
-rw-r--r--schema/mysql-migrations/upgrade_116.sql18
-rw-r--r--schema/mysql-migrations/upgrade_117.sql20
-rw-r--r--schema/mysql-migrations/upgrade_119.sql6
-rw-r--r--schema/mysql-migrations/upgrade_120.sql184
-rw-r--r--schema/mysql-migrations/upgrade_121.sql8
-rw-r--r--schema/mysql-migrations/upgrade_122.sql12
-rw-r--r--schema/mysql-migrations/upgrade_123.sql30
-rw-r--r--schema/mysql-migrations/upgrade_124.sql3
-rw-r--r--schema/mysql-migrations/upgrade_125.sql18
-rw-r--r--schema/mysql-migrations/upgrade_126.sql217
-rw-r--r--schema/mysql-migrations/upgrade_127.sql152
-rw-r--r--schema/mysql-migrations/upgrade_128.sql6
-rw-r--r--schema/mysql-migrations/upgrade_63.sql12
-rw-r--r--schema/mysql-migrations/upgrade_64.sql10
-rw-r--r--schema/mysql-migrations/upgrade_65.sql37
-rw-r--r--schema/mysql-migrations/upgrade_66.sql37
-rw-r--r--schema/mysql-migrations/upgrade_67.sql23
-rw-r--r--schema/mysql-migrations/upgrade_68.sql6
-rw-r--r--schema/mysql-migrations/upgrade_69.sql9
-rw-r--r--schema/mysql-migrations/upgrade_70.sql13
-rw-r--r--schema/mysql-migrations/upgrade_71.sql44
-rw-r--r--schema/mysql-migrations/upgrade_72.sql14
-rw-r--r--schema/mysql-migrations/upgrade_73.sql50
-rw-r--r--schema/mysql-migrations/upgrade_74.sql14
-rw-r--r--schema/mysql-migrations/upgrade_75.sql50
-rw-r--r--schema/mysql-migrations/upgrade_76.sql28
-rw-r--r--schema/mysql-migrations/upgrade_77.sql78
-rw-r--r--schema/mysql-migrations/upgrade_78.sql20
-rw-r--r--schema/mysql-migrations/upgrade_82.sql17
-rw-r--r--schema/mysql-migrations/upgrade_84.sql5
-rw-r--r--schema/mysql-migrations/upgrade_85.sql15
-rw-r--r--schema/mysql-migrations/upgrade_86.sql35
-rw-r--r--schema/mysql-migrations/upgrade_87.sql6
-rw-r--r--schema/mysql-migrations/upgrade_89.sql6
-rw-r--r--schema/mysql-migrations/upgrade_90.sql5
-rw-r--r--schema/mysql-migrations/upgrade_91.sql5
-rw-r--r--schema/mysql-migrations/upgrade_92.sql27
-rw-r--r--schema/mysql-migrations/upgrade_93.sql22
-rw-r--r--schema/mysql-migrations/upgrade_94.sql29
-rw-r--r--schema/mysql-migrations/upgrade_95.sql22
-rw-r--r--schema/mysql-migrations/upgrade_96.sql5
-rw-r--r--schema/mysql-migrations/upgrade_97.sql11
-rw-r--r--schema/mysql.sql1511
-rw-r--r--schema/pgsql-legacy-changes/upgrade-10.sql20
-rw-r--r--schema/pgsql-legacy-changes/upgrade-11.sql20
-rw-r--r--schema/pgsql-legacy-changes/upgrade-2.sql2
-rw-r--r--schema/pgsql-legacy-changes/upgrade-3.sql21
-rw-r--r--schema/pgsql-legacy-changes/upgrade-4.sql20
-rw-r--r--schema/pgsql-legacy-changes/upgrade-5.sql20
-rw-r--r--schema/pgsql-legacy-changes/upgrade-6.sql20
-rw-r--r--schema/pgsql-legacy-changes/upgrade-7.sql20
-rw-r--r--schema/pgsql-legacy-changes/upgrade-8.sql20
-rw-r--r--schema/pgsql-legacy-changes/upgrade-9.sql20
-rw-r--r--schema/pgsql-legacy-changes/upgrade_1.sql13
-rw-r--r--schema/pgsql-legacy-changes/upgrade_21.sql17
-rw-r--r--schema/pgsql-legacy-changes/upgrade_22.sql55
-rw-r--r--schema/pgsql-legacy-changes/upgrade_23.sql60
-rw-r--r--schema/pgsql-legacy-changes/upgrade_34.sql189
-rw-r--r--schema/pgsql-legacy-changes/upgrade_35.sql2
-rw-r--r--schema/pgsql-migrations/upgrade_100.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_101.sql9
-rw-r--r--schema/pgsql-migrations/upgrade_102.sql13
-rw-r--r--schema/pgsql-migrations/upgrade_103.sql11
-rw-r--r--schema/pgsql-migrations/upgrade_104.sql19
-rw-r--r--schema/pgsql-migrations/upgrade_105.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_106.sql9
-rw-r--r--schema/pgsql-migrations/upgrade_107.sql9
-rw-r--r--schema/pgsql-migrations/upgrade_109.sql16
-rw-r--r--schema/pgsql-migrations/upgrade_110.sql104
-rw-r--r--schema/pgsql-migrations/upgrade_111.sql11
-rw-r--r--schema/pgsql-migrations/upgrade_113.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_114.sql63
-rw-r--r--schema/pgsql-migrations/upgrade_115.sql28
-rw-r--r--schema/pgsql-migrations/upgrade_116.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_117.sql26
-rw-r--r--schema/pgsql-migrations/upgrade_119.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_120.sql201
-rw-r--r--schema/pgsql-migrations/upgrade_121.sql8
-rw-r--r--schema/pgsql-migrations/upgrade_122.sql12
-rw-r--r--schema/pgsql-migrations/upgrade_123.sql34
-rw-r--r--schema/pgsql-migrations/upgrade_124.sql21
-rw-r--r--schema/pgsql-migrations/upgrade_125.sql18
-rw-r--r--schema/pgsql-migrations/upgrade_127.sql197
-rw-r--r--schema/pgsql-migrations/upgrade_128.sql5
-rw-r--r--schema/pgsql-migrations/upgrade_77.sql72
-rw-r--r--schema/pgsql-migrations/upgrade_78.sql25
-rw-r--r--schema/pgsql-migrations/upgrade_79.sql11
-rw-r--r--schema/pgsql-migrations/upgrade_80.sql11
-rw-r--r--schema/pgsql-migrations/upgrade_81.sql5
-rw-r--r--schema/pgsql-migrations/upgrade_82.sql12
-rw-r--r--schema/pgsql-migrations/upgrade_83.sql7
-rw-r--r--schema/pgsql-migrations/upgrade_84.sql5
-rw-r--r--schema/pgsql-migrations/upgrade_85.sql15
-rw-r--r--schema/pgsql-migrations/upgrade_86.sql35
-rw-r--r--schema/pgsql-migrations/upgrade_88.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_89.sql5
-rw-r--r--schema/pgsql-migrations/upgrade_90.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_91.sql5
-rw-r--r--schema/pgsql-migrations/upgrade_92.sql27
-rw-r--r--schema/pgsql-migrations/upgrade_93.sql24
-rw-r--r--schema/pgsql-migrations/upgrade_94.sql34
-rw-r--r--schema/pgsql-migrations/upgrade_95.sql20
-rw-r--r--schema/pgsql-migrations/upgrade_96.sql7
-rw-r--r--schema/pgsql-migrations/upgrade_97.sql11
-rw-r--r--schema/pgsql-migrations/upgrade_98.sql7
-rw-r--r--schema/pgsql.sql1778
-rw-r--r--test/bootstrap.php9
-rw-r--r--test/config/authentication.ini0
-rw-r--r--test/config/config.ini0
-rw-r--r--test/config/resources.ini13
-rw-r--r--test/php/library/Director/CustomVariable/CustomVariablesTest.php74
-rw-r--r--test/php/library/Director/IcingaConfig/AssignRendererTest.php49
-rw-r--r--test/php/library/Director/IcingaConfig/ExtensibleSetTest.php162
-rw-r--r--test/php/library/Director/IcingaConfig/IcingaConfigHelperTest.php106
-rw-r--r--test/php/library/Director/IcingaConfig/StateFilterTest.php171
-rw-r--r--test/php/library/Director/IcingaConfig/rendered/dict1.out6
-rw-r--r--test/php/library/Director/Objects/IcingaCommandTest.php215
-rw-r--r--test/php/library/Director/Objects/IcingaHostTest.php729
-rw-r--r--test/php/library/Director/Objects/IcingaNotificationTest.php248
-rw-r--r--test/php/library/Director/Objects/IcingaServiceSetTest.php189
-rw-r--r--test/php/library/Director/Objects/IcingaServiceTest.php289
-rw-r--r--test/php/library/Director/Objects/IcingaTemplateResolverTest.php158
-rw-r--r--test/php/library/Director/Objects/IcingaTimePeriodTest.php113
-rw-r--r--test/php/library/Director/Objects/rendered/command1.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command2.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command3.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command4.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command5.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command6.out4
-rw-r--r--test/php/library/Director/Objects/rendered/command7.out9
-rw-r--r--test/php/library/Director/Objects/rendered/host1.out12
-rw-r--r--test/php/library/Director/Objects/rendered/host2.out17
-rw-r--r--test/php/library/Director/Objects/rendered/host3.out14
-rw-r--r--test/php/library/Director/Objects/rendered/notification1.out4
-rw-r--r--test/php/library/Director/Objects/rendered/service1.out14
-rw-r--r--test/php/library/Director/Objects/rendered/service2.out16
-rw-r--r--test/php/library/Director/Objects/rendered/service3.out15
-rw-r--r--test/php/library/Director/Objects/rendered/service4.out12
-rw-r--r--test/php/library/Director/Objects/rendered/service5.out14
-rw-r--r--test/php/library/Director/Objects/rendered/service6.out15
-rw-r--r--test/php/library/Director/Objects/rendered/service7.out14
-rwxr-xr-xtest/travis-prepare.sh20
726 files changed, 69064 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..99b4353
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+## Editors
+/.idea/
+.*.sw[op]
+
+## PHP / composer
+/vendor/
+composer.lock
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..539893e
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,125 @@
+stages:
+- Coding Standards
+- Unit-Tests with DB
+
+PSR2 CS Test:
+ stage: Coding Standards
+ tags:
+ - xenial
+ script:
+ - phpcs --report-width=auto --report-full --report-gitblame --report-summary -p --standard=PSR2 --extensions=php --encoding=utf-8 -w -s library/Director/ application/ configuration.php run.php test
+
+CentOS 7/MySQL:
+ stage: Unit-Tests with DB
+ tags:
+ - centos7
+ - director
+ variables:
+ DIRECTOR_TESTDB: "director_test_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ DIRECTOR_TESTDB_RES: "Director MySQL TestDB"
+ before_script:
+ - mysql -u root -e "CREATE DATABASE $DIRECTOR_TESTDB"
+ after_script:
+ - mysql -u root -e "DROP DATABASE $DIRECTOR_TESTDB"
+ script:
+ - phpunit
+
+CentOS 7/PostgreSQL:
+ stage: Unit-Tests with DB
+ tags:
+ - centos7
+ - director
+ variables:
+ DIRECTOR_TESTDB: "director_test_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ DIRECTOR_TESTDB_RES: "Director PostgreSQL TestDB"
+ DIRECTOR_TESTDB_USER: "director_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ before_script:
+ - psql postgres -q -c "CREATE DATABASE $DIRECTOR_TESTDB WITH ENCODING 'UTF8';"
+ - psql $DIRECTOR_TESTDB -q -c "CREATE USER $DIRECTOR_TESTDB_USER WITH PASSWORD 'testing'; GRANT ALL PRIVILEGES ON DATABASE $DIRECTOR_TESTDB TO $DIRECTOR_TESTDB_USER; CREATE EXTENSION pgcrypto;"
+ after_script:
+ - psql postgres -c "DROP DATABASE $DIRECTOR_TESTDB"
+ - psql postgres -c "DROP USER $DIRECTOR_TESTDB_USER"
+ script:
+ - phpunit
+
+CentOS 6/MySQL:
+ stage: Unit-Tests with DB
+ tags:
+ - centos6
+ - director
+ variables:
+ DIRECTOR_TESTDB: "director_test_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ DIRECTOR_TESTDB_RES: "Director MySQL TestDB"
+ before_script:
+ - mysql -u root -e "CREATE DATABASE $DIRECTOR_TESTDB"
+ after_script:
+ - mysql -u root -e "DROP DATABASE $DIRECTOR_TESTDB"
+ script:
+ - phpunit
+
+Jessie/MySQL:
+ stage: Unit-Tests with DB
+ tags:
+ - jessie
+ - director
+ variables:
+ DIRECTOR_TESTDB: "director_test_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ DIRECTOR_TESTDB_RES: "Director MySQL TestDB"
+ before_script:
+ - mysql -u root -e "CREATE DATABASE $DIRECTOR_TESTDB"
+ after_script:
+ - mysql -u root -e "DROP DATABASE $DIRECTOR_TESTDB"
+ script:
+ - phpunit
+
+Jessie/PostgreSQL:
+ stage: Unit-Tests with DB
+ tags:
+ - jessie
+ - director
+ variables:
+ DIRECTOR_TESTDB: "director_test_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ DIRECTOR_TESTDB_RES: "Director PostgreSQL TestDB"
+ DIRECTOR_TESTDB_USER: "director_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ before_script:
+ - psql postgres -q -c "CREATE DATABASE $DIRECTOR_TESTDB WITH ENCODING 'UTF8';"
+ - psql $DIRECTOR_TESTDB -q -c "CREATE USER $DIRECTOR_TESTDB_USER WITH PASSWORD 'testing'; GRANT ALL PRIVILEGES ON DATABASE $DIRECTOR_TESTDB TO $DIRECTOR_TESTDB_USER; CREATE EXTENSION pgcrypto;"
+ after_script:
+ - psql postgres -c "DROP DATABASE $DIRECTOR_TESTDB"
+ - psql postgres -c "DROP USER $DIRECTOR_TESTDB_USER"
+ script:
+ - phpunit
+
+Xenial/MySQL:
+ stage: Unit-Tests with DB
+ tags:
+ - xenial
+ - director
+ variables:
+ DIRECTOR_TESTDB: "director_test_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ DIRECTOR_TESTDB_RES: "Director MySQL TestDB"
+ before_script:
+ - mysql -u root -e "CREATE DATABASE $DIRECTOR_TESTDB"
+ after_script:
+ - mysql -u root -e "DROP DATABASE $DIRECTOR_TESTDB"
+ script:
+ - phpunit
+
+Xenial/PostgreSQL:
+ stage: Unit-Tests with DB
+ tags:
+ - ubuntu
+ - director
+ variables:
+ DIRECTOR_TESTDB: "director_test_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ DIRECTOR_TESTDB_RES: "Director PostgreSQL TestDB"
+ DIRECTOR_TESTDB_USER: "director_${CI_BUILD_ID}_${CI_RUNNER_ID}"
+ before_script:
+ - psql postgres -q -c "CREATE DATABASE $DIRECTOR_TESTDB WITH ENCODING 'UTF8';"
+ - psql $DIRECTOR_TESTDB -q -c "CREATE USER $DIRECTOR_TESTDB_USER WITH PASSWORD 'testing'; GRANT ALL PRIVILEGES ON DATABASE $DIRECTOR_TESTDB TO $DIRECTOR_TESTDB_USER; CREATE EXTENSION pgcrypto;"
+ after_script:
+ - psql postgres -c "DROP DATABASE $DIRECTOR_TESTDB"
+ - psql postgres -c "DROP USER $DIRECTOR_TESTDB_USER"
+ script:
+ - phpunit
+
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d3fc415
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,34 @@
+language: php
+php:
+ - '5.3'
+ - '5.4'
+ - '5.5'
+ - '5.6'
+ - '7.0'
+ - '7.1'
+ - nightly
+
+services:
+ - mysql
+ - postgresql
+
+cache:
+ directories:
+ - vendor
+
+env:
+ - DB=mysql DIRECTOR_TESTDB_RES="Director MySQL TestDB" DIRECTOR_TESTDB="director_test"
+ - DB=pgsql DIRECTOR_TESTDB_RES="Director PostgreSQL TestDB" DIRECTOR_TESTDB="director_test"
+ DIRECTOR_TESTDB_USER="director_test"
+
+before_script:
+ - curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
+ - wget https://github.com/Icinga/icingaweb2/archive/v2.4.0.tar.gz
+ - tar xfz v2.4.0.tar.gz
+ - ln -s icingaweb2-2.4.0/library/Icinga
+ - ln -s icingaweb2-2.4.0/library/vendor/Zend
+ - ./test/travis-prepare.sh
+
+script:
+ - php phpcs.phar --report-width=auto --report-full --report-gitblame --report-summary -p --standard=PSR2 --extensions=php --encoding=utf-8 -w -s library/Director/ application/ configuration.php run.php test
+ - phpunit --testdox || phpunit --verbose
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b7249e9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,62 @@
+Icinga Director
+===============
+
+Icinga Director has been designed to make Icinga 2 configuration handling easy.
+It tries to target two main audiences:
+
+* Users with the desire to completely automate their datacenter
+* Sysops willing to grant their "point & click" users a lot of flexibility
+
+What makes Icinga Director so special is the fact that it tries to target both
+of them at once.
+
+![Icinga Director](doc/screenshot/director/readme/director_main_screen.png)
+
+Read more about Icinga Director in our [Introduction](doc/01-Introduction.md) section.
+Afterwards, you should be ready for [getting started](doc/04-Getting-started.md).
+
+Documentation
+-------------
+
+Please have a look at our [Installation instructions](doc/02-Installation.md)
+and our hints for how to apply [Upgrades](doc/05-Upgrading.md). We love automation
+and in case you also do so, the [Automation chapter](doc/03-Automation.md) could
+be worth a read. When upgrading, you should also have a look at our [Changelog](doc/82-Changelog.md).
+
+You could be interested in understanding how the [Director works](doc/10-How-it-works.md)
+internally. [Working with agents](doc/24-Working-with-agents.md) is a topic that
+affects many Icinga administrators. Other interesting entry points might be
+[Import and Synchronization](doc/70-Import-and-Sync.md), our [CLI interface](doc/60-CLI.md),
+the [REST API](doc/70-REST-API.md) and last but not least our [FAQ](doc/80-FAQ.md).
+
+A complete list of all our documentation can be found in the [doc](doc/) directory.
+
+Contributing
+------------
+
+Icinga Director is an Open Source project and lives from your contributions. No
+matter whether these are feature requests, issues, translations, documentation
+or code.
+
+* Please check whether a related issue alredy exists on our [Issue Tracker](https://dev.icinga.com/projects/icingaweb2-module-director/issues)
+* Make sure your code conforms to the [PSR-2: Coding Style Guide](http://www.php-fig.org/psr/psr-2/)
+* [Unit-Tests](doc/93-Testing.md) would be great
+* Send a [Pull Request](https://github.com/Icinga/icingaweb2-module-director/pulls)
+ (it will automatically be tested on Travis-CI)
+* We try hard to keep our master always green: [![Build Status](https://travis-ci.org/Icinga/icingaweb2-module-director.svg?branch=master)](https://travis-ci.org/Icinga/icingaweb2-module-director)
+
+
+Addons
+------
+
+The following are to be considered community-supported modules, as they are not
+supported by the Icinga Team. At least not yet. But please give them a try if
+they fit your needs. They are being used in productive environments:
+
+* [AWS - Amazon Web Services](https://github.com/Thomas-Gelf/icingaweb2-module-aws):
+ provides an Import Source for Autoscaling Groups on AWS
+* [File-Shipper](https://github.com/Thomas-Gelf/icingaweb2-module-fileshipper):
+ allows Director to ship additional config files with manual config with its
+ deployments
+* [PuppetDB](https://github.com/Thomas-Gelf/icingaweb2-module-puppetdb): provides
+ an Import Source dealing with your PuppetDB
diff --git a/application/clicommands/BenchmarkCommand.php b/application/clicommands/BenchmarkCommand.php
new file mode 100644
index 0000000..4192854
--- /dev/null
+++ b/application/clicommands/BenchmarkCommand.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Application\Benchmark;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaHostVar;
+
+class BenchmarkCommand extends Command
+{
+ public function filterAction()
+ {
+ $flat = array();
+
+ /** @var FilterChain|FilterExpression $filter */
+ $filter = Filter::fromQueryString(
+ // 'object_name=*ic*2*&object_type=object'
+ 'vars.bpconfig=*'
+ );
+ Benchmark::measure('ready');
+ $objs = IcingaHost::loadAll($this->db());
+ Benchmark::measure('db done');
+
+ foreach ($objs as $host) {
+ $flat[$host->get('id')] = (object) array();
+ foreach ($host->getProperties() as $k => $v) {
+ $flat[$host->get('id')]->$k = $v;
+ }
+ }
+ Benchmark::measure('objects ready');
+
+ $vars = IcingaHostVar::loadAll($this->db());
+ Benchmark::measure('vars loaded');
+ foreach ($vars as $var) {
+ if (! array_key_exists($var->get('host_id'), $flat)) {
+ // Templates?
+ continue;
+ }
+ $flat[$var->get('host_id')]->{'vars.' . $var->get('varname')} = $var->get('varvalue');
+ }
+ Benchmark::measure('vars done');
+
+ foreach ($flat as $host) {
+ if ($filter->matches($host)) {
+ echo $host->object_name . "\n";
+ }
+ }
+ }
+}
diff --git a/application/clicommands/CommandCommand.php b/application/clicommands/CommandCommand.php
new file mode 100644
index 0000000..5c96442
--- /dev/null
+++ b/application/clicommands/CommandCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Commands
+ *
+ * Use this command to show, create, modify or delete Icinga Command
+ * objects
+ */
+class CommandCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/CommandsCommand.php b/application/clicommands/CommandsCommand.php
new file mode 100644
index 0000000..9a74337
--- /dev/null
+++ b/application/clicommands/CommandsCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectsCommand;
+
+/**
+ * List Icinga Commands
+ *
+ * Use this command to list Icinga Command objects
+ */
+class CommandsCommand extends ObjectsCommand
+{
+}
diff --git a/application/clicommands/ConfigCommand.php b/application/clicommands/ConfigCommand.php
new file mode 100644
index 0000000..f2fdd18
--- /dev/null
+++ b/application/clicommands/ConfigCommand.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Application\Benchmark;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Util;
+
+/**
+ * Generate, show and deploy Icinga 2 configuration
+ */
+class ConfigCommand extends Command
+{
+ /**
+ * Re-render the current configuration
+ */
+ public function renderAction()
+ {
+ $profile = $this->params->shift('profile');
+ if ($profile) {
+ $this->enableDbProfiler();
+ }
+
+ $config = new IcingaConfig($this->db());
+ Benchmark::measure('Rendering config');
+ if ($config->hasBeenModified()) {
+ Benchmark::measure('Config rendered, storing to db');
+ $config->store();
+ Benchmark::measure('All done');
+ $checksum = $config->getHexChecksum();
+ printf(
+ "New config with checksum %s has been generated\n",
+ $checksum
+ );
+ } else {
+ $checksum = $config->getHexChecksum();
+ printf(
+ "Config with checksum %s already exists\n",
+ $checksum
+ );
+ }
+
+ if ($profile) {
+ $this->dumpDbProfile();
+ }
+ }
+
+ protected function dumpDbProfile()
+ {
+ $profiler = $this->getDbProfiler();
+
+ $totalTime = $profiler->getTotalElapsedSecs();
+ $queryCount = $profiler->getTotalNumQueries();
+ $longestTime = 0;
+ $longestQuery = null;
+
+ /** @var \Zend_Db_Profiler_Query $query */
+ foreach ($profiler->getQueryProfiles() as $query) {
+ echo $query->getQuery() . "\n";
+ if ($query->getElapsedSecs() > $longestTime) {
+ $longestTime = $query->getElapsedSecs();
+ $longestQuery = $query->getQuery();
+ }
+ }
+
+ echo 'Executed ' . $queryCount . ' queries in ' . $totalTime . ' seconds' . "\n";
+ echo 'Average query length: ' . $totalTime / $queryCount . ' seconds' . "\n";
+ echo 'Queries per second: ' . $queryCount / $totalTime . "\n";
+ echo 'Longest query length: ' . $longestTime . "\n";
+ echo "Longest query: \n" . $longestQuery . "\n";
+ }
+
+ protected function getDbProfiler()
+ {
+ return $this->db()->getDbAdapter()->getProfiler();
+ }
+
+ protected function enableDbProfiler()
+ {
+ return $this->getDbProfiler()->setEnabled(true);
+ }
+
+ /**
+ * Deploy the current configuration
+ *
+ * Does nothing if config didn't change unless you provide
+ * the --force parameter
+ */
+ public function deployAction()
+ {
+ $api = $this->api();
+ $db = $this->db();
+
+ $checksum = $this->params->get('checksum');
+ if ($checksum) {
+ $config = IcingaConfig::load(Util::hex2binary($checksum), $db);
+ } else {
+ $config = IcingaConfig::generate($db);
+ $checksum = $config->getHexChecksum();
+ }
+
+ $api->wipeInactiveStages($db);
+ $current = $api->getActiveChecksum($db);
+ if ($current === $checksum) {
+ if ($this->params->get('force')) {
+ echo "Config matches active stage, deploying anyway\n";
+ } else {
+ echo "Config matches active stage, nothing to do\n";
+
+ return;
+ }
+ }
+
+ if ($api->dumpConfig($config, $db)) {
+ printf("Config '%s' has been deployed\n", $checksum);
+ } else {
+ $this->fail(
+ sprintf("Failed to deploy config '%s'\n", $checksum)
+ );
+ }
+ }
+
+ public function filesAction()
+ {
+ }
+
+ public function fileAction()
+ {
+ }
+}
diff --git a/application/clicommands/CoreCommand.php b/application/clicommands/CoreCommand.php
new file mode 100644
index 0000000..4927aa5
--- /dev/null
+++ b/application/clicommands/CoreCommand.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\PlainObjectRenderer;
+
+class CoreCommand extends Command
+{
+ public function constantsAction()
+ {
+ foreach ($this->api()->getConstants() as $name => $value) {
+ printf("const %s = %s\n", $name, PlainObjectRenderer::render($value));
+ }
+ }
+}
diff --git a/application/clicommands/EndpointCommand.php b/application/clicommands/EndpointCommand.php
new file mode 100644
index 0000000..d899ee4
--- /dev/null
+++ b/application/clicommands/EndpointCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Endpoints
+ *
+ * Use this command to show, create, modify or delete Icinga Endpoint
+ * objects
+ */
+class EndpointCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/HostCommand.php b/application/clicommands/HostCommand.php
new file mode 100644
index 0000000..21ec5eb
--- /dev/null
+++ b/application/clicommands/HostCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Hosts
+ *
+ * Use this command to show, create, modify or delete Icinga Host
+ * objects
+ */
+class HostCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/HostgroupCommand.php b/application/clicommands/HostgroupCommand.php
new file mode 100644
index 0000000..88b17d9
--- /dev/null
+++ b/application/clicommands/HostgroupCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Hostgroups
+ *
+ * Use this command to show, create, modify or delete Icinga Hostgroups
+ * objects
+ */
+class HostGroupCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/HostsCommand.php b/application/clicommands/HostsCommand.php
new file mode 100644
index 0000000..3008284
--- /dev/null
+++ b/application/clicommands/HostsCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectsCommand;
+
+/**
+ * Manage Icinga Hosts
+ *
+ * Use this command to list Icinga Host objects
+ */
+class HostsCommand extends ObjectsCommand
+{
+}
diff --git a/application/clicommands/HousekeepingCommand.php b/application/clicommands/HousekeepingCommand.php
new file mode 100644
index 0000000..58c673a
--- /dev/null
+++ b/application/clicommands/HousekeepingCommand.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Exception\MissingParameterException;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Db\Housekeeping;
+
+class HousekeepingCommand extends Command
+{
+ protected $housekeeping;
+
+ public function tasksAction()
+ {
+ if ($pending = $this->params->shift('pending')) {
+ $tasks = $this->housekeeping()->getPendingTaskSummary();
+ } else {
+ $tasks = $this->housekeeping()->getTaskSummary();
+ }
+
+ $len = array_reduce(
+ $tasks,
+ function ($max, $task) {
+ return max(
+ $max,
+ strlen($task->title) + strlen($task->name) + 3
+ );
+ }
+ );
+
+ if (count($tasks)) {
+ print "\n";
+ printf(" %-" . $len . "s | %s\n", 'Housekeeping task (name)', 'Count');
+ printf("-%-" . $len . "s-|-------\n", str_repeat('-', $len));
+ }
+
+ foreach ($tasks as $task) {
+ printf(
+ " %-" . $len . "s | %5d\n",
+ sprintf('%s (%s)', $task->title, $task->name),
+ $task->count
+ );
+ }
+
+ if (count($tasks)) {
+ print "\n";
+ }
+ }
+
+ public function runAction()
+ {
+ if (!$job = $this->params->shift()) {
+ throw new MissingParameterException(
+ 'Job is required, say ALL to run all pending jobs'
+ );
+ }
+
+ if ($job === 'ALL') {
+ $this->housekeeping()->runAllTasks();
+ } else {
+ $this->housekeeping()->runTask($job);
+ }
+ }
+
+ protected function housekeeping()
+ {
+ if ($this->housekeeping === null) {
+ $this->housekeeping = new Housekeeping($this->db());
+ }
+
+ return $this->housekeeping;
+ }
+}
diff --git a/application/clicommands/JobsCommand.php b/application/clicommands/JobsCommand.php
new file mode 100644
index 0000000..20e3d73
--- /dev/null
+++ b/application/clicommands/JobsCommand.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Job\JobRunner;
+use Icinga\Module\Director\Objects\DirectorJob;
+use Icinga\Application\Logger;
+use Exception;
+
+class JobsCommand extends Command
+{
+ public function runAction()
+ {
+ $forever = $this->params->shift('forever');
+ if (! $forever && $this->params->getStandalone() === 'forever') {
+ $forever = true;
+ $this->params->shift();
+ }
+
+ $jobId = $this->params->shift();
+ if ($jobId) {
+ $job = DirectorJob::load($jobId, $this->db());
+ $job->run();
+ exit(0);
+ }
+
+ if ($forever) {
+ $this->runforever();
+ } else {
+ $this->runAllPendingJobs();
+ }
+ }
+
+ protected function runforever()
+ {
+ // We'll terminate ourselves after 24h for now:
+ $runUnless = time() + 86400;
+
+ // We'll exit in case more than 100MB of memory are still in use
+ // after the last job execution:
+ $maxMem = 100 * 1024 * 1024;
+
+ while (true) {
+ $this->runAllPendingJobs();
+ if (memory_get_usage() > $maxMem) {
+ exit(0);
+ }
+
+ if (time() > $runUnless) {
+ exit(0);
+ }
+
+ sleep(2);
+ }
+ }
+
+ protected function runAllPendingJobs()
+ {
+ $jobs = new JobRunner($this->db());
+
+ try {
+ if ($this->hasBeenDisabled()) {
+ return;
+ }
+
+ $jobs->runPendingJobs();
+ } catch (Exception $e) {
+ Logger::error('Director Job Error: ' . $e->getMessage());
+ sleep(10);
+ }
+ }
+
+ protected function hasBeenDisabled()
+ {
+ return $this->db()->settings()->disable_all_jobs === 'y';
+ }
+}
diff --git a/application/clicommands/KickstartCommand.php b/application/clicommands/KickstartCommand.php
new file mode 100644
index 0000000..e06957f
--- /dev/null
+++ b/application/clicommands/KickstartCommand.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\KickstartHelper;
+
+/**
+ * Kickstart a Director installation
+ *
+ * Once you prepared your DB resource this command retrieves information about
+ * unapplied database migration and helps applying them.
+ */
+class KickstartCommand extends Command
+{
+ /**
+ * Check whether a kickstart run is required
+ *
+ * This is the case when there is a kickstart.ini in your Directors config
+ * directory and no ApiUser in your Director DB.
+ *
+ * This is mostly for automation, so one could create a Puppet manifest
+ * as follows:
+ *
+ * exec { 'Icinga Director Kickstart':
+ * path => '/usr/local/bin:/usr/bin:/bin',
+ * command => 'icingacli director kickstart run',
+ * onlyif => 'icingacli director kickstart required',
+ * require => Exec['Icinga Director DB migration'],
+ * }
+ *
+ * Exit code 0 means that a kickstart run is required, code 2 that it is
+ * not.
+ */
+ public function requiredAction()
+ {
+ if ($this->kickstart()->isConfigured()) {
+ if ($this->kickstart()->isRequired()) {
+ if ($this->isVerbose) {
+ echo "Kickstart has been configured and should be triggered\n";
+ }
+
+ exit(0);
+ } else {
+ echo "Kickstart configured, execution is not required\n";
+ exit(1);
+ }
+ } else {
+ echo "Kickstart has not been configured\n";
+ exit(2);
+ }
+ }
+
+ /**
+ * Trigger the kickstart helper
+ *
+ * This will connect to the endpoint configured in your kickstart.ini,
+ * store the given API user and import existing objects like zones,
+ * endpoints and commands.
+ *
+ * /etc/icingaweb2/modules/director/kickstart.ini could look as follows:
+ *
+ * [config]
+ * endpoint = "master-node.example.com"
+ *
+ * ; Host can be an IP address or a hostname. Equals to endpoint name
+ * ; if not set:
+ * host = "127.0.0.1"
+ *
+ * ; Port is 5665 if none given
+ * port = 5665
+ *
+ * username = "director"
+ * password = "***"
+ *
+ */
+ public function runAction()
+ {
+ $this->kickstart()->loadConfigFromFile()->run();
+ exit(0);
+ }
+
+ protected function kickstart()
+ {
+ return new KickstartHelper($this->db());
+ }
+}
diff --git a/application/clicommands/MigrationCommand.php b/application/clicommands/MigrationCommand.php
new file mode 100644
index 0000000..6a4d002
--- /dev/null
+++ b/application/clicommands/MigrationCommand.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Db\Migrations;
+
+/**
+ * Handle DB migrations
+ *
+ * This command retrieves information about unapplied database migration and
+ * helps applying them.
+ */
+class MigrationCommand extends Command
+{
+ /**
+ * Check whether there are pending migrations
+ *
+ * This is mostly for automation, so one could create a Puppet manifest
+ * as follows:
+ *
+ * exec { 'Icinga Director DB migration':
+ * command => 'icingacli director migration run',
+ * onlyif => 'icingacli director migration pending',
+ * }
+ *
+ * Exit code 0 means that there are pending migrations, code 1 that there
+ * are no such. Use --verbose for human readable output
+ */
+ public function pendingAction()
+ {
+ if ($count = $this->migrations()->countPendingMigrations()) {
+ if ($this->isVerbose) {
+ if ($count === 1) {
+ echo "There is 1 pending migration\n";
+ } else {
+ printf("There are %d pending migrations\n", $count);
+ }
+ }
+
+ exit(0);
+ } else {
+ if ($this->isVerbose) {
+ echo "There are no pending migrations\n";
+ }
+
+ exit(1);
+ }
+ }
+
+ /**
+ * Run any pending migrations
+ *
+ * All pending migrations will be silently applied
+ */
+ public function runAction()
+ {
+ $this->migrations()->applyPendingMigrations();
+ exit(0);
+ }
+
+ protected function migrations()
+ {
+ return new Migrations($this->db());
+ }
+}
diff --git a/application/clicommands/NotificationCommand.php b/application/clicommands/NotificationCommand.php
new file mode 100644
index 0000000..bb5402a
--- /dev/null
+++ b/application/clicommands/NotificationCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Notifications
+ *
+ * Use this command to show, create, modify or delete Icinga Notification
+ * objects
+ */
+class NotificationCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/ServiceCommand.php b/application/clicommands/ServiceCommand.php
new file mode 100644
index 0000000..b472088
--- /dev/null
+++ b/application/clicommands/ServiceCommand.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+use Icinga\Module\Director\Objects\IcingaHost;
+
+/**
+ * Manage Icinga Services
+ *
+ * Use this command to show, create, modify or delete Icinga Service
+ * objects
+ */
+class ServiceCommand extends ObjectCommand
+{
+
+ protected function load($name)
+ {
+ return parent::load($this->makeServiceKey($this->getName()));
+ }
+
+ protected function exists($name)
+ {
+ return parent::exists($this->makeServiceKey($this->getName()));
+ }
+
+ protected function makeServiceKey($name)
+ {
+ if ($host = $this->params->get('host')) {
+ return array(
+ 'object_name' => $name,
+ 'host_id' => IcingaHost::load($host, $this->db())->get('id'),
+ );
+ } else {
+ return array(
+ 'object_name' => $name,
+ 'object_type' => 'template',
+ );
+ }
+ }
+}
diff --git a/application/clicommands/ServicesetCommand.php b/application/clicommands/ServicesetCommand.php
new file mode 100644
index 0000000..648a42c
--- /dev/null
+++ b/application/clicommands/ServicesetCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+/**
+ * Manage Icinga Service Sets
+ *
+ * Use this command to show, create, modify or delete Icinga Service
+ * objects
+ */
+class ServicesetCommand extends ServiceCommand
+{
+ protected $type = 'ServiceSet';
+}
diff --git a/application/clicommands/TestCommand.php b/application/clicommands/TestCommand.php
new file mode 100644
index 0000000..dcb6d17
--- /dev/null
+++ b/application/clicommands/TestCommand.php
@@ -0,0 +1,246 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Application\Logger;
+use Icinga\Cli\Command;
+use Icinga\Module\Director\Test\TestSuiteLint;
+use Icinga\Module\Director\Test\TestSuiteStyle;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+
+class TestCommand extends Command
+{
+ /**
+ * Default arguments and options for PHP_CodeSniffer
+ *
+ * @var array
+ */
+ protected $phpcsDefaultParams = array(
+ '-p',
+ '--standard=PSR2',
+ '--extensions=php',
+ '--encoding=utf-8'
+ );
+
+ public function lintAction()
+ {
+ $test = new TestSuiteLint();
+ $test->run();
+ if ($test->hasFailures()) {
+ Logger::error('Lint check failed');
+ exit(1);
+ } else {
+ Logger::info('Lint check succeeded');
+ exit(0);
+ }
+ }
+
+ /**
+ * Run all unit-test suites
+ *
+ * This command runs the unit- and regression-tests of icingaweb and installed modules.
+ *
+ * USAGE
+ *
+ * icingacli test php unit [options]
+ *
+ * OPTIONS
+ *
+ * --verbose Be more verbose.
+ * --build Enable reporting.
+ * --include Pattern to use for including files/test cases.
+ *
+ * EXAMPLES
+ *
+ * icingacli test php unit --verbose
+ * icingacli test php unit --build
+ * icingacli test php unit --include=*SpecialTest
+ */
+ public function phpAction()
+ {
+ $basedir = $this->getBaseDir();
+ $build = $this->params->shift('build');
+ $include = $this->params->shift('include');
+
+ $phpUnit = exec('which phpunit');
+ if (!file_exists($phpUnit)) {
+ $this->fail('PHPUnit not found. Please install PHPUnit to be able to run the unit-test suites.');
+ }
+
+ $options = array(
+ '--bootstrap' => $basedir . '/test/bootstrap.php'
+ );
+ if ($this->isVerbose) {
+ $options[] = '--verbose --testdox';
+ }
+ if ($build) {
+ $reportPath = $this->setupAndReturnReportDirectory();
+ $options[] = '--verbose';
+ $options[] = '--log-junit';
+ $options[] = $reportPath . '/phpunit_results.xml';
+ $options[] = '--coverage-html';
+ $options[] = $reportPath . '/php_html_coverage';
+ }
+ if ($include !== null) {
+ $options[] = '--filter';
+ $options[] = $include;
+ }
+
+ chdir(realpath(__DIR__ . '/../..'));
+ $command = $phpUnit . ' ' . join(' ', array_merge($options, $this->params->getAllStandalone()));
+ if ($this->isVerbose) {
+ $res = `$command`;
+ foreach (preg_split('/\n/', $res) as $line) {
+ if (preg_match('~\s+\[([x\s])\]\s~', $line, $m)) {
+ if ($m[1] === 'x') {
+ echo $this->screen->colorize($line, 'green') . "\n";
+ } else {
+ echo $this->screen->colorize($line, 'red') . "\n";
+ }
+ } else {
+ echo $line . "\n";
+ }
+ }
+ } else {
+ passthru($command);
+ }
+ }
+
+ /**
+ * Run code-style checks
+ *
+ * This command checks whether icingaweb and installed modules match the PSR-2 coding standard.
+ *
+ * USAGE
+ *
+ * icingacli test php style [options]
+ *
+ * OPTIONS
+ *
+ * --verbose Be more verbose.
+ * --build Enable reporting.
+ * --include Include only specific files. (Can be supplied multiple times.)
+ * --exclude Pattern to use for excluding files. (Can be supplied multiple times.)
+ *
+ * EXAMPLES
+ *
+ * icingacli test php style --verbose
+ * icingacli test php style --build
+ * icingacli test php style --include=path/to/your/file
+ * icingacli test php style --exclude=*someFile* --exclude=someOtherFile*
+ */
+ public function styleAction()
+ {
+ // passthru(
+ // 'phpcs -p --standard=PSR2 --extensions=php --encoding=utf-8 -w -s
+ // --report-checkstyle=/tmp/style/bla library/Director/ application/
+ // run.php configuration.php'
+ // );
+
+
+ $test = new TestSuiteStyle();
+ $test->run();
+
+ return;
+ // TODO: obsolete:
+
+ if ($test->hasFailures()) {
+ $this->fail('Lint check failed');
+ } else {
+ Logger::info('Lint check succeeded');
+ }
+
+ $out = TestRunner::newTempFile();
+ $check = array(
+ 'library/Director/',
+ 'application/',
+ 'configuration.php',
+ 'run.php',
+ );
+
+ $cmd = sprintf(
+ "phpcs -p --standard=PSR2 --extensions=php --encoding=utf-8 -w -s --report-checkstyle=%s '%s'",
+ $out,
+ implode("' '", $check)
+ );
+
+ // TODO: Debug only:
+ `$cmd`;
+ echo $cmd . "\n";
+ echo $out ."\n";
+ echo file_get_contents($out);
+ unlink($out);
+ exit;
+
+ $build = $this->params->shift('build');
+ $include = (array) $this->params->shift('include', array());
+ $exclude = (array) $this->params->shift('exclude', array());
+
+ $phpcs = exec('which phpcs');
+ if (!file_exists($phpcs)) {
+ $this->fail(
+ 'PHP_CodeSniffer not found. Please install PHP_CodeSniffer to be able to run code style tests.'
+ );
+ }
+
+ $options = array();
+ if ($this->isVerbose) {
+ $options[] = '-v';
+ }
+ if ($build) {
+ $options[] = '--report-checkstyle=' . $this->setupAndReturnReportDirectory();
+ }
+ if (!empty($exclude)) {
+ $options[] = '--ignore=' . join(',', $exclude);
+ }
+ $arguments = array_filter(
+ array_map(
+ function ($p) {
+ return realpath($p);
+ },
+ $include
+ )
+ );
+ if (empty($arguments)) {
+ $arguments = array(
+ realpath(__DIR__ . '/../../../../application'),
+ realpath(__DIR__ . '/../../../../library/Icinga')
+ );
+ }
+
+ chdir(realpath(__DIR__ . '/../..'));
+ passthru(
+ $phpcs . ' ' . join(
+ ' ',
+ array_merge(
+ $options,
+ $this->phpcsDefaultParams,
+ $arguments,
+ $this->params->getAllStandalone()
+ )
+ )
+ );
+ }
+
+ protected function getBaseDir()
+ {
+ return dirname(dirname(__DIR__));
+ }
+
+ /**
+ * Setup the directory where to put report files and return its path
+ *
+ * @return string
+ */
+ protected function setupAndReturnReportDirectory()
+ {
+ $path = '/tmp/test-devel';
+
+ if (!is_dir($path) && !@mkdir($path, 0755, true)) {
+ $this->fail("Could not create directory: $path");
+ }
+
+ return $path;
+ }
+}
diff --git a/application/clicommands/TimeperiodCommand.php b/application/clicommands/TimeperiodCommand.php
new file mode 100644
index 0000000..352289a
--- /dev/null
+++ b/application/clicommands/TimeperiodCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Timeperiods
+ *
+ * Use this command to show, create, modify or delete Icinga Timeperiod
+ * objects
+ */
+class TimePeriodCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/UserCommand.php b/application/clicommands/UserCommand.php
new file mode 100644
index 0000000..9c4c9d4
--- /dev/null
+++ b/application/clicommands/UserCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Users
+ *
+ * Use this command to show, create, modify or delete Icinga User
+ * objects
+ */
+class UserCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/UsergroupCommand.php b/application/clicommands/UsergroupCommand.php
new file mode 100644
index 0000000..04ba7c3
--- /dev/null
+++ b/application/clicommands/UsergroupCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Usergroups
+ *
+ * Use this command to show, create, modify or delete Icinga Usergroup
+ * objects
+ */
+class UsergroupCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/ZoneCommand.php b/application/clicommands/ZoneCommand.php
new file mode 100644
index 0000000..a5c45f9
--- /dev/null
+++ b/application/clicommands/ZoneCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Zones
+ *
+ * Use this command to show, create, modify or delete Icinga Zone
+ * objects
+ */
+class ZoneCommand extends ObjectCommand
+{
+}
diff --git a/application/controllers/ApiuserController.php b/application/controllers/ApiuserController.php
new file mode 100644
index 0000000..36438ae
--- /dev/null
+++ b/application/controllers/ApiuserController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class ApiuserController extends ObjectController
+{
+}
diff --git a/application/controllers/ApiusersController.php b/application/controllers/ApiusersController.php
new file mode 100644
index 0000000..5597521
--- /dev/null
+++ b/application/controllers/ApiusersController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class ApiusersController extends ObjectsController
+{
+}
diff --git a/application/controllers/CommandController.php b/application/controllers/CommandController.php
new file mode 100644
index 0000000..f485083
--- /dev/null
+++ b/application/controllers/CommandController.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Data\Filter\Filter;
+
+class CommandController extends ObjectController
+{
+ public function init()
+ {
+ parent::init();
+ if ($this->object && ! $this->object->isExternal()) {
+ $this->getTabs()->add('arguments', array(
+ 'url' => 'director/command/arguments',
+ 'urlParams' => array('name' => $this->object->object_name),
+ 'label' => 'Arguments'
+ ));
+ }
+ }
+
+ public function argumentsAction()
+ {
+ $this->gracefullyActivateTab('arguments');
+ $this->view->title = sprintf(
+ $this->translate('Command arguments: %s'),
+ $this->object->object_name
+ );
+
+ $this->view->table = $this
+ ->loadTable('icingaCommandArgument')
+ ->setCommandObject($this->object)
+ ->setFilter(Filter::where('command', $this->params->get('name')));
+
+ $form = $this->view->form = $this
+ ->loadForm('icingaCommandArgument')
+ ->setCommandObject($this->object);
+
+ if ($id = $this->params->shift('argument_id')) {
+ $form->loadObject($id);
+ }
+
+ $form->handleRequest();
+ }
+}
diff --git a/application/controllers/CommandsController.php b/application/controllers/CommandsController.php
new file mode 100644
index 0000000..7451199
--- /dev/null
+++ b/application/controllers/CommandsController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class CommandsController extends ObjectsController
+{
+}
diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
new file mode 100644
index 0000000..5bc1526
--- /dev/null
+++ b/application/controllers/ConfigController.php
@@ -0,0 +1,399 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\ConfigDiff;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Settings;
+use Icinga\Module\Director\Util;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+use Exception;
+
+class ConfigController extends ActionController
+{
+ protected $isApified = true;
+
+ protected function checkDirectorPermissions()
+ {
+ }
+
+ public function deploymentsAction()
+ {
+ $this->assertPermission('director/deploy');
+ try {
+ if ($this->db()->hasUncollectedDeployments()) {
+ $this->setAutorefreshInterval(5);
+ $this->api()->collectLogFiles($this->db());
+ } else {
+ $this->setAutorefreshInterval(10);
+ }
+ } catch (Exception $e) {
+ // No problem, Icinga might be reloading
+ }
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Render config'),
+ 'director/config/store',
+ null,
+ array('class' => 'icon-wrench')
+ );
+
+ $this->overviewTabs()->activate('deploymentlog');
+ $this->view->title = $this->translate('Deployments');
+ $this->prepareTable('deploymentLog');
+ try {
+ // Move elsewhere
+ $this->view->table->setActiveStageName(
+ $this->api()->getActiveStageName()
+ );
+ } catch (Exception $e) {
+ // Don't care
+ }
+
+ $this->render('objects/table', null, 'objects');
+ }
+
+ public function deployAction()
+ {
+ $this->assertPermission('director/deploy');
+
+ // TODO: require POST
+ $checksum = $this->params->get('checksum');
+ if ($checksum) {
+ $config = IcingaConfig::load(Util::hex2binary($checksum), $this->db());
+ } else {
+ $config = IcingaConfig::generate($this->db());
+ $checksum = $config->getHexChecksum();
+ }
+
+ try {
+ $this->api()->wipeInactiveStages($this->db());
+ } catch (Exception $e) {
+ $this->deploymentFailed($checksum, $e->getMessage());
+ }
+
+ if ($this->api()->dumpConfig($config, $this->db())) {
+ $this->deploymentSucceeded($checksum);
+ } else {
+ $this->deploymentFailed($checksum);
+ }
+ }
+
+ protected function deploymentSucceeded($checksum)
+ {
+ if ($this->getRequest()->isApiRequest()) {
+ return $this->sendJson((object) array('checksum' => $checksum));
+ } else {
+ $url = Url::fromPath('director/config/deployments');
+ Notification::success(
+ $this->translate('Config has been submitted, validation is going on')
+ );
+ $this->redirectNow($url);
+ }
+ }
+
+ protected function deploymentFailed($checksum, $error = null)
+ {
+ $extra = $error ? ': ' . $error: '';
+
+ if ($this->getRequest()->isApiRequest()) {
+ return $this->sendJsonError('Config deployment failed' . $extra);
+ } else {
+ $url = Url::fromPath('director/config/files', array('checksum' => $checksum));
+ Notification::error(
+ $this->translate('Config deployment failed') . $extra
+ );
+ $this->redirectNow($url);
+ }
+ }
+
+ public function activitiesAction()
+ {
+ $this->assertPermission('director/audit');
+
+ $this->setAutorefreshInterval(10);
+ $this->overviewTabs()->activate('activitylog');
+ $this->view->title = $this->translate('Activity Log');
+ $lastDeployedId = $this->db()->getLastDeploymentActivityLogId();
+ $this->prepareTable('activityLog');
+ $this->view->table->setLastDeployedId($lastDeployedId);
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('My changes'),
+ $this->getRequest()->getUrl()
+ ->with('author', $this->Auth()->getUser()->getUsername())
+ ->without('page'),
+ null,
+ array('class' => 'icon-user', 'data-base-target' => '_self')
+ );
+ if ($this->hasPermission('director/deploy')) {
+ $this->view->addLink .= $this
+ ->loadForm('DeployConfig')
+ ->setDb($this->db())
+ ->setApi($this->api())
+ ->handleRequest();
+ }
+ $this->provideFilterEditorForTable($this->view->table);
+
+ $this->setViewScript('list/table');
+ }
+
+ public function settingsAction()
+ {
+ $this->assertPermission('director/admin');
+
+ $this->overviewTabs()->activate('settings');
+ $this->view->title = $this->translate('Settings');
+ $this->view->form = $this
+ ->loadForm('Settings')
+ ->setSettings(new Settings($this->db()))
+ ->handleRequest();
+
+ $this->setViewScript('object/form');
+ }
+
+ // Show all files for a given config
+ public function filesAction()
+ {
+ $this->assertPermission('director/showconfig');
+
+ $this->view->title = $this->translate('Generated config');
+ $tabs = $this->getTabs();
+
+ if ($deploymentId = $this->view->deploymentId = $this->params->get('deployment_id')) {
+ $tabs->add('deployment', array(
+ 'label' => $this->translate('Deployment'),
+ 'url' => 'director/deployment',
+ 'urlParams' => array(
+ 'id' => $deploymentId
+ )
+ ));
+ }
+
+ $tabs->add('config', array(
+ 'label' => $this->translate('Config'),
+ 'url' => $this->getRequest()->getUrl(),
+ ))->activate('config');
+
+ $checksum = $this->params->get('checksum');
+
+ $this->view->deployForm = $this->loadForm('DeployConfig')
+ ->setAttrib('class', 'inline')
+ ->setDb($this->db())
+ ->setApi($this->api())
+ ->setChecksum($checksum)
+ ->setDeploymentId($deploymentId)
+ ->handleRequest();
+
+ $this->view->table = $this
+ ->loadTable('GeneratedConfigFile')
+ ->setActiveFilename($this->params->get('active_file'))
+ ->setConnection($this->db())
+ ->setConfigChecksum($checksum);
+
+ if ($deploymentId) {
+ $this->view->table->setDeploymentId($deploymentId);
+ }
+
+ $this->view->config = IcingaConfig::load(
+ Util::hex2binary($this->params->get('checksum')),
+ $this->db()
+ );
+ }
+
+ // Show a single file
+ public function fileAction()
+ {
+ $this->assertPermission('director/showconfig');
+ $filename = $this->view->filename = $this->params->get('file_path');
+ $fileOnly = $this->params->get('fileOnly');
+ $this->view->highlight = $this->params->get('highlight');
+ $this->view->highlightSeverity = $this->params->get('highlightSeverity');
+ $tabs = $this->configTabs()->add('file', array(
+ 'label' => $this->translate('Rendered file'),
+ 'url' => $this->getRequest()->getUrl(),
+ ))->activate('file');
+
+ $params = $this->getConfigTabParams();
+ if ('deployment' === $this->params->get('backTo')) {
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('back'),
+ 'director/deployment',
+ array('id' => $params['deployment_id']),
+ array('class' => 'icon-left-big')
+ );
+ } else {
+ $params['active_file'] = $filename;
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('back'),
+ 'director/config/files',
+ $params,
+ array('class' => 'icon-left-big')
+ );
+ }
+
+ $this->view->config = IcingaConfig::load(Util::hex2binary($this->params->get('config_checksum')), $this->db());
+ $this->view->title = sprintf(
+ $this->translate('Config file "%s"'),
+ $filename
+ );
+ $this->view->file = $this->view->config->getFile($filename);
+ }
+
+ public function showAction()
+ {
+ $this->assertPermission('director/showconfig');
+
+ $this->configTabs()->activate('config');
+ $this->view->config = IcingaConfig::load(Util::hex2binary($this->params->get('checksum')), $this->db());
+ }
+
+ // TODO: Check if this can be removed
+ public function storeAction()
+ {
+ $config = IcingaConfig::generate($this->db());
+ $this->redirectNow(
+ Url::fromPath(
+ 'director/config/files',
+ array('checksum' => $config->getHexChecksum())
+ )
+ );
+ }
+
+ public function diffAction()
+ {
+ $this->assertPermission('director/showconfig');
+
+ $db = $this->db();
+ $this->view->title = $this->translate('Config diff');
+
+ $tabs = $this->getTabs()->add('diff', array(
+ 'label' => $this->translate('Config diff'),
+ 'url' => $this->getRequest()->getUrl()
+ ))->activate('diff');
+
+ $leftSum = $this->view->leftSum = $this->params->get('left');
+ $rightSum = $this->view->rightSum = $this->params->get('right');
+ $left = IcingaConfig::load(Util::hex2binary($leftSum), $db);
+
+ $configs = $db->enumDeployedConfigs();
+ foreach (array($leftSum, $rightSum) as $sum) {
+ if (! array_key_exists($sum, $configs)) {
+ $configs[$sum] = substr($sum, 0, 7);
+ }
+ }
+
+ $this->view->configs = $configs;
+ if ($rightSum === null) {
+ return;
+ }
+
+ $right = IcingaConfig::load(Util::hex2binary($rightSum), $db);
+ $this->view->table = $this
+ ->loadTable('ConfigFileDiff')
+ ->setConnection($this->db())
+ ->setLeftChecksum($leftSum)
+ ->setRightChecksum($rightSum);
+ }
+
+ public function filediffAction()
+ {
+ $this->assertPermission('director/showconfig');
+
+ $db = $this->db();
+ $leftSum = $this->params->get('left');
+ $rightSum = $this->params->get('right');
+ $filename = $this->view->filename = $this->params->get('file_path');
+
+ $left = IcingaConfig::load(Util::hex2binary($leftSum), $db);
+ $right = IcingaConfig::load(Util::hex2binary($rightSum), $db);
+
+ $leftFile = $left->getFile($filename);
+ $rightFile = $right->getFile($filename);
+
+ $d = ConfigDiff::create($leftFile, $rightFile);
+
+ $this->view->title = sprintf(
+ $this->translate('Config file "%s"'),
+ $filename
+ );
+
+ $this->view->output = $d->renderHtml();
+ }
+
+ protected function overviewTabs()
+ {
+ $this->view->tabs = $tabs = $this->getTabs();
+
+ if ($this->hasPermission('director/audit')) {
+ $tabs->add(
+ 'activitylog',
+ array(
+ 'label' => $this->translate('Activity Log'),
+ 'url' => 'director/config/activities'
+ )
+ );
+ }
+
+ if ($this->hasPermission('director/deploy')) {
+ $tabs->add(
+ 'deploymentlog',
+ array(
+ 'label' => $this->translate('Deployments'),
+ 'url' => 'director/config/deployments'
+ )
+ );
+ }
+ if ($this->hasPermission('director/admin')) {
+ $tabs->add(
+ 'settings',
+ array(
+ 'label' => $this->translate('Settings'),
+ 'url' => 'director/config/settings'
+ )
+ );
+ }
+ return $this->view->tabs;
+ }
+
+ protected function configTabs()
+ {
+ $tabs = $this->getTabs();
+
+ if ($this->hasPermission('director/deploy') && $deploymentId = $this->params->get('deployment_id')) {
+ $tabs->add('deployment', array(
+ 'label' => $this->translate('Deployment'),
+ 'url' => 'director/deployment',
+ 'urlParams' => array(
+ 'id' => $deploymentId
+ )
+ ));
+ }
+
+ if ($this->hasPermission('director/showconfig')) {
+ $tabs->add('config', array(
+ 'label' => $this->translate('Config'),
+ 'url' => 'director/config/files',
+ 'urlParams' => $this->getConfigTabParams()
+ ));
+ }
+
+ return $tabs;
+ }
+
+ protected function getConfigTabParams()
+ {
+ $params = array(
+ 'checksum' => $this->params->get(
+ 'config_checksum',
+ $this->params->get('checksum')
+ )
+ );
+
+ if ($deploymentId = $this->params->get('deployment_id')) {
+ $params['deployment_id'] = $deploymentId;
+ }
+
+ return $params;
+ }
+}
diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php
new file mode 100644
index 0000000..f0db1bb
--- /dev/null
+++ b/application/controllers/DashboardController.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Acl;
+use Icinga\Module\Director\Dashboard\Dashboard;
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class DashboardController extends ActionController
+{
+ protected function checkDirectorPermissions()
+ {
+ }
+
+ public function indexAction()
+ {
+ if ($this->getRequest()->isGet()) {
+ $this->setAutorefreshInterval(10);
+ }
+
+ $this->view->title = $this->translate('Icinga Director');
+ $names = $this->params->getValues('name', array('Objects', 'Deployment', 'Data'));
+ if (count($names) === 1) {
+ // TODO: Find a better way for this
+ $this->singleTab($this->translate(ucfirst($names[0])));
+ } else {
+ $this->singleTab($this->translate('Overview'));
+ }
+ $dashboards = array();
+ foreach ($names as $name) {
+ $dashboard = Dashboard::loadByName($name, $this->db(), $this->view);
+ if ($dashboard->isAvailable()) {
+ $dashboards[$name] = $dashboard;
+ }
+ }
+
+ $this->view->dashboards = $dashboards;
+ }
+}
diff --git a/application/controllers/DataController.php b/application/controllers/DataController.php
new file mode 100644
index 0000000..cbe0b1c
--- /dev/null
+++ b/application/controllers/DataController.php
@@ -0,0 +1,188 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\DirectorDatalist;
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class DataController extends ActionController
+{
+ public function listsAction()
+ {
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add'),
+ 'director/data/list',
+ null,
+ array('class' => 'icon-plus')
+ );
+
+ $this->setDataTabs()->activate('datalist');
+ $this->view->title = $this->translate('Data lists');
+ $this->prepareAndRenderTable('datalist');
+ $this->provideFilterEditorForTable($this->view->table);
+ }
+
+ public function listAction()
+ {
+ $this->view->stayHere = true;
+
+ $form = $this->view->form = $this->loadForm('directorDatalist')
+ ->setSuccessUrl('director/data/lists')
+ ->setDb($this->db());
+
+ if ($id = $this->getRequest()->getUrl()->shift('id')) {
+ $form->loadObject($id);
+ $this->view->title = sprintf(
+ $this->translate('Data list: %s'),
+ $form->getObject()->list_name
+ );
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('back'),
+ 'director/data/list',
+ null,
+ array('class' => 'icon-left-big')
+ );
+
+ $this->view->addLink .= $this->view->qlink(
+ $this->translate('Entries'),
+ 'director/data/listentry',
+ array('list_id' => $id),
+ array(
+ 'class' => 'icon-doc-text',
+ 'data-base-target' => '_next'
+ )
+ );
+
+ $this->getTabs()->add('editlist', array(
+ 'url' => 'director/data/list' . '?id=' . $id,
+ 'label' => $this->translate('Edit list'),
+ ))->add('entries', array(
+ 'url' => 'director/data/listentry' . '?list_id=' . $id,
+ 'label' => $this->translate('List entries'),
+ ))->activate('editlist');
+ } else {
+ $this->view->title = $this->translate('Add');
+
+ $this->getTabs()->add('addlist', array(
+ 'url' => 'director/data/list',
+ 'label' => $this->view->title,
+ ))->activate('addlist');
+ }
+
+ $form->handleRequest();
+ $this->setViewScript('object/form');
+ }
+
+ public function indexAction()
+ {
+ $edit = false;
+
+ if ($id = $this->params->get('id')) {
+ $edit = true;
+ }
+
+ if ($edit) {
+ $this->view->title = $this->translate('Edit list');
+ $this->getTabs()->add('editlist', array(
+ 'url' => 'director/datalist/edit' . '?id=' . $id,
+ 'label' => $this->view->title,
+ ))->add('entries', array(
+ 'url' => 'director/data/listentry' . '?list_id=' . $id,
+ 'label' => $this->translate('List entries'),
+ ))->activate('editlist');
+ } else {
+ $this->view->title = $this->translate('Add list');
+ $this->getTabs()->add('addlist', array(
+ 'url' => 'director/datalist/add',
+ 'label' => $this->view->title,
+ ))->activate('addlist');
+ }
+
+ $form = $this->view->form = $this->loadForm('directorDatalist')
+ ->setSuccessUrl('director/data/lists')
+ ->setDb($this->db());
+
+ if ($edit) {
+ $form->loadObject($id);
+ }
+
+ $form->handleRequest();
+
+ $this->render('object/form', null, true);
+ }
+
+
+ public function fieldsAction()
+ {
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add'),
+ 'director/datafield/add',
+ null,
+ array('class' => 'icon-plus')
+ );
+
+ $this->setDataTabs()->activate('datafield');
+ $this->view->title = $this->translate('Data fields');
+ $this->prepareAndRenderTable('datafield');
+ $this->provideFilterEditorForTable($this->view->table);
+ }
+
+ public function listentryAction()
+ {
+ $this->view->stayHere = true;
+
+ $url = $this->getRequest()->getUrl();
+ $entryName = $url->shift('entry_name');
+ $list = DirectorDatalist::load($url->shift('list_id'), $this->db());
+ $listId = $list->id;
+
+ $form = $this->view->form = $this->loadForm('directorDatalistentry')
+ ->setSuccessUrl('director/data/listentry?list_id=' . $listId)
+ ->setList($list)
+ ->setDb($this->db());
+
+ if ($entryName) {
+ $form->loadObject(array(
+ 'list_id' => $listId,
+ 'entry_name' => $entryName
+ ));
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('back'),
+ 'director/data/listentry' . '?list_id=' . $listId,
+ null,
+ array('class' => 'icon-left-big')
+ );
+ }
+
+ $form->handleRequest();
+
+
+ $this->view->title = $this->translate('List entries')
+ . ': ' . $list->list_name;
+ $this->getTabs()->add('editlist', array(
+ 'url' => 'director/data/list' . '?id=' . $listId,
+ 'label' => $this->translate('Edit list'),
+ ))->add('datalistentry', array(
+ 'url' => 'director/data/listentry' . '?list_id=' . $listId,
+ 'label' => $this->view->title,
+ ))->activate('datalistentry');
+
+ $this->prepareTable('datalistEntry')->setList($list);
+ $this->setViewScript('objects/table');
+ }
+
+ protected function prepareTable($name)
+ {
+ $table = $this->loadTable($name)->setConnection($this->db());
+ $this->view->filterEditor = $table->getFilterEditor($this->getRequest());
+ $this->view->table = $this->applyPaginationLimits($table);
+ return $table;
+ }
+
+ protected function prepareAndRenderTable($name)
+ {
+ $this->prepareTable($name);
+ $this->setViewScript('objects/table');
+ }
+}
diff --git a/application/controllers/DatafieldController.php b/application/controllers/DatafieldController.php
new file mode 100644
index 0000000..d0c8c69
--- /dev/null
+++ b/application/controllers/DatafieldController.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class DatafieldController extends ActionController
+{
+ public function addAction()
+ {
+ $this->indexAction();
+ }
+
+ public function editAction()
+ {
+ $this->indexAction();
+ }
+
+ public function indexAction()
+ {
+ $edit = false;
+
+ if ($id = $this->params->get('id')) {
+ $edit = true;
+ }
+
+ $form = $this->view->form = $this->loadForm('directorDatafield')
+ ->setSuccessUrl('director/data/fields')
+ ->setDb($this->db());
+
+ if ($edit) {
+ $form->loadObject($id);
+ $this->view->title = sprintf(
+ $this->translate('Modify %s'),
+ $form->getObject()->varname
+ );
+ $this->singleTab($this->translate('Edit a field'));
+ } else {
+ $this->view->title = $this->translate('Add a new Data Field');
+ $this->singleTab($this->translate('New field'));
+ }
+
+ $form->handleRequest();
+ $this->render('object/form', null, true);
+ }
+}
diff --git a/application/controllers/DeploymentController.php b/application/controllers/DeploymentController.php
new file mode 100644
index 0000000..100c721
--- /dev/null
+++ b/application/controllers/DeploymentController.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\DirectorDeploymentLog;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Util;
+
+class DeploymentController extends ActionController
+{
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/deploy');
+ }
+
+ public function indexAction()
+ {
+ $this->view->title = $this->translate('Deployment details');
+
+ $deploymentId = $this->params->get('id');
+ $this->view->deployment = $deployment = DirectorDeploymentLog::load(
+ $deploymentId,
+ $this->db()
+ );
+ $this->view->config_checksum = Util::binary2hex($deployment->config_checksum);
+ $this->view->config = IcingaConfig::load($deployment->config_checksum, $this->db());
+
+ $tabs = $this->getTabs()->add('deployment', array(
+ 'label' => $this->translate('Deployment'),
+ 'url' => $this->getRequest()->getUrl()
+ ))->activate('deployment');
+
+ if ($deployment->config_checksum !== null) {
+ $tabs->add('config', array(
+ 'label' => $this->translate('Config'),
+ 'url' => 'director/config/files',
+ 'urlParams' => array(
+ 'checksum' => $this->view->config_checksum,
+ 'deployment_id' => $deploymentId
+ )
+ ));
+ }
+ }
+}
diff --git a/application/controllers/EndpointController.php b/application/controllers/EndpointController.php
new file mode 100644
index 0000000..5e64e2b
--- /dev/null
+++ b/application/controllers/EndpointController.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class EndpointController extends ObjectController
+{
+ public function init()
+ {
+ $this->assertPermission('director/inspect');
+ parent::init();
+ if ($this->object && $this->object->hasApiUser()) {
+ $params['endpoint'] = $this->object->object_name;
+
+ $this->getTabs()->add('inspect', array(
+ 'url' => 'director/inspect/types',
+ 'urlParams' => $params,
+ 'label' => $this->translate('Inspect')
+ ));
+ }
+ }
+}
diff --git a/application/controllers/EndpointsController.php b/application/controllers/EndpointsController.php
new file mode 100644
index 0000000..40501a4
--- /dev/null
+++ b/application/controllers/EndpointsController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class EndpointsController extends ObjectsController
+{
+}
diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php
new file mode 100644
index 0000000..6b087a7
--- /dev/null
+++ b/application/controllers/HostController.php
@@ -0,0 +1,410 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Exception\NestingError;
+use Icinga\Module\Director\IcingaConfig\AgentWizard;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+use Icinga\Module\Director\Util;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Web\Url;
+
+class HostController extends ObjectController
+{
+ public function init()
+ {
+ parent::init();
+ if ($this->object) {
+ $tabs = $this->getTabs();
+ $tabs->add('services', array(
+ 'url' => 'director/host/services',
+ 'urlParams' => array('name' => $this->object->object_name),
+ 'label' => 'Services'
+ ));
+ try {
+ if ($this->object->object_type === 'object'
+ && $this->object->getResolvedProperty('has_agent') === 'y'
+ ) {
+ $tabs->add('agent', array(
+ 'url' => 'director/host/agent',
+ 'urlParams' => array('name' => $this->object->object_name),
+ 'label' => 'Agent'
+ ));
+ }
+ } catch (NestingError $e) {
+ // Ignore nesting errors
+ }
+ }
+ }
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/hosts');
+ }
+
+ public function editAction()
+ {
+ parent::editAction();
+ $host = $this->object;
+ $mon = $this->monitoring();
+ if ($host->isObject() && $mon->isAvailable() && $mon->hasHost($host->object_name)) {
+ $this->view->actionLinks .= ' ' . $this->view->qlink(
+ $this->translate('Show'),
+ 'monitoring/host/show',
+ array('host' => $host->object_name),
+ array(
+ 'class' => 'icon-globe critical',
+ 'data-base-target' => '_next'
+ )
+ );
+ }
+ }
+
+ public function servicesAction()
+ {
+ $db = $this->db();
+ $host = $this->object;
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add service'),
+ 'director/service/add',
+ array('host' => $host->object_name),
+ array('class' => 'icon-plus')
+ ) . ' ' . $this->view->qlink(
+ $this->translate('Add service set'),
+ 'director/serviceset/add',
+ array('host' => $host->object_name),
+ array('class' => 'icon-plus')
+ );
+
+ $this->getTabs()->activate('services');
+ $this->view->title = sprintf(
+ $this->translate('Services: %s'),
+ $host->object_name
+ );
+
+ $resolver = $this->object->templateResolver();
+
+ $tables = array();
+ $table = $this->loadTable('IcingaHostService')
+ ->setHost($host)
+ ->setTitle($this->translate('Individual Service objects'))
+ ->enforceFilter('host_id', $host->id)
+ ->setConnection($db);
+
+ if (count($table)) {
+ $tables[0] = $table;
+ }
+
+ if ($applied = $host->vars()->get($db->settings()->magic_apply_for)) {
+ $table = $this->loadTable('IcingaHostAppliedForService')
+ ->setHost($host)
+ ->setDictionary($applied)
+ ->setTitle($this->translate('Generated from host vars'));
+
+ if (count($table)) {
+ $tables[1] = $table;
+ }
+ }
+
+ $parents = $resolver->fetchResolvedParents();
+ foreach ($parents as $parent) {
+ $table = $this->loadTable('IcingaHostService')
+ ->setHost($parent)
+ ->setInheritedBy($host)
+ ->enforceFilter('host_id', $parent->id)
+ ->setConnection($db);
+ if (! count($table)) {
+ continue;
+ }
+
+ // dup dup
+ $title = sprintf(
+ 'Inherited from %s',
+ $parent->object_name
+ );
+
+ $tables[$title] = $table->setTitle($title);
+ }
+
+ $this->addHostServiceSetTables($host, $tables);
+ foreach ($parents as $parent) {
+ $this->addHostServiceSetTables($host, $tables);
+ }
+
+ $title = $this->translate('Applied services');
+ $table = $this->loadTable('IcingaHostAppliedServices')
+ ->setHost($host)
+ ->setTitle($title)
+ ->setConnection($db);
+
+ $tables[$title] = $table;
+
+ $this->view->tables = $tables;
+ }
+
+ protected function addHostServiceSetTables(IcingaHost $host, & $tables)
+ {
+ $db = $this->db();
+
+ $query = $db->getDbAdapter()->select()
+ ->from(
+ array('ss' => 'icinga_service_set'),
+ 'ss.*'
+ )->join(
+ array('hsi' => 'icinga_service_set_inheritance'),
+ 'hsi.parent_service_set_id = ss.id',
+ array()
+ )->join(
+ array('hs' => 'icinga_service_set'),
+ 'hs.id = hsi.service_set_id',
+ array()
+ )->where('hs.host_id = ?', $host->id);
+
+ $sets = IcingaServiceSet::loadAll($db, $query, 'object_name');
+
+ foreach ($sets as $name => $set) {
+ $title = sprintf($this->translate('%s (Service set)'), $name);
+ $table = $this->loadTable('IcingaServiceSetService')
+ ->setServiceSet($set)
+ ->setHost($host)
+ ->setTitle($title)
+ ->setConnection($db);
+
+ $tables[$title] = $table;
+ }
+ }
+
+ public function appliedserviceAction()
+ {
+ $db = $this->db();
+ /** @var IcingaHost $host */
+ $host = $this->object;
+ $serviceId = $this->params->get('service_id');
+ $parent = IcingaService::loadWithAutoIncId($serviceId, $db);
+ $serviceName = $parent->object_name;
+
+ $service = IcingaService::create(array(
+ 'imports' => $parent,
+ 'object_type' => 'apply',
+ 'object_name' => $serviceName,
+ 'host_id' => $host->id,
+ 'vars' => $host->getOverriddenServiceVars($serviceName),
+ ), $db);
+
+ $this->view->title = sprintf(
+ $this->translate('Applied service: %s'),
+ $serviceName
+ );
+
+ $this->view->form = $this->loadForm('IcingaService')
+ ->setDb($db)
+ ->setHost($host)
+ ->setApplyGenerated($parent)
+ ->setObject($service)
+ ;
+
+ $this->commonForServices();
+ }
+
+ public function inheritedserviceAction()
+ {
+ $db = $this->db();
+ $host = $this->object;
+ $serviceName = $this->params->get('service');
+ $from = IcingaHost::load($this->params->get('inheritedFrom'), $this->db());
+
+ $parent = IcingaService::load(
+ array(
+ 'object_name' => $serviceName,
+ 'host_id' => $from->id
+ ),
+ $this->db()
+ );
+
+ // TODO: we want to eventually show the host template name, doesn't work
+ // as template resolution would break.
+ // $parent->object_name = $from->object_name;
+
+ $service = IcingaService::create(array(
+ 'object_type' => 'apply',
+ 'object_name' => $serviceName,
+ 'host_id' => $host->id,
+ 'imports' => array($parent),
+ 'vars' => $host->getOverriddenServiceVars($serviceName),
+ ), $db);
+
+ $this->view->title = sprintf(
+ $this->translate('Inherited service: %s'),
+ $serviceName
+ );
+
+ $this->view->form = $this->loadForm('IcingaService')
+ ->setDb($db)
+ ->setHost($host)
+ ->setInheritedFrom($from->object_name)
+ ->setObject($service);
+
+ // TODO: figure out whether this has any effect
+ // $this->view->form->setResolvedImports();
+ $this->commonForServices();
+ }
+
+ public function removesetAction()
+ {
+ // TODO: clean this up, use POST
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()->from(
+ array('ss' => 'icinga_service_set'),
+ array('id' => 'ss.id')
+ )->join(
+ array('si' => 'icinga_service_set_inheritance'),
+ 'si.service_set_id = ss.id',
+ array()
+ )->where('si.parent_service_set_id = ?', $this->params->get('setId'))
+ ->where('ss.host_id = ?', $this->object->id);
+
+ IcingaServiceSet::loadWithAutoIncId($db->fetchOne($query), $this->db())->delete();
+ $this->redirectNow(
+ Url::fromPath('director/host/services', array(
+ 'name' => $this->object->getObjectName()
+ ))
+ );
+ }
+
+ public function servicesetserviceAction()
+ {
+ $db = $this->db();
+ /** @var IcingaHost $host */
+ $host = $this->object;
+ $serviceName = $this->params->get('service');
+ $set = IcingaServiceSet::load($this->params->get('set'), $db);
+
+ $service = IcingaService::load(
+ array(
+ 'object_name' => $serviceName,
+ 'service_set_id' => $set->get('id')
+ ),
+ $this->db()
+ );
+ $service = IcingaService::create(array(
+ 'object_type' => 'apply',
+ 'object_name' => $serviceName,
+ 'host_id' => $host->id,
+ 'imports' => array($service),
+ 'vars' => $host->getOverriddenServiceVars($serviceName),
+ ), $db);
+
+ // $set->copyVarsToService($service);
+ $this->view->title = sprintf(
+ $this->translate('%s on %s (from set: %s)'),
+ $serviceName,
+ $host->getObjectName(),
+ $set->getObjectName()
+ );
+
+ $this->getTabs()->activate('services');
+
+ $this->view->form = $this->loadForm('IcingaService')
+ ->setDb($db)
+ ->setHost($host)
+ ->setServiceSet($set)
+ ->setObject($service);
+ // $this->view->form->setResolvedImports();
+ $this->view->form->handleRequest();
+ $this->commonForServices();
+ }
+
+ protected function commonForServices()
+ {
+ $host = $this->object;
+ $this->view->actionLinks = $this->view->qlink(
+ $this->translate('back'),
+ 'director/host/services',
+ array('name' => $host->object_name),
+ array('class' => 'icon-left-big')
+ );
+ $this->getTabs()->activate('services');
+ $this->view->form->handleRequest();
+ $this->setViewScript('object/form');
+ }
+
+ public function agentAction()
+ {
+ if ($os = $this->params->get('download')) {
+ $wizard = new AgentWizard($this->object);
+ $wizard->setTicketSalt($this->api()->getTicketSalt());
+
+ switch ($os) {
+ case 'windows-kickstart':
+ $ext = 'ps1';
+ $script = preg_replace('/\n/', "\r\n", $wizard->renderWindowsInstaller());
+ break;
+ case 'linux':
+ $ext = 'bash';
+ $script = $wizard->renderLinuxInstaller();
+ break;
+ default:
+ throw new NotFoundError('There is no kickstart helper for %s', $os);
+ }
+
+ header('Content-type: application/octet-stream');
+ header('Content-Disposition: attachment; filename=icinga2-agent-kickstart.' . $ext);
+ echo $script;
+ exit;
+ }
+
+ $this->gracefullyActivateTab('agent');
+ $this->view->title = 'Agent deployment instructions';
+ // TODO: Fail when no ticket
+ $this->view->certname = $this->object->object_name;
+
+ try {
+ $this->view->ticket = Util::getIcingaTicket(
+ $this->view->certname,
+ $this->api()->getTicketSalt()
+ );
+
+ $wizard = $this->view->wizard = new AgentWizard($this->object);
+ $wizard->setTicketSalt($this->api()->getTicketSalt());
+ $this->view->windows = $wizard->renderWindowsInstaller();
+ $this->view->linux = $wizard->renderLinuxInstaller();
+ } catch (Exception $e) {
+ $this->view->ticket = 'ERROR';
+ $this->view->error = sprintf(
+ $this->translate(
+ 'A ticket for this agent could not have been requested from'
+ . ' your deployment endpoint: %s'
+ ),
+ $e->getMessage()
+ );
+ }
+
+ $this->view->master = $this->db()->getDeploymentEndpointName();
+ $this->view->masterzone = $this->db()->getMasterZoneName();
+ $this->view->globalzone = $this->db()->getDefaultGlobalZoneName();
+ }
+
+ public function ticketAction()
+ {
+ if (! $this->getRequest()->isApiRequest() || ! $this->object) {
+ throw new NotFoundError('Not found');
+ }
+
+ $host = $this->object;
+ if ($host->getResolvedProperty('has_agent') !== 'y') {
+ throw new NotFoundError('The host "%s" is not an agent', $host->object_name);
+ }
+
+ return $this->sendJson(
+ Util::getIcingaTicket(
+ $host->object_name,
+ $this->api()->getTicketSalt()
+ )
+ );
+ }
+}
diff --git a/application/controllers/HostgroupController.php b/application/controllers/HostgroupController.php
new file mode 100644
index 0000000..aa4cc51
--- /dev/null
+++ b/application/controllers/HostgroupController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class HostgroupController extends ObjectController
+{
+}
diff --git a/application/controllers/HostgroupsController.php b/application/controllers/HostgroupsController.php
new file mode 100644
index 0000000..2b4b417
--- /dev/null
+++ b/application/controllers/HostgroupsController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class HostgroupsController extends ObjectsController
+{
+}
diff --git a/application/controllers/HostsController.php b/application/controllers/HostsController.php
new file mode 100644
index 0000000..468976a
--- /dev/null
+++ b/application/controllers/HostsController.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class HostsController extends ObjectsController
+{
+ protected $multiEdit = array(
+ 'imports',
+ 'groups',
+ 'disabled'
+ );
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/hosts');
+ }
+}
diff --git a/application/controllers/ImportrunController.php b/application/controllers/ImportrunController.php
new file mode 100644
index 0000000..9476d32
--- /dev/null
+++ b/application/controllers/ImportrunController.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\ImportRun;
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class ImportrunController extends ActionController
+{
+ public function indexAction()
+ {
+ $db = $this->db();
+ $id = $this->getRequest()->getUrl()->getParams()->shift('id');
+ $importRun = ImportRun::load($id, $db);
+ $url = clone($this->getRequest()->getUrl());
+ $chosenColumns = $this->getRequest()->getUrl()->shift('chosenColumns');
+
+ $this->view->title = $this->translate('Import run');
+ $this->getTabs()->add('importrun', array(
+ 'label' => $this->view->title,
+ 'url' => $url
+ ))->activate('importrun');
+
+ $table = $this
+ ->loadTable('importedrows')
+ ->setConnection($db)
+ ->setImportRun($importRun);
+
+ if ($chosenColumns) {
+ $table->setColumns(preg_split('/,/', $chosenColumns, -1, PREG_SPLIT_NO_EMPTY));
+ }
+
+ $this->view->table = $this->applyPaginationLimits($table);
+ $this->view->filterEditor = $table->getFilterEditor($this->getRequest());
+ }
+}
diff --git a/application/controllers/ImportsourceController.php b/application/controllers/ImportsourceController.php
new file mode 100644
index 0000000..dcd4fb8
--- /dev/null
+++ b/application/controllers/ImportsourceController.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Import\Import;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+
+class ImportsourceController extends ActionController
+{
+ public function indexAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $id = $this->params->get('id');
+ $this->prepareTabs($id)->activate('show');
+ $source = $this->view->source = ImportSource::load($id, $this->db());
+ $this->view->title = sprintf(
+ $this->translate('Import source: %s'),
+ $source->source_name
+ );
+
+ $this->view->checkForm = $this
+ ->loadForm('ImportCheck')
+ ->setImportSource($source)
+ ->handleRequest();
+
+ $this->view->runForm = $this
+ ->loadForm('ImportRun')
+ ->setImportSource($source)
+ ->handleRequest();
+ }
+
+ public function addAction()
+ {
+ $this->editAction();
+ }
+
+ public function editAction()
+ {
+ $id = $this->params->get('id');
+
+ $form = $this->view->form = $this->loadForm('importSource')->setDb($this->db());
+
+ if ($id) {
+ $form->loadObject($id)->setListUrl('director/list/importsource');
+ $this->prepareTabs($id)->activate('edit');
+ $this->view->title = $this->translate('Edit import source');
+ } else {
+ $form->setSuccessUrl('director/list/importsource');
+ $this->view->title = $this->translate('Add import source');
+ $this->prepareTabs()->activate('add');
+ }
+
+ $form->handleRequest();
+ $this->setViewScript('object/form');
+ }
+
+ public function previewAction()
+ {
+ $id = $this->params->get('id');
+
+ $source = ImportSource::load($id, $this->db());
+ $this->prepareTabs($id)->activate('preview');
+
+ $this->view->title = sprintf(
+ $this->translate('Import source preview: "%s"'),
+ $source->source_name
+ );
+
+ $this->view->table = $this->applyPaginationLimits(
+ $this->loadTable('importsourceHook')
+ ->setConnection($this->db())
+ ->setImportSource($source)
+ );
+ $this->setViewScript('list/table');
+ }
+
+ public function modifierAction()
+ {
+ $this->view->stayHere = true;
+ $id = $this->params->get('source_id');
+ $this->prepareTabs($id)->activate('modifier');
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add property modifier'),
+ 'director/importsource/addmodifier',
+ array('source_id' => $id),
+ array('class' => 'icon-plus')
+ );
+
+ $this->view->title = $this->translate('Property modifiers');
+ $this->view->table = $this->loadTable('propertymodifier')
+ ->enforceFilter(Filter::where('source_id', $id))
+ ->setConnection($this->db());
+ $this->setViewScript('list/table');
+ }
+
+ public function historyAction()
+ {
+ $url = $this->getRequest()->getUrl();
+ $id = $url->shift('id');
+ if ($url->shift('action') === 'remove') {
+ $this->view->form = $this->loadForm('removeImportrun');
+ }
+
+ $this->prepareTabs($id)->activate('history');
+ $this->view->title = $this->translate('Import run history');
+
+ // TODO: temporarily disabled, find a better place for stats:
+ // $this->view->stats = $this->db()->fetchImportStatistics();
+ $this->prepareTable('importrun');
+ $this->view->table->enforceFilter(Filter::where('source_id', $id));
+ }
+
+ public function editmodifierAction()
+ {
+ $this->addmodifierAction();
+ }
+
+ public function addmodifierAction()
+ {
+ $this->view->stayHere = true;
+ $edit = false;
+
+ if ($id = $this->params->get('id')) {
+ $edit = true;
+ }
+
+ $form = $this->view->form = $this->loadForm('importRowModifier')->setDb($this->db());
+
+ if ($edit) {
+ $form->loadObject($id);
+ $source_id = $form->getObject()->source_id;
+ $form->setSource(ImportSource::load($source_id, $this->db()));
+ } elseif ($source_id = $this->params->get('source_id')) {
+ $form->setSource(ImportSource::load($source_id, $this->db()));
+ }
+ $form->setSuccessUrl('director/importsource/modifier', array('source_id' => $source_id));
+
+ $form->handleRequest();
+
+ $tabs = $this->prepareTabs($source_id)->activate('modifier');
+
+ $this->view->title = $this->translate('Modifier'); // add/edit
+ $this->view->table = $this->loadTable('propertymodifier')
+ ->enforceFilter(Filter::where('source_id', $source_id))
+ ->setConnection($this->db());
+
+ $this->setViewScript('list/table');
+ }
+
+ protected function prepareTabs($id = null)
+ {
+ $tabs = $this->getTabs();
+
+ if ($id) {
+ $tabs->add('show', array(
+ 'url' => 'director/importsource' . '?id=' . $id,
+ 'label' => $this->translate('Import source'),
+ ))->add('edit', array(
+ 'url' => 'director/importsource/edit' . '?id=' . $id,
+ 'label' => $this->translate('Modify'),
+ ))->add('modifier', array(
+ 'url' => 'director/importsource/modifier' . '?source_id=' . $id,
+ 'label' => $this->translate('Modifiers'),
+ ))->add('history', array(
+ 'url' => 'director/importsource/history' . '?id=' . $id,
+ 'label' => $this->translate('History'),
+ ))->add('preview', array(
+ 'url' => 'director/importsource/preview' . '?id=' . $id,
+ 'label' => $this->translate('Preview'),
+ ));
+ } else {
+ $tabs->add('add', array(
+ 'url' => 'director/importsource/add',
+ 'label' => $this->translate('New import source'),
+ ))->activate('add');
+ }
+
+ return $tabs;
+ }
+}
diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php
new file mode 100644
index 0000000..f9c25ec
--- /dev/null
+++ b/application/controllers/IndexController.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use Icinga\Module\Director\Db\Migrations;
+
+class IndexController extends DashboardController
+{
+ public function indexAction()
+ {
+ $this->view->dashboards = array();
+
+ $this->setViewScript('dashboard/index');
+
+ if ($this->Config()->get('db', 'resource')) {
+ $migrations = new Migrations($this->db());
+
+ if ($migrations->hasSchema()) {
+ if (!$this->hasDeploymentEndpoint()) {
+ $this->showKickstartForm();
+ }
+ } else {
+ $this->showKickstartForm();
+ return;
+ }
+
+ if ($migrations->hasPendingMigrations()) {
+ $this->view->form = $this
+ ->loadForm('applyMigrations')
+ ->setMigrations($migrations)
+ ->handleRequest();
+ }
+
+ parent::indexAction();
+ } else {
+ $this->showKickstartForm();
+ }
+ }
+
+ protected function showKickstartForm()
+ {
+ $this->singleTab($this->translate('Kickstart'));
+ $this->view->form = $this->loadForm('kickstart')->handleRequest();
+ }
+
+ protected function hasDeploymentEndpoint()
+ {
+ try {
+ $this->view->hasDeploymentEndpoint = $this->db()->hasDeploymentEndpoint();
+ } catch (Exception $e) {
+ return false;
+ }
+
+ return $this->view->hasDeploymentEndpoint;
+ }
+}
diff --git a/application/controllers/InspectController.php b/application/controllers/InspectController.php
new file mode 100644
index 0000000..5ef958b
--- /dev/null
+++ b/application/controllers/InspectController.php
@@ -0,0 +1,130 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class InspectController extends ActionController
+{
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/inspect');
+ }
+
+ public function typesAction()
+ {
+ $api = $this->api();
+ $params = array('name' => $this->view->endpoint);
+ $this->getTabs()->add('modify', array(
+ 'url' => 'director/endpoint',
+ 'urlParams' => $params,
+ 'label' => $this->translate('Endpoint')
+ ))->add('render', array(
+ 'url' => 'director/endpoint/render',
+ 'urlParams' => $params,
+ 'label' => $this->translate('Preview'),
+ ))->add('history', array(
+ 'url' => 'director/endpoint/history',
+ 'urlParams' => $params,
+ 'label' => $this->translate('History')
+ ))->add('inspect', array(
+ 'url' => $this->getRequest()->getUrl(),
+ 'label' => $this->translate('Inspect')
+ ))->activate('inspect');
+
+ $this->view->title = sprintf(
+ $this->translate('Icinga2 Objects: %s'),
+ $this->view->endpoint
+ );
+ $types = $api->getTypes();
+ $rootNodes = array();
+ foreach ($types as $name => $type) {
+ if (property_exists($type, 'base')) {
+ $base = $type->base;
+ if (! property_exists($types[$base], 'children')) {
+ $types[$base]->children = array();
+ }
+
+ $types[$base]->children[$name] = $type;
+ } else {
+ $rootNodes[$name] = $type;
+ }
+ }
+ $this->view->types = $rootNodes;
+ }
+
+ public function typeAction()
+ {
+ $typeName = $this->params->get('name');
+ $this->singleTab($this->translate('Inspect - object list'));
+ $this->view->title = sprintf(
+ $this->translate('Object type "%s"'),
+ $typeName
+ );
+ $this->view->type = $type = $this->api()->getType($typeName);
+ if ($type->abstract) {
+ return;
+ }
+
+ if (! empty($type->fields)) {
+ $this->view->objects = $this->api()->listObjects(
+ $typeName,
+ $type->plural_name
+ );
+ }
+ }
+
+ public function objectAction()
+ {
+ $this->singleTab($this->translate('Object Inspection'));
+ $this->view->object = $this->api()->getObject(
+ $this->params->get('name'),
+ $this->params->get('plural')
+ );
+ }
+
+ public function commandsAction()
+ {
+ $db = $this->db();
+ echo '<pre>';
+ foreach ($this->api()->setDb($db)->getCheckCommandObjects() as $object) {
+ if (! $object::exists($object->object_name, $db)) {
+ // var_dump($object->store());
+ echo $object;
+ }
+ }
+ echo '</pre>';
+ exit;
+ }
+
+ public function zonesAction()
+ {
+ $db = $this->db();
+ echo '<pre>';
+ foreach ($this->api()->setDb($db)->getZoneObjects() as $zone) {
+ if (! $zone::exists($zone->object_name, $db)) {
+ // var_dump($zone->store());
+ echo $zone;
+ }
+ }
+ echo '</pre>';
+ exit;
+ }
+
+ public function statusAction()
+ {
+ $this->view->status = $status = $this->api()->getStatus();
+ print_r($status);
+ exit;
+ }
+
+ protected function api($endpointName = null)
+ {
+ $this->view->endpoint = $this->params->get('endpoint');
+ if ($this->view->endpoint === null) {
+ $this->view->endpoint = $this->db()->getDeploymentEndpointName();
+ }
+
+ return parent::api($this->view->endpoint);
+ }
+}
diff --git a/application/controllers/JobController.php b/application/controllers/JobController.php
new file mode 100644
index 0000000..0599134
--- /dev/null
+++ b/application/controllers/JobController.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\DirectorJob;
+use Icinga\Data\Filter\Filter;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+
+class JobController extends ActionController
+{
+ public function addAction()
+ {
+ $this->indexAction();
+ }
+
+ public function indexAction()
+ {
+ if (! ($id = $this->params->get('id'))) {
+ return $this->editAction();
+ }
+
+ $this->prepareTabs($id)->activate('show');
+ $this->view->job = DirectorJob::load($id, $this->db());
+ $this->view->title = sprintf(
+ $this->translate('Job: %s'),
+ $this->view->job->job_name
+ );
+ }
+
+ public function runAction()
+ {
+ // TODO: Form, POST
+ $id = $this->params->get('id');
+ $job = DirectorJob::load($id, $this->db());
+ if ($job->run()) {
+ Notification::success('Job has successfully been completed');
+ $this->redirectNow(
+ Url::fromPath(
+ 'director/job',
+ array('id' => $id)
+ )
+ );
+ } else {
+ Notification::success('Job run failed');
+ }
+ }
+
+ public function editAction()
+ {
+ $form = $this->view->form = $this->loadForm('directorJob')
+ ->setSuccessUrl('director/job')
+ ->setDb($this->db());
+
+ if ($id = $this->params->get('id')) {
+ $this->prepareTabs($id)->activate('edit');
+ $form->loadObject($id);
+ $this->view->title = sprintf(
+ $this->translate('Job %s'),
+ $form->getObject()->job_name
+ );
+ } else {
+ $this->view->title = $this->translate('Add job');
+ $this->prepareTabs()->activate('add');
+ }
+
+ $form->handleRequest();
+ $this->setViewScript('object/form');
+ }
+
+ protected function prepareTabs($id = null)
+ {
+ if ($id) {
+ return $this->getTabs()->add('show', array(
+ 'url' => 'director/job',
+ 'urlParams' => array('id' => $id),
+ 'label' => $this->translate('Job'),
+ ))->add('edit', array(
+ 'url' => 'director/job/edit',
+ 'urlParams' => array('id' => $id),
+ 'label' => $this->translate('Config'),
+ ));
+ } else {
+ return $this->getTabs()->add('add', array(
+ 'url' => 'director/job/add',
+ 'label' => $this->translate('Add a job'),
+ ));
+ }
+ }
+}
diff --git a/application/controllers/JobsController.php b/application/controllers/JobsController.php
new file mode 100644
index 0000000..c1b1ad1
--- /dev/null
+++ b/application/controllers/JobsController.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class JobsController extends ActionController
+{
+ public function indexAction()
+ {
+ $this->setAutoRefreshInterval(10);
+ $this->view->title = $this->translate('Jobs');
+ $this->setImportTabs()->activate('jobs');
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add'),
+ 'director/job',
+ null,
+ array('class' => 'icon-plus')
+ );
+
+ $this->view->table = $this->applyPaginationLimits(
+ $this->loadTable('job')
+ ->setConnection($this->db())
+ );
+
+ $this->setViewScript('list/table');
+ }
+}
diff --git a/application/controllers/KickstartController.php b/application/controllers/KickstartController.php
new file mode 100644
index 0000000..6e2c297
--- /dev/null
+++ b/application/controllers/KickstartController.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+
+class KickstartController extends DashboardController
+{
+ public function indexAction()
+ {
+ $this->singleTab($this->view->title = $this->translate('Kickstart'));
+ $form = $this->view->form = $this->loadForm('kickstart');
+ try {
+ $form->setEndpoint($this->db()->getDeploymentEndpoint());
+ } catch (Exception $e) {
+ // Silently ignore DB errors
+ }
+
+ $form->handleRequest();
+ }
+}
diff --git a/application/controllers/ListController.php b/application/controllers/ListController.php
new file mode 100644
index 0000000..4f02989
--- /dev/null
+++ b/application/controllers/ListController.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Exception;
+
+class ListController extends ActionController
+{
+ public function importsourceAction()
+ {
+ $this->setAutoRefreshInterval(10);
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add import source'),
+ 'director/importsource/add',
+ null,
+ array('class' => 'icon-plus')
+ );
+
+ $this->setImportTabs()->activate('importsource');
+ $this->view->title = $this->translate('Import source');
+ $this->prepareAndRenderTable('importsource');
+ }
+
+ public function syncruleAction()
+ {
+ $this->setAutoRefreshInterval(10);
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add sync rule'),
+ 'director/syncrule/add',
+ null,
+ array('class' => 'icon-plus')
+ );
+
+ $this->setImportTabs()->activate('syncrule');
+ $this->view->title = $this->translate('Sync rule');
+ $this->view->table = $this->loadTable('syncrule')->setConnection($this->db());
+ $this->render('table');
+ }
+}
diff --git a/application/controllers/NotificationController.php b/application/controllers/NotificationController.php
new file mode 100644
index 0000000..72f52bb
--- /dev/null
+++ b/application/controllers/NotificationController.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaNotification;
+use Icinga\Module\Director\Objects\IcingaService;
+
+class NotificationController extends ObjectController
+{
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/notifications');
+ }
+
+ public function init()
+ {
+ parent::init();
+ // TODO: Check if this is still needed, remove it otherwise
+ if ($this->object && $this->object->object_type === 'apply') {
+ if ($host = $this->params->get('host')) {
+ foreach ($this->getTabs()->getTabs() as $tab) {
+ $tab->getUrl()->setParam('host', $host);
+ }
+ }
+
+ if ($service = $this->params->get('service')) {
+ foreach ($this->getTabs()->getTabs() as $tab) {
+ $tab->getUrl()->setParam('service', $service);
+ }
+ }
+ }
+ }
+
+ protected function loadObject()
+ {
+ if ($this->object === null) {
+ if ($name = $this->params->get('name')) {
+ $params = array('object_name' => $name);
+ $db = $this->db();
+
+ if ($hostname = $this->params->get('host')) {
+ $this->view->host = IcingaHost::load($hostname, $db);
+ $params['host_id'] = $this->view->host->id;
+ }
+
+ if ($service = $this->params->get('service')) {
+ $this->view->service = IcingaService::load($service, $db);
+ $params['service_id'] = $this->view->service->id;
+ }
+
+ $this->object = IcingaNotification::load($params, $db);
+ } else {
+ parent::loadObject();
+ }
+ }
+
+ return $this->object;
+ }
+}
diff --git a/application/controllers/NotificationsController.php b/application/controllers/NotificationsController.php
new file mode 100644
index 0000000..8e55aa3
--- /dev/null
+++ b/application/controllers/NotificationsController.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\NewObjectsController;
+
+class NotificationsController extends NewObjectsController
+{
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/notifications');
+ }
+}
diff --git a/application/controllers/SchemaController.php b/application/controllers/SchemaController.php
new file mode 100644
index 0000000..6028a33
--- /dev/null
+++ b/application/controllers/SchemaController.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class SchemaController extends ActionController
+{
+ protected $schema;
+
+ public function init()
+ {
+ $this->schemas = array(
+ 'mysql' => $this->translate('MySQL schema'),
+ 'pgsql' => $this->translate('PostgreSQL schema'),
+ );
+ }
+
+ protected function tabs()
+ {
+ $tabs = $this->getTabs();
+ foreach ($this->schemas as $type => $title) {
+ $tabs->add($type, array(
+ 'url' => 'director/schema/' . $type,
+ 'label' => $title,
+ ));
+ }
+ return $tabs;
+ }
+
+ public function mysqlAction()
+ {
+ $this->serveSchema('mysql');
+ }
+
+ public function pgsqlAction()
+ {
+ $this->serveSchema('pgsql');
+ }
+
+ protected function serveSchema($type)
+ {
+ $schema = file_get_contents(
+ sprintf(
+ '%s/schema/%s.sql',
+ $this->Module()->getBasedir(),
+ $type
+ )
+ );
+
+ if ($this->params->get('format') === 'sql') {
+ header('Content-type: application/octet-stream');
+ header('Content-Disposition: attachment; filename=' . $type . '.sql');
+ echo $schema;
+ exit;
+ // TODO: Shutdown
+ } else {
+ $this->tabs()->activate($type);
+ $this->view->title = $this->schemas[$type];
+ $this->view->schema = $schema;
+ $this->render('schema');
+ }
+ }
+}
diff --git a/application/controllers/ScreenshotController.php b/application/controllers/ScreenshotController.php
new file mode 100644
index 0000000..1957d3f
--- /dev/null
+++ b/application/controllers/ScreenshotController.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Exception\NotFoundError;
+use Icinga\Web\Controller;
+
+class ScreenshotController extends Controller
+{
+ public function indexAction()
+ {
+ $subdir = $this->getParam('subdir');
+ $file = $this->getParam('file');
+ $valid = '[A-z0-9][A-z0-9_-]*';
+ if (!preg_match('/^' . $valid . '$/', $subdir)
+ || !preg_match('/^' . $valid . '\.png$/', $file)
+ ) {
+ throw new NotFoundError('Not found');
+ }
+
+ $filename = sprintf(
+ '%s/doc/screenshot/director/%s/%s',
+ $this->Module()->getBaseDir(),
+ $subdir,
+ $file
+ );
+
+ if (file_exists($filename)) {
+ $this->getResponse()->setHeader('Content-Type', 'image/png', true);
+ $this->_helper->layout()->disableLayout();
+ $this->_helper->viewRenderer->setNoRender(true);
+ $fh = fopen($filename, 'r');
+ fpassthru($fh);
+ } else {
+ throw new NotFoundError('Not found: ' . $filename);
+ }
+ }
+}
diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php
new file mode 100644
index 0000000..5a4153a
--- /dev/null
+++ b/application/controllers/ServiceController.php
@@ -0,0 +1,241 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use Icinga\Module\Director\Forms\IcingaServiceForm;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\IcingaHost;
+
+class ServiceController extends ObjectController
+{
+ protected $host;
+
+ protected $set;
+
+ protected $apply;
+
+ protected function beforeTabs()
+ {
+ if ($this->host) {
+ $this->getTabs()->add('host', array(
+ 'url' => 'director/host',
+ 'urlParams' => array('name' => $this->host->object_name),
+ 'label' => $this->translate('Host'),
+ ));
+ }
+ }
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/hosts');
+ }
+
+ public function init()
+ {
+ if ($host = $this->params->get('host')) {
+ $this->host = IcingaHost::load($host, $this->db());
+ } elseif ($set = $this->params->get('set')) {
+ $this->set = IcingaServiceSet::load(array('object_name' => $set), $this->db());
+ } elseif ($apply = $this->params->get('apply')) {
+ $this->apply = IcingaService::load(
+ array('object_name' => $apply, 'object_type' => 'template'),
+ $this->db()
+ );
+ }
+
+ parent::init();
+
+ if ($this->object) {
+ if ($this->host) {
+ foreach ($this->getTabs()->getTabs() as $tab) {
+ $tab->getUrl()->setParam('host', $this->host->object_name);
+ }
+ }
+
+ if (! $this->set && $this->object->service_set_id) {
+ $this->set = $this->object->getRelated('service_set');
+ }
+ }
+
+ if ($this->host) {
+ $this->getTabs()->add('services', array(
+ 'url' => 'director/host/services',
+ 'urlParams' => array('name' => $this->host->object_name),
+ 'label' => $this->translate('Services'),
+ ));
+ } elseif ($this->set) {
+ $this->getTabs()->add('services', array(
+ 'url' => 'director/serviceset/services',
+ 'urlParams' => array('name' => $this->set->object_name),
+ 'label' => $this->translate('Services'),
+ ));
+ }
+ }
+
+ public function addAction()
+ {
+ parent::addAction();
+ if ($this->host) {
+ $this->view->title = $this->host->object_name . ': ' . $this->view->title;
+ } elseif ($this->set) {
+ $this->view->title = sprintf(
+ $this->translate('Add a service to "%s"'),
+ $this->set->object_name
+ );
+ } elseif ($this->apply) {
+ $this->view->title = sprintf(
+ $this->translate('Apply "%s"'),
+ $this->apply->object_name
+ );
+ }
+ }
+
+ /**
+ * @param IcingaServiceForm $form
+ */
+ protected function beforeHandlingAddRequest($form)
+ {
+ if ($this->apply) {
+ $form->createApplyRuleFor($this->apply);
+ }
+ }
+
+ public function futureoverviewIndexAction()
+ {
+ $object = $this->loadObject();
+ $title = $this->view->title = $object->object_name;
+ $this->singleTab($this->translate('Icinga Service Template'));
+ }
+
+ public function editAction()
+ {
+ $object = $this->object;
+
+ if ($this->host) {
+ $this->view->actionLinks = $this->view->qlink(
+ $this->translate('back'),
+ 'director/host/services',
+ array('name' => $this->host->object_name),
+ array('class' => 'icon-left-big')
+ );
+ }
+
+ if ($this->host && $object->usesVarOverrides()) {
+ $parent = IcingaService::create(array(
+ 'object_type' => 'template',
+ // TODO: => 'myself', -> There is no such import: "myself"
+ 'object_name' => $object->object_name,
+ 'vars' => $object->vars,
+ ), $this->db());
+
+ $object->vars = $this->host->getOverriddenServiceVars($object->object_name);
+ $object->imports()->add($parent);
+ }
+
+ $this->getTabs()->activate('modify');
+
+ $this->view->form = $form = $this->loadForm('icingaService')
+ ->setDb($this->db())
+ ->setObject($object);
+
+ $this->view->form->handleRequest();
+ $this->view->actionLinks .= $this->createCloneLink();
+
+ $this->view->title = $object->object_name;
+ if ($this->host) {
+ $this->view->subtitle = sprintf(
+ $this->translate('(on %s)'),
+ $this->host->object_name
+ );
+ }
+
+ try {
+ if ($object->isTemplate()
+ && $object->getResolvedProperty('check_command_id')
+ ) {
+ $this->view->actionLinks .= ' ' . $this->view->qlink(
+ 'Create apply-rule',
+ 'director/service/add',
+ array('apply' => $object->object_name),
+ array('class' => 'icon-plus')
+ );
+ }
+ } catch (Exception $e) {
+ // ignore the error, show no apply link
+ }
+
+ $this->setViewScript('object/form');
+ }
+
+ public function assignAction()
+ {
+ $service = $this->object;
+ $this->view->stayHere = true;
+
+ $this->view->actionLinks = $this->view->qlink(
+ $this->translate('back'),
+ $this->getRequest()->getUrl()->without('rule_id'),
+ null,
+ array('class' => 'icon-left-big')
+ );
+
+ $this->getTabs()->activate('applied');
+ $this->view->title = sprintf(
+ $this->translate('Apply: %s'),
+ $service->object_name
+ );
+ $this->view->table = $this->loadTable('IcingaAppliedService')
+ ->setService($service)
+ ->setConnection($this->db());
+
+ $this->setViewScript('objects/table');
+ }
+
+ public function loadForm($name)
+ {
+ $form = parent::loadForm($name);
+ if ($name === 'icingaService') {
+ if ($this->host) {
+ $form->setHost($this->host);
+ } elseif ($this->set) {
+ $form->setServiceSet($this->set)->setSuccessUrl(
+ 'director/serviceset/services',
+ array('name' => $this->set->object_name)
+ );
+ }
+ }
+
+ return $form;
+ }
+
+ protected function loadObject()
+ {
+ if ($this->object === null) {
+ if ($name = $this->params->get('name')) {
+ $params = array('object_name' => $name);
+ $db = $this->db();
+
+ if ($this->host) {
+ $this->view->host = $this->host;
+ $params['host_id'] = $this->host->id;
+ }
+
+ if ($this->set) {
+ $this->view->set = $this->set;
+ $params['service_set_id'] = $this->set->id;
+ }
+ $this->object = IcingaService::load($params, $db);
+ } else {
+ parent::loadObject();
+ }
+ }
+ $this->view->undeployedChanges = $this->countUndeployedChanges();
+ $this->view->totalUndeployedChanges = $this->db()
+ ->countActivitiesSinceLastDeployedConfig();
+
+ return $this->object;
+ }
+}
diff --git a/application/controllers/ServicegroupController.php b/application/controllers/ServicegroupController.php
new file mode 100644
index 0000000..b2fc50e
--- /dev/null
+++ b/application/controllers/ServicegroupController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class ServicegroupController extends ObjectController
+{
+}
diff --git a/application/controllers/ServicegroupsController.php b/application/controllers/ServicegroupsController.php
new file mode 100644
index 0000000..e5b73dd
--- /dev/null
+++ b/application/controllers/ServicegroupsController.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class ServicegroupsController extends ObjectsController
+{
+ public function init()
+ {
+ parent::init();
+ $this->view->tabs->remove('objects');
+ }
+}
diff --git a/application/controllers/ServicesController.php b/application/controllers/ServicesController.php
new file mode 100644
index 0000000..035cb18
--- /dev/null
+++ b/application/controllers/ServicesController.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class ServicesController extends ObjectsController
+{
+ public function init()
+ {
+ parent::init();
+ $this->view->tabs->remove('objects');
+ }
+
+ public function indexAction()
+ {
+ $r = $this->getRequest();
+ if ($r->getActionName() !== 'templates' && ! $this->getRequest()->isApiRequest()) {
+ $this->redirectNow('director/services/templates');
+ }
+
+ return parent::indexAction();
+ }
+}
diff --git a/application/controllers/ServicesetController.php b/application/controllers/ServicesetController.php
new file mode 100644
index 0000000..0010422
--- /dev/null
+++ b/application/controllers/ServicesetController.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class ServicesetController extends ObjectController
+{
+ protected $host;
+
+ public function init()
+ {
+ if ($host = $this->params->get('host')) {
+ $this->host = IcingaHost::load($host, $this->db());
+ }
+
+ parent::init();
+ if ($this->object) {
+ $tabs = $this->getTabs();
+ $tabs->add('services', array(
+ 'url' => 'director/serviceset/services',
+ 'urlParams' => array('name' => $this->object->object_name),
+ 'label' => 'Services'
+ ));
+ $tabs->add('hosts', array(
+ 'url' => 'director/serviceset/hosts',
+ 'urlParams' => array('name' => $this->object->object_name),
+ 'label' => 'Hosts'
+ ));
+ }
+ }
+
+ public function loadForm($name)
+ {
+ $form = parent::loadForm($name);
+ if ($name === 'icingaServiceSet' && $this->host) {
+ $form->setHost($this->host);
+ }
+
+ return $form;
+ }
+
+ public function addAction()
+ {
+ parent::addAction();
+ if ($this->host) {
+ $this->view->title = sprintf(
+ $this->translate('Add a service set to "%s"'),
+ $this->host->object_name
+ );
+ }
+ }
+
+ public function servicesAction()
+ {
+ $db = $this->db();
+ $set = $this->object;
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add service'),
+ 'director/service/add',
+ array('set' => $set->object_name),
+ array('class' => 'icon-plus')
+ );
+ $this->view->stayHere = true;
+
+ $this->getTabs()->activate('services');
+ $this->view->title = sprintf(
+ $this->translate('Services in this set: %s'),
+ $set->object_name
+ );
+
+ $this->view->table = $this->loadTable('IcingaServiceSetService')
+ ->setServiceSet($set)
+ ->setConnection($db);
+
+ $this->setViewScript('objects/table');
+ }
+
+ public function hostsAction()
+ {
+ $db = $this->db();
+ $set = $this->object;
+ $this->getTabs()->activate('hosts');
+ $this->view->title = sprintf(
+ $this->translate('Hosts using this set: %s'),
+ $set->object_name
+ );
+
+ $this->view->table = $table = $this->loadTable('IcingaServiceSetHost')
+ ->setServiceSet($set)
+ ->setConnection($db);
+
+ $this->setViewScript('objects/table');
+ }
+
+ protected function loadObject()
+ {
+ if ($this->object === null) {
+ if ($name = $this->params->get('name')) {
+ $params = array('object_name' => $name);
+ $db = $this->db();
+
+ if ($this->host) {
+ $this->view->host = $this->host;
+ $params['host_id'] = $this->host->id;
+ }
+
+ $this->object = IcingaServiceSet::load($params, $db);
+ } else {
+ parent::loadObject();
+ }
+ }
+
+ return $this->object;
+ }
+}
diff --git a/application/controllers/SettingsController.php b/application/controllers/SettingsController.php
new file mode 100644
index 0000000..5ba05e9
--- /dev/null
+++ b/application/controllers/SettingsController.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class SettingsController extends ActionController
+{
+ public function indexAction()
+ {
+ $this->view->tabs = $this->Module()
+ ->getConfigTabs()
+ ->activate('config');
+
+ $this->view->form = $this->loadForm('kickstart')
+ ->setModuleConfig($this->Config())
+ ->handleRequest();
+ }
+}
diff --git a/application/controllers/ShowController.php b/application/controllers/ShowController.php
new file mode 100644
index 0000000..624a5b2
--- /dev/null
+++ b/application/controllers/ShowController.php
@@ -0,0 +1,250 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\ConfigDiff;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Util;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Exception;
+
+class ShowController extends ActionController
+{
+ protected $defaultTab;
+
+ protected $oldObject;
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/showconfig');
+ }
+
+ protected function objectKey($entry)
+ {
+ if ($entry->object_type === 'icinga_service' || $entry->object_type === 'icinga_service_set') {
+ // TODO: this is not correct. Activity needs to get (multi) key support
+ return array('name' => $entry->object_name);
+ }
+
+ return $entry->object_name;
+ }
+
+ protected function activityTabs($entry)
+ {
+ $db = $this->db();
+
+ if (IcingaObject::existsByType($entry->object_type, $this->objectKey($entry), $db)) {
+ $this->view->currentObject = IcingaObject::loadByType(
+ $entry->object_type,
+ $entry->object_name,
+ $db
+ );
+ }
+
+ $tabs = $this->getTabs();
+ if ($entry->action_name === 'modify') {
+ $tabs->add('diff', array(
+ 'label' => $this->translate('Diff'),
+ 'url' => 'director/show/activitylog',
+ 'urlParams' => array('id' => $entry->id)
+ ));
+
+ $this->defaultTab = 'diff';
+ }
+
+ if (in_array($entry->action_name, array('create', 'modify'))) {
+ $tabs->add('new', array(
+ 'label' => $this->translate('New object'),
+ 'url' => 'director/show/activitylog',
+ 'urlParams' => array('id' => $entry->id, 'show' => 'new')
+ ));
+
+ if ($this->defaultTab === null) {
+ $this->defaultTab = 'new';
+ }
+ }
+
+ if (in_array($entry->action_name, array('delete', 'modify'))) {
+ $tabs->add('old', array(
+ 'label' => $this->translate('Former object'),
+ 'url' => 'director/show/activitylog',
+ 'urlParams' => array('id' => $entry->id, 'show' => 'old')
+ ));
+
+ if ($this->defaultTab === null) {
+ $this->defaultTab = 'old';
+ }
+ }
+
+ return $tabs;
+ }
+
+ protected function newConfig($entry)
+ {
+ return $this->newObject($entry)->toSingleIcingaConfig();
+ }
+
+ protected function oldConfig($entry)
+ {
+ return $this->oldObject($entry)->toSingleIcingaConfig();
+ }
+
+ protected function showDiff($entry)
+ {
+ $this->view->title = sprintf('%s config diff', $entry->object_name);
+ $this->getTabs()->activate('diff');
+
+ $oldConfig = $this->oldConfig($entry);
+ $newConfig = $this->newConfig($entry);
+ $this->showConfigDiff($oldConfig, $newConfig);
+ }
+
+ protected function showConfigDiff(IcingaConfig $oldConfig, IcingaConfig $newConfig)
+ {
+ $oldFilenames = $oldConfig->getFileNames();
+ $newFilenames = $newConfig->getFileNames();
+
+ $fileNames = array_merge($oldFilenames, $newFilenames);
+
+ $this->view->diffs = array();
+ foreach ($fileNames as $filename) {
+ if (in_array($filename, $oldFilenames)) {
+ $left = $oldConfig->getFile($filename)->getContent();
+ } else {
+ $left = '';
+ }
+
+ if (in_array($filename, $newFilenames)) {
+ $right = $newConfig->getFile($filename)->getContent();
+ } else {
+ $right = '';
+ }
+ if ($left === $right) {
+ continue;
+ }
+
+ $d = ConfigDiff::create($left, $right);
+
+ $this->view->diffs[$filename] = $d->renderHtml();
+ }
+ }
+
+ protected function showOld($entry)
+ {
+ $this->view->title = sprintf('%s former config', $entry->object_name);
+ $this->getTabs()->activate('old');
+ $this->showConfigDiff($this->oldConfig($entry), new IcingaConfig($this->db()));
+ }
+
+ protected function showNew($entry)
+ {
+ $this->view->title = sprintf('%s new config', $entry->object_name);
+ $this->getTabs()->activate('new');
+ $this->showConfigDiff(new IcingaConfig($this->db()), $this->newConfig($entry));
+ }
+
+ protected function oldObject($entry)
+ {
+ if ($this->oldObject === null) {
+ $this->oldObject = $this->createObject(
+ $entry->object_type,
+ $entry->old_properties
+ );
+ }
+
+ return $this->oldObject;
+ }
+
+ protected function newObject($entry)
+ {
+ return $this->createObject(
+ $entry->object_type,
+ $entry->new_properties
+ );
+ }
+
+ protected function showInfo($entry)
+ {
+ $typeName = $this->translate(
+ ucfirst(preg_replace('/^icinga_/', '', $entry->object_type)) // really?
+ );
+
+ switch ($entry->action_name) {
+ case 'create':
+ $this->view->title = sprintf(
+ $this->translate('%s "%s" has been created'),
+ $typeName,
+ $entry->object_name
+ );
+ break;
+ case 'delete':
+ $this->view->title = sprintf(
+ $this->translate('%s "%s" has been deleted'),
+ $typeName,
+ $entry->object_name
+ );
+ break;
+ case 'modify':
+ $this->view->title = sprintf(
+ $this->translate('%s "%s" has been modified'),
+ $typeName,
+ $entry->object_name
+ );
+ break;
+ }
+ }
+
+ public function activitylogAction()
+ {
+ $v = $this->view;
+
+ $v->object_type = $this->params->get('type');
+ $v->object_name = $this->params->get('name');
+
+ if ($id = $this->params->get('id')) {
+ $v->entry = $this->db()->fetchActivityLogEntryById($id);
+ } elseif ($checksum = $this->params->get('checksum')) {
+ $v->entry = $this->db()->fetchActivityLogEntry($checksum);
+ $id = $v->entry->id;
+ }
+
+ $v->neighbors = $this->db()->getActivitylogNeighbors(
+ $id,
+ $v->object_type,
+ $v->object_name
+ );
+
+ $entry = $v->entry;
+
+ if ($entry->old_properties) {
+ $this->view->form = $this
+ ->loadForm('restoreObject')
+ ->setDb($this->db())
+ ->setObject($this->oldObject($entry))
+ ->handleRequest();
+ }
+
+ $this->activityTabs($entry);
+ $this->showInfo($entry);
+ $func = 'show' . ucfirst($this->params->get('show', $this->defaultTab));
+ $this->$func($entry);
+ }
+
+ protected function createObject($type, $props)
+ {
+ $props = json_decode($props);
+ $newProps = array(
+ 'object_name' => $props->object_name
+ );
+ if (property_exists($props, 'object_type')) {
+ $newProps['object_type'] = $props->object_type;
+ }
+
+ return IcingaObject::createByType(
+ $type,
+ $newProps,
+ $this->db()
+ )->setProperties((array) $props);
+ }
+}
diff --git a/application/controllers/SuggestController.php b/application/controllers/SuggestController.php
new file mode 100644
index 0000000..b9eb2ab
--- /dev/null
+++ b/application/controllers/SuggestController.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Data\Filter\Filter;
+
+class SuggestController extends ActionController
+{
+ /*
+ // TODO: Allow any once applying restrictions here
+ protected function checkDirectorPermissions()
+ {
+ }
+ */
+
+ public function indexAction()
+ {
+ // TODO: Using some temporarily hardcoded methods, should use DataViews later on
+ $context = $this->getRequest()->getPost('context');
+ $func = 'suggest' . ucfirst($context);
+ if (method_exists($this, $func)) {
+ $all = $this->$func();
+ } else {
+ $all = array();
+ }
+
+ $search = $this->getRequest()->getPost('value');
+ $begins = array();
+ $matches = array();
+ $begin = Filter::expression('value', '=', $search . '*');
+ $middle = Filter::expression('value', '=', '*' . $search . '*');
+ $prefixes = array();
+ foreach ($all as $str) {
+ if (false !== ($pos = strrpos($str, '.'))) {
+ $prefix = substr($str, 0, $pos) . '.';
+ $prefixes[$prefix] = $prefix;
+ }
+ if (strlen($search)) {
+ $row = (object) array('value' => $str);
+ if ($begin->matches($row)) {
+ $begins[] = $this->highlight($str, $search);
+ } elseif ($middle->matches($row)) {
+ $matches[] = $this->highlight($str, $search);
+ }
+ }
+ }
+
+ $containing = array_slice(array_merge($begins, $matches), 0, 30);
+ $suggestions = $containing;
+
+ if ($func === 'suggestHostFilterColumns' || $func === 'suggestHostaddresses') {
+ ksort($prefixes);
+
+ if (count($suggestions) < 5) {
+ $suggestions = array_merge($suggestions, array_keys($prefixes));
+ }
+ }
+ $this->view->suggestions = $suggestions;
+ }
+
+ /**
+ * One more dummy helper for tests
+ *
+ * TODO: Should not remain here
+ *
+ * @return array
+ */
+ protected function suggestLocations()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()
+ ->distinct()
+ ->from('icinga_host_var', 'varvalue')
+ ->where('varname = ?', 'location')
+ ->order('varvalue');
+ return $db->fetchCol($query);
+ }
+
+ protected function suggestHostnames()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()->from('icinga_host', 'object_name')->order('object_name');
+ return $db->fetchCol($query);
+ }
+
+ protected function suggestCheckcommandnames()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()->from('icinga_command', 'object_name')->order('object_name');
+ return $db->fetchCol($query);
+ }
+
+ protected function suggestHostgroupnames()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()->from('icinga_hostgroup', 'object_name')->order('object_name');
+ return $db->fetchCol($query);
+ }
+
+ protected function suggestHostaddresses()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()->from('icinga_host', 'address')->order('address');
+ return $db->fetchCol($query);
+ }
+
+ protected function suggestHostFilterColumns()
+ {
+ $all = IcingaHost::enumProperties($this->db(), 'host.');
+ $all = array_merge(
+ array_keys($all[$this->translate('Host properties')]),
+ array_keys($all[$this->translate('Custom variables')])
+ );
+ natsort($all);
+ return $all;
+ }
+
+ protected function suggestServiceFilterColumns()
+ {
+ $all = IcingaService::enumProperties($this->db(), 'service.');
+ $all = array_merge(
+ array_keys($all[$this->translate('Service properties')]),
+ array_keys($all[$this->translate('Host properties')]),
+ array_keys($all[$this->translate('Host Custom variables')]),
+ array_keys($all[$this->translate('Custom variables')])
+ );
+ // natsort($all);
+ return $all;
+ }
+
+ protected function highlight($val, $search)
+ {
+ $search = $this->view->escape($search);
+ $val = $this->view->escape($val);
+ return str_replace($search, '<strong>' . $search . '</strong>', $val);
+ }
+}
diff --git a/application/controllers/SyncpropertyController.php b/application/controllers/SyncpropertyController.php
new file mode 100644
index 0000000..e4ccfa1
--- /dev/null
+++ b/application/controllers/SyncpropertyController.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\SyncRule;
+
+class SyncpropertyController extends ActionController
+{
+ public function addAction()
+ {
+ $this->indexAction();
+ }
+
+ public function editAction()
+ {
+ $this->indexAction();
+ }
+
+ public function indexAction()
+ {
+ $edit = false;
+
+ if ($id = $this->params->get('id')) {
+ $edit = true;
+ }
+
+ if ($edit) {
+ $this->view->title = $this->translate('Edit sync property rule');
+ $this->getTabs()->add('edit', array(
+ 'url' => 'director/syncproperty/edit' . '?id=' . $id,
+ 'label' => $this->view->title,
+ ))->activate('edit');
+ } else {
+ $this->view->title = $this->translate('Add sync property rule');
+ $this->getTabs()->add('add', array(
+ 'url' => 'director/syncproperty/add',
+ 'label' => $this->view->title,
+ ))->activate('add');
+ }
+
+ $form = $this->view->form = $this->loadForm('syncProperty')->setDb($this->db());
+
+ if ($edit) {
+ $form->loadObject($id);
+ $rule_id = $form->getObject()->rule_id;
+ $form->setRule(SyncRule::load($rule_id, $this->db()));
+ } elseif ($rule_id = $this->params->get('rule_id')) {
+ $form->setRule(SyncRule::load($rule_id, $this->db()));
+ }
+ $form->setSuccessUrl('director/syncrule/property', array('rule_id' => $rule_id));
+
+ $form->handleRequest();
+
+ $this->render('object/form', null, true);
+ }
+}
diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php
new file mode 100644
index 0000000..dc8022c
--- /dev/null
+++ b/application/controllers/SyncruleController.php
@@ -0,0 +1,244 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\SyncRule;
+use Icinga\Module\Director\Objects\SyncRun;
+use Icinga\Module\Director\Import\Sync;
+use Icinga\Data\Filter\Filter;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+
+class SyncruleController extends ActionController
+{
+ public function indexAction()
+ {
+ $this->setAutoRefreshInterval(10);
+ $id = $this->params->get('id');
+ $this->prepareRuleTabs($id)->activate('show');
+ $rule = $this->view->rule = SyncRule::load($id, $this->db());
+ $this->view->title = sprintf(
+ $this->translate('Sync rule: %s'),
+ $rule->rule_name
+ );
+
+ if ($lastRunId = $rule->getLastSyncRunId()) {
+ $this->loadSyncRun($lastRunId);
+ } else {
+ $this->view->run = null;
+ }
+ $this->view->checkForm = $this
+ ->loadForm('syncCheck')
+ ->setSyncRule($rule)
+ ->handleRequest();
+
+ $this->view->runForm = $this
+ ->loadForm('syncRun')
+ ->setSyncRule($rule)
+ ->handleRequest();
+ }
+
+ public function addAction()
+ {
+ $this->editAction();
+ }
+
+ public function editAction()
+ {
+ $form = $this->view->form = $this->loadForm('syncRule')
+ ->setSuccessUrl('director/list/syncrule')
+ ->setDb($this->db());
+
+ if ($id = $this->params->get('id')) {
+ $this->prepareRuleTabs($id)->activate('edit');
+ $form->loadObject($id);
+ $this->view->title = sprintf(
+ $this->translate('Sync rule: %s'),
+ $form->getObject()->rule_name
+ );
+ } else {
+ $this->view->title = $this->translate('Add sync rule');
+ $this->prepareRuleTabs()->activate('add');
+ }
+
+ $form->handleRequest();
+ $this->setViewScript('object/form');
+ }
+
+ public function runAction()
+ {
+ $id = $this->params->get('id');
+ $rule = SyncRule::load($id, $this->db());
+ $changed = $rule->applyChanges();
+
+ if ($changed) {
+ $runId = $rule->getCurrentSyncRunId();
+ Notification::success('Source has successfully been synchronized');
+ $this->redirectNow(
+ Url::fromPath(
+ 'director/syncrule/history',
+ array(
+ 'id' => $id,
+ 'run_id' => $runId
+ )
+ )
+ );
+ } elseif ($rule->sync_state === 'in-sync') {
+ Notification::success('Nothing changed, rule is in sync');
+ } else {
+ Notification::error('Synchronization failed');
+ }
+
+ $this->redirectNow('director/syncrule?id=' . $id);
+ }
+
+ public function propertyAction()
+ {
+ $this->view->stayHere = true;
+
+ $db = $this->db();
+ $id = $this->params->get('rule_id');
+ $rule = SyncRule::load($id, $db);
+
+ $this->prepareRuleTabs($id)->activate('property');
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('Add sync property rule'),
+ 'director/syncrule/addproperty',
+ array('rule_id' => $id),
+ array('class' => 'icon-plus')
+ );
+
+ $this->view->title = $this->translate('Sync properties') . ': ' . $rule->rule_name;
+ $this->view->table = $this->loadTable('syncproperty')
+ ->enforceFilter(Filter::where('rule_id', $id))
+ ->setConnection($this->db());
+ $this->setViewScript('list/table');
+ }
+
+ public function editpropertyAction()
+ {
+ $this->addpropertyAction();
+ }
+
+ public function addpropertyAction()
+ {
+ $this->view->stayHere = true;
+ $edit = false;
+
+ $db = $this->db();
+ $ruleId = $this->params->get('rule_id');
+ $rule = SyncRule::load($ruleId, $db);
+
+ if ($id = $this->params->get('id')) {
+ $edit = true;
+ }
+
+ $this->view->addLink = $this->view->qlink(
+ $this->translate('back'),
+ 'director/syncrule/property',
+ array('rule_id' => $ruleId),
+ array('class' => 'icon-left-big')
+ );
+
+ $form = $this->view->form = $this->loadForm('syncProperty')->setDb($db);
+
+ if ($edit) {
+ $form->loadObject($id);
+ $rule_id = $form->getObject()->rule_id;
+ $form->setRule(SyncRule::load($rule_id, $db));
+ } elseif ($rule_id = $this->params->get('rule_id')) {
+ $form->setRule(SyncRule::load($rule_id, $db));
+ }
+
+ $form->setSuccessUrl('director/syncrule/property', array('rule_id' => $rule_id));
+ $form->handleRequest();
+
+ $this->prepareRuleTabs($rule_id)->activate('property');
+
+ if ($edit) {
+ $this->view->title = sprintf(
+ $this->translate('Sync "%s": %s'),
+ $form->getObject()->destination_field,
+ $rule->rule_name
+ );
+ } else {
+ $this->view->title = sprintf(
+ $this->translate('Add sync property: %s'),
+ $rule->rule_name
+ );
+ }
+
+ $this->view->table = $this->loadTable('syncproperty')
+ ->enforceFilter(Filter::where('rule_id', $rule_id))
+ ->setConnection($this->db());
+ $this->setViewScript('list/table');
+ }
+
+ public function historyAction()
+ {
+ $this->view->stayHere = true;
+
+ $db = $this->db();
+ $id = $this->params->get('id');
+ $rule = SyncRule::load($id, $db);
+
+ $this->prepareRuleTabs($id)->activate('history');
+ $this->view->title = $this->translate('Sync history') . ': ' . $rule->rule_name;
+ $this->view->table = $this->loadTable('syncRun')
+ ->enforceFilter(Filter::where('rule_id', $id))
+ ->setConnection($this->db());
+
+ if ($runId = $this->params->get('run_id')) {
+ $this->loadSyncRun($runId);
+ }
+ }
+
+ protected function loadSyncRun($id)
+ {
+ $db = $this->db();
+ $this->view->run = SyncRun::load($id, $db);
+ if ($this->view->run->last_former_activity !== null) {
+ $this->view->formerId = $db->fetchActivityLogIdByChecksum(
+ $this->view->run->last_former_activity
+ );
+
+ $this->view->lastId = $db->fetchActivityLogIdByChecksum(
+ $this->view->run->last_related_activity
+ );
+ }
+ }
+
+ protected function prepareRuleTabs($ruleId = null)
+ {
+ if ($ruleId) {
+ $tabs = $this->getTabs()->add('show', array(
+ 'url' => 'director/syncrule',
+ 'urlParams' => array('id' => $ruleId),
+ 'label' => $this->translate('Sync rule'),
+ ))->add('edit', array(
+ 'url' => 'director/syncrule/edit',
+ 'urlParams' => array('id' => $ruleId),
+ 'label' => $this->translate('Modify'),
+ ))->add('property', array(
+ 'label' => $this->translate('Properties'),
+ 'url' => 'director/syncrule/property',
+ 'urlParams' => array('rule_id' => $ruleId)
+ ));
+
+ $tabs->add('history', array(
+ 'label' => $this->translate('History'),
+ 'url' => 'director/syncrule/history',
+ 'urlParams' => array('id' => $ruleId)
+ ));
+
+ return $tabs;
+ } else {
+ return $this->getTabs()->add('add', array(
+ 'url' => 'director/syncrule/add',
+ 'label' => $this->translate('Sync rule'),
+ ));
+ }
+ }
+}
diff --git a/application/controllers/TimeperiodController.php b/application/controllers/TimeperiodController.php
new file mode 100644
index 0000000..a720527
--- /dev/null
+++ b/application/controllers/TimeperiodController.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class TimeperiodController extends ObjectController
+{
+ public function init()
+ {
+ parent::init();
+ if ($this->object && $this->object->hasBeenLoadedFromDb()) {
+ $this->getTabs()->add('ranges', array(
+ 'url' => 'director/timeperiod/ranges',
+ 'urlParams' => $this->object->getUrlParams(),
+ 'label' => $this->translate('Ranges')
+ ));
+ }
+ }
+
+ public function rangesAction()
+ {
+ $this->getTabs()->activate('ranges');
+ $this->view->form = $form = $this->loadForm('icingaTimePeriodRange');
+ $form
+ ->setTimePeriod($this->object)
+ ->setDb($this->db());
+ if ($name = $this->params->get('range')) {
+ $this->view->actionLinks = $this->view->qlink(
+ $this->translate('back'),
+ $this->getRequest()->getUrl()->without('range_id'),
+ null,
+ array('class' => 'icon-left-big')
+ );
+ $form->loadObject(array(
+ 'timeperiod_id' => $this->object->id,
+ 'range_key' => $name,
+ 'range_type' => $this->params->get('range_type')
+ ));
+ }
+ $form->handleRequest();
+
+ $this->view->table = $this->loadTable('icingaTimePeriodRange')
+ ->setTimePeriod($this->object);
+ $this->view->title = $this->translate('Time period ranges');
+ $this->render('object/fields', null, true); // TODO: render table
+ }
+}
diff --git a/application/controllers/TimeperiodsController.php b/application/controllers/TimeperiodsController.php
new file mode 100644
index 0000000..e5adb19
--- /dev/null
+++ b/application/controllers/TimeperiodsController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class TimeperiodsController extends ObjectsController
+{
+}
diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php
new file mode 100644
index 0000000..12f1bd9
--- /dev/null
+++ b/application/controllers/UserController.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class UserController extends ObjectController
+{
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/users');
+ }
+}
diff --git a/application/controllers/UsergroupController.php b/application/controllers/UsergroupController.php
new file mode 100644
index 0000000..e58fd7e
--- /dev/null
+++ b/application/controllers/UsergroupController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class UsergroupController extends ObjectController
+{
+}
diff --git a/application/controllers/UsergroupsController.php b/application/controllers/UsergroupsController.php
new file mode 100644
index 0000000..057890f
--- /dev/null
+++ b/application/controllers/UsergroupsController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class UsergroupsController extends ObjectsController
+{
+}
diff --git a/application/controllers/UsersController.php b/application/controllers/UsersController.php
new file mode 100644
index 0000000..ee6d93d
--- /dev/null
+++ b/application/controllers/UsersController.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class UsersController extends ObjectsController
+{
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/users');
+ }
+}
diff --git a/application/controllers/ZoneController.php b/application/controllers/ZoneController.php
new file mode 100644
index 0000000..a4125bb
--- /dev/null
+++ b/application/controllers/ZoneController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class ZoneController extends ObjectController
+{
+}
diff --git a/application/controllers/ZonesController.php b/application/controllers/ZonesController.php
new file mode 100644
index 0000000..2dcaf58
--- /dev/null
+++ b/application/controllers/ZonesController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class ZonesController extends ObjectsController
+{
+}
diff --git a/application/forms/ApplyMigrationsForm.php b/application/forms/ApplyMigrationsForm.php
new file mode 100644
index 0000000..4102e01
--- /dev/null
+++ b/application/forms/ApplyMigrationsForm.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Exception;
+use Icinga\Module\Director\Db\Migrations;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class ApplyMigrationsForm extends QuickForm
+{
+ /** @var Migrations */
+ protected $migrations;
+
+ public function setup()
+ {
+ if ($this->migrations->hasSchema()) {
+ $count = $this->migrations->countPendingMigrations();
+ if ($count === 1) {
+ $this->setSubmitLabel(
+ $this->translate('Apply a pending schema migration')
+ );
+ } else {
+ $this->setSubmitLabel(
+ sprintf(
+ $this->translate('Apply %d pending schema migrations'),
+ $count
+ )
+ );
+ }
+ } else {
+ $this->setSubmitLabel($this->translate('Create schema'));
+ }
+ }
+
+ public function onSuccess()
+ {
+ try {
+ $this->setSuccessMessage($this->translate(
+ 'Pending database schema migrations have successfully been applied'
+ ));
+
+ $this->migrations->applyPendingMigrations();
+ parent::onSuccess();
+ } catch (Exception $e) {
+ $this->addError($e->getMessage());
+ }
+ }
+
+ public function setMigrations(Migrations $migrations)
+ {
+ $this->migrations = $migrations;
+ return $this;
+ }
+}
diff --git a/application/forms/CustomvarForm.php b/application/forms/CustomvarForm.php
new file mode 100644
index 0000000..759464c
--- /dev/null
+++ b/application/forms/CustomvarForm.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class CustomvarForm extends QuickForm
+{
+ protected $submitLabel = false;
+
+ public function setup()
+ {
+ $this->removeCsrfToken();
+ $this->removeElement(self::ID);
+ $this->addElement('text', 'varname', array(
+ 'label' => $this->translate('Variable name'),
+ 'required' => true,
+ ));
+
+ $this->addElement('text', 'varvalue', array(
+ 'label' => $this->translate('Value'),
+ ));
+
+ // $this->addHidden('format', 'string'); // expression, json?
+ }
+}
diff --git a/application/forms/DeployConfigForm.php b/application/forms/DeployConfigForm.php
new file mode 100644
index 0000000..e387394
--- /dev/null
+++ b/application/forms/DeployConfigForm.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Director\Core\DeploymentApiInterface;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+// use Icinga\Module\Director\Objects\DirectorDeploymentLog;
+use Icinga\Module\Director\Util;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class DeployConfigForm extends QuickForm
+{
+ /** @var DeploymentApiInterface */
+ private $api;
+
+ /** @var Db */
+ private $db;
+
+ /** @var string */
+ private $checksum;
+
+ /** @var int */
+ private $deploymentId;
+
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ public function setup()
+ {
+ $activities = $this->db->countActivitiesSinceLastDeployedConfig();
+ if ($this->deploymentId) {
+ $label = $this->translate('Re-deploy now');
+ } elseif ($activities === 0) {
+ $label = $this->translate('There are no pending changes. Deploy anyways');
+ } else {
+ $label = sprintf(
+ $this->translate('Deploy %d pending changes'),
+ $activities
+ );
+ }
+
+ if ($this->deploymentId) {
+ $deployIcon = 'reply-all';
+ } else {
+ $deployIcon = 'forward';
+ }
+
+ $this->addHtml(
+ $this->getView()->icon(
+ $deployIcon,
+ $label,
+ array('class' => 'link-color')
+ ) . '<nobr>'
+ );
+
+ $el = $this->createElement('submit', 'btn_deploy', array(
+ 'label' => $label,
+ 'escape' => false,
+ 'decorators' => array('ViewHelper'),
+ 'class' => 'link-button ' . $deployIcon,
+ ));
+
+ $this->addHtml('</nobr>');
+ $this->submitButtonName = $el->getName();
+ $this->setSubmitLabel($label);
+ $this->addElement($el);
+ }
+
+ public function onSuccess()
+ {
+ $db = $this->db;
+ $checksum = $this->checksum;
+ $msg = $this->translate('Config has been submitted, validation is going on');
+ $this->setSuccessMessage($msg);
+
+ $isApiRequest = $this->getRequest()->isApiRequest();
+ if ($this->checksum) {
+ $config = IcingaConfig::load(Util::hex2binary($this->checksum), $db);
+ } else {
+ $config = IcingaConfig::generate($db);
+ }
+
+ $this->api->wipeInactiveStages($db);
+
+ if ($this->api->dumpConfig($config, $db)) {
+ if ($isApiRequest) {
+ die('Api not ready');
+ // return $this->sendJson((object) array('checksum' => $checksum));
+ } else {
+ $this->setSuccessUrl('director/config/deployments');
+ $this->setSuccessMessage(
+ $this->translate('Config has been submitted, validation is going on')
+ );
+ }
+ parent::onSuccess();
+ } else {
+ throw new IcingaException($this->translate('Config deployment failed'));
+ }
+ }
+
+ public function setChecksum($checksum)
+ {
+ $this->checksum = $checksum;
+ return $this;
+ }
+
+ public function setDeploymentId($id)
+ {
+ $this->deploymentId = $id;
+ return $this;
+ }
+
+ public function setApi(DeploymentApiInterface $api)
+ {
+ $this->api = $api;
+ return $this;
+ }
+
+ public function setDb(Db $db)
+ {
+ $this->db = $db;
+ return $this;
+ }
+}
diff --git a/application/forms/DirectorDatafieldForm.php b/application/forms/DirectorDatafieldForm.php
new file mode 100644
index 0000000..ba4446b
--- /dev/null
+++ b/application/forms/DirectorDatafieldForm.php
@@ -0,0 +1,224 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Director\CustomVariable\CustomVariables;
+use Icinga\Module\Director\Hook\DataTypeHook;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Application\Hook;
+use Exception;
+
+class DirectorDatafieldForm extends DirectorObjectForm
+{
+ protected $objectName = 'Data field';
+
+ protected function onRequest()
+ {
+ if ($this->hasBeenSent()) {
+ if ($this->shouldBeDeleted()) {
+ $varname = $this->getSentValue('varname');
+ if ($cnt = CustomVariables::countAll($varname, $this->getDb())) {
+ $this->askForVariableDeletion($varname, $cnt);
+ }
+ }
+ }
+
+ return parent::onRequest();
+ }
+
+ protected function askForVariableDeletion($varname, $cnt)
+ {
+ $msg = $this->translate(
+ 'Leaving custom variables in place while removing the related field is'
+ . ' perfectly legal and might be a desired operation. This way you can'
+ . ' no longer modify related custom variables in the Director GUI, but'
+ . ' the variables themselves will stay there and continue to be deployed.'
+ . ' When you re-add a field for the same variable later on, everything'
+ . ' will continue to work as before'
+ );
+
+ $this->addBoolean('wipe_vars', array(
+ 'label' => $this->translate('Wipe related vars'),
+ 'description' => sprintf($msg, $this->getSentValue('varname')),
+ 'required' => true,
+ ));
+
+ if ($wipe = $this->getSentValue('wipe_vars')) {
+ if ($wipe === 'y') {
+ CustomVariables::deleteAll($varname, $this->getDb());
+ }
+ } else {
+ $this->abortDeletion();
+ $this->addError(
+ sprintf(
+ $this->translate('Also wipe all "%s" custom variables from %d objects?'),
+ $varname,
+ $cnt
+ )
+ );
+ $this->getElement('wipe_vars')->addError(
+ sprintf(
+ $this->translate(
+ 'There are %d objects with a related property. Should I also'
+ . ' remove the "%s" property from them?'
+ ),
+ $cnt,
+ $varname
+ )
+ );
+ }
+ }
+
+ public function setup()
+ {
+ $this->addHtmlHint(
+ $this->translate(
+ 'Data fields allow you to customize input controls for Icinga custom'
+ . ' variables. Once you defined them here, you can provide them through'
+ . ' your defined templates. This gives you a granular control over what'
+ . ' properties your users should be allowed to configure in which way.'
+ )
+ );
+
+ $this->addElement('text', 'varname', array(
+ 'label' => $this->translate('Field name'),
+ 'description' => $this->translate(
+ 'The unique name of the field. This will be the name of the custom'
+ . ' variable in the rendered Icinga configuration.'
+ ),
+ 'required' => true,
+ ));
+
+ $this->addElement('text', 'caption', array(
+ 'label' => $this->translate('Caption'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'The caption which should be displayed to your users when this field'
+ . ' is shown'
+ )
+ ));
+
+ $this->addElement('textarea', 'description', array(
+ 'label' => $this->translate('Description'),
+ 'description' => $this->translate(
+ 'An extended description for this field. Will be shown as soon as a'
+ . ' user puts the focus on this field'
+ ),
+ 'rows' => '3',
+ ));
+
+ $error = false;
+ try {
+ $types = $this->enumDataTypes();
+ } catch (Exception $e) {
+ $error = $e->getMessage();
+ $types = $this->optionalEnum(array());
+ }
+
+ $this->addElement('select', 'datatype', array(
+ 'label' => $this->translate('Data type'),
+ 'description' => $this->translate('Field type'),
+ 'required' => true,
+ 'multiOptions' => $types,
+ 'class' => 'autosubmit',
+ ));
+ if ($error) {
+ $this->getElement('datatype')->addError($error);
+ }
+
+ try {
+ if ($class = $this->getSentValue('datatype')) {
+ if ($class && array_key_exists($class, $types)) {
+ $this->addSettings($class);
+ }
+ } elseif ($class = $this->object()->get('datatype')) {
+ $this->addSettings($class);
+ }
+
+ // TODO: next line looks like obsolete duplicate code to me
+ $this->addSettings();
+ } catch (Exception $e) {
+ $this->getElement('datatype')->addError($e->getMessage());
+ }
+
+ foreach ($this->object()->getSettings() as $key => $val) {
+ if ($el = $this->getElement($key)) {
+ $el->setValue($val);
+ }
+ }
+
+ $this->setButtons();
+ }
+
+ protected function addSettings($class = null)
+ {
+ if ($class === null) {
+ $class = $this->getValue('datatype');
+ }
+
+ if ($class !== null) {
+ if (! class_exists($class)) {
+ throw new ConfigurationError(
+ 'The hooked class "%s" for this data field does no longer exist',
+ $class
+ );
+ }
+
+ $class::addSettingsFormFields($this);
+ }
+ }
+
+ protected function clearOutdatedSettings()
+ {
+ $names = array();
+ $object = $this->object();
+ $global = array('varname', 'description', 'caption', 'datatype');
+
+ /** @var \Zend_Form_Element $el */
+ foreach ($this->getElements() as $el) {
+ if ($el->getIgnore()) {
+ continue;
+ }
+
+ $name = $el->getName();
+ if (in_array($name, $global)) {
+ continue;
+ }
+
+ $names[$name] = $name;
+ }
+
+
+ foreach ($object->getSettings() as $setting => $value) {
+ if (! array_key_exists($setting, $names)) {
+ unset($object->$setting);
+ }
+ }
+ }
+
+ public function onSuccess()
+ {
+ $this->clearOutdatedSettings();
+
+ if ($class = $this->getValue('datatype')) {
+ if (array_key_exists($class, $this->enumDataTypes())) {
+ $this->addHidden('format', $class::getFormat());
+ }
+ }
+
+ parent::onSuccess();
+ }
+
+ protected function enumDataTypes()
+ {
+ $hooks = Hook::all('Director\\DataType');
+ $enum = array(null => '- please choose -');
+ /** @var DataTypeHook $hook */
+ foreach ($hooks as $hook) {
+ $enum[get_class($hook)] = $hook->getName();
+ }
+
+ return $enum;
+ }
+}
diff --git a/application/forms/DirectorDatalistForm.php b/application/forms/DirectorDatalistForm.php
new file mode 100644
index 0000000..91c0ea7
--- /dev/null
+++ b/application/forms/DirectorDatalistForm.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Authentication\Auth;
+
+class DirectorDatalistForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addElement('text', 'list_name', array(
+ 'label' => $this->translate('List name'),
+ 'description' => $this->translate(
+ 'Data lists are mainly used as data providers for custom variables'
+ . ' presented as dropdown boxes boxes. You can manually manage'
+ . ' their entries here in place, but you could also create dedicated'
+ . ' sync rules after creating a new empty list. This would allow you'
+ . ' to keep your available choices in sync with external data providers'
+ ),
+ 'required' => true,
+ ));
+ $this->addSimpleDisplayGroup(array('list_name'), 'list', array(
+ 'legend' => $this->translate('Data list')
+ ));
+
+ $this->setButtons();
+ }
+
+ public function onSuccess()
+ {
+ $this->object()->set('owner', self::username());
+ parent::onSuccess();
+ }
+
+ protected static function username()
+ {
+ $auth = Auth::getInstance();
+ if ($auth->isAuthenticated()) {
+ return $auth->getUser()->getUsername();
+ } else {
+ return '<unknown>';
+ }
+ }
+}
diff --git a/application/forms/DirectorDatalistentryForm.php b/application/forms/DirectorDatalistentryForm.php
new file mode 100644
index 0000000..c3542a4
--- /dev/null
+++ b/application/forms/DirectorDatalistentryForm.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\DirectorDatalist;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class DirectorDatalistEntryForm extends DirectorObjectForm
+{
+ /** @var DirectorDatalist */
+ protected $datalist;
+
+ public function setup()
+ {
+ $this->addElement('text', 'entry_name', array(
+ 'label' => $this->translate('Key'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'Will be stored as a custom variable value when this entry'
+ . ' is chosen from the list'
+ )
+ ));
+
+ $this->addElement('text', 'entry_value', array(
+ 'label' => $this->translate('Value'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'This will be the visible caption for this entry'
+ )
+ ));
+
+ $this->addHidden('list_id', $this->datalist->get('id'));
+ $this->addHidden('format', 'string');
+ if (!$this->isNew()) {
+ $this->addHidden('entry_name', $this->object->get('entry_name'));
+ }
+
+ $this->addSimpleDisplayGroup(array('entry_name', 'entry_value'), 'entry', array(
+ 'legend' => $this->isNew()
+ ? $this->translate('Add data list entry')
+ : $this->translate('Modify data list entry')
+ ));
+
+ $this->setButtons();
+ }
+
+ public function setList(DirectorDatalist $list)
+ {
+ $this->datalist = $list;
+ return $this;
+ }
+}
diff --git a/application/forms/DirectorJobForm.php b/application/forms/DirectorJobForm.php
new file mode 100644
index 0000000..7ca998c
--- /dev/null
+++ b/application/forms/DirectorJobForm.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
+use Icinga\Module\Director\Hook\JobHook;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Web\Hook;
+
+class DirectorJobForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $jobTypes = $this->enumJobTypes();
+
+ $this->addElement('select', 'job_class', array(
+ 'label' => $this->translate('Job Type'),
+ 'required' => true,
+ 'multiOptions' => $this->optionalEnum($jobTypes),
+ 'description' => $this->translate(
+ 'These are different available job types'
+ ),
+ 'class' => 'autosubmit'
+ ));
+
+ if (! $jobClass = $this->getJobClass()) {
+ return;
+ }
+
+ if ($desc = $jobClass::getDescription($this)) {
+ $this->addHtmlHint($desc);
+ }
+
+ $this->addBoolean(
+ 'disabled',
+ array(
+ 'label' => $this->translate('Disabled'),
+ 'description' => $this->translate(
+ 'This allows to temporarily disable this job'
+ )
+ ),
+ 'n'
+ );
+
+ $this->addElement('text', 'run_interval', array(
+ 'label' => $this->translate('Run interval'),
+ 'description' => $this->translate(
+ 'Execution interval for this job, in seconds'
+ ),
+ 'value' => $jobClass::getSuggestedRunInterval($this)
+ ));
+
+ $periods = $this->db->enumTimeperiods();
+
+ if (!empty($periods)) {
+ $this->addElement(
+ 'select',
+ 'timeperiod_id',
+ array(
+ 'label' => $this->translate('Time period'),
+ 'description' => $this->translate(
+ 'The name of a time period within this job should be active.'
+ . ' Supports only simple time periods (weekday and multiple'
+ . ' time definitions)'
+ ),
+ 'multiOptions' => $this->optionalEnum($periods),
+ )
+ );
+ }
+
+ $this->addElement('text', 'job_name', array(
+ 'label' => $this->translate('Job name'),
+ 'description' => $this->translate(
+ 'A short name identifying this job. Use something meaningful,'
+ . ' like "Import Puppet Hosts"'
+ ),
+ 'required' => true,
+ ));
+
+ $this->addSettings();
+ $this->setButtons();
+ }
+
+ public function getSentOrObjectSetting($name, $default = null)
+ {
+ if ($this->hasObject()) {
+ $value = $this->getSentValue($name);
+ if ($value === null) {
+ /** @var DbObjectWithSettings $object */
+ $object = $this->getObject();
+ return $object->getSetting($name, $default);
+ } else {
+ return $value;
+ }
+ } else {
+ return $this->getSentValue($name, $default);
+ }
+ }
+
+ protected function getJobClass($class = null)
+ {
+ if ($class === null) {
+ $class = $this->getSentOrObjectValue('job_class');
+ }
+
+ if (array_key_exists($class, $this->enumJobTypes())) {
+ return $class;
+ }
+
+ return null;
+ }
+
+ protected function addSettings($class = null)
+ {
+ if (! $class = $this->getJobClass($class)) {
+ return;
+ }
+
+ $class::addSettingsFormFields($this);
+ foreach ($this->object()->getSettings() as $key => $val) {
+ if ($el = $this->getElement($key)) {
+ $el->setValue($val);
+ }
+ }
+ }
+
+ protected function enumJobTypes()
+ {
+ /** @var JobHook[] $hooks */
+ $hooks = Hook::all('Director\\Job');
+
+ $enum = array();
+
+ foreach ($hooks as $hook) {
+ $enum[get_class($hook)] = $hook->getName();
+ }
+ asort($enum);
+
+ return $enum;
+ }
+}
diff --git a/application/forms/IcingaApiUserForm.php b/application/forms/IcingaApiUserForm.php
new file mode 100644
index 0000000..eda0857
--- /dev/null
+++ b/application/forms/IcingaApiUserForm.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaApiUserForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addHidden('object_type', 'external_object');
+
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Name'),
+ 'required' => true,
+ ));
+
+ $this->addElement('password', 'password', array(
+ 'label' => $this->translate('Password'),
+ 'required' => true,
+ ));
+
+ $this->setButtons();
+ }
+}
diff --git a/application/forms/IcingaCloneObjectForm.php b/application/forms/IcingaCloneObjectForm.php
new file mode 100644
index 0000000..0a33e03
--- /dev/null
+++ b/application/forms/IcingaCloneObjectForm.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class IcingaCloneObjectForm extends QuickForm
+{
+ /** @var IcingaObject */
+ protected $object;
+
+ public function setup()
+ {
+ $this->addElement('text', 'new_object_name', array(
+ 'label' => $this->translate('New name'),
+ 'required' => true,
+ 'value' => $this->object->getObjectName(),
+ ));
+
+ $this->addElement('select', 'clone_type', array(
+ 'label' => 'Clone type',
+ 'required' => true,
+ 'multiOptions' => array(
+ 'equal' => $this->translate('Clone the object as is, preserving imports'),
+ 'flat' => $this->translate('Flatten all inherited properties, strip imports'),
+ )
+ ));
+
+ $this->submitLabel = sprintf(
+ $this->translate('Clone "%s"'),
+ $this->object->getObjectName()
+ );
+ }
+
+ public function onSuccess()
+ {
+
+ $object = $this->object;
+ $newname = $this->getValue('new_object_name');
+ $resolve = $this->getValue('clone_type') === 'flat';
+
+ $msg = sprintf(
+ 'The %s "%s" has been cloned from "%s"',
+ $object->getShortTableName(),
+ $newname,
+ $object->getObjectName()
+ );
+
+ $new = $object::fromPlainObject(
+ $object->toPlainObject($resolve),
+ $object->getConnection()
+ )->set('object_name', $newname);
+
+ if ($new->store()) {
+ $this->setSuccessUrl(
+ 'director/' . strtolower($object->getShortTableName()),
+ $new->getUrlParams()
+ );
+
+ $this->redirectOnSuccess($msg);
+ }
+ }
+
+ public function setObject(IcingaObject $object)
+ {
+ $this->object = $object;
+ return $this;
+ }
+}
diff --git a/application/forms/IcingaCommandArgumentForm.php b/application/forms/IcingaCommandArgumentForm.php
new file mode 100644
index 0000000..dadd6c6
--- /dev/null
+++ b/application/forms/IcingaCommandArgumentForm.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaCommand;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaCommandArgumentForm extends DirectorObjectForm
+{
+ /** @var IcingaCommand */
+ protected $commandObject;
+
+ public function setCommandObject(IcingaCommand $object)
+ {
+ $this->commandObject = $object;
+ $this->setDb($object->getConnection());
+ return $this;
+ }
+
+ public function setup()
+ {
+ $this->addHidden('command_id', $this->commandObject->get('id'));
+
+ $this->addElement('text', 'argument_name', array(
+ 'label' => $this->translate('Argument name'),
+ 'filters' => array('StringTrim'),
+ 'description' => $this->translate('e.g. -H or --hostname, empty means "skip_key"')
+ ));
+
+ $this->addElement('select', 'argument_format', array(
+ 'label' => $this->translate('Value type'),
+ 'multiOptions' => array(
+ 'string' => $this->translate('String'),
+ 'expression' => $this->translate('Icinga DSL')
+ ),
+ 'description' => $this->translate(
+ 'Whether the argument value is a string (allowing macros like $host$)'
+ . ' or an Icinga DSL lambda function (will be enclosed with {{ ... }}'
+ ),
+ 'class' => 'autosubmit',
+ ));
+
+ if ($this->getSentOrObjectValue('argument_format') === 'expression') {
+ $this->addElement('textarea', 'argument_value', array(
+ 'label' => $this->translate('Value'),
+ 'description' => $this->translate(
+ 'An Icinga DSL expression, e.g.: var cmd = macro("$cmd$");'
+ . ' return typeof(command) == String ...'
+ ),
+ 'rows' => 3
+ ));
+ } else {
+ $this->addElement('text', 'argument_value', array(
+ 'label' => $this->translate('Value'),
+ 'description' => $this->translate(
+ 'e.g. 5%, $hostname$, $lower$%:$upper$%'
+ )
+ ));
+ }
+
+ $this->addElement('text', 'sort_order', array(
+ 'label' => $this->translate('Position'),
+ 'description' => $this->translate(
+ 'Leave empty for non-positional arguments. Can be a positive or'
+ . ' negative number and influences argument ordering'
+ )
+ ));
+
+ $this->addElement('select', 'set_if_format', array(
+ 'label' => $this->translate('Condition format'),
+ 'multiOptions' => array(
+ 'string' => $this->translate('String'),
+ 'expression' => $this->translate('Icinga DSL')
+ ),
+ 'description' => $this->translate(
+ 'Whether the set_if parameter is a string (allowing macros like $host$)'
+ . ' or an Icinga DSL lambda function (will be enclosed with {{ ... }}'
+ ),
+ 'class' => 'autosubmit',
+ ));
+
+ if ($this->getSentOrObjectValue('set_if_format') === 'expression') {
+ $this->addElement('textarea', 'set_if', array(
+ 'label' => $this->translate('Condition (set_if)'),
+ 'description' => $this->translate(
+ 'An Icinga DSL expression that returns a boolean value, e.g.: var cmd = bool(macro("$cmd$"));'
+ . ' return cmd ...'
+ ),
+ 'rows' => 3
+ ));
+ } else {
+ $this->addElement('text', 'set_if', array(
+ 'label' => $this->translate('Condition (set_if)'),
+ 'description' => $this->translate(
+ 'Only set this parameter if the argument value resolves to a'
+ . ' numeric value. String values are not supported'
+ )
+ ));
+ }
+
+ $this->addBoolean('repeat_key', array(
+ 'label' => $this->translate('Repeat key'),
+ 'description' => $this->translate(
+ 'Whether this parameter should be repeated when multiple values'
+ . ' (read: array) are given'
+ )
+ ));
+
+ $this->addBoolean('required', array(
+ 'label' => $this->translate('Required'),
+ 'required' => false,
+ 'description' => $this->translate('Whether this argument should be required')
+ ));
+
+ $this->setButtons();
+ }
+
+ protected function deleteObject($object)
+ {
+ $cmd = $this->commandObject;
+
+ $msg = sprintf(
+ '%s argument "%s" has been removed',
+ $this->translate($this->getObjectShortClassName()),
+ $object->argument_name
+ );
+
+ $url = $this->getSuccessUrl()->without('argument_id');
+
+ $cmd->arguments()->remove($object->argument_name);
+ if ($cmd->store()) {
+ $this->setSuccessUrl($url);
+ }
+
+ $this->redirectOnSuccess($msg);
+ }
+
+ public function onSuccess()
+ {
+ $object = $this->object();
+ $cmd = $this->commandObject;
+ if (! $object->hasBeenLoadedFromDb()) {
+ if ($object->get('argument_name') === null) {
+ $object->set('skip_key', true);
+ $object->set('argument_name', $cmd->getNextSkippableKeyName());
+ }
+ }
+
+ if ($object->hasBeenModified()) {
+ $cmd->arguments()->set(
+ $object->get('argument_name'),
+ $object
+ );
+ $msg = sprintf(
+ $this->translate('The argument %s has successfully been stored'),
+ $object->get('argument_name')
+ );
+ $cmd->store($this->db);
+ } else {
+ $this->setHttpResponseCode(304);
+ $msg = $this->translate('No action taken, object has not been modified');
+ }
+ $this->setSuccessUrl(
+ 'director/command/arguments',
+ array('name' => $cmd->getObjectName())
+ );
+
+ $this->redirectOnSuccess($msg);
+ }
+}
diff --git a/application/forms/IcingaCommandForm.php b/application/forms/IcingaCommandForm.php
new file mode 100644
index 0000000..b4db1c6
--- /dev/null
+++ b/application/forms/IcingaCommandForm.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaCommandForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addObjectTypeElement();
+ if (! $this->hasObjectType()) {
+ return;
+ }
+
+ $this->addElement('select', 'methods_execute', array(
+ 'label' => $this->translate('Command type'),
+ 'multiOptions' => array(
+ null => '- please choose -',
+ $this->translate('Plugin commands') => array(
+ 'PluginCheck' => 'Plugin Check Command',
+ 'PluginNotification' => 'Notification Plugin Command',
+ 'PluginEvent' => 'Event Plugin Command',
+ ),
+ $this->translate('Internal commands') => array(
+ 'IcingaCheck' => 'Icinga Check Command',
+ 'ClusterCheck' => 'Icinga Cluster Check Command',
+ 'ClusterZoneCheck' => 'Icinga Cluster Zone Check Command',
+ 'IdoCheck' => 'Ido Check Command',
+ 'RandomCheck' => 'Random Check Command',
+ 'CrlCheck' => 'Crl Check Command',
+ )
+ ),
+ 'required' => ! $this->isTemplate(),
+ 'description' => $this->translate(
+ 'Plugin Check commands are what you need when running checks agains'
+ . ' your infrastructure. Notification commands will be used when it'
+ . ' comes to notify your users. Event commands allow you to trigger'
+ . ' specific actions when problems occur. Some people use them for'
+ . ' auto-healing mechanisms, like restarting services or rebooting'
+ . ' systems at specific thresholds'
+ ),
+ 'class' => 'autosubmit'
+ ));
+
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Command name'),
+ 'required' => true,
+ 'description' => $this->translate('Identifier for the Icinga command you are going to create')
+ ));
+
+ $this->addImportsElement(false);
+
+ $this->addElement('text', 'command', array(
+ 'label' => $this->translate('Command'),
+ 'required' => ! $this->isTemplate(),
+ 'description' => $this->translate(
+ 'The command Icinga should run. Absolute paths are accepted as provided,'
+ . ' relative paths are prefixed with "PluginDir + ", similar Constant prefixes are allowed.'
+ . ' Spaces will lead to separation of command path and standalone arguments. Please note that'
+ . ' this means that we do not support spaces in plugin names and paths right now.'
+ )
+ ));
+
+ $this->addElement('text', 'timeout', array(
+ 'label' => $this->translate('Timeout'),
+ 'description' => $this->translate(
+ 'Optional command timeout. Allowed values are seconds or durations postfixed with a'
+ . ' specific unit (e.g. 1m or also 3m 30s).'
+ )
+ ));
+ $this->addDisabledElement();
+
+ $this->setButtons();
+ }
+
+ protected function enumAllowedTemplates()
+ {
+ $object = $this->object();
+ $tpl = $this->db->enum($object->getTableName());
+ if (empty($tpl)) {
+ return array();
+ }
+
+ $id = $object->get('id');
+
+ if (array_key_exists($id, $tpl)) {
+ unset($tpl[$id]);
+ }
+
+ if (empty($tpl)) {
+ return array();
+ }
+
+ $tpl = array_combine($tpl, $tpl);
+ return $tpl;
+ }
+}
diff --git a/application/forms/IcingaDeleteObjectForm.php b/application/forms/IcingaDeleteObjectForm.php
new file mode 100644
index 0000000..409bdc3
--- /dev/null
+++ b/application/forms/IcingaDeleteObjectForm.php
@@ -0,0 +1,41 @@
+<?php
+
+// TODO: Check whether this can be removed
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class IcingaDeleteObjectForm extends QuickForm
+{
+ /** @var IcingaObject */
+ protected $object;
+
+ public function setup()
+ {
+ $this->submitLabel = sprintf(
+ $this->translate('YES, please delete "%s"'),
+ $this->object->getObjectName()
+ );
+ }
+
+ public function onSuccess()
+ {
+ $object = $this->object;
+ $msg = sprintf(
+ 'The %s "%s" has been deleted',
+ $object->getShortTableName(),
+ $object->getObjectName()
+ );
+
+ if ($object->delete()) {
+ $this->redirectOnSuccess($msg);
+ }
+ }
+
+ public function setObject(IcingaObject $object)
+ {
+ $this->object = $object;
+ return $this;
+ }
+}
diff --git a/application/forms/IcingaEndpointForm.php b/application/forms/IcingaEndpointForm.php
new file mode 100644
index 0000000..1c08cb4
--- /dev/null
+++ b/application/forms/IcingaEndpointForm.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaEndpointForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addObjectTypeElement();
+ if (! $this->hasObjectType()) {
+ return;
+ }
+
+ if ($this->isTemplate()) {
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Endpoint template name'),
+ 'required' => true,
+ 'description' => $this->translate('Name for the Icinga endpoint template you are going to create')
+ ));
+ } else {
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Endpoint'),
+ 'required' => true,
+ 'description' => $this->translate('Name for the Icinga endpoint you are going to create')
+ ));
+ }
+
+ $this->addElement('text', 'host', array(
+ 'label' => $this->translate('Endpoint address'),
+ 'description' => $this->translate('IP address / hostname of remote node')
+ ));
+
+ $this->addElement('text', 'port', array(
+ 'label' => $this->translate('Port'),
+ 'description' => $this->translate('The port of the endpoint.'),
+ ));
+
+ $this->addElement('text', 'log_duration', array(
+ 'label' => $this->translate('Log Duration'),
+ 'description' => $this->translate('The log duration time.')
+ ));
+
+ $this->addElement('select', 'apiuser_id', array(
+ 'label' => $this->translate('API user'),
+ 'multiOptions' => $this->optionalEnum($this->db->enumApiUsers())
+ ));
+
+ $this->addZoneElement();
+
+ if ($this->object->hasBeenLoadedFromDb()) {
+ $imports = $this->object->get('imports');
+ if ($imports !== null && count($imports) > 0) {
+ $this->addImportsElement(false);
+ }
+ }
+
+ $this->setButtons();
+ }
+}
diff --git a/application/forms/IcingaHostForm.php b/application/forms/IcingaHostForm.php
new file mode 100644
index 0000000..2361bc6
--- /dev/null
+++ b/application/forms/IcingaHostForm.php
@@ -0,0 +1,231 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaHostForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addObjectTypeElement();
+ if (! $this->hasObjectType()) {
+ $this->groupMainProperties();
+ return;
+ }
+
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Hostname'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'Icinga object name for this host. This is usually a fully qualified host name'
+ . ' but it could basically be any kind of string. To make things easier for your'
+ . ' users we strongly suggest to use meaningful names for templates. E.g. "generic-host"'
+ . ' is ugly, "Standard Linux Server" is easier to understand'
+ )
+ ));
+
+ if ($this->isNew() && $this->isObject() && $this->allowsExperimental()) {
+ $this->addBoolean('create_live', array(
+ 'label' => $this->translate('Create immediately'),
+ 'ignore' => true,
+ ), 'n');
+ }
+
+ $this->addGroupsElement()
+ ->addImportsElement()
+ ->addDisplayNameElement()
+ ->addAddressElements()
+ ->addDisabledElement()
+ ->groupMainProperties()
+ ->addClusteringElements();
+
+ $this->addCheckCommandElements()
+ ->addCheckExecutionElements()
+ ->addExtraInfoElements()
+ ->setButtons();
+ }
+
+ /**
+ * @return $this
+ */
+ protected function addClusteringElements()
+ {
+ $this->addZoneElement();
+
+ $this->addBoolean('has_agent', array(
+ 'label' => $this->translate('Icinga2 Agent'),
+ 'description' => $this->translate(
+ 'Whether this host has the Icinga 2 Agent installed'
+ ),
+ 'class' => 'autosubmit',
+ ));
+
+ if ($this->getSentOrResolvedObjectValue('has_agent') === 'y') {
+ $this->addBoolean('master_should_connect', array(
+ 'label' => $this->translate('Establish connection'),
+ 'description' => $this->translate(
+ 'Whether the parent (master) node should actively try to connect to this agent'
+ ),
+ 'required' => true
+ ));
+ $this->addBoolean('accept_config', array(
+ 'label' => $this->translate('Accepts config'),
+ 'description' => $this->translate('Whether the agent is configured to accept config'),
+ 'required' => true
+ ));
+
+ $this->addHidden('command_endpoint_id', null);
+ $this->setSentValue('command_endpoint_id', null);
+ } else {
+ if ($this->isTemplate()) {
+ $this->addElement('select', 'command_endpoint_id', array(
+ 'label' => $this->translate('Command endpoint'),
+ 'description' => $this->translate(
+ 'Setting a command endpoint allows you to force host checks'
+ . ' to be executed by a specific endpoint. Please carefully'
+ . ' study the related Icinga documentation before using this'
+ . ' feature'
+ ),
+ 'multiOptions' => $this->optionalEnum($this->enumEndpoints())
+ ));
+ }
+
+ foreach (array('master_should_connect', 'accept_config') as $key) {
+ $this->addHidden($key, null);
+ $this->setSentValue($key, null);
+ }
+ }
+
+ $elements = array(
+ 'zone_id',
+ 'has_agent',
+ 'master_should_connect',
+ 'accept_config',
+ 'command_endpoint_id',
+ );
+ $this->addDisplayGroup($elements, 'clustering', array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl')),
+ 'Fieldset',
+ ),
+ 'order' => 80,
+ 'legend' => $this->translate('Icinga Agent and zone settings')
+ ));
+
+ return $this;
+ }
+
+ protected function beforeSuccessfulRedirect()
+ {
+ if ($this->allowsExperimental() && $this->getSentValue('create_live') === 'y') {
+ $host = $this->getObject();
+ if ($this->api()->createObjectAtRuntime($host)) {
+ $this->api()->checkHostNow($host->object_name);
+ }
+ }
+ }
+
+ /**
+ * @return $this
+ */
+ protected function addGroupsElement()
+ {
+ $groups = $this->enumHostgroups();
+ if (empty($groups)) {
+ return $this;
+ }
+
+ $this->addElement('extensibleSet', 'groups', array(
+ 'label' => $this->translate('Groups'),
+ 'multiOptions' => $this->optionallyAddFromEnum($groups),
+ 'positional' => false,
+ 'description' => $this->translate(
+ 'Hostgroups that should be directly assigned to this node. Hostgroups can be useful'
+ . ' for various reasons. You might assign service checks based on assigned hostgroup.'
+ . ' They are also often used as an instrument to enforce restricted views in Icinga Web 2.'
+ . ' Hostgroups can be directly assigned to single hosts or to host templates. You might'
+ . ' also want to consider assigning hostgroups using apply rules'
+ )
+ ));
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ protected function addAddressElements()
+ {
+ if ($this->isTemplate()) {
+ return $this;
+ }
+
+ $this->addElement('text', 'address', array(
+ 'label' => $this->translate('Host address'),
+ 'description' => $this->translate(
+ 'Host address. Usually an IPv4 address, but may be any kind of address'
+ . ' your check plugin is able to deal with'
+ )
+ ));
+
+ $this->addElement('text', 'address6', array(
+ 'label' => $this->translate('IPv6 address'),
+ 'description' => $this->translate('Usually your hosts main IPv6 address')
+ ));
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ protected function addDisplayNameElement()
+ {
+ if ($this->isTemplate()) {
+ return $this;
+ }
+
+ $this->addElement('text', 'display_name', array(
+ 'label' => $this->translate('Display name'),
+ 'description' => $this->translate(
+ 'Alternative name for this host. Might be a host alias or and kind'
+ . ' of string helping your users to identify this host'
+ )
+ ));
+
+ return $this;
+ }
+
+ protected function enumEndpoints()
+ {
+ $db = $this->db->getDbAdapter();
+ $select = $db->select()->from(
+ 'icinga_endpoint',
+ array(
+ 'id',
+ 'object_name'
+ )
+ )->where(
+ 'object_type IN (?)',
+ array('object', 'external_object')
+ )->order('object_name');
+
+ return $db->fetchPairs($select);
+ }
+
+ protected function enumHostgroups()
+ {
+ $db = $this->db->getDbAdapter();
+ $select = $db->select()->from(
+ 'icinga_hostgroup',
+ array(
+ 'name' => 'object_name',
+ 'display' => 'COALESCE(display_name, object_name)'
+ )
+ )->where('object_type = ?', 'object')->order('display');
+
+ return $db->fetchPairs($select);
+ }
+}
diff --git a/application/forms/IcingaHostGroupForm.php b/application/forms/IcingaHostGroupForm.php
new file mode 100644
index 0000000..1a16a4d
--- /dev/null
+++ b/application/forms/IcingaHostGroupForm.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaHostGroupForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addHidden('object_type', 'object');
+
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Hostgroup'),
+ 'required' => true,
+ 'description' => $this->translate('Icinga object name for this host group')
+ ));
+
+ $this->addGroupDisplayNameElement()
+ ->addAssignmentElements()
+ ->setButtons();
+ }
+
+ protected function addAssignmentElements()
+ {
+ $this->addAssignFilter(array(
+ 'columns' => IcingaHost::enumProperties($this->db, 'host.'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'This allows you to configure an assignment filter. Please feel'
+ . ' free to combine as many nested operators as you want'
+ )
+ ));
+
+ return $this;
+ }
+}
diff --git a/application/forms/IcingaHostVarForm.php b/application/forms/IcingaHostVarForm.php
new file mode 100644
index 0000000..cb15bcb
--- /dev/null
+++ b/application/forms/IcingaHostVarForm.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+/**
+ * @deprecated
+ */
+class IcingaHostVarForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addElement('select', 'host_id', array(
+ 'label' => $this->translate('Host'),
+ 'description' => $this->translate('The name of the host'),
+ 'multiOptions' => $this->optionalEnum($this->db->enumHosts()),
+ 'required' => true
+ ));
+
+ $this->addElement('text', 'varname', array(
+ 'label' => $this->translate('Name'),
+ 'description' => $this->translate('host var name')
+ ));
+
+ $this->addElement('textarea', 'varvalue', array(
+ 'label' => $this->translate('Value'),
+ 'description' => $this->translate('host var value')
+ ));
+
+ $this->addElement('text', 'format', array(
+ 'label' => $this->translate('Format'),
+ 'description' => $this->translate('value format')
+ ));
+ }
+}
diff --git a/application/forms/IcingaImportObjectForm.php b/application/forms/IcingaImportObjectForm.php
new file mode 100644
index 0000000..3942f74
--- /dev/null
+++ b/application/forms/IcingaImportObjectForm.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class IcingaImportObjectForm extends QuickForm
+{
+ /** @var IcingaObject */
+ protected $object;
+
+ public function setup()
+ {
+ $this->addNote($this->translate(
+ "Importing an object means that its type will change from"
+ . ' "external" to "object". That way it will make part of the'
+ . ' next deployment. So in case you imported this object from'
+ . ' your Icinga node make sure to remove it from your local'
+ . ' configuration before issueing the next deployment. In case'
+ . ' of a conflict nothing bad will happen, just your config'
+ . " won't deploy."
+ ));
+
+ $this->submitLabel = sprintf(
+ $this->translate('Import external "%s"'),
+ $this->object->object_name
+ );
+ }
+
+ public function onSuccess()
+ {
+ $object = $this->object;
+ if ($object->set('object_type', 'object')->store()) {
+ $this->redirectOnSuccess(sprintf(
+ $this->translate('%s "%s" has been imported"'),
+ $object->getShortTableName(),
+ $object->getObjectName()
+ ));
+ } else {
+ $this->addError(sprintf(
+ $this->translate('Failed to import %s "%s"'),
+ $object->getShortTableName(),
+ $object->getObjectName()
+ ));
+ }
+ }
+
+ public function setObject(IcingaObject $object)
+ {
+ $this->object = $object;
+ return $this;
+ }
+}
diff --git a/application/forms/IcingaMultiEditForm.php b/application/forms/IcingaMultiEditForm.php
new file mode 100644
index 0000000..414aa85
--- /dev/null
+++ b/application/forms/IcingaMultiEditForm.php
@@ -0,0 +1,279 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Data\Db\DbObject;
+use Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Module\Director\Web\Form\QuickForm;
+use Zend_Form_Element as ZfElement;
+
+class IcingaMultiEditForm extends DirectorObjectForm
+{
+ /** @var DbObject[] */
+ private $objects;
+
+ private $elementGroupMap;
+
+ /** @var QuickForm */
+ private $relatedForm;
+
+ private $propertiesToPick;
+
+ public function setObjects($objects)
+ {
+ $this->objects = $objects;
+ $this->object = current($this->objects);
+ $this->db = $this->object()->getConnection();
+ return $this;
+ }
+
+ public function pickElementsFrom(QuickForm $form, $properties)
+ {
+ $this->relatedForm = $form;
+ $this->propertiesToPick = $properties;
+ return $this;
+ }
+
+ public function setup()
+ {
+ $object = $this->object;
+
+ $loader = new IcingaObjectFieldLoader($object);
+ $loader->addFieldsToForm($this);
+
+ if ($form = $this->relatedForm) {
+ if ($form instanceof DirectorObjectForm) {
+ $form->setDb($object->getConnection())
+ ->setObject($object);
+ }
+
+ $form->prepareElements();
+ } else {
+ $this->propertiesToPick = array();
+ }
+
+ foreach ($this->propertiesToPick as $property) {
+ if ($el = $form->getElement($property)) {
+ $this->makeVariants($el);
+ }
+ }
+
+ /** @var \Zend_Form_Element $el */
+ foreach ($this->getElements() as $el) {
+ $name = $el->getName();
+ if (substr($name, 0, 4) === 'var_') {
+ $this->makeVariants($el);
+ }
+ }
+
+ $this->setButtons();
+ }
+
+ public function onSuccess()
+ {
+ foreach ($this->getValues() as $key => $value) {
+ $this->setSubmittedMultiValue($key, $value);
+ }
+
+ $modified = $this->storeModifiedObjects();
+ if ($modified === 0) {
+ $msg = $this->translate('No object has been modified');
+ } elseif ($modified === 1) {
+ $msg = $this->translate('One object has been modified');
+ } else {
+ $msg = sprintf(
+ $this->translate('%d objects have been modified'),
+ $modified
+ );
+ }
+
+ $this->redirectOnSuccess($msg);
+ }
+
+ /**
+ * No default objects behaviour
+ */
+ protected function onRequest()
+ {
+ }
+
+ protected function setSubmittedMultiValue($key, $value)
+ {
+ $parts = preg_split('/_/', $key);
+ $objectsSum = array_pop($parts);
+ $valueSum = array_pop($parts);
+ $property = implode('_', $parts);
+
+ if ($value === '') {
+ $value = null;
+ }
+
+ foreach ($this->getVariants($property) as $json => $objects) {
+ if ($valueSum !== sha1($json)) {
+ continue;
+ }
+
+ if ($objectsSum !== sha1(json_encode($objects))) {
+ continue;
+ }
+
+ if (substr($property, 0, 4) === 'var_') {
+ $property = 'vars.' . substr($property, 4);
+ }
+
+ foreach ($this->getObjects($objects) as $object) {
+ $object->$property = $value;
+ }
+ }
+ }
+
+ protected function storeModifiedObjects()
+ {
+ $modified = 0;
+ foreach ($this->objects as $object) {
+ if ($object->hasBeenModified()) {
+ $modified++;
+ $object->store();
+ }
+ }
+
+ return $modified;
+ }
+
+ protected function getDisplayGroupForElement(ZfElement $element)
+ {
+ if ($this->elementGroupMap === null) {
+ $this->resolveDisplayGroups();
+ }
+
+ $name = $element->getName();
+ if (array_key_exists($name, $this->elementGroupMap)) {
+ $groupName = $this->elementGroupMap[$name];
+
+ if ($group = $this->getDisplayGroup($groupName)) {
+ return $group;
+ } elseif ($this->relatedForm) {
+ return $this->stealDisplayGroup($groupName, $this->relatedForm);
+ }
+ }
+
+ return null;
+ }
+
+ protected function stealDisplayGroup($name, QuickForm $form)
+ {
+ if ($group = $form->getDisplayGroup($name)) {
+ $group = clone($group);
+ $group->setElements(array());
+ $this->_displayGroups[$name] = $group;
+ $this->_order[$name] = $group->getOrder();
+ $this->_orderUpdated = true;
+
+ return $group;
+ }
+
+ return null;
+ }
+
+ protected function resolveDisplayGroups()
+ {
+ $this->elementGroupMap = array();
+ if ($form = $this->relatedForm) {
+ $this->extractFormDisplayGroups($form);
+ }
+
+ $this->extractFormDisplayGroups($this);
+ }
+
+ protected function extractFormDisplayGroups(QuickForm $form)
+ {
+ /** @var \Zend_Form_DisplayGroup $group */
+ foreach ($form->getDisplayGroups() as $group) {
+ $groupName = $group->getName();
+ foreach ($group->getElements() as $name => $e) {
+ $this->elementGroupMap[$name] = $groupName;
+ }
+ }
+ }
+
+ protected function makeVariants(ZfElement $element)
+ {
+ $key = $element->getName();
+ $this->removeElement($key);
+ $label = $element->getLabel();
+ $group = $this->getDisplayGroupForElement($element);
+ $description = $element->getDescription();
+
+ foreach ($this->getVariants($key) as $json => $objects) {
+ $value = json_decode($json);
+ $checksum = sha1($json) . '_' . sha1(json_encode($objects));
+
+ $v = clone($element);
+ $v->setName($key . '_' . $checksum);
+ $v->setDescription($description . ' ' . $this->descriptionForObjects($objects));
+ $v->setLabel($label . $this->labelCount($objects));
+ $v->setValue($value);
+ if ($group) {
+ $group->addElement($v);
+ }
+ $this->addElement($v);
+ }
+ }
+
+ protected function getVariants($key)
+ {
+ $variants = array();
+ if (substr($key, 0, 4) === 'var_') {
+ $key = 'vars.' . substr($key, 4);
+ }
+
+ foreach ($this->objects as $name => $object) {
+ $value = json_encode($object->$key);
+ if (! array_key_exists($value, $variants)) {
+ $variants[$value] = array();
+ }
+
+ $variants[$value][] = $name;
+ }
+
+ foreach ($variants as & $objects) {
+ natsort($objects);
+ }
+
+ return $variants;
+ }
+
+ protected function descriptionForObjects($list)
+ {
+ return sprintf(
+ $this->translate('Changing this value affects %d object(s): %s'),
+ count($list),
+ implode(', ', $list)
+ );
+ }
+
+ protected function labelCount($list)
+ {
+ return ' (' . count($list) . ')';
+ }
+
+ protected function db()
+ {
+ if ($this->db === null) {
+ $this->db = $this->object()->getConnection();
+ }
+
+ return $this->db;
+ }
+
+ protected function getObjects($names)
+ {
+ $res = array();
+ foreach ($names as $name) {
+ $res[$name] = $this->objects[$name];
+ }
+
+ return $res;
+ }
+}
diff --git a/application/forms/IcingaNotificationForm.php b/application/forms/IcingaNotificationForm.php
new file mode 100644
index 0000000..86ab730
--- /dev/null
+++ b/application/forms/IcingaNotificationForm.php
@@ -0,0 +1,259 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaNotificationForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addObjectTypeElement();
+ if (! $this->hasObjectType()) {
+ $this->groupMainProperties();
+ return;
+ }
+
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Notification'),
+ 'required' => true,
+ 'description' => $this->translate('Icinga object name for this notification')
+ ));
+
+ $this->addDisabledElement()
+ ->addImportsElement()
+ ->addUsersElement()
+ ->addUsergroupsElement()
+ ->addIntervalElement()
+ ->addPeriodElement()
+ ->addTimesElements()
+ ->addAssignmentElements()
+ ->addDisabledElement()
+ ->addCommandElements()
+ ->addEventFilterElements()
+ ->groupMainProperties()
+ ->setButtons();
+ }
+
+ /**
+ * @return self
+ */
+ protected function addAssignmentElements()
+ {
+ if (!$this->object || !$this->object->isApplyRule()) {
+ return $this;
+ }
+
+ $this->addElement('select', 'apply_to', array(
+ 'label' => $this->translate('Apply to'),
+ 'description' => $this->translate(
+ 'Whether this notification should affect hosts or services'
+ ),
+ 'required' => true,
+ 'class' => 'autosubmit',
+ 'multiOptions' => $this->optionalEnum(
+ array(
+ 'host' => $this->translate('Hosts'),
+ 'service' => $this->translate('Services'),
+ )
+ )
+ ));
+
+ $applyTo = $this->getSentOrObjectValue('apply_to');
+
+ if ($applyTo === 'host') {
+ $columns = IcingaHost::enumProperties($this->db, 'host.');
+ } elseif ($applyTo === 'service') {
+ // TODO: Also add host properties
+ $columns = IcingaService::enumProperties($this->db, 'service.');
+ } else {
+ return $this;
+ }
+
+ $this->addAssignFilter(array(
+ 'columns' => $columns,
+ 'required' => true,
+ 'description' => $this->translate(
+ 'This allows you to configure an assignment filter. Please feel'
+ . ' free to combine as many nested operators as you want'
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ protected function addUsersElement()
+ {
+ $users = $this->enumUsers();
+ if (empty($users)) {
+ return $this;
+ }
+
+ $this->addElement(
+ 'extensibleSet',
+ 'users',
+ array(
+ 'label' => $this->translate('Users'),
+ 'description' => $this->translate(
+ 'Users that should be notified by this notifications'
+ ),
+ 'multiOptions' => $this->optionalEnum($users)
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ protected function addUsergroupsElement()
+ {
+ $groups = $this->enumUsergroups();
+ if (empty($groups)) {
+ return $this;
+ }
+
+ $this->addElement(
+ 'extensibleSet',
+ 'user_groups',
+ array(
+ 'label' => $this->translate('User groups'),
+ 'description' => $this->translate(
+ 'User groups that should be notified by this notifications'
+ ),
+ 'multiOptions' => $this->optionalEnum($groups)
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ protected function addIntervalElement()
+ {
+ $this->addElement(
+ 'text',
+ 'notification_interval',
+ array(
+ 'label' => $this->translate('Notification interval'),
+ 'description' => $this->translate(
+ 'The notification interval (in seconds). This interval is'
+ . ' used for active notifications. Defaults to 30 minutes.'
+ . ' If set to 0, re-notifications are disabled.'
+ )
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ protected function addTimesElements()
+ {
+ $this->addElement(
+ 'text',
+ 'times_begin',
+ array(
+ 'label' => $this->translate('First notification delay'),
+ 'description' => $this->translate(
+ 'Delay unless the first notification should be sent'
+ )
+ )
+ );
+
+ $this->addElement(
+ 'text',
+ 'times_end',
+ array(
+ 'label' => $this->translate('Last notification'),
+ 'description' => $this->translate(
+ 'When the last notification should be sent'
+ )
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ protected function addPeriodElement()
+ {
+ $periods = $this->db->enumTimeperiods();
+ if (empty($periods)) {
+ return $this;
+ }
+
+ $this->addElement(
+ 'select',
+ 'period_id',
+ array(
+ 'label' => $this->translate('Time period'),
+ 'description' => $this->translate(
+ 'The name of a time period which determines when this'
+ . ' notification should be triggered. Not set by default.'
+ ),
+ 'multiOptions' => $this->optionalEnum($periods),
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @return self
+ */
+ protected function addCommandElements()
+ {
+ if (! $this->isTemplate()) {
+ return $this;
+ }
+
+ $this->addElement('select', 'command_id', array(
+ 'label' => $this->translate('Notification command'),
+ 'description' => $this->translate('Check command definition'),
+ 'multiOptions' => $this->optionalEnum($this->db->enumNotificationCommands()),
+ 'class' => 'autosubmit',
+ ));
+
+ return $this;
+ }
+
+ protected function enumUsers()
+ {
+ $db = $this->db->getDbAdapter();
+ $select = $db->select()->from(
+ 'icinga_user',
+ array(
+ 'name' => 'object_name',
+ 'display' => 'COALESCE(display_name, object_name)'
+ )
+ )->where('object_type = ?', 'object')->order('display');
+
+ return $db->fetchPairs($select);
+ }
+
+ protected function enumUsergroups()
+ {
+ $db = $this->db->getDbAdapter();
+ $select = $db->select()->from(
+ 'icinga_usergroup',
+ array(
+ 'name' => 'object_name',
+ 'display' => 'COALESCE(display_name, object_name)'
+ )
+ )->where('object_type = ?', 'object')->order('display');
+
+ return $db->fetchPairs($select);
+ }
+}
diff --git a/application/forms/IcingaObjectFieldForm.php b/application/forms/IcingaObjectFieldForm.php
new file mode 100644
index 0000000..d07d9de
--- /dev/null
+++ b/application/forms/IcingaObjectFieldForm.php
@@ -0,0 +1,206 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\DirectorDatafield;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader;
+
+class IcingaObjectFieldForm extends DirectorObjectForm
+{
+ /** @var IcingaObject Please note that $object would conflict with logic in parent class */
+ protected $icingaObject;
+
+ public function setIcingaObject($object)
+ {
+ $this->icingaObject = $object;
+ $this->className = get_class($object) . 'Field';
+ return $this;
+ }
+
+ public function setup()
+ {
+ $object = $this->icingaObject;
+ $type = $object->getShortTableName();
+ $this->addHidden($type . '_id', $object->get('id'));
+
+ $this->addHtmlHint(
+ 'Custom data fields allow you to easily fill custom variables with'
+ . " meaningful data. It's perfectly legal to override inherited fields."
+ . ' You may for example want to allow "network devices" specifying any'
+ . ' string for vars.snmp_community, but restrict "customer routers" to'
+ . ' a specific set, shown as a dropdown.'
+ );
+
+ // TODO: remove assigned ones!
+ $existingFields = $this->db->enumDatafields();
+ $blacklistedVars = array();
+ $suggestedFields = array();
+
+ foreach ($existingFields as $id => $field) {
+ if (preg_match('/ \(([^\)]+)\)$/', $field, $m)) {
+ $blacklistedVars['$' . $m[1] . '$'] = $id;
+ }
+ }
+
+ // TODO: think about imported existing vars without fields
+ // TODO: extract vars from command line (-> dummy)
+ // TODO: do not suggest chosen ones
+ $argumentVars = array();
+ $argumentVarDescriptions = array();
+
+ if ($object->supportsArguments()) {
+ foreach ($object->arguments() as $arg) {
+ if ($arg->argument_format === 'string') {
+ $val = $arg->argument_value;
+ // TODO: create var::extractMacros or so
+
+ if (preg_match_all('/(\$[a-z0-9_]+\$)/', $val, $m, PREG_PATTERN_ORDER)) {
+ foreach ($m[1] as $val) {
+ if (array_key_exists($val, $blacklistedVars)) {
+ $id = $blacklistedVars[$val];
+ $suggestedFields[$id] = $existingFields[$id];
+ unset($existingFields[$id]);
+ } else {
+ $argumentVars[$val] = $val;
+ $argumentVarDescriptions[$val] = $arg->description;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Prepare combined fields array
+ $fields = array();
+ if (! empty($suggestedFields)) {
+ asort($existingFields);
+ $fields[$this->translate('Suggested fields')] = $suggestedFields;
+ }
+
+ if (! empty($argumentVars)) {
+ ksort($argumentVars);
+ $fields[$this->translate('Argument macros')] = $argumentVars;
+ }
+
+ if (! empty($existingFields)) {
+ $fields[$this->translate('Other available fields')] = $existingFields;
+ }
+
+ $this->addElement('select', 'datafield_id', array(
+ 'label' => 'Field',
+ 'required' => true,
+ 'description' => 'Field to assign',
+ 'class' => 'autosubmit',
+ 'multiOptions' => $this->optionalEnum($fields)
+ ));
+
+ if (empty($fields)) {
+ // TODO: show message depending on permissions
+ $msg = $this->translate(
+ 'There are no data fields available. Please ask an administrator to create such'
+ );
+
+ $this->getElement('datafield_id')->addError($msg);
+ }
+
+ if (($id = $this->getSentValue('datafield_id')) && ! ctype_digit($id)) {
+ $this->addElement('text', 'caption', array(
+ 'label' => $this->translate('Caption'),
+ 'required' => true,
+ 'ignore' => true,
+ 'value' => trim($id, '$'),
+ 'description' => $this->translate('The caption which should be displayed')
+ ));
+
+ $this->addElement('textarea', 'description', array(
+ 'label' => $this->translate('Description'),
+ 'description' => $this->translate('A description about the field'),
+ 'ignore' => true,
+ 'value' => array_key_exists($id, $argumentVarDescriptions) ? $argumentVarDescriptions[$id] : null,
+ 'rows' => '3',
+ ));
+ }
+
+ $this->addElement('select', 'is_required', array(
+ 'label' => $this->translate('Mandatory'),
+ 'description' => $this->translate('Whether this field should be mandatory'),
+ 'required' => true,
+ 'multiOptions' => array(
+ 'n' => $this->translate('Optional'),
+ 'y' => $this->translate('Mandatory'),
+ )
+ ));
+
+ $filterFields = array();
+ $prefix = null;
+ if ($object instanceof IcingaHost) {
+ $prefix = 'host.vars.';
+ } elseif ($object instanceof IcingaService) {
+ $prefix = 'service.vars.';
+ }
+
+ if ($prefix) {
+ $loader = new IcingaObjectFieldLoader($object);
+ $fields = $loader->getFields();
+
+ foreach ($fields as $varName => $field) {
+ $filterFields[$prefix . $field->varname] = $field->caption;
+ }
+
+ $this->addFilterElement('var_filter', array(
+ 'description' => $this->translate(
+ 'You might want to show this field only when certain conditions are met.'
+ . ' Otherwise it will not be available and values eventually set before'
+ . ' will be cleared once stored'
+ ),
+ 'columns' => $filterFields,
+ ));
+
+ $this->addDisplayGroup(array($this->getElement('var_filter')), 'field_filter', array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl')),
+ 'Fieldset',
+ ),
+ 'order' => 30,
+ 'legend' => $this->translate('Show based on filter')
+ ));
+ }
+
+ $this->setButtons();
+ }
+
+ protected function onRequest()
+ {
+ parent::onRequest();
+ if ($this->getSentValue('delete') === $this->translate('Delete')) {
+ $this->object()->delete();
+ $this->setSuccessUrl($this->getSuccessUrl()->without('field_id'));
+ $this->redirectOnSuccess($this->translate('Field has been removed'));
+ }
+ }
+
+ public function onSuccess()
+ {
+ $fieldId = $this->getValue('datafield_id');
+
+ if (! ctype_digit($fieldId)) {
+ $field = DirectorDatafield::create(array(
+ 'varname' => trim($fieldId, '$'),
+ 'caption' => $this->getValue('caption'),
+ 'description' => $this->getValue('description'),
+ 'datatype' => 'Icinga\Module\Director\DataType\DataTypeString',
+ ));
+ $field->store($this->getDb());
+ $this->setElementValue('datafield_id', $field->get('id'));
+ $this->object()->set('datafield_id', $field->get('id'));
+ }
+
+ $this->object()->set('var_filter', $this->getValue('var_filter'));
+ return parent::onSuccess();
+ }
+}
diff --git a/application/forms/IcingaServiceForm.php b/application/forms/IcingaServiceForm.php
new file mode 100644
index 0000000..4450ec1
--- /dev/null
+++ b/application/forms/IcingaServiceForm.php
@@ -0,0 +1,486 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Director\Data\PropertiesFilter\ArrayCustomVariablesFilter;
+use Icinga\Module\Director\Exception\NestingError;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+
+class IcingaServiceForm extends DirectorObjectForm
+{
+ /** @var IcingaHost */
+ private $host;
+
+ private $set;
+
+ private $apply;
+
+ /** @var IcingaService */
+ protected $object;
+
+ private $applyGenerated;
+
+ private $inheritedFrom;
+
+ public function setApplyGenerated(IcingaService $applyGenerated)
+ {
+ $this->applyGenerated = $applyGenerated;
+ return $this;
+ }
+
+ public function setInheritedFrom($hostname)
+ {
+ $this->inheritedFrom = $hostname;
+ return $this;
+ }
+
+ public function setup()
+ {
+ if ($this->providesOverrides()) {
+ return;
+ }
+
+ if ($this->host && $this->set) {
+ $this->setupOnHostForSet();
+ return;
+ }
+
+ try {
+ if (!$this->isNew() && $this->host === null) {
+ $this->host = $this->object->getResolvedRelated('host');
+ }
+ } catch (NestingError $nestingError) {
+ // ignore for the form to load
+ }
+
+ if ($this->set !== null) {
+ $this->setupSetRelatedElements();
+ } elseif ($this->host === null) {
+ $this->setupServiceElements();
+ } else {
+ $this->setupHostRelatedElements();
+ }
+ }
+
+ protected function providesOverrides()
+ {
+ return $this->applyGenerated
+ || $this->inheritedFrom
+ || ($this->host && $this->set)
+ || ($this->object && $this->object->usesVarOverrides());
+ }
+
+ protected function onAddedFields()
+ {
+ if (! $this->providesOverrides()) {
+ return;
+ }
+
+ $this->addHtmlHint(
+ $this->getOverrideHint(),
+ array('name' => 'inheritance_hint')
+ );
+
+ $group = $this->getDisplayGroup('custom_fields');
+
+ if ($group) {
+ $elements = $group->getElements();
+ $group->setElements(array($this->getElement('inheritance_hint')));
+ $group->addElements($elements);
+ $this->setSubmitLabel(
+ $this->translate('Override vars')
+ );
+ } else {
+ $this->addElementsToGroup(
+ array('inheritance_hint'),
+ 'custom_fields',
+ 20,
+ $this->translate('Hints regarding this service')
+ );
+
+ $this->setSubmitLabel(false);
+ }
+ }
+
+ public function createApplyRuleFor(IcingaService $service)
+ {
+ $this->apply = $service;
+ $object = $this->object();
+ $object->set('imports', $service->getObjectName());
+ $object->object_type = 'apply';
+ $object->object_name = $service->object_name;
+ return $this;
+ }
+
+ protected function setupServiceElements()
+ {
+ if ($this->object) {
+ $this->addHidden('object_type', $this->object->object_type);
+ } else {
+ $this->addHidden('object_type', 'template');
+ }
+
+ $this->addNameElement()
+ ->addHostObjectElement()
+ ->addImportsElement()
+ ->addGroupsElement()
+ ->addDisabledElement()
+ ->addApplyForElement()
+ ->groupMainProperties()
+ ->addAssignmentElements()
+ ->addCheckCommandElements()
+ ->addCheckExecutionElements()
+ ->addExtraInfoElements()
+ ->addAgentAndZoneElements()
+ ->setButtons();
+ }
+
+ protected function getOverrideHint()
+ {
+ $view = $this->getView();
+
+ if ($this->object && $this->object->usesVarOverrides()) {
+ return $this->translate(
+ 'This service has been generated in an automated way, but still'
+ . ' allows you to override the following properties in a safe way.'
+ );
+ }
+
+ if ($this->applyGenerated) {
+ return $view->escape(sprintf(
+ $this->translate(
+ 'This service has been generated using an apply rule, assigned where %s'
+ ),
+ Filter::fromQueryString($this->applyGenerated->assign_filter)
+ ));
+ }
+
+ if ($this->inheritedFrom) {
+ $msg = $view->escape($this->translate(
+ 'This service has been inherited from %s. Still, you might want'
+ . ' to change the following properties for this host only.'
+ ));
+
+ $name = $this->inheritedFrom;
+ $link = $view->qlink(
+ $name,
+ 'director/service',
+ array(
+ 'host' => $name,
+ 'name' => $this->object->object_name,
+ ),
+ array('data-base-target' => '_next')
+ );
+
+ return sprintf($msg, $link);
+ }
+
+ $this->setSubmitLabel(
+ $this->translate('Override vars')
+ );
+ }
+
+ protected function setupOnHostForSet()
+ {
+ $view = $this->getView();
+ $msg = $view->escape($this->translate(
+ 'This service belongs to the service set "%s". Still, you might want'
+ . ' to change the following properties for this host only.'
+ ));
+
+ $name = $this->set->getObjectName();
+ $link = $view->qlink(
+ $name,
+ 'director/serviceset',
+ array(
+ 'name' => $name,
+ ),
+ array('data-base-target' => '_next')
+ );
+
+ $this->addHtmlHint(
+ sprintf($msg, $link),
+ array('name' => 'inheritance_hint')
+ );
+
+ $this->addElementsToGroup(
+ array('inheritance_hint'),
+ 'custom_fields',
+ 50,
+ $this->translate('Custom properties')
+ );
+
+ $this->setSubmitLabel(
+ $this->translate('Override vars')
+ );
+ }
+
+ protected function addAssignmentElements()
+ {
+ $this->addAssignFilter(array(
+ 'columns' => IcingaHost::enumProperties($this->db, 'host.'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'This allows you to configure an assignment filter. Please feel'
+ . ' free to combine as many nested operators as you want'
+ )
+ ));
+
+ return $this;
+ }
+
+ protected function setupHostRelatedElements()
+ {
+ $this->addHidden('host_id', $this->host->id);
+ $this->addHidden('object_type', 'object');
+ $this->addImportsElement();
+ $imports = $this->getSentOrObjectValue('imports');
+
+ if ($this->hasBeenSent()) {
+ $imports = $this->getElement('imports')->setValue($imports)->getValue();
+ }
+
+ if ($this->isNew() && empty($imports)) {
+ $this->groupMainProperties();
+ return;
+ }
+
+ $this->addNameElement()
+ ->addDisabledElement()
+ ->groupMainProperties()
+ ->addCheckCommandElements()
+ ->addExtraInfoElements()
+ ->setButtons();
+
+ if ($this->hasBeenSent()) {
+ $name = $this->getSentOrObjectValue('object_name');
+ if (!strlen($name)) {
+ $this->setElementValue('object_name', end($imports));
+ $this->object->object_name = end($imports);
+ }
+ }
+ }
+
+ public function setHost(IcingaHost $host)
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ protected function setupSetRelatedElements()
+ {
+ $this->addHidden('service_set_id', $this->set->id);
+ $this->addHidden('object_type', 'apply');
+ $this->addImportsElement();
+ $imports = $this->getSentOrObjectValue('imports');
+
+ if ($this->hasBeenSent()) {
+ $imports = $this->getElement('imports')->setValue($imports)->getValue();
+ }
+
+ if ($this->isNew() && empty($imports)) {
+ $this->groupMainProperties();
+ return;
+ }
+
+ $this->addNameElement()
+ ->addDisabledElement()
+ ->groupMainProperties()
+ ->addCheckCommandElements(true)
+ ->addCheckExecutionElements(true)
+ ->addExtraInfoElements()
+ ->setButtons();
+
+ if ($this->hasBeenSent()) {
+ $name = $this->getSentOrObjectValue('object_name');
+ if (!strlen($name)) {
+ $this->setElementValue('object_name', end($imports));
+ $this->object->object_name = end($imports);
+ }
+ }
+ }
+
+ public function setServiceSet(IcingaServiceSet $set)
+ {
+ $this->set = $set;
+ return $this;
+ }
+
+ protected function addNameElement()
+ {
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Name'),
+ 'required' => !$this->object()->isApplyRule(),
+ 'description' => $this->translate(
+ 'Name for the Icinga service you are going to create'
+ )
+ ));
+
+ return $this;
+ }
+
+ protected function addHostObjectElement()
+ {
+ if ($this->isObject()) {
+ $this->addElement('select', 'host_id', array(
+ 'label' => $this->translate('Host'),
+ 'required' => true,
+ 'multiOptions' => $this->optionalEnum($this->enumHostsAndTemplates()),
+ 'description' => $this->translate(
+ 'Choose the host this single service should be assigned to'
+ )
+ ));
+ }
+
+ return $this;
+ }
+
+ protected function addApplyForElement()
+ {
+ if ($this->object->isApplyRule()) {
+ $hostProperties = IcingaHost::enumProperties(
+ $this->object->getConnection(),
+ 'host.',
+ new ArrayCustomVariablesFilter()
+ );
+
+ $this->addElement('select', 'apply_for', array(
+ 'label' => $this->translate('Apply For'),
+ 'class' => 'assign-property autosubmit',
+ 'multiOptions' => $this->optionalEnum($hostProperties, $this->translate('None')),
+ 'description' => $this->translate(
+ 'Evaluates the apply for rule for ' .
+ 'all objects with the custom attribute specified. ' .
+ 'E.g selecting "host.vars.custom_attr" will generate "for (config in ' .
+ 'host.vars.array_var)" where "config" will be accessible through "$config$". ' .
+ 'NOTE: only custom variables of type "Array" are eligible.'
+ )
+ ));
+ }
+
+ return $this;
+ }
+
+ protected function addGroupsElement()
+ {
+ $groups = $this->enumServicegroups();
+
+ if (! empty($groups)) {
+ $this->addElement('extensibleSet', 'groups', array(
+ 'label' => $this->translate('Groups'),
+ 'multiOptions' => $this->optionallyAddFromEnum($groups),
+ 'positional' => false,
+ 'description' => $this->translate(
+ 'Service groups that should be directly assigned to this service.'
+ . ' Servicegroups can be useful for various reasons. They are'
+ . ' helpful to provided service-type specific view in Icinga Web 2,'
+ . ' either for custom dashboards or as an instrument to enforce'
+ . ' restrictions. Service groups can be directly assigned to'
+ . ' single services or to service templates.'
+ )
+ ));
+ }
+
+ return $this;
+ }
+
+ protected function addAgentAndZoneElements()
+ {
+ if (!$this->isTemplate()) {
+ return $this;
+ }
+
+ $this->optionalBoolean(
+ 'use_agent',
+ $this->translate('Run on agent'),
+ $this->translate(
+ 'Whether the check commmand for this service should be executed'
+ . ' on the Icinga agent'
+ )
+ );
+ $this->addZoneElement();
+
+ $elements = array(
+ 'use_agent',
+ 'zone_id',
+ );
+ $this->addDisplayGroup($elements, 'clustering', array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl')),
+ 'Fieldset',
+ ),
+ 'order' => 40,
+ 'legend' => $this->translate('Icinga Agent and zone settings')
+ ));
+
+ return $this;
+ }
+
+ protected function enumHostsAndTemplates()
+ {
+ return array(
+ $this->translate('Templates') => $this->db->enumHostTemplates(),
+ $this->translate('Hosts') => $this->db->enumHosts(),
+ );
+ }
+
+ protected function enumServicegroups()
+ {
+ $db = $this->db->getDbAdapter();
+ $select = $db->select()->from(
+ 'icinga_servicegroup',
+ array(
+ 'name' => 'object_name',
+ 'display' => 'COALESCE(display_name, object_name)'
+ )
+ )->where('object_type = ?', 'object')->order('display');
+
+ return $db->fetchPairs($select);
+ }
+
+ protected function succeedForOverrides()
+ {
+ $vars = array();
+ foreach ($this->object->vars() as $key => $var) {
+ $vars[$key] = $var->getValue();
+ }
+
+ $host = $this->host;
+ $serviceName = $this->object->getObjectName();
+
+ $this->host->overrideServiceVars($serviceName, (object) $vars);
+
+ if ($host->hasBeenModified()) {
+ $msg = sprintf(
+ empty($vars)
+ ? $this->translate('All overrides have been removed from "%s"')
+ : $this->translate('The given properties have been stored for "%s"'),
+ $this->translate($host->getObjectName())
+ );
+
+ $host->store();
+ } else {
+ if ($this->isApiRequest()) {
+ $this->setHttpResponseCode(304);
+ }
+
+ $msg = $this->translate('No action taken, object has not been modified');
+ }
+
+ $this->redirectOnSuccess($msg);
+ }
+
+ public function onSuccess()
+ {
+ if ($this->providesOverrides()) {
+ return $this->succeedForOverrides();
+ }
+
+ return parent::onSuccess();
+ }
+}
diff --git a/application/forms/IcingaServiceGroupForm.php b/application/forms/IcingaServiceGroupForm.php
new file mode 100644
index 0000000..cc4226b
--- /dev/null
+++ b/application/forms/IcingaServiceGroupForm.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaServiceGroupForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addHidden('object_type', 'object');
+
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Servicegroup'),
+ 'required' => true,
+ 'description' => $this->translate('Icinga object name for this service group')
+ ));
+
+ $this->addGroupDisplayNameElement()
+ ->addAssignmentElements()
+ ->setButtons();
+ }
+
+ protected function addAssignmentElements()
+ {
+ $this->addAssignFilter(array(
+ 'columns' => IcingaService::enumProperties($this->db, 'service.'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'This allows you to configure an assignment filter. Please feel'
+ . ' free to combine as many nested operators as you want'
+ )
+ ));
+
+ return $this;
+ }
+}
diff --git a/application/forms/IcingaServiceSetForm.php b/application/forms/IcingaServiceSetForm.php
new file mode 100644
index 0000000..d95e94c
--- /dev/null
+++ b/application/forms/IcingaServiceSetForm.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaServiceSetForm extends DirectorObjectForm
+{
+ protected $host;
+
+ protected $listUrl = 'director/services/sets';
+
+ public function setup()
+ {
+ if ($this->host === null) {
+ $this->setupTemplate();
+ } else {
+ $this->setupHost();
+ }
+
+ $this->setupFields()
+ ->setButtons();
+ }
+
+ protected function setupFields()
+ {
+ /** @var IcingaServiceSet $object */
+ $object = $this->object();
+
+ $this->assertResolvedImports();
+
+ if ($this->hasBeenSent() && $services = $this->getSentValue('service')) {
+ $object->service = $services;
+ }
+
+ // TODO: disabled for now. Sets have no fields, so somehow the resolver
+ // fails here
+ if (false && $this->assertResolvedImports()) {
+ $this->fieldLoader($object)
+ ->loadFieldsForMultipleObjects($object->getServiceObjects());
+ }
+
+ return $this;
+ }
+
+ protected function setupTemplate()
+ {
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Service set name'),
+ 'description' => $this->translate(
+ 'A short name identifying this set of services'
+ ),
+ 'required' => true,
+ ));
+
+ $this->addHidden('object_type', 'template');
+ $this->addDescriptionElement()
+ ->addAssignmentElements();
+ }
+
+ protected function setupHost()
+ {
+ $object = $this->object();
+ if ($this->hasBeenSent()) {
+ $object->set('object_name', $this->getSentValue('imports'));
+ $object->set('imports', $object->object_name);
+ }
+
+ if (! $object->hasBeenLoadedFromDb()) {
+ $this->addSingleImportsElement();
+ }
+
+ if (count($object->get('imports'))) {
+ $this->addHtmlHint(
+ $this->getView()->escape(
+ $object->getResolvedProperty('description')
+ )
+ );
+ }
+
+ $this->addHidden('object_type', 'object');
+ $this->addHidden('host_id', $this->host->id);
+ }
+
+ public function setHost(IcingaHost $host)
+ {
+ $this->host = $host;
+ return $this;
+ }
+ protected function addSingleImportsElement()
+ {
+ $enum = $this->enumAllowedTemplates();
+
+ $this->addElement('select', 'imports', array(
+ 'label' => $this->translate('Service set'),
+ 'description' => $this->translate(
+ 'The service set that should be assigned to this host'
+ ),
+ 'required' => true,
+ 'multiOptions' => $this->optionallyAddFromEnum($enum),
+ 'class' => 'autosubmit'
+ ));
+
+ return $this;
+ }
+
+ protected function addDescriptionElement()
+ {
+ $this->addElement('textarea', 'description', array(
+ 'label' => $this->translate('Description'),
+ 'description' => $this->translate(
+ 'A meaningful description explaining your users what to expect'
+ . ' when assigning this set of services'
+ ),
+ 'rows' => '3',
+ 'required' => ! $this->isTemplate(),
+ ));
+
+ return $this;
+ }
+
+ protected function addAssignmentElements()
+ {
+ $this->addAssignFilter(array(
+ 'columns' => IcingaHost::enumProperties($this->db, 'host.'),
+ 'description' => $this->translate(
+ 'This allows you to configure an assignment filter. Please feel'
+ . ' free to combine as many nested operators as you want. You'
+ . ' might also want to skip this, define it later and/or just'
+ . ' add this set of services to single hosts'
+ )
+ ));
+
+ return $this;
+ }
+}
diff --git a/application/forms/IcingaServiceVarForm.php b/application/forms/IcingaServiceVarForm.php
new file mode 100644
index 0000000..e7ac4a0
--- /dev/null
+++ b/application/forms/IcingaServiceVarForm.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+/**
+ * @deprecated
+ */
+class IcingaServiceVarForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addElement('select', 'service_id', array(
+ 'label' => $this->translate('Service'),
+ 'description' => $this->translate('The name of the service'),
+ 'multiOptions' => $this->optionalEnum($this->db->enumServices()),
+ 'required' => true
+ ));
+
+ $this->addElement('text', 'varname', array(
+ 'label' => $this->translate('Name'),
+ 'description' => $this->translate('service var name')
+ ));
+
+ $this->addElement('textarea', 'varvalue', array(
+ 'label' => $this->translate('Value'),
+ 'description' => $this->translate('service var value')
+ ));
+
+ $this->addElement('text', 'format', array(
+ 'label' => $this->translate('Format'),
+ 'description' => $this->translate('value format')
+ ));
+ }
+}
diff --git a/application/forms/IcingaTimePeriodForm.php b/application/forms/IcingaTimePeriodForm.php
new file mode 100644
index 0000000..dca062f
--- /dev/null
+++ b/application/forms/IcingaTimePeriodForm.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaTimePeriodForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $isTemplate = isset($_POST['object_type']) && $_POST['object_type'] === 'template';
+ $this->addElement('select', 'object_type', array(
+ 'label' => $this->translate('Object type'),
+ 'description' => $this->translate('Whether this should be a template'),
+ 'class' => 'autosubmit',
+ 'multiOptions' => $this->optionalEnum(array(
+ 'object' => $this->translate('Timeperiod object'),
+ 'template' => $this->translate('Timeperiod template'),
+ ))
+ ));
+
+ if ($isTemplate) {
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Timeperiod template name'),
+ 'required' => true,
+ 'description' => $this->translate('Name for the Icinga timperiod template you are going to create')
+ ));
+ } else {
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Timeperiod'),
+ 'required' => true,
+ 'description' => $this->translate('Name for the Icinga timeperiod you are going to create')
+ ));
+ }
+
+ $this->addElement('text', 'display_name', array(
+ 'label' => $this->translate('Display Name'),
+ 'description' => $this->translate('the display name')
+ ));
+
+ if ($this->isTemplate()) {
+ $this->addElement('text', 'update_method', array(
+ 'label' => $this->translate('Update Method'),
+ 'description' => $this->translate('the update method'),
+ 'value' => 'LegacyTimePeriod',
+ ));
+ } else {
+ // TODO: I'd like to skip this for objects inheriting from a template
+ // with a defined update_method. However, unfortunately it's too
+ // early for $this->object()->getResolvedProperty('update_method').
+ // Should be fixed.
+ $this->addHidden('update_method', 'LegacyTimePeriod');
+ }
+
+ $this->addImportsElement();
+
+ $this->setButtons();
+ }
+}
diff --git a/application/forms/IcingaTimePeriodRangeForm.php b/application/forms/IcingaTimePeriodRangeForm.php
new file mode 100644
index 0000000..cddadab
--- /dev/null
+++ b/application/forms/IcingaTimePeriodRangeForm.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\IcingaTimePeriod;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaTimePeriodRangeForm extends DirectorObjectForm
+{
+ /**
+ * @var IcingaTimePeriod
+ */
+ private $period;
+
+ public function setup()
+ {
+ $this->addHidden('timeperiod_id', $this->period->get('id'));
+ $this->addElement('text', 'range_key', array(
+ 'label' => $this->translate('Day(s)'),
+ 'description' => $this->translate(
+ 'Might by, monday, tuesday, 2016-01-28 - have a look at the documentation for more examples'
+ ),
+ ));
+
+ $this->addElement('text', 'range_value', array(
+ 'label' => $this->translate('Timerperiods'),
+ 'description' => $this->translate(
+ 'One or more time periods, e.g. 00:00-24:00 or 00:00-09:00,17:00-24:00'
+ ),
+ ));
+
+ $this->setButtons();
+ }
+
+ public function setTimePeriod(IcingaTimePeriod $period)
+ {
+ $this->period = $period;
+ return $this;
+ }
+
+ public function onSuccess()
+ {
+ $object = $this->object();
+ if ($object->hasBeenModified()) {
+ $this->period->ranges()->setRange(
+ $this->getValue('range_key'),
+ $this->getValue('range_value')
+ );
+ }
+
+ if ($this->period->hasBeenModified()) {
+ if (! $object->hasBeenLoadedFromDb()) {
+ $this->setHttpResponseCode(201);
+ }
+
+ $msg = sprintf(
+ $object->hasBeenLoadedFromDb()
+ ? $this->translate('The %s has successfully been stored')
+ : $this->translate('A new %s has successfully been created'),
+ $this->translate($this->getObjectShortClassName())
+ );
+
+ $this->period->store($this->db);
+ } else {
+ if ($this->isApiRequest()) {
+ $this->setHttpResponseCode(304);
+ }
+ $msg = $this->translate('No action taken, object has not been modified');
+ }
+ if ($object instanceof IcingaObject) {
+ $this->setSuccessUrl(
+ 'director/' . strtolower($this->getObjectShortClassName()),
+ $object->getUrlParams()
+ );
+ }
+
+ $this->redirectOnSuccess($msg);
+ }
+}
diff --git a/application/forms/IcingaUserForm.php b/application/forms/IcingaUserForm.php
new file mode 100644
index 0000000..5ff1ff4
--- /dev/null
+++ b/application/forms/IcingaUserForm.php
@@ -0,0 +1,161 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaUserForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addObjectTypeElement();
+ if (! $this->hasObjectType()) {
+ $this->groupMainProperties();
+ return;
+ }
+
+ if ($this->isTemplate()) {
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('User template name'),
+ 'required' => true,
+ 'description' => $this->translate('Name for the Icinga user template you are going to create')
+ ));
+ } else {
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Username'),
+ 'required' => true,
+ 'description' => $this->translate('Name for the Icinga user object you are going to create')
+ ));
+ }
+
+ if (! $this->isTemplate()) {
+ $this->addElement('text', 'email', array(
+ 'label' => $this->translate('Email'),
+ 'description' => $this->translate('The Email address of the user.')
+ ));
+
+ $this->addElement('text', 'pager', array(
+ 'label' => $this->translate('Pager'),
+ 'description' => $this->translate('The pager address of the user.')
+ ));
+ }
+
+ $this->addGroupsElement()
+ ->addImportsElement()
+ ->addDisplayNameElement()
+ ->addEnableNotificationsElement()
+ ->addDisabledElement()
+ ->addZoneElements()
+ ->addEventFilterElements()
+ ->groupMainProperties()
+ ->setButtons();
+ }
+
+ protected function addZoneElements()
+ {
+ if (! $this->isTemplate()) {
+ return $this;
+ }
+
+ $this->addZoneElement();
+ $this->addDisplayGroup(array('zone_id'), 'clustering', array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl')),
+ 'Fieldset',
+ ),
+ 'order' => 80,
+ 'legend' => $this->translate('Zone settings')
+ ));
+
+ return $this;
+ }
+
+ protected function addEnableNotificationsElement()
+ {
+ $this->optionalBoolean(
+ 'enable_notifications',
+ $this->translate('Send notifications'),
+ $this->translate('Whether to send notifications for this user')
+ );
+
+ return $this;
+ }
+
+ protected function addGroupsElement()
+ {
+ $groups = $this->enumUsergroups();
+
+ if (empty($groups)) {
+ return $this;
+ }
+
+ $this->addElement('extensibleSet', 'groups', array(
+ 'label' => $this->translate('Groups'),
+ 'multiOptions' => $this->optionallyAddFromEnum($groups),
+ 'positional' => false,
+ 'description' => $this->translate(
+ 'User groups that should be directly assigned to this user. Groups can be useful'
+ . ' for various reasons. You might prefer to send notifications to groups instead of'
+ . ' single users'
+ )
+ ));
+
+ return $this;
+ }
+
+ protected function addDisplayNameElement()
+ {
+ if ($this->isTemplate()) {
+ return $this;
+ }
+
+ $this->addElement('text', 'display_name', array(
+ 'label' => $this->translate('Display name'),
+ 'description' => $this->translate(
+ 'Alternative name for this user. In case your object name is a'
+ . ' username, this could be the full name of the corresponding person'
+ )
+ ));
+
+ return $this;
+ }
+
+ protected function groupObjectDefinition()
+ {
+ $elements = array(
+ 'object_type',
+ 'object_name',
+ 'display_name',
+ 'imports',
+ 'groups',
+ 'email',
+ 'pager',
+ 'enable_notifications',
+ 'disabled',
+ );
+ $this->addDisplayGroup($elements, 'object_definition', array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl')),
+ 'Fieldset',
+ ),
+ 'order' => 20,
+ 'legend' => $this->translate('User properties')
+ ));
+ }
+
+ protected function enumUsergroups()
+ {
+ $db = $this->db->getDbAdapter();
+ $select = $db->select()->from(
+ 'icinga_usergroup',
+ array(
+ 'name' => 'object_name',
+ 'display' => 'COALESCE(display_name, object_name)'
+ )
+ )->where('object_type = ?', 'object')->order('display');
+
+ return $db->fetchPairs($select);
+ }
+}
diff --git a/application/forms/IcingaUserGroupForm.php b/application/forms/IcingaUserGroupForm.php
new file mode 100644
index 0000000..2aceaf9
--- /dev/null
+++ b/application/forms/IcingaUserGroupForm.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaUserGroupForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addHidden('object_type', 'object');
+
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Usergroup'),
+ 'required' => true,
+ 'description' => $this->translate('Icinga object name for this user group')
+ ));
+
+ $this->addGroupDisplayNameElement()
+ ->addZoneElements()
+ ->groupMainProperties()
+ ->setButtons();
+ }
+
+ protected function addZoneElements()
+ {
+ $this->addZoneElement();
+ $this->addDisplayGroup(array('zone_id'), 'clustering', array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl')),
+ 'Fieldset',
+ ),
+ 'order' => 80,
+ 'legend' => $this->translate('Zone settings')
+ ));
+
+ return $this;
+ }
+}
diff --git a/application/forms/IcingaZoneForm.php b/application/forms/IcingaZoneForm.php
new file mode 100644
index 0000000..bf27cae
--- /dev/null
+++ b/application/forms/IcingaZoneForm.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class IcingaZoneForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addHidden('object_type', 'object');
+
+ $this->addElement('text', 'object_name', array(
+ 'label' => $this->translate('Zone name'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'Name for the Icinga zone you are going to create'
+ )
+ ));
+
+ $this->addElement('select', 'is_global', array(
+ 'label' => $this->translate('Global zone'),
+ 'description' => $this->translate(
+ 'Whether this zone should be available everywhere. Please note that'
+ . ' it rarely leads to the desired result when you try to distribute'
+ . ' global zones in distrubuted environments'
+ ),
+ 'multiOptions' => array(
+ 'n' => $this->translate('No'),
+ 'y' => $this->translate('Yes'),
+ ),
+ 'required' => true,
+ ));
+
+ $this->addElement('select', 'parent_id', array(
+ 'label' => $this->translate('Parent Zone'),
+ 'description' => $this->translate('Chose an (optional) parent zone'),
+ 'multiOptions' => $this->optionalEnum($this->db->enumZones()),
+ ));
+
+ $this->setButtons();
+ }
+}
diff --git a/application/forms/ImportCheckForm.php b/application/forms/ImportCheckForm.php
new file mode 100644
index 0000000..ae28305
--- /dev/null
+++ b/application/forms/ImportCheckForm.php
@@ -0,0 +1,51 @@
+<?php
+
+// TODO: Check whether this can be removed
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class ImportCheckForm extends QuickForm
+{
+ /** @var ImportSource */
+ protected $source;
+
+ public function setImportSource(ImportSource $source)
+ {
+ $this->source = $source;
+ return $this;
+ }
+
+ public function setup()
+ {
+ $this->submitLabel = false;
+ $this->addElement('submit', 'submit', array(
+ 'label' => $this->translate('Check for changes'),
+ 'decorators' => array('ViewHelper')
+ ));
+ }
+
+ public function onSuccess()
+ {
+ $source = $this->source;
+ if ($source->checkForChanges()) {
+ $this->setSuccessMessage(
+ $this->translate('This Import Source provides modified data')
+ );
+ } else {
+ $this->setSuccessMessage(
+ $this->translate(
+ 'Nothing to do, data provided by this Import Source'
+ . " didn't change since the last import run"
+ )
+ );
+ }
+
+ if ($source->get('import_state') === 'failing') {
+ $this->addError($this->translate('Checking this Import Source failed'));
+ } else {
+ parent::onSuccess();
+ }
+ }
+}
diff --git a/application/forms/ImportRowModifierForm.php b/application/forms/ImportRowModifierForm.php
new file mode 100644
index 0000000..4f6511f
--- /dev/null
+++ b/application/forms/ImportRowModifierForm.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Exception;
+use Icinga\Application\Hook;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Director\Hook\ImportSourceHook;
+use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Import\Import;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class ImportRowModifierForm extends DirectorObjectForm
+{
+ /** @var ImportSource */
+ protected $source;
+
+ /** @var ImportSourceHook */
+ protected $importSource;
+
+ public function setup()
+ {
+ $this->addHidden('source_id', $this->source->id);
+ $this->addHidden('priority', 1);
+
+ $this->addElement('select', 'property_name', array(
+ 'label' => $this->translate('Property'),
+ 'description' => $this->translate('This must be an import source column (property)'),
+ 'multiOptions' => $this->optionalEnum($this->enumSourceColumns()),
+ 'required' => true,
+ ));
+
+ $this->addElement('text', 'target_property', array(
+ 'label' => $this->translate('Target property'),
+ 'description' => $this->translate(
+ 'You might want to write the modified value to another (new) property.'
+ . ' This property name can be defined here, the original property would'
+ . ' remain unmodified. Please leave this blank in case you just want to'
+ . ' modify the value of a specific property'
+ ),
+ ));
+
+ $error = false;
+ try {
+ $mods = $this->enumModifiers();
+ } catch (Exception $e) {
+ $error = $e->getMessage();
+ $mods = $this->optionalEnum(array());
+ }
+
+ $this->addElement('select', 'provider_class', array(
+ 'label' => $this->translate('Modifier'),
+ 'required' => true,
+ 'description' => $this->translate(
+ 'A property modifier allows you to modify a specific property at import time'
+ ),
+ 'multiOptions' => $this->optionalEnum($mods),
+ 'class' => 'autosubmit',
+ ));
+ if ($error) {
+ $this->getElement('provider_class')->addError($error);
+ }
+
+ try {
+ if ($class = $this->getSentValue('provider_class')) {
+ if ($class && array_key_exists($class, $mods)) {
+ $this->addSettings($class);
+ }
+ } elseif ($class = $this->object()->get('provider_class')) {
+ $this->addSettings($class);
+ }
+
+ // TODO: next line looks like obsolete duplicate code to me
+ $this->addSettings();
+ } catch (Exception $e) {
+ $this->getElement('provider_class')->addError($e->getMessage());
+ }
+
+ foreach ($this->object()->getSettings() as $key => $val) {
+ if ($el = $this->getElement($key)) {
+ $el->setValue($val);
+ }
+ }
+
+ $this->setButtons();
+ }
+
+ protected function enumSourceColumns()
+ {
+ $columns = array_merge(
+ $this->getImportSource()->listColumns(),
+ $this->source->listModifierTargetProperties()
+ );
+
+ $columns = array_combine($columns, $columns);
+ return $columns;
+ }
+
+ protected function getImportSource()
+ {
+ if ($this->importSource === null) {
+ $this->importSource = ImportSourceHook::loadByName(
+ $this->source->get('source_name'),
+ $this->db
+ );
+ }
+
+ return $this->importSource;
+ }
+
+ protected function enumModifiers()
+ {
+ /** @var PropertyModifierHook[] $hooks */
+ $hooks = Hook::all('Director\\PropertyModifier');
+ $enum = array();
+ foreach ($hooks as $hook) {
+ $enum[get_class($hook)] = $hook->getName();
+ }
+
+ asort($enum);
+
+ return $enum;
+ }
+
+ protected function addSettings($class = null)
+ {
+ if ($class === null) {
+ $class = $this->getValue('provider_class');
+ }
+
+ if ($class !== null) {
+ if (! class_exists($class)) {
+ throw new ConfigurationError(
+ 'The hooked class "%s" for this property modifier does no longer exist',
+ $class
+ );
+ }
+
+ $class::addSettingsFormFields($this);
+ }
+ }
+
+ public function setSource(ImportSource $source)
+ {
+ $this->source = $source;
+ return $this;
+ }
+}
diff --git a/application/forms/ImportRunForm.php b/application/forms/ImportRunForm.php
new file mode 100644
index 0000000..5764e38
--- /dev/null
+++ b/application/forms/ImportRunForm.php
@@ -0,0 +1,51 @@
+<?php
+
+// TODO: Check whether this can be removed
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class ImportRunForm extends QuickForm
+{
+ /** @var ImportSource */
+ protected $source;
+
+ public function setImportSource(ImportSource $source)
+ {
+ $this->source = $source;
+ return $this;
+ }
+
+ public function setup()
+ {
+ $this->submitLabel = false;
+ $this->addElement('submit', 'submit', array(
+ 'label' => $this->translate('Trigger Import Run'),
+ 'decorators' => array('ViewHelper')
+ ));
+ }
+
+ public function onSuccess()
+ {
+ $source = $this->source;
+ if ($source->runImport()) {
+ $this->setSuccessMessage(
+ $this->translate('Imported new data from this Import Source')
+ );
+ } else {
+ $this->setSuccessMessage(
+ $this->translate(
+ 'Nothing to do, data provided by this Import Source'
+ . " didn't change since the last import run"
+ )
+ );
+ }
+
+ if ($source->import_state === 'failing') {
+ $this->addError($this->translate('Triggering this Import Source failed'));
+ } else {
+ parent::onSuccess();
+ }
+ }
+}
diff --git a/application/forms/ImportSourceForm.php b/application/forms/ImportSourceForm.php
new file mode 100644
index 0000000..d800802
--- /dev/null
+++ b/application/forms/ImportSourceForm.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Hook\ImportSourceHook;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Web\Hook;
+
+class ImportSourceForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $this->addElement('text', 'source_name', array(
+ 'label' => $this->translate('Import source name'),
+ 'description' => $this->translate(
+ 'A short name identifying this import source. Use something meaningful,'
+ . ' like "Hosts from Puppet", "Users from Active Directory" or similar'
+ ),
+ 'required' => true,
+ ));
+
+ $this->addElement('select', 'provider_class', array(
+ 'label' => $this->translate('Source Type'),
+ 'required' => true,
+ 'multiOptions' => $this->optionalEnum($this->enumSourceTypes()),
+ 'description' => $this->translate(
+ 'These are different data providers fetching data from various sources.'
+ . ' You didn\'t find what you\'re looking for? Import sources are implemented'
+ . ' as a hook in Director, so you might find (or write your own) Icinga Web 2'
+ . ' module fetching data from wherever you want'
+ ),
+ 'class' => 'autosubmit'
+ ));
+
+ $this->addSettings();
+ $this->setButtons();
+ }
+
+ public function getSentOrObjectSetting($name, $default = null)
+ {
+ if ($this->hasObject()) {
+ $value = $this->getSentValue($name);
+ if ($value === null) {
+ /** @var ImportSource $object */
+ $object = $this->getObject();
+
+ return $object->getSetting($name, $default);
+ } else {
+ return $value;
+ }
+ } else {
+ return $this->getSentValue($name, $default);
+ }
+ }
+
+ protected function addSettings()
+ {
+ if (! ($class = $this->getProviderClass())) {
+ return;
+ }
+
+ $defaultKeyCol = $this->getDefaultKeyColumnName();
+
+ $this->addElement('text', 'key_column', array(
+ 'label' => $this->translate('Key column name'),
+ 'description' => $this->translate(
+ 'This must be a column containing unique values like hostnames. Unless otherwise'
+ . ' specified this will then be used as the object_name for the syncronized'
+ . ' Icinga object. Especially when getting started with director please make'
+ . ' sure to strictly follow this rule. Duplicate values for this column on different'
+ . ' rows will trigger a failure, your import run will not succeed'
+ ),
+ 'placeholder' => $defaultKeyCol,
+ 'required' => $defaultKeyCol === null,
+ ));
+
+ if (array_key_exists($class, $this->enumSourceTypes())) {
+ $class::addSettingsFormFields($this);
+ foreach ($this->object()->getSettings() as $key => $val) {
+ if ($el = $this->getElement($key)) {
+ $el->setValue($val);
+ }
+ }
+ }
+ }
+
+ protected function getDefaultKeyColumnName()
+ {
+ if (! ($class = $this->getProviderClass())) {
+ return null;
+ }
+
+ if (! class_exists($class)) {
+ return null;
+ }
+
+ return $class::getDefaultKeyColumnName();
+ }
+
+ protected function getProviderClass()
+ {
+ if ($this->hasBeenSent()) {
+ $class = $this->getRequest()->getPost('provider_class');
+ } else {
+ if (! ($class = $this->object()->get('provider_class'))) {
+ return null;
+ }
+ }
+
+ return $class;
+ }
+
+ public function onSuccess()
+ {
+ if (! $this->getValue('key_column')) {
+ if ($default = $this->getDefaultKeyColumnName()) {
+ $this->setElementValue('key_column', $default);
+ $this->object()->set('key_column', $default);
+ }
+ }
+
+ parent::onSuccess();
+ }
+
+ protected function enumSourceTypes()
+ {
+ /** @var ImportSourceHook[] $hooks */
+ $hooks = Hook::all('Director\\ImportSource');
+
+ $enum = array();
+ foreach ($hooks as $hook) {
+ $enum[get_class($hook)] = $hook->getName();
+ }
+ asort($enum);
+
+ return $enum;
+ }
+}
diff --git a/application/forms/KickstartForm.php b/application/forms/KickstartForm.php
new file mode 100644
index 0000000..3c906bc
--- /dev/null
+++ b/application/forms/KickstartForm.php
@@ -0,0 +1,405 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Data\ResourceFactory;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Db\Migrations;
+use Icinga\Module\Director\Objects\IcingaEndpoint;
+use Icinga\Module\Director\KickstartHelper;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class KickstartForm extends QuickForm
+{
+ private $config;
+
+ private $storeConfigLabel;
+
+ private $createDbLabel;
+
+ private $migrateDbLabel;
+
+ /** @var IcingaEndpoint */
+ private $endpoint;
+
+ public function setup()
+ {
+ $this->storeConfigLabel = $this->translate('Store configuration');
+ $this->createDbLabel = $this->translate('Create database schema');
+ $this->migrateDbLabel = $this->translate('Apply schema migrations');
+
+ $this->addResourceConfigElements();
+ $this->addResourceDisplayGroup();
+
+ if (!$this->config()->get('db', 'resource')
+ || ($this->config()->get('db', 'resource') !== $this->getResourceName())) {
+ return;
+ }
+
+ if (!$this->migrations()->hasSchema()) {
+ $this->addHtmlHint($this->translate(
+ 'No database schema has been created yet'
+ ), array('name' => 'HINT_schema'));
+
+ $this->addResourceDisplayGroup();
+ $this->setSubmitLabel($this->createDbLabel);
+ return;
+ }
+
+ if ($this->migrations()->hasPendingMigrations()) {
+ $this->addHtmlHint($this->translate(
+ 'There are pending database migrations'
+ ), array('name' => 'HINT_schema'));
+
+ $this->addResourceDisplayGroup();
+ $this->setSubmitLabel($this->migrateDbLabel);
+ return;
+ }
+
+ if (! $this->endpoint && $this->getDb()->hasDeploymentEndpoint()) {
+ $hint = sprintf($this->translate(
+ 'Your database looks good, you are ready to %s'
+ ), $this->getView()->qlink(
+ 'start working with the Icinga Director',
+ 'director',
+ null,
+ array('data-base-target' => '_main')
+ ));
+
+ $this->addHtmlHint($hint, array('name' => 'HINT_ready'));
+ $this->getDisplayGroup('config')->addElements(
+ array($this->getElement('HINT_ready'))
+ );
+
+ return;
+ }
+
+ $this->addResourceDisplayGroup();
+
+ $this->addHtmlHint(
+ $this->translate(
+ 'Your installation of Icinga Director has not yet been prepared for'
+ . ' deployments. This kickstart wizard will assist you with setting'
+ . ' up the connection to your Icinga 2 server.'
+ ),
+ array('name' => 'HINT_kickstart')
+ // http://docs.icinga.com/icinga2/latest/doc/module/icinga2/chapter/object-types#objecttype-apilistener
+ );
+
+ $this->addElement('text', 'endpoint', array(
+ 'label' => $this->translate('Endpoint Name'),
+ 'description' => $this->translate(
+ 'This is the name of the Endpoint object (and certificate name) you'
+ . ' created for your ApiListener object. In case you are unsure what'
+ . ' this means please make sure to read the documentation first'
+ ),
+ 'required' => true,
+ ));
+
+ $this->addElement('text', 'host', array(
+ 'label' => $this->translate('Icinga Host'),
+ 'description' => $this->translate(
+ 'IP address / hostname of your Icinga node. Please note that this'
+ . ' information will only be used for the very first connection to'
+ . ' your Icinga instance. The Director then relies on a correctly'
+ . ' configured Endpoint object. Correctly configures means that either'
+ . ' it\'s name is resolvable or that it\'s host property contains'
+ . ' either an IP address or a resolvable host name. Your Director must'
+ . ' be able to reach this endpoint'
+ ),
+ 'required' => false,
+ ));
+
+ $this->addElement('text', 'port', array(
+ 'label' => $this->translate('Port'),
+ 'value' => '5665',
+ 'description' => $this->translate(
+ 'The port you are going to use. The default port 5665 will be used'
+ . ' if none is set'
+ ),
+ 'required' => false,
+ ));
+
+ $this->addElement('text', 'username', array(
+ 'label' => $this->translate('API user'),
+ 'description' => $this->translate(
+ 'Your Icinga 2 API username'
+ ),
+ 'required' => true,
+ ));
+
+ $this->addElement('password', 'password', array(
+ 'label' => $this->translate('Password'),
+ 'description' => $this->translate(
+ 'The corresponding password'
+ ),
+ 'required' => true,
+ ));
+
+ if ($ep = $this->endpoint) {
+ $user = $ep->getApiUser();
+ $this->setDefaults(array(
+ 'endpoint' => $ep->get('object_name'),
+ 'host' => $ep->get('host'),
+ 'port' => $ep->get('port'),
+ 'username' => $user->get('object_name'),
+ 'password' => $user->get('password'),
+ ));
+
+ if (! empty($user->password)) {
+ $this->getElement('password')->setAttrib(
+ 'placeholder',
+ '(use stored password)'
+ )->setRequired(false);
+ }
+ }
+
+ $this->addKickstartDisplayGroup();
+ $this->setSubmitLabel($this->translate('Run import'));
+ }
+
+ protected function onSetup()
+ {
+ if ($this->hasBeenSubmitted()) {
+ // Do not hinder the form from being stored
+ return;
+ }
+ if ($resourceName = $this->getResourceName()) {
+ $resourceConfig = ResourceFactory::getResourceConfig($resourceName);
+ if (! isset($resourceConfig->charset)
+ || $resourceConfig->charset !== 'utf8'
+ ) {
+ $this->getElement('resource')
+ ->addError('Please change the encoding for the director database to utf8');
+ }
+
+ $resource = $this->getResource();
+ $db = $resource->getDbAdapter();
+
+ try {
+ $db->fetchOne('SELECT 1');
+ } catch (Exception $e) {
+ $this->getElement('resource')
+ ->addError('Could not connect to database: ' . $e->getMessage());
+
+ $hint = $this->translate(
+ 'Please make sure that your database exists and your user has'
+ . ' been granted enough permissions'
+ );
+
+ $this->addHtmlHint($hint, array('name' => 'HINT_db_perms'));
+ }
+ }
+ }
+
+ protected function addResourceConfigElements()
+ {
+ $config = $this->config();
+ $resources = $this->enumResources();
+
+ if (!$this->getResourceName()) {
+ $this->addHtmlHint($this->translate(
+ 'No database resource has been configured yet. Please choose a'
+ . ' resource to complete your config'
+ ), array('name' => 'HINT_no_resource'));
+ }
+
+ $this->addElement('select', 'resource', array(
+ 'required' => true,
+ 'label' => $this->translate('DB Resource'),
+ 'multiOptions' => $this->optionalEnum($resources),
+ 'class' => 'autosubmit',
+ 'value' => $config->get('db', 'resource')
+ ));
+
+ if (empty($resources)) {
+ $this->getElement('resource')->addError(
+ $this->translate('This has to be a MySQL or PostgreSQL database')
+ );
+
+ $hint = $this->translate('Please click %s to create new DB resources');
+ $link = $this->getView()->qlink(
+ $this->translate('here'),
+ 'config/resource',
+ null,
+ array('data-base-target' => '_main')
+ );
+ $this->addHtmlHint(sprintf($hint, $link));
+ }
+
+ $this->setSubmitLabel($this->storeConfigLabel);
+ }
+
+ protected function addResourceDisplayGroup()
+ {
+ $elements = array(
+ 'HINT_no_resource',
+ 'resource',
+ 'HINT_ready',
+ 'HINT_schema',
+ 'HINT_db_perms',
+ 'HINT_config_store'
+ );
+
+ $this->addDisplayGroup($elements, 'config', array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl')),
+ 'Fieldset',
+ ),
+ 'order' => 40,
+ 'legend' => $this->translate('Database backend')
+ ));
+ }
+
+ protected function addKickstartDisplayGroup()
+ {
+ $elements = array(
+ 'HINT_kickstart', 'endpoint', 'host', 'port', 'username', 'password'
+ );
+
+ $this->addDisplayGroup($elements, 'wizard', array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'dl')),
+ 'Fieldset',
+ ),
+ 'order' => 60,
+ 'legend' => $this->translate('Kickstart Wizard')
+ ));
+ }
+
+ protected function storeResourceConfig()
+ {
+ $config = $this->config();
+ $value = $this->getValue('resource');
+
+ $config->setSection('db', array('resource' => $value));
+
+ try {
+ $config->saveIni();
+ $this->setSuccessMessage($this->translate('Configuration has been stored'));
+
+ return true;
+ } catch (Exception $e) {
+ $this->getElement('resource')->addError(
+ sprintf(
+ $this->translate(
+ 'Unable to store the configuration to "%s". Please check'
+ . ' file permissions or manually store the content shown below'
+ ),
+ $config->getConfigFile()
+ )
+ );
+ $this->addHtmlHint(
+ '<pre>' . $config . '</pre>',
+ array('name' => 'HINT_config_store')
+ );
+
+ $this->getDisplayGroup('config')->addElements(
+ array($this->getElement('HINT_config_store'))
+ );
+ $this->removeElement('HINT_ready');
+
+ return false;
+ }
+ }
+
+ public function setEndpoint(IcingaEndpoint $endpoint)
+ {
+ $this->endpoint = $endpoint;
+ return $this;
+ }
+
+ public function onSuccess()
+ {
+ try {
+ if ($this->getSubmitLabel() === $this->storeConfigLabel) {
+ if ($this->storeResourceConfig()) {
+ parent::onSuccess();
+ } else {
+ return;
+ }
+ }
+
+ if ($this->getSubmitLabel() === $this->createDbLabel
+ || $this->getSubmitLabel() === $this->migrateDbLabel) {
+ $this->migrations()->applyPendingMigrations();
+ parent::onSuccess();
+ }
+
+ $values = $this->getValues();
+ if ($this->endpoint && empty($values['password'])) {
+ $values['password'] = $this->endpoint->getApiUser()->password;
+ }
+
+ $kickstart = new KickstartHelper($this->getDb());
+ unset($values['resource']);
+ $kickstart->setConfig($values)->run();
+ parent::onSuccess();
+ } catch (Exception $e) {
+ $this->addError($e->getMessage());
+ }
+ }
+
+ protected function getResourceName()
+ {
+ if ($this->hasBeenSent()) {
+ $resource = $this->getSentValue('resource');
+ $resources = $this->enumResources();
+ if (in_array($resource, $resources)) {
+ return $resource;
+ } else {
+ return null;
+ }
+ } else {
+ return $this->config()->get('db', 'resource');
+ }
+ }
+
+ protected function getDb()
+ {
+ return Db::fromResourceName($this->getResourceName());
+ }
+
+ protected function getResource()
+ {
+ return ResourceFactory::create($this->getResourceName());
+ }
+
+ protected function migrations()
+ {
+ return new Migrations($this->getDb());
+ }
+
+ public function setModuleConfig(Config $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ protected function config()
+ {
+ if ($this->config === null) {
+ $this->config = Config::module('director');
+ }
+
+ return $this->config;
+ }
+
+ protected function enumResources()
+ {
+ $resources = array();
+ $allowed = array('mysql', 'pgsql');
+
+ foreach (ResourceFactory::getResourceConfigs() as $name => $resource) {
+ if ($resource->get('type') === 'db' && in_array($resource->get('db'), $allowed)) {
+ $resources[$name] = $name;
+ }
+ }
+
+ return $resources;
+ }
+}
diff --git a/application/forms/RestoreObjectForm.php b/application/forms/RestoreObjectForm.php
new file mode 100644
index 0000000..a8f9e7f
--- /dev/null
+++ b/application/forms/RestoreObjectForm.php
@@ -0,0 +1,58 @@
+<?php
+
+// TODO: Check whether this can be removed
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class RestoreObjectForm extends QuickForm
+{
+ /** @var Db */
+ protected $db;
+
+ /** @var IcingaObject */
+ protected $object;
+
+ public function setup()
+ {
+ $this->submitLabel = $this->translate('Restore former object');
+ }
+
+ protected function addSubmitButtonIfSet()
+ {
+ $res = parent::addSubmitButtonIfSet();
+ $this->getDisplayGroup('buttons')->setDecorators(array('FormElements'));
+ return $res;
+ }
+
+ public function onSuccess()
+ {
+ $object = $this->object;
+ $name = $object->getObjectName();
+ $db = $this->db;
+ $msg = $this->translate('Object has been restored');
+
+ // TODO: service -> multi-key
+ if ($object::exists($name, $db)) {
+ $object::load($name, $db)->replaceWith($object)->store();
+ } else {
+ $object->store($db);
+ }
+
+ $this->redirectOnSuccess($msg);
+ }
+
+ public function setDb($db)
+ {
+ $this->db = $db;
+ return $this;
+ }
+
+ public function setObject(IcingaObject $object)
+ {
+ $this->object = $object;
+ return $this;
+ }
+}
diff --git a/application/forms/SettingsForm.php b/application/forms/SettingsForm.php
new file mode 100644
index 0000000..f1e401b
--- /dev/null
+++ b/application/forms/SettingsForm.php
@@ -0,0 +1,181 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Exception;
+use Icinga\Module\Director\Settings;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class SettingsForm extends QuickForm
+{
+ /** @var Settings */
+ protected $settings;
+
+ public function setup()
+ {
+ $settings = $this->settings;
+
+ $this->addHtmlHint(
+ $this->translate(
+ 'Please only change those settings in case you are really sure'
+ . ' that you are required to do so. Usually the defaults chosen'
+ . ' by the Icinga Director should make a good fit for your'
+ . ' environment.'
+ )
+ );
+
+ $globalZones = array(
+ null => sprintf(
+ $this->translate('%s (default)'),
+ $settings->getDefaultValue('default_global_zone')
+ )
+ );
+
+ $this->addElement('select', 'default_global_zone', array(
+ 'label' => $this->translate('Default global zone'),
+ 'multiOptions' => $globalZones,
+ 'description' => $this->translate(
+ 'Icinga Director decides to deploy objects like CheckCommands'
+ . ' to a global zone. This defaults to "director-global" but'
+ . ' might be adjusted to a custom Zone name'
+ ),
+ 'value' => $settings->getStoredValue('default_global_zone')
+ ));
+
+ $this->addElement('select', 'disable_all_jobs', array(
+ 'label' => $this->translate('Disable all Jobs'),
+ 'multiOptions' => $this->eventuallyConfiguredEnum(
+ 'disable_all_jobs',
+ array(
+ 'n' => $this->translate('No'),
+ 'y' => $this->translate('Yes'),
+ )
+ ),
+ 'description' => $this->translate(
+ 'Whether all configured Jobs should be disabled'
+ ),
+ 'value' => $settings->getStoredValue('disable_all_jobs')
+ ));
+
+ $this->addElement('select', 'enable_audit_log', array(
+ 'label' => $this->translate('Enable audit log'),
+ 'multiOptions' => $this->eventuallyConfiguredEnum(
+ 'enable_audit_log',
+ array(
+ 'n' => $this->translate('No'),
+ 'y' => $this->translate('Yes'),
+ )
+ ),
+ 'description' => $this->translate(
+ 'All changes are tracked in the Director database. In addition'
+ . ' you might also want to send an audit log through the Icinga'
+ . " Web 2 logging mechanism. That way all changes would be"
+ . ' written to either Syslog or the configured log file. When'
+ . ' enabling this please make sure that you configured Icinga'
+ . ' Web 2 to log at least at "informational" level.'
+ ),
+ 'value' => $settings->getStoredValue('enable_audit_log')
+ ));
+
+ $this->addElement('select', 'config_format', array(
+ 'label' => $this->translate('Configuration format'),
+ 'multiOptions' => $this->eventuallyConfiguredEnum(
+ 'config_format',
+ array(
+ 'v2' => $this->translate('Icinga v2.x'),
+ 'v1' => $this->translate('Icinga v1.x'),
+ )
+ ),
+ 'description' => $this->translate(
+ 'Default configuration format. Please note that v1.x is for'
+ . ' special transitional projects only and completely'
+ . ' unsupported. There are no plans to make Director a first-'
+ . 'class configuration backends for Icinga 1.x'
+ ),
+ 'class' => 'autosubmit',
+ 'value' => $settings->getStoredValue('config_format')
+ ));
+
+ $this->setSubmitLabel($this->translate('Store'));
+
+ if ($this->hasBeenSent()) {
+ if ($this->getSentValue('config_format') !== 'v1') {
+ return;
+ }
+ } elseif ($settings->getStoredValue('config_format') !== 'v1') {
+ return;
+ }
+
+ $this->addElement('select', 'deployment_mode_v1', array(
+ 'label' => $this->translate('Deployment mode'),
+ 'multiOptions' => $this->eventuallyConfiguredEnum(
+ 'deployment_mode_v1',
+ array(
+ 'active-passive' => $this->translate('Active-Passive'),
+ 'masterless' => $this->translate('Master-less'),
+ )
+ ),
+ 'description' => $this->translate(
+ 'Deployment mode for Icinga 1 configuration'
+ ),
+ 'value' => $settings->getStoredValue('deployment_mode_v1')
+ ));
+
+ $this->addElement('text', 'deployment_path_v1', array(
+ 'label' => $this->translate('Deployment Path'),
+ 'description' => $this->translate(
+ 'Local directory to deploy Icinga 1.x configuration.'
+ . ' Must be writable by icingaweb2.'
+ . ' (e.g. /etc/icinga/director)'
+ ),
+ 'value' => $settings->getStoredValue('deployment_path_v1')
+ ));
+
+ $this->addElement('text', 'activation_script_v1', array(
+ 'label' => $this->translate('Activation Tool'),
+ 'description' => $this->translate(
+ 'Script or tool to call when activating a new configuration stage.'
+ . ' (e.g. sudo /usr/local/bin/icinga-director-activate)'
+ . ' (name of the stage will be the argument for the script)'
+ ),
+ 'value' => $settings->getStoredValue('activation_script_v1')
+ ));
+ }
+
+ protected function eventuallyConfiguredEnum($name, $enum)
+ {
+ return array(
+ null => sprintf(
+ $this->translate('%s (default)'),
+ $enum[$this->settings->getDefaultValue($name)]
+ )
+ ) + $enum;
+ }
+
+ public function setSettings(Settings $settings)
+ {
+ $this->settings = $settings;
+ return $this;
+ }
+
+ public function onSuccess()
+ {
+ try {
+ foreach ($this->getValues() as $key => $value) {
+ if ($value === '') {
+ $value = null;
+ }
+
+ $this->settings->set($key, $value);
+ }
+
+ $this->setSuccessMessage($this->translate(
+ 'Settings have been stored'
+ ));
+
+ parent::onSuccess();
+ } catch (Exception $e) {
+ $this->addError($e->getMessage());
+ }
+ }
+}
diff --git a/application/forms/SyncCheckForm.php b/application/forms/SyncCheckForm.php
new file mode 100644
index 0000000..c00104c
--- /dev/null
+++ b/application/forms/SyncCheckForm.php
@@ -0,0 +1,66 @@
+<?php
+
+// TODO: Check whether this can be removed
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\SyncRule;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class SyncCheckForm extends QuickForm
+{
+ /** @var SyncRule */
+ protected $rule;
+
+ public function setSyncRule(SyncRule $rule)
+ {
+ $this->rule = $rule;
+ return $this;
+ }
+
+ public function setup()
+ {
+ $this->submitLabel = false;
+ $this->addElement('submit', 'submit', array(
+ 'label' => $this->translate('Check for changes'),
+ 'decorators' => array('ViewHelper')
+ ));
+ }
+
+ public function onSuccess()
+ {
+ if ($this->rule->checkForChanges()) {
+ $this->notifySuccess(
+ $this->translate(('This Sync Rule would apply new changes'))
+ );
+ $sum = array('create' => 0, 'modify' => 0, 'delete' => 0);
+
+ // TODO: Preview them? Like "hosta, hostb and 4 more would be...
+ foreach ($this->rule->getExpectedModifications() as $object) {
+ if ($object->shouldBeRemoved()) {
+ $sum['delete']++;
+ } elseif (! $object->hasBeenLoadedFromDb()) {
+ $sum['create']++;
+ } elseif ($object->hasBeenModified()) {
+ $sum['modify']++;
+ }
+ }
+
+ /**
+ if ($sum['modify'] === 1) {
+ $html .= $this->translate('One object would be modified'
+ } elseif ($sum['modify'] > 1) {
+ }
+ */
+ $html = '<pre>' . print_r($sum, 1) . '</pre>';
+
+ $this->addHtml($html);
+ } elseif ($this->rule->get('sync_state') === 'in-sync') {
+ $this->setSuccessMessage(
+ $this->translate('Nothing would change, this rule is still in sync')
+ );
+ parent::onSuccess();
+ } else {
+ $this->addError($this->translate('Checking this sync rule failed'));
+ }
+ }
+}
diff --git a/application/forms/SyncPropertyForm.php b/application/forms/SyncPropertyForm.php
new file mode 100644
index 0000000..fa798c4
--- /dev/null
+++ b/application/forms/SyncPropertyForm.php
@@ -0,0 +1,401 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Exception;
+use Icinga\Module\Director\Hook\ImportSourceHook;
+use Icinga\Module\Director\Objects\SyncRule;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class SyncPropertyForm extends DirectorObjectForm
+{
+ /**
+ * @var SyncRule
+ */
+ private $rule;
+
+ /** @var ImportSource */
+ private $importSource;
+
+ /** @var ImportSourceHook */
+ private $importSourceHook;
+
+ private $dummyObject;
+
+ const EXPRESSION = '__EXPRESSION__';
+
+ public function setup()
+ {
+ $this->addHidden('rule_id', $this->rule->get('id'));
+
+ $this->addElement('select', 'source_id', array(
+ 'label' => $this->translate('Source Name'),
+ 'multiOptions' => $this->enumImportSource(),
+ 'required' => true,
+ 'class' => 'autosubmit',
+ ));
+ if (! $this->hasObject() && ! $this->getSentValue('source_id')) {
+ return;
+ }
+
+ $this->addElement('select', 'destination_field', array(
+ 'label' => $this->translate('Destination Field'),
+ 'multiOptions' => $this->optionalEnum($this->listDestinationFields()),
+ 'required' => true,
+ 'class' => 'autosubmit',
+ ));
+
+ if ($this->getSentValue('destination_field')) {
+ $destination = $this->getSentValue('destination_field');
+ } elseif ($this->hasObject()) {
+ $destination = $this->getObject()->destination_field;
+ } else {
+ return;
+ }
+
+ $isCustomvar = substr($destination, 0, 5) === 'vars.';
+
+ if ($isCustomvar) {
+ $varname = substr($destination, 5);
+ $this->addElement('text', 'customvar', array(
+ 'label' => $this->translate('Custom variable'),
+ 'required' => true,
+ 'ignore' => true,
+ ));
+
+ if ($varname !== '*') {
+ $this->setElementValue('destination_field', 'vars.*');
+ $this->setElementValue('customvar', $varname);
+ if ($this->hasObject()) {
+ $this->getObject()->destination_field = 'vars.*';
+ }
+ }
+ }
+
+ $this->addSourceColumnElement($destination);
+
+ $this->addElement('YesNo', 'use_filter', array(
+ 'label' => $this->translate('Set based on filter'),
+ 'ignore' => true,
+ 'class' => 'autosubmit',
+ 'required' => true,
+ ));
+
+ if ($this->hasBeenSent()) {
+ $useFilter = $this->getSentValue('use_filter');
+ if ($useFilter === null) {
+ $this->setElementValue('use_filter', $useFilter = 'n');
+ }
+ } else {
+ $useFilter = strlen($this->getObject()->filter_expression) ? 'y' : 'n';
+ $this->setElementValue('use_filter', $useFilter);
+ }
+
+ if ($useFilter === 'y') {
+ $this->addElement('text', 'filter_expression', array(
+ 'label' => $this->translate('Filter Expression'),
+ 'description' => $this->translate(
+ 'This allows to filter for specific parts within the given source expression.'
+ . ' You are allowed to refer all imported columns. Examples: host=www* would'
+ . ' set this property only for rows imported with a host property starting'
+ . ' with "www". Complex example: host=www*&!(address=127.*|address6=::1)'
+ ),
+ 'required' => true,
+ // TODO: validate filter
+ ));
+ }
+
+ if ($isCustomvar || $destination === 'vars') {
+ $this->addElement('select', 'merge_policy', array(
+ 'label' => $this->translate('Merge Policy'),
+ 'description' => $this->translate(
+ 'Whether you want to merge or replace the destination field.'
+ . ' Makes no difference for strings'
+ ),
+ 'required' => true,
+ 'multiOptions' => $this->optionalEnum(array(
+ 'merge' => 'merge',
+ 'override' => 'replace'
+ ))
+ ));
+ } else {
+ $this->addHidden('merge_policy', 'override');
+ }
+
+ $this->setButtons();
+ }
+
+ protected function hasSubOption($options, $key)
+ {
+ foreach ($options as $mainKey => $sub) {
+ if (! is_array($sub)) {
+ // null -> please choose - or similar
+ continue;
+ }
+
+ if (array_key_exists($key, $sub)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function addSourceColumnElement($destination)
+ {
+ $error = false;
+
+ $srcTitle = $this->translate('Source columns');
+ try {
+ $columns[$srcTitle] = $this->listSourceColumns();
+ natsort($columns[$srcTitle]);
+ } catch (Exception $e) {
+ $srcTitle .= sprintf(' (%s)', $this->translate('failed to fetch'));
+ $columns[$srcTitle] = array();
+ $error = sprintf(
+ $this->translate('Unable to fetch data: %s'),
+ $e->getMessage()
+ );
+ }
+
+ if ($destination === 'import') {
+ $funcTemplates = 'enum' . ucfirst($this->rule->get('object_type')) . 'Templates';
+ if (method_exists($this->db, $funcTemplates)) {
+ $templates = $this->db->$funcTemplates();
+ if (! empty($templates)) {
+ $templates = array_combine($templates, $templates);
+ }
+
+ $importTitle = $this->translate('Existing templates');
+ $columns[$importTitle] = $templates;
+ natsort($columns[$importTitle]);
+ }
+ }
+
+ $xpTitle = $this->translate('Expert mode');
+ $columns[$xpTitle][self::EXPRESSION] = $this->translate('Custom expression');
+
+ $this->addElement('select', 'source_column', array(
+ 'label' => $this->translate('Source Column'),
+ 'multiOptions' => $this->optionalEnum($columns),
+ 'required' => true,
+ 'ignore' => true,
+ 'class' => 'autosubmit',
+ ));
+
+ if ($error) {
+ $this->getElement('source_column')->addError($error);
+ }
+
+ $showExpression = false;
+
+ if ($this->hasBeenSent()) {
+ $sentValue = $this->getSentValue('source_column');
+ if ($sentValue === self::EXPRESSION) {
+ $showExpression = true;
+ }
+ } elseif ($this->hasObject()) {
+ $objectValue = $this->getObject()->source_expression;
+ if ($this->hasSubOption($columns, $objectValue)) {
+ $this->setElementValue('source_column', $objectValue);
+ } else {
+ $this->setElementValue('source_column', self::EXPRESSION);
+ $showExpression = true;
+ }
+ }
+
+ if ($showExpression) {
+ $this->addElement('text', 'source_expression', array(
+ 'label' => $this->translate('Source Expression'),
+ 'description' => $this->translate(
+ 'A custom string. Might contain source columns, please use placeholders'
+ . ' of the form ${columnName} in such case'
+ ),
+ 'required' => true,
+ ));
+ }
+
+
+ return $this;
+ }
+
+ protected function enumImportSource()
+ {
+ $sources = $this->db->enumImportSource();
+ $usedIds = $this->rule->listInvolvedSourceIds();
+ if (empty($usedIds)) {
+ return $this->optionalEnum($sources);
+ }
+ $usedSources = array();
+ foreach ($usedIds as $id) {
+ $usedSources[$id] = $sources[$id];
+ unset($sources[$id]);
+ }
+
+ if (empty($sources)) {
+ return $this->optionalEnum($usedSources);
+ }
+
+ return $this->optionalEnum(
+ array(
+ $this->translate('Used sources') => $usedSources,
+ $this->translate('Other sources') => $sources
+ )
+ );
+ }
+
+ protected function listSourceColumns()
+ {
+ $columns = array();
+ $source = $this->getImportSource();
+ $hook = $this->getImportSourceHook();
+ foreach ($hook->listColumns() as $col) {
+ $columns['${' . $col . '}'] = $col;
+ }
+
+ foreach ($source->listModifierTargetProperties() as $property) {
+ $columns['${' . $property . '}'] = $property;
+ }
+
+ return $columns;
+ }
+
+ protected function listDestinationFields()
+ {
+ $props = array();
+ $special = array();
+ $dummy = $this->dummyObject();
+
+ if ($dummy instanceof IcingaObject) {
+ if ($dummy->supportsCustomVars()) {
+ $special['vars.*'] = $this->translate('Custom variable (vars.)');
+ $special['vars'] = $this->translate('All custom variables (vars)');
+ }
+ if ($dummy->supportsImports()) {
+ $special['import'] = $this->translate('Inheritance (import)');
+ }
+ if ($dummy->supportsArguments()) {
+ $special['arguments'] = $this->translate('Arguments');
+ }
+ if ($dummy->supportsGroups()) {
+ $special['groups'] = $this->translate('Group membership');
+ }
+ if ($dummy->supportsRanges()) {
+ $special['ranges'] = $this->translate('Time ranges');
+ }
+ }
+
+ foreach ($dummy->listProperties() as $prop) {
+ if ($prop === 'id') {
+ continue;
+ }
+
+ // TODO: allow those fields, but munge them (store ids)
+ //if (preg_match('~_id$~', $prop)) continue;
+ if (substr($prop, -3) === '_id') {
+ $prop = substr($prop, 0, -3);
+ if (! $dummy instanceof IcingaObject || ! $dummy->hasRelation($prop)) {
+ continue;
+ }
+ }
+
+ $props[$prop] = $prop;
+ }
+
+ foreach ($dummy->listMultiRelations() as $prop) {
+ $props[$prop] = sprintf('%s (%s)', $prop, $this->translate('a list'));
+ }
+
+ ksort($props);
+
+ return array(
+ $this->translate('Special properties') => $special,
+ $this->translate('Object properties') => $props
+ );
+ }
+
+ /**
+ * @return ImportSource
+ */
+ protected function getImportSource()
+ {
+ if ($this->importSource === null) {
+ if ($this->hasObject()) {
+ $this->importSource = ImportSource::load($this->object->get('source_id'), $this->db);
+ } else {
+ $this->importSource = ImportSource::load($this->getSentValue('source_id'), $this->db);
+ }
+ }
+
+ return $this->importSource;
+ }
+
+ /**
+ * @return ImportSourceHook
+ */
+ protected function getImportSourceHook()
+ {
+ if ($this->importSourceHook === null) {
+ $this->importSourceHook = ImportSourceHook::loadByName(
+ $this->getImportSource()->get('source_name'),
+ $this->db
+ );
+ }
+
+ return $this->importSourceHook;
+ }
+
+ public function onSuccess()
+ {
+ $object = $this->getObject();
+ $object->set('rule_id', $this->rule->get('id')); // ?!
+
+ if ($this->getValue('use_filter') === 'n') {
+ $object->set('filter_expression', null);
+ }
+
+ $sourceColumn = $this->getValue('source_column');
+ unset($this->source_column);
+ $this->removeElement('source_column');
+
+ if ($sourceColumn !== self::EXPRESSION) {
+ $object->source_expression = $sourceColumn;
+ }
+
+ $destination = $this->getValue('destination_field');
+ if ($destination === 'vars.*') {
+ $destination = $this->getValue('customvar');
+ $object->destination_field = 'vars.' . $destination;
+ }
+
+ if ($object->hasBeenModified()) {
+ if (! $object->hasBeenLoadedFromDb()) {
+ $object->priority = $this->rule->getPriorityForNextProperty();
+ }
+ }
+
+
+ return parent::onSuccess();
+ }
+
+ protected function dummyObject()
+ {
+ if ($this->dummyObject === null) {
+ $this->dummyObject = IcingaObject::createByType(
+ $this->rule->get('object_type'),
+ array(),
+ $this->db
+ );
+ }
+
+ return $this->dummyObject;
+ }
+
+ public function setRule(SyncRule $rule)
+ {
+ $this->rule = $rule;
+ return $this;
+ }
+}
diff --git a/application/forms/SyncRuleForm.php b/application/forms/SyncRuleForm.php
new file mode 100644
index 0000000..2c478fa
--- /dev/null
+++ b/application/forms/SyncRuleForm.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class SyncRuleForm extends DirectorObjectForm
+{
+ public function setup()
+ {
+ $availableTypes = array(
+ 'host' => $this->translate('Host'),
+ 'hostgroup' => $this->translate('Host group'),
+ 'service' => $this->translate('Service'),
+ 'servicegroup' => $this->translate('Service group'),
+ 'serviceSet' => $this->translate('Service Set'),
+ 'user' => $this->translate('User'),
+ 'usergroup' => $this->translate('User group'),
+ 'datalistEntry' => $this->translate('Datalist entry'),
+ 'command' => $this->translate('Command'),
+ 'timePeriod' => $this->translate('Time period'),
+ 'endpoint' => $this->translate('Endpoint'),
+ 'zone' => $this->translate('Zone'),
+ );
+
+ $this->addElement('text', 'rule_name', array(
+ 'label' => $this->translate('Rule name'),
+ 'description' => $this->translate('Please provide a rule name'),
+ 'required' => true,
+ ));
+
+ $this->addElement('select', 'object_type', array(
+ 'label' => $this->translate('Object Type'),
+ 'description' => $this->translate('Choose an object type'),
+ 'required' => true,
+ 'multiOptions' => $this->optionalEnum($availableTypes)
+ ));
+
+ $this->addElement('select', 'update_policy', array(
+ 'label' => $this->translate('Update Policy'),
+ 'description' => $this->translate(
+ 'Define what should happen when an object with a matching key'
+ . " already exists. You could merge its properties (import source"
+ . ' wins), replace it completely with the imported object or ignore'
+ . ' it (helpful for one-time imports)'
+ ),
+ 'required' => true,
+ 'multiOptions' => $this->optionalEnum(array(
+ 'merge' => $this->translate('Merge'),
+ 'override' => $this->translate('Replace'),
+ 'ignore' => $this->translate('Ignore'),
+ ))
+ ));
+
+ $this->addElement('select', 'purge_existing', array(
+ 'label' => $this->translate('Purge'),
+ 'description' => $this->translate(
+ 'Whether to purge existing objects. This means that objects of'
+ . ' the same type will be removed from Director in case they no'
+ . ' longer exist at your import source.'
+ ),
+ 'required' => true,
+ 'multiOptions' => $this->optionalEnum(array(
+ 'y' => $this->translate('Yes'),
+ 'n' => $this->translate('No')
+ ))
+ ));
+
+ $this->addElement('text', 'filter_expression', array(
+ 'label' => $this->translate('Filter Expression'),
+ 'description' => sprintf(
+ $this->translate(
+ 'Sync only part of your imported objects with this rule. Icinga Web 2'
+ . ' filter syntax is allowed, so this could look as follows: %s'
+ ),
+ '(host=a|host=b)&!ip=127.*'
+ ),
+ ));
+
+ $this->setButtons();
+ }
+}
diff --git a/application/forms/SyncRunForm.php b/application/forms/SyncRunForm.php
new file mode 100644
index 0000000..fd1b375
--- /dev/null
+++ b/application/forms/SyncRunForm.php
@@ -0,0 +1,48 @@
+<?php
+
+// TODO: Check whether this can be removed
+namespace Icinga\Module\Director\Forms;
+
+use Icinga\Module\Director\Objects\SyncRule;
+use Icinga\Module\Director\Web\Form\QuickForm;
+
+class SyncRunForm extends QuickForm
+{
+ /** @var SyncRule */
+ protected $rule;
+
+ public function setSyncRule(SyncRule $rule)
+ {
+ $this->rule = $rule;
+ return $this;
+ }
+
+ public function setup()
+ {
+ $this->submitLabel = false;
+ $this->addElement('submit', 'submit', array(
+ 'label' => $this->translate('Trigger this Sync'),
+ 'decorators' => array('ViewHelper')
+ ));
+ }
+
+ public function onSuccess()
+ {
+ $rule = $this->rule;
+ $changed = $rule->applyChanges();
+
+ if ($changed) {
+ $this->setSuccessMessage(
+ $this->translate(('Source has successfully been synchronized'))
+ );
+ } elseif ($rule->get('sync_state') === 'in-sync') {
+ $this->setSuccessMessage(
+ $this->translate('Nothing changed, rule is in sync')
+ );
+ } else {
+ $this->addError($this->translate('Synchronization failed'));
+ }
+
+ parent::onSuccess();
+ }
+}
diff --git a/application/locale/de_DE/LC_MESSAGES/director.mo b/application/locale/de_DE/LC_MESSAGES/director.mo
new file mode 100644
index 0000000..8b07c69
--- /dev/null
+++ b/application/locale/de_DE/LC_MESSAGES/director.mo
Binary files differ
diff --git a/application/locale/de_DE/LC_MESSAGES/director.po b/application/locale/de_DE/LC_MESSAGES/director.po
new file mode 100644
index 0000000..9d2ed27
--- /dev/null
+++ b/application/locale/de_DE/LC_MESSAGES/director.po
@@ -0,0 +1,4468 @@
+# Icinga Web 2 - Head for multiple monitoring backends.
+# Copyright (C) 2017 Icinga Development Team
+# This file is distributed under the same license as Director Module.
+# Thomas Widhalm <widhalmt@widhalm.or.at>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Director Module (1.3.0)\n"
+"Report-Msgid-Bugs-To: dev@icinga.org\n"
+"POT-Creation-Date: 2017-01-16 17:27+0100\n"
+"PO-Revision-Date: 2017-01-16 17:28+0100\n"
+"Last-Translator: Thomas Gelf <thomas@gelf.net>\n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"Language-Team: \n"
+"X-Generator: Poedit 1.8.7.1\n"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:450
+#, php-format
+msgid " (inherited from \"%s\")"
+msgstr "(geerbt von \"%s\")"
+
+#: application/tables/IcingaServiceSetTable.php:43
+msgid "# Hosts"
+msgstr "# Hosts"
+
+#: application/tables/IcingaServiceSetTable.php:42
+msgid "# Services"
+msgstr "# Services"
+
+#: application/tables/DatafieldTable.php:40
+msgid "# Used"
+msgstr "# Benutzt"
+
+#: application/tables/DatafieldTable.php:41
+msgid "# Vars"
+msgstr "# Vars"
+
+#: library/Director/Dashboard/Dashlet/Dashlet.php:225
+#, php-format
+msgid "%d apply rules have been defined"
+msgstr "%d Apply-Regeln wurden definiert"
+
+#: application/views/scripts/deployment/index.phtml:25
+#, php-format
+msgid "%d files"
+msgstr "%d Dateien"
+
+#: application/views/scripts/config/files.phtml:32
+#, php-format
+msgid "%d files rendered in %0.2fs"
+msgstr "%d Dateien in %0.2fs erstellt "
+
+#: library/Director/Dashboard/Dashlet/Dashlet.php:268
+#, php-format
+msgid "%d have been externally defined and will not be deployed"
+msgstr "%d wurden extern definiert und werden nicht ausgebracht"
+
+#: application/views/scripts/inspect/type.phtml:8
+#, php-format
+msgid "%d objects found"
+msgstr "%d Objekte gefunden"
+
+#: library/Director/Dashboard/Dashlet/Dashlet.php:251
+#, php-format
+msgid "%d objects have been defined"
+msgstr "%d Objekte wurden definiert"
+
+#: application/forms/IcingaMultiEditForm.php:86
+#, php-format
+msgid "%d objects have been modified"
+msgstr "%d Objekte wurden verändert"
+
+#: application/views/scripts/deployment/index.phtml:34
+#, php-format
+msgid "%d objects, %d templates, %d apply rules"
+msgstr "%d Objekte, %d Vorlagen, %d Apply-Regeln"
+
+#: library/Director/Dashboard/Dashlet/Dashlet.php:260
+#, php-format
+msgid "%d of them are templates"
+msgstr "%d davon sind Vorlagen"
+
+#: library/Director/Dashboard/Dashlet/Dashlet.php:205
+#, php-format
+msgid "%d templates have been defined"
+msgstr "%d Vorlagen wurden definiert"
+
+#: application/controllers/ShowController.php:176
+#, php-format
+msgid "%s \"%s\" has been created"
+msgstr "%s \"%s\" wurde erstellt"
+
+#: application/controllers/ShowController.php:183
+#, php-format
+msgid "%s \"%s\" has been deleted"
+msgstr "%s \"%s\" wurde gelöscht"
+
+#: application/forms/IcingaImportObjectForm.php:36
+#, php-format
+msgid "%s \"%s\" has been imported\""
+msgstr "%s \"%s\" wurde importiert\""
+
+#: application/controllers/ShowController.php:190
+#, php-format
+msgid "%s \"%s\" has been modified"
+msgstr "%s \"%s\" wurde geändert"
+
+#: application/controllers/HostController.php:170
+#, php-format
+msgid "%s (Service set)"
+msgstr "%s (Service-Set)"
+
+#: application/forms/SettingsForm.php:29 application/forms/SettingsForm.php:149
+#, php-format
+msgid "%s (default)"
+msgstr "%s (default)"
+
+#: application/tables/IcingaHostAppliedServicesTable.php:68
+#, php-format
+msgid "%s (where %s)"
+msgstr "%s (wo %s)"
+
+#: library/Director/Web/Navigation/Renderer/ConfigHealthItemRenderer.php:99
+#, php-format
+msgid "%s config changes happend since the last deployed configuration"
+msgstr ""
+"%s Konfigurationsänderungen seit der letzten ausgebrachten Konfiguration"
+
+#: application/views/scripts/syncrule/runSummary.phtml:10
+#, php-format
+msgid "%s objects have been modified"
+msgstr "%s Objekte wurden verändert"
+
+#: application/controllers/HostController.php:277
+#, php-format
+msgid "%s on %s (from set: %s)"
+msgstr "%s auf %s (aus dem Set \"%s\")"
+
+#: library/Director/Dashboard/Dashlet/Dashlet.php:283
+#, php-format
+msgid "%s related group objects have been created"
+msgstr "%s verwandte Gruppenobjekte wurden erstellt"
+
+#: application/controllers/ServiceController.php:150
+#, php-format
+msgid "(on %s)"
+msgstr "(auf %s)"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:886
+msgid "- click to add more -"
+msgstr "- Hier klicken um mehr hinzuzufügen -"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:459
+msgid "- inherited -"
+msgstr "- geerbt -"
+
+#: application/views/helpers/FormDataFilter.php:376
+#: application/views/scripts/config/diff.phtml:12
+#: application/views/scripts/config/diff.phtml:28
+#: library/Director/DataType/DataTypeDirectorObject.php:33
+#: library/Director/Web/Form/QuickBaseForm.php:112
+#: library/Director/Job/SyncJob.php:93 library/Director/Job/ImportJob.php:84
+msgid "- please choose -"
+msgstr "- bitte wählen -"
+
+#: library/Director/Import/ImportSourceLdap.php:61
+msgid ""
+"A custom LDAP filter to use in addition to the object class. This allows for "
+"a lot of flexibility but requires LDAP filter skills. Simple filters might "
+"look as follows: operatingsystem=*server*"
+msgstr ""
+"Ein benutzerdefinierter LDAP Filter zur gemeinsamen Verwendung mit der "
+"Objektklasse. Dies gewährt ein großes Maß an Flexibilität, benötigt jedoch "
+"Kenntnisse über LDAP Filter. Einfache Filter können wie folgt aussehen: "
+"operatingsystem=*server*"
+
+#: application/forms/SyncPropertyForm.php:213
+msgid ""
+"A custom string. Might contain source columns, please use placeholders of "
+"the form ${columnName} in such case"
+msgstr ""
+"Benutzerdefinierte Zeichenkette. Falls Quellspalten enthalten sind, müssen "
+"Platzhalter in der Form ${columnName} genutzt werden"
+
+#: application/forms/IcingaObjectFieldForm.php:122
+msgid "A description about the field"
+msgstr "Eine Beschreibung des Feldes"
+
+#: application/forms/IcingaServiceSetForm.php:114
+msgid ""
+"A meaningful description explaining your users what to expect when assigning "
+"this set of services"
+msgstr ""
+"Eine aussagekräftige Beschreibung, die den Benutzern erklärt, was geschieht, "
+"wenn dieses Service-Set zugewiesen wird"
+
+# Geschlecht kann hier leider nicht bestimmt werden -ThW
+#: application/forms/IcingaTimePeriodRangeForm.php:60
+#: library/Director/Web/Form/DirectorObjectForm.php:489
+#, php-format
+msgid "A new %s has successfully been created"
+msgstr "Ein neuer %s wurde erfolgreich erstellt"
+
+#: application/forms/ImportRowModifierForm.php:56
+msgid ""
+"A property modifier allows you to modify a specific property at import time"
+msgstr ""
+"Ein Eigenschaftsmodifikator erlaubt das Verändern bestimmter Eigenschaften "
+"während des Imports"
+
+#: application/forms/ImportSourceForm.php:17
+msgid ""
+"A short name identifying this import source. Use something meaningful, like "
+"\"Hosts from Puppet\", \"Users from Active Directory\" or similar"
+msgstr ""
+"Eine kurzer Name um diese Importquelle zu bezeichnen. Sprechende "
+"Bezeichnungen wie \"Hosts aus Puppet\" oder \"Benutzer aus LDAP\" bieten "
+"sich an"
+
+#: application/forms/DirectorJobForm.php:74
+msgid ""
+"A short name identifying this job. Use something meaningful, like \"Import "
+"Puppet Hosts\""
+msgstr ""
+"Eine kurzer Name um diesen Auftrag zu bezeichnen. Sprechende Bezeichnungen "
+"wie \"Puppet Hosts importieren\" bieten sich an"
+
+#: application/forms/IcingaServiceSetForm.php:53
+msgid "A short name identifying this set of services"
+msgstr "Eine kurzer Name um dieses Service-Set zu bezeichnen"
+
+#: application/controllers/HostController.php:354
+#, php-format
+msgid ""
+"A ticket for this agent could not have been requested from your deployment "
+"endpoint: %s"
+msgstr ""
+"Für diesen Agenten konnte kein Ticket vom Deployment-Endpoint angefordert "
+"werden: %s"
+
+#: library/Director/Dashboard/Dashlet/DeploymentDashlet.php:95
+#, php-format
+msgid ""
+"A total of %d config changes happened since your last deployed config has "
+"been rendered"
+msgstr ""
+"Insgesamt wurde die Konfiguration %d mal seit dem letzten Ausbringen geändert"
+
+#: application/forms/KickstartForm.php:126
+#: application/forms/IcingaEndpointForm.php:46
+msgid "API user"
+msgstr "API Benutzer"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1144
+msgid "Accept passive checks"
+msgstr "Passive Checkergebnisse akzeptieren"
+
+#: application/forms/IcingaHostForm.php:73
+msgid "Accepts config"
+msgstr "Akzeptiert Konfiguration"
+
+# Aktuell wird es so in anderen Modulen übersetzt. -ThW
+#: library/Director/IcingaConfig/TypeFilterSet.php:28
+msgid "Acknowledgement"
+msgstr "Bestätigung"
+
+#: application/views/scripts/show/activitylog.phtml:42
+#: application/views/scripts/show/activitylog.phtml:47
+#: application/tables/ActivityLogTable.php:107
+#: application/tables/ConfigFileDiffTable.php:65
+msgid "Action"
+msgstr "Aktion"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1230
+msgid "Action URL"
+msgstr "Aktions-URL"
+
+#: application/views/scripts/show/activitylog.phtml:79
+#: application/views/scripts/config/files.phtml:16
+#: library/Director/Web/Table/QuickTable.php:279
+msgid "Actions"
+msgstr "Aktionen"
+
+#: application/forms/SettingsForm.php:135
+msgid "Activation Tool"
+msgstr "Aktivierungswerkzeug"
+
+#: application/forms/SettingsForm.php:114
+msgid "Active-Passive"
+msgstr "Aktiv-Passiv"
+
+#: application/views/scripts/syncrule/syncRunDetails.phtml:11
+msgid "Activity"
+msgstr "Aktivität"
+
+#: application/controllers/ConfigController.php:117
+#: application/controllers/ConfigController.php:332
+#: library/Director/Web/Controller/ActionController.php:171
+#: library/Director/Dashboard/Dashlet/ActivityLogDashlet.php:11
+msgid "Activity Log"
+msgstr "Aktivitätslog"
+
+#: library/Director/Web/Controller/ObjectController.php:304
+#, php-format
+msgid "Activity Log: %s"
+msgstr "Aktivitätslog: %s"
+
+#: configuration.php:70
+msgid "Activity log"
+msgstr "Aktivitätslog"
+
+#: application/views/scripts/show/activitylog.phtml:27
+msgid "Activity log entry"
+msgstr "Aktivitätslogeintrag"
+
+#: application/controllers/DataController.php:13
+#: application/controllers/DataController.php:65
+#: application/controllers/DataController.php:119
+#: application/controllers/JobsController.php:16
+#: library/Director/Web/Form/DirectorObjectForm.php:364
+#: library/Director/Web/Controller/NewObjectsController.php:121
+#: library/Director/Web/Controller/NewObjectsController.php:148
+#: library/Director/Web/Controller/ObjectsController.php:148
+#: library/Director/Web/Controller/ObjectsController.php:229
+msgid "Add"
+msgstr "Hinzufügen"
+
+#: library/Director/Web/Controller/ObjectController.php:91
+#, php-format
+msgid "Add %s"
+msgstr "%s hinzufügen"
+
+#: application/controllers/JobController.php:87
+msgid "Add a job"
+msgstr "Auftrag hinzufügen"
+
+#: application/controllers/DatafieldController.php:39
+msgid "Add a new Data Field"
+msgstr "Einen neues Datenfeld hinzufügen"
+
+#: application/views/helpers/FormExtensibleSet.php:226
+msgid "Add a new entry"
+msgstr "Einen neuen Eintrag hinzufügen"
+
+#: application/views/helpers/FormExtensibleSet.php:135
+msgid "Add a new one..."
+msgstr "Einen Neuen hinzufügen..."
+
+#: application/controllers/ServicesetController.php:50
+#, php-format
+msgid "Add a service set to \"%s\""
+msgstr "Füge ein Service-Set zu \"%s\" hinzu"
+
+#: application/controllers/ServiceController.php:85
+#, php-format
+msgid "Add a service to \"%s\""
+msgstr "Füge einen Service zu \"%s\" hinzu"
+
+#: application/views/helpers/FormDataFilter.php:427
+msgid "Add another filter"
+msgstr "Weiteren Filter hinzufügen"
+
+#: application/forms/DirectorDatalistentryForm.php:40
+msgid "Add data list entry"
+msgstr "Datenlisteneintrag hinzufügen"
+
+#: application/controllers/ListController.php:15
+#: application/controllers/ImportsourceController.php:53
+msgid "Add import source"
+msgstr "Importquelle hinzufügen"
+
+#: application/controllers/JobController.php:64
+msgid "Add job"
+msgstr "Auftrag hinzufügen"
+
+#: application/controllers/DataController.php:95
+msgid "Add list"
+msgstr "Liste hinzufügen"
+
+#: library/Director/Web/Controller/ObjectController.php:214
+#, php-format
+msgid "Add new Icinga %s"
+msgstr "Neuen Icinga %s hinzufügen"
+
+#: library/Director/Web/Controller/ObjectController.php:209
+#, php-format
+msgid "Add new Icinga %s template"
+msgstr "Neue Icinga %s Vorlage hinzufügen"
+
+#: application/controllers/ImportsourceController.php:88
+msgid "Add property modifier"
+msgstr "Eigenschaftsmodifikator hinzufügen"
+
+#: application/controllers/HostController.php:72
+#: application/controllers/ServicesetController.php:62
+msgid "Add service"
+msgstr "Service hinzufügen"
+
+#: application/controllers/HostController.php:77
+msgid "Add service set"
+msgstr "Service-Set hinzufügen"
+
+#: application/controllers/SyncruleController.php:107
+#: application/controllers/SyncpropertyController.php:35
+msgid "Add sync property rule"
+msgstr "Regel für Synchronisationseigenschaft hinzufügen"
+
+#: application/controllers/SyncruleController.php:168
+#, php-format
+msgid "Add sync property: %s"
+msgstr "Synchronisationseigenschaft hinzufügen: %s"
+
+#: application/controllers/SyncruleController.php:61
+#: application/controllers/ListController.php:31
+msgid "Add sync rule"
+msgstr "Synchronisationsregel hinzufügen"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1216
+msgid "Additional notes for this object"
+msgstr "Weitere Notizen zu diesem Objekt"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1269
+msgid "Additional properties"
+msgstr "Weitere Eigenschaften"
+
+#: application/tables/IcingaHostTable.php:55
+msgid "Address"
+msgstr "Adresse"
+
+#: application/forms/SettingsForm.php:70
+msgid ""
+"All changes are tracked in the Director database. In addition you might also "
+"want to send an audit log through the Icinga Web 2 logging mechanism. That "
+"way all changes would be written to either Syslog or the configured log "
+"file. When enabling this please make sure that you configured Icinga Web 2 "
+"to log at least at \"informational\" level."
+msgstr ""
+"Alle Änderungen werden in der Director-Datenbank nachvollziehbar "
+"gespeichert. Zusätzlich kann ein Revisionslog durch den Logging-Mechanismus "
+"von Icinga Web 2 geschickt werden. Auf diese Weise werden alle Änderungen "
+"entweder an Syslog oder die konfigurierte Logdatei geschickt. Dafür muss "
+"Icinga Web 2 mindestens mit dem \"Info\" Level loggen."
+
+#: application/forms/SyncPropertyForm.php:274
+msgid "All custom variables (vars)"
+msgstr "Alle benutzerdefinierten Variablen (vars)"
+
+#: application/forms/IcingaServiceForm.php:461
+#, php-format
+msgid "All overrides have been removed from \"%s\""
+msgstr "Alle überschriebenen Eigenschaften wurden von \"%s\" entfernt"
+
+#: configuration.php:3
+msgid "Allow to access the director API"
+msgstr "Zugriff auf die Director API erlauben"
+
+#: configuration.php:4
+msgid "Allow to access the full audit log"
+msgstr "Zugriff auf das volle Audit-Log erlauben"
+
+#: configuration.php:10
+msgid "Allow to configure hosts"
+msgstr "Erlauben, Hosts zu konfigurieren"
+
+#: configuration.php:12
+msgid "Allow to configure notifications"
+msgstr "Erlauben, Benachrichtigungen zu konfigurieren"
+
+#: configuration.php:11
+msgid "Allow to configure users"
+msgstr "Erlauben, Benutzer zu konfigurieren"
+
+#: configuration.php:9
+msgid "Allow to deploy configuration"
+msgstr "Das Ausbringen von Konfigurationen erlauben"
+
+#: configuration.php:16
+msgid ""
+"Allow to inspect objects through the Icinga 2 API (could contain sensitive "
+"information)"
+msgstr ""
+"Untersuchen von Objekten über die API von Icinga 2 erlauben (könnte sensible "
+"Daten enthalten)"
+
+#: configuration.php:7
+msgid "Allow to show configuration (could contain sensitive information)"
+msgstr "Anzeigen der Konfiguration erlauben (könnte sensible Daten enthalten)"
+
+#: configuration.php:19
+msgid "Allow unrestricted access to Icinga Director"
+msgstr "Unbeschränkten Zugriff auf die Director API erlauben"
+
+#: application/forms/DirectorDatafieldForm.php:55
+#, php-format
+msgid "Also wipe all \"%s\" custom variables from %d objects?"
+msgstr ""
+"Außerdem alle \"%s\" benutzerdefinierten Variablen von %d Objekten entfernen?"
+
+#: application/forms/IcingaHostForm.php:193
+msgid ""
+"Alternative name for this host. Might be a host alias or and kind of string "
+"helping your users to identify this host"
+msgstr ""
+"Alternativer Name für diesen Host. Kann ein Alias oder jede Zeichenkette "
+"sein, die Benutzern hilft, diesen Host zu identifizieren"
+
+#: application/forms/IcingaUserForm.php:116
+msgid ""
+"Alternative name for this user. In case your object name is a username, this "
+"could be the full name of the corresponding person"
+msgstr ""
+"Alternativer Name für diesen Benutzer. Falls der Name des Objekts ein "
+"Benutzername ist, könnte hier der vollständige Name der betreffenden Person "
+"hinterlegt werden"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1250
+msgid "Alternative text to be shown in case above icon is missing"
+msgstr "Alternativer Text der angezeigt werden soll, falls obiges Icon fehlt"
+
+#: application/forms/IcingaCommandArgumentForm.php:86
+msgid ""
+"An Icinga DSL expression that returns a boolean value, e.g.: var cmd = "
+"bool(macro(\"$cmd$\")); return cmd ..."
+msgstr ""
+"Ein Icinga DSL Ausdruck welcher einen booleschen Wert liefert, z.B.: var cmd "
+"= bool(macro(\"$cmd$\")); return cmd ..."
+
+#: application/forms/IcingaCommandArgumentForm.php:47
+msgid ""
+"An Icinga DSL expression, e.g.: var cmd = macro(\"$cmd$\"); return "
+"typeof(command) == String ..."
+msgstr ""
+"Ein Icinga DSL Ausdruck, z.B.: var cmd = macro(\"$cmd$\"); return "
+"typeof(command) == String ..."
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1232
+msgid ""
+"An URL leading to additional actions for this object. Often used with Icinga "
+"Classic, rarely with Icinga Web 2 as it provides far better possibilities to "
+"integrate addons"
+msgstr ""
+"Eine URL zu weiteren Aktionen für dieses Objekt. Wird oft mit Icinga "
+"Classic, jedoch selten mit Icinga Web 2 genutzt, da dieses viel bessere "
+"Möglichkeiten zur Integration von Addons bietet"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1225
+msgid "An URL pointing to additional notes for this object"
+msgstr "Eine URL zu Notizen für dieses Objekt"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1241
+msgid ""
+"An URL pointing to an icon for this object. Try \"tux.png\" for icons "
+"relative to public/img/icons or \"cloud\" (no extension) for items from the "
+"Icinga icon font"
+msgstr ""
+"Eine URL zu einem Icon für dieses Objekt. \"tux.png\" für Icons relativ zu "
+"public/img/icons oder \"cloud\" (ohne Erweiterung) für Objekte aus dem "
+"Icinga Icon-Fundus"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1035
+msgid ""
+"An alternative display name for this group. If you wonder how this could be "
+"helpful just leave it blank"
+msgstr ""
+"Ein alternativer Anzeigename für diese Gruppe. Kann leer gelassen werden."
+
+#: application/forms/DirectorDatafieldForm.php:105
+msgid ""
+"An extended description for this field. Will be shown as soon as a user puts "
+"the focus on this field"
+msgstr ""
+"Eine ausführliche Beschreibung dieses Felds. Wird angezeigt, sobald ein "
+"Benutzer den Fokus auf dieses Feld legt"
+
+#: library/Director/Import/ImportSourceLdap.php:55
+msgid ""
+"An object class to search for. Might be \"user\", \"group\", \"computer\" or "
+"similar"
+msgstr ""
+"Die Objektklasse, nach der gesucht werden soll. z.B. \"user\", \"group\", "
+"\"computer\" oder Ähnliches"
+
+#: library/Director/PropertyModifier/PropertyModifierExtractFromDN.php:20
+msgid "Any first (leftmost) component"
+msgstr "Beliebige erste (linkeste) Komponente"
+
+#: library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php:13
+msgid "Api users"
+msgstr "Api Benutzer"
+
+#: application/controllers/HostController.php:199
+#, php-format
+msgid "Applied service: %s"
+msgstr "Zugewiesener Service: %s"
+
+#: application/controllers/HostController.php:138
+msgid "Applied services"
+msgstr "Zugewiesene Services"
+
+#: application/controllers/NotificationController.php:51
+#: application/controllers/ServiceController.php:90
+#, php-format
+msgid "Apply \"%s\""
+msgstr "Apply \"%s\""
+
+#: application/forms/ApplyMigrationsForm.php:25
+#, php-format
+msgid "Apply %d pending schema migrations"
+msgstr "%d Schema-Migrations-Scripte anwenden"
+
+#: application/forms/IcingaServiceForm.php:352
+msgid "Apply For"
+msgstr "Anwenden auf"
+
+#: library/Director/Web/Controller/NewObjectsController.php:166
+#, php-format
+msgid "Apply Icinga %s"
+msgstr "Icinga %s anwenden"
+
+#: application/forms/ApplyMigrationsForm.php:20
+msgid "Apply a pending schema migration"
+msgstr "Ein ausstehende Schema-Migration durchführen"
+
+#: library/Director/Job/SyncJob.php:61
+msgid "Apply changes"
+msgstr "Änderungen anwenden"
+
+#: library/Director/Dashboard/Dashlet/NotificationApplyDashlet.php:19
+msgid "Apply notifications with specific properties according to given rules. "
+msgstr ""
+"Benachrichtigungen mit bestimmten Eigenschaften anhand von Regeln hinzufügen."
+
+#: library/Director/Web/Form/DirectorObjectForm.php:919
+msgid "Apply rule"
+msgstr "Apply Regel"
+
+#: application/forms/KickstartForm.php:31
+msgid "Apply schema migrations"
+msgstr "Schema-Migrations-Scripte anwenden"
+
+#: application/forms/IcingaNotificationForm.php:63
+msgid "Apply to"
+msgstr "Anwenden auf"
+
+#: application/controllers/ServiceController.php:187
+#, php-format
+msgid "Apply: %s"
+msgstr "Apply: %s"
+
+#: application/tables/IcingaCommandArgumentTable.php:52
+msgid "Argument"
+msgstr "Argument"
+
+#: application/forms/IcingaObjectFieldForm.php:87
+msgid "Argument macros"
+msgstr "Argument Makros"
+
+#: application/forms/IcingaCommandArgumentForm.php:25
+msgid "Argument name"
+msgstr "Argumentname"
+
+#: application/forms/SyncPropertyForm.php:280
+msgid "Arguments"
+msgstr "Argumente"
+
+#: library/Director/DataType/DataTypeDirectorObject.php:72
+msgid "Array"
+msgstr "Array"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1301
+msgid "Assign where"
+msgstr "Zuweisen wo"
+
+#: application/views/scripts/show/activitylog.phtml:33
+msgid "Author"
+msgstr "Autor"
+
+#: configuration.php:66
+msgid "Automation"
+msgstr "Automatisierung"
+
+#: application/forms/IcingaObjectFieldForm.php:113
+#: application/forms/DirectorDatafieldForm.php:94
+msgid "Caption"
+msgstr "Überschrift"
+
+#: application/forms/IcingaMultiEditForm.php:250
+#, php-format
+msgid "Changing this value affects %d object(s): %s"
+msgstr "Diesen Wert zu ändern beeinflusst %d Objekt(e): %s"
+
+#: library/Director/Import/ImportSourceCoreApi.php:60
+msgid "Check Commands"
+msgstr "Check-Kommando"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1055
+msgid "Check command"
+msgstr "Check-Kommando"
+
+#: application/forms/IcingaNotificationForm.php:237
+#: library/Director/Web/Form/DirectorObjectForm.php:1056
+msgid "Check command definition"
+msgstr "Check-Kommandodefinition"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:211
+msgid "Check execution"
+msgstr "Check-Ausführung"
+
+#: application/forms/ImportCheckForm.php:24
+#: application/forms/SyncCheckForm.php:24
+msgid "Check for changes"
+msgstr "Auf Änderungen prüfen"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1092
+msgid "Check interval"
+msgstr "Check-Intervall"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1126
+msgid "Check period"
+msgstr "Checkzeitraum"
+
+#: application/forms/ImportCheckForm.php:46
+msgid "Checking this Import Source failed"
+msgstr "Überprüfen dieser Importquelle fehlgeschlagen"
+
+#: application/forms/SyncCheckForm.php:63
+msgid "Checking this sync rule failed"
+msgstr "Überprüfen dieser Synchronisationsregel fehlgeschlagen"
+
+#: application/views/scripts/show/activitylog.phtml:74
+msgid "Checksum"
+msgstr "Prüfsumme"
+
+#: application/forms/SyncRuleForm.php:34
+msgid "Choose an object type"
+msgstr "Einen Objekttyp auswählen"
+
+#: application/forms/IcingaServiceForm.php:334
+msgid "Choose the host this single service should be assigned to"
+msgstr "Host wählen, dem dieser einzelne Service zugewiesen werden soll"
+
+#: application/forms/IcingaZoneForm.php:37
+msgid "Chose an (optional) parent zone"
+msgstr "Eine (optionale) Elternzone auswählen"
+
+#: library/Director/Web/Controller/ObjectController.php:182
+msgid "Clone"
+msgstr "Klonen"
+
+#: application/forms/IcingaCloneObjectForm.php:31
+#, php-format
+msgid "Clone \"%s\""
+msgstr "Klone \"%s\""
+
+#: library/Director/Web/Controller/ObjectController.php:240
+#, php-format
+msgid "Clone Icinga %s"
+msgstr "Klone Icinga %s"
+
+#: application/forms/IcingaCloneObjectForm.php:25
+msgid "Clone the object as is, preserving imports"
+msgstr "Objekt vollständig unter Erhalt der Vererbung klonen"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:958
+msgid "Cluster Zone"
+msgstr "Cluster Zone"
+
+#: application/forms/SyncRuleForm.php:20
+#: application/forms/IcingaCommandForm.php:55
+#: application/tables/IcingaCommandTable.php:32
+#: library/Director/Web/Form/DirectorObjectForm.php:1425
+msgid "Command"
+msgstr "Kommando"
+
+#: application/controllers/CommandController.php:26
+#, php-format
+msgid "Command arguments: %s"
+msgstr "Kommandoargumente: %s"
+
+#: application/forms/IcingaHostForm.php:83
+msgid "Command endpoint"
+msgstr "Kommandoendpunkt"
+
+#: application/tables/IcingaCommandTable.php:33
+msgid "Command line"
+msgstr "Kommandozeile"
+
+#: application/forms/IcingaCommandForm.php:47
+msgid "Command name"
+msgstr "Kommandoname"
+
+#: application/forms/IcingaCommandForm.php:17
+msgid "Command type"
+msgstr "Kommandotyp"
+
+#: configuration.php:58
+#: library/Director/Dashboard/Dashlet/CommandObjectDashlet.php:13
+msgid "Commands"
+msgstr "Kommandos"
+
+#: application/forms/IcingaCommandArgumentForm.php:84
+#: application/forms/IcingaCommandArgumentForm.php:93
+msgid "Condition (set_if)"
+msgstr "Bedingung (set_if)"
+
+#: application/forms/IcingaCommandArgumentForm.php:70
+msgid "Condition format"
+msgstr "Bedingungsformat"
+
+#: application/controllers/DeploymentController.php:36
+#: application/controllers/JobController.php:82
+#: application/controllers/ConfigController.php:174
+#: application/controllers/ConfigController.php:375
+msgid "Config"
+msgstr "Konfiguration"
+
+#: library/Director/Dashboard/Dashlet/DeploymentDashlet.php:18
+msgid "Config Deployment"
+msgstr "Ausrollen der Konfiguration"
+
+#: application/controllers/ConfigController.php:105
+#: application/forms/DeployConfigForm.php:101
+msgid "Config deployment failed"
+msgstr "Ausrollen der Konfiguration fehlgeschlagen"
+
+#: application/controllers/ConfigController.php:268
+#: application/controllers/ConfigController.php:271
+msgid "Config diff"
+msgstr "Konfigurationsunterschied"
+
+#: application/controllers/ConfigController.php:237
+#: application/controllers/ConfigController.php:317
+#, php-format
+msgid "Config file \"%s\""
+msgstr "Konfigurationsdatei \"%s\""
+
+#: application/controllers/ConfigController.php:90
+#: application/forms/DeployConfigForm.php:77
+#: application/forms/DeployConfigForm.php:96
+msgid "Config has been submitted, validation is going on"
+msgstr "Konfiguration wurde übergeben, Überprüfung ist im Gange"
+
+#: library/Director/Web/Controller/ObjectController.php:153
+#, php-format
+msgid "Config preview: %s"
+msgstr "Konfigurationsvorschau: %s"
+
+#: library/Director/Web/Controller/ActionController.php:165
+msgid "Configs"
+msgstr "Konfigurationen"
+
+#: application/views/scripts/deployment/index.phtml:22
+msgid "Configuration"
+msgstr "Konfiguration"
+
+#: application/forms/SettingsForm.php:81
+msgid "Configuration format"
+msgstr "Konfigurationsformat"
+
+#: application/forms/KickstartForm.php:283
+msgid "Configuration has been stored"
+msgstr "Konfiguration wurde gespeichert"
+
+#: application/tables/IcingaHostTemplateTable.php:35
+msgid "Create a new host based on this template"
+msgstr "Einen neuen Host auf dieser Vorlage erstellen"
+
+#: application/forms/KickstartForm.php:30
+msgid "Create database schema"
+msgstr "Datenbankschema erstellen"
+
+#: application/forms/IcingaHostForm.php:30
+msgid "Create immediately"
+msgstr "Sofort erstellen"
+
+#: application/forms/ApplyMigrationsForm.php:31
+msgid "Create schema"
+msgstr "Datenbankschema erstellen"
+
+#: application/tables/SyncRunTable.php:55
+msgid "Created"
+msgstr "Erstellt"
+
+#: library/Director/IcingaConfig/StateFilterSet.php:26
+msgid "Critical"
+msgstr "Kritisch"
+
+#: application/forms/SyncPropertyForm.php:178
+msgid "Custom expression"
+msgstr "Benutzerdefinierter Ausdruck"
+
+#: library/Director/Web/Controller/ObjectController.php:264
+#, php-format
+msgid "Custom fields: %s"
+msgstr "Benutzerdefinierte Felder: \"%s\""
+
+#: library/Director/IcingaConfig/TypeFilterSet.php:25
+msgid "Custom notification"
+msgstr "Benutzerdefinierte Benachrichtigung"
+
+#: application/forms/IcingaServiceForm.php:214
+#: library/Director/Web/Form/IcingaObjectFieldLoader.php:212
+msgid "Custom properties"
+msgstr "Benutzerdefinierte Eigenschaften"
+
+#: application/forms/SyncPropertyForm.php:63
+msgid "Custom variable"
+msgstr "Benutzerdefinierte Variable"
+
+#: application/forms/SyncPropertyForm.php:273
+msgid "Custom variable (vars.)"
+msgstr "Benutzerdefinierte Variable (vars.)"
+
+#: application/controllers/SuggestController.php:115
+#: application/controllers/SuggestController.php:128
+#: library/Director/Objects/IcingaHost.php:138
+#: library/Director/Objects/IcingaService.php:457
+msgid "Custom variables"
+msgstr "Benutzerdefinierte Variablen"
+
+#: application/forms/KickstartForm.php:211
+msgid "DB Resource"
+msgstr "Datenbankressource"
+
+#: library/Director/PropertyModifier/PropertyModifierExtractFromDN.php:15
+msgid "DN component"
+msgstr "DN Komponente"
+
+#: library/Director/PropertyModifier/PropertyModifierDnsRecords.php:25
+msgid "DNS record type"
+msgstr "DNS-Record-Typ"
+
+#: application/controllers/DataController.php:126
+#: library/Director/Web/Controller/ActionController.php:183
+msgid "Data fields"
+msgstr "Datenfelder"
+
+#: application/forms/DirectorDatafieldForm.php:77
+msgid ""
+"Data fields allow you to customize input controls for Icinga custom "
+"variables. Once you defined them here, you can provide them through your "
+"defined templates. This gives you a granular control over what properties "
+"your users should be allowed to configure in which way."
+msgstr ""
+"Datenfelder erlauben es, Eingabefelder für benutzerdefinierte Variablen zu "
+"personalisieren. Sobald diese hier definiert wurden, können sie über "
+"definierte Templates zur Verfügung gestellt werden. Das erlaubt eine "
+"granuläre Kontrolle darüber, welche Eigenschaften in welche Weise "
+"konfigurierbar sein sollen."
+
+#: library/Director/Dashboard/Dashlet/DatafieldDashlet.php:17
+msgid "Data fields make sure that configuration fits your rules"
+msgstr ""
+"Datenfelder sorgen dafür, dass die Konfiguration in vorgegebene Regeln passt."
+
+#: application/forms/DirectorDatalistForm.php:24
+msgid "Data list"
+msgstr "Datenliste"
+
+#: application/controllers/DataController.php:36
+#, php-format
+msgid "Data list: %s"
+msgstr "Datenliste: %s"
+
+#: application/controllers/DataController.php:20
+#: library/Director/Web/Controller/ActionController.php:189
+msgid "Data lists"
+msgstr "Datenlisten"
+
+#: application/forms/DirectorDatalistForm.php:15
+msgid ""
+"Data lists are mainly used as data providers for custom variables presented "
+"as dropdown boxes boxes. You can manually manage their entries here in "
+"place, but you could also create dedicated sync rules after creating a new "
+"empty list. This would allow you to keep your available choices in sync with "
+"external data providers"
+msgstr ""
+"Datenlisten werden hauptsächlich als Datenquelle für benutzerdefinierte "
+"Variablen benutzt, die in Dropdown-Auswahlfeldern präsentiert werden. Ihre "
+"Einträge können hier manuell, oder über eine dedizierte "
+"Synchronisationsregel nach dem Anlegen einer leeren Liste verwaltet werden. "
+"Letzteres erlaubt das Synchronisieren der verfügbaren Auswahlmöglichkeiten "
+"mit externen Datenquellen"
+
+#: application/forms/DirectorDatafieldForm.php:120
+msgid "Data type"
+msgstr "Datentyp"
+
+#: application/forms/KickstartForm.php:253
+msgid "Database backend"
+msgstr "Datenbankbackend"
+
+#: application/forms/SyncRuleForm.php:19
+msgid "Datalist entry"
+msgstr "Datenlisteneintrag"
+
+#: application/views/scripts/show/activitylog.phtml:37
+msgid "Date"
+msgstr "Datum"
+
+#: application/forms/IcingaTimePeriodRangeForm.php:20
+#: application/tables/IcingaTimePeriodRangeTable.php:49
+msgid "Day(s)"
+msgstr "Tag(e)"
+
+#: application/forms/SettingsForm.php:90
+msgid ""
+"Default configuration format. Please note that v1.x is for special "
+"transitional projects only and completely unsupported. There are no plans to "
+"make Director a first-class configuration backends for Icinga 1.x"
+msgstr ""
+"Standardkonfigurationsformat. Bitte beachten, dass v1.x nur für ganz "
+"spezielle Migrationsprojekte implementiert wurde und in keiner Weise "
+"unterstützt wird. Es gibt keine Pläne aus dem Director ein "
+"Konfigurationswerkzeug für Icinga 1.x zu machen"
+
+#: application/forms/SettingsForm.php:35
+msgid "Default global zone"
+msgstr "Globale Standard-Zone"
+
+#: library/Director/Dashboard/Dashlet/ImportSourceDashlet.php:29
+msgid "Define and manage imports from various data sources"
+msgstr "Definiert und verwaltet Importe von diversen Datenquellen"
+
+#: library/Director/Dashboard/Dashlet/DatafieldDashlet.php:11
+msgid "Define data fields"
+msgstr "Datenfelder definieren"
+
+#: library/Director/Dashboard/Dashlet/SyncDashlet.php:29
+msgid "Define how imported data should be synchronized with Icinga"
+msgstr ""
+"Definieren, wie importierte Daten mit Icinga synchronisiert werden sollen"
+
+#: application/forms/SyncRuleForm.php:42
+msgid ""
+"Define what should happen when an object with a matching key already exists. "
+"You could merge its properties (import source wins), replace it completely "
+"with the imported object or ignore it (helpful for one-time imports)"
+msgstr ""
+"Angeben, was geschehen soll wenn ein Objekt mit gleichem Schlüssel bereits "
+"existiert. Die Eigenschaften können zusammengeführt (Importquelle gewinnt), "
+"durch das importierte Objekt ersetzt oder ignoriert (hilfreich für einmalige "
+"Importe) werden"
+
+#: library/Director/Dashboard/ObjectsDashboard.php:16
+msgid "Define whatever you want to be monitored"
+msgstr "Angeben, was überwacht werden soll"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1114
+msgid "Defines after how many check attempts a new hard state is reached"
+msgstr ""
+"Legt fest, nach wie vielen Versuchen ein neuer Hard State erreicht wird"
+
+#: application/forms/IcingaNotificationForm.php:181
+msgid "Delay unless the first notification should be sent"
+msgstr "Verzögerung bis die erste Benachrichtigung verschickt werden soll"
+
+#: application/forms/IcingaObjectFieldForm.php:184
+#: library/Director/Web/Form/DirectorObjectForm.php:729
+msgid "Delete"
+msgstr "Löschen"
+
+#: application/tables/SyncRunTable.php:57
+msgid "Deleted"
+msgstr "Gelöscht"
+
+#: library/Director/PropertyModifier/PropertyModifierSplit.php:13
+msgid "Delimiter"
+msgstr "Trenner"
+
+#: application/views/scripts/object/deploymentLink.phtml:36
+msgid "Deploy"
+msgstr "Ausbringen"
+
+#: application/forms/DeployConfigForm.php:41
+#, php-format
+msgid "Deploy %d pending changes"
+msgstr "%d ausstehende Änderungen ausbringen"
+
+#: library/Director/Dashboard/DeploymentDashboard.php:16
+msgid "Deploy configuration to your Icinga nodes"
+msgstr "Konfiguration auf Icinga Knoten ausbringen"
+
+#: library/Director/Job/ConfigJob.php:191
+msgid "Deploy modified config"
+msgstr "Veränderte Konfiguration ausbringen"
+
+#: application/views/scripts/config/show.phtml:7
+msgid "Deploy to master"
+msgstr "Auf den Master ausbringen"
+
+#: application/controllers/DeploymentController.php:30
+#: application/controllers/ConfigController.php:165
+#: application/controllers/ConfigController.php:365
+msgid "Deployment"
+msgstr "Deployment"
+
+#: application/forms/SettingsForm.php:125
+msgid "Deployment Path"
+msgstr "Ausbringungspfad"
+
+#: application/controllers/DeploymentController.php:19
+msgid "Deployment details"
+msgstr "Deployment Details"
+
+#: application/forms/SettingsForm.php:110
+msgid "Deployment mode"
+msgstr "Ausbringungsmodus"
+
+#: application/forms/SettingsForm.php:119
+msgid "Deployment mode for Icinga 1 configuration"
+msgstr "Ausbringungsmodus für Icinga 1 Konfiguration"
+
+#: application/views/scripts/deployment/index.phtml:14
+msgid "Deployment time"
+msgstr "Deployment Zeit"
+
+#: configuration.php:75 application/controllers/ConfigController.php:43
+#: application/controllers/ConfigController.php:342
+#: library/Director/Web/Controller/ActionController.php:159
+msgid "Deployments"
+msgstr "Deployments"
+
+#: application/forms/IcingaServiceSetForm.php:112
+#: application/forms/IcingaObjectFieldForm.php:121
+#: application/forms/DirectorDatafieldForm.php:103
+msgid "Description"
+msgstr "Beschreibung"
+
+#: application/tables/SyncpropertyTable.php:43
+msgid "Destination"
+msgstr "Ziel"
+
+#: application/forms/SyncPropertyForm.php:44
+msgid "Destination Field"
+msgstr "Zielfeld"
+
+#: application/controllers/ShowController.php:48
+msgid "Diff"
+msgstr "Diff"
+
+#: application/views/scripts/config/files.phtml:23
+msgid "Diff with other config"
+msgstr "Mit anderer Konfiguration vergleichen"
+
+#: library/Director/Web/Navigation/Renderer/ConfigHealthItemRenderer.php:66
+msgid "Director database schema has not been created yet"
+msgstr "Datenbankschema für Director wurde noch nicht erstellt"
+
+#: application/forms/SettingsForm.php:46
+msgid "Disable all Jobs"
+msgstr "Alle Aufträge deaktivieren"
+
+#: application/forms/DirectorJobForm.php:37
+#: library/Director/Web/Form/DirectorObjectForm.php:1021
+msgid "Disabled"
+msgstr "Deaktiviert"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1022
+msgid "Disabled objects will not be deployed"
+msgstr "Deaktivierte Objekte werden nicht ausgebracht"
+
+#: application/forms/IcingaTimePeriodForm.php:37
+#: application/tables/IcingaUserGroupTable.php:33
+#: application/tables/IcingaTimePeriodTable.php:32
+#: application/tables/IcingaServiceGroupTable.php:33
+#: application/tables/IcingaHostGroupTable.php:33
+#: library/Director/Web/Form/DirectorObjectForm.php:1033
+msgid "Display Name"
+msgstr "Anzeigename"
+
+#: application/forms/IcingaUserForm.php:114
+#: application/forms/IcingaHostForm.php:191
+msgid "Display name"
+msgstr "Anzeigename"
+
+#: library/Director/Dashboard/DataDashboard.php:16
+msgid "Do more with your data"
+msgstr "Mehr mit Daten anfangen"
+
+#: library/Director/IcingaConfig/StateFilterSet.php:21
+msgid "Down"
+msgstr "Down"
+
+#: library/Director/IcingaConfig/TypeFilterSet.php:30
+msgid "Downtime ends"
+msgstr "Downtime endet"
+
+#: library/Director/IcingaConfig/TypeFilterSet.php:31
+msgid "Downtime removed"
+msgstr "Downtime entfernt"
+
+#: library/Director/IcingaConfig/TypeFilterSet.php:29
+msgid "Downtime starts"
+msgstr "Downtime startet"
+
+#: application/views/scripts/syncrule/syncRunDetails.phtml:7
+#: application/views/scripts/deployment/index.phtml:41
+msgid "Duration"
+msgstr "Dauer"
+
+#: application/controllers/DatafieldController.php:37
+msgid "Edit a field"
+msgstr "Ein Feld bearbeiten"
+
+#: application/controllers/ImportsourceController.php:50
+msgid "Edit import source"
+msgstr "Importquelle bearbeiten"
+
+#: application/controllers/DataController.php:59
+#: application/controllers/DataController.php:86
+#: application/controllers/DataController.php:165
+msgid "Edit list"
+msgstr "Liste bearbeiten"
+
+#: application/controllers/SyncpropertyController.php:29
+msgid "Edit sync property rule"
+msgstr "Synchronisationseigenschaftsregel bearbeiten"
+
+#: application/forms/IcingaUserForm.php:33
+#: application/tables/IcingaUserTable.php:39
+msgid "Email"
+msgstr "E-Mail"
+
+#: application/forms/SettingsForm.php:61
+msgid "Enable audit log"
+msgstr "Revisionslog aktivieren"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1156
+msgid "Enable event handler"
+msgstr "Eventhandler aktivieren"
+
+#: application/controllers/InspectController.php:21
+#: application/forms/SyncRuleForm.php:22
+#: application/forms/IcingaEndpointForm.php:24
+#: application/tables/IcingaEndpointTable.php:50
+msgid "Endpoint"
+msgstr "Endpunkt"
+
+#: application/forms/KickstartForm.php:92
+msgid "Endpoint Name"
+msgstr "Name des Endpunkts"
+
+#: application/forms/IcingaEndpointForm.php:31
+msgid "Endpoint address"
+msgstr "Adresse des Endpunkts"
+
+#: application/forms/IcingaEndpointForm.php:18
+msgid "Endpoint template name"
+msgstr "Name der Endpunktsvorlage"
+
+#: application/tables/IcingaZoneTable.php:39
+#: library/Director/Import/ImportSourceCoreApi.php:62
+#: library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php:17
+msgid "Endpoints"
+msgstr "Endpunkte"
+
+#: application/controllers/DataController.php:48
+msgid "Entries"
+msgstr "Einträge"
+
+#: application/forms/IcingaHostForm.php:66
+msgid "Establish connection"
+msgstr "Verbindung herstellen"
+
+#: application/forms/IcingaServiceForm.php:356
+msgid ""
+"Evaluates the apply for rule for all objects with the custom attribute "
+"specified. E.g selecting \"host.vars.custom_attr\" will generate \"for "
+"(config in host.vars.array_var)\" where \"config\" will be accessible "
+"through \"$config$\". NOTE: only custom variables of type \"Array\" are "
+"eligible."
+msgstr ""
+"Berechnet die Apply-Regel für alle Objekte für welche diese "
+"benutzerdefinierte Eigenschaft spezifiziert wurde. Wählt man z.B. \"host."
+"vars.custom_attr\", wird \"for (config in host.vars.array_var)\" generiert. "
+"Dabei ist \"config\" dann als \"$config$\" zugänglich. HINWEIS: nur "
+"benutzerdefinierte Eigenschaften vom Typ \"Array\" sind wählbar."
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1071
+msgid "Event command"
+msgstr "Event-Kommando"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1072
+msgid "Event command definition"
+msgstr "Event-Kommandodefinition"
+
+#: library/Director/Web/Form/DirectorObjectForm.php:1138
+msgid "Execute active checks"
+msgstr "Aktive Checks ausführen"
+
+#: application/forms/DirectorJobForm.php:48
+msgid "Execution interval for this job, in seconds"
+msgstr "Ausführungsintervall dieses Auftrags, in Sekunden"
+
+#: application/forms/SyncPropertyForm.php:171
+msgid "Existing templates"
+msgstr "Vorhandene Vorlagen"
+
+#: application/forms/SyncPropertyForm.php:177
+msgid "Expert mode"
+msgstr "Expertenmodus"
+
+#: application/views/scripts/deployment/index.phtml:68
+msgid "Failed"
+msgstr "Fehlgeschlagen"
+
+#: application/forms/IcingaImportObjectForm.php:42
+#, php-format
+msgid "Failed to import %s \"%s\""
+msgstr "Import von %s fehlgeschlagen \"%s\""
+
+#: application/forms/IcingaObjectFieldForm.php:187
+msgid "Field has been removed"
+msgstr "Feld wurde entfernt"
+
+#: application/forms/DirectorDatafieldForm.php:85
+#: application/tables/IcingaObjectDatafieldTable.php:63
+#: application/tables/DatafieldTable.php:39
+msgid "Field name"
+msgstr "Feldname"
+
+#: application/forms/DirectorDatafieldForm.php:121
+msgid "Field type"
+msgstr "Feldtyp"
+
+#: library/Director/Web/Controller/ObjectController.php:84
+msgid "Fields"
+msgstr "Felder"
+
+#: application/tables/ConfigFileDiffTable.php:66
+#: application/tables/GeneratedConfigFileTable.php:73
+msgid "File"
+msgstr "Datei"
+
+#: library/Director/Web/Controller/ActionController.php:280
+msgid "Filter"
+msgstr "Filter"
+
+#: application/forms/SyncRuleForm.php:70
+#: application/forms/SyncPropertyForm.php:98
+msgid "Filter Expression"
+msgstr "Filterausdruck"
+
+#: application/forms/IcingaNotificationForm.php:179
+msgid "First notification delay"
+msgstr "Verzögerung der ersten Benachrichtigung"
+
+#: library/Director/IcingaConfig/TypeFilterSet.php:33
+msgid "Flapping"
+msgstr "Flapping"
+
+#: library/Director/IcingaConfig/TypeFilterSet.php:35
+msgid "Flapping ends"
+msgstr "Flapping endet"
+
+#: library/Director/IcingaConfig/TypeFilterSet.php:34
+msgid "Flapping starts"
+msgstr "Flapping beginnt"
+
+#: application/forms/IcingaCloneObjectForm.php:26
+msgid "Flatten all inherited properties, strip imports"
+msgstr "Alle vererbten Eigenschaften flach machen, Importierte entfernen"
+
+#: library/Director/Job/ConfigJob.php:177
+msgid "Force rendering"
+msgstr "Erstellen erzwingen"
+
+#: library/Director/Objects/DirectorDatafield.php:74
+#, php-format
+msgid "Form element could not be created, %s is missing"
+msgstr "Formularelement konnte nicht erstellt werden, %s ist nicht vorhanden"
+
+#: library/Director/Web/Form/QuickForm.php:389
+#: library/Director/Web/Form/QuickForm.php:414
+msgid "Form has successfully been sent"
+msgstr "Formular wurde erfolgreich abgeschickt"
+
+#: application/forms/IcingaHostVarForm.php:32
+#: application/forms/IcingaServiceVarForm.php:32
+msgid "Format"
+msgstr "Format"
+
+#: application/controllers/ShowController.php:70
+msgid "Former object"
+msgstr "Vorheriges Objekt"
+
+#: application/controllers/ConfigController.php:160
+msgid "Generated config"
+msgstr "Erzeugte Konfiguration"
+
+#: application/views/scripts/config/show.phtml:25
+msgid "Generated files"
+msgstr "Erzeugte Dateien"
+
+#: application/controllers/HostController.php:106
+msgid "Generated from host vars"
+msgstr "Aus Host-Variablen generiert"
+
+#: application/forms/IcingaZoneForm.php:22
+msgid "Global zone"
+msgstr "Globale Zone"
+
+#: library/Director/PropertyModifier/PropertyModifierJoin.php:13
+msgid "Glue"
+msgstr "Kleben"
+
+#: library/Director/Job/ConfigJob.php:203
+msgid "Grace period"
+msgstr "Gnadenfrist"
+
+#: application/forms/SyncPropertyForm.php:283
+msgid "Group membership"
+msgstr "Gruppenmitgliedschaft"
+
+#: application/forms/IcingaServiceForm.php:374
+#: application/forms/IcingaUserForm.php:94
+#: application/forms/IcingaHostForm.php:141
+#: library/Director/Web/Controller/ObjectsController.php:79
+msgid "Groups"
+msgstr "Gruppen"
+
+#: application/forms/IcingaServiceForm.php:102
+msgid "Hints regarding this service"
+msgstr "Hinweise zu diesem Service"
+
+#: application/controllers/SyncruleController.php:231
+#: application/controllers/InspectController.php:29
+#: application/controllers/ImportsourceController.php:171
+#: library/Director/Web/Controller/ObjectController.php:75
+msgid "History"
+msgstr "Historie"
+
+#: application/controllers/ServiceController.php:26
+#: application/forms/SyncRuleForm.php:12
+#: application/forms/IcingaServiceForm.php:330
+#: application/forms/IcingaHostVarForm.php:15
+#: application/tables/IcingaHostVarTable.php:32
+#: application/tables/IcingaEndpointTable.php:51
+#: library/Director/Web/Form/DirectorObjectForm.php:1422
+msgid "Host"
+msgstr "Host"
+
+#: application/controllers/SuggestController.php:127
+#: library/Director/Objects/IcingaService.php:470
+msgid "Host Custom variables"
+msgstr "Benutzerdefinierte Host-Variablen"
+
+#: application/forms/IcingaHostForm.php:166
+msgid "Host address"
+msgstr "Hostadresse"
+
+#: application/forms/IcingaHostForm.php:168
+msgid ""
+"Host address. Usually an IPv4 address, but may be any kind of address your "
+"check plugin is able to deal with"
+msgstr ""
+"Hostadresse. Üblicherweise eine IPv4 Adresse, kann jedoch jede Art von "
+"Adresse sein, mit der das Plugin umgehen kann"
+
+#: configuration.php:21
+msgid "Host configs"
+msgstr "Host-Konfigurationen"
+
+#: application/forms/SyncRuleForm.php:13
+msgid "Host group"
+msgstr "Hostgruppe"
+
+#: library/Director/DataType/DataTypeDirectorObject.php:51
+msgid "Host groups"
+msgstr "Hostgruppen"
+
+#: library/Director/Dashboard/Dashlet/HostObjectDashlet.php:13
+msgid "Host objects"
+msgstr "Hostobjekte"
+
+#: application/controllers/SuggestController.php:114
+#: application/controllers/SuggestController.php:126
+#: library/Director/Objects/IcingaHost.php:137
+#: library/Director/Objects/IcingaService.php:469
+msgid "Host properties"
+msgstr "Hosteigenschaften"
+
+#: application/forms/IcingaHostGroupForm.php:15
+#: application/tables/IcingaHostGroupTable.php:32
+msgid "Hostgroup"
+msgstr "Hostgruppe"
+
+#: library/Director/Import/ImportSourceCoreApi.php:64
+msgid "Hostgroups"
+msgstr "Hostgruppen"
+
+#: application/forms/IcingaHostForm.php:145
+msgid ""
+"Hostgroups that should be directly assigned to this node. Hostgroups can be "
+"useful for various reasons. You might assign service checks based on "
+"assigned hostgroup. They are also often used as an instrument to enforce "
+"restricted views in Icinga Web 2. Hostgroups can be directly assigned to "
+"single hosts or to host templates. You might also want to consider assigning "
+"hostgroups using apply rules"
+msgstr ""
+"Hostgruppen, die diesem Knoten direkt zugeordnet werden sollen. Hostgruppen "
+"für verschiedene Zwecke verwendet werden. Services können anhand von "
+"Hostgruppen zugeordnet werden. Außerdem werden sie oft zum Umsetzen von "
+"eingeschränkten Ansichten in Icinga Web 2 verwendet. Hostgruppen können "
+"direkt einzelnen Hosts oder Hostvorlagen zugeordnet werden. Auch über Apply "
+"Regeln können Hostgruppen zugewiesen werden."
+
+#: application/forms/IcingaHostForm.php:18
+#: application/tables/IcingaServiceSetHostTable.php:44
+#: application/tables/IcingaHostTable.php:54
+msgid "Hostname"
+msgstr "Hostname"
+
+#: configuration.php:50 application/forms/IcingaServiceForm.php:428
+#: application/forms/IcingaNotificationForm.php:71
+#: library/Director/DataType/DataTypeDirectorObject.php:50
+#: library/Director/Import/ImportSourceCoreApi.php:63
+#: library/Director/IcingaConfig/StateFilterSet.php:19
+msgid "Hosts"
+msgstr "Hosts"
+
+#: application/controllers/ServicesetController.php:88
+#, php-format
+msgid "Hosts using this set: %s"
+msgstr "Hosts welche dieses Set benutzen: %s"
+
+#: application/forms/IcingaEndpointForm.php:32
+msgid "IP address / hostname of remote node"
+msgstr "IP Adresse / Hostname des entfernten Knoten"
+
+#: application/forms/KickstartForm.php:104
+msgid ""
+"IP address / hostname of your Icinga node. Please note that this information "
+"will only be used for the very first connection to your Icinga instance. The "
+"Director then relies on a correctly configured Endpoint object. Correctly "
+"configures means that either it's name is resolvable or that it's host "
+"property contains either an IP address or a resolvable host name. Your "
+"Director must be able to reach this endpoint"
+msgstr ""
+"IP Adresse / Hostname des Icinga Knotens. Diese Information wird nur für den "
+"ersten Verbindungsaufbau zur Icinga Instanz verwendet. Danach verlässt sich "
+"der Director auf ein richtig konfiguriertes Endpunkt-Objekt. \"Richtig "
+"konfiguriert\" bedeutet dabei, dass entweder der Name auflösbar ist oder die "
+"host-Eigenschaft entweder eine IP Adresse oder einen auflösbaren Hostname "
+"enthält. Der Director muss diesen Endpunkt erreichen können"
+
+#: application/forms/IcingaHostForm.php:174
+msgid "IPv6 address"
+msgstr "IPv6 Adresse"
+
+#: library/Director/Web/Controller/ObjectsController.php:132
+#: library/Director/Web/Controller/ObjectsController.php:136
+#: library/Director/Web/Controller/ObjectsController.php:225
+msgid "Icinga "
+msgstr "Icinga "
+
+#: library/Director/Web/Controller/NewObjectsController.php:171
+#, php-format
+msgid "Icinga %s"
+msgstr "Icinga %s"
+
+#: library/Director/Web/Controller/NewObjectsController.php:114
+#, php-format
+msgid "Icinga %s Sets"
+msgstr "Icinga %s-Sets"
+
+#: library/Director/Web/Controller/NewObjectsController.php:161
+#, php-format
+msgid "Icinga %s Temp