summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:43:12 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 12:43:12 +0000
commitcd989f9c3aff968e19a3aeabc4eb9085787a6673 (patch)
treefbff2135e7013f196b891bbde54618eb050e4aaf
parentInitial commit. (diff)
downloadicingaweb2-module-director-upstream.tar.xz
icingaweb2-module-director-upstream.zip
Adding upstream version 1.10.2.upstream/1.10.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.github/ISSUE_TEMPLATE.md24
-rw-r--r--.github/workflows/L10n-update.yml20
-rw-r--r--.gitignore6
-rw-r--r--LICENSE339
-rw-r--r--README.md61
-rw-r--r--application/clicommands/BasketCommand.php127
-rw-r--r--application/clicommands/BenchmarkCommand.php152
-rw-r--r--application/clicommands/CommandCommand.php15
-rw-r--r--application/clicommands/CommandsCommand.php14
-rw-r--r--application/clicommands/ConfigCommand.php178
-rw-r--r--application/clicommands/CoreCommand.php16
-rw-r--r--application/clicommands/DaemonCommand.php26
-rw-r--r--application/clicommands/DependencyCommand.php15
-rw-r--r--application/clicommands/EndpointCommand.php19
-rw-r--r--application/clicommands/ExportCommand.php180
-rw-r--r--application/clicommands/HealthCommand.php80
-rw-r--r--application/clicommands/HostCommand.php15
-rw-r--r--application/clicommands/HostgroupCommand.php15
-rw-r--r--application/clicommands/HostgroupsCommand.php14
-rw-r--r--application/clicommands/HostsCommand.php14
-rw-r--r--application/clicommands/HousekeepingCommand.php74
-rw-r--r--application/clicommands/ImportCommand.php62
-rw-r--r--application/clicommands/ImportsourceCommand.php168
-rw-r--r--application/clicommands/JobsCommand.php74
-rw-r--r--application/clicommands/KickstartCommand.php88
-rw-r--r--application/clicommands/MigrationCommand.php66
-rw-r--r--application/clicommands/NotificationCommand.php15
-rw-r--r--application/clicommands/ServiceCommand.php92
-rw-r--r--application/clicommands/ServicegroupCommand.php15
-rw-r--r--application/clicommands/ServicesetCommand.php14
-rw-r--r--application/clicommands/ServicesetsCommand.php15
-rw-r--r--application/clicommands/SyncruleCommand.php195
-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/BasketController.php416
-rw-r--r--application/controllers/BasketsController.php53
-rw-r--r--application/controllers/BranchController.php138
-rw-r--r--application/controllers/CommandController.php126
-rw-r--r--application/controllers/CommandsController.php20
-rw-r--r--application/controllers/CommandtemplateController.php16
-rw-r--r--application/controllers/ConfigController.php539
-rw-r--r--application/controllers/CustomvarController.php17
-rw-r--r--application/controllers/DaemonController.php64
-rw-r--r--application/controllers/DashboardController.php78
-rw-r--r--application/controllers/DataController.php406
-rw-r--r--application/controllers/DatafieldController.php40
-rw-r--r--application/controllers/DatafieldcategoryController.php46
-rw-r--r--application/controllers/DependenciesController.php15
-rw-r--r--application/controllers/DependencyController.php63
-rw-r--r--application/controllers/DependencytemplateController.php16
-rw-r--r--application/controllers/DeploymentController.php28
-rw-r--r--application/controllers/EndpointController.php9
-rw-r--r--application/controllers/EndpointsController.php9
-rw-r--r--application/controllers/HealthController.php31
-rw-r--r--application/controllers/HostController.php637
-rw-r--r--application/controllers/HostgroupController.php9
-rw-r--r--application/controllers/HostgroupsController.php9
-rw-r--r--application/controllers/HostsController.php138
-rw-r--r--application/controllers/HosttemplateController.php16
-rw-r--r--application/controllers/ImportrunController.php24
-rw-r--r--application/controllers/ImportsourceController.php375
-rw-r--r--application/controllers/ImportsourcesController.php57
-rw-r--r--application/controllers/IndexController.php79
-rw-r--r--application/controllers/InspectController.php200
-rw-r--r--application/controllers/JobController.php117
-rw-r--r--application/controllers/JobsController.php20
-rw-r--r--application/controllers/KickstartController.php30
-rw-r--r--application/controllers/NotificationController.php85
-rw-r--r--application/controllers/NotificationsController.php31
-rw-r--r--application/controllers/NotificationtemplateController.php16
-rw-r--r--application/controllers/PhperrorController.php43
-rw-r--r--application/controllers/ScheduledDowntimeController.php45
-rw-r--r--application/controllers/ScheduledDowntimesController.php47
-rw-r--r--application/controllers/SchemaController.php113
-rw-r--r--application/controllers/SelfServiceController.php435
-rw-r--r--application/controllers/ServiceController.php311
-rw-r--r--application/controllers/ServiceapplyrulesController.php39
-rw-r--r--application/controllers/ServicegroupController.php9
-rw-r--r--application/controllers/ServicegroupsController.php9
-rw-r--r--application/controllers/ServicesController.php42
-rw-r--r--application/controllers/ServicesetController.php141
-rw-r--r--application/controllers/ServicetemplateController.php16
-rw-r--r--application/controllers/SettingsController.php48
-rw-r--r--application/controllers/SuggestController.php415
-rw-r--r--application/controllers/SyncruleController.php696
-rw-r--r--application/controllers/SyncrulesController.php45
-rw-r--r--application/controllers/TemplatechoiceController.php41
-rw-r--r--application/controllers/TemplatechoicesController.php39
-rw-r--r--application/controllers/TimeperiodController.php33
-rw-r--r--application/controllers/TimeperiodsController.php9
-rw-r--r--application/controllers/TimeperiodtemplateController.php16
-rw-r--r--application/controllers/UserController.php18
-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/UsertemplateController.php16
-rw-r--r--application/controllers/ZoneController.php9
-rw-r--r--application/controllers/ZonesController.php9
-rw-r--r--application/forms/AddToBasketForm.php129
-rw-r--r--application/forms/ApplyMigrationsForm.php54
-rw-r--r--application/forms/BasketCreateSnapshotForm.php37
-rw-r--r--application/forms/BasketForm.php147
-rw-r--r--application/forms/BasketUploadForm.php147
-rw-r--r--application/forms/CustomvarForm.php26
-rw-r--r--application/forms/DeployConfigForm.php121
-rw-r--r--application/forms/DeployFormsBug7530.php126
-rw-r--r--application/forms/DeploymentLinkForm.php170
-rw-r--r--application/forms/DirectorDatafieldCategoryForm.php36
-rw-r--r--application/forms/DirectorDatafieldForm.php301
-rw-r--r--application/forms/DirectorDatalistEntryForm.php80
-rw-r--r--application/forms/DirectorDatalistForm.php45
-rw-r--r--application/forms/DirectorJobForm.php141
-rw-r--r--application/forms/IcingaAddServiceForm.php183
-rw-r--r--application/forms/IcingaAddServiceSetForm.php123
-rw-r--r--application/forms/IcingaApiUserForm.php25
-rw-r--r--application/forms/IcingaCloneObjectForm.php259
-rw-r--r--application/forms/IcingaCommandArgumentForm.php190
-rw-r--r--application/forms/IcingaCommandForm.php137
-rw-r--r--application/forms/IcingaDeleteObjectForm.php41
-rw-r--r--application/forms/IcingaDependencyForm.php309
-rw-r--r--application/forms/IcingaEndpointForm.php61
-rw-r--r--application/forms/IcingaForgetApiKeyForm.php34
-rw-r--r--application/forms/IcingaGenerateApiKeyForm.php42
-rw-r--r--application/forms/IcingaHostForm.php390
-rw-r--r--application/forms/IcingaHostGroupForm.php40
-rw-r--r--application/forms/IcingaHostSelfServiceForm.php156
-rw-r--r--application/forms/IcingaHostVarForm.php36
-rw-r--r--application/forms/IcingaImportObjectForm.php54
-rw-r--r--application/forms/IcingaMultiEditForm.php324
-rw-r--r--application/forms/IcingaNotificationForm.php298
-rw-r--r--application/forms/IcingaObjectFieldForm.php219
-rw-r--r--application/forms/IcingaScheduledDowntimeForm.php133
-rw-r--r--application/forms/IcingaScheduledDowntimeRangeForm.php110
-rw-r--r--application/forms/IcingaServiceDictionaryMemberForm.php54
-rw-r--r--application/forms/IcingaServiceForm.php806
-rw-r--r--application/forms/IcingaServiceGroupForm.php40
-rw-r--r--application/forms/IcingaServiceSetForm.php135
-rw-r--r--application/forms/IcingaServiceVarForm.php36
-rw-r--r--application/forms/IcingaTemplateChoiceForm.php140
-rw-r--r--application/forms/IcingaTimePeriodForm.php82
-rw-r--r--application/forms/IcingaTimePeriodRangeForm.php105
-rw-r--r--application/forms/IcingaUserForm.php214
-rw-r--r--application/forms/IcingaUserGroupForm.php47
-rw-r--r--application/forms/IcingaZoneForm.php43
-rw-r--r--application/forms/ImportCheckForm.php50
-rw-r--r--application/forms/ImportRowModifierForm.php182
-rw-r--r--application/forms/ImportRunForm.php50
-rw-r--r--application/forms/ImportSourceForm.php163
-rw-r--r--application/forms/KickstartForm.php482
-rw-r--r--application/forms/RemoveLinkForm.php59
-rw-r--r--application/forms/RestoreBasketForm.php77
-rw-r--r--application/forms/RestoreObjectForm.php92
-rw-r--r--application/forms/SelfServiceSettingsForm.php306
-rw-r--r--application/forms/SettingsForm.php238
-rw-r--r--application/forms/SyncCheckForm.php69
-rw-r--r--application/forms/SyncPropertyForm.php444
-rw-r--r--application/forms/SyncRuleForm.php112
-rw-r--r--application/forms/SyncRunForm.php67
-rw-r--r--application/locale/de_DE/LC_MESSAGES/director.mobin0 -> 170714 bytes
-rw-r--r--application/locale/de_DE/LC_MESSAGES/director.po8230
-rw-r--r--application/locale/it_IT/LC_MESSAGES/director.mobin0 -> 148601 bytes
-rw-r--r--application/locale/it_IT/LC_MESSAGES/director.po7431
-rw-r--r--application/locale/ja_JP/LC_MESSAGES/director.mobin0 -> 154051 bytes
-rw-r--r--application/locale/ja_JP/LC_MESSAGES/director.po6186
-rw-r--r--application/locale/translateMe.php12
-rw-r--r--application/views/helpers/FormDataFilter.php564
-rw-r--r--application/views/helpers/FormIplExtensibleSet.php23
-rw-r--r--application/views/helpers/FormSimpleNote.php15
-rw-r--r--application/views/helpers/FormStoredPassword.php60
-rw-r--r--application/views/helpers/RenderPlainObject.php14
-rw-r--r--application/views/scripts/phperror/dependencies.phtml9
-rw-r--r--application/views/scripts/phperror/error.phtml8
-rw-r--r--application/views/scripts/settings/index.phtml7
-rw-r--r--application/views/scripts/suggest/index.phtml3
-rw-r--r--configuration.php181
-rwxr-xr-xcontrib/docker-test.sh51
-rw-r--r--contrib/linux-agent-installer/Icinga2Agent.bash318
-rw-r--r--contrib/systemd/icinga-director.service21
-rw-r--r--contrib/windows-agent-installer/Icinga2Agent.psm13402
-rw-r--r--doc/01-Introduction.md51
-rw-r--r--doc/02-Installation.md75
-rw-r--r--doc/02-Installation.md.d/From-Source.md83
-rw-r--r--doc/03-Automation.md134
-rw-r--r--doc/04-Getting-started.md60
-rw-r--r--doc/05-Upgrading.md230
-rw-r--r--doc/10-How-it-works.md112
-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/30-Configuration-Baskets.md92
-rw-r--r--doc/60-CLI.md719
-rw-r--r--doc/70-Import-and-Sync.md88
-rw-r--r--doc/70-REST-API.md684
-rw-r--r--doc/74-Self-Service-API.md49
-rw-r--r--doc/75-Background-Daemon.md65
-rw-r--r--doc/79-Jobs.md40
-rw-r--r--doc/80-FAQ.md75
-rw-r--r--doc/82-Changelog.md1202
-rw-r--r--doc/91-Want-more.md17
-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 -> 46752 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/74_self-service-api/7401-director_self-service-dashboard.pngbin0 -> 55190 bytes
-rw-r--r--doc/screenshot/director/74_self-service-api/7402-director_self-service-choose-source.pngbin0 -> 39570 bytes
-rw-r--r--doc/screenshot/director/74_self-service-api/7403-director_self-service-settings.pngbin0 -> 63224 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 -> 290202 bytes
-rw-r--r--library/Director/Acl.php90
-rw-r--r--library/Director/Application/Dependency.php113
-rw-r--r--library/Director/Application/DependencyChecker.php73
-rw-r--r--library/Director/Application/MemoryLimit.php53
-rw-r--r--library/Director/CheckPlugin/Check.php59
-rw-r--r--library/Director/CheckPlugin/CheckResult.php31
-rw-r--r--library/Director/CheckPlugin/CheckResults.php150
-rw-r--r--library/Director/CheckPlugin/PluginState.php114
-rw-r--r--library/Director/CheckPlugin/Range.php101
-rw-r--r--library/Director/CheckPlugin/Threshold.php47
-rw-r--r--library/Director/Cli/Command.php115
-rw-r--r--library/Director/Cli/ObjectCommand.php517
-rw-r--r--library/Director/Cli/ObjectsCommand.php115
-rw-r--r--library/Director/Cli/PluginOutputBeautifier.php75
-rw-r--r--library/Director/ConfigDiff.php47
-rw-r--r--library/Director/Core/CoreApi.php940
-rw-r--r--library/Director/Core/DeploymentApiInterface.php75
-rw-r--r--library/Director/Core/Json.php34
-rw-r--r--library/Director/Core/LegacyDeploymentApi.php466
-rw-r--r--library/Director/Core/RestApiClient.php276
-rw-r--r--library/Director/Core/RestApiResponse.php149
-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.php286
-rw-r--r--library/Director/CustomVariable/CustomVariableArray.php100
-rw-r--r--library/Director/CustomVariable/CustomVariableBoolean.php53
-rw-r--r--library/Director/CustomVariable/CustomVariableDictionary.php130
-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.php488
-rw-r--r--library/Director/Daemon/BackgroundDaemon.php235
-rw-r--r--library/Director/Daemon/DaemonDb.php365
-rw-r--r--library/Director/Daemon/DaemonProcessDetails.php122
-rw-r--r--library/Director/Daemon/DaemonProcessState.php85
-rw-r--r--library/Director/Daemon/DaemonUtil.php16
-rw-r--r--library/Director/Daemon/DbBasedComponent.php19
-rw-r--r--library/Director/Daemon/DeploymentChecker.php51
-rw-r--r--library/Director/Daemon/JobRunner.php234
-rw-r--r--library/Director/Daemon/JsonRpcLogWriter.php37
-rw-r--r--library/Director/Daemon/LogProxy.php76
-rw-r--r--library/Director/Daemon/Logger.php24
-rw-r--r--library/Director/Daemon/ProcessList.php125
-rw-r--r--library/Director/Daemon/RunningDaemonInfo.php154
-rw-r--r--library/Director/Daemon/SystemdLogWriter.php27
-rw-r--r--library/Director/Dashboard/AlertsDashboard.php19
-rw-r--r--library/Director/Dashboard/AutomationDashboard.php17
-rw-r--r--library/Director/Dashboard/BranchesDashboard.php36
-rw-r--r--library/Director/Dashboard/CommandsDashboard.php35
-rw-r--r--library/Director/Dashboard/Dashboard.php305
-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/BasketDashlet.php30
-rw-r--r--library/Director/Dashboard/Dashlet/CheckCommandsDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/ChoicesDashlet.php41
-rw-r--r--library/Director/Dashboard/Dashlet/CommandObjectDashlet.php25
-rw-r--r--library/Director/Dashboard/Dashlet/CommandTemplatesDashlet.php26
-rw-r--r--library/Director/Dashboard/Dashlet/CustomvarDashlet.php30
-rw-r--r--library/Director/Dashboard/Dashlet/Dashlet.php239
-rw-r--r--library/Director/Dashboard/Dashlet/DatafieldCategoryDashlet.php30
-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/DependencyObjectDashlet.php26
-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/ExternalCheckCommandsDashlet.php26
-rw-r--r--library/Director/Dashboard/Dashlet/ExternalNotificationCommandsDashlet.php21
-rw-r--r--library/Director/Dashboard/Dashlet/HostChoicesDashlet.php7
-rw-r--r--library/Director/Dashboard/Dashlet/HostGroupsDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/HostObjectDashlet.php25
-rw-r--r--library/Director/Dashboard/Dashlet/HostTemplatesDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/HostsDashlet.php32
-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.php37
-rw-r--r--library/Director/Dashboard/Dashlet/NotificationCommandsDashlet.php21
-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/ScheduledDowntimeApplyDashlet.php25
-rw-r--r--library/Director/Dashboard/Dashlet/SelfServiceDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceApplyRulesDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceChoicesDashlet.php7
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceGroupsDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php34
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceTemplatesDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/SettingsDashlet.php30
-rw-r--r--library/Director/Dashboard/Dashlet/SingleServicesDashlet.php31
-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/TimeperiodTemplateDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/TimeperiodsDashlet.php25
-rw-r--r--library/Director/Dashboard/Dashlet/UserGroupsDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/UserObjectDashlet.php23
-rw-r--r--library/Director/Dashboard/Dashlet/UserTemplateDashlet.php31
-rw-r--r--library/Director/Dashboard/Dashlet/UsersDashlet.php25
-rw-r--r--library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php25
-rw-r--r--library/Director/Dashboard/DataDashboard.php18
-rw-r--r--library/Director/Dashboard/DeploymentDashboard.php17
-rw-r--r--library/Director/Dashboard/DirectorDashboard.php17
-rw-r--r--library/Director/Dashboard/HostsDashboard.php35
-rw-r--r--library/Director/Dashboard/InfrastructureDashboard.php60
-rw-r--r--library/Director/Dashboard/NotificationsDashboard.php44
-rw-r--r--library/Director/Dashboard/ObjectsDashboard.php17
-rw-r--r--library/Director/Dashboard/ServicesDashboard.php39
-rw-r--r--library/Director/Dashboard/TimeperiodsDashboard.php33
-rw-r--r--library/Director/Dashboard/UsersDashboard.php36
-rw-r--r--library/Director/Data/AssignFilterHelper.php160
-rw-r--r--library/Director/Data/DataArrayHelper.php48
-rw-r--r--library/Director/Data/Db/DbConnection.php51
-rw-r--r--library/Director/Data/Db/DbDataFormatter.php26
-rw-r--r--library/Director/Data/Db/DbObject.php1487
-rw-r--r--library/Director/Data/Db/DbObjectStore.php169
-rw-r--r--library/Director/Data/Db/DbObjectTypeRegistry.php75
-rw-r--r--library/Director/Data/Db/DbObjectWithSettings.php168
-rw-r--r--library/Director/Data/Db/IcingaObjectFilterRenderer.php133
-rw-r--r--library/Director/Data/Db/IcingaObjectQuery.php255
-rw-r--r--library/Director/Data/Db/ServiceSetQueryBuilder.php158
-rw-r--r--library/Director/Data/Exporter.php303
-rw-r--r--library/Director/Data/FieldReferenceLoader.php51
-rw-r--r--library/Director/Data/HostServiceLoader.php170
-rw-r--r--library/Director/Data/ImportExportDeniedProperties.php52
-rw-r--r--library/Director/Data/InvalidDataException.php26
-rw-r--r--library/Director/Data/Json.php69
-rw-r--r--library/Director/Data/PropertiesFilter.php25
-rw-r--r--library/Director/Data/PropertiesFilter/ArrayCustomVariablesFilter.php20
-rw-r--r--library/Director/Data/PropertiesFilter/CustomVariablesFilter.php13
-rw-r--r--library/Director/Data/PropertyMangler.php60
-rw-r--r--library/Director/Data/RecursiveUtf8Validator.php59
-rw-r--r--library/Director/Data/Serializable.php10
-rw-r--r--library/Director/Data/SerializableValue.php90
-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.php159
-rw-r--r--library/Director/DataType/DataTypeDictionary.php107
-rw-r--r--library/Director/DataType/DataTypeDirectorObject.php87
-rw-r--r--library/Director/DataType/DataTypeNumber.php19
-rw-r--r--library/Director/DataType/DataTypeSqlQuery.php96
-rw-r--r--library/Director/DataType/DataTypeString.php35
-rw-r--r--library/Director/DataType/DataTypeTime.php16
-rw-r--r--library/Director/Db.php755
-rw-r--r--library/Director/Db/AppliedServiceSetLoader.php58
-rw-r--r--library/Director/Db/Branch/Branch.php216
-rw-r--r--library/Director/Db/Branch/BranchActivity.php390
-rw-r--r--library/Director/Db/Branch/BranchMerger.php157
-rw-r--r--library/Director/Db/Branch/BranchModificationInspection.php93
-rw-r--r--library/Director/Db/Branch/BranchSettings.php121
-rw-r--r--library/Director/Db/Branch/BranchStore.php240
-rw-r--r--library/Director/Db/Branch/BranchSupport.php91
-rw-r--r--library/Director/Db/Branch/BranchedObject.php404
-rw-r--r--library/Director/Db/Branch/MergeError.php37
-rw-r--r--library/Director/Db/Branch/MergeErrorDeleteMissingObject.php15
-rw-r--r--library/Director/Db/Branch/MergeErrorModificationForMissingObject.php15
-rw-r--r--library/Director/Db/Branch/MergeErrorRecreateOnMerge.php15
-rw-r--r--library/Director/Db/Branch/PlainObjectPropertyDiff.php50
-rw-r--r--library/Director/Db/Branch/UuidLookup.php141
-rw-r--r--library/Director/Db/Cache/CustomVariableCache.php84
-rw-r--r--library/Director/Db/Cache/GroupMembershipCache.php104
-rw-r--r--library/Director/Db/Cache/PrefetchCache.php166
-rw-r--r--library/Director/Db/DbSelectParenthesis.php24
-rw-r--r--library/Director/Db/DbUtil.php96
-rw-r--r--library/Director/Db/HostMembershipHousekeeping.php8
-rw-r--r--library/Director/Db/Housekeeping.php249
-rw-r--r--library/Director/Db/IcingaObjectFilterHelper.php133
-rw-r--r--library/Director/Db/MembershipHousekeeping.php135
-rw-r--r--library/Director/Db/Migration.php70
-rw-r--r--library/Director/Db/Migrations.php239
-rw-r--r--library/Director/Deployment/ConditionalConfigRenderer.php64
-rw-r--r--library/Director/Deployment/ConditionalDeployment.php190
-rw-r--r--library/Director/Deployment/DeploymentGracePeriod.php61
-rw-r--r--library/Director/Deployment/DeploymentInfo.php59
-rw-r--r--library/Director/Deployment/DeploymentStatus.php164
-rw-r--r--library/Director/DirectorObject/Automation/Basket.php232
-rw-r--r--library/Director/DirectorObject/Automation/BasketContent.php24
-rw-r--r--library/Director/DirectorObject/Automation/BasketSnapshot.php531
-rw-r--r--library/Director/DirectorObject/Automation/BasketSnapshotFieldResolver.php226
-rw-r--r--library/Director/DirectorObject/Automation/CompareBasketObject.php146
-rw-r--r--library/Director/DirectorObject/Automation/ExportInterface.php20
-rw-r--r--library/Director/DirectorObject/Automation/ImportExport.php149
-rw-r--r--library/Director/DirectorObject/Lookup/AppliedServiceInfo.php109
-rw-r--r--library/Director/DirectorObject/Lookup/AppliedServiceSetServiceInfo.php127
-rw-r--r--library/Director/DirectorObject/Lookup/InheritedServiceInfo.php94
-rw-r--r--library/Director/DirectorObject/Lookup/ServiceFinder.php79
-rw-r--r--library/Director/DirectorObject/Lookup/ServiceInfo.php46
-rw-r--r--library/Director/DirectorObject/Lookup/ServiceSetServiceInfo.php121
-rw-r--r--library/Director/DirectorObject/Lookup/SingleServiceInfo.php83
-rw-r--r--library/Director/DirectorObject/ObjectPurgeHelper.php149
-rw-r--r--library/Director/Exception/DuplicateKeyException.php9
-rw-r--r--library/Director/Exception/JsonEncodeException.php7
-rw-r--r--library/Director/Exception/JsonException.php55
-rw-r--r--library/Director/Exception/NestingError.php9
-rw-r--r--library/Director/Field/FieldSpec.php206
-rw-r--r--library/Director/Health.php285
-rw-r--r--library/Director/Hook/BranchSupportHook.php39
-rw-r--r--library/Director/Hook/DataTypeHook.php59
-rw-r--r--library/Director/Hook/DeploymentHook.php65
-rw-r--r--library/Director/Hook/HostFieldHook.php19
-rw-r--r--library/Director/Hook/IcingaObjectFormHook.php22
-rw-r--r--library/Director/Hook/ImportSourceHook.php136
-rw-r--r--library/Director/Hook/JobHook.php86
-rw-r--r--library/Director/Hook/PropertyModifierHook.php258
-rw-r--r--library/Director/Hook/ServiceFieldHook.php19
-rw-r--r--library/Director/Hook/ShipConfigFilesHook.php11
-rw-r--r--library/Director/IcingaConfig/AgentWizard.php337
-rw-r--r--library/Director/IcingaConfig/AssignRenderer.php268
-rw-r--r--library/Director/IcingaConfig/ExtensibleSet.php574
-rw-r--r--library/Director/IcingaConfig/IcingaConfig.php781
-rw-r--r--library/Director/IcingaConfig/IcingaConfigFile.php168
-rw-r--r--library/Director/IcingaConfig/IcingaConfigHelper.php430
-rw-r--r--library/Director/IcingaConfig/IcingaConfigRendered.php34
-rw-r--r--library/Director/IcingaConfig/IcingaConfigRenderer.php10
-rw-r--r--library/Director/IcingaConfig/IcingaLegacyConfigHelper.php110
-rw-r--r--library/Director/IcingaConfig/StateFilterSet.php31
-rw-r--r--library/Director/IcingaConfig/TypeFilterSet.php39
-rw-r--r--library/Director/Import/Import.php481
-rw-r--r--library/Director/Import/ImportSourceCoreApi.php92
-rw-r--r--library/Director/Import/ImportSourceDirectorObject.php120
-rw-r--r--library/Director/Import/ImportSourceLdap.php90
-rw-r--r--library/Director/Import/ImportSourceRestApi.php380
-rw-r--r--library/Director/Import/ImportSourceSql.php70
-rw-r--r--library/Director/Import/PurgeStrategy/ImportRunBasedPurgeStrategy.php90
-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.php942
-rw-r--r--library/Director/Import/SyncUtils.php153
-rw-r--r--library/Director/Job/ConfigJob.php75
-rw-r--r--library/Director/Job/HousekeepingJob.php39
-rw-r--r--library/Director/Job/ImportJob.php122
-rw-r--r--library/Director/Job/SyncJob.php128
-rw-r--r--library/Director/KickstartHelper.php555
-rw-r--r--library/Director/Monitoring.php149
-rw-r--r--library/Director/Objects/DirectorActivityLog.php232
-rw-r--r--library/Director/Objects/DirectorDatafield.php344
-rw-r--r--library/Director/Objects/DirectorDatafieldCategory.php64
-rw-r--r--library/Director/Objects/DirectorDatalist.php225
-rw-r--r--library/Director/Objects/DirectorDatalistEntry.php112
-rw-r--r--library/Director/Objects/DirectorDeploymentLog.php199
-rw-r--r--library/Director/Objects/DirectorJob.php314
-rw-r--r--library/Director/Objects/DynamicApplyMatches.php14
-rw-r--r--library/Director/Objects/Extension/Arguments.php61
-rw-r--r--library/Director/Objects/Extension/FlappingSupport.php54
-rw-r--r--library/Director/Objects/Extension/PriorityColumn.php40
-rw-r--r--library/Director/Objects/GroupMembershipResolver.php689
-rw-r--r--library/Director/Objects/HostApplyMatches.php8
-rw-r--r--library/Director/Objects/HostGroupMembershipResolver.php8
-rw-r--r--library/Director/Objects/IcingaApiUser.php31
-rw-r--r--library/Director/Objects/IcingaArguments.php442
-rw-r--r--library/Director/Objects/IcingaCommand.php365
-rw-r--r--library/Director/Objects/IcingaCommandArgument.php263
-rw-r--r--library/Director/Objects/IcingaCommandField.php17
-rw-r--r--library/Director/Objects/IcingaDependency.php631
-rw-r--r--library/Director/Objects/IcingaEndpoint.php157
-rw-r--r--library/Director/Objects/IcingaFlatVar.php61
-rw-r--r--library/Director/Objects/IcingaHost.php668
-rw-r--r--library/Director/Objects/IcingaHostField.php17
-rw-r--r--library/Director/Objects/IcingaHostGroup.php42
-rw-r--r--library/Director/Objects/IcingaHostGroupAssignment.php20
-rw-r--r--library/Director/Objects/IcingaHostVar.php29
-rw-r--r--library/Director/Objects/IcingaNotification.php254
-rw-r--r--library/Director/Objects/IcingaNotificationField.php17
-rw-r--r--library/Director/Objects/IcingaObject.php3258
-rw-r--r--library/Director/Objects/IcingaObjectField.php26
-rw-r--r--library/Director/Objects/IcingaObjectGroup.php76
-rw-r--r--library/Director/Objects/IcingaObjectGroups.php408
-rw-r--r--library/Director/Objects/IcingaObjectImports.php439
-rw-r--r--library/Director/Objects/IcingaObjectLegacyAssignments.php79
-rw-r--r--library/Director/Objects/IcingaObjectMultiRelations.php454
-rw-r--r--library/Director/Objects/IcingaRanges.php321
-rw-r--r--library/Director/Objects/IcingaRelatedObject.php211
-rw-r--r--library/Director/Objects/IcingaScheduledDowntime.php135
-rw-r--r--library/Director/Objects/IcingaScheduledDowntimeRange.php88
-rw-r--r--library/Director/Objects/IcingaScheduledDowntimeRanges.php18
-rw-r--r--library/Director/Objects/IcingaService.php828
-rw-r--r--library/Director/Objects/IcingaServiceAssignment.php20
-rw-r--r--library/Director/Objects/IcingaServiceField.php17
-rw-r--r--library/Director/Objects/IcingaServiceGroup.php42
-rw-r--r--library/Director/Objects/IcingaServiceSet.php591
-rw-r--r--library/Director/Objects/IcingaServiceSetAssignment.php20
-rw-r--r--library/Director/Objects/IcingaServiceVar.php29
-rw-r--r--library/Director/Objects/IcingaTemplateChoice.php321
-rw-r--r--library/Director/Objects/IcingaTemplateChoiceHost.php14
-rw-r--r--library/Director/Objects/IcingaTemplateChoiceService.php14
-rw-r--r--library/Director/Objects/IcingaTemplateResolver.php479
-rw-r--r--library/Director/Objects/IcingaTimePeriod.php190
-rw-r--r--library/Director/Objects/IcingaTimePeriodRange.php88
-rw-r--r--library/Director/Objects/IcingaTimePeriodRanges.php35
-rw-r--r--library/Director/Objects/IcingaUser.php92
-rw-r--r--library/Director/Objects/IcingaUserField.php17
-rw-r--r--library/Director/Objects/IcingaUserGroup.php29
-rw-r--r--library/Director/Objects/IcingaVar.php72
-rw-r--r--library/Director/Objects/IcingaZone.php110
-rw-r--r--library/Director/Objects/ImportExportHelper.php68
-rw-r--r--library/Director/Objects/ImportRowModifier.php91
-rw-r--r--library/Director/Objects/ImportRun.php159
-rw-r--r--library/Director/Objects/ImportSource.php537
-rw-r--r--library/Director/Objects/InstantiatedViaHook.php14
-rw-r--r--library/Director/Objects/ObjectApplyMatches.php239
-rw-r--r--library/Director/Objects/ObjectWithArguments.php18
-rw-r--r--library/Director/Objects/ServiceGroupMembershipResolver.php8
-rw-r--r--library/Director/Objects/SyncProperty.php48
-rw-r--r--library/Director/Objects/SyncRule.php553
-rw-r--r--library/Director/Objects/SyncRun.php46
-rw-r--r--library/Director/PlainObjectRenderer.php130
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayElementByPosition.php172
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayFilter.php152
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayToRow.php68
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayUnique.php37
-rw-r--r--library/Director/PropertyModifier/PropertyModifierBitmask.php39
-rw-r--r--library/Director/PropertyModifier/PropertyModifierCombine.php40
-rw-r--r--library/Director/PropertyModifier/PropertyModifierDictionaryToRow.php94
-rw-r--r--library/Director/PropertyModifier/PropertyModifierDnsRecords.php112
-rw-r--r--library/Director/PropertyModifier/PropertyModifierExtractFromDN.php99
-rw-r--r--library/Director/PropertyModifier/PropertyModifierFromAdSid.php36
-rw-r--r--library/Director/PropertyModifier/PropertyModifierFromLatin1.php23
-rw-r--r--library/Director/PropertyModifier/PropertyModifierGetHostByAddr.php53
-rw-r--r--library/Director/PropertyModifier/PropertyModifierGetHostByName.php54
-rw-r--r--library/Director/PropertyModifier/PropertyModifierGetPropertyFromOtherImportSource.php129
-rw-r--r--library/Director/PropertyModifier/PropertyModifierJoin.php34
-rw-r--r--library/Director/PropertyModifier/PropertyModifierJsonDecode.php68
-rw-r--r--library/Director/PropertyModifier/PropertyModifierLConfCustomVar.php48
-rw-r--r--library/Director/PropertyModifier/PropertyModifierListToObject.php95
-rw-r--r--library/Director/PropertyModifier/PropertyModifierLowercase.php17
-rw-r--r--library/Director/PropertyModifier/PropertyModifierMakeBoolean.php90
-rw-r--r--library/Director/PropertyModifier/PropertyModifierMap.php97
-rw-r--r--library/Director/PropertyModifier/PropertyModifierNegateBoolean.php26
-rw-r--r--library/Director/PropertyModifier/PropertyModifierParseURL.php81
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRegexReplace.php45
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRegexSplit.php51
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php147
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRenameColumn.php34
-rw-r--r--library/Director/PropertyModifier/PropertyModifierReplace.php36
-rw-r--r--library/Director/PropertyModifier/PropertyModifierReplaceNull.php33
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSimpleGroupBy.php68
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSkipDuplicates.php26
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSplit.php51
-rw-r--r--library/Director/PropertyModifier/PropertyModifierStripDomain.php38
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSubstring.php54
-rw-r--r--library/Director/PropertyModifier/PropertyModifierToInt.php31
-rw-r--r--library/Director/PropertyModifier/PropertyModifierTrim.php54
-rw-r--r--library/Director/PropertyModifier/PropertyModifierURLEncode.php19
-rw-r--r--library/Director/PropertyModifier/PropertyModifierUpperCaseFirst.php44
-rw-r--r--library/Director/PropertyModifier/PropertyModifierUppercase.php17
-rw-r--r--library/Director/PropertyModifier/PropertyModifierUuidBinToHex.php19
-rw-r--r--library/Director/PropertyModifier/PropertyModifierXlsNumericIp.php26
-rw-r--r--library/Director/ProvidedHook/CubeLinks.php65
-rw-r--r--library/Director/ProvidedHook/IcingaDbCubeLinks.php66
-rw-r--r--library/Director/ProvidedHook/Monitoring/HostActions.php73
-rw-r--r--library/Director/ProvidedHook/Monitoring/ServiceActions.php87
-rw-r--r--library/Director/Repository/IcingaTemplateRepository.php122
-rw-r--r--library/Director/Repository/RepositoryByObjectHelper.php99
-rw-r--r--library/Director/Resolver/CommandUsage.php104
-rw-r--r--library/Director/Resolver/HostServiceBlacklist.php91
-rw-r--r--library/Director/Resolver/IcingaHostObjectResolver.php44
-rw-r--r--library/Director/Resolver/IcingaObjectResolver.php558
-rw-r--r--library/Director/Resolver/OverriddenVarsResolver.php74
-rw-r--r--library/Director/Resolver/OverrideHelper.php38
-rw-r--r--library/Director/Resolver/TemplateTree.php491
-rw-r--r--library/Director/RestApi/IcingaObjectHandler.php196
-rw-r--r--library/Director/RestApi/IcingaObjectsHandler.php144
-rw-r--r--library/Director/RestApi/RequestHandler.php86
-rw-r--r--library/Director/RestApi/RestApiClient.php311
-rw-r--r--library/Director/RestApi/RestApiParams.php29
-rw-r--r--library/Director/Restriction/FilterByNameRestriction.php64
-rw-r--r--library/Director/Restriction/HostgroupRestriction.php171
-rw-r--r--library/Director/Restriction/MatchingFilter.php40
-rw-r--r--library/Director/Restriction/ObjectRestriction.php84
-rw-r--r--library/Director/Settings.php219
-rw-r--r--library/Director/StartupLogRenderer.php126
-rw-r--r--library/Director/Test/BaseTestCase.php127
-rw-r--r--library/Director/Test/Bootstrap.php28
-rw-r--r--library/Director/Test/IcingaObjectTestCase.php92
-rw-r--r--library/Director/Test/ImportSourceDummy.php52
-rw-r--r--library/Director/Test/SyncTest.php105
-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/TranslationDummy.php20
-rw-r--r--library/Director/Util.php175
-rw-r--r--library/Director/Web/ActionBar/AutomationObjectActionBar.php65
-rw-r--r--library/Director/Web/ActionBar/ChoicesActionBar.php27
-rw-r--r--library/Director/Web/ActionBar/DirectorBaseActionBar.php67
-rw-r--r--library/Director/Web/ActionBar/ObjectsActionBar.php27
-rw-r--r--library/Director/Web/ActionBar/TemplateActionBar.php42
-rw-r--r--library/Director/Web/Controller/ActionController.php253
-rw-r--r--library/Director/Web/Controller/BranchHelper.php76
-rw-r--r--library/Director/Web/Controller/Extension/CoreApi.php46
-rw-r--r--library/Director/Web/Controller/Extension/DirectorDb.php160
-rw-r--r--library/Director/Web/Controller/Extension/ObjectRestrictions.php48
-rw-r--r--library/Director/Web/Controller/Extension/RestApi.php114
-rw-r--r--library/Director/Web/Controller/Extension/SingleObjectApiHandler.php236
-rw-r--r--library/Director/Web/Controller/ObjectController.php733
-rw-r--r--library/Director/Web/Controller/ObjectsController.php548
-rw-r--r--library/Director/Web/Controller/TemplateController.php243
-rw-r--r--library/Director/Web/Form/ClickHereForm.php31
-rw-r--r--library/Director/Web/Form/CloneImportSourceForm.php72
-rw-r--r--library/Director/Web/Form/CloneSyncRuleForm.php76
-rw-r--r--library/Director/Web/Form/CsrfToken.php53
-rw-r--r--library/Director/Web/Form/DbSelectorForm.php85
-rw-r--r--library/Director/Web/Form/Decorator/ViewHelperRaw.php14
-rw-r--r--library/Director/Web/Form/DirectorForm.php58
-rw-r--r--library/Director/Web/Form/DirectorObjectForm.php1734
-rw-r--r--library/Director/Web/Form/Element/Boolean.php90
-rw-r--r--library/Director/Web/Form/Element/DataFilter.php361
-rw-r--r--library/Director/Web/Form/Element/ExtensibleSet.php89
-rw-r--r--library/Director/Web/Form/Element/FormElement.php9
-rw-r--r--library/Director/Web/Form/Element/InstanceSummary.php51
-rw-r--r--library/Director/Web/Form/Element/OptionalYesNo.php22
-rw-r--r--library/Director/Web/Form/Element/SimpleNote.php34
-rw-r--r--library/Director/Web/Form/Element/StoredPassword.php62
-rw-r--r--library/Director/Web/Form/Element/Text.php16
-rw-r--r--library/Director/Web/Form/Element/YesNo.php14
-rw-r--r--library/Director/Web/Form/Filter/QueryColumnsFromSql.php48
-rw-r--r--library/Director/Web/Form/FormLoader.php43
-rw-r--r--library/Director/Web/Form/IcingaObjectFieldLoader.php628
-rw-r--r--library/Director/Web/Form/IconHelper.php89
-rw-r--r--library/Director/Web/Form/IplElement/ExtensibleSetElement.php570
-rw-r--r--library/Director/Web/Form/QuickBaseForm.php177
-rw-r--r--library/Director/Web/Form/QuickForm.php641
-rw-r--r--library/Director/Web/Form/QuickSubForm.php36
-rw-r--r--library/Director/Web/Form/Validate/IsDataListEntry.php55
-rw-r--r--library/Director/Web/Form/Validate/NamePattern.php38
-rw-r--r--library/Director/Web/Navigation/Renderer/ConfigHealthItemRenderer.php196
-rw-r--r--library/Director/Web/ObjectPreview.php182
-rw-r--r--library/Director/Web/SelfService.php311
-rw-r--r--library/Director/Web/Table/ActivityLogTable.php294
-rw-r--r--library/Director/Web/Table/ApplyRulesTable.php240
-rw-r--r--library/Director/Web/Table/BasketSnapshotTable.php125
-rw-r--r--library/Director/Web/Table/BasketTable.php50
-rw-r--r--library/Director/Web/Table/BranchActivityTable.php116
-rw-r--r--library/Director/Web/Table/BranchedIcingaCommandArgumentTable.php78
-rw-r--r--library/Director/Web/Table/ChoicesTable.php65
-rw-r--r--library/Director/Web/Table/ConfigFileDiffTable.php140
-rw-r--r--library/Director/Web/Table/CoreApiFieldsTable.php106
-rw-r--r--library/Director/Web/Table/CoreApiObjectsTable.php60
-rw-r--r--library/Director/Web/Table/CoreApiPrototypesTable.php43
-rw-r--r--library/Director/Web/Table/CustomvarTable.php102
-rw-r--r--library/Director/Web/Table/CustomvarVariantsTable.php125
-rw-r--r--library/Director/Web/Table/DatafieldCategoryTable.php64
-rw-r--r--library/Director/Web/Table/DatafieldTable.php118
-rw-r--r--library/Director/Web/Table/DatalistEntryTable.php73
-rw-r--r--library/Director/Web/Table/DatalistTable.php41
-rw-r--r--library/Director/Web/Table/DbHelper.php67
-rw-r--r--library/Director/Web/Table/Dependency/DependencyInfoTable.php101
-rw-r--r--library/Director/Web/Table/Dependency/Html.php74
-rw-r--r--library/Director/Web/Table/DependencyTemplateUsageTable.php22
-rw-r--r--library/Director/Web/Table/DeploymentLogTable.php90
-rw-r--r--library/Director/Web/Table/FilterableByUsage.php10
-rw-r--r--library/Director/Web/Table/GeneratedConfigFileTable.php120
-rw-r--r--library/Director/Web/Table/GroupMemberTable.php201
-rw-r--r--library/Director/Web/Table/HostTemplateUsageTable.php22
-rw-r--r--library/Director/Web/Table/IcingaAppliedServiceTable.php49
-rw-r--r--library/Director/Web/Table/IcingaCommandArgumentTable.php89
-rw-r--r--library/Director/Web/Table/IcingaHostAppliedForServiceTable.php117
-rw-r--r--library/Director/Web/Table/IcingaHostAppliedServicesTable.php207
-rw-r--r--library/Director/Web/Table/IcingaHostsMatchingFilterTable.php71
-rw-r--r--library/Director/Web/Table/IcingaObjectDatafieldTable.php87
-rw-r--r--library/Director/Web/Table/IcingaScheduledDowntimeRangeTable.php67
-rw-r--r--library/Director/Web/Table/IcingaServiceSetHostTable.php64
-rw-r--r--library/Director/Web/Table/IcingaServiceSetServiceTable.php259
-rw-r--r--library/Director/Web/Table/IcingaTimePeriodRangeTable.php61
-rw-r--r--library/Director/Web/Table/ImportedrowsTable.php103
-rw-r--r--library/Director/Web/Table/ImportrunTable.php90
-rw-r--r--library/Director/Web/Table/ImportsourceHookTable.php107
-rw-r--r--library/Director/Web/Table/ImportsourceTable.php63
-rw-r--r--library/Director/Web/Table/JobTable.php82
-rw-r--r--library/Director/Web/Table/NotificationTemplateUsageTable.php22
-rw-r--r--library/Director/Web/Table/ObjectSetTable.php211
-rw-r--r--library/Director/Web/Table/ObjectsTable.php315
-rw-r--r--library/Director/Web/Table/ObjectsTableApiUser.php13
-rw-r--r--library/Director/Web/Table/ObjectsTableCommand.php67
-rw-r--r--library/Director/Web/Table/ObjectsTableEndpoint.php86
-rw-r--r--library/Director/Web/Table/ObjectsTableHost.php40
-rw-r--r--library/Director/Web/Table/ObjectsTableHostTemplateChoice.php27
-rw-r--r--library/Director/Web/Table/ObjectsTableService.php219
-rw-r--r--library/Director/Web/Table/ObjectsTableZone.php13
-rw-r--r--library/Director/Web/Table/PropertymodifierTable.php145
-rw-r--r--library/Director/Web/Table/QuickTable.php547
-rw-r--r--library/Director/Web/Table/ReadOnlyFormAvpTable.php113
-rw-r--r--library/Director/Web/Table/ServiceTemplateUsageTable.php27
-rw-r--r--library/Director/Web/Table/SyncRunTable.php90
-rw-r--r--library/Director/Web/Table/SyncpropertyTable.php97
-rw-r--r--library/Director/Web/Table/SyncruleTable.php67
-rw-r--r--library/Director/Web/Table/TableLoader.php34
-rw-r--r--library/Director/Web/Table/TableWithBranchSupport.php69
-rw-r--r--library/Director/Web/Table/TemplateUsageTable.php157
-rw-r--r--library/Director/Web/Table/TemplatesTable.php156
-rw-r--r--library/Director/Web/Tabs/DataTabs.php34
-rw-r--r--library/Director/Web/Tabs/ImportTabs.php30
-rw-r--r--library/Director/Web/Tabs/ImportsourceTabs.php58
-rw-r--r--library/Director/Web/Tabs/InfraTabs.php49
-rw-r--r--library/Director/Web/Tabs/MainTabs.php85
-rw-r--r--library/Director/Web/Tabs/ObjectTabs.php160
-rw-r--r--library/Director/Web/Tabs/ObjectsTabs.php85
-rw-r--r--library/Director/Web/Tabs/SyncRuleTabs.php54
-rw-r--r--library/Director/Web/Tree/InspectTreeRenderer.php97
-rw-r--r--library/Director/Web/Tree/TemplateTreeRenderer.php91
-rw-r--r--library/Director/Web/Widget/AbstractList.php40
-rw-r--r--library/Director/Web/Widget/ActivityLogInfo.php634
-rw-r--r--library/Director/Web/Widget/AdditionalTableActions.php158
-rw-r--r--library/Director/Web/Widget/BackgroundDaemonDetails.php131
-rw-r--r--library/Director/Web/Widget/BranchedObjectHint.php69
-rw-r--r--library/Director/Web/Widget/BranchedObjectsHint.php27
-rw-r--r--library/Director/Web/Widget/Daemon/BackgroundDaemonState.php57
-rw-r--r--library/Director/Web/Widget/DeployedConfigInfoHeader.php101
-rw-r--r--library/Director/Web/Widget/DeploymentInfo.php169
-rw-r--r--library/Director/Web/Widget/Documentation.php97
-rw-r--r--library/Director/Web/Widget/HealthCheckPluginOutput.php94
-rw-r--r--library/Director/Web/Widget/IcingaConfigDiff.php58
-rw-r--r--library/Director/Web/Widget/IcingaObjectInspection.php254
-rw-r--r--library/Director/Web/Widget/ImportSourceDetails.php83
-rw-r--r--library/Director/Web/Widget/InspectPackages.php174
-rw-r--r--library/Director/Web/Widget/JobDetails.php69
-rw-r--r--library/Director/Web/Widget/ListItem.php26
-rw-r--r--library/Director/Web/Widget/NotInBranchedHint.php23
-rw-r--r--library/Director/Web/Widget/OrderedList.php8
-rw-r--r--library/Director/Web/Widget/ShowConfigFile.php106
-rw-r--r--library/Director/Web/Widget/SyncRunDetails.php129
-rw-r--r--library/Director/Web/Widget/UnorderedList.php8
-rw-r--r--library/Director/Web/Window.php13
-rw-r--r--module.info6
-rw-r--r--phpcs.xml21
-rw-r--r--phpunit.xml17
-rw-r--r--public/css/module.less1835
-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.js840
-rw-r--r--register-hooks.php146
-rw-r--r--run-missingdeps.php23
-rw-r--r--run-php5.3.php26
-rw-r--r--run.php18
-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_129.sql6
-rw-r--r--schema/mysql-migrations/upgrade_130.sql6
-rw-r--r--schema/mysql-migrations/upgrade_131.sql19
-rw-r--r--schema/mysql-migrations/upgrade_132.sql21
-rw-r--r--schema/mysql-migrations/upgrade_133.sql21
-rw-r--r--schema/mysql-migrations/upgrade_134.sql19
-rw-r--r--schema/mysql-migrations/upgrade_135.sql9
-rw-r--r--schema/mysql-migrations/upgrade_136.sql6
-rw-r--r--schema/mysql-migrations/upgrade_137.sql9
-rw-r--r--schema/mysql-migrations/upgrade_138.sql6
-rw-r--r--schema/mysql-migrations/upgrade_139.sql7
-rw-r--r--schema/mysql-migrations/upgrade_140.sql5
-rw-r--r--schema/mysql-migrations/upgrade_141.sql7
-rw-r--r--schema/mysql-migrations/upgrade_143.sql21
-rw-r--r--schema/mysql-migrations/upgrade_144.sql91
-rw-r--r--schema/mysql-migrations/upgrade_145.sql7
-rw-r--r--schema/mysql-migrations/upgrade_146.sql14
-rw-r--r--schema/mysql-migrations/upgrade_147.sql20
-rw-r--r--schema/mysql-migrations/upgrade_148.sql10
-rw-r--r--schema/mysql-migrations/upgrade_149.sql11
-rw-r--r--schema/mysql-migrations/upgrade_150.sql17
-rw-r--r--schema/mysql-migrations/upgrade_151.sql38
-rw-r--r--schema/mysql-migrations/upgrade_152.sql9
-rw-r--r--schema/mysql-migrations/upgrade_153.sql42
-rw-r--r--schema/mysql-migrations/upgrade_154.sql12
-rw-r--r--schema/mysql-migrations/upgrade_155.sql19
-rw-r--r--schema/mysql-migrations/upgrade_156.sql7
-rw-r--r--schema/mysql-migrations/upgrade_157.sql6
-rw-r--r--schema/mysql-migrations/upgrade_159.sql6
-rw-r--r--schema/mysql-migrations/upgrade_160.sql6
-rw-r--r--schema/mysql-migrations/upgrade_161.sql58
-rw-r--r--schema/mysql-migrations/upgrade_162.sql6
-rw-r--r--schema/mysql-migrations/upgrade_163.sql38
-rw-r--r--schema/mysql-migrations/upgrade_164.sql8
-rw-r--r--schema/mysql-migrations/upgrade_165.sql6
-rw-r--r--schema/mysql-migrations/upgrade_166.sql21
-rw-r--r--schema/mysql-migrations/upgrade_167.sql25
-rw-r--r--schema/mysql-migrations/upgrade_168.sql21
-rw-r--r--schema/mysql-migrations/upgrade_170.sql7
-rw-r--r--schema/mysql-migrations/upgrade_171.sql3
-rw-r--r--schema/mysql-migrations/upgrade_172.sql11
-rw-r--r--schema/mysql-migrations/upgrade_173.sql6
-rw-r--r--schema/mysql-migrations/upgrade_174.sql241
-rw-r--r--schema/mysql-migrations/upgrade_175.sql484
-rw-r--r--schema/mysql-migrations/upgrade_176.sql6
-rw-r--r--schema/mysql-migrations/upgrade_177.sql20
-rw-r--r--schema/mysql-migrations/upgrade_178.sql20
-rw-r--r--schema/mysql-migrations/upgrade_179.sql5
-rw-r--r--schema/mysql-migrations/upgrade_180.sql26
-rw-r--r--schema/mysql-migrations/upgrade_182.sql12
-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.sql2442
-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.sql25
-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_131.sql22
-rw-r--r--schema/pgsql-migrations/upgrade_132.sql25
-rw-r--r--schema/pgsql-migrations/upgrade_133.sql25
-rw-r--r--schema/pgsql-migrations/upgrade_134.sql19
-rw-r--r--schema/pgsql-migrations/upgrade_135.sql9
-rw-r--r--schema/pgsql-migrations/upgrade_136.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_137.sql9
-rw-r--r--schema/pgsql-migrations/upgrade_138.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_139.sql9
-rw-r--r--schema/pgsql-migrations/upgrade_140.sql5
-rw-r--r--schema/pgsql-migrations/upgrade_141.sql7
-rw-r--r--schema/pgsql-migrations/upgrade_142.sql13
-rw-r--r--schema/pgsql-migrations/upgrade_143.sql27
-rw-r--r--schema/pgsql-migrations/upgrade_144.sql99
-rw-r--r--schema/pgsql-migrations/upgrade_146.sql14
-rw-r--r--schema/pgsql-migrations/upgrade_147.sql23
-rw-r--r--schema/pgsql-migrations/upgrade_148.sql10
-rw-r--r--schema/pgsql-migrations/upgrade_149.sql14
-rw-r--r--schema/pgsql-migrations/upgrade_150.sql17
-rw-r--r--schema/pgsql-migrations/upgrade_151.sql38
-rw-r--r--schema/pgsql-migrations/upgrade_152.sql7
-rw-r--r--schema/pgsql-migrations/upgrade_153.sql45
-rw-r--r--schema/pgsql-migrations/upgrade_154.sql12
-rw-r--r--schema/pgsql-migrations/upgrade_155.sql22
-rw-r--r--schema/pgsql-migrations/upgrade_156.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_157.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_158.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_160.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_161.sql70
-rw-r--r--schema/pgsql-migrations/upgrade_162.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_164.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_165.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_166.sql7
-rw-r--r--schema/pgsql-migrations/upgrade_167.sql24
-rw-r--r--schema/pgsql-migrations/upgrade_168.sql25
-rw-r--r--schema/pgsql-migrations/upgrade_169.sql8
-rw-r--r--schema/pgsql-migrations/upgrade_170.sql5
-rw-r--r--schema/pgsql-migrations/upgrade_171.sql3
-rw-r--r--schema/pgsql-migrations/upgrade_172.sql13
-rw-r--r--schema/pgsql-migrations/upgrade_173.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_174.sql61
-rw-r--r--schema/pgsql-migrations/upgrade_175.sql512
-rw-r--r--schema/pgsql-migrations/upgrade_176.sql6
-rw-r--r--schema/pgsql-migrations/upgrade_177.sql8
-rw-r--r--schema/pgsql-migrations/upgrade_178.sql23
-rw-r--r--schema/pgsql-migrations/upgrade_179.sql5
-rw-r--r--schema/pgsql-migrations/upgrade_180.sql32
-rw-r--r--schema/pgsql-migrations/upgrade_181.sql19
-rw-r--r--schema/pgsql-migrations/upgrade_182.sql14
-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.sql2781
-rw-r--r--test/bootstrap.php16
-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/Application/DependencyTest.php72
-rw-r--r--test/php/library/Director/Application/FiltersWorkAsExpectedTest.php15
-rw-r--r--test/php/library/Director/Application/MemoryLimitTest.php67
-rw-r--r--test/php/library/Director/CustomVariable/CustomVariablesTest.php79
-rw-r--r--test/php/library/Director/Data/AssignFilterHelperTest.php86
-rw-r--r--test/php/library/Director/Data/RecursiveUtf8ValidatorTest.php45
-rw-r--r--test/php/library/Director/IcingaConfig/AssignRendererTest.php126
-rw-r--r--test/php/library/Director/IcingaConfig/ExtensibleSetTest.php162
-rw-r--r--test/php/library/Director/IcingaConfig/IcingaConfigHelperTest.php130
-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/Import/HostSyncTest.php250
-rw-r--r--test/php/library/Director/Import/ImportSourceRestApiTest.php29
-rw-r--r--test/php/library/Director/Import/SyncUtilsTest.php108
-rw-r--r--test/php/library/Director/Objects/HostApplyMatchesTest.php93
-rw-r--r--test/php/library/Director/Objects/HostGroupMembershipResolverTest.php353
-rw-r--r--test/php/library/Director/Objects/IcingaCommandTest.php216
-rw-r--r--test/php/library/Director/Objects/IcingaHostTest.php771
-rw-r--r--test/php/library/Director/Objects/IcingaNotificationTest.php248
-rw-r--r--test/php/library/Director/Objects/IcingaServiceSetTest.php183
-rw-r--r--test/php/library/Director/Objects/IcingaServiceTest.php293
-rw-r--r--test/php/library/Director/Objects/IcingaTemplateResolverTest.php158
-rw-r--r--test/php/library/Director/Objects/IcingaTimePeriodTest.php184
-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.out13
-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
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierArrayElementByPositionTest.php143
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierArrayFilterTest.php120
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierCombineTest.php51
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierListToObjectTest.php104
-rw-r--r--test/php/library/Director/PropertyModifier/PropertyModifierParseURLTest.php147
-rw-r--r--test/php/library/Director/Resolver/TemplateTreeTest.php259
-rw-r--r--test/php/library/Director/Restriction/MatchingFilterTest.php56
-rw-r--r--test/phpunit-compat.php10
-rwxr-xr-xtest/setup_vendor.sh71
-rwxr-xr-xtest/travis-prepare.sh25
1129 files changed, 137634 insertions, 0 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..e9bddce
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,24 @@
+## Expected Behavior
+<!--- If you're describing a bug, tell us what should happen -->
+<!--- If you're suggesting a change/improvement, tell us how it should work -->
+
+## Current Behavior
+<!--- If describing a bug, tell us what happens instead of the expected behavior -->
+<!--- If suggesting a change/improvement, explain the difference from current behavior -->
+
+## Possible Solution
+<!--- Not obligatory, but suggest a fix/reason for the bug, -->
+<!--- or ideas how to implement: the addition or change -->
+
+## Steps to Reproduce (for bugs)
+<!--- Provide a link to a live example, or an unambiguous set of steps to -->
+<!--- reproduce this bug. Include configuration, logs, etc. to reproduce, if relevant -->
+
+## Your Environment
+<!--- Include as many relevant details about the environment you experienced the problem in -->
+* Director version (System - About):
+* Icinga Web 2 version and modules (System - About):
+* Icinga 2 version (`icinga2 --version`):
+* Operating System and version:
+* Webserver, PHP versions:
+
diff --git a/.github/workflows/L10n-update.yml b/.github/workflows/L10n-update.yml
new file mode 100644
index 0000000..9dce59a
--- /dev/null
+++ b/.github/workflows/L10n-update.yml
@@ -0,0 +1,20 @@
+name: L10n Update
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ trigger-update:
+ name: L10n Update Trigger
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Repository dispatch
+ uses: peter-evans/repository-dispatch@v1
+ with:
+ token: ${{ secrets.ICINGABOT_TOKEN }}
+ repository: Icinga/L10n
+ event-type: update
+ client-payload: '{"origin": "${{ github.repository }}", "commit": "${{ github.sha }}"}'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3511072
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+## Editors
+/.idea/
+.*.sw[op]
+
+## PHP vendor artifacts
+/vendor/
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..c6af13c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,61 @@
+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 already exists on our [Issue Tracker](https://github.com/Icinga/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)
+
+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/Icinga/icingaweb2-module-aws):
+ provides an Import Source for Autoscaling Groups on AWS
+* [File-Shipper](https://github.com/Icinga/icingaweb2-module-fileshipper):
+ allows Director to ship additional config files with manual config with its
+ deployments
+* [PuppetDB](https://github.com/Icinga/icingaweb2-module-puppetdb): provides
+ an Import Source dealing with your PuppetDB
+* [vSphere](https://github.com/Icinga/icingaweb2-module-vsphere): VMware vSphere
+ Import Source for Virtual Machines and Host Systems
diff --git a/application/clicommands/BasketCommand.php b/application/clicommands/BasketCommand.php
new file mode 100644
index 0000000..dd2434f
--- /dev/null
+++ b/application/clicommands/BasketCommand.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Core\Json;
+use Icinga\Module\Director\DirectorObject\Automation\Basket;
+use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
+use Icinga\Module\Director\DirectorObject\ObjectPurgeHelper;
+
+/**
+ * Export Director Config Objects
+ */
+class BasketCommand extends Command
+{
+ /**
+ * List configured Baskets
+ *
+ * USAGE
+ *
+ * icingacli director basket list
+ *
+ * OPTIONS
+ */
+ public function listAction()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()
+ ->from('director_basket', 'basket_name')
+ ->order('basket_name');
+ foreach ($db->fetchCol($query) as $name) {
+ echo "$name\n";
+ }
+ }
+
+ /**
+ * JSON-dump for objects related to the given Basket
+ *
+ * USAGE
+ *
+ * icingacli director basket dump --name <basket>
+ *
+ * OPTIONS
+ */
+ public function dumpAction()
+ {
+ $basket = $this->requireBasket();
+ $snapshot = BasketSnapshot::createForBasket($basket, $this->db());
+ echo $snapshot->getJsonDump() . "\n";
+ }
+
+ /**
+ * Take a snapshot for the given Basket
+ *
+ * USAGE
+ *
+ * icingacli director basket snapshot --name <basket>
+ *
+ * OPTIONS
+ */
+ public function snapshotAction()
+ {
+ $basket = $this->requireBasket();
+ $snapshot = BasketSnapshot::createForBasket($basket, $this->db());
+ $snapshot->store();
+ $hexSum = bin2hex($snapshot->get('content_checksum'));
+ printf(
+ "Snapshot '%s' taken for Basket '%s' at %s\n",
+ substr($hexSum, 0, 7),
+ $basket->get('basket_name'),
+ DateFormatter::formatDateTime($snapshot->get('ts_create') / 1000)
+ );
+ }
+
+ /**
+ * Restore a Basket from JSON dump provided on STDIN
+ *
+ * USAGE
+ *
+ * icingacli director basket restore < basket-dump.json
+ *
+ * OPTIONS
+ * --purge <ObjectType>[,<ObjectType] Purge objects of the
+ * Given types. WARNING: this removes ALL objects that are
+ * not shipped with the given basket
+ * --force Purge refuses to purge Objects in case there are
+ * no Objects of a given ObjectType in the provided basket
+ * unless forced to do so
+ */
+ public function restoreAction()
+ {
+ if ($purge = $this->params->get('purge')) {
+ $purge = explode(',', $purge);
+ ObjectPurgeHelper::assertObjectTypesAreEligibleForPurge($purge);
+ }
+ $json = file_get_contents('php://stdin');
+ BasketSnapshot::restoreJson($json, $this->db());
+ if ($purge) {
+ $this->purgeObjectTypes(Json::decode($json), $purge, $this->params->get('force'));
+ }
+ echo "Objects from Basket Snapshot have been restored\n";
+ }
+
+ protected function purgeObjectTypes($objects, array $types, $force = false)
+ {
+ $helper = new ObjectPurgeHelper($this->db());
+ if ($force) {
+ $helper->force();
+ }
+ foreach ($types as $type) {
+ list($className, $typeFilter) = BasketSnapshot::getClassAndObjectTypeForType($type);
+ $helper->purge(
+ isset($objects->$type) ? (array) $objects->$type : [],
+ $className,
+ $typeFilter
+ );
+ }
+ }
+
+ /**
+ */
+ protected function requireBasket()
+ {
+ return Basket::load($this->params->getRequired('name'), $this->db());
+ }
+}
diff --git a/application/clicommands/BenchmarkCommand.php b/application/clicommands/BenchmarkCommand.php
new file mode 100644
index 0000000..6ccd8c8
--- /dev/null
+++ b/application/clicommands/BenchmarkCommand.php
@@ -0,0 +1,152 @@
+<?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\CustomVariable\CustomVariable;
+use Icinga\Module\Director\Data\Db\IcingaObjectFilterRenderer;
+use Icinga\Module\Director\Data\Db\IcingaObjectQuery;
+use Icinga\Module\Director\Objects\HostGroupMembershipResolver;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaHostVar;
+use Icinga\Module\Director\Objects\IcingaVar;
+
+class BenchmarkCommand extends Command
+{
+ public function testflatfilterAction()
+ {
+ $q = new IcingaObjectQuery('host', $this->db());
+ $filter = Filter::fromQueryString(
+ // 'host.vars.snmp_community="*ub*"&(host.vars.location="London"|host.vars.location="Berlin")'
+ // 'host.vars.snmp_community="*ub*"&(host.vars.location="FRA DC"|host.vars.location="NBG DC")'
+ 'host.vars.priority="*igh"&(host.vars.location="FRA DC"|host.vars.location="NBG DC")'
+ );
+ IcingaObjectFilterRenderer::apply($filter, $q);
+ echo $q->getSql() . "\n";
+
+ print_r($q->listNames());
+ }
+
+ public function rerendervarsAction()
+ {
+ $conn = $this->db();
+ $db = $conn->getDbAdapter();
+ $db->beginTransaction();
+ $query = $db->select()->from(
+ array('v' => 'icinga_var'),
+ array(
+ 'v.varname',
+ 'v.varvalue',
+ 'v.checksum',
+ 'v.rendered_checksum',
+ 'v.rendered',
+ 'format' => "('json')",
+ )
+ );
+ Benchmark::measure('Ready to fetch all vars');
+ $rows = $db->fetchAll($query);
+ Benchmark::measure('Got vars, storing flat');
+ foreach ($rows as $row) {
+ $var = CustomVariable::fromDbRow($row);
+ $rendered = $var->render();
+ $checksum = sha1($rendered, true);
+ if ($checksum === $row->rendered_checksum) {
+ continue;
+ }
+
+ $where = $db->quoteInto('checksum = ?', $row->checksum);
+ $db->update(
+ 'icinga_var',
+ array(
+ 'rendered' => $rendered,
+ 'rendered_checksum' => $checksum
+ ),
+ $where
+ );
+ }
+
+ $db->commit();
+ }
+
+ public function flattenvarsAction()
+ {
+ $conn = $this->db();
+ $db = $conn->getDbAdapter();
+ $db->beginTransaction();
+ $query = $db->select()->from(['v' => 'icinga_host_var'], [
+ 'v.host_id',
+ 'v.varname',
+ 'v.varvalue',
+ 'v.format',
+ 'v.checksum'
+ ]);
+ Benchmark::measure('Ready to fetch all vars');
+ $rows = $db->fetchAll($query);
+ Benchmark::measure('Got vars, storing flat');
+
+ foreach ($rows as $row) {
+ $var = CustomVariable::fromDbRow($row);
+ $checksum = $var->checksum();
+ if (! IcingaVar::exists($checksum, $conn)) {
+ IcingaVar::generateForCustomVar($var, $conn);
+ }
+
+ if ($row->checksum === null) {
+ $where = $db->quoteInto('host_id = ?', $row->host_id)
+ . $db->quoteInto(' AND varname = ?', $row->varname);
+ $db->update('icinga_host_var', ['checksum' => $checksum], $where);
+ }
+ }
+
+ $db->commit();
+ }
+
+ public function resolvehostgroupsAction()
+ {
+ $resolver = new HostGroupMembershipResolver($this->db());
+ $resolver->refreshDb();
+ }
+
+ public function filterAction()
+ {
+ $flat = [];
+
+ /** @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) [];
+ 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..e313aa4
--- /dev/null
+++ b/application/clicommands/ConfigCommand.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Application\Benchmark;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Core\Json;
+use Icinga\Module\Director\Deployment\ConditionalDeployment;
+use Icinga\Module\Director\Deployment\DeploymentGracePeriod;
+use Icinga\Module\Director\Deployment\DeploymentStatus;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Import\SyncUtils;
+
+/**
+ * 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
+ *
+ * USAGE
+ *
+ * icingacli director config deploy [--checksum <checksum>] [--force] [--wait <seconds>]
+ * [--grace-period <seconds>]
+ *
+ * OPTIONS
+ *
+ * --checksum <checksum> Optionally deploy a specific configuration
+ * --force Force a deployment, even when the configuration
+ * hasn't changed
+ * --wait <seconds> Optionally wait until Icinga completed it's
+ * restart
+ * --grace-period <seconds> Do not deploy if a deployment took place
+ * less than <seconds> ago
+ */
+ public function deployAction()
+ {
+ $db = $this->db();
+
+ $checksum = $this->params->get('checksum');
+ if ($checksum) {
+ $config = IcingaConfig::load(hex2bin($checksum), $db);
+ } else {
+ $config = IcingaConfig::generate($db);
+ $checksum = $config->getHexChecksum();
+ }
+
+ $deployer = new ConditionalDeployment($db, $this->api());
+ $deployer->force((bool) $this->params->get('force'));
+ if ($graceTime = $this->params->get('grace-period')) {
+ $deployer->setGracePeriod(new DeploymentGracePeriod((int) $graceTime, $db));
+ if ($this->params->get('force')) {
+ fwrite(STDERR, "WARNING: force overrides Grace period\n");
+ }
+ }
+ $deployer->refresh();
+
+ if ($deployment = $deployer->deploy($config)) {
+ if ($deployer->hasBeenForced()) {
+ echo $deployer->getNoDeploymentReason() . ", deploying anyway\n";
+ }
+ printf("Config '%s' has been deployed\n", $checksum);
+ } else {
+ echo $deployer->getNoDeploymentReason() . "\n";
+ return;
+ }
+
+ if ($timeout = $this->getWaitTime()) {
+ $deployed = $deployer->waitForStartupAfterDeploy($deployment, $timeout);
+ if ($deployed !== true) {
+ $this->fail("Waiting for Icinga restart failed '%s': %s\n", $checksum, $deployed);
+ }
+ }
+ }
+
+ /**
+ * Checks the deployments status
+ */
+ public function deploymentstatusAction()
+ {
+ $db = $this->db();
+ $api = $this->api();
+ $status = new DeploymentStatus($db, $api);
+ $result = $status->getDeploymentStatus($this->params->get('configs'), $this->params->get('activities'));
+ if ($key = $this->params->get('key')) {
+ $result = SyncUtils::getSpecificValue($result, $key);
+ }
+
+ if (is_string($result)) {
+ echo "$result\n";
+ } else {
+ echo Json::encode($result, JSON_PRETTY_PRINT) . "\n";
+ }
+ }
+
+ protected function getWaitTime()
+ {
+ if ($timeout = $this->params->get('wait')) {
+ if (!ctype_digit($timeout)) {
+ $this->fail("--wait must be the number of seconds to wait'");
+ }
+
+ return (int) $timeout;
+ }
+
+ return null;
+ }
+}
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/DaemonCommand.php b/application/clicommands/DaemonCommand.php
new file mode 100644
index 0000000..e89e1da
--- /dev/null
+++ b/application/clicommands/DaemonCommand.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Daemon\BackgroundDaemon;
+
+class DaemonCommand extends Command
+{
+ /**
+ * Run the main Director daemon
+ *
+ * USAGE
+ *
+ * icingacli director daemon run [--db-resource <name>]
+ */
+ public function runAction()
+ {
+ $this->app->getModuleManager()->loadEnabledModules();
+ $daemon = new BackgroundDaemon();
+ if ($dbResource = $this->params->get('db-resource')) {
+ $daemon->setDbResourceName($dbResource);
+ }
+ $daemon->run();
+ }
+}
diff --git a/application/clicommands/DependencyCommand.php b/application/clicommands/DependencyCommand.php
new file mode 100644
index 0000000..ff5cbdc
--- /dev/null
+++ b/application/clicommands/DependencyCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Dependencies
+ *
+ * Use this command to show, create, modify or delete Icinga Dependency
+ * objects
+ */
+class DependencyCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/EndpointCommand.php b/application/clicommands/EndpointCommand.php
new file mode 100644
index 0000000..f61f4fc
--- /dev/null
+++ b/application/clicommands/EndpointCommand.php
@@ -0,0 +1,19 @@
+<?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
+{
+ public function statusAction()
+ {
+ print_r($this->api()->getStatus());
+ }
+}
diff --git a/application/clicommands/ExportCommand.php b/application/clicommands/ExportCommand.php
new file mode 100644
index 0000000..2b2119d
--- /dev/null
+++ b/application/clicommands/ExportCommand.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\DirectorObject\Automation\ImportExport;
+
+/**
+ * Export Director Config Objects
+ */
+class ExportCommand extends Command
+{
+ /**
+ * Export all ImportSource definitions
+ *
+ * USAGE
+ *
+ * icingacli director export importsources [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function importsourcesAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllImportSources(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ /**
+ * Export all SyncRule definitions
+ *
+ * USAGE
+ *
+ * icingacli director export syncrules [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function syncrulesAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllSyncRules(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ /**
+ * Export all Job definitions
+ *
+ * USAGE
+ *
+ * icingacli director export jobs [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function jobsAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllJobs(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ /**
+ * Export all DataField definitions
+ *
+ * USAGE
+ *
+ * icingacli director export datafields [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function datafieldsAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllDataFields(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ /**
+ * Export all DataList definitions
+ *
+ * USAGE
+ *
+ * icingacli director export datalists [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function datalistsAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllDataLists(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ // /**
+ // * Export all IcingaHostGroup definitions
+ // *
+ // * USAGE
+ // *
+ // * icingacli director export hostgroup [options]
+ // *
+ // * OPTIONS
+ // *
+ // * --no-pretty JSON is pretty-printed per default
+ // * Use this flag to enforce unformatted JSON
+ // */
+ // public function hostgroupAction()
+ // {
+ // $export = new ImportExport($this->db());
+ // echo $this->renderJson(
+ // $export->serializeAllHostGroups(),
+ // !$this->params->shift('no-pretty')
+ // );
+ // }
+ //
+ // /**
+ // * Export all IcingaServiceGroup definitions
+ // *
+ // * USAGE
+ // *
+ // * icingacli director export servicegroup [options]
+ // *
+ // * OPTIONS
+ // *
+ // * --no-pretty JSON is pretty-printed per default
+ // * Use this flag to enforce unformatted JSON
+ // */
+ // public function servicegroupAction()
+ // {
+ // $export = new ImportExport($this->db());
+ // echo $this->renderJson(
+ // $export->serializeAllServiceGroups(),
+ // !$this->params->shift('no-pretty')
+ // );
+ // }
+
+ /**
+ * Export all IcingaTemplateChoiceHost definitions
+ *
+ * USAGE
+ *
+ * icingacli director export hosttemplatechoices [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function hosttemplatechoicesAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllHostTemplateChoices(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+}
diff --git a/application/clicommands/HealthCommand.php b/application/clicommands/HealthCommand.php
new file mode 100644
index 0000000..1635c50
--- /dev/null
+++ b/application/clicommands/HealthCommand.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\CheckPlugin\PluginState;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Health;
+use Icinga\Module\Director\Cli\PluginOutputBeautifier;
+
+/**
+ * Check Icinga Director Health
+ *
+ * Use this command as a CheckPlugin to monitor your Icinga Director health
+ */
+class HealthCommand extends Command
+{
+ /**
+ * Run health checks
+ *
+ * Use this command to run all or a specific set of Health Checks.
+ *
+ * USAGE
+ *
+ * icingacli director health check [options]
+ *
+ * OPTIONS
+ *
+ * --check <name> Run only a specific set of checks
+ * valid names: config, sync, import, jobs, deployment
+ * --db <name> Use a specific Icinga Web DB resource
+ * --watch <seconds> Refresh every <second>. For interactive use only
+ */
+ public function checkAction()
+ {
+ $health = new Health();
+ if ($name = $this->params->get('db')) {
+ $health->setDbResourceName($name);
+ }
+
+ if ($name = $this->params->get('check')) {
+ $check = $health->getCheck($name);
+ echo PluginOutputBeautifier::beautify($check->getOutput(), $this->screen);
+
+ exit($check->getState()->getNumeric());
+ } else {
+ $state = new PluginState('OK');
+ $checks = $health->getAllChecks();
+
+ $output = [];
+ foreach ($checks as $check) {
+ $state->raise($check->getState());
+ $output[] = $check->getOutput();
+ }
+
+ if ($state->getNumeric() === 0) {
+ echo "Icinga Director: everything is fine\n\n";
+ } else {
+ echo "Icinga Director: there are problems\n\n";
+ }
+
+ $out = PluginOutputBeautifier::beautify(implode("\n", $output), $this->screen);
+ echo $out;
+
+ if (! $this->isBeingWatched()) {
+ exit($state->getNumeric());
+ }
+ }
+ }
+
+ /**
+ * Cli should provide this information, as it shifts the parameter
+ *
+ * @return bool
+ */
+ protected function isBeingWatched()
+ {
+ global $argv;
+ return in_array('--watch', $argv);
+ }
+}
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/HostgroupsCommand.php b/application/clicommands/HostgroupsCommand.php
new file mode 100644
index 0000000..1007a05
--- /dev/null
+++ b/application/clicommands/HostgroupsCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectsCommand;
+
+/**
+ * Manage Icinga Hostgroups
+ *
+ * Use this command to list Icinga Hostgroup objects
+ */
+class HostgroupsCommand extends ObjectsCommand
+{
+}
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..974e28d
--- /dev/null
+++ b/application/clicommands/HousekeepingCommand.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Exception\MissingParameterException;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Db\Housekeeping;
+use Icinga\Module\Director\Db\MembershipHousekeeping;
+
+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/ImportCommand.php b/application/clicommands/ImportCommand.php
new file mode 100644
index 0000000..3edfff2
--- /dev/null
+++ b/application/clicommands/ImportCommand.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\DirectorObject\Automation\ImportExport;
+use Icinga\Module\Director\Objects\ImportSource;
+
+/**
+ * Export Director Config Objects
+ */
+class ImportCommand extends Command
+{
+ /**
+ * Import ImportSource definitions
+ *
+ * USAGE
+ *
+ * icingacli director import importsources < importsources.json
+ *
+ * OPTIONS
+ */
+ public function importsourcesAction()
+ {
+ $json = file_get_contents('php://stdin');
+ $import = new ImportExport($this->db());
+ $count = $import->unserializeImportSources(json_decode($json));
+ echo "$count Import Sources have been imported\n";
+ }
+
+ // /**
+ // * Import an ImportSource definition
+ // *
+ // * USAGE
+ // *
+ // * icingacli director import importsource < importsource.json
+ // *
+ // * OPTIONS
+ // */
+ // public function importsourcection()
+ // {
+ // $json = file_get_contents('php://stdin');
+ // $object = ImportSource::import(json_decode($json), $this->db());
+ // $object->store();
+ // printf("Import Source '%s' has been imported\n", $object->getObjectName());
+ // }
+
+ /**
+ * Import SyncRule definitions
+ *
+ * USAGE
+ *
+ * icingacli director import syncrules < syncrules.json
+ */
+ public function syncrulesAction()
+ {
+ $json = file_get_contents('php://stdin');
+ $import = new ImportExport($this->db());
+ $count = $import->unserializeSyncRules(json_decode($json));
+ echo "$count Sync Rules have been imported\n";
+ }
+}
diff --git a/application/clicommands/ImportsourceCommand.php b/application/clicommands/ImportsourceCommand.php
new file mode 100644
index 0000000..477fdf5
--- /dev/null
+++ b/application/clicommands/ImportsourceCommand.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Application\Benchmark;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Core\Json;
+use Icinga\Module\Director\Hook\ImportSourceHook;
+use Icinga\Module\Director\Objects\ImportSource;
+
+/**
+ * Deal with Director Import Sources
+ *
+ * Use this command to check or trigger your defined Import Sources
+ */
+class ImportsourceCommand extends Command
+{
+ /**
+ * List defined Import Sources
+ *
+ * This shows a table with your defined Import Sources, their IDs and
+ * current state. As triggering Imports requires an ID, this is where
+ * you can look up the desired ID.
+ *
+ * USAGE
+ *
+ * icingacli director importsource list
+ */
+ public function listAction()
+ {
+ $sources = ImportSource::loadAll($this->db());
+ if (empty($sources)) {
+ echo "No Import Source has been defined\n";
+
+ return;
+ }
+
+ printf("%4s | %s\n", 'ID', 'Import Source name');
+ printf("-----+%s\n", str_repeat('-', 64));
+
+ foreach ($sources as $source) {
+ $state = $source->get('import_state');
+ printf("%4d | %s\n", $source->get('id'), $source->get('source_name'));
+ printf(" | -> %s%s\n", $state, $state === 'failing' ? ': ' . $source->get('last_error_message') : '');
+ }
+ }
+
+ /**
+ * Check a given Import Source for changes
+ *
+ * This command fetches data from the given Import Source and compares it
+ * to the most recently imported data.
+ *
+ * USAGE
+ *
+ * icingacli director importsource check --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> An Import Source ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function checkAction()
+ {
+ $source = $this->getImportSource();
+ $source->checkForChanges();
+ $this->showImportStateDetails($source);
+ }
+
+ /**
+ * Fetch current data from a given Import Source
+ *
+ * This command fetches data from the given Import Source and outputs
+ * them as plain JSON
+ *
+ * USAGE
+ *
+ * icingacli director importsource fetch --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> An Import Source ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function fetchAction()
+ {
+ $source = $this->getImportSource();
+ $source->checkForChanges();
+ $hook = ImportSourceHook::forImportSource($source);
+ Benchmark::measure('Ready to fetch data');
+ $data = $hook->fetchData();
+ $source->applyModifiers($data);
+ Benchmark::measure(sprintf('Got %d rows, ready to dump JSON', count($data)));
+ echo Json::encode($data, JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * Trigger an Import Run for a given Import Source
+ *
+ * This command fetches data from the given Import Source and stores it to
+ * the Director DB, so that the next related Sync Rule run can work with
+ * fresh data. In case data didn't change, nothing is going to be stored.
+ *
+ * USAGE
+ *
+ * icingacli director importsource run --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> An Import Source ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function runAction()
+ {
+ $source = $this->getImportSource();
+
+ if ($source->runImport()) {
+ print "New data has been imported\n";
+ $this->showImportStateDetails($source);
+ } else {
+ print "Nothing has been changed, imported data is still up to date\n";
+ }
+ }
+
+ /**
+ * @return ImportSource
+ */
+ protected function getImportSource()
+ {
+ return ImportSource::loadWithAutoIncId(
+ (int) $this->params->getRequired('id'),
+ $this->db()
+ );
+ }
+
+ /**
+ * @param ImportSource $source
+ * @throws \Icinga\Exception\IcingaException
+ */
+ protected function showImportStateDetails(ImportSource $source)
+ {
+ echo $this->getImportStateDescription($source) . "\n";
+ }
+
+ /**
+ * @param ImportSource $source
+ * @return string
+ * @throws \Icinga\Exception\IcingaException
+ */
+ protected function getImportStateDescription(ImportSource $source)
+ {
+ switch ($source->get('import_state')) {
+ case 'unknown':
+ return "It's currently unknown whether we are in sync with this"
+ . ' Import Source. You should either check for changes or'
+ . ' trigger a new Import Run.';
+ case 'in-sync':
+ return 'This Import Source is in sync';
+ case 'pending-changes':
+ return 'There are pending changes for this Import Source. You'
+ . ' should trigger a new Import Run.';
+ case 'failing':
+ return 'This Import Source failed: ' . $source->get('last_error_message');
+ default:
+ return 'This Import Source has an invalid state: ' . $source->get('import_state');
+ }
+ }
+}
diff --git a/application/clicommands/JobsCommand.php b/application/clicommands/JobsCommand.php
new file mode 100644
index 0000000..1c6297f
--- /dev/null
+++ b/application/clicommands/JobsCommand.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Exception;
+use gipfl\Cli\Process;
+use gipfl\Protocol\JsonRpc\Connection;
+use gipfl\Protocol\NetString\StreamWrapper;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Daemon\JsonRpcLogWriter as JsonRpcLogWriterAlias;
+use Icinga\Module\Director\Daemon\Logger;
+use Icinga\Module\Director\Objects\DirectorJob;
+use React\EventLoop\Factory as Loop;
+use React\EventLoop\LoopInterface;
+use React\Stream\ReadableResourceStream;
+use React\Stream\WritableResourceStream;
+
+class JobsCommand extends Command
+{
+ public function runAction()
+ {
+ $this->app->getModuleManager()->loadEnabledModules();
+ $loop = Loop::create();
+ if ($this->params->get('rpc')) {
+ $this->enableRpc($loop);
+ }
+ if ($this->params->get('rpc') && $jobId = $this->params->get('id')) {
+ $exitCode = 1;
+ $jobId = (int) $jobId;
+ $loop->futureTick(function () use ($jobId, $loop, &$exitCode) {
+ Process::setTitle('icinga::director::job');
+ try {
+ $this->raiseLimits();
+ $job = DirectorJob::loadWithAutoIncId($jobId, $this->db());
+ Process::setTitle('icinga::director::job (' . $job->get('job_name') . ')');
+ if ($job->run()) {
+ $exitCode = 0;
+ } else {
+ $exitCode = 1;
+ }
+ } catch (Exception $e) {
+ Logger::error($e->getMessage());
+ $exitCode = 1;
+ }
+ $loop->futureTick(function () use ($loop) {
+ $loop->stop();
+ });
+ });
+ } else {
+ Logger::error('This command is no longer available. Please check our Upgrading documentation');
+ $exitCode = 1;
+ }
+
+ $loop->run();
+ exit($exitCode);
+ }
+
+ protected function enableRpc(LoopInterface $loop)
+ {
+ // stream_set_blocking(STDIN, 0);
+ // stream_set_blocking(STDOUT, 0);
+ // print_r(stream_get_meta_data(STDIN));
+ // stream_set_write_buffer(STDOUT, 0);
+ // ini_set('implicit_flush', 1);
+ $netString = new StreamWrapper(
+ new ReadableResourceStream(STDIN, $loop),
+ new WritableResourceStream(STDOUT, $loop)
+ );
+ $jsonRpc = new Connection();
+ $jsonRpc->handle($netString);
+
+ Logger::replaceRunningInstance(new JsonRpcLogWriterAlias($jsonRpc));
+ }
+}
diff --git a/application/clicommands/KickstartCommand.php b/application/clicommands/KickstartCommand.php
new file mode 100644
index 0000000..80aa183
--- /dev/null
+++ b/application/clicommands/KickstartCommand.php
@@ -0,0 +1,88 @@
+<?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->raiseLimits();
+ $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..1bd21e7
--- /dev/null
+++ b/application/clicommands/ServiceCommand.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Cli\Params;
+use Icinga\Module\Director\Cli\ObjectCommand;
+use Icinga\Module\Director\DirectorObject\Lookup\ServiceFinder;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Resolver\OverrideHelper;
+use InvalidArgumentException;
+
+/**
+ * Manage Icinga Services
+ *
+ * Use this command to show, create, modify or delete Icinga Service
+ * objects
+ */
+class ServiceCommand extends ObjectCommand
+{
+ public function setAction()
+ {
+ if (($host = $this->params->get('host')) && $this->params->shift('allow-overrides')) {
+ if ($this->setServiceProperties($host)) {
+ return;
+ }
+ }
+
+ parent::setAction();
+ }
+
+ protected function setServiceProperties($hostname)
+ {
+ $serviceName = $this->getName();
+ $host = IcingaHost::load($hostname, $this->db());
+ $service = ServiceFinder::find($host, $serviceName);
+ if ($service->requiresOverrides()) {
+ self::checkForOverrideSafety($this->params);
+ $properties = $this->remainingParams();
+ unset($properties['host']);
+ OverrideHelper::applyOverriddenVars($host, $serviceName, $properties);
+ $this->persistChanges($host, 'Host', $hostname . " (Overrides for $serviceName)", 'modified');
+ return true;
+ }
+
+ return false;
+ }
+
+ protected static function checkForOverrideSafety(Params $params)
+ {
+ if ($params->shift('replace')) {
+ throw new InvalidArgumentException('--replace is not available for Variable Overrides');
+ }
+ $appends = self::stripPrefixedProperties($params, 'append-');
+ $remove = self::stripPrefixedProperties($params, 'remove-');
+ OverrideHelper::assertVarsForOverrides($appends);
+ OverrideHelper::assertVarsForOverrides($remove);
+ if (!empty($appends)) {
+ throw new InvalidArgumentException('--append- is not available for Variable Overrides');
+ }
+ if (!empty($remove)) {
+ throw new InvalidArgumentException('--remove- is not available for Variable Overrides');
+ }
+ // Alternative, untested:
+ // $this->appendToArrayProperties($object, $appends);
+ // $this->removeProperties($object, $remove);
+ }
+
+ protected function load($name)
+ {
+ return parent::load($this->makeServiceKey($name));
+ }
+
+ protected function exists($name)
+ {
+ return parent::exists($this->makeServiceKey($name));
+ }
+
+ protected function makeServiceKey($name)
+ {
+ if ($host = $this->params->get('host')) {
+ return [
+ 'object_name' => $name,
+ 'host_id' => IcingaHost::load($host, $this->db())->get('id'),
+ ];
+ } else {
+ return [
+ 'object_name' => $name,
+ 'object_type' => 'template',
+ ];
+ }
+ }
+}
diff --git a/application/clicommands/ServicegroupCommand.php b/application/clicommands/ServicegroupCommand.php
new file mode 100644
index 0000000..1c732d4
--- /dev/null
+++ b/application/clicommands/ServicegroupCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Servicegroups
+ *
+ * Use this command to show, create, modify or delete Icinga Servicegroups
+ * objects
+ */
+class ServiceGroupCommand extends ObjectCommand
+{
+}
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/ServicesetsCommand.php b/application/clicommands/ServicesetsCommand.php
new file mode 100644
index 0000000..54669d5
--- /dev/null
+++ b/application/clicommands/ServicesetsCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectsCommand;
+
+/**
+ * Manage Icinga Service Sets
+ *
+ * Use this command to list Icinga Service Set objects
+ */
+class ServicesetsCommand extends ObjectsCommand
+{
+ protected $type = 'ServiceSet';
+}
diff --git a/application/clicommands/SyncruleCommand.php b/application/clicommands/SyncruleCommand.php
new file mode 100644
index 0000000..37a3f0e
--- /dev/null
+++ b/application/clicommands/SyncruleCommand.php
@@ -0,0 +1,195 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Objects\DirectorActivityLog;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\SyncRule;
+use RuntimeException;
+
+/**
+ * Deal with Director Sync Rules
+ *
+ * Use this command to check or trigger your defined Sync Rules
+ */
+class SyncruleCommand extends Command
+{
+ /**
+ * List defined Sync Rules
+ *
+ * This shows a table with your defined Sync Rules, their IDs and
+ * current state. As triggering a Sync requires an ID, this is where
+ * you can look up the desired ID.
+ *
+ * USAGE
+ *
+ * icingacli director syncrule list
+ */
+ public function listAction()
+ {
+ $rules = SyncRule::loadAll($this->db());
+ if (empty($rules)) {
+ echo "No Sync Rule has been defined\n";
+
+ return;
+ }
+
+ printf("%4s | %s\n", 'ID', 'Sync Rule name');
+ printf("-----+%s\n", str_repeat('-', 64));
+
+ foreach ($rules as $rule) {
+ $state = $rule->get('sync_state');
+ printf("%4d | %s\n", $rule->get('id'), $rule->get('rule_name'));
+ printf(" | -> %s%s\n", $state, $state === 'failing' ? ': ' . $rule->get('last_error_message') : '');
+ }
+ }
+
+ /**
+ * Check a given Sync Rule for changes
+ *
+ * This command runs a complete Sync in memory but doesn't persist eventual changes.
+ *
+ * USAGE
+ *
+ * icingacli director syncrule check --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> A Sync Rule ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function checkAction()
+ {
+ $rule = $this->getSyncRule();
+ $hasChanges = $rule->checkForChanges();
+ $this->showSyncStateDetails($rule);
+ if ($hasChanges) {
+ $mods = $this->getExpectedModificationCounts($rule);
+ printf(
+ "Expected modifications: %dx create, %dx modify, %dx delete\n",
+ $mods->modify,
+ $mods->create,
+ $mods->delete
+ );
+ }
+
+ exit($this->getSyncStateExitCode($rule));
+ }
+
+ protected function getExpectedModificationCounts(SyncRule $rule)
+ {
+ $modifications = $rule->getExpectedModifications();
+
+ $create = 0;
+ $modify = 0;
+ $delete = 0;
+
+ /** @var IcingaObject $object */
+ foreach ($modifications as $object) {
+ if ($object->hasBeenLoadedFromDb()) {
+ if ($object->shouldBeRemoved()) {
+ $delete++;
+ } else {
+ $modify++;
+ }
+ } else {
+ $create++;
+ }
+ }
+
+ return (object) [
+ DirectorActivityLog::ACTION_CREATE => $create,
+ DirectorActivityLog::ACTION_MODIFY => $modify,
+ DirectorActivityLog::ACTION_DELETE => $delete,
+ ];
+ }
+
+ /**
+ * Trigger a Sync Run for a given Sync Rule
+ *
+ * This command builds new objects according your Sync Rule, compares them
+ * with existing ones and persists eventual changes.
+ *
+ * USAGE
+ *
+ * icingacli director syncrule run --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> A Sync Rule ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function runAction()
+ {
+ $rule = $this->getSyncRule();
+
+ if ($rule->applyChanges()) {
+ print "New data has been imported\n";
+ $this->showSyncStateDetails($rule);
+ } else {
+ print "Nothing has been changed, imported data is still up to date\n";
+ }
+ }
+
+ /**
+ * @return SyncRule
+ */
+ protected function getSyncRule()
+ {
+ return SyncRule::loadWithAutoIncId(
+ (int) $this->params->getRequired('id'),
+ $this->db()
+ );
+ }
+
+ /**
+ * @param SyncRule $rule
+ */
+ protected function showSyncStateDetails(SyncRule $rule)
+ {
+ echo $this->getSyncStateDescription($rule) . "\n";
+ }
+
+ /**
+ * @param SyncRule $rule
+ * @return string
+ */
+ protected function getSyncStateDescription(SyncRule $rule)
+ {
+ switch ($rule->get('sync_state')) {
+ case 'unknown':
+ return "It's currently unknown whether we are in sync with this rule."
+ . ' You should either check for changes or trigger a new Sync Run.';
+ case 'in-sync':
+ return 'This Sync Rule is in sync';
+ case 'pending-changes':
+ return 'There are pending changes for this Sync Rule. You should'
+ . ' trigger a new Sync Run.';
+ case 'failing':
+ return 'This Sync Rule failed: '. $rule->get('last_error_message');
+ default:
+ throw new RuntimeException('Invalid sync state: ' . $rule->get('sync_state'));
+ }
+ }
+
+ /**
+ * @param SyncRule $rule
+ * @return string
+ */
+ protected function getSyncStateExitCode(SyncRule $rule)
+ {
+ switch ($rule->get('sync_state')) {
+ case 'unknown':
+ return 3;
+ case 'in-sync':
+ return 0;
+ case 'pending-changes':
+ return 1;
+ case 'failing':
+ return 2;
+ default:
+ throw new RuntimeException('Invalid sync state: ' . $rule->get('sync_state'));
+ }
+ }
+}
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/BasketController.php b/application/controllers/BasketController.php
new file mode 100644
index 0000000..8733d16
--- /dev/null
+++ b/application/controllers/BasketController.php
@@ -0,0 +1,416 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use gipfl\Diff\HtmlRenderer\InlineDiff;
+use gipfl\Diff\PhpDiff;
+use gipfl\IcingaWeb2\Link;
+use gipfl\Web\Table\NameValueTable;
+use gipfl\Web\Widget\Hint;
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Director\Core\Json;
+use Icinga\Module\Director\Data\Exporter;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\DirectorObject\Automation\Basket;
+use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
+use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver;
+use Icinga\Module\Director\DirectorObject\Automation\CompareBasketObject;
+use Icinga\Module\Director\Forms\AddToBasketForm;
+use Icinga\Module\Director\Forms\BasketCreateSnapshotForm;
+use Icinga\Module\Director\Forms\BasketForm;
+use Icinga\Module\Director\Forms\BasketUploadForm;
+use Icinga\Module\Director\Forms\RestoreBasketForm;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use ipl\Html\Html;
+use Icinga\Module\Director\Web\Table\BasketSnapshotTable;
+
+class BasketController extends ActionController
+{
+ protected $isApified = true;
+
+ protected function basketTabs()
+ {
+ $name = $this->params->get('name');
+ return $this->tabs()->add('show', [
+ 'label' => $this->translate('Basket'),
+ 'url' => 'director/basket',
+ 'urlParams' => ['name' => $name]
+ ])->add('snapshots', [
+ 'label' => $this->translate('Snapshots'),
+ 'url' => 'director/basket/snapshots',
+ 'urlParams' => ['name' => $name]
+ ]);
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Exception\MissingParameterException
+ */
+ public function indexAction()
+ {
+ $this->actions()->add(
+ Link::create(
+ $this->translate('Back'),
+ 'director/baskets',
+ null,
+ ['class' => 'icon-left-big']
+ )
+ );
+ $basket = $this->requireBasket();
+ $this->basketTabs()->activate('show');
+ $this->addTitle($basket->get('basket_name'));
+ if ($basket->isEmpty()) {
+ $this->content()->add(Hint::info($this->translate('This basket is empty')));
+ }
+ $this->content()->add(
+ (new BasketForm())->setObject($basket)->handleRequest()
+ );
+ }
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ */
+ public function addAction()
+ {
+ $this->actions()->add(
+ Link::create(
+ $this->translate('Baskets'),
+ 'director/baskets',
+ null,
+ ['class' => 'icon-tag']
+ )
+ );
+ $this->addSingleTab($this->translate('Add to Basket'));
+ $this->addTitle($this->translate('Add chosen objects to a Configuration Basket'));
+ $form = new AddToBasketForm();
+ $form->setDb($this->db())
+ ->setType($this->params->getRequired('type'))
+ ->setNames($this->url()->getParams()->getValues('names'))
+ ->handleRequest();
+ $this->content()->add($form);
+ }
+
+ public function createAction()
+ {
+ $this->actions()->add(
+ Link::create(
+ $this->translate('back'),
+ 'director/baskets',
+ null,
+ ['class' => 'icon-left-big']
+ )
+ );
+ $this->addSingleTab($this->translate('Create Basket'));
+ $this->addTitle($this->translate('Create a new Configuration Basket'));
+ $form = (new BasketForm())
+ ->setDb($this->db())
+ ->handleRequest();
+ $this->content()->add($form);
+ }
+
+ public function uploadAction()
+ {
+ $this->actions()->add(
+ Link::create(
+ $this->translate('back'),
+ 'director/baskets',
+ null,
+ ['class' => 'icon-left-big']
+ )
+ );
+ $this->addSingleTab($this->translate('Upload a Basket'));
+ $this->addTitle($this->translate('Upload a Configuration Basket'));
+ $form = (new BasketUploadForm())
+ ->setDb($this->db())
+ ->handleRequest();
+ $this->content()->add($form);
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function snapshotsAction()
+ {
+ $name = $this->params->get('name');
+ if ($name === null || $name === '') {
+ $basket = null;
+ } else {
+ $basket = Basket::load($name, $this->db());
+ }
+ if ($basket === null) {
+ $this->addTitle($this->translate('Basket Snapshots'));
+ $this->addSingleTab($this->translate('Snapshots'));
+ } else {
+ $this->addTitle(sprintf(
+ $this->translate('%s: Snapshots'),
+ $basket->get('basket_name')
+ ));
+ $this->basketTabs()->activate('snapshots');
+ }
+ if ($basket !== null) {
+ $this->content()->add(
+ (new BasketCreateSnapshotForm())
+ ->setBasket($basket)
+ ->handleRequest()
+ );
+ }
+ $table = new BasketSnapshotTable($this->db());
+ if ($basket !== null) {
+ $table->setBasket($basket);
+ }
+
+ $table->renderTo($this);
+ }
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function snapshotAction()
+ {
+ $basket = $this->requireBasket();
+ $snapshot = BasketSnapshot::load([
+ 'basket_uuid' => $basket->get('uuid'),
+ 'ts_create' => $this->params->getRequired('ts'),
+ ], $this->db());
+ $snapSum = bin2hex($snapshot->get('content_checksum'));
+
+ if ($this->params->get('action') === 'download') {
+ $this->getResponse()->setHeader('Content-Type', 'application/json', true);
+ $this->getResponse()->setHeader('Content-Disposition', sprintf(
+ 'attachment; filename=Director-Basket_%s_%s.json',
+ str_replace([' ', '"'], ['_', '_'], iconv(
+ 'UTF-8',
+ 'ISO-8859-1//IGNORE',
+ $basket->get('basket_name')
+ )),
+ substr($snapSum, 0, 7)
+ ));
+ echo $snapshot->getJsonDump();
+ return;
+ }
+
+ $this->addTitle(
+ $this->translate('%s: %s (Snapshot)'),
+ $basket->get('basket_name'),
+ substr($snapSum, 0, 7)
+ );
+
+ $this->actions()->add([
+ Link::create(
+ $this->translate('Show Basket'),
+ 'director/basket',
+ ['name' => $basket->get('basket_name')],
+ ['data-base-target' => '_next']
+ ),
+ Link::create(
+ $this->translate('Restore'),
+ $this->url()->with('action', 'restore'),
+ null,
+ ['class' => 'icon-rewind']
+ ),
+ Link::create(
+ $this->translate('Download'),
+ $this->url()
+ ->with([
+ 'action' => 'download',
+ 'dbResourceName' => $this->getDbResourceName()
+ ]),
+ null,
+ [
+ 'class' => 'icon-download',
+ 'target' => '_blank'
+ ]
+ ),
+ ]);
+
+ $properties = new NameValueTable();
+ $properties->addNameValuePairs([
+ $this->translate('Created') => DateFormatter::formatDateTime($snapshot->get('ts_create') / 1000),
+ $this->translate('Content Checksum') => bin2hex($snapshot->get('content_checksum')),
+ ]);
+ $this->content()->add($properties);
+
+ if ($this->params->get('action') === 'restore') {
+ $form = new RestoreBasketForm();
+ $form
+ ->setSnapshot($snapshot)
+ ->handleRequest();
+ $this->content()->add($form);
+ $targetDbName = $form->getValue('target_db');
+ $connection = $form->getDb();
+ } else {
+ $targetDbName = null;
+ $connection = $this->db();
+ }
+
+ $json = $snapshot->getJsonDump();
+ $this->addSingleTab($this->translate('Snapshot'));
+ $all = Json::decode($json);
+ $exporter = new Exporter($this->db());
+ $fieldResolver = new BasketSnapshotFieldResolver($all, $connection);
+ foreach ($all as $type => $objects) {
+ if ($type === 'Datafield') {
+ // TODO: we should now be able to show all fields and link
+ // to a "diff" for the ones that should be created
+ // $this->content()->add(Html::tag('h2', sprintf('+%d Datafield(s)', count($objects))));
+ continue;
+ }
+ $table = new NameValueTable();
+ $table->addAttributes([
+ 'class' => ['table-basket-changes', 'table-row-selectable'],
+ 'data-base-target' => '_next',
+ ]);
+ foreach ($objects as $key => $object) {
+ $linkParams = [
+ 'name' => $basket->get('basket_name'),
+ 'checksum' => $this->params->get('checksum'),
+ 'ts' => $this->params->get('ts'),
+ 'type' => $type,
+ 'key' => $key,
+ ];
+ if ($targetDbName !== null) {
+ $linkParams['target_db'] = $targetDbName;
+ }
+ try {
+ $current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
+ if ($current === null) {
+ $table->addNameValueRow(
+ $key,
+ Link::create(
+ Html::tag('strong', ['style' => 'color: green'], $this->translate('new')),
+ 'director/basket/snapshotobject',
+ $linkParams
+ )
+ );
+ continue;
+ }
+ $currentExport = $exporter->export($current);
+ $fieldResolver->tweakTargetIds($currentExport);
+
+ // Ignore originalId
+ if (isset($currentExport->originalId)) {
+ unset($currentExport->originalId);
+ }
+ if (isset($object->originalId)) {
+ unset($object->originalId);
+ }
+ $hasChanged = ! CompareBasketObject::equals($currentExport, $object);
+ $table->addNameValueRow(
+ $key,
+ $hasChanged
+ ? Link::create(
+ Html::tag('strong', ['style' => 'color: orange'], $this->translate('modified')),
+ 'director/basket/snapshotobject',
+ $linkParams
+ )
+ : Html::tag('span', ['style' => 'color: green'], $this->translate('unchanged'))
+ );
+ } catch (Exception $e) {
+ $table->addNameValueRow(
+ $key,
+ Html::tag('a', sprintf(
+ '%s (%s:%d)',
+ $e->getMessage(),
+ basename($e->getFile()),
+ $e->getLine()
+ ))
+ );
+ }
+ }
+ $this->content()->add(Html::tag('h2', $type));
+ $this->content()->add($table);
+ }
+ $this->content()->add(Html::tag('div', ['style' => 'height: 5em']));
+ }
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function snapshotobjectAction()
+ {
+ $basket = $this->requireBasket();
+ $snapshot = BasketSnapshot::load([
+ 'basket_uuid' => $basket->get('uuid'),
+ 'ts_create' => $this->params->getRequired('ts'),
+ ], $this->db());
+ $snapshotUrl = $this->url()->without('type')->without('key')->setPath('director/basket/snapshot');
+ $type = $this->params->get('type');
+ $key = $this->params->get('key');
+
+ $this->addTitle($this->translate('Single Object Diff'));
+ $this->content()->add(Hint::info(Html::sprintf(
+ $this->translate('Comparing %s "%s" from Snapshot "%s" to current config'),
+ $type,
+ $key,
+ Link::create(
+ substr(bin2hex($snapshot->get('content_checksum')), 0, 7),
+ $snapshotUrl,
+ null,
+ ['data-base-target' => '_next']
+ )
+ )));
+ $this->actions()->add([
+ Link::create(
+ $this->translate('back'),
+ $snapshotUrl,
+ null,
+ ['class' => 'icon-left-big']
+ ),
+ /*
+ Link::create(
+ $this->translate('Restore'),
+ $this->url()->with('action', 'restore'),
+ null,
+ ['class' => 'icon-rewind']
+ )
+ */
+ ]);
+ $exporter = new Exporter($this->db());
+ $json = $snapshot->getJsonDump();
+ $this->addSingleTab($this->translate('Snapshot'));
+ $objects = Json::decode($json);
+ $targetDbName = $this->params->get('target_db');
+ if ($targetDbName === null) {
+ $connection = $this->db();
+ } else {
+ $connection = Db::fromResourceName($targetDbName);
+ }
+ $fieldResolver = new BasketSnapshotFieldResolver($objects, $connection);
+ $objectFromBasket = $objects->$type->$key;
+ unset($objectFromBasket->originalId);
+ CompareBasketObject::normalize($objectFromBasket);
+ $objectFromBasket = Json::encode($objectFromBasket, JSON_PRETTY_PRINT);
+ $current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
+ if ($current === null) {
+ $current = '';
+ } else {
+ $exported = $exporter->export($current);
+ $fieldResolver->tweakTargetIds($exported);
+ unset($exported->originalId);
+ CompareBasketObject::normalize($exported);
+ $current = Json::encode($exported, JSON_PRETTY_PRINT);
+ }
+
+ if ($current === $objectFromBasket) {
+ $this->content()->add([
+ Hint::ok('Basket equals current object'),
+ Html::tag('pre', $current)
+ ]);
+ } else {
+ $this->content()->add(new InlineDiff(new PhpDiff($current, $objectFromBasket)));
+ }
+ }
+
+ /**
+ * @return Basket
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function requireBasket()
+ {
+ return Basket::load($this->params->getRequired('name'), $this->db());
+ }
+}
diff --git a/application/controllers/BasketsController.php b/application/controllers/BasketsController.php
new file mode 100644
index 0000000..6b50b62
--- /dev/null
+++ b/application/controllers/BasketsController.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use ipl\Html\Html;
+use gipfl\IcingaWeb2\Link;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Table\BasketTable;
+
+class BasketsController extends ActionController
+{
+ protected $isApified = false;
+
+ public function indexAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->addSingleTab($this->translate('Baskets'));
+ $this->actions()->add([
+ Link::create(
+ $this->translate('Create'),
+ 'director/basket/create',
+ null,
+ ['class' => 'icon-plus']
+ ),
+ Link::create(
+ $this->translate('Upload'),
+ 'director/basket/upload',
+ null,
+ ['class' => 'icon-upload']
+ ),
+ ]);
+ $this->addTitle($this->translate('Configuration Baskets'));
+ $this->content()->add(Html::tag('p', $this->translate(
+ 'A Configuration Basket references specific Configuration'
+ . ' Objects or all objects of a specific type. It has been'
+ . ' designed to share Templates, Import/Sync strategies and'
+ . ' other base Configuration Objects. It is not a tool to'
+ . ' operate with single Hosts or Services.'
+ )));
+ $this->content()->add(Html::tag('p', $this->translate(
+ 'You can create Basket snapshots at any time, this will persist'
+ . ' a serialized representation of all involved objects at that'
+ . ' moment in time. Snapshots can be exported, imported, shared'
+ . ' and restored - to the very same or another Director instance.'
+ )));
+ $table = (new BasketTable($this->db()))
+ ->setAttribute('data-base-target', '_self');
+ // TODO: temporarily disabled, this was a thing in dipl
+ if (/*$table->hasSearch() || */count($table)) {
+ $table->renderTo($this);
+ }
+ }
+}
diff --git a/application/controllers/BranchController.php b/application/controllers/BranchController.php
new file mode 100644
index 0000000..3b36e83
--- /dev/null
+++ b/application/controllers/BranchController.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\Diff\HtmlRenderer\SideBySideDiff;
+use gipfl\Diff\PhpDiff;
+use gipfl\IcingaWeb2\Widget\NameValueTable;
+use Icinga\Module\Director\Data\Db\DbObjectStore;
+use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
+use Icinga\Module\Director\Db\Branch\BranchActivity;
+use Icinga\Module\Director\Db\Branch\BranchStore;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\SyncRule;
+use Icinga\Module\Director\PlainObjectRenderer;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Controller\BranchHelper;
+use Icinga\Module\Director\Web\Widget\IcingaConfigDiff;
+use ipl\Html\Html;
+
+class BranchController extends ActionController
+{
+ use BranchHelper;
+
+ public function init()
+ {
+ parent::init();
+ IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
+ SyncRule::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
+ }
+
+ protected function checkDirectorPermissions()
+ {
+ }
+
+ public function activityAction()
+ {
+ $this->assertPermission('director/showconfig');
+ $ts = $this->params->getRequired('ts');
+ $activity = BranchActivity::load($ts, $this->db());
+ $store = new BranchStore($this->db());
+ $branch = $store->fetchBranchByUuid($activity->getBranchUuid());
+ if ($branch->isSyncPreview()) {
+ $this->addSingleTab($this->translate('Sync Preview'));
+ $this->addTitle($this->translate('Expected Modification'));
+ } else {
+ $this->addSingleTab($this->translate('Activity'));
+ $this->addTitle($this->translate('Branch Activity'));
+ }
+
+ $this->content()->add($this->prepareActivityInfo($activity));
+ $this->showActivity($activity);
+ }
+
+ protected function prepareActivityInfo(BranchActivity $activity)
+ {
+ $table = new NameValueTable();
+ $table->addNameValuePairs([
+ $this->translate('Author') => $activity->getAuthor(),
+ $this->translate('Date') => date('Y-m-d H:i:s', $activity->getTimestamp()),
+ $this->translate('Action') => $activity->getAction()
+ . ' ' . preg_replace('/^icinga_/', '', $activity->getObjectTable())
+ . ' ' . $activity->getObjectName(),
+ // $this->translate('Actions') => ['Undo form'],
+ ]);
+ return $table;
+ }
+
+ protected function leftFromActivity(BranchActivity $activity)
+ {
+ if ($activity->isActionCreate()) {
+ return null;
+ }
+ $object = DbObjectTypeRegistry::newObject($activity->getObjectTable(), [], $this->db());
+ $properties = $this->objectTypeFirst($activity->getFormerProperties()->jsonSerialize());
+ foreach ($properties as $key => $value) {
+ $object->set($key, $value);
+ }
+
+ return $object;
+ }
+
+ protected function rightFromActivity(BranchActivity $activity)
+ {
+ if ($activity->isActionDelete()) {
+ return null;
+ }
+ $object = DbObjectTypeRegistry::newObject($activity->getObjectTable(), [], $this->db());
+ if (! $activity->isActionCreate()) {
+ foreach ($activity->getFormerProperties()->jsonSerialize() as $key => $value) {
+ $object->set($key, $value);
+ }
+ }
+ $properties = $this->objectTypeFirst($activity->getModifiedProperties()->jsonSerialize());
+ foreach ($properties as $key => $value) {
+ $object->set($key, $value);
+ }
+
+ return $object;
+ }
+
+ protected function objectTypeFirst($properties)
+ {
+ $properties = (array) $properties;
+ if (isset($properties['object_type'])) {
+ $type = $properties['object_type'];
+ unset($properties['object_type']);
+ $properties = ['object_type' => $type] + $properties;
+ }
+
+ return $properties;
+ }
+
+ protected function showActivity(BranchActivity $activity)
+ {
+ $left = $this->leftFromActivity($activity);
+ $right = $this->rightFromActivity($activity);
+ if ($left instanceof IcingaObject || $right instanceof IcingaObject) {
+ $this->content()->add(new IcingaConfigDiff(
+ $left ? $left->toSingleIcingaConfig() : $this->createEmptyConfig(),
+ $right ? $right->toSingleIcingaConfig() : $this->createEmptyConfig()
+ ));
+ } else {
+ $this->content()->add([
+ Html::tag('h3', $this->translate('Modification')),
+ new SideBySideDiff(new PhpDiff(
+ PlainObjectRenderer::render($left->getProperties()),
+ PlainObjectRenderer::render($right->getProperties())
+ ))
+ ]);
+ }
+ }
+
+ protected function createEmptyConfig()
+ {
+ return new IcingaConfig($this->db());
+ }
+}
diff --git a/application/controllers/CommandController.php b/application/controllers/CommandController.php
new file mode 100644
index 0000000..de0ba54
--- /dev/null
+++ b/application/controllers/CommandController.php
@@ -0,0 +1,126 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\Web\Widget\Hint;
+use Icinga\Module\Director\Objects\IcingaCommandArgument;
+use Icinga\Module\Director\Web\Table\BranchedIcingaCommandArgumentTable;
+use ipl\Html\Html;
+use Icinga\Module\Director\Forms\IcingaCommandArgumentForm;
+use Icinga\Module\Director\Objects\IcingaCommand;
+use Icinga\Module\Director\Resolver\CommandUsage;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Module\Director\Web\Table\IcingaCommandArgumentTable;
+
+class CommandController extends ObjectController
+{
+ /**
+ * @throws \Icinga\Exception\AuthenticationException
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function init()
+ {
+ parent::init();
+ $o = $this->object;
+ if ($o && ! $o->isExternal()) {
+ if ($this->getBranch()->isBranch()) {
+ $urlParams = ['uuid' => $o->getUniqueId()->toString()];
+ } else {
+ $urlParams = ['name' => $o->getObjectName()];
+ }
+ $this->tabs()->add('arguments', [
+ 'url' => 'director/command/arguments',
+ 'urlParams' => $urlParams,
+ 'label' => 'Arguments'
+ ]);
+ }
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Zend_Db_Select_Exception
+ */
+ public function indexAction()
+ {
+ if (! $this->getRequest()->isApiRequest()) {
+ $this->showUsage();
+ }
+ parent::indexAction();
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Security\SecurityException
+ * @throws \Zend_Db_Select_Exception
+ */
+ public function renderAction()
+ {
+ if ($this->object->isExternal()) {
+ $this->showUsage();
+ }
+
+ parent::renderAction();
+ }
+
+ /**
+ * @throws \Zend_Db_Select_Exception
+ */
+ protected function showUsage()
+ {
+ /** @var IcingaCommand $command */
+ $command = $this->object;
+ if ($command->isInUse()) {
+ $usage = new CommandUsage($command);
+ $this->content()->add(Hint::info(Html::sprintf(
+ $this->translate('This Command is currently being used by %s'),
+ Html::tag('span', null, $usage->getLinks())->setSeparator(', ')
+ ))->addAttributes([
+ 'data-base-target' => '_next'
+ ]));
+ } else {
+ $this->content()->add(Hint::warning($this->translate('This Command is currently not in use')));
+ }
+ }
+
+ public function argumentsAction()
+ {
+ $p = $this->params;
+ /** @var IcingaCommand $o */
+ $o = $this->object;
+ $this->tabs()->activate('arguments');
+ $this->addTitle($this->translate('Command arguments: %s'), $o->getObjectName());
+ $form = (new IcingaCommandArgumentForm)
+ ->setBranch($this->getBranch())
+ ->setCommandObject($o);
+ if ($argument = $p->shift('argument')) {
+ $this->addBackLink('director/command/arguments', [
+ 'name' => $p->get('name')
+ ]);
+ if ($this->branch->isBranch()) {
+ $arguments = $o->arguments();
+ $argument = $arguments->get($argument);
+ // IcingaCommandArgument::create((array) $arguments->get($argument)->toFullPlainObject());
+ // $argument->setBeingLoadedFromDb();
+ } else {
+ $argument = IcingaCommandArgument::load([
+ 'command_id' => $o->get('id'),
+ 'argument_name' => $argument
+ ], $this->db());
+ }
+ $form->setObject($argument);
+ }
+ $form->handleRequest();
+ $this->content()->add([$form]);
+ if ($this->branch->isBranch()) {
+ (new BranchedIcingaCommandArgumentTable($o, $this->getBranch()))->renderTo($this);
+ } else {
+ (new IcingaCommandArgumentTable($o, $this->getBranch()))->renderTo($this);
+ }
+ }
+
+ protected function hasBasketSupport()
+ {
+ return true;
+ }
+}
diff --git a/application/controllers/CommandsController.php b/application/controllers/CommandsController.php
new file mode 100644
index 0000000..246028f
--- /dev/null
+++ b/application/controllers/CommandsController.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class CommandsController extends ObjectsController
+{
+ public function indexAction()
+ {
+ parent::indexAction();
+ $validTypes = ['object', 'external_object'];
+ $type = $this->params->get('type', 'object');
+ if (! in_array($type, $validTypes)) {
+ $type = 'object';
+ }
+
+ $this->table->setType($type);
+ }
+}
diff --git a/application/controllers/CommandtemplateController.php b/application/controllers/CommandtemplateController.php
new file mode 100644
index 0000000..ca5f827
--- /dev/null
+++ b/application/controllers/CommandtemplateController.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\IcingaCommand;
+use Icinga\Module\Director\Web\Controller\TemplateController;
+
+class CommandtemplateController extends TemplateController
+{
+ protected function requireTemplate()
+ {
+ return IcingaCommand::load([
+ 'object_name' => $this->params->get('name')
+ ], $this->db());
+ }
+}
diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
new file mode 100644
index 0000000..3f8a105
--- /dev/null
+++ b/application/controllers/ConfigController.php
@@ -0,0 +1,539 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\Diff\HtmlRenderer\SideBySideDiff;
+use gipfl\Diff\PhpDiff;
+use gipfl\Web\Widget\Hint;
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Db\Branch\Branch;
+use Icinga\Module\Director\Deployment\DeploymentStatus;
+use Icinga\Module\Director\Forms\DeployConfigForm;
+use Icinga\Module\Director\Forms\SettingsForm;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Objects\DirectorDeploymentLog;
+use Icinga\Module\Director\Settings;
+use Icinga\Module\Director\Web\Controller\BranchHelper;
+use Icinga\Module\Director\Web\Table\ActivityLogTable;
+use Icinga\Module\Director\Web\Table\BranchActivityTable;
+use Icinga\Module\Director\Web\Table\ConfigFileDiffTable;
+use Icinga\Module\Director\Web\Table\DeploymentLogTable;
+use Icinga\Module\Director\Web\Table\GeneratedConfigFileTable;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Tabs\InfraTabs;
+use Icinga\Module\Director\Web\Widget\ActivityLogInfo;
+use Icinga\Module\Director\Web\Widget\DeployedConfigInfoHeader;
+use Icinga\Module\Director\Web\Widget\ShowConfigFile;
+use Icinga\Web\Notification;
+use Exception;
+use RuntimeException;
+use ipl\Html\Html;
+use ipl\Html\HtmlString;
+use gipfl\IcingaWeb2\Icon;
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+
+class ConfigController extends ActionController
+{
+ use BranchHelper;
+
+ protected $isApified = true;
+
+ protected function checkDirectorPermissions()
+ {
+ }
+
+ /**
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function deploymentsAction()
+ {
+ if ($this->sendNotFoundForRestApi()) {
+ return;
+ }
+ $this->assertPermission('director/deploy');
+ $this->addTitle($this->translate('Deployments'));
+ try {
+ if (DirectorDeploymentLog::hasUncollected($this->db())) {
+ $this->setAutorefreshInterval(2);
+ } else {
+ $this->setAutorefreshInterval(20);
+ }
+ } catch (Exception $e) {
+ $this->content()->prepend(Hint::warning($e->getMessage()));
+ // No problem, Icinga might be reloading
+ }
+
+ if (! $this->getBranch()->isBranch()) {
+ // TODO: a form!
+ $this->actions()->add(Link::create(
+ $this->translate('Render config'),
+ 'director/config/store',
+ null,
+ ['class' => 'icon-wrench']
+ ));
+ }
+
+ $this->tabs(new InfraTabs($this->Auth()))->activate('deploymentlog');
+ $table = new DeploymentLogTable($this->db());
+ try {
+ // Move elsewhere
+ $table->setActiveStageName(
+ $this->api()->getActiveStageName()
+ );
+ } catch (Exception $e) {
+ // Don't care
+ }
+
+ $table->renderTo($this);
+ }
+
+ /**
+ * @throws NotFoundError
+ * @throws \Icinga\Module\Director\Exception\DuplicateKeyException
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function deployAction()
+ {
+ $request = $this->getRequest();
+ if (! $request->isApiRequest()) {
+ throw new NotFoundError('Not found');
+ }
+
+ if (! $request->isPost()) {
+ throw new RuntimeException(sprintf(
+ 'Unsupported method: %s',
+ $request->getMethod()
+ ));
+ }
+ $this->assertPermission('director/deploy');
+
+ // TODO: require POST
+ $checksum = $this->params->get('checksum');
+ if ($checksum) {
+ $config = IcingaConfig::load(hex2bin($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);
+ }
+ }
+
+ public function deploymentStatusAction()
+ {
+ if ($this->sendNotFoundUnlessRestApi()) {
+ return;
+ }
+ $db = $this->db();
+ $api = $this->api();
+ $status = new DeploymentStatus($db, $api);
+ $result = $status->getDeploymentStatus($this->params->get('configs'), $this->params->get('activities'));
+
+ $this->sendJson($this->getResponse(), (object) $result);
+ }
+
+ /**
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function activitiesAction()
+ {
+ if ($this->sendNotFoundForRestApi()) {
+ return;
+ }
+ $this->assertPermission('director/audit');
+ $this->showOptionalBranchActivity();
+ $this->setAutorefreshInterval(10);
+ $this->tabs(new InfraTabs($this->Auth()))->activate('activitylog');
+ $this->addTitle($this->translate('Activity Log'));
+ $lastDeployedId = $this->db()->getLastDeploymentActivityLogId();
+ $table = new ActivityLogTable($this->db());
+ $table->setLastDeployedId($lastDeployedId);
+ if ($idRangeEx = $this->url()->getParam('idRangeEx')) {
+ $table->applyFilter(Filter::fromQueryString($idRangeEx));
+ }
+ $filter = Filter::fromQueryString(
+ $this->url()->without(['page', 'limit', 'q', 'idRangeEx'])->getQueryString()
+ );
+ $table->applyFilter($filter);
+ if ($this->url()->hasParam('author')) {
+ $this->actions()->add(Link::create(
+ $this->translate('All changes'),
+ $this->url()
+ ->without(['author', 'page']),
+ null,
+ ['class' => 'icon-users', 'data-base-target' => '_self']
+ ));
+ } else {
+ $this->actions()->add(Link::create(
+ $this->translate('My changes'),
+ $this->url()
+ ->with('author', $this->Auth()->getUser()->getUsername())
+ ->without('page'),
+ null,
+ ['class' => 'icon-user', 'data-base-target' => '_self']
+ ));
+ }
+ if ($this->hasPermission('director/deploy') && ! $this->getBranch()->isBranch()) {
+ if ($this->db()->hasDeploymentEndpoint()) {
+ $this->actions()->add(DeployConfigForm::load()
+ ->setDb($this->db())
+ ->setApi($this->api())
+ ->handleRequest());
+ }
+ }
+
+ $table->renderTo($this);
+ }
+
+ /**
+ * @throws IcingaException
+ * @throws \Icinga\Exception\Http\HttpNotFoundException
+ * @throws \Icinga\Exception\ProgrammingError
+ */
+ public function activityAction()
+ {
+ if ($this->sendNotFoundForRestApi()) {
+ return;
+ }
+ $this->assertPermission('director/showconfig');
+ $p = $this->params;
+ $info = new ActivityLogInfo(
+ $this->db(),
+ $p->get('type'),
+ $p->get('name')
+ );
+
+ $info->setChecksum($p->get('checksum'))
+ ->setId($p->get('id'));
+
+ $this->tabs($info->getTabs($this->url()));
+ $info->showTab($this->params->get('show'));
+
+ $this->addTitle($info->getTitle());
+ $this->controls()->prepend($info->getPagination($this->url()));
+ $this->content()->add($info);
+ }
+
+ /**
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function settingsAction()
+ {
+ if ($this->sendNotFoundForRestApi()) {
+ return;
+ }
+ $this->assertPermission('director/admin');
+
+ $this->addSingleTab($this->translate('Settings'))
+ ->addTitle($this->translate('Global Director Settings'));
+ $this->content()->add(
+ SettingsForm::load()
+ ->setSettings(new Settings($this->db()))
+ ->handleRequest()
+ );
+ }
+
+ /**
+ * Show all files for a given config
+ *
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function filesAction()
+ {
+ if ($this->sendNotFoundForRestApi()) {
+ return;
+ }
+ $this->assertPermission('director/showconfig');
+ $config = IcingaConfig::load(
+ hex2bin($this->params->getRequired('checksum')),
+ $this->db()
+ );
+ $deploymentId = $this->params->get('deployment_id');
+
+ $tabs = $this->tabs();
+ if ($deploymentId) {
+ $tabs->add('deployment', [
+ 'label' => $this->translate('Deployment'),
+ 'url' => 'director/deployment',
+ 'urlParams' => ['id' => $deploymentId]
+ ]);
+ }
+
+ $tabs->add('config', [
+ 'label' => $this->translate('Config'),
+ 'url' => $this->url(),
+ ])->activate('config');
+
+ $this->addTitle($this->translate('Generated config'));
+ $this->content()->add(new DeployedConfigInfoHeader(
+ $config,
+ $this->db(),
+ $this->api(),
+ $this->getBranch(),
+ $deploymentId
+ ));
+
+ GeneratedConfigFileTable::load($config, $this->db())
+ ->setActiveFilename($this->params->get('active_file'))
+ ->setDeploymentId($deploymentId)
+ ->renderTo($this);
+ }
+
+ /**
+ * Show a single file
+ *
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function fileAction()
+ {
+ if ($this->sendNotFoundForRestApi()) {
+ return;
+ }
+ $this->assertPermission('director/showconfig');
+ $filename = $this->params->getRequired('file_path');
+ $this->configTabs()->add('file', array(
+ 'label' => $this->translate('Rendered file'),
+ 'url' => $this->url(),
+ ))->activate('file');
+
+ $params = $this->getConfigTabParams();
+ if ('deployment' === $this->params->get('backTo')) {
+ $this->addBackLink('director/deployment', ['id' => $params['deployment_id']]);
+ } else {
+ $params['active_file'] = $filename;
+ $this->addBackLink('director/config/files', $params);
+ }
+
+ $config = IcingaConfig::load(hex2bin($this->params->get('config_checksum')), $this->db());
+ $this->addTitle($this->translate('Config file "%s"'), $filename);
+ $this->content()->add(new ShowConfigFile(
+ $config->getFile($filename),
+ $this->params->get('highlight'),
+ $this->params->get('highlightSeverity')
+ ));
+ }
+
+ /**
+ * TODO: Check if this can be removed
+ *
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function storeAction()
+ {
+ $this->assertPermission('director/deploy');
+ try {
+ $config = IcingaConfig::generate($this->db());
+ } catch (Exception $e) {
+ Notification::error($e->getMessage());
+ $this->redirectNow('director/config/deployments');
+ }
+ $this->redirectNow(
+ Url::fromPath(
+ 'director/config/files',
+ array('checksum' => $config->getHexChecksum())
+ )
+ );
+ }
+
+ /**
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function diffAction()
+ {
+ if ($this->sendNotFoundForRestApi()) {
+ return;
+ }
+ $this->assertPermission('director/showconfig');
+
+ $db = $this->db();
+ $this->addTitle($this->translate('Config diff'));
+ $this->addSingleTab($this->translate('Config diff'));
+
+ $leftSum = $this->params->get('left');
+ $rightSum = $this->params->get('right');
+
+ $configs = $db->enumDeployedConfigs();
+ foreach (array($leftSum, $rightSum) as $sum) {
+ if (! array_key_exists($sum, $configs)) {
+ $configs[$sum] = substr($sum, 0, 7);
+ }
+ }
+
+ $baseUrl = $this->url()->without(['left', 'right']);
+ $this->content()->add(Html::tag('form', ['action' => (string) $baseUrl, 'method' => 'GET'], [
+ new HtmlString($this->view->formSelect(
+ 'left',
+ $leftSum,
+ ['class' => 'autosubmit', 'style' => 'width: 37%'],
+ [null => $this->translate('- please choose -')] + $configs
+ )),
+ Link::create(
+ Icon::create('flapping'),
+ $baseUrl,
+ ['left' => $rightSum, 'right' => $leftSum]
+ ),
+ new HtmlString($this->view->formSelect(
+ 'right',
+ $rightSum,
+ ['class' => 'autosubmit', 'style' => 'width: 37%'],
+ [null => $this->translate('- please choose -')] + $configs
+ )),
+ ]));
+
+ if ($rightSum === null || $leftSum === null || ! strlen($rightSum) || ! strlen($leftSum)) {
+ return;
+ }
+ ConfigFileDiffTable::load($leftSum, $rightSum, $this->db())->renderTo($this);
+ }
+
+ /**
+ * @throws IcingaException
+ * @throws \Icinga\Exception\MissingParameterException
+ */
+ public function filediffAction()
+ {
+ if ($this->sendNotFoundForRestApi()) {
+ return;
+ }
+ $this->assertPermission('director/showconfig');
+
+ $p = $this->params;
+ $db = $this->db();
+ $leftSum = $p->getRequired('left');
+ $rightSum = $p->getRequired('right');
+ $filename = $p->getRequired('file_path');
+
+ $left = IcingaConfig::load(hex2bin($leftSum), $db);
+ $right = IcingaConfig::load(hex2bin($rightSum), $db);
+
+ $this
+ ->addTitle($this->translate('Config file "%s"'), $filename)
+ ->addSingleTab($this->translate('Diff'))
+ ->content()->add(new SideBySideDiff(new PhpDiff(
+ $left->getFile($filename),
+ $right->getFile($filename)
+ )));
+ }
+
+ protected function showOptionalBranchActivity()
+ {
+ if ($this->url()->hasParam('idRangeEx')) {
+ return;
+ }
+ $branch = $this->getBranch();
+ if ($branch->isBranch() && (int) $this->params->get('page', '1') === 1) {
+ $table = new BranchActivityTable($branch->getUuid(), $this->db());
+ if (count($table) > 0) {
+ $this->content()->add(Hint::info(Html::sprintf($this->translate(
+ 'The following modifications are visible in this %s only...'
+ ), Branch::requireHook()->linkToBranch(
+ $branch,
+ $this->Auth(),
+ $this->translate('configuration branch')
+ ))));
+ $this->content()->add($table);
+ $this->content()->add(Html::tag('br'));
+ $this->content()->add(Hint::ok($this->translate(
+ '...and the modifications below are already in the main branch:'
+ )));
+ $this->content()->add(Html::tag('br'));
+ }
+ }
+ }
+
+ /**
+ * @param $checksum
+ */
+ protected function deploymentSucceeded($checksum)
+ {
+ if ($this->getRequest()->isApiRequest()) {
+ $this->sendJson($this->getResponse(), (object) array('checksum' => $checksum));
+ return;
+ } else {
+ $url = Url::fromPath('director/config/deployments');
+ Notification::success(
+ $this->translate('Config has been submitted, validation is going on')
+ );
+ $this->redirectNow($url);
+ }
+ }
+
+ /**
+ * @param $checksum
+ * @param null $error
+ */
+ protected function deploymentFailed($checksum, $error = null)
+ {
+ $extra = $error ? ': ' . $error: '';
+
+ if ($this->getRequest()->isApiRequest()) {
+ $this->sendJsonError($this->getResponse(), 'Config deployment failed' . $extra);
+ return;
+ } else {
+ $url = Url::fromPath('director/config/files', array('checksum' => $checksum));
+ Notification::error(
+ $this->translate('Config deployment failed') . $extra
+ );
+ $this->redirectNow($url);
+ }
+ }
+
+ /**
+ * @return \gipfl\IcingaWeb2\Widget\Tabs
+ */
+ protected function configTabs()
+ {
+ $tabs = $this->tabs();
+
+ if ($this->hasPermission('director/deploy')
+ && $deploymentId = $this->params->get('deployment_id')
+ ) {
+ $tabs->add('deployment', [
+ 'label' => $this->translate('Deployment'),
+ 'url' => 'director/deployment',
+ 'urlParams' => ['id' => $deploymentId]
+ ]);
+ }
+
+ if ($this->hasPermission('director/showconfig')) {
+ $tabs->add('config', [
+ 'label' => $this->translate('Config'),
+ 'url' => 'director/config/files',
+ 'urlParams' => $this->getConfigTabParams()
+ ]);
+ }
+
+ return $tabs;
+ }
+
+ protected function getConfigTabParams()
+ {
+ $params = [
+ '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/CustomvarController.php b/application/controllers/CustomvarController.php
new file mode 100644
index 0000000..f0d4574
--- /dev/null
+++ b/application/controllers/CustomvarController.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Table\CustomvarVariantsTable;
+
+class CustomvarController extends ActionController
+{
+ public function variantsAction()
+ {
+ $varName = $this->params->getRequired('name');
+ $this->addSingleTab($this->translate('Custom Variable'))
+ ->addTitle($this->translate('Custom Variable variants: %s'), $varName);
+ CustomvarVariantsTable::create($this->db(), $varName)->renderTo($this);
+ }
+}
diff --git a/application/controllers/DaemonController.php b/application/controllers/DaemonController.php
new file mode 100644
index 0000000..ab0038f
--- /dev/null
+++ b/application/controllers/DaemonController.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\Web\Widget\Hint;
+use Icinga\Application\Icinga;
+use Icinga\Module\Director\Daemon\RunningDaemonInfo;
+use Icinga\Module\Director\Web\Tabs\MainTabs;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Widget\BackgroundDaemonDetails;
+use Icinga\Module\Director\Web\Widget\Documentation;
+use ipl\Html\Html;
+
+class DaemonController extends ActionController
+{
+ public function indexAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->tabs(new MainTabs($this->Auth(), $this->getDbResourceName()))->activate('daemon');
+ $this->setTitle($this->translate('Director Background Daemon'));
+ // Avoiding layout issues:
+ $this->content()->add(Html::tag('h1', $this->translate('Director Background Daemon')));
+ // TODO: move dashboard titles into controls. Or figure out whether 2.7 "broke" this
+
+ $error = null;
+ try {
+ $db = $this->db()->getDbAdapter();
+ $daemons = $db->fetchAll(
+ $db->select()->from('director_daemon_info')->order('fqdn')->order('username')->order('pid')
+ );
+ } catch (\Exception $e) {
+ $daemons = [];
+ $error = $e->getMessage();
+ }
+
+ if (empty($daemons)) {
+ $documentation = new Documentation(Icinga::app(), $this->Auth());
+ $message = Html::sprintf($this->translate(
+ 'The Icinga Director Background Daemon is not running.'
+ . ' Please check our %s in case you need step by step instructions'
+ . ' showing you how to fix this.'
+ ), $documentation->getModuleLink(
+ $this->translate('documentation'),
+ 'director',
+ '75-Background-Daemon',
+ $this->translate('Icinga Director Background Daemon')
+ ));
+ $this->content()->add(Hint::error([
+ $message,
+ ($error ? [Html::tag('br'), Html::tag('strong', $error)] : null),
+ ]));
+ return;
+ }
+
+ try {
+ foreach ($daemons as $daemon) {
+ $info = new RunningDaemonInfo($daemon);
+ $this->content()->add([new BackgroundDaemonDetails($info, $daemon) /*, $logWindow*/]);
+ }
+ } catch (\Exception $e) {
+ $this->content()->add(Hint::error($e->getMessage()));
+ }
+ }
+}
diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php
new file mode 100644
index 0000000..95c1cd0
--- /dev/null
+++ b/application/controllers/DashboardController.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Tabs\MainTabs;
+use Icinga\Module\Director\Dashboard\Dashboard;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Form\DbSelectorForm;
+
+class DashboardController extends ActionController
+{
+ protected function checkDirectorPermissions()
+ {
+ // No special permissions required, override parent method
+ }
+
+ protected function addDbSelection()
+ {
+ if ($this->isMultiDbSetup()) {
+ $form = new DbSelectorForm(
+ $this->getResponse(),
+ $this->Window(),
+ $this->listAllowedDbResourceNames()
+ );
+ $this->content()->add($form);
+ $form->handleRequest($this->getServerRequest());
+ }
+ }
+
+ public function indexAction()
+ {
+ if ($this->getRequest()->isGet()) {
+ $this->setAutorefreshInterval(10);
+ }
+
+ $mainDashboards = [
+ 'Objects',
+ 'Alerts',
+ 'Branches',
+ 'Automation',
+ 'Deployment',
+ 'Director',
+ 'Data',
+ ];
+ $this->setTitle($this->translate('Icinga Director - Main Dashboard'));
+ $names = $this->params->getValues('name', $mainDashboards);
+ if (! $this->params->has('name')) {
+ $this->addDbSelection();
+ }
+ if (count($names) === 1) {
+ $name = $names[0];
+ $dashboard = Dashboard::loadByName($name, $this->db());
+ $this->tabs($dashboard->getTabs())->activate($name);
+ } else {
+ $this->tabs(new MainTabs($this->Auth(), $this->getDbResourceName()))->activate('main');
+ }
+
+ $cntDashboards = 0;
+ foreach ($names as $name) {
+ if ($name instanceof Dashboard) {
+ $dashboard = $name;
+ } else {
+ $dashboard = Dashboard::loadByName($name, $this->db());
+ }
+ if ($dashboard->isAvailable()) {
+ $cntDashboards++;
+ $this->content()->add($dashboard);
+ }
+ }
+
+ if ($cntDashboards === 0) {
+ $msg = $this->translate(
+ 'No dashboard available, you might have not enough permissions'
+ );
+ $this->content()->add($msg);
+ }
+ }
+}
diff --git a/application/controllers/DataController.php b/application/controllers/DataController.php
new file mode 100644
index 0000000..ae4bbcf
--- /dev/null
+++ b/application/controllers/DataController.php
@@ -0,0 +1,406 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\Web\Widget\Hint;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Forms\DirectorDatalistEntryForm;
+use Icinga\Module\Director\Forms\DirectorDatalistForm;
+use Icinga\Module\Director\Forms\IcingaServiceDictionaryMemberForm;
+use Icinga\Module\Director\Objects\DirectorDatalist;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\PlainObjectRenderer;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader;
+use Icinga\Module\Director\Web\Table\CustomvarTable;
+use Icinga\Module\Director\Web\Table\DatafieldCategoryTable;
+use Icinga\Module\Director\Web\Table\DatafieldTable;
+use Icinga\Module\Director\Web\Table\DatalistEntryTable;
+use Icinga\Module\Director\Web\Table\DatalistTable;
+use Icinga\Module\Director\Web\Tabs\DataTabs;
+use gipfl\IcingaWeb2\Link;
+use InvalidArgumentException;
+use ipl\Html\Html;
+use ipl\Html\Table;
+
+class DataController extends ActionController
+{
+ public function listsAction()
+ {
+ $this->addTitle($this->translate('Data lists'));
+ $this->actions()->add(
+ Link::create($this->translate('Add'), 'director/data/list', null, [
+ 'class' => 'icon-plus',
+ 'data-base-target' => '_next'
+ ])
+ );
+
+ $this->tabs(new DataTabs())->activate('datalist');
+ (new DatalistTable($this->db()))->renderTo($this);
+ }
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function listAction()
+ {
+ $form = DirectorDatalistForm::load()
+ ->setSuccessUrl('director/data/lists')
+ ->setDb($this->db());
+
+ if ($name = $this->params->get('name')) {
+ $list = $this->requireList('name');
+ $form->setObject($list);
+ $this->addListActions($list);
+ $this->addTitle(
+ $this->translate('Data List: %s'),
+ $list->get('list_name')
+ )->addListTabs($name, 'list');
+ } else {
+ $this
+ ->addTitle($this->translate('Add a new Data List'))
+ ->addSingleTab($this->translate('Data List'));
+ }
+
+ $this->content()->add($form->handleRequest());
+ }
+
+ public function fieldsAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->tabs(new DataTabs())->activate('datafield');
+ $this->addTitle($this->translate('Data Fields'));
+ $this->actions()->add(Link::create(
+ $this->translate('Add'),
+ 'director/datafield/add',
+ null,
+ [
+ 'class' => 'icon-plus',
+ 'data-base-target' => '_next',
+ ]
+ ));
+
+ (new DatafieldTable($this->db()))->renderTo($this);
+ }
+
+ public function fieldcategoriesAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->tabs(new DataTabs())->activate('datafieldcategory');
+ $this->addTitle($this->translate('Data Field Categories'));
+ $this->actions()->add(Link::create(
+ $this->translate('Add'),
+ 'director/datafieldcategory/add',
+ null,
+ [
+ 'class' => 'icon-plus',
+ 'data-base-target' => '_next',
+ ]
+ ));
+
+ (new DatafieldCategoryTable($this->db()))->renderTo($this);
+ }
+
+ public function varsAction()
+ {
+ $this->tabs(new DataTabs())->activate('customvars');
+ $this->addTitle($this->translate('Custom Vars - Overview'));
+ (new CustomvarTable($this->db()))->renderTo($this);
+ }
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function listentryAction()
+ {
+ $entryName = $this->params->get('entry_name');
+ $list = $this->requireList('list');
+ $this->addListActions($list);
+ $listId = $list->get('id');
+ $listName = $list->get('list_name');
+ $title = $title = $this->translate('List Entries') . ': ' . $listName;
+ $this->addTitle($title);
+
+ $form = DirectorDatalistEntryForm::load()
+ ->setSuccessUrl('director/data/listentry', ['list' => $listName])
+ ->setList($list);
+
+ if (null !== $entryName) {
+ $form->loadObject([
+ 'list_id' => $listId,
+ 'entry_name' => $entryName
+ ]);
+ $this->actions()->add(Link::create(
+ $this->translate('back'),
+ 'director/data/listentry',
+ ['list' => $listName],
+ ['class' => 'icon-left-big']
+ ));
+ }
+ $form->handleRequest();
+
+ $this->addListTabs($listName, 'entries');
+
+ $table = new DatalistEntryTable($this->db());
+ $table->getAttributes()->set('data-base-target', '_self');
+ $table->setList($list);
+ $this->content()->add([$form, $table]);
+ }
+
+ public function dictionaryAction()
+ {
+ $connection = $this->db();
+ $this->addSingleTab('Nested Dictionary');
+ $varName = $this->params->get('varname');
+ $instance = $this->url()->getParam('instance');
+ $action = $this->url()->getParam('action');
+ $object = $this->requireObject();
+
+ if ($instance || $action) {
+ $this->actions()->add(
+ Link::create($this->translate('Back'), $this->url()->without(['action', 'instance']), null, [
+ 'class' => 'icon-edit'
+ ])
+ );
+ } else {
+ $this->actions()->add(
+ Link::create($this->translate('Add'), $this->url(), [
+ 'action' => 'add'
+ ], [
+ 'class' => 'icon-edit'
+ ])
+ );
+ }
+ $subjects = $this->prepareSubjectsLabel($object, $varName);
+ $fieldLoader = new IcingaObjectFieldLoader($object);
+ $instances = $this->getCurrentInstances($object, $varName);
+
+ if (empty($instances)) {
+ $this->content()->add(Hint::info(sprintf(
+ $this->translate('No %s have been created yet'),
+ $subjects
+ )));
+ } else {
+ $this->content()->add($this->prepareInstancesTable($instances));
+ }
+
+ $field = $this->getFieldByName($fieldLoader, $varName);
+ $template = $object::load([
+ 'object_name' => $field->getSetting('template_name')
+ ], $connection);
+
+ $form = new IcingaServiceDictionaryMemberForm();
+ $form->setDb($connection);
+ if ($instance) {
+ $instanceObject = $object::create([
+ 'imports' => [$template],
+ 'object_name' => $instance,
+ 'vars' => $instances[$instance]
+ ], $connection);
+ $form->setObject($instanceObject);
+ } elseif ($action === 'add') {
+ $form->presetImports([$template->getObjectName()]);
+ } else {
+ return;
+ }
+ if ($instance) {
+ if (! isset($instances[$instance])) {
+ throw new NotFoundError("There is no such instance: $instance");
+ }
+ $subTitle = sprintf($this->translate('Modify instance: %s'), $instance);
+ } else {
+ $subTitle = $this->translate('Add a new instance');
+ }
+
+ $this->content()->add(Html::tag('h2', ['style' => 'margin-top: 2em'], $subTitle));
+ $form->handleRequest($this->getRequest());
+ $this->content()->add($form);
+ if ($form->succeeded()) {
+ $virtualObject = $form->getObject();
+ $name = $virtualObject->getObjectName();
+ $params = $form->getObject()->getVars();
+ $instances[$name] = $params;
+ if ($name !== $instance) { // Has been renamed
+ unset($instances[$instance]);
+ }
+ ksort($instances);
+ $object->set("vars.$varName", (object)$instances);
+ $object->store();
+ $this->redirectNow($this->url()->without(['instance', 'action']));
+ } elseif ($form->shouldBeDeleted()) {
+ unset($instances[$instance]);
+ if (empty($instances)) {
+ $object->set("vars.$varName", null)->store();
+ } else {
+ $object->set("vars.$varName", (object)$instances)->store();
+ }
+ $this->redirectNow($this->url()->without(['instance', 'action']));
+ }
+ }
+
+ protected function requireObject()
+ {
+ $connection = $this->db();
+ $hostName = $this->params->getRequired('host');
+ $serviceName = $this->params->get('service');
+ if ($serviceName) {
+ $host = IcingaHost::load($hostName, $connection);
+ $object = IcingaService::load([
+ 'host_id' => $host->get('id'),
+ 'object_name' => $serviceName,
+ ], $connection);
+ } else {
+ $object = IcingaHost::load($hostName, $connection);
+ }
+
+ if (! $object->isObject()) {
+ throw new InvalidArgumentException(sprintf(
+ 'Only single objects allowed, %s is a %s',
+ $object->getObjectName(),
+ $object->get('object_type')
+ ));
+ }
+ return $object;
+ }
+
+ protected function shorten($string, $maxLen)
+ {
+ if (strlen($string) <= $maxLen) {
+ return $string;
+ }
+
+ return substr($string, 0, $maxLen) . '...';
+ }
+
+ protected function getFieldByName(IcingaObjectFieldLoader $loader, $name)
+ {
+ foreach ($loader->getFields() as $field) {
+ if ($field->get('varname') === $name) {
+ return $field;
+ }
+ }
+
+ throw new InvalidArgumentException("Found no configured field for '$name'");
+ }
+
+ /**
+ * @param IcingaObject $object
+ * @param $varName
+ * @return array
+ */
+ protected function getCurrentInstances(IcingaObject $object, $varName)
+ {
+ $currentVars = $object->getVars();
+ if (isset($currentVars->$varName)) {
+ $currentValue = $currentVars->$varName;
+ } else {
+ $currentValue = (object)[];
+ }
+ if (is_object($currentValue)) {
+ $currentValue = (array)$currentValue;
+ } else {
+ throw new InvalidArgumentException(sprintf(
+ '"%s" is not a valid Dictionary',
+ json_encode($currentValue)
+ ));
+ }
+ return $currentValue;
+ }
+
+ /**
+ * @param array $currentValue
+ * @param $subjects
+ * @return Hint|Table
+ */
+ protected function prepareInstancesTable(array $currentValue)
+ {
+ $table = new Table();
+ $table->addAttributes([
+ 'class' => 'common-table table-row-selectable'
+ ]);
+ $table->getHeader()->add(
+ Table::row([
+ $this->translate('Key / Instance'),
+ $this->translate('Properties')
+ ], ['style' => 'text-align: left'], 'th')
+ );
+ foreach ($currentValue as $key => $item) {
+ $table->add(Table::row([
+ Link::create($key, $this->url()->with('instance', $key)),
+ str_replace("\n", ' ', $this->shorten(PlainObjectRenderer::render($item), 512))
+ ]));
+ }
+
+ return $table;
+ }
+
+ /**
+ * @param IcingaObject $object
+ * @param $varName
+ * @return string
+ */
+ protected function prepareSubjectsLabel(IcingaObject $object, $varName)
+ {
+ if ($object instanceof IcingaService) {
+ $hostName = $object->get('host');
+ $subjects = $object->getObjectName() . " ($varName)";
+ } else {
+ $hostName = $object->getObjectName();
+ $subjects = sprintf(
+ $this->translate('%s instances'),
+ $varName
+ );
+ }
+ $this->addTitle(sprintf(
+ $this->translate('%s on %s'),
+ $subjects,
+ $hostName
+ ));
+ return $subjects;
+ }
+
+ protected function addListActions(DirectorDatalist $list)
+ {
+ $this->actions()->add(
+ Link::create(
+ $this->translate('Add to Basket'),
+ 'director/basket/add',
+ [
+ 'type' => 'DataList',
+ 'names' => $list->getUniqueIdentifier()
+ ],
+ ['class' => 'icon-tag']
+ )
+ );
+ }
+
+ /**
+ * @param $paramName
+ * @return DirectorDatalist
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function requireList($paramName)
+ {
+ return DirectorDatalist::load($this->params->getRequired($paramName), $this->db());
+ }
+
+ protected function addListTabs($name, $activate)
+ {
+ $this->tabs()->add('list', [
+ 'url' => 'director/data/list',
+ 'urlParams' => ['name' => $name],
+ 'label' => $this->translate('Edit list'),
+ ])->add('entries', [
+ 'url' => 'director/data/listentry',
+ 'urlParams' => ['list' => $name],
+ 'label' => $this->translate('List entries'),
+ ])->activate($activate);
+
+ return $this;
+ }
+}
diff --git a/application/controllers/DatafieldController.php b/application/controllers/DatafieldController.php
new file mode 100644
index 0000000..afad317
--- /dev/null
+++ b/application/controllers/DatafieldController.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Forms\DirectorDatafieldForm;
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class DatafieldController extends ActionController
+{
+ public function addAction()
+ {
+ $this->indexAction();
+ }
+
+ public function editAction()
+ {
+ $this->indexAction();
+ }
+
+ public function indexAction()
+ {
+ $form = DirectorDatafieldForm::load()
+ ->setDb($this->db());
+
+ if ($id = $this->params->get('id')) {
+ $form->loadObject((int) $id);
+ $this->addTitle(
+ $this->translate('Modify %s'),
+ $form->getObject()->varname
+ );
+ $this->addSingleTab($this->translate('Edit a Field'));
+ } else {
+ $this->addTitle($this->translate('Add a new Data Field'));
+ $this->addSingleTab($this->translate('New Field'));
+ }
+
+ $form->handleRequest();
+ $this->content()->add($form);
+ }
+}
diff --git a/application/controllers/DatafieldcategoryController.php b/application/controllers/DatafieldcategoryController.php
new file mode 100644
index 0000000..32c76ef
--- /dev/null
+++ b/application/controllers/DatafieldcategoryController.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Forms\DirectorDatafieldCategoryForm;
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class DatafieldcategoryController extends ActionController
+{
+ public function addAction()
+ {
+ $this->indexAction();
+ }
+
+ public function editAction()
+ {
+ $this->indexAction();
+ }
+
+ public function indexAction()
+ {
+ $edit = false;
+
+ if ($name = $this->params->get('name')) {
+ $edit = true;
+ }
+
+ $form = DirectorDatafieldCategoryForm::load()
+ ->setDb($this->db());
+
+ if ($edit) {
+ $form->loadObject($name);
+ $this->addTitle(
+ $this->translate('Modify %s'),
+ $form->getObject()->category_name
+ );
+ $this->addSingleTab($this->translate('Edit a Category'));
+ } else {
+ $this->addTitle($this->translate('Add a new Data Field Category'));
+ $this->addSingleTab($this->translate('New Category'));
+ }
+
+ $form->handleRequest();
+ $this->content()->add($form);
+ }
+}
diff --git a/application/controllers/DependenciesController.php b/application/controllers/DependenciesController.php
new file mode 100644
index 0000000..276dd63
--- /dev/null
+++ b/application/controllers/DependenciesController.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class DependenciesController extends ObjectsController
+{
+ protected function addObjectsTabs()
+ {
+ $res = parent::addObjectsTabs();
+ $this->tabs()->remove('index');
+ return $res;
+ }
+}
diff --git a/application/controllers/DependencyController.php b/application/controllers/DependencyController.php
new file mode 100644
index 0000000..9d21cd5
--- /dev/null
+++ b/application/controllers/DependencyController.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Forms\IcingaDependencyForm;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Module\Director\Objects\IcingaDependency;
+
+class DependencyController extends ObjectController
+{
+ protected $apply;
+
+ /**
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function init()
+ {
+ parent::init();
+
+ if ($apply = $this->params->get('apply')) {
+ $this->apply = IcingaDependency::load(
+ array('object_name' => $apply, 'object_type' => 'template'),
+ $this->db()
+ );
+ }
+ }
+
+ /**
+ * @return \Icinga\Module\Director\Objects\IcingaObject
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\InvalidPropertyException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function loadObject()
+ {
+ if ($this->object === null) {
+ if ($name = $this->params->get('name')) {
+ $params = array('object_name' => $name);
+ $db = $this->db();
+
+ $this->object = IcingaDependency::load($params, $db);
+ } else {
+ parent::loadObject();
+ }
+ }
+
+ return $this->object;
+ }
+
+ /**
+ * Hint: this is never being called. Why?
+ *
+ * @param $form
+ */
+ protected function beforeHandlingAddRequest($form)
+ {
+ /** @var IcingaDependencyForm $form */
+ if ($this->apply) {
+ $form->createApplyRuleFor($this->apply);
+ }
+ }
+}
diff --git a/application/controllers/DependencytemplateController.php b/application/controllers/DependencytemplateController.php
new file mode 100644
index 0000000..e2bc49d
--- /dev/null
+++ b/application/controllers/DependencytemplateController.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\IcingaDependency;
+use Icinga\Module\Director\Web\Controller\TemplateController;
+
+class DependencytemplateController extends TemplateController
+{
+ protected function requireTemplate()
+ {
+ return IcingaDependency::load([
+ 'object_name' => $this->params->get('name')
+ ], $this->db());
+ }
+}
diff --git a/application/controllers/DeploymentController.php b/application/controllers/DeploymentController.php
new file mode 100644
index 0000000..2d35f3c
--- /dev/null
+++ b/application/controllers/DeploymentController.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\DirectorDeploymentLog;
+use Icinga\Module\Director\Web\Widget\DeploymentInfo;
+
+class DeploymentController extends ActionController
+{
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/deploy');
+ }
+
+ public function indexAction()
+ {
+ $info = new DeploymentInfo(DirectorDeploymentLog::load(
+ $this->params->get('id'),
+ $this->db()
+ ));
+ $this->addTitle($this->translate('Deployment details'));
+ $this->tabs(
+ $info->getTabs($this->getAuth(), $this->getRequest())
+ )->activate('deployment');
+ $this->content()->add($info);
+ }
+}
diff --git a/application/controllers/EndpointController.php b/application/controllers/EndpointController.php
new file mode 100644
index 0000000..e8a4fb0
--- /dev/null
+++ b/application/controllers/EndpointController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectController;
+
+class EndpointController extends ObjectController
+{
+}
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/HealthController.php b/application/controllers/HealthController.php
new file mode 100644
index 0000000..4fac4d2
--- /dev/null
+++ b/application/controllers/HealthController.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Tabs\MainTabs;
+use ipl\Html\Html;
+use Icinga\Module\Director\Web\Widget\HealthCheckPluginOutput;
+use Icinga\Module\Director\Health;
+use Icinga\Module\Director\Web\Controller\ActionController;
+
+class HealthController extends ActionController
+{
+ public function indexAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->tabs(new MainTabs($this->Auth(), $this->getDbResourceName()))->activate('health');
+ $this->setTitle($this->translate('Director Health'));
+ $health = new Health();
+ $health->setDbResourceName($this->getDbResourceName());
+ $output = new HealthCheckPluginOutput($health);
+ $this->content()->add($output);
+ $this->content()->add([
+ Html::tag('h1', ['class' => 'icon-pin'], $this->translate('Hint: Check Plugin')),
+ Html::tag('p', $this->translate(
+ 'Did you know that you can run this entire Health Check'
+ . ' (or just some sections) as an Icinga Check on a regular'
+ . ' base?'
+ ))
+ ]);
+ }
+}
diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php
new file mode 100644
index 0000000..e107d22
--- /dev/null
+++ b/application/controllers/HostController.php
@@ -0,0 +1,637 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\Web\Widget\Hint;
+use Icinga\Module\Director\Monitoring;
+use Icinga\Module\Director\Web\Table\ObjectsTableService;
+use ipl\Html\Html;
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+use gipfl\IcingaWeb2\Widget\Tabs;
+use Exception;
+use Icinga\Module\Director\CustomVariable\CustomVariableDictionary;
+use Icinga\Module\Director\Db\AppliedServiceSetLoader;
+use Icinga\Module\Director\DirectorObject\Lookup\ServiceFinder;
+use Icinga\Module\Director\Forms\IcingaAddServiceForm;
+use Icinga\Module\Director\Forms\IcingaServiceForm;
+use Icinga\Module\Director\Forms\IcingaServiceSetForm;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+use Icinga\Module\Director\Restriction\HostgroupRestriction;
+use Icinga\Module\Director\Repository\IcingaTemplateRepository;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Module\Director\Web\SelfService;
+use Icinga\Module\Director\Web\Table\IcingaHostAppliedForServiceTable;
+use Icinga\Module\Director\Web\Table\IcingaHostAppliedServicesTable;
+use Icinga\Module\Director\Web\Table\IcingaServiceSetServiceTable;
+
+class HostController extends ObjectController
+{
+ protected function checkDirectorPermissions()
+ {
+ if ($this->isServiceAction() && (new Monitoring())->authCanEditService(
+ $this->Auth(),
+ $this->getParam('name'),
+ $this->getParam('service')
+ )) {
+ return;
+ }
+
+ if ($this->isServicesReadOnlyAction()) {
+ $this->assertPermission('director/monitoring/services-ro');
+ return;
+ }
+
+ if ($this->hasPermission('director/hosts')) { // faster
+ return;
+ }
+
+ if ($this->canModifyHostViaMonitoringPermissions($this->getParam('name'))) {
+ return;
+ }
+
+ $this->assertPermission('director/hosts'); // complain about default hosts permission
+ }
+
+ protected function isServicesReadOnlyAction()
+ {
+ return in_array($this->getRequest()->getActionName(), [
+ 'servicesro',
+ 'findservice',
+ 'invalidservice',
+ ]);
+ }
+
+ protected function isServiceAction()
+ {
+ return in_array($this->getRequest()->getActionName(), [
+ 'servicesro',
+ 'findservice',
+ 'invalidservice',
+ 'servicesetservice',
+ 'appliedservice',
+ 'inheritedservice',
+ ]);
+ }
+
+ protected function canModifyHostViaMonitoringPermissions($hostname)
+ {
+ if ($this->hasPermission('director/monitoring/hosts')) {
+ $monitoring = new Monitoring();
+ return $monitoring->authCanEditHost($this->Auth(), $hostname);
+ }
+
+ return false;
+ }
+
+ /**
+ * @return HostgroupRestriction
+ */
+ protected function getHostgroupRestriction()
+ {
+ return new HostgroupRestriction($this->db(), $this->Auth());
+ }
+
+ public function editAction()
+ {
+ parent::editAction();
+ $this->addOptionalMonitoringLink();
+ }
+
+ public function serviceAction()
+ {
+ $host = $this->getHostObject();
+ $this->addServicesHeader();
+ $this->addTitle($this->translate('Add Service to %s'), $host->getObjectName());
+ $this->content()->add(
+ IcingaAddServiceForm::load()
+ ->setBranch($this->getBranch())
+ ->setHost($host)
+ ->setDb($this->db())
+ ->handleRequest()
+ );
+ }
+
+ public function servicesetAction()
+ {
+ $host = $this->getHostObject();
+ $this->addServicesHeader();
+ $this->addTitle($this->translate('Add Service Set to %s'), $host->getObjectName());
+
+ $this->content()->add(
+ IcingaServiceSetForm::load()
+ ->setBranch($this->getBranch())
+ ->setHost($host)
+ ->setDb($this->db())
+ ->handleRequest()
+ );
+ }
+
+ protected function addServicesHeader()
+ {
+ $host = $this->getHostObject();
+ $hostname = $host->getObjectName();
+ $this->tabs()->activate('services');
+
+ $this->actions()->add(Link::create(
+ $this->translate('Add service'),
+ 'director/host/service',
+ ['name' => $hostname],
+ ['class' => 'icon-plus']
+ ))->add(Link::create(
+ $this->translate('Add service set'),
+ 'director/host/serviceset',
+ ['name' => $hostname],
+ ['class' => 'icon-plus']
+ ));
+ }
+
+ public function findserviceAction()
+ {
+ $host = $this->getHostObject();
+ $this->redirectNow(
+ (new ServiceFinder($host, $this->getAuth()))
+ ->getRedirectionUrl($this->params->get('service'))
+ );
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function invalidserviceAction()
+ {
+ if (! $this->showInfoForNonDirectorService()) {
+ $this->content()->add(Hint::error(sprintf(
+ $this->translate('No such service: %s'),
+ $this->params->get('service')
+ )));
+ }
+
+ $this->servicesAction();
+ }
+
+ protected function showInfoForNonDirectorService()
+ {
+ try {
+ $api = $this->getApiIfAvailable();
+ if ($api) {
+ $name = $this->params->get('name') . '!' . $this->params->get('service');
+ $info = $api->getObject($name, 'Services');
+ if (isset($info->attrs->source_location)) {
+ $source = $info->attrs->source_location;
+ $this->content()->add(Hint::info(Html::sprintf(
+ 'The configuration for this object has not been rendered by'
+ . ' Icinga Director. You can find it on line %s in %s.',
+ Html::tag('strong', null, $source->first_line),
+ Html::tag('strong', null, $source->path)
+ )));
+ }
+ }
+
+ return true;
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function servicesAction()
+ {
+ $this->addServicesHeader();
+ $host = $this->getHostObject();
+ $this->addTitle($this->translate('Services: %s'), $host->getObjectName());
+ $branch = $this->getBranch();
+ $hostHasBeenCreatedInBranch = $branch->isBranch() && $host->get('id');
+ $content = $this->content();
+ $table = (new ObjectsTableService($this->db()))
+ ->setAuth($this->Auth())
+ ->setHost($host)
+ ->setBranch($branch)
+ ->setTitle($this->translate('Individual Service objects'))
+ ->removeQueryLimit();
+
+ if (count($table)) {
+ $content->add($table);
+ }
+
+ /** @var IcingaHost[] $parents */
+ $parents = IcingaTemplateRepository::instanceByObject($this->object)
+ ->getTemplatesFor($this->object, true);
+ foreach ($parents as $parent) {
+ $table = (new ObjectsTableService($this->db()))
+ ->setAuth($this->Auth())
+ ->setBranch($branch)
+ ->setHost($parent)
+ ->setInheritedBy($host)
+ ->removeQueryLimit();
+
+ if (count($table)) {
+ $content->add(
+ $table->setTitle(sprintf(
+ $this->translate('Inherited from %s'),
+ $parent->getObjectName()
+ ))
+ );
+ }
+ }
+
+ if (! $hostHasBeenCreatedInBranch) {
+ $this->addHostServiceSetTables($host);
+ }
+ foreach ($parents as $parent) {
+ $this->addHostServiceSetTables($parent, $host);
+ }
+
+ $appliedSets = AppliedServiceSetLoader::fetchForHost($host);
+ foreach ($appliedSets as $set) {
+ $title = sprintf($this->translate('%s (Applied Service set)'), $set->getObjectName());
+
+ $content->add(
+ IcingaServiceSetServiceTable::load($set)
+ // ->setHost($host)
+ ->setBranch($branch)
+ ->setAffectedHost($host)
+ ->setTitle($title)
+ ->removeQueryLimit()
+ );
+ }
+
+ $table = IcingaHostAppliedServicesTable::load($host)
+ ->setTitle($this->translate('Applied services'));
+
+ if (count($table)) {
+ $content->add($table);
+ }
+ }
+
+ /**
+ * Hint: this duplicates quite some logic from servicesAction. We might want
+ * to clean this up, but as soon as we store fully resolved Services this
+ * will be obsolete anyways
+ *
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Security\SecurityException
+ * @throws \Icinga\Exception\MissingParameterException
+ */
+ public function servicesroAction()
+ {
+ $this->assertPermission('director/monitoring/services-ro');
+ $host = $this->getHostObject();
+ $service = $this->params->getRequired('service');
+ $db = $this->db();
+ $branch = $this->getBranch();
+ $this->controls()->setTabs(new Tabs());
+ $this->addSingleTab($this->translate('Configuration (read-only)'));
+ $this->addTitle($this->translate('Services on %s'), $host->getObjectName());
+ $content = $this->content();
+
+ $table = (new ObjectsTableService($db))
+ ->setAuth($this->Auth())
+ ->setHost($host)
+ ->setBranch($branch)
+ ->setReadonly()
+ ->highlightService($service)
+ ->setTitle($this->translate('Individual Service objects'));
+
+ if (count($table)) {
+ $content->add($table);
+ }
+
+ /** @var IcingaHost[] $parents */
+ $parents = IcingaTemplateRepository::instanceByObject($this->object)
+ ->getTemplatesFor($this->object, true);
+ foreach ($parents as $parent) {
+ $table = (new ObjectsTableService($db))
+ ->setReadonly()
+ ->setBranch($branch)
+ ->setHost($parent)
+ ->highlightService($service)
+ ->setInheritedBy($host);
+ if (count($table)) {
+ $content->add(
+ $table->setTitle(sprintf(
+ 'Inherited from %s',
+ $parent->getObjectName()
+ ))
+ );
+ }
+ }
+
+ $this->addHostServiceSetTables($host);
+ foreach ($parents as $parent) {
+ $this->addHostServiceSetTables($parent, $host, $service);
+ }
+
+ $appliedSets = AppliedServiceSetLoader::fetchForHost($host);
+ foreach ($appliedSets as $set) {
+ $title = sprintf($this->translate('%s (Applied Service set)'), $set->getObjectName());
+
+ $content->add(
+ IcingaServiceSetServiceTable::load($set)
+ // ->setHost($host)
+ ->setBranch($branch)
+ ->setAffectedHost($host)
+ ->setReadonly()
+ ->highlightService($service)
+ ->setTitle($title)
+ );
+ }
+
+ $table = IcingaHostAppliedServicesTable::load($host)
+ ->setReadonly()
+ ->highlightService($service)
+ ->setTitle($this->translate('Applied services'));
+
+ if (count($table)) {
+ $content->add($table);
+ }
+ }
+
+ /**
+ * @param IcingaHost $host
+ * @param IcingaHost|null $affectedHost
+ */
+ protected function addHostServiceSetTables(IcingaHost $host, IcingaHost $affectedHost = null, $roService = null)
+ {
+ $db = $this->db();
+ if ($affectedHost === null) {
+ $affectedHost = $host;
+ }
+ if ($host->get('id') === null) {
+ return;
+ }
+
+ $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->get('id'));
+
+ $sets = IcingaServiceSet::loadAll($db, $query, 'object_name');
+ /** @var IcingaServiceSet $set*/
+ foreach ($sets as $name => $set) {
+ $title = sprintf($this->translate('%s (Service set)'), $name);
+ $table = IcingaServiceSetServiceTable::load($set)
+ ->setHost($host)
+ ->setBranch($this->getBranch())
+ ->setAffectedHost($affectedHost)
+ ->setTitle($title);
+ if ($roService) {
+ $table->setReadonly()->highlightService($roService);
+ }
+ $this->content()->add($table);
+ }
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function appliedserviceAction()
+ {
+ $db = $this->db();
+ $host = $this->getHostObject();
+ $serviceId = $this->params->get('service_id');
+ $parent = IcingaService::loadWithAutoIncId($serviceId, $db);
+ $serviceName = $parent->getObjectName();
+
+ $service = IcingaService::create([
+ 'imports' => $parent,
+ 'object_type' => 'apply',
+ 'object_name' => $serviceName,
+ 'host_id' => $host->get('id'),
+ 'vars' => $host->getOverriddenServiceVars($serviceName),
+ ], $db);
+
+ $this->addTitle(
+ $this->translate('Applied service: %s'),
+ $serviceName
+ );
+
+ $this->content()->add(
+ IcingaServiceForm::load()
+ ->setDb($db)
+ ->setBranch($this->getBranch())
+ ->setHost($host)
+ ->setApplyGenerated($parent)
+ ->setObject($service)
+ ->handleRequest()
+ );
+
+ $this->commonForServices();
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function inheritedserviceAction()
+ {
+ $db = $this->db();
+ $host = $this->getHostObject();
+ $serviceName = $this->params->get('service');
+ $from = IcingaHost::load($this->params->get('inheritedFrom'), $this->db());
+
+ $parent = IcingaService::load([
+ 'object_name' => $serviceName,
+ 'host_id' => $from->get('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([
+ 'object_type' => 'apply',
+ 'object_name' => $serviceName,
+ 'host_id' => $host->get('id'),
+ 'imports' => [$parent],
+ 'vars' => $host->getOverriddenServiceVars($serviceName),
+ ], $db);
+
+ $this->addTitle($this->translate('Inherited service: %s'), $serviceName);
+
+ $form = IcingaServiceForm::load()
+ ->setDb($db)
+ ->setBranch($this->getBranch())
+ ->setHost($host)
+ ->setInheritedFrom($from->getObjectName())
+ ->setObject($service)
+ ->handleRequest();
+ $this->content()->add($form);
+ $this->commonForServices();
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ 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->get('id'));
+
+ IcingaServiceSet::loadWithAutoIncId($db->fetchOne($query), $this->db())->delete();
+ $this->redirectNow(
+ Url::fromPath('director/host/services', array(
+ 'name' => $this->object->getObjectName()
+ ))
+ );
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function servicesetserviceAction()
+ {
+ $db = $this->db();
+ $host = $this->getHostObject();
+ $serviceName = $this->params->get('service');
+ $setParams = [
+ 'object_name' => $this->params->get('set'),
+ 'host_id' => $host->get('id')
+ ];
+ $setTemplate = IcingaServiceSet::load($this->params->get('set'), $db);
+ if (IcingaServiceSet::exists($setParams, $db)) {
+ $set = IcingaServiceSet::load($setParams, $db);
+ } else {
+ $set = $setTemplate;
+ }
+
+ $service = IcingaService::load([
+ 'object_name' => $serviceName,
+ 'service_set_id' => $setTemplate->get('id')
+ ], $this->db());
+ $service = IcingaService::create([
+ 'id' => $service->get('id'),
+ 'object_type' => 'apply',
+ 'object_name' => $serviceName,
+ 'host_id' => $host->get('id'),
+ 'imports' => $service->listImportNames(),
+ 'vars' => $host->getOverriddenServiceVars($serviceName),
+ ], $db);
+
+ // $set->copyVarsToService($service);
+ $this->addTitle(
+ $this->translate('%s on %s (from set: %s)'),
+ $serviceName,
+ $host->getObjectName(),
+ $set->getObjectName()
+ );
+
+ $form = IcingaServiceForm::load()
+ ->setDb($db)
+ ->setBranch($this->getBranch())
+ ->setHost($host)
+ ->setServiceSet($set)
+ ->setObject($service)
+ ->handleRequest();
+ $this->tabs()->activate('services');
+ $this->content()->add($form);
+ $this->commonForServices();
+ }
+
+ protected function commonForServices()
+ {
+ $host = $this->object;
+ $this->actions()->add(Link::create(
+ $this->translate('back'),
+ 'director/host/services',
+ ['name' => $host->getObjectName()],
+ ['class' => 'icon-left-big']
+ ));
+ $this->tabs()->activate('services');
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function agentAction()
+ {
+ $selfService = new SelfService($this->getHostObject(), $this->api());
+ if ($os = $this->params->get('download')) {
+ $selfService->handleLegacyAgentDownloads($os);
+ return;
+ }
+
+ $selfService->renderTo($this);
+ $this->tabs()->activate('agent');
+ }
+
+ protected function addOptionalMonitoringLink()
+ {
+ $host = $this->object;
+ try {
+ $mon = $this->monitoring();
+ if ($host->isObject()
+ && $mon->isAvailable()
+ && $mon->hasHost($host->getObjectName())
+ ) {
+ $this->actions()->add(Link::create(
+ $this->translate('Show'),
+ 'monitoring/host/show',
+ ['host' => $host->getObjectName()],
+ [
+ 'class' => 'icon-globe critical',
+ 'data-base-target' => '_next'
+ ]
+ ));
+
+ // Intentionally placed here, show it only for deployed Hosts
+ $this->addOptionalInspectLink();
+ }
+ } catch (Exception $e) {
+ // Silently ignore errors in the monitoring module
+ }
+ }
+
+ protected function addOptionalInspectLink()
+ {
+ if (! $this->hasPermission('director/inspect')) {
+ return;
+ }
+
+ $this->actions()->add(Link::create(
+ $this->translate('Inspect'),
+ 'director/inspect/object',
+ [
+ 'type' => 'host',
+ 'plural' => 'hosts',
+ 'name' => $this->object->getObjectName()
+ ],
+ [
+ 'class' => 'icon-zoom-in',
+ 'data-base-target' => '_next'
+ ]
+ ));
+ }
+
+ /**
+ * @return IcingaHost
+ */
+ protected function getHostObject()
+ {
+ assert($this->object instanceof IcingaHost);
+ return $this->object;
+ }
+}
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..0332072
--- /dev/null
+++ b/application/controllers/HostsController.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\IcingaWeb2\Url;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
+use Icinga\Module\Director\Forms\IcingaAddServiceForm;
+use Icinga\Module\Director\Forms\IcingaAddServiceSetForm;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+use gipfl\IcingaWeb2\Link;
+
+class HostsController extends ObjectsController
+{
+ protected $multiEdit = array(
+ 'imports',
+ 'groups',
+ 'disabled'
+ );
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/hosts');
+ }
+
+ public function editAction()
+ {
+ $url = clone($this->getRequest()->getUrl());
+ $url->setPath('director/hosts/addservice');
+
+ $urlSet = clone($url);
+ $urlSet->setPath('director/hosts/addserviceset');
+
+ parent::editAction();
+
+ $this->actions()->add(Link::create(
+ $this->translate('Add Service'),
+ $url,
+ null,
+ ['class' => 'icon-plus']
+ ))->add(Link::create(
+ $this->translate('Add Service Set'),
+ $urlSet,
+ null,
+ ['class' => 'icon-plus']
+ ));
+ }
+
+ public function edittemplatesAction()
+ {
+ parent::editAction();
+
+ $objects = $this->loadMultiObjectsFromParams();
+ $names = [];
+ /** @var ExportInterface $object */
+ foreach ($objects as $object) {
+ $names[] = $object->getUniqueIdentifier();
+ }
+
+ $url = Url::fromPath('director/basket/add', [
+ 'type' => 'HostTemplate',
+ ]);
+
+ $url->getParams()->addValues('names', $names);
+
+ $this->actions()->add(Link::create(
+ $this->translate('Add to Basket'),
+ $url,
+ null,
+ ['class' => 'icon-tag']
+ ));
+ }
+
+ public function addserviceAction()
+ {
+ $this->addSingleTab($this->translate('Add Service'));
+ $filter = Filter::fromQueryString($this->params->toString());
+
+ $objects = array();
+ $db = $this->db();
+ /** @var $filter FilterChain */
+ foreach ($filter->filters() as $sub) {
+ /** @var $sub FilterChain */
+ foreach ($sub->filters() as $ex) {
+ /** @var $ex FilterChain|FilterExpression */
+ if ($ex->isExpression() && $ex->getColumn() === 'name') {
+ $name = $ex->getExpression();
+ $objects[$name] = IcingaHost::load($name, $db);
+ }
+ }
+ }
+ $this->addTitle(
+ $this->translate('Add service to %d hosts'),
+ count($objects)
+ );
+
+ $this->content()->add(
+ IcingaAddServiceForm::load()
+ ->setHosts($objects)
+ ->setDb($this->db())
+ ->handleRequest()
+ );
+ }
+
+ public function addservicesetAction()
+ {
+ $this->addSingleTab($this->translate('Add Service Set'));
+ $filter = Filter::fromQueryString($this->params->toString());
+
+ $objects = array();
+ $db = $this->db();
+ /** @var $filter FilterChain */
+ foreach ($filter->filters() as $sub) {
+ /** @var $sub FilterChain */
+ foreach ($sub->filters() as $ex) {
+ /** @var $ex FilterChain|FilterExpression */
+ if ($ex->isExpression() && $ex->getColumn() === 'name') {
+ $name = $ex->getExpression();
+ $objects[$name] = IcingaHost::load($name, $db);
+ }
+ }
+ }
+ $this->addTitle(
+ $this->translate('Add Service Set to %d hosts'),
+ count($objects)
+ );
+
+ $this->content()->add(
+ IcingaAddServiceSetForm::load()
+ ->setHosts($objects)
+ ->setDb($this->db())
+ ->handleRequest()
+ );
+ }
+}
diff --git a/application/controllers/HosttemplateController.php b/application/controllers/HosttemplateController.php
new file mode 100644
index 0000000..a5bfc2b
--- /dev/null
+++ b/application/controllers/HosttemplateController.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Web\Controller\TemplateController;
+
+class HosttemplateController extends TemplateController
+{
+ protected function requireTemplate()
+ {
+ return IcingaHost::load([
+ 'object_name' => $this->params->get('name')
+ ], $this->db());
+ }
+}
diff --git a/application/controllers/ImportrunController.php b/application/controllers/ImportrunController.php
new file mode 100644
index 0000000..d0e34e5
--- /dev/null
+++ b/application/controllers/ImportrunController.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\ImportRun;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Table\ImportedrowsTable;
+
+class ImportrunController extends ActionController
+{
+ public function indexAction()
+ {
+ $importRun = ImportRun::load($this->params->getRequired('id'), $this->db());
+ $this->addTitle($this->translate('Import run'));
+ $this->addSingleTab($this->translate('Import run'));
+
+ $table = ImportedrowsTable::load($importRun);
+ if ($chosen = $this->params->get('chosenColumns')) {
+ $table->setColumns(preg_split('/,/', $chosen, -1, PREG_SPLIT_NO_EMPTY));
+ }
+
+ $table->renderTo($this);
+ }
+}
diff --git a/application/controllers/ImportsourceController.php b/application/controllers/ImportsourceController.php
new file mode 100644
index 0000000..cbddb9e
--- /dev/null
+++ b/application/controllers/ImportsourceController.php
@@ -0,0 +1,375 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use gipfl\Web\Widget\Hint;
+use Icinga\Module\Director\Data\Exporter;
+use Icinga\Module\Director\Db\Branch\Branch;
+use Icinga\Module\Director\Forms\ImportRowModifierForm;
+use Icinga\Module\Director\Forms\ImportSourceForm;
+use Icinga\Module\Director\Hook\ImportSourceHook;
+use Icinga\Module\Director\Web\ActionBar\AutomationObjectActionBar;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Web\Controller\BranchHelper;
+use Icinga\Module\Director\Web\Form\CloneImportSourceForm;
+use Icinga\Module\Director\Web\Table\ImportrunTable;
+use Icinga\Module\Director\Web\Table\ImportsourceHookTable;
+use Icinga\Module\Director\Web\Table\PropertymodifierTable;
+use Icinga\Module\Director\Web\Tabs\ImportsourceTabs;
+use Icinga\Module\Director\Web\Widget\ImportSourceDetails;
+use InvalidArgumentException;
+use gipfl\IcingaWeb2\Link;
+use ipl\Html\Error;
+use ipl\Html\Html;
+
+class ImportsourceController extends ActionController
+{
+ use BranchHelper;
+
+ /** @var ImportSource|null */
+ private $importSource;
+
+ private $id;
+
+ /**
+ * @throws \Icinga\Exception\AuthenticationException
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Security\SecurityException
+ */
+ public function init()
+ {
+ parent::init();
+ $id = $this->params->get('source_id', $this->params->get('id'));
+ if ($id !== null && is_numeric($id)) {
+ $this->id = (int) $id;
+ }
+
+ $tabs = $this->tabs(new ImportsourceTabs($this->id));
+ $action = $this->getRequest()->getActionName();
+ if ($tabs->has($action)) {
+ $tabs->activate($action);
+ }
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function addMainActions()
+ {
+ $this->actions(new AutomationObjectActionBar(
+ $this->getRequest()
+ ));
+ $source = $this->getImportSource();
+
+ $this->actions()->add(Link::create(
+ $this->translate('Add to Basket'),
+ 'director/basket/add',
+ [
+ 'type' => 'ImportSource',
+ 'names' => $source->getUniqueIdentifier()
+ ],
+ [
+ 'class' => 'icon-tag',
+ 'data-base-target' => '_next'
+ ]
+ ));
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function indexAction()
+ {
+ $this->addMainActions();
+ $source = $this->getImportSource();
+ if ($this->params->get('format') === 'json') {
+ $this->sendJson($this->getResponse(), (new Exporter($this->db()))->export($source));
+ return;
+ }
+ $this->addTitle(
+ $this->translate('Import source: %s'),
+ $source->get('source_name')
+ )->setAutorefreshInterval(10);
+ $branch = $this->getBranch();
+ if ($this->getBranch()->isBranch()) {
+ $this->content()->add(Hint::info(Html::sprintf($this->translate(
+ 'Please note that importing data will take place in your main Branch.'
+ . ' Modifications to Import Sources are not allowed while being in a Configuration Branch.'
+ . ' To get the full functionality, please deactivate %s'
+ ), Branch::requireHook()->linkToBranch($branch, $this->getAuth(), $branch->getName()))));
+ }
+ $this->content()->add(new ImportSourceDetails($source));
+ }
+
+ public function addAction()
+ {
+ $this->addTitle($this->translate('Add import source'));
+ if ($this->showNotInBranch($this->translate('Creating Import Sources'))) {
+ return;
+ }
+
+ $this->content()->add(
+ ImportSourceForm::load()->setDb($this->db())
+ ->setSuccessUrl('director/importsources')
+ ->handleRequest()
+ );
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function editAction()
+ {
+ $this->addMainActions();
+ $this->activateTabWithPostfix($this->translate('Modify'));
+ if ($this->showNotInBranch($this->translate('Modifying Import Sources'))) {
+ return;
+ }
+ $form = ImportSourceForm::load()
+ ->setObject($this->getImportSource())
+ ->setListUrl('director/importsources')
+ ->handleRequest();
+ $this->addTitle(
+ $this->translate('Import source: %s'),
+ $form->getObject()->get('source_name')
+ )->setAutorefreshInterval(10);
+
+ $this->content()->add($form);
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function cloneAction()
+ {
+ $this->addMainActions();
+ $this->activateTabWithPostfix($this->translate('Clone'));
+ if ($this->showNotInBranch($this->translate('Cloning Import Sources'))) {
+ return;
+ }
+ $source = $this->getImportSource();
+ $this->addTitle('Clone: %s', $source->get('source_name'));
+ $form = new CloneImportSourceForm($source);
+ $this->content()->add($form);
+ $form->on(CloneImportSourceForm::ON_SUCCESS, function (CloneImportSourceForm $form) {
+ $this->getResponse()->redirectAndExit($form->getSuccessUrl());
+ });
+ $form->handleRequest($this->getServerRequest());
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function previewAction()
+ {
+ $source = $this->getImportSource();
+
+ $this->addTitle(
+ $this->translate('Import source preview: %s'),
+ $source->get('source_name')
+ );
+ $fetchUrl = clone($this->url());
+
+ $this->actions()->add(Link::create(
+ $this->translate('Download JSON'),
+ $fetchUrl->setPath('director/importsource/fetch'),
+ null,
+ [
+ 'target' => '_blank',
+ 'class' => 'icon-download',
+ ]
+ ))->add(Link::create('[..]', '#', null, [
+ 'onclick' => 'javascript:$("table.raw-data-table").toggleClass("collapsed");'
+ ]));
+ try {
+ (new ImportsourceHookTable())->setImportSource($source)->renderTo($this);
+ } catch (Exception $e) {
+ $this->content()->add(Error::show($e));
+ }
+ }
+
+ /**
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Module\Director\Exception\DuplicateKeyException
+ */
+ public function fetchAction()
+ {
+ $response = $this->getResponse();
+ try {
+ $source = $this->getImportSource();
+ $source->checkForChanges();
+ $hook = ImportSourceHook::forImportSource($source);
+ $data = $hook->fetchData();
+ $source->applyModifiers($data);
+
+ $filename = sprintf(
+ "director-importsource-%d_%s.json",
+ $this->getParam('id'),
+ date('YmdHis')
+ );
+ $response->setHeader('Content-Type', 'application/json', true);
+ $response->setHeader('Content-disposition', "attachment; filename=$filename", true);
+ $response->sendHeaders();
+ $this->sendJson($this->getResponse(), $data);
+ } catch (Exception $e) {
+ $this->sendJsonError($response, $e->getMessage());
+ }
+ // TODO: this is not clean
+ if (\ob_get_level()) {
+ \ob_end_flush();
+ }
+ exit;
+ }
+
+ /**
+ * @return ImportSource
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function requireImportSourceAndAddModifierTable()
+ {
+ $source = $this->getImportSource();
+ $table = PropertymodifierTable::load($source, $this->url());
+ if ($this->getBranch()->isBranch()) {
+ $table->setReadOnly();
+ } else {
+ $table->handleSortPriorityActions($this->getRequest(), $this->getResponse());
+ }
+ $table->renderTo($this);
+
+ return $source;
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function modifierAction()
+ {
+ $source = $this->requireImportSourceAndAddModifierTable();
+ $this->addTitle($this->translate('Property modifiers: %s'), $source->get('source_name'));
+ $this->addAddLink(
+ $this->translate('Add property modifier'),
+ 'director/importsource/addmodifier',
+ ['source_id' => $source->get('id')],
+ '_self'
+ );
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function historyAction()
+ {
+ $source = $this->getImportSource();
+ $this->addTitle($this->translate('Import run history: %s'), $source->get('source_name'));
+
+ // TODO: temporarily disabled, find a better place for stats:
+ // $this->view->stats = $this->db()->fetchImportStatistics();
+ ImportrunTable::load($source)->renderTo($this);
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function addmodifierAction()
+ {
+ $source = $this->requireImportSourceAndAddModifierTable();
+ $this->addTitle(
+ $this->translate('%s: add Property Modifier'),
+ $source->get('source_name')
+ )->addBackToModifiersLink($source);
+ $this->tabs()->activate('modifier');
+
+ if ($this->showNotInBranch($this->translate('Modifying Import Sources'))) {
+ return;
+ }
+
+ $this->content()->prepend(
+ ImportRowModifierForm::load()->setDb($this->db())
+ ->setSource($source)
+ ->setSuccessUrl(
+ 'director/importsource/modifier',
+ ['source_id' => $source->get('id')]
+ )->handleRequest()
+ );
+ }
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function editmodifierAction()
+ {
+ // We need to load the table AFTER adding the title, otherwise search
+ // will not be placed next to the title
+ $source = $this->getImportSource();
+
+ $this->addTitle(
+ $this->translate('%s: Property Modifier'),
+ $source->get('source_name')
+ )->addBackToModifiersLink($source);
+ $source = $this->requireImportSourceAndAddModifierTable();
+ $this->tabs()->activate('modifier');
+ if ($this->showNotInBranch($this->translate('Modifying Import Sources'))) {
+ return;
+ }
+
+ $listUrl = 'director/importsource/modifier?source_id='
+ . (int) $source->get('id');
+ $this->content()->prepend(
+ ImportRowModifierForm::load()->setDb($this->db())
+ ->loadObject((int) $this->params->getRequired('id'))
+ ->setListUrl($listUrl)
+ ->setSource($source)
+ ->handleRequest()
+ );
+ }
+
+ /**
+ * @return ImportSource
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function getImportSource()
+ {
+ if ($this->importSource === null) {
+ if ($this->id === null) {
+ throw new InvalidArgumentException('Got no ImportSource id');
+ }
+ $this->importSource = ImportSource::loadWithAutoIncId(
+ $this->id,
+ $this->db()
+ );
+ }
+
+ return $this->importSource;
+ }
+
+ protected function activateTabWithPostfix($title)
+ {
+ /** @var ImportsourceTabs $tabs */
+ $tabs = $this->tabs();
+ $tabs->activateMainWithPostfix($title);
+
+ return $this;
+ }
+
+ /**
+ * @param ImportSource $source
+ * @return $this
+ */
+ protected function addBackToModifiersLink(ImportSource $source)
+ {
+ $this->actions()->add(
+ Link::create(
+ $this->translate('back'),
+ 'director/importsource/modifier',
+ ['source_id' => $source->get('id')],
+ ['class' => 'icon-left-big']
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/application/controllers/ImportsourcesController.php b/application/controllers/ImportsourcesController.php
new file mode 100644
index 0000000..4287292
--- /dev/null
+++ b/application/controllers/ImportsourcesController.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\DirectorObject\Automation\ImportExport;
+use Icinga\Module\Director\Web\Table\ImportsourceTable;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Tabs\ImportTabs;
+
+class ImportsourcesController extends ActionController
+{
+ protected $isApified = true;
+
+ public function indexAction()
+ {
+ if ($this->getRequest()->isApiRequest()) {
+ switch (strtolower($this->getRequest()->getMethod())) {
+ case 'get':
+ $this->sendExport();
+ break;
+ case 'post':
+ $this->acceptImport($this->getRequest()->getRawBody());
+ break;
+ // TODO: put / replace all?
+ default:
+ $this->sendUnsupportedMethod();
+ }
+
+ return;
+ }
+
+ $this->addTitle($this->translate('Import source'))
+ ->setAutoRefreshInterval(10)
+ ->addAddLink(
+ $this->translate('Add a new Import Source'),
+ 'director/importsource/add'
+ )->tabs(new ImportTabs())->activate('importsource');
+
+ (new ImportsourceTable($this->db()))->renderTo($this);
+ }
+
+ /**
+ * @param $raw
+ */
+ protected function acceptImport($raw)
+ {
+ (new ImportExport($this->db()))->unserializeImportSources(json_decode($raw));
+ }
+
+ protected function sendExport()
+ {
+ $this->sendJson(
+ $this->getResponse(),
+ (new ImportExport($this->db()))->serializeAllImportSources()
+ );
+ }
+}
diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php
new file mode 100644
index 0000000..3f6c62e
--- /dev/null
+++ b/application/controllers/IndexController.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use gipfl\Web\Widget\Hint;
+use Icinga\Module\Director\Db\Migrations;
+use Icinga\Module\Director\Forms\ApplyMigrationsForm;
+use Icinga\Module\Director\Forms\KickstartForm;
+use ipl\Html\Html;
+
+class IndexController extends DashboardController
+{
+ protected $hasDeploymentEndpoint;
+
+ public function indexAction()
+ {
+ if ($this->Config()->get('db', 'resource')) {
+ $migrations = new Migrations($this->db());
+
+ if ($migrations->hasSchema()) {
+ if (!$this->hasDeploymentEndpoint()) {
+ $this->showKickstartForm();
+ }
+ }
+
+ if ($migrations->hasPendingMigrations()) {
+ $this->content()->prepend(
+ ApplyMigrationsForm::load()
+ ->setMigrations($migrations)
+ ->handleRequest()
+ );
+ } elseif ($migrations->hasBeenDowngraded()) {
+ $this->content()->add(Hint::warning(sprintf($this->translate(
+ 'Your DB schema (migration #%d) is newer than your code base.'
+ . ' Downgrading Icinga Director is not supported and might'
+ . ' lead to unexpected problems.'
+ ), $migrations->getLastMigrationNumber())));
+ }
+
+ if ($migrations->hasSchema()) {
+ parent::indexAction();
+ } else {
+ $this->addTitle(sprintf(
+ $this->translate('Icinga Director Setup: %s'),
+ $this->translate('Create Schema')
+ ));
+ $this->addSingleTab('Setup');
+ }
+ } else {
+ $this->addTitle(sprintf(
+ $this->translate('Icinga Director Setup: %s'),
+ $this->translate('Choose DB Resource')
+ ));
+ $this->addSingleTab('Setup');
+ $this->showKickstartForm();
+ }
+ }
+
+ protected function showKickstartForm()
+ {
+ $form = KickstartForm::load();
+ if ($name = $this->getPreferredDbResourceName()) {
+ $form->setDbResourceName($name);
+ }
+ $this->content()->prepend($form->handleRequest());
+ }
+
+ protected function hasDeploymentEndpoint()
+ {
+ try {
+ $this->hasDeploymentEndpoint = $this->db()->hasDeploymentEndpoint();
+ } catch (Exception $e) {
+ return false;
+ }
+
+ return $this->hasDeploymentEndpoint;
+ }
+}
diff --git a/application/controllers/InspectController.php b/application/controllers/InspectController.php
new file mode 100644
index 0000000..d631652
--- /dev/null
+++ b/application/controllers/InspectController.php
@@ -0,0 +1,200 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\IcingaWeb2\Link;
+use Icinga\Module\Director\Objects\IcingaEndpoint;
+use Icinga\Module\Director\PlainObjectRenderer;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Table\CoreApiFieldsTable;
+use Icinga\Module\Director\Web\Table\CoreApiObjectsTable;
+use Icinga\Module\Director\Web\Table\CoreApiPrototypesTable;
+use Icinga\Module\Director\Web\Tabs\ObjectTabs;
+use Icinga\Module\Director\Web\Tree\InspectTreeRenderer;
+use Icinga\Module\Director\Web\Widget\IcingaObjectInspection;
+use Icinga\Module\Director\Web\Widget\InspectPackages;
+use ipl\Html\Html;
+
+class InspectController extends ActionController
+{
+ private $endpoint;
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/inspect');
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function typesAction()
+ {
+ $object = $this->endpoint();
+ $name = $object->getObjectName();
+ $this->tabs(
+ new ObjectTabs('endpoint', $this->Auth(), $object)
+ )->activate('inspect');
+
+ $this->addTitle($this->translate('Icinga 2 - Objects: %s'), $name);
+
+ $this->actions()->add(
+ Link::create(
+ $this->translate('Status'),
+ 'director/inspect/status',
+ ['endpoint' => $name],
+ [
+ 'class' => 'icon-eye',
+ 'data-base-target' => '_next'
+ ]
+ )
+ );
+ $this->content()->add(
+ new InspectTreeRenderer($object)
+ );
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function typeAction()
+ {
+ $api = $this->endpoint()->api();
+ $typeName = $this->params->get('type');
+ $this->addSingleTab($this->translate('Inspect - object list'));
+ $this->addTitle(
+ $this->translate('Object type "%s"'),
+ $typeName
+ );
+ $c = $this->content();
+ $type = $api->getType($typeName);
+ if ($type->abstract) {
+ $c->add($this->translate('This is an abstract object type.'));
+ }
+
+ if (! $type->abstract) {
+ $objects = $api->listObjects($typeName, $type->plural_name);
+ $c->add(Html::tag('p', null, sprintf($this->translate('%d objects found'), count($objects))));
+ $c->add(new CoreApiObjectsTable($objects, $this->endpoint(), $type));
+ }
+
+ if (count((array) $type->fields)) {
+ $c->add([
+ Html::tag('h2', null, $this->translate('Type attributes')),
+ new CoreApiFieldsTable($type->fields, $this->url())
+ ]);
+ }
+
+ if (count($type->prototype_keys)) {
+ $c->add([
+ Html::tag('h2', null, $this->translate('Prototypes (methods)')),
+ new CoreApiPrototypesTable($type->prototype_keys, $type->name)
+ ]);
+ }
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function objectAction()
+ {
+ $name = $this->params->get('name');
+ $pType = $this->params->get('plural');
+ $this->addSingleTab($this->translate('Object Inspection'));
+ $this->addTitle('%s "%s"', $pType, $name);
+ $this->showEndpointInformation($this->endpoint());
+ $this->content()->add(
+ new IcingaObjectInspection(
+ $this->endpoint()->api()->getObject($name, $pType),
+ $this->db()
+ )
+ );
+ }
+
+ /**
+ * @param IcingaEndpoint $endpoint
+ */
+ protected function showEndpointInformation(IcingaEndpoint $endpoint)
+ {
+ $this->content()->add(
+ Html::tag('p', null, Html::sprintf(
+ 'Inspected via %s (%s)',
+ $this->linkToEndpoint($endpoint),
+ $endpoint->getDescriptiveUrl()
+ ))
+ );
+ }
+
+ /**
+ * @param IcingaEndpoint $endpoint
+ * @return Link
+ */
+ protected function linkToEndpoint(IcingaEndpoint $endpoint)
+ {
+ return Link::create($endpoint->getObjectName(), 'director/endpoint', [
+ 'name' => $endpoint->getObjectName()
+ ]);
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function statusAction()
+ {
+ $this->addSingleTab($this->translate('Status'));
+ $this->addTitle($this->translate('Icinga 2 API - Status'));
+ $this->content()->add(Html::tag(
+ 'pre',
+ null,
+ PlainObjectRenderer::render($this->endpoint()->api()->getStatus())
+ ));
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function packagesAction()
+ {
+ $db = $this->db();
+ $endpointName = $this->params->get('endpoint');
+ $package = $this->params->get('package');
+ $stage = $this->params->get('stage');
+ $file = $this->params->get('file');
+ if ($endpointName === null) {
+ $endpoint = null;
+ } else {
+ $endpoint = IcingaEndpoint::load($endpointName, $db);
+ }
+ if ($endpoint === null) {
+ $this->addSingleTab($this->translate('Inspect Packages'));
+ } elseif ($file !== null) {
+ $this->addSingleTab($this->translate('Inspect File Content'));
+ } else {
+ $this->tabs(
+ new ObjectTabs('endpoint', $this->Auth(), $endpoint)
+ )->activate('packages');
+ }
+ $widget = new InspectPackages($this->db(), 'director/inspect/packages');
+ $this->addTitle($widget->getTitle($endpoint, $package, $stage, $file));
+ if ($file === null) {
+ $this->actions()->add($widget->getBreadCrumb($endpoint, $package, $stage));
+ }
+ $this->content()->add($widget->getContent($endpoint, $package, $stage, $file));
+ }
+
+ /**
+ * @return IcingaEndpoint
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function endpoint()
+ {
+ if ($this->endpoint === null) {
+ if ($name = $this->params->get('endpoint')) {
+ $this->endpoint = IcingaEndpoint::load($name, $this->db());
+ } else {
+ $this->endpoint = $this->db()->getDeploymentEndpoint();
+ }
+ }
+
+ return $this->endpoint;
+ }
+}
diff --git a/application/controllers/JobController.php b/application/controllers/JobController.php
new file mode 100644
index 0000000..278c96b
--- /dev/null
+++ b/application/controllers/JobController.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\IcingaWeb2\Link;
+use Icinga\Module\Director\Forms\DirectorJobForm;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\DirectorJob;
+use Icinga\Module\Director\Web\Controller\BranchHelper;
+use Icinga\Module\Director\Web\Widget\JobDetails;
+
+class JobController extends ActionController
+{
+ use BranchHelper;
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function indexAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $job = $this->requireJob();
+ $this
+ ->addJobTabs($job, 'show')
+ ->addTitle($this->translate('Job: %s'), $job->get('job_name'))
+ ->addToBasketLink()
+ ->content()->add(new JobDetails($job));
+ }
+
+ public function addAction()
+ {
+ $this
+ ->addSingleTab($this->translate('New Job'))
+ ->addTitle($this->translate('Add a new Job'));
+ if ($this->showNotInBranch($this->translate('Creating Jobs'))) {
+ return;
+ }
+
+ $this->content()->add(
+ DirectorJobForm::load()
+ ->setSuccessUrl('director/job')
+ ->setDb($this->db())
+ ->handleRequest()
+ );
+ }
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function editAction()
+ {
+ $job = $this->requireJob();
+ $this
+ ->addJobTabs($job, 'edit')
+ ->addTitle($this->translate('Job: %s'), $job->get('job_name'))
+ ->addToBasketLink();
+ if ($this->showNotInBranch($this->translate('Modifying Jobs'))) {
+ return;
+ }
+
+ $form = DirectorJobForm::load()
+ ->setListUrl('director/jobs')
+ ->setObject($job)
+ ->handleRequest();
+ $this->content()->add($form);
+ }
+
+ /**
+ * @return DirectorJob
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Exception\MissingParameterException
+ */
+ protected function requireJob()
+ {
+ return DirectorJob::loadWithAutoIncId((int) $this->params->getRequired('id'), $this->db());
+ }
+
+ /**
+ * @return $this
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function addToBasketLink()
+ {
+ $job = $this->requireJob();
+ $this->actions()->add(Link::create(
+ $this->translate('Add to Basket'),
+ 'director/basket/add',
+ [
+ 'type' => 'DirectorJob',
+ 'names' => $job->getUniqueIdentifier()
+ ],
+ ['class' => 'icon-tag']
+ ));
+
+ return $this;
+ }
+
+ protected function addJobTabs(DirectorJob $job, $active)
+ {
+ $id = $job->get('id');
+
+ $this->tabs()->add('show', [
+ 'url' => 'director/job',
+ 'urlParams' => ['id' => $id],
+ 'label' => $this->translate('Job'),
+ ])->add('edit', [
+ 'url' => 'director/job/edit',
+ 'urlParams' => ['id' => $id],
+ 'label' => $this->translate('Config'),
+ ])->activate($active);
+
+ return $this;
+ }
+}
diff --git a/application/controllers/JobsController.php b/application/controllers/JobsController.php
new file mode 100644
index 0000000..11e86ed
--- /dev/null
+++ b/application/controllers/JobsController.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Table\JobTable;
+use Icinga\Module\Director\Web\Tabs\ImportTabs;
+
+class JobsController extends ActionController
+{
+ public function indexAction()
+ {
+ $this->addTitle($this->translate('Jobs'))
+ ->setAutoRefreshInterval(10)
+ ->addAddLink($this->translate('Add a new Job'), 'director/job/add')
+ ->tabs(new ImportTabs())->activate('jobs');
+
+ (new JobTable($this->db()))->renderTo($this);
+ }
+}
diff --git a/application/controllers/KickstartController.php b/application/controllers/KickstartController.php
new file mode 100644
index 0000000..99cde1b
--- /dev/null
+++ b/application/controllers/KickstartController.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use Icinga\Module\Director\Forms\KickstartForm;
+use Icinga\Module\Director\Web\Controller\BranchHelper;
+
+class KickstartController extends DashboardController
+{
+ use BranchHelper;
+
+ public function indexAction()
+ {
+ $this->addSingleTab($this->translate('Kickstart'))
+ ->addTitle($this->translate('Director Kickstart Wizard'));
+ if ($this->showNotInBranch($this->translate('Kickstart'))) {
+ return;
+ }
+ $form = KickstartForm::load();
+ try {
+ $form->setEndpoint($this->db()->getDeploymentEndpoint());
+ } catch (Exception $e) {
+ // Silently ignore DB errors
+ }
+
+ $form->handleRequest();
+ $this->content()->add($form);
+ }
+}
diff --git a/application/controllers/NotificationController.php b/application/controllers/NotificationController.php
new file mode 100644
index 0000000..97fa0f4
--- /dev/null
+++ b/application/controllers/NotificationController.php
@@ -0,0 +1,85 @@
+<?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;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+
+class NotificationController extends ObjectController
+{
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/notifications');
+ }
+
+ // TODO: KILL IT
+ public function init()
+ {
+ parent::init();
+ // TODO: Check if this is still needed, remove it otherwise
+ /** @var \Icinga\Web\Widget\Tab $tab */
+ 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);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param DirectorObjectForm $form
+ */
+ protected function onObjectFormLoaded(DirectorObjectForm $form)
+ {
+ if (! $this->object) {
+ return;
+ }
+
+ if ($this->object->isTemplate()) {
+ $form->setListUrl('director/notifications/templates');
+ } else {
+ $form->setListUrl('director/notifications/applyrules');
+ }
+ }
+
+ protected function hasBasketSupport()
+ {
+ return $this->object->isTemplate() || $this->object->isApplyRule();
+ }
+
+ 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..2ddb360
--- /dev/null
+++ b/application/controllers/NotificationsController.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class NotificationsController extends ObjectsController
+{
+ protected function addObjectsTabs()
+ {
+ $res = parent::addObjectsTabs();
+ $this->tabs()->remove('index');
+ return $res;
+ }
+
+ public function indexAction()
+ {
+ throw new NotFoundError('Not found');
+ }
+
+ protected function assertApplyRulePermission()
+ {
+ return $this->assertPermission('director/notifications');
+ }
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/notifications');
+ }
+}
diff --git a/application/controllers/NotificationtemplateController.php b/application/controllers/NotificationtemplateController.php
new file mode 100644
index 0000000..0b8602c
--- /dev/null
+++ b/application/controllers/NotificationtemplateController.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\IcingaNotification;
+use Icinga\Module\Director\Web\Controller\TemplateController;
+
+class NotificationtemplateController extends TemplateController
+{
+ protected function requireTemplate()
+ {
+ return IcingaNotification::load([
+ 'object_name' => $this->params->get('name')
+ ], $this->db());
+ }
+}
diff --git a/application/controllers/PhperrorController.php b/application/controllers/PhperrorController.php
new file mode 100644
index 0000000..40a32c1
--- /dev/null
+++ b/application/controllers/PhperrorController.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Application\Icinga;
+use Icinga\Module\Director\Application\DependencyChecker;
+use Icinga\Module\Director\Web\Table\Dependency\DependencyInfoTable;
+use Icinga\Web\Controller;
+
+class PhperrorController extends Controller
+{
+ public function errorAction()
+ {
+ $this->getTabs()->add('error', array(
+ 'label' => $this->translate('Error'),
+ 'url' => $this->getRequest()->getUrl()
+ ))->activate('error');
+ $msg = $this->translate(
+ "PHP version 5.4.x is required for Director >= 1.4.0, you're running %s."
+ . ' Please either upgrade PHP or downgrade Icinga Director'
+ );
+ $this->view->title = $this->translate('Unsatisfied dependencies');
+ $this->view->message = sprintf($msg, PHP_VERSION);
+ }
+
+ public function dependenciesAction()
+ {
+ $checker = new DependencyChecker(Icinga::app());
+ if ($checker->satisfiesDependencies($this->Module())) {
+ $this->redirectNow('director');
+ }
+ $this->setAutorefreshInterval(15);
+ $this->getTabs()->add('error', [
+ 'label' => $this->translate('Error'),
+ 'url' => $this->getRequest()->getUrl()
+ ])->activate('error');
+ $this->view->title = $this->translate('Unsatisfied dependencies');
+ $this->view->table = (new DependencyInfoTable($checker, $this->Module()))->render();
+ $this->view->message = $this->translate(
+ "Icinga Director depends on the following modules, please install/upgrade as required"
+ );
+ }
+}
diff --git a/application/controllers/ScheduledDowntimeController.php b/application/controllers/ScheduledDowntimeController.php
new file mode 100644
index 0000000..e681a70
--- /dev/null
+++ b/application/controllers/ScheduledDowntimeController.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Forms\IcingaScheduledDowntimeRangeForm;
+use Icinga\Module\Director\Objects\IcingaScheduledDowntime;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Module\Director\Web\Table\IcingaScheduledDowntimeRangeTable;
+
+class ScheduledDowntimeController extends ObjectController
+{
+ protected $objectBaseUrl = 'director/scheduled-downtime';
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/scheduled-downtimes');
+ }
+
+ public function rangesAction()
+ {
+ /** @var IcingaScheduledDowntime $object */
+ $object = $this->object;
+ $this->tabs()->activate('ranges');
+ $this->addTitle($this->translate('Time period ranges'));
+ $form = IcingaScheduledDowntimeRangeForm::load()
+ ->setScheduledDowntime($object);
+
+ if (null !== ($name = $this->params->get('range'))) {
+ $this->addBackLink($this->url()->without('range'));
+ $form->loadObject([
+ 'scheduled_downtime_id' => $object->get('id'),
+ 'range_key' => $name,
+ 'range_type' => $this->params->get('range_type')
+ ]);
+ }
+
+ $this->content()->add($form->handleRequest());
+ IcingaScheduledDowntimeRangeTable::load($object)->renderTo($this);
+ }
+
+ public function getType()
+ {
+ return 'scheduledDowntime';
+ }
+}
diff --git a/application/controllers/ScheduledDowntimesController.php b/application/controllers/ScheduledDowntimesController.php
new file mode 100644
index 0000000..b6d314c
--- /dev/null
+++ b/application/controllers/ScheduledDowntimesController.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class ScheduledDowntimesController extends ObjectsController
+{
+ protected function addObjectsTabs()
+ {
+ $res = parent::addObjectsTabs();
+ $this->tabs()->remove('index');
+ $this->tabs()->remove('templates');
+ return $res;
+ }
+
+ protected function getTable()
+ {
+ return parent::getTable()
+ ->setBaseObjectUrl('director/scheduled-downtime');
+ }
+
+ protected function getApplyRulesTable()
+ {
+ return parent::getApplyRulesTable()->createLinksWithNames();
+ }
+
+ public function getType()
+ {
+ return 'scheduledDowntime';
+ }
+
+ public function getBaseObjectUrl()
+ {
+ return 'scheduled-downtime';
+ }
+
+ protected function assertApplyRulePermission()
+ {
+ return $this->assertPermission('director/scheduled-downtimes');
+ }
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/scheduled-downtimes');
+ }
+}
diff --git a/application/controllers/SchemaController.php b/application/controllers/SchemaController.php
new file mode 100644
index 0000000..b0ca24e
--- /dev/null
+++ b/application/controllers/SchemaController.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ActionController;
+use ipl\Html\Html;
+use gipfl\IcingaWeb2\Link;
+
+class SchemaController extends ActionController
+{
+ protected $schemas;
+
+ public function init()
+ {
+ $this->schemas = [
+ 'mysql' => $this->translate('MySQL schema'),
+ 'pgsql' => $this->translate('PostgreSQL schema'),
+ ];
+ }
+
+ /**
+ * @throws \Icinga\Exception\IcingaException
+ */
+ public function mysqlAction()
+ {
+ $this->serveSchema('mysql');
+ }
+
+ /**
+ * @throws \Icinga\Exception\IcingaException
+ */
+ public function pgsqlAction()
+ {
+ $this->serveSchema('pgsql');
+ }
+
+ /**
+ * @param $type
+ * @throws \Icinga\Exception\IcingaException
+ */
+ protected function serveSchema($type)
+ {
+ $schema = $this->loadSchema($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
+ }
+
+ $this
+ ->addSchemaTabs($type)
+ ->addTitle($this->schemas[$type])
+ ->addDownloadAction()
+ ->content()->add(Html::tag('pre', null, $schema));
+ }
+
+ protected function loadSchema($type)
+ {
+ return file_get_contents(
+ sprintf(
+ '%s/schema/%s.sql',
+ $this->Module()->getBasedir(),
+ $type
+ )
+ );
+ }
+
+ /**
+ * @return $this
+ * @throws \Icinga\Exception\IcingaException
+ * @throws \Icinga\Exception\ProgrammingError
+ */
+ protected function addDownloadAction()
+ {
+ $this->actions()->add(
+ Link::create(
+ $this->translate('Download'),
+ $this->url()->with('format', 'sql'),
+ null,
+ [
+ 'target' => '_blank',
+ 'class' => 'icon-download',
+ ]
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * @param $active
+ * @return $this
+ * @throws \Icinga\Exception\Http\HttpNotFoundException
+ * @throws \Icinga\Exception\ProgrammingError
+ */
+ protected function addSchemaTabs($active)
+ {
+ $tabs = $this->tabs();
+ foreach ($this->schemas as $type => $title) {
+ $tabs->add($type, [
+ 'url' => 'director/schema/' . $type,
+ 'label' => $title,
+ ]);
+ }
+
+ $tabs->activate($active);
+
+ return $this;
+ }
+}
diff --git a/application/controllers/SelfServiceController.php b/application/controllers/SelfServiceController.php
new file mode 100644
index 0000000..0b3b642
--- /dev/null
+++ b/application/controllers/SelfServiceController.php
@@ -0,0 +1,435 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use Icinga\Exception\NotFoundError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Director\Forms\IcingaHostSelfServiceForm;
+use Icinga\Module\Director\Objects\IcingaEndpoint;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaZone;
+use Icinga\Module\Director\Settings;
+use Icinga\Module\Director\Util;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use ipl\Html\Html;
+
+class SelfServiceController extends ActionController
+{
+ /** @var bool */
+ protected $isApified = true;
+
+ /** @var bool */
+ protected $requiresAuthentication = false;
+
+ /** @var Settings */
+ protected $settings;
+
+ protected function assertApiPermission()
+ {
+ // no permission required, we'll check the API key
+ }
+
+ protected function checkDirectorPermissions()
+ {
+ }
+
+ /**
+ * @throws NotFoundError
+ * @throws ProgrammingError
+ * @throws \Zend_Controller_Request_Exception
+ */
+ public function apiVersionAction()
+ {
+ if ($this->getRequest()->isApiRequest()) {
+ $this->sendPowerShellResponse('1.4.0');
+ } else {
+ throw new NotFoundError('Not found');
+ }
+ }
+
+ /**
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\IcingaException
+ * @throws \Zend_Controller_Exception
+ */
+ public function registerHostAction()
+ {
+ $request = $this->getRequest();
+ $form = IcingaHostSelfServiceForm::create($this->db());
+ $form->setApiRequest($request->isApiRequest());
+ try {
+ if ($key = $this->params->get('key')) {
+ $form->loadTemplateWithApiKey($key);
+ }
+ } catch (Exception $e) {
+ $this->sendPowerShellError($e->getMessage(), 404);
+ return;
+ }
+ if ($name = $this->params->get('name')) {
+ $form->setHostName($name);
+ }
+
+ if ($request->isApiRequest()) {
+ $data = json_decode($request->getRawBody());
+ $request->setPost((array) $data);
+ $form->handleRequest();
+ if ($newKey = $form->getHostApiKey()) {
+ $this->sendPowerShellResponse($newKey);
+ } else {
+ $error = implode('; ', $form->getErrorMessages());
+ if ($error === '') {
+ if ($form->isMissingRequiredFields()) {
+ $fields = $form->listMissingRequiredFields();
+ if (count($fields) === 1) {
+ $this->sendPowerShellError(
+ sprintf("%s is required", $fields[0]),
+ 400
+ );
+ } else {
+ $this->sendPowerShellError(
+ sprintf("Missing parameters: %s", implode(', ', $fields)),
+ 400
+ );
+ }
+ return;
+ } else {
+ $this->sendPowerShellError('An unknown error ocurred', 500);
+ }
+ } else {
+ $this->sendPowerShellError($error, 400);
+ }
+ }
+ return;
+ }
+
+ $form->handleRequest();
+ $this->addSingleTab($this->translate('Self Service'))
+ ->addTitle($this->translate('Self Service - Host Registration'))
+ ->content()->add(Html::tag('p', null, $this->translate(
+ 'In case an Icinga Admin provided you with a self service API'
+ . ' token, this is where you can register new hosts'
+ )))
+ ->add($form);
+ }
+
+ /**
+ * @throws NotFoundError
+ * @throws \Zend_Controller_Request_Exception
+ * @throws \Zend_Controller_Response_Exception
+ */
+ public function ticketAction()
+ {
+ if (!$this->getRequest()->isApiRequest()) {
+ throw new NotFoundError('Not found');
+ }
+
+ try {
+ $key = $this->params->getRequired('key');
+ $host = IcingaHost::loadWithApiKey($key, $this->db());
+ if ($host->isTemplate()) {
+ throw new NotFoundError('Got invalid API key "%s"', $key);
+ }
+ $name = $host->getObjectName();
+
+ if ($host->getResolvedProperty('has_agent') !== 'y') {
+ throw new NotFoundError('The host "%s" is not an agent', $name);
+ }
+
+ $this->sendPowerShellResponse($this->api()->getTicket($name));
+ } catch (Exception $e) {
+ if ($e instanceof NotFoundError) {
+ $this->sendPowerShellError($e->getMessage(), 404);
+ } else {
+ $this->sendPowerShellError($e->getMessage(), 500);
+ }
+ }
+ }
+
+ /**
+ * @param $response
+ * @throws ProgrammingError
+ * @throws \Zend_Controller_Request_Exception
+ */
+ protected function sendPowerShellResponse($response)
+ {
+ if ($this->getRequest()->getHeader('X-Director-Accept') === 'text/plain') {
+ if (is_array($response)) {
+ echo $this->makePlainTextPowerShellArray($response);
+ } else {
+ echo $response;
+ }
+ } else {
+ $this->sendJson($this->getResponse(), $response);
+ }
+ }
+
+ /**
+ * @param $error
+ * @param $code
+ * @throws \Zend_Controller_Request_Exception
+ * @throws \Zend_Controller_Response_Exception
+ */
+ protected function sendPowerShellError($error, $code)
+ {
+ if ($this->getRequest()->getHeader('X-Director-Accept') === 'text/plain') {
+ $this->getResponse()->setHttpResponseCode($code);
+ echo "ERROR: $error";
+ } else {
+ $this->sendJsonError($this->getResponse(), $error, $code);
+ }
+ }
+
+ /**
+ * @param $value
+ * @return string
+ * @throws ProgrammingError
+ */
+ protected function makePowerShellBoolean($value)
+ {
+ if ($value === 'y' || $value === true) {
+ return 'true';
+ } elseif ($value === 'n' || $value === false) {
+ return 'false';
+ } else {
+ throw new ProgrammingError(
+ 'Expected boolean value, got %s',
+ var_export($value, 1)
+ );
+ }
+ }
+
+ /**
+ * @param array $params
+ * @return string
+ * @throws ProgrammingError
+ */
+ protected function makePlainTextPowerShellArray(array $params)
+ {
+ $plain = '';
+
+ foreach ($params as $key => $value) {
+ if (is_bool($value)) {
+ $value = $this->makePowerShellBoolean($value);
+ } elseif (is_array($value)) {
+ $value = implode('!', $value);
+ }
+ $plain .= "$key: $value\r\n";
+ }
+
+ return $plain;
+ }
+
+ /**
+ * @throws NotFoundError
+ * @throws \Zend_Controller_Request_Exception
+ * @throws \Zend_Controller_Response_Exception
+ */
+ public function powershellParametersAction()
+ {
+ if (!$this->getRequest()->isApiRequest()) {
+ throw new NotFoundError('Not found');
+ }
+
+ try {
+ $this->shipPowershellParams();
+ } catch (Exception $e) {
+ if ($e instanceof NotFoundError) {
+ $this->sendPowerShellError($e->getMessage(), 404);
+ } else {
+ $this->sendPowerShellError($e->getMessage(), 500);
+ }
+ }
+ }
+
+ /**
+ * @throws NotFoundError
+ * @throws ProgrammingError
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\IcingaException
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Zend_Controller_Request_Exception
+ * @throws \Zend_Controller_Response_Exception
+ */
+ protected function shipPowershellParams()
+ {
+ $db = $this->db();
+ $key = $this->params->getRequired('key');
+ $host = IcingaHost::loadWithApiKey($key, $db);
+
+ $settings = $this->getSettings();
+ $transform = $settings->get('self-service/transform_hostname');
+ $params = [
+ 'fetch_agent_name' => $settings->get('self-service/agent_name') === 'hostname',
+ 'fetch_agent_fqdn' => $settings->get('self-service/agent_name') === 'fqdn',
+ 'transform_hostname' => $transform,
+ 'flush_api_directory' => $settings->get('self-service/flush_api_dir') === 'y',
+ // ConvertEndpointIPConfig:
+ 'resolve_parent_host' => $settings->get('self-service/resolve_parent_host'),
+ // InstallFrameworkService:
+ 'install_framework_service' => '0',
+ // ServiceDirectory => framework_service_directory
+ // FrameworkServiceUrl => framework_service_url
+ // InstallFrameworkPlugins:
+ 'install_framework_plugins' => '0',
+ // PluginsUrl => framework_plugins_url
+ ];
+ $username = $settings->get('self-service/icinga_service_user');
+ if ($username !== null && strlen($username) > 0) {
+ $params['icinga_service_user'] = $username;
+ }
+
+ if ($transform === '2') {
+ $transformMethod = '.upperCase';
+ } elseif ($transform === '1') {
+ $transformMethod = '.lowerCase';
+ } else {
+ $transformMethod = '';
+ }
+
+ $hostObject = (object) [
+ 'address' => '&ipaddress&',
+ ];
+
+ switch ($settings->get('self-service/agent_name')) {
+ case 'hostname':
+ $hostObject->display_name = "&fqdn$transformMethod&";
+ break;
+ case 'fqdn':
+ $hostObject->display_name = "&hostname$transformMethod&";
+ break;
+ }
+ $params['director_host_object'] = json_encode($hostObject);
+
+ if ($settings->get('self-service/download_type')) {
+ $params['download_url'] = $settings->get('self-service/download_url');
+ $params['agent_version'] = $settings->get('self-service/agent_version');
+ $params['allow_updates'] = $settings->get('self-service/allow_updates') === 'y';
+ $params['agent_listen_port'] = $host->getAgentListenPort();
+ if ($hashes = $settings->get('self-service/installer_hashes')) {
+ $params['installer_hashes'] = $hashes;
+ }
+
+ if ($settings->get('self-service/install_nsclient') === 'y') {
+ $params['install_nsclient'] = true;
+ $this->addBooleanSettingsToParams($settings, [
+ 'nsclient_add_defaults',
+ 'nsclient_firewall',
+ 'nsclient_service',
+ ], $params);
+
+
+ $this->addStringSettingsToParams($settings, [
+ 'nsclient_directory',
+ 'nsclient_installer_path'
+ ], $params);
+ }
+ }
+
+ $this->addHostToParams($host, $params);
+
+ if ($this->getRequest()->getHeader('X-Director-Accept') === 'text/plain') {
+ echo $this->makePlainTextPowerShellArray($params);
+ } else {
+ $this->sendJson($this->getResponse(), $params);
+ }
+ }
+
+ /**
+ * @param IcingaHost $host
+ * @param array $params
+ * @throws NotFoundError
+ * @throws ProgrammingError
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\IcingaException
+ * @throws \Zend_Controller_Request_Exception
+ * @throws \Zend_Controller_Response_Exception
+ */
+ protected function addHostToParams(IcingaHost $host, array &$params)
+ {
+ if (! $host->isObject()) {
+ return;
+ }
+
+ $db = $this->db();
+ $settings = $this->getSettings();
+ $name = $host->getObjectName();
+ if ($host->getSingleResolvedProperty('has_agent') !== 'y') {
+ $this->sendPowerShellError(sprintf(
+ '%s is not configured for Icinga Agent usage',
+ $name
+ ), 403);
+ return;
+ }
+
+ $zoneName = $host->getRenderingZone();
+ if ($zoneName === IcingaHost::RESOLVE_ERROR) {
+ $this->sendPowerShellError(sprintf(
+ 'Could not resolve target Zone for %s',
+ $name
+ ), 404);
+ return;
+ }
+
+ $masterConnectsToAgent = $host->getSingleResolvedProperty(
+ 'master_should_connect'
+ ) === 'y';
+ $params['agent_add_firewall_rule'] = $masterConnectsToAgent;
+
+ $params['global_zones'] = $settings->get('self-service/global_zones');
+
+ $zone = IcingaZone::load($zoneName, $db);
+ $endpointNames = $zone->listEndpoints();
+ if (! $masterConnectsToAgent) {
+ $endpointsConfig = [];
+ foreach ($endpointNames as $endpointName) {
+ $endpoint = IcingaEndpoint::load($endpointName, $db);
+ $endpointsConfig[] = sprintf(
+ '%s;%s',
+ $endpoint->getSingleResolvedProperty('host'),
+ $endpoint->getResolvedPort()
+ );
+ }
+
+ $params['endpoints_config'] = $endpointsConfig;
+ }
+ $master = $db->getDeploymentEndpoint();
+ $params['parent_zone'] = $zoneName;
+ $params['ca_server'] = $master->getObjectName();
+ $params['parent_endpoints'] = $endpointNames;
+ $params['accept_config'] = $host->getSingleResolvedProperty('accept_config')=== 'y';
+ }
+
+ protected function addStringSettingsToParams(Settings $settings, array $keys, array &$params)
+ {
+ foreach ($keys as $key) {
+ $value = $settings->get("self-service/$key");
+ if (strlen($value)) {
+ $params[$key] = $value;
+ }
+ }
+ }
+
+ protected function addBooleanSettingsToParams(Settings $settings, array $keys, array &$params)
+ {
+ foreach ($keys as $key) {
+ $value = $settings->get("self-service/$key");
+ if ($value !== null) {
+ $params[$key] = $value === 'y';
+ }
+ }
+ }
+
+ /**
+ * @return Settings
+ * @throws \Icinga\Exception\ConfigurationError
+ */
+ protected function getSettings()
+ {
+ if ($this->settings === null) {
+ $this->settings = new Settings($this->db());
+ }
+
+ return $this->settings;
+ }
+}
diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php
new file mode 100644
index 0000000..3cd54d6
--- /dev/null
+++ b/application/controllers/ServiceController.php
@@ -0,0 +1,311 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Exception;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Data\Db\DbObjectStore;
+use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
+use Icinga\Module\Director\Db\Branch\UuidLookup;
+use Icinga\Module\Director\Forms\IcingaServiceForm;
+use Icinga\Module\Director\Monitoring;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Module\Director\Web\Table\IcingaAppliedServiceTable;
+use Icinga\Web\Widget\Tab;
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Widget\Tabs;
+
+class ServiceController extends ObjectController
+{
+ /** @var IcingaHost */
+ protected $host;
+
+ protected $set;
+
+ protected $apply;
+
+ protected function checkDirectorPermissions()
+ {
+ if ($this->hasPermission('director/monitoring/services')) {
+ $monitoring = new Monitoring();
+ if ($monitoring->authCanEditService($this->Auth(), $this->getParam('host'), $this->getParam('name'))) {
+ return;
+ }
+ }
+ $this->assertPermission('director/hosts');
+ }
+
+ public function init()
+ {
+ // This happens in parent::init() too, but is required to take place before the next two lines
+ $this->enableStaticObjectLoader($this->getTableName());
+
+ // Hint: having Host and Set loaded first is important for UUID lookups with legacy URLs
+ $this->host = $this->getOptionalRelatedObjectFromParams('host', 'host');
+ $this->set = $this->getOptionalRelatedObjectFromParams('service_set', 'set');
+ parent::init();
+ if ($this->object) {
+ if ($this->host === null) {
+ $this->host = $this->loadOptionalRelatedObject($this->object, 'host');
+ }
+ if ($this->set === null) {
+ $this->set = $this->loadOptionalRelatedObject($this->object, 'service_set');
+ }
+ }
+ $this->addOptionalHostTabs();
+ $this->addOptionalSetTabs();
+ }
+
+ protected function getOptionalRelatedObjectFromParams($type, $parameter)
+ {
+ if ($id = $this->params->get("${parameter}_id")) {
+ $key = (int) $id;
+ } else {
+ $key = $this->params->get($parameter);
+ }
+ if ($key !== null) {
+ $table = DbObjectTypeRegistry::tableNameByType($type);
+ $key = UuidLookup::findUuidForKey($key, $table, $this->db(), $this->getBranch());
+ return $this->loadSpecificObject($table, $key);
+ }
+
+ return null;
+ }
+
+ protected function loadOptionalRelatedObject(IcingaObject $object, $relation)
+ {
+ $key = $object->getUnresolvedRelated($relation);
+ if ($key === null) {
+ if ($key = $object->get("${relation}_id")) {
+ $key = (int) $key;
+ } else {
+ $key = $object->get($relation);
+ // We reach this when accessing Service Template Fields
+ }
+ }
+
+ if ($key === null) {
+ return null;
+ }
+
+ $table = DbObjectTypeRegistry::tableNameByType($relation);
+ $uuid = UuidLookup::findUuidForKey($key, $table, $this->db(), $this->getBranch());
+ return $this->loadSpecificObject($table, $uuid);
+ }
+
+ protected function addParamToTabs($name, $value)
+ {
+ foreach ($this->tabs()->getTabs() as $tab) {
+ /** @var Tab $tab */
+ $tab->getUrl()->setParam($name, $value);
+ }
+
+ return $this;
+ }
+
+ public function addAction()
+ {
+ parent::addAction();
+ if ($this->host) {
+ // TODO: use setTitle. And figure out, where we use this old route.
+ $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
+ );
+ }
+ }
+
+ protected function onObjectFormLoaded(DirectorObjectForm $form)
+ {
+ if ($this->set) {
+ /** @var IcingaServiceForm$form */
+ $form->setServiceSet($this->set);
+ }
+ if ($this->object === null && $this->apply) {
+ $form->createApplyRuleFor($this->apply);
+ }
+ }
+
+ public function editAction()
+ {
+ $this->tabs()->activate('modify');
+
+ /** @var IcingaService $object */
+ $object = $this->object;
+ $this->addTitle($object->getObjectName());
+ if ($object->isTemplate() && $this->showNotInBranch($this->translate('Modifying Templates'))) {
+ return;
+ }
+
+ $form = IcingaServiceForm::load()->setDb($this->db());
+ $form->setBranch($this->getBranch());
+
+ if ($this->host) {
+ $this->actions()->add(Link::create(
+ $this->translate('back'),
+ 'director/host/services',
+ ['uuid' => $this->host->getUniqueId()->toString()],
+ ['class' => 'icon-left-big']
+ ));
+ $form->setHost($this->host);
+ }
+
+ if ($this->set) {
+ $form->setServiceSet($this->set);
+ }
+ if ($this->host && $object->usesVarOverrides()) {
+ $fake = IcingaService::create(array(
+ 'object_type' => 'object',
+ 'host_id' => $object->get('host_id'),
+ 'imports' => $object,
+ 'object_name' => $object->object_name,
+ 'use_var_overrides' => 'y',
+ 'vars' => $this->host->getOverriddenServiceVars($object->object_name),
+ ), $this->db());
+
+ $form->setObject($fake);
+ } else {
+ $form->setObject($object);
+ }
+
+ $form->handleRequest();
+ $this->addActionClone();
+
+ 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->content()->add($form);
+ }
+
+ public function assignAction()
+ {
+ // TODO: figure out whether and where we link to this
+ /** @var IcingaService $service */
+ $service = $this->object;
+ $this->actions()->add(new Link(
+ $this->translate('back'),
+ $this->getRequest()->getUrl()->without('rule_id'),
+ null,
+ array('class' => 'icon-left-big')
+ ));
+
+ $this->tabs()->activate('applied');
+ $this->addTitle(
+ $this->translate('Apply: %s'),
+ $service->getObjectName()
+ );
+ $table = (new IcingaAppliedServiceTable($this->db()))
+ ->setService($service);
+ $table->getAttributes()->set('data-base-target', '_self');
+
+ $this->content()->add($table);
+ }
+
+ protected function getLegacyKey()
+ {
+ if ($key = $this->params->get('id')) {
+ $key = (int) $key;
+ } else {
+ $key = $this->params->get('name');
+ }
+
+ if ($key === null) {
+ throw new \InvalidArgumentException('uuid, name or id required');
+ }
+
+ return $key;
+ }
+
+ protected function loadObject()
+ {
+ if ($this->params->has('uuid')) {
+ parent::loadObject();
+ return;
+ }
+
+ $key = $this->getLegacyKey();
+ // Hint: not passing 'object' as type, we still have name-based links in previews and similar
+ $uuid = UuidLookup::findServiceUuid($this->db(), $this->getBranch(), null, $key, $this->host, $this->set);
+ if ($uuid === null) {
+ if (! $this->params->get('allowOverrides')) {
+ throw new NotFoundError('Not found');
+ }
+ } else {
+ $this->params->set('uuid', $uuid->toString());
+ parent::loadObject();
+ }
+ }
+
+ protected function addOptionalHostTabs()
+ {
+ if ($this->host === null) {
+ return;
+ }
+ $hostname = $this->host->getObjectName();
+ $tabs = new Tabs();
+ $urlParams = ['uuid' => $this->host->getUniqueId()->toString()];
+ $tabs->add('host', [
+ 'url' => 'director/host',
+ 'urlParams' => $urlParams,
+ 'label' => $this->translate('Host'),
+ ])->add('services', [
+ 'url' => 'director/host/services',
+ 'urlParams' => $urlParams,
+ 'label' => $this->translate('Services'),
+ ]);
+
+ $this->addParamToTabs('host', $hostname);
+ $this->controls()->prependTabs($tabs);
+ }
+
+ protected function addOptionalSetTabs()
+ {
+ if ($this->set === null) {
+ return;
+ }
+ $setName = $this->set->getObjectName();
+ $tabs = new Tabs();
+ $tabs->add('set', [
+ 'url' => 'director/serviceset',
+ 'urlParams' => ['name' => $setName],
+ 'label' => $this->translate('ServiceSet'),
+ ])->add('services', [
+ 'url' => 'director/serviceset/services',
+ 'urlParams' => ['name' => $setName],
+ 'label' => $this->translate('Services'),
+ ]);
+
+ $this->addParamToTabs('serviceset', $setName);
+ $this->controls()->prependTabs($tabs);
+ }
+}
diff --git a/application/controllers/ServiceapplyrulesController.php b/application/controllers/ServiceapplyrulesController.php
new file mode 100644
index 0000000..c3a7f2b
--- /dev/null
+++ b/application/controllers/ServiceapplyrulesController.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\RestApi\IcingaObjectsHandler;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Web\Table\ApplyRulesTable;
+
+class ServiceapplyrulesController extends ActionController
+{
+ protected $isApified = true;
+
+ public function indexAction()
+ {
+ $request = $this->getRequest();
+ if (! $request->isApiRequest()) {
+ throw new NotFoundError('Not found');
+ }
+
+ $table = ApplyRulesTable::create('service', $this->db());
+/*
+ $query = $this->db()->getDbAdapter()
+ ->select()
+ ->from('icinga_service')
+ ->where('object_type = ?', 'apply');
+ $rules = IcingaService::loadAll($this->db(), $query);
+*/
+
+ $handler = (new IcingaObjectsHandler(
+ $request,
+ $this->getResponse(),
+ $this->db()
+ ))->setTable($table);
+
+ $handler->dispatch();
+ }
+}
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..d35e638
--- /dev/null
+++ b/application/controllers/ServicegroupsController.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class ServicegroupsController extends ObjectsController
+{
+}
diff --git a/application/controllers/ServicesController.php b/application/controllers/ServicesController.php
new file mode 100644
index 0000000..8d178c2
--- /dev/null
+++ b/application/controllers/ServicesController.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\IcingaWeb2\Link;
+use gipfl\IcingaWeb2\Url;
+use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
+use Icinga\Module\Director\Web\Controller\ObjectsController;
+
+class ServicesController extends ObjectsController
+{
+ protected $multiEdit = array(
+ 'imports',
+ 'groups',
+ 'disabled'
+ );
+
+ public function edittemplatesAction()
+ {
+ parent::editAction();
+
+ $objects = $this->loadMultiObjectsFromParams();
+ $names = [];
+ /** @var ExportInterface $object */
+ foreach ($objects as $object) {
+ $names[] = $object->getUniqueIdentifier();
+ }
+
+ $url = Url::fromPath('director/basket/add', [
+ 'type' => 'ServiceTemplate',
+ ]);
+
+ $url->getParams()->addValues('names', $names);
+
+ $this->actions()->add(Link::create(
+ $this->translate('Add to Basket'),
+ $url,
+ null,
+ ['class' => 'icon-tag']
+ ));
+ }
+}
diff --git a/application/controllers/ServicesetController.php b/application/controllers/ServicesetController.php
new file mode 100644
index 0000000..684d2fc
--- /dev/null
+++ b/application/controllers/ServicesetController.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Director\Forms\IcingaServiceSetForm;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+use Icinga\Module\Director\Web\Controller\ObjectController;
+use Icinga\Module\Director\Web\Form\DirectorObjectForm;
+use Icinga\Module\Director\Web\Table\IcingaHostsMatchingFilterTable;
+use Icinga\Module\Director\Web\Table\IcingaServiceSetHostTable;
+use Icinga\Module\Director\Web\Table\IcingaServiceSetServiceTable;
+use gipfl\IcingaWeb2\Link;
+
+class ServicesetController extends ObjectController
+{
+ /** @var IcingaHost */
+ protected $host;
+
+ protected function checkDirectorPermissions()
+ {
+ $this->assertPermission('director/servicesets');
+ }
+
+ public function init()
+ {
+ if (null !== ($host = $this->params->get('host'))) {
+ $this->host = IcingaHost::load($host, $this->db());
+ }
+
+ parent::init();
+ if ($this->object) {
+ $this->addServiceSetTabs();
+ }
+ }
+
+ protected function onObjectFormLoaded(DirectorObjectForm $form)
+ {
+ if ($this->host) {
+ /** @var IcingaServiceSetForm $form */
+ $form->setHost($this->host);
+ }
+ }
+
+ public function addAction()
+ {
+ parent::addAction();
+ if ($this->host) {
+ $this->addTitle(
+ $this->translate('Add a service set to "%s"'),
+ $this->host->getObjectName()
+ );
+ }
+ }
+
+ public function servicesAction()
+ {
+ /** @var IcingaServiceSet $set */
+ $set = $this->object;
+ $name = $set->getObjectName();
+ $this->tabs()->activate('services');
+ $this->addTitle(
+ $this->translate('Services in this set: %s'),
+ $name
+ );
+ $this->actions()->add(Link::create(
+ $this->translate('Add service'),
+ 'director/service/add',
+ ['set' => $name],
+ ['class' => 'icon-plus']
+ ));
+
+ IcingaServiceSetServiceTable::load($set)
+ ->setBranch($this->getBranch())
+ ->renderTo($this);
+ }
+
+ public function hostsAction()
+ {
+ /** @var IcingaServiceSet $set */
+ $set = $this->object;
+ $this->tabs()->activate('hosts');
+ $this->addTitle(
+ $this->translate('Hosts using this set: %s'),
+ $set->getObjectName()
+ );
+
+ $table = IcingaServiceSetHostTable::load($set);
+ if ($table->count()) {
+ $table->renderTo($this);
+ }
+ $filter = $set->get('assign_filter');
+ if ($filter !== null && \strlen($filter) > 0) {
+ $this->content()->add(
+ IcingaHostsMatchingFilterTable::load(Filter::fromQueryString($filter), $this->db())
+ );
+ }
+ }
+
+ protected function addServiceSetTabs()
+ {
+ $hexUuid = $this->object->getUniqueId()->toString();
+ $tabs = $this->tabs();
+ $tabs->add('services', [
+ 'url' => 'director/serviceset/services',
+ 'urlParams' => ['uuid' => $hexUuid],
+ 'label' => 'Services'
+ ]);
+ if ($this->branch->isBranch()) {
+ return $this;
+ }
+ $tabs->add('hosts', [
+ 'url' => 'director/serviceset/hosts',
+ 'urlParams' => ['uuid' => $hexUuid],
+ 'label' => 'Hosts'
+ ]);
+
+ return $this;
+ }
+
+ protected function loadObject()
+ {
+ if ($this->object === null) {
+ if (null !== ($name = $this->params->get('name'))) {
+ $params = ['object_name' => $name];
+ $db = $this->db();
+
+ if ($this->host) {
+ $params['host_id'] = $this->host->get('id');
+ }
+
+ $this->object = IcingaServiceSet::load($params, $db);
+ } else {
+ parent::loadObject();
+ }
+ }
+
+ return $this->object;
+ }
+}
diff --git a/application/controllers/ServicetemplateController.php b/application/controllers/ServicetemplateController.php
new file mode 100644
index 0000000..25d0742
--- /dev/null
+++ b/application/controllers/ServicetemplateController.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Web\Controller\TemplateController;
+
+class ServicetemplateController extends TemplateController
+{
+ protected function requireTemplate()
+ {
+ return IcingaService::load([
+ 'object_name' => $this->params->get('name')
+ ], $this->db());
+ }
+}
diff --git a/application/controllers/SettingsController.php b/application/controllers/SettingsController.php
new file mode 100644
index 0000000..c4709e6
--- /dev/null
+++ b/application/controllers/SettingsController.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Forms\KickstartForm;
+use Icinga\Module\Director\Forms\SelfServiceSettingsForm;
+use Icinga\Module\Director\Settings;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use ipl\Html\Html;
+
+class SettingsController extends ActionController
+{
+ /**
+ * @throws \Icinga\Exception\Http\HttpNotFoundException
+ */
+ public function indexAction()
+ {
+ // Hint: this is for the module configuration tab, legacy code
+ $this->view->tabs = $this->Module()
+ ->getConfigTabs()
+ ->activate('config');
+
+ $this->view->form = KickstartForm::load()
+ ->setModuleConfig($this->Config())
+ ->handleRequest();
+ }
+
+ /**
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\IcingaException
+ */
+ public function selfServiceAction()
+ {
+ $form = SelfServiceSettingsForm::create($this->db(), new Settings($this->db()));
+ $form->handleRequest();
+
+ $hint = $this->translate(
+ 'The Icinga Director Self Service API allows your Hosts to register'
+ . ' themselves. This allows them to get their Icinga Agent configured,'
+ . ' installed and upgraded in an automated way.'
+ );
+
+ $this->addSingleTab($this->translate('Self Service'))
+ ->addTitle($this->translate('Self Service API - Global Settings'))
+ ->content()->add(Html::tag('p', null, $hint))
+ ->add($form);
+ }
+}
diff --git a/application/controllers/SuggestController.php b/application/controllers/SuggestController.php
new file mode 100644
index 0000000..659c48c
--- /dev/null
+++ b/application/controllers/SuggestController.php
@@ -0,0 +1,415 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use Icinga\Module\Director\Restriction\HostgroupRestriction;
+use ipl\Html\Html;
+use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Hook\ImportSourceHook;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Director\Objects\HostApplyMatches;
+
+class SuggestController extends ActionController
+{
+ protected function checkDirectorPermissions()
+ {
+ }
+
+ public function indexAction()
+ {
+ // TODO: Using some temporarily hardcoded methods, should use DataViews later on
+ $context = $this->getRequest()->getPost('context');
+ $key = null;
+
+ if (strpos($context, '!') !== false) {
+ list($context, $key) = preg_split('~!~', $context, 2);
+ }
+
+ $func = 'suggest' . ucfirst($context);
+ if (method_exists($this, $func)) {
+ if (! empty($key)) {
+ $all = $this->$func($key);
+ } else {
+ $all = $this->$func();
+ }
+ } else {
+ $all = array();
+ }
+ // TODO: also get cursor position and eventually add an asterisk in the middle
+ // tODO: filter also when fetching, eventually limit somehow
+ $search = $this->getRequest()->getPost('value');
+ $begins = array();
+ $matches = array();
+ $begin = Filter::expression('value', '=', $search . '*');
+ $middle = Filter::expression('value', '=', '*' . $search . '*')->setCaseSensitive(false);
+ $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);
+ }
+ } else {
+ $matches[] = Html::escape($str);
+ }
+ }
+
+ $containing = array_slice(array_merge($begins, $matches), 0, 100);
+ $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
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Security\SecurityException
+ */
+ protected function suggestLocations()
+ {
+ $this->assertPermission('director/hosts');
+ $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($type = 'object')
+ {
+ $this->assertPermission('director/hosts');
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()
+ ->from('icinga_host', 'object_name')
+ ->order('object_name');
+
+ if ($type !== null) {
+ $query->where('object_type = ?', $type);
+ }
+ $restriction = new HostgroupRestriction($this->db(), $this->Auth());
+ $restriction->filterHostsQuery($query);
+
+ return $db->fetchCol($query);
+ }
+
+ protected function suggestHostsAndTemplates()
+ {
+ return $this->suggestHostnames(null);
+ }
+
+ protected function suggestServicenames()
+ {
+ $r=array();
+ $this->assertPermission('director/services');
+ $db = $this->db()->getDbAdapter();
+ $for_host = $this->getRequest()->getPost('for_host');
+ if (!empty($for_host)) {
+ $tmp_host = IcingaHost::load($for_host, $this->db());
+ }
+
+ $query = $db->select()->distinct()
+ ->from('icinga_service', 'object_name')
+ ->order('object_name')
+ ->where("object_type IN ('object','apply')");
+ if (!empty($tmp_host)) {
+ $query->where('host_id = ?', $tmp_host->id);
+ }
+ $r = array_merge($r, $db->fetchCol($query));
+ if (!empty($tmp_host)) {
+ $resolver = $tmp_host->templateResolver();
+ foreach ($resolver->fetchResolvedParents() as $template_obj) {
+ $query = $db->select()->distinct()
+ ->from('icinga_service', 'object_name')
+ ->order('object_name')
+ ->where("object_type IN ('object','apply')")
+ ->where('host_id = ?', $template_obj->id);
+ $r = array_merge($r, $db->fetchCol($query));
+ }
+
+ $matcher = HostApplyMatches::prepare($tmp_host);
+ foreach ($this->getAllApplyRules() as $rule) {
+ if ($matcher->matchesFilter($rule->filter)) { //TODO
+ $r[]=$rule->name;
+ }
+ }
+ }
+ natcasesort($r);
+ return $r;
+ }
+
+ protected function suggestHosttemplates()
+ {
+ $this->assertPermission('director/hosts');
+ return $this->fetchTemplateNames('icinga_host', 'template_choice_id IS NULL');
+ }
+
+ protected function suggestServicetemplates()
+ {
+ $this->assertPermission('director/services');
+ return $this->fetchTemplateNames('icinga_service', 'template_choice_id IS NULL');
+ }
+
+ protected function suggestNotificationtemplates()
+ {
+ $this->assertPermission('director/notifications');
+ return $this->fetchTemplateNames('icinga_notification');
+ }
+
+ protected function suggestCommandtemplates()
+ {
+ $this->assertPermission('director/commands');
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()
+ ->from('icinga_command', 'object_name')
+ ->order('object_name');
+ return $db->fetchCol($query);
+ }
+
+ protected function suggestUsertemplates()
+ {
+ $this->assertPermission('director/users');
+ return $this->fetchTemplateNames('icinga_user');
+ }
+
+ /**
+ * @return array
+ * @throws \Icinga\Security\SecurityException
+ * @codingStandardsIgnoreStart
+ */
+ protected function suggestScheduled_downtimetemplates()
+ {
+ // @codingStandardsIgnoreEnd
+ $this->assertPermission('director/scheduled-downtimes');
+ return $this->fetchTemplateNames('icinga_scheduled_downtime');
+ }
+
+ protected function suggestCheckcommandnames()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()
+ ->from('icinga_command', 'object_name')
+ ->where('object_type != ?', 'template')
+ ->order('object_name');
+
+ return $db->fetchCol($query);
+ }
+
+ protected function fetchTemplateNames($table, $where = null)
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()
+ ->from($table, 'object_name')
+ ->where('object_type = ?', 'template')
+ ->order('object_name');
+
+ if ($where !== null) {
+ $query->where('template_choice_id IS NULL');
+ }
+
+ 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()
+ {
+ return $this->getFilterColumns('host.', [
+ $this->translate('Host properties'),
+ $this->translate('Custom variables')
+ ]);
+ }
+
+ protected function suggestServiceFilterColumns()
+ {
+ return $this->getFilterColumns('service.', [
+ $this->translate('Service properties'),
+ $this->translate('Host properties'),
+ $this->translate('Host Custom variables'),
+ $this->translate('Custom variables')
+ ]);
+ }
+
+ protected function suggestDataListValuesForListId($id)
+ {
+ $db = $this->db()->getDbAdapter();
+ $select = $db->select()
+ ->from('director_datalist_entry', ['entry_name', 'entry_value'])
+ ->where('list_id = ?', $id)
+ ->order('entry_value ASC');
+
+ $result = $db->fetchPairs($select);
+ if ($result) {
+ return $result;
+ } else {
+ return [];
+ }
+ }
+
+ protected function suggestDataListValues($field = null)
+ {
+ if ($field === null) {
+ // field is required!
+ return [];
+ }
+
+ $datalistType = 'Icinga\\Module\\Director\\DataType\\DataTypeDatalist';
+ $db = $this->db()->getDbAdapter();
+
+ $query = $db->select()
+ ->from(['f' =>'director_datafield'], [])
+ ->join(
+ ['sid' => 'director_datafield_setting'],
+ 'sid.datafield_id = f.id AND sid.setting_name = \'datalist_id\'',
+ []
+ )
+ ->join(
+ ['l' => 'director_datalist'],
+ 'l.id = sid.setting_value',
+ []
+ )
+ ->join(
+ ['e' => 'director_datalist_entry'],
+ 'e.list_id = l.id',
+ ['entry_name', 'entry_value']
+ )
+ ->where('datatype = ?', $datalistType)
+ ->where('varname = ?', $field)
+ ->order('entry_value');
+
+
+ // TODO: respect allowed_roles
+ /* this implementation from DataTypeDatalist is broken
+ $roles = array_map('json_encode', Acl::instance()->listRoleNames());
+
+ if (empty($roles)) {
+ $query->where('allowed_roles IS NULL');
+ } else {
+ $query->where('(allowed_roles IS NULL OR allowed_roles IN (?))', $roles);
+ }
+ */
+
+ $data = [];
+ foreach ($db->fetchPairs($query) as $key => $label) {
+ // TODO: find a better solution here
+ // $data[] = sprintf("%s [%s]", $label, $key);
+ $data[] = $key;
+ }
+ return $data;
+ }
+
+ protected function getFilterColumns($prefix, $keys)
+ {
+ if ($prefix === 'host.') {
+ $all = IcingaHost::enumProperties($this->db(), $prefix);
+ } else {
+ $all = IcingaService::enumProperties($this->db(), $prefix);
+ }
+ $res = [];
+ foreach ($keys as $key) {
+ if (array_key_exists($key, $all)) {
+ $res = array_merge($res, array_keys($all[$key]));
+ }
+ }
+
+ natsort($res);
+ return $res;
+ }
+
+ protected function suggestDependencytemplates()
+ {
+ $this->assertPermission('director/hosts');
+ return $this->fetchTemplateNames('icinga_dependency');
+ }
+
+ protected function highlight($val, $search)
+ {
+ $search = ($search);
+ $val = Html::escape($val);
+ return preg_replace(
+ '/(' . preg_quote($search, '/') . ')/i',
+ '<strong>\1</strong>',
+ $val
+ );
+ }
+
+ protected function getAllApplyRules()
+ {
+ $allApplyRules=$this->fetchAllApplyRules();
+ foreach ($allApplyRules as $rule) {
+ $rule->filter = Filter::fromQueryString($rule->assign_filter);
+ }
+
+ return $allApplyRules;
+ }
+
+ protected function fetchAllApplyRules()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()->from(
+ array('s' => 'icinga_service'),
+ array(
+ 'id' => 's.id',
+ 'name' => 's.object_name',
+ 'assign_filter' => 's.assign_filter',
+ )
+ )->where('object_type = ? AND assign_filter IS NOT NULL', 'apply');
+
+ return $db->fetchAll($query);
+ }
+
+ protected function suggestImportsourceproperties($sourceId = null)
+ {
+ if ($sourceId === null) {
+ return [];
+ }
+
+ try {
+ $importSource = ImportSource::loadWithAutoIncId($sourceId, $this->db());
+ $source = ImportSourceHook::loadByName($importSource->get('source_name'), $this->db());
+
+ $columns = array_merge(
+ $source->listColumns(),
+ $importSource->listProperties()
+ );
+
+ return array_combine($columns, $columns);
+ } catch (NotFoundError $e) {
+ return [];
+ }
+ }
+}
diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php
new file mode 100644
index 0000000..928cf2c
--- /dev/null
+++ b/application/controllers/SyncruleController.php
@@ -0,0 +1,696 @@
+<?php
+
+namespace Icinga\Module\Director\Controllers;
+
+use gipfl\IcingaWeb2\Link;
+use gipfl\Web\Widget\Hint;
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Director\Data\Db\DbObjectStore;
+use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
+use Icinga\Module\Director\Db\Branch\Branch;
+use Icinga\Module\Director\Db\Branch\BranchStore;
+use Icinga\Module\Director\Db\Branch\BranchSupport;
+use Icinga\Module\Director\Web\Controller\BranchHelper;
+use Icinga\Module\Director\Web\Form\ClickHereForm;
+use Icinga\Module\Director\Web\Table\BranchActivityTable;
+use Icinga\Module\Director\Web\Widget\IcingaConfigDiff;
+use Icinga\Module\Director\Web\Widget\UnorderedList;
+use Icinga\Module\Director\Db\Cache\PrefetchCache;
+use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
+use Icinga\Module\Director\Forms\SyncCheckForm;
+use Icinga\Module\Director\Forms\SyncPropertyForm;
+use Icinga\Module\Director\Forms\SyncRuleForm;
+use Icinga\Module\Director\Forms\SyncRunForm;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Import\Sync;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Web\ActionBar\AutomationObjectActionBar;
+use Icinga\Module\Director\Web\Controller\ActionController;
+use Icinga\Module\Director\Objects\SyncRule;
+use Icinga\Module\Director\Objects\SyncRun;
+use Icinga\Module\Director\Web\Form\CloneSyncRuleForm;
+use Icinga\Module\Director\Web\Table\SyncpropertyTable;
+use Icinga\Module\Director\Web\Table\SyncRunTable;
+use Icinga\Module\Director\Web\Tabs\SyncRuleTabs;
+use Icinga\Module\Director\Web\Widget\SyncRunDetails;
+use Icinga\Web\Notification;
+use ipl\Html\Form;
+use ipl\Html\Html;
+
+class SyncruleController extends ActionController
+{
+ use BranchHelper;
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function indexAction()
+ {
+ $this->setAutoRefreshInterval(10);
+ $rule = $this->requireSyncRule();
+ $this->tabs(new SyncRuleTabs($rule))->activate('show');
+ $ruleName = $rule->get('rule_name');
+ $this->addTitle($this->translate('Sync rule: %s'), $ruleName);
+
+ $checkForm = SyncCheckForm::load()->setSyncRule($rule)->handleRequest();
+ $store = new DbObjectStore($this->db(), $this->getBranch());
+ $runForm = new SyncRunForm($rule, $store);
+ $runForm->on(SyncRunForm::ON_SUCCESS, function (SyncRunForm $form) {
+ $message = $form->getSuccessMessage();
+ if ($message === null) {
+ Notification::error($this->translate('Synchronization failed'));
+ } else {
+ Notification::success($message);
+ }
+ $this->redirectNow($this->url());
+ });
+ $runForm->handleRequest($this->getServerRequest());
+
+ if ($lastRunId = $rule->getLastSyncRunId()) {
+ $run = SyncRun::load($lastRunId, $this->db());
+ } else {
+ $run = null;
+ }
+
+ $c = $this->content();
+ $c->add(Html::tag('p', null, $rule->get('description')));
+ if (! $rule->hasSyncProperties()) {
+ $this->addPropertyHint($rule);
+ return;
+ }
+ $this->addMainActions();
+ if (! $run) {
+ $c->add(Hint::warning($this->translate('This Sync Rule has never been run before.')));
+ }
+
+ switch ($rule->get('sync_state')) {
+ case 'unknown':
+ $c->add(Html::tag('p', null, $this->translate(
+ "It's currently unknown whether we are in sync with this rule."
+ . ' You should either check for changes or trigger a new Sync Run.'
+ )));
+ break;
+ case 'in-sync':
+ $c->add(Html::tag('p', null, sprintf(
+ $this->translate('This Sync Rule was last found to by in Sync at %s.'),
+ $rule->get('last_attempt')
+ )));
+ /*
+ TODO: check whether...
+ - there have been imports since then, differing from former ones
+ - there have been activities since then
+ */
+ break;
+ case 'pending-changes':
+ $c->add(Hint::warning($this->translate(
+ 'There are pending changes for this Sync Rule. You should trigger a new'
+ . ' Sync Run.'
+ )));
+ break;
+ case 'failing':
+ $c->add(Hint::error(sprintf(
+ $this->translate(
+ 'This Sync Rule failed when last checked at %s: %s'
+ ),
+ $rule->get('last_attempt'),
+ $rule->get('last_error_message')
+ )));
+ break;
+ }
+
+ $c->add($checkForm);
+ if ($this->hasBranch()) {
+ $objectType = $rule->get('object_type');
+ $table = DbObjectTypeRegistry::tableNameByType($objectType);
+ if (! BranchSupport::existsForTableName($table)) {
+ $this->showNotInBranch(sprintf($this->translate("Synchronizing '%s'"), $objectType));
+ return;
+ }
+ }
+
+ $c->add($runForm);
+
+ if ($run) {
+ $c->add(Html::tag('h3', null, $this->translate('Last sync run details')));
+ $c->add(new SyncRunDetails($run));
+ if ($run->get('rule_name') !== $ruleName) {
+ $c->add(Html::tag('p', null, sprintf(
+ $this->translate("It has been renamed since then, its former name was %s"),
+ $run->get('rule_name')
+ )));
+ }
+ }
+ }
+
+ /**
+ * @param SyncRule $rule
+ */
+ protected function addPropertyHint(SyncRule $rule)
+ {
+ $this->content()->add(Hint::warning(Html::sprintf(
+ $this->translate('You must define some %s before you can run this Sync Rule'),
+ new Link(
+ $this->translate('Sync Properties'),
+ 'director/syncrule/property',
+ ['rule_id' => $rule->get('id')]
+ )
+ )));
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function addAction()
+ {
+ $this->editAction();
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ * @throws \Exception
+ */
+ public function previewAction()
+ {
+ $rule = $this->requireSyncRule();
+ $branchSupport = BranchSupport::existsForSyncRule($rule);
+ $branchStore = new BranchStore($this->db());
+ $owner = $this->getAuth()->getUser()->getUsername();
+ if ($branchSupport) {
+ if ($this->getBranch()->isBranch()) {
+ $tmpBranchName = sprintf(
+ '%s/%s-%s',
+ Branch::PREFIX_SYNC_PREVIEW,
+ $this->getBranch()->getUuid()->toString(),
+ $rule->get('id')
+ );
+ // We could keep changes for preview on branch too
+ $branchStore->deleteByName($tmpBranchName);
+ $tmpBranch = $branchStore->cloneBranchForSync($this->getBranch(), $tmpBranchName, $owner);
+ $after = 1600000000; // a date in 2020, minus 10000000
+ } else {
+ $tmpBranchName = Branch::PREFIX_SYNC_PREVIEW . '/' . $rule->get('id');
+ $tmpBranch = $branchStore->fetchOrCreateByName($tmpBranchName, $owner);
+ $after = null;
+ }
+ $store = new DbObjectStore($this->db(), $tmpBranch);
+ } else {
+ $tmpBranch = $store = null;
+ }
+
+ $this->tabs(new SyncRuleTabs($rule))->activate('preview');
+ $this->addTitle($this->translate('Sync Preview'));
+ $sync = new Sync($rule, $store);
+ $keepBranchPreview = false;
+ if ($tmpBranch) {
+ if ($lastTime = $branchStore->getLastActivityTime($tmpBranch, $after)) {
+ if ((time() - $lastTime) > 100) {
+ $branchStore->wipeBranch($tmpBranch, $after);
+ } else {
+ $here = (new ClickHereForm())->handleRequest($this->getServerRequest());
+ if ($here->hasBeenClicked()) {
+ $branchStore->wipeBranch($tmpBranch, $after);
+ $this->redirectNow($this->url());
+ } else {
+ $keepBranchPreview = true;
+ }
+ $this->content()->add(Hint::info(Html::sprintf(
+ $this->translate('This preview has been generated %s, please click %s to regenerate it'),
+ DateFormatter::timeAgo($lastTime),
+ $here
+ )));
+ }
+ }
+ }
+ if (!$keepBranchPreview) {
+ $modifications = $sync->getExpectedModifications();
+ }
+
+ if ($tmpBranch) {
+ try {
+ if (!$keepBranchPreview) {
+ $sync->apply();
+ }
+ } catch (\Exception $e) {
+ $this->content()->add(Hint::error($e->getMessage()));
+ return;
+ }
+
+ $changes = new BranchActivityTable($tmpBranch->getUuid(), $this->db());
+ $changes->disableObjectLink();
+ if (count($changes) === 0) {
+ $this->showInSync();
+ }
+ $changes->renderTo($this);
+ } else {
+ if (empty($modifications)) {
+ $this->showInSync();
+ return;
+ }
+ $this->showExpectedModificationSummary($modifications);
+ }
+ }
+
+ protected function showInSync()
+ {
+ $this->content()->add(Hint::ok($this->translate(
+ 'This Sync Rule is in sync and would currently not apply any changes'
+ )));
+ }
+
+ protected function showExpectedModificationSummary($modifications)
+ {
+ $create = [];
+ $modify = [];
+ $delete = [];
+ $modifiedProperties = [];
+ /** @var IcingaObject $object */
+ foreach ($modifications as $object) {
+ if ($object->hasBeenLoadedFromDb()) {
+ if ($object->shouldBeRemoved()) {
+ $delete[] = $object;
+ } else {
+ $modify[] = $object;
+ foreach ($object->getModifiedProperties() as $property => $value) {
+ if (isset($modifiedProperties[$property])) {
+ $modifiedProperties[$property]++;
+ } else {
+ $modifiedProperties[$property] = 1;
+ }
+ }
+ if (! $object instanceof IcingaObject) {
+ continue;
+ }
+ if ($object->supportsGroups()) {
+ if ($object->hasModifiedGroups()) {
+ if (isset($modifiedProperties['groups'])) {
+ $modifiedProperties['groups']++;
+ } else {
+ $modifiedProperties['groups'] = 1;
+ }
+ }
+ }
+
+ if ($object->supportsImports()) {
+ if ($object->imports()->hasBeenModified()) {
+ if (isset($modifiedProperties['imports'])) {
+ $modifiedProperties['imports']++;
+ } else {
+ $modifiedProperties['imports'] = 1;
+ }
+ }
+ }
+ if ($object->supportsCustomVars()) {
+ if ($object->vars()->hasBeenModified()) {
+ foreach ($object->vars() as $var) {
+ if ($var->isNew()) {
+ $varName = 'add vars.' . $var->getKey();
+ } elseif ($var->hasBeenDeleted()) {
+ $varName = 'remove vars.' . $var->getKey();
+ } elseif ($var->hasBeenModified()) {
+ $varName = 'vars.' . $var->getKey();
+ } else {
+ continue;
+ }
+ if (isset($modifiedProperties[$varName])) {
+ $modifiedProperties[$varName]++;
+ } else {
+ $modifiedProperties[$varName] = 1;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ $create[] = $object;
+ }
+ }
+
+ $content = $this->content();
+ if (! empty($delete)) {
+ $content->add([
+ Html::tag('h2', ['class' => 'icon-cancel action-delete'], sprintf(
+ $this->translate('%d object(s) will be deleted'),
+ count($delete)
+ )),
+ $this->objectList($delete)
+ ]);
+ }
+ if (! empty($modify)) {
+ $content->add([
+ Html::tag('h2', ['class' => 'icon-wrench action-modify'], sprintf(
+ $this->translate('%d object(s) will be modified'),
+ count($modify)
+ )),
+ $this->listModifiedProperties($modifiedProperties),
+ $this->objectList($modify),
+ ]);
+ }
+ if (! empty($create)) {
+ $content->add([
+ Html::tag('h2', ['class' => 'icon-plus action-create'], sprintf(
+ $this->translate('%d object(s) will be created'),
+ count($create)
+ )),
+ $this->objectList($create)
+ ]);
+ }
+ }
+
+ /**
+ * @param IcingaObject[] $objects
+ * @return \ipl\Html\HtmlElement
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function objectList($objects)
+ {
+ return Html::tag('p', $this->firstNames($objects));
+ }
+
+ /**
+ * Lots of duplicated code, this whole diff logic should be mouved to a
+ * dedicated class
+ *
+ * @param IcingaObject[] $objects
+ * @param int $max
+ * @return string
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ protected function firstNames($objects, $max = 50)
+ {
+ $names = [];
+ $list = new UnorderedList();
+ $list->addAttributes([
+ 'style' => 'list-style-type: none; marign: 0; padding: 0',
+ ]);
+ $total = count($objects);
+ $i = 0;
+ PrefetchCache::forget();
+ IcingaHost::clearAllPrefetchCaches(); // why??
+ IcingaService::clearAllPrefetchCaches();
+ foreach ($objects as $object) {
+ $i++;
+ $name = $this->getObjectNameString($object);
+ if ($object->hasBeenLoadedFromDb()) {
+ if ($object instanceof IcingaHost) {
+ $names[$name] = Link::create(
+ $name,
+ 'director/host',
+ ['name' => $name],
+ ['data-base-target' => '_next']
+ );
+ $oldObject = IcingaHost::load($object->getObjectName(), $this->db());
+ $cfgNew = new IcingaConfig($this->db());
+ $cfgOld = new IcingaConfig($this->db());
+ $oldObject->renderToConfig($cfgOld);
+ $object->renderToConfig($cfgNew);
+ foreach (IcingaConfigDiff::getDiffs($cfgOld, $cfgNew) as $file => $diff) {
+ $names[$name . '___PRETITLE___' . $file] = Html::tag('h3', $file);
+ $names[$name . '___PREVIEW___' . $file] = $diff;
+ }
+ } elseif ($object instanceof IcingaService && $object->isObject()) {
+ $host = $object->getRelated('host');
+
+ $names[$name] = Link::create(
+ $name,
+ 'director/service/edit',
+ [
+ 'name' => $object->getObjectName(),
+ 'host' => $host->getObjectName()
+ ],
+ ['data-base-target' => '_next']
+ );
+ $oldObject = IcingaService::load([
+ 'host_id' => $host->get('id'),
+ 'object_name' => $object->getObjectName()
+ ], $this->db());
+
+ $cfgNew = new IcingaConfig($this->db());
+ $cfgOld = new IcingaConfig($this->db());
+ $oldObject->renderToConfig($cfgOld);
+ $object->renderToConfig($cfgNew);
+ foreach (IcingaConfigDiff::getDiffs($cfgOld, $cfgNew) as $file => $diff) {
+ $names[$name . '___PRETITLE___' . $file] = Html::tag('h3', $file);
+ $names[$name . '___PREVIEW___' . $file] = $diff;
+ }
+ } else {
+ $names[$name] = $name;
+ }
+ } else {
+ $names[$name] = $name;
+ }
+ if ($i === $max) {
+ break;
+ }
+ }
+ ksort($names);
+
+ foreach ($names as $name) {
+ $list->addItem($name);
+ }
+
+ if ($total > $max) {
+ $list->add(sprintf(
+ $this->translate('...and %d more'),
+ $total - $max
+ ));
+ }
+
+ return $list;
+ }
+
+ protected function listModifiedProperties($properties)
+ {
+ $list = new UnorderedList();
+ foreach ($properties as $property => $cnt) {
+ $list->addItem("${cnt}x $property");
+ }
+
+ return $list;
+ }
+
+ protected function getObjectNameString($object)
+ {
+ if ($object instanceof IcingaService) {
+ if ($object->isObject()) {
+ return $object->getRelated('host')->getObjectName()
+ . ': ' . $object->getObjectName();
+ } else {
+ return $object->getObjectName();
+ }
+ } elseif ($object instanceof IcingaHost) {
+ return $object->getObjectName();
+ } elseif ($object instanceof ExportInterface) {
+ return $object->getUniqueIdentifier();
+ } elseif ($object instanceof IcingaObject) {
+ return $object->getObjectName();
+ } else {
+ /** @var \Icinga\Module\Director\Data\Db\DbObject $object */
+ return json_encode($object->getKeyParams());
+ }
+ }
+
+ /**
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function editAction()
+ {
+ $form = SyncRuleForm::load()
+ ->setListUrl('director/syncrules')
+ ->setDb($this->db());
+
+ if ($id = $this->params->get('id')) {
+ $form->loadObject((int) $id);
+ /** @var SyncRule $rule */
+ $rule = $form->getObject();
+ $this->tabs(new SyncRuleTabs($rule))->activate('edit');
+ $this->addTitle(sprintf(
+ $this->translate('Sync rule: %s'),
+ $rule->get('rule_name')
+ ));
+ $this->addMainActions();
+
+ if (! $rule->hasSyncProperties()) {
+ $this->addPropertyHint($rule);
+ }
+ if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
+ return;
+ }
+
+ } else {
+ $this->addTitle($this->translate('Add sync rule'));
+ $this->tabs(new SyncRuleTabs())->activate('add');
+ if ($this->showNotInBranch($this->translate('Creating Sync Rules'))) {
+ return;
+ }
+ }
+
+ $form->handleRequest();
+ $this->content()->add($form);
+ }
+
+ /**
+ * @throws \Icinga\Exception\MissingParameterException
+ * @throws \Icinga\Exception\NotFoundError
+ */
+ public function cloneAction()
+ {
+ $id = $this->params->getRequired('id');
+ $rule = SyncRule::loadWithAutoIncId((int) $id, $this->db());
+ $this->tabs()->add('show', [
+ 'url' => 'director/syncrule',
+ 'urlParams' => ['id' => $id],
+ 'label' => $this->translate('Sync rule'),
+ ])->add('clone', [
+ 'url' => 'director/syncrule/clone',
+ 'urlParams' => ['id' => $id],
+ 'label' => $this->translate('Clone'),
+ ])->activate('clone');
+ $this->addTitle('Clone: %s', $rule->get('rule_name'));
+ $this->actions()->add(
+ Link::create(
+ $this->