diff --git a/.travis.yml b/.travis.yml
index 03ff00591..d054f696a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,6 +16,8 @@ addons:
- libmysqlclient-dev
- libedit-dev
- libyajl-dev
+ - libwxbase3.0-dev
+ - libwxgtk3.0-dev
before_script:
- mkdir build
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ca7c2719f..2274a92db 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -37,6 +37,7 @@ option(ICINGA2_WITH_HELLO "Build the hello module" OFF)
option(ICINGA2_WITH_LIVESTATUS "Build the Livestatus module" ON)
option(ICINGA2_WITH_NOTIFICATION "Build the notification module" ON)
option(ICINGA2_WITH_PERFDATA "Build the perfdata module" ON)
+option(ICINGA2_WITH_STUDIO "Build the Icinga Studio application" OFF)
file(STRINGS icinga2.spec VERSION_LINE REGEX "^Version: ")
string(REPLACE "Version: " "" ICINGA2_VERSION ${VERSION_LINE})
@@ -244,6 +245,10 @@ add_subdirectory(test)
add_subdirectory(agent)
add_subdirectory(plugins)
+if(ICINGA2_WITH_STUDIO)
+ add_subdirectory(icinga-studio)
+endif()
+
set(CPACK_PACKAGE_NAME "Icinga2")
set(CPACK_PACKAGE_VENDOR "Icinga Development Team")
set(CPACK_PACKAGE_VERSION ${ICINGA2_VERSION})
diff --git a/icinga-studio/CMakeLists.txt b/icinga-studio/CMakeLists.txt
new file mode 100644
index 000000000..40394a24c
--- /dev/null
+++ b/icinga-studio/CMakeLists.txt
@@ -0,0 +1,65 @@
+# Icinga 2
+# Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org)
+#
+# 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+set(wxWidgets_CONFIGURATION mswu)
+find_package(wxWidgets COMPONENTS core base propgrid REQUIRED)
+include(${wxWidgets_USE_FILE})
+
+if(MSVC)
+ set(WindowsSources icinga.rc)
+else()
+ set(WindowsSources "")
+endif()
+
+add_executable(icinga-studio MACOSX_BUNDLE WIN32 icinga-studio.cpp
+ forms.cpp aboutform.cpp connectform.cpp mainform.cpp
+ icinga.icns api.cpp ${WindowsSources})
+
+include_directories(${Boost_INCLUDE_DIRS})
+target_link_libraries(icinga-studio ${Boost_LIBRARIES} ${wxWidgets_LIBRARIES} base remote)
+
+if(APPLE)
+ set_source_files_properties(icinga.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
+endif()
+
+set_target_properties (
+ icinga-studio PROPERTIES
+ INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR}/icinga2
+ FOLDER Bin
+ OUTPUT_NAME icinga-studio
+ MACOSX_BUNDLE_INFO_STRING "Icinga Studio"
+ MACOSX_BUNDLE_BUNDLE_NAME "Icinga Studio"
+ MACOSX_BUNDLE_GUI_IDENTIFIER "Icinga Studio"
+ MACOSX_BUNDLE_ICON_FILE icinga.icns
+ MACOSX_BUNDLE_SHORT_VERSION_STRING "${GIT_VERSION}"
+ MACOSX_BUNDLE_LONG_VERSION_STRING "${GIT_VERSION}"
+ MACOSX_BUNDLE_COPYRIGHT "(c) Icinga Development Team"
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/MacOSXBundleInfo.plist.in"
+)
+
+if(WIN32)
+ set(InstallPath "${CMAKE_INSTALL_SBINDIR}")
+else()
+ set(InstallPath "${CMAKE_INSTALL_BINDIR}")
+endif()
+
+install(
+ TARGETS icinga-studio
+ RUNTIME DESTINATION ${InstallPath}
+ BUNDLE DESTINATION ${InstallPath}
+)
+
diff --git a/icinga-studio/IcingaStudio.fbp b/icinga-studio/IcingaStudio.fbp
new file mode 100644
index 000000000..33038d07a
--- /dev/null
+++ b/icinga-studio/IcingaStudio.fbp
@@ -0,0 +1,2128 @@
+
+
+
+
+
diff --git a/icinga-studio/MacOSXBundleInfo.plist.in b/icinga-studio/MacOSXBundleInfo.plist.in
new file mode 100644
index 000000000..681035201
--- /dev/null
+++ b/icinga-studio/MacOSXBundleInfo.plist.in
@@ -0,0 +1,38 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ ${MACOSX_BUNDLE_EXECUTABLE_NAME}
+ CFBundleGetInfoString
+ ${MACOSX_BUNDLE_INFO_STRING}
+ CFBundleIconFile
+ ${MACOSX_BUNDLE_ICON_FILE}
+ CFBundleIdentifier
+ ${MACOSX_BUNDLE_GUI_IDENTIFIER}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleLongVersionString
+ ${MACOSX_BUNDLE_LONG_VERSION_STRING}
+ CFBundleName
+ ${MACOSX_BUNDLE_BUNDLE_NAME}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ ${MACOSX_BUNDLE_SHORT_VERSION_STRING}
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ ${MACOSX_BUNDLE_BUNDLE_VERSION}
+ CSResourcesFileMapped
+
+ LSRequiresCarbon
+
+ NSHumanReadableCopyright
+ ${MACOSX_BUNDLE_COPYRIGHT}
+ NSHighResolutionCapable
+
+
+
diff --git a/icinga-studio/aboutform.cpp b/icinga-studio/aboutform.cpp
new file mode 100644
index 000000000..d47b22c1b
--- /dev/null
+++ b/icinga-studio/aboutform.cpp
@@ -0,0 +1,30 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "base/application.hpp"
+#include "icinga-studio/aboutform.hpp"
+
+using namespace icinga;
+
+AboutForm::AboutForm(wxWindow *parent)
+ : AboutFormBase(parent)
+{
+ std::string version = "Version " + Application::GetVersion();
+ m_VersionLabel->SetLabelText(version);
+}
diff --git a/icinga-studio/aboutform.hpp b/icinga-studio/aboutform.hpp
new file mode 100644
index 000000000..58c6da516
--- /dev/null
+++ b/icinga-studio/aboutform.hpp
@@ -0,0 +1,36 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef ABOUTFORM_H
+#define ABOUTFORM_H
+
+#include "icinga-studio/forms.h"
+
+namespace icinga
+{
+
+class AboutForm : public AboutFormBase
+{
+public:
+ AboutForm(wxWindow *parent);
+};
+
+}
+
+#endif /* ABOUTFORM_H */
\ No newline at end of file
diff --git a/icinga-studio/api.cpp b/icinga-studio/api.cpp
new file mode 100644
index 000000000..ac9073dd1
--- /dev/null
+++ b/icinga-studio/api.cpp
@@ -0,0 +1,165 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "icinga-studio/api.hpp"
+#include "remote/base64.hpp"
+#include "base/json.hpp"
+#include "base/logger.hpp"
+#include "base/exception.hpp"
+#include
+
+using namespace icinga;
+
+ApiClient::ApiClient(const String& host, const String& port,
+ const String& user, const String& password)
+ : m_Connection(new HttpClientConnection(host, port, true)), m_User(user), m_Password(password)
+{
+ m_Connection->Start();
+}
+
+void ApiClient::GetTypes(const TypesCompletionCallback& callback) const
+{
+ boost::shared_ptr req = m_Connection->NewRequest();
+ req->RequestMethod = "GET";
+ req->RequestUrl = new Url("https://" + m_Connection->GetHost() + ":" + m_Connection->GetPort() + "/v1/types");
+ req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password));
+ m_Connection->SubmitRequest(req, boost::bind(TypesHttpCompletionCallback, _1, _2, callback));
+}
+
+void ApiClient::TypesHttpCompletionCallback(HttpRequest& request, HttpResponse& response,
+ const TypesCompletionCallback& callback)
+{
+ Dictionary::Ptr result;
+
+ String body;
+ char buffer[1024];
+ size_t count;
+
+ while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0)
+ body += String(buffer, buffer + count);
+
+ std::vector types;
+
+ try {
+ result = JsonDecode(body);
+
+ Array::Ptr results = result->Get("results");
+
+ ObjectLock olock(results);
+ BOOST_FOREACH(const Dictionary::Ptr typeInfo, results)
+ {
+ ApiType::Ptr type = new ApiType();;
+ type->Abstract = typeInfo->Get("abstract");
+ type->BaseName = typeInfo->Get("base");
+ type->Name = typeInfo->Get("name");
+ type->PluralName = typeInfo->Get("plural_name");
+ // TODO: attributes
+ types.push_back(type);
+ }
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "ApiClient")
+ << "Error while decoding response: " << DiagnosticInformation(ex);
+ }
+
+ callback(types);
+}
+
+void ApiClient::GetObjects(const String& pluralType, const ObjectsCompletionCallback& callback,
+ const std::vector& names, const std::vector& attrs) const
+{
+ String url = "https://" + m_Connection->GetHost() + ":" + m_Connection->GetPort() + "/v1/" + pluralType;
+ String qp;
+
+ BOOST_FOREACH(const String& name, names) {
+ if (!qp.IsEmpty())
+ qp += "&";
+
+ qp += pluralType.ToLower() + "=" + name;
+ }
+
+ BOOST_FOREACH(const String& attr, attrs) {
+ if (!qp.IsEmpty())
+ qp += "&";
+
+ qp += "attrs[]=" + attr;
+ }
+
+ boost::shared_ptr req = m_Connection->NewRequest();
+ req->RequestMethod = "GET";
+ req->RequestUrl = new Url(url + "?" + qp);
+ req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password));
+ m_Connection->SubmitRequest(req, boost::bind(ObjectsHttpCompletionCallback, _1, _2, callback));
+}
+
+void ApiClient::ObjectsHttpCompletionCallback(HttpRequest& request,
+ HttpResponse& response, const ObjectsCompletionCallback& callback)
+{
+ Dictionary::Ptr result;
+
+ String body;
+ char buffer[1024];
+ size_t count;
+
+ while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0)
+ body += String(buffer, buffer + count);
+
+ std::vector objects;
+
+ try {
+ result = JsonDecode(body);
+
+ Array::Ptr results = result->Get("results");
+
+ ObjectLock olock(results);
+ BOOST_FOREACH(const Dictionary::Ptr objectInfo, results)
+ {
+ ApiObject::Ptr object = new ApiObject();
+
+ Dictionary::Ptr attrs = objectInfo->Get("attrs");
+
+ {
+ ObjectLock olock(attrs);
+ BOOST_FOREACH(const Dictionary::Pair& kv, attrs)
+ {
+ object->Attrs[kv.first] = kv.second;
+ }
+ }
+
+ Array::Ptr used_by = objectInfo->Get("used_by");
+
+ {
+ ObjectLock olock(used_by);
+ BOOST_FOREACH(const Dictionary::Ptr& refInfo, used_by)
+ {
+ ApiObjectReference ref;
+ ref.Name = refInfo->Get("name");
+ ref.Type = refInfo->Get("type");
+ object->UsedBy.push_back(ref);
+ }
+ }
+
+ objects.push_back(object);
+ }
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "ApiClient")
+ << "Error while decoding response: " << DiagnosticInformation(ex);
+ }
+
+ callback(objects);
+}
\ No newline at end of file
diff --git a/icinga-studio/api.hpp b/icinga-studio/api.hpp
new file mode 100644
index 000000000..c3bb0351a
--- /dev/null
+++ b/icinga-studio/api.hpp
@@ -0,0 +1,111 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef API_H
+#define API_H
+
+#include "remote/httpclientconnection.hpp"
+#include "base/value.hpp"
+#include
+
+namespace icinga
+{
+
+struct ApiFieldAttributes
+{
+public:
+ bool Config;
+ bool Internal;
+ bool Required;
+ bool State;
+};
+
+class ApiType;
+
+struct ApiField
+{
+public:
+ String Name;
+ int ID;
+ int ArrayRank;
+ ApiFieldAttributes FieldAttributes;
+ String TypeName;
+ intrusive_ptr Type;
+};
+
+class ApiType : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ApiType);
+
+ String Name;
+ String PluralName;
+ String BaseName;
+ ApiType::Ptr Base;
+ bool Abstract;
+ std::map Fields;
+ std::vector PrototypeKeys;
+};
+
+struct ApiObjectReference
+{
+public:
+ String Name;
+ String Type;
+};
+
+struct ApiObject : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ApiObject);
+
+ std::map Attrs;
+ std::vector UsedBy;
+};
+
+class ApiClient : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ApiClient);
+
+ ApiClient(const String& host, const String& port,
+ const String& user, const String& password);
+
+ typedef boost::function&)> TypesCompletionCallback;
+ void GetTypes(const TypesCompletionCallback& callback) const;
+
+ typedef boost::function&)> ObjectsCompletionCallback;
+ void GetObjects(const String& pluralType, const ObjectsCompletionCallback& callback,
+ const std::vector& names = std::vector(),
+ const std::vector& attrs = std::vector()) const;
+
+private:
+ HttpClientConnection::Ptr m_Connection;
+ String m_User;
+ String m_Password;
+
+ static void TypesHttpCompletionCallback(HttpRequest& request,
+ HttpResponse& response, const TypesCompletionCallback& callback);
+ static void ObjectsHttpCompletionCallback(HttpRequest& request,
+ HttpResponse& response, const ObjectsCompletionCallback& callback);
+};
+
+}
+
+#endif /* API_H */
\ No newline at end of file
diff --git a/icinga-studio/connectform.cpp b/icinga-studio/connectform.cpp
new file mode 100644
index 000000000..1a38ee1b3
--- /dev/null
+++ b/icinga-studio/connectform.cpp
@@ -0,0 +1,63 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "icinga-studio/connectform.hpp"
+#include
+#include
+
+using namespace icinga;
+
+ConnectForm::ConnectForm(wxWindow *parent, const Url::Ptr& url)
+ : ConnectFormBase(parent)
+{
+#ifdef _WIN32
+ SetIcon(wxICON(icinga));
+#endif /* _WIN32 */
+
+ std::string authority = url->GetAuthority();
+
+ std::vector tokens;
+ boost::algorithm::split(tokens, authority, boost::is_any_of("@"));
+
+ if (tokens.size() > 1) {
+ std::vector userinfo;
+ boost::algorithm::split(userinfo, tokens[0], boost::is_any_of(":"));
+
+ m_UserText->SetValue(userinfo[0]);
+ m_PasswordText->SetValue(userinfo[1]);
+ }
+
+ std::vector hostport;
+ boost::algorithm::split(hostport, tokens.size() > 1 ? tokens[1] : tokens[0], boost::is_any_of(":"));
+
+ m_HostText->SetValue(hostport[0]);
+
+ if (hostport.size() > 1)
+ m_PortText->SetValue(hostport[1]);
+ else
+ m_PortText->SetValue("5665");
+}
+
+Url::Ptr ConnectForm::GetUrl(void) const
+{
+ wxString url = "https://" + m_UserText->GetValue() + ":" + m_PasswordText->GetValue()
+ + "@" + m_HostText->GetValue() + ":" + m_PortText->GetValue() + "/";
+
+ return new Url(url.ToStdString());
+}
diff --git a/icinga-studio/connectform.hpp b/icinga-studio/connectform.hpp
new file mode 100644
index 000000000..8c0a9a9a5
--- /dev/null
+++ b/icinga-studio/connectform.hpp
@@ -0,0 +1,39 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef CONNECTFORM_H
+#define CONNECTFORM_H
+
+#include "remote/url.hpp"
+#include "icinga-studio/forms.h"
+
+namespace icinga
+{
+
+class ConnectForm : public ConnectFormBase
+{
+public:
+ ConnectForm(wxWindow *parent, const Url::Ptr& url);
+
+ Url::Ptr GetUrl(void) const;
+};
+
+}
+
+#endif /* CONNECTFORM_H */
\ No newline at end of file
diff --git a/icinga-studio/forms.cpp b/icinga-studio/forms.cpp
new file mode 100644
index 000000000..ec3b25ff3
--- /dev/null
+++ b/icinga-studio/forms.cpp
@@ -0,0 +1,242 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Jun 17 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "forms.h"
+
+#include "icinga.xpm"
+
+///////////////////////////////////////////////////////////////////////////
+
+MainFormBase::MainFormBase( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
+{
+ this->SetSizeHints( wxSize( 800,569 ), wxDefaultSize );
+
+ m_MenuBar = new wxMenuBar( 0 );
+ wxMenu* m_FileMenu;
+ m_FileMenu = new wxMenu();
+ wxMenuItem* m_QuitMenuItem;
+ m_QuitMenuItem = new wxMenuItem( m_FileMenu, wxID_EXIT, wxString( wxT("&Quit") ) , wxEmptyString, wxITEM_NORMAL );
+ m_FileMenu->Append( m_QuitMenuItem );
+
+ m_MenuBar->Append( m_FileMenu, wxT("&File") );
+
+ wxMenu* m_HelpMenu;
+ m_HelpMenu = new wxMenu();
+ wxMenuItem* m_AboutMenuItem;
+ m_AboutMenuItem = new wxMenuItem( m_HelpMenu, wxID_ABOUT, wxString( wxT("&About Icinga Studio...") ) , wxEmptyString, wxITEM_NORMAL );
+ m_HelpMenu->Append( m_AboutMenuItem );
+
+ m_MenuBar->Append( m_HelpMenu, wxT("&Help") );
+
+ this->SetMenuBar( m_MenuBar );
+
+ wxBoxSizer* m_DialogSizer;
+ m_DialogSizer = new wxBoxSizer( wxVERTICAL );
+
+ wxBoxSizer* m_ConnectionDetailsSizer;
+ m_ConnectionDetailsSizer = new wxBoxSizer( wxHORIZONTAL );
+
+ m_TypesTree = new wxTreeCtrl( this, wxID_ANY, wxDefaultPosition, wxSize( 315,-1 ), wxTR_DEFAULT_STYLE|wxTR_HIDE_ROOT );
+ m_ConnectionDetailsSizer->Add( m_TypesTree, 0, wxALL|wxEXPAND, 2 );
+
+ wxBoxSizer* m_ObjectDetailsSizer;
+ m_ObjectDetailsSizer = new wxBoxSizer( wxVERTICAL );
+
+ m_ObjectsList = new wxListCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT );
+ m_ObjectDetailsSizer->Add( m_ObjectsList, 1, wxALL|wxEXPAND, 2 );
+
+ m_PropertyGrid = new wxPropertyGrid(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_DEFAULT_STYLE);
+ m_ObjectDetailsSizer->Add( m_PropertyGrid, 1, wxALL|wxEXPAND, 5 );
+
+
+ m_ConnectionDetailsSizer->Add( m_ObjectDetailsSizer, 1, wxEXPAND, 5 );
+
+
+ m_DialogSizer->Add( m_ConnectionDetailsSizer, 1, wxEXPAND, 5 );
+
+
+ this->SetSizer( m_DialogSizer );
+ this->Layout();
+ m_StatusBar = this->CreateStatusBar( 1, wxST_SIZEGRIP, wxID_ANY );
+
+ this->Centre( wxBOTH );
+
+ // Connect Events
+ this->Connect( m_QuitMenuItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainFormBase::OnQuitClicked ) );
+ this->Connect( m_AboutMenuItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainFormBase::OnAboutClicked ) );
+ m_TypesTree->Connect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( MainFormBase::OnTypeSelected ), NULL, this );
+ m_ObjectsList->Connect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( MainFormBase::OnObjectSelected ), NULL, this );
+}
+
+MainFormBase::~MainFormBase()
+{
+ // Disconnect Events
+ this->Disconnect( wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainFormBase::OnQuitClicked ) );
+ this->Disconnect( wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( MainFormBase::OnAboutClicked ) );
+ m_TypesTree->Disconnect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( MainFormBase::OnTypeSelected ), NULL, this );
+ m_ObjectsList->Disconnect( wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler( MainFormBase::OnObjectSelected ), NULL, this );
+
+}
+
+ConnectFormBase::ConnectFormBase( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
+{
+ this->SetSizeHints( wxDefaultSize, wxDefaultSize );
+
+ wxBoxSizer* m_DialogSizer;
+ m_DialogSizer = new wxBoxSizer( wxVERTICAL );
+
+ wxPanel* m_ConnectionDetailsPanel;
+ m_ConnectionDetailsPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+ wxStaticBoxSizer* m_DetailsSizer;
+ m_DetailsSizer = new wxStaticBoxSizer( new wxStaticBox( m_ConnectionDetailsPanel, wxID_ANY, wxT("Connection Details") ), wxVERTICAL );
+
+ wxStaticText* m_HostLabel;
+ m_HostLabel = new wxStaticText( m_DetailsSizer->GetStaticBox(), wxID_ANY, wxT("Host:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_HostLabel->Wrap( -1 );
+ m_DetailsSizer->Add( m_HostLabel, 0, wxALL, 5 );
+
+ m_HostText = new wxTextCtrl( m_DetailsSizer->GetStaticBox(), wxID_OK, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+ m_DetailsSizer->Add( m_HostText, 0, wxALL|wxEXPAND, 5 );
+
+ wxStaticText* m_PortLabel;
+ m_PortLabel = new wxStaticText( m_DetailsSizer->GetStaticBox(), wxID_ANY, wxT("Port:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_PortLabel->Wrap( -1 );
+ m_DetailsSizer->Add( m_PortLabel, 0, wxALL, 5 );
+
+ m_PortText = new wxTextCtrl( m_DetailsSizer->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+ m_DetailsSizer->Add( m_PortText, 0, wxALL, 5 );
+
+ wxStaticText* m_UserLabel;
+ m_UserLabel = new wxStaticText( m_DetailsSizer->GetStaticBox(), wxID_ANY, wxT("API User:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_UserLabel->Wrap( -1 );
+ m_DetailsSizer->Add( m_UserLabel, 0, wxALL, 5 );
+
+ m_UserText = new wxTextCtrl( m_DetailsSizer->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
+ m_DetailsSizer->Add( m_UserText, 0, wxALL|wxEXPAND, 5 );
+
+ wxStaticText* m_PasswordLabel;
+ m_PasswordLabel = new wxStaticText( m_DetailsSizer->GetStaticBox(), wxID_ANY, wxT("API Password:"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_PasswordLabel->Wrap( -1 );
+ m_DetailsSizer->Add( m_PasswordLabel, 0, wxALL, 5 );
+
+ m_PasswordText = new wxTextCtrl( m_DetailsSizer->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PASSWORD );
+ m_DetailsSizer->Add( m_PasswordText, 0, wxALL|wxEXPAND, 5 );
+
+ wxStaticText* m_InfoLabel;
+ m_InfoLabel = new wxStaticText( m_DetailsSizer->GetStaticBox(), wxID_ANY, wxT("You can find the username and password for the default user in /etc/icinga2/conf.d/api-users.conf."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_InfoLabel->Wrap( 270 );
+ m_DetailsSizer->Add( m_InfoLabel, 0, wxALL, 5 );
+
+
+ m_ConnectionDetailsPanel->SetSizer( m_DetailsSizer );
+ m_ConnectionDetailsPanel->Layout();
+ m_DetailsSizer->Fit( m_ConnectionDetailsPanel );
+ m_DialogSizer->Add( m_ConnectionDetailsPanel, 1, wxEXPAND | wxALL, 5 );
+
+ wxPanel* m_ButtonsPanel;
+ m_ButtonsPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+ wxBoxSizer* m_ButtonsSizer;
+ m_ButtonsSizer = new wxBoxSizer( wxHORIZONTAL );
+
+ wxStdDialogButtonSizer* m_Buttons;
+ wxButton* m_ButtonsOK;
+ wxButton* m_ButtonsCancel;
+ m_Buttons = new wxStdDialogButtonSizer();
+ m_ButtonsOK = new wxButton( m_ButtonsPanel, wxID_OK );
+ m_Buttons->AddButton( m_ButtonsOK );
+ m_ButtonsCancel = new wxButton( m_ButtonsPanel, wxID_CANCEL );
+ m_Buttons->AddButton( m_ButtonsCancel );
+ m_Buttons->Realize();
+
+ m_ButtonsSizer->Add( m_Buttons, 1, wxEXPAND, 5 );
+
+
+ m_ButtonsPanel->SetSizer( m_ButtonsSizer );
+ m_ButtonsPanel->Layout();
+ m_ButtonsSizer->Fit( m_ButtonsPanel );
+ m_DialogSizer->Add( m_ButtonsPanel, 0, wxEXPAND | wxALL, 5 );
+
+
+ this->SetSizer( m_DialogSizer );
+ this->Layout();
+ m_DialogSizer->Fit( this );
+
+ this->Centre( wxBOTH );
+}
+
+ConnectFormBase::~ConnectFormBase()
+{
+}
+
+AboutFormBase::AboutFormBase( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
+{
+ this->SetSizeHints( wxDefaultSize, wxDefaultSize );
+
+ wxBoxSizer* m_DialogSizer;
+ m_DialogSizer = new wxBoxSizer( wxVERTICAL );
+
+ wxBoxSizer* m_InfoSizer;
+ m_InfoSizer = new wxBoxSizer( wxHORIZONTAL );
+
+ wxStaticBitmap* m_ProductIcon;
+ m_ProductIcon = new wxStaticBitmap( this, wxID_ANY, wxBitmap( icinga_xpm ), wxDefaultPosition, wxDefaultSize, 0 );
+ m_InfoSizer->Add( m_ProductIcon, 0, wxALL, 5 );
+
+ wxBoxSizer* m_AboutInfoSizer;
+ m_AboutInfoSizer = new wxBoxSizer( wxVERTICAL );
+
+ wxStaticText* m_ProductNameLabel;
+ m_ProductNameLabel = new wxStaticText( this, wxID_ANY, wxT("Icinga Studio"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_ProductNameLabel->Wrap( -1 );
+ m_AboutInfoSizer->Add( m_ProductNameLabel, 0, wxALL, 5 );
+
+ m_VersionLabel = new wxStaticText( this, wxID_ANY, wxT("Version"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_VersionLabel->Wrap( -1 );
+ m_AboutInfoSizer->Add( m_VersionLabel, 0, wxALL, 5 );
+
+ wxStaticText* m_CopyrightLabel;
+ m_CopyrightLabel = new wxStaticText( this, wxID_ANY, wxT("Copyright (c) 2015 Icinga Development Team"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_CopyrightLabel->Wrap( -1 );
+ m_AboutInfoSizer->Add( m_CopyrightLabel, 0, wxALL, 5 );
+
+
+ m_InfoSizer->Add( m_AboutInfoSizer, 1, wxEXPAND, 5 );
+
+
+ m_DialogSizer->Add( m_InfoSizer, 1, wxEXPAND, 5 );
+
+ wxPanel* m_ButtonsPanel;
+ m_ButtonsPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+ wxBoxSizer* m_ButtonsSizer;
+ m_ButtonsSizer = new wxBoxSizer( wxVERTICAL );
+
+ wxStdDialogButtonSizer* m_Buttons;
+ wxButton* m_ButtonsOK;
+ m_Buttons = new wxStdDialogButtonSizer();
+ m_ButtonsOK = new wxButton( m_ButtonsPanel, wxID_OK );
+ m_Buttons->AddButton( m_ButtonsOK );
+ m_Buttons->Realize();
+
+ m_ButtonsSizer->Add( m_Buttons, 0, wxEXPAND, 5 );
+
+
+ m_ButtonsPanel->SetSizer( m_ButtonsSizer );
+ m_ButtonsPanel->Layout();
+ m_ButtonsSizer->Fit( m_ButtonsPanel );
+ m_DialogSizer->Add( m_ButtonsPanel, 0, wxEXPAND | wxALL, 5 );
+
+
+ this->SetSizer( m_DialogSizer );
+ this->Layout();
+ m_DialogSizer->Fit( this );
+
+ this->Centre( wxBOTH );
+}
+
+AboutFormBase::~AboutFormBase()
+{
+}
diff --git a/icinga-studio/forms.h b/icinga-studio/forms.h
new file mode 100644
index 000000000..613b7d469
--- /dev/null
+++ b/icinga-studio/forms.h
@@ -0,0 +1,105 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Jun 17 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef __FORMS_H__
+#define __FORMS_H__
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+///////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class MainFormBase
+///////////////////////////////////////////////////////////////////////////////
+class MainFormBase : public wxFrame
+{
+ private:
+
+ protected:
+ wxMenuBar* m_MenuBar;
+ wxTreeCtrl* m_TypesTree;
+ wxListCtrl* m_ObjectsList;
+ wxPropertyGrid* m_PropertyGrid;
+ wxStatusBar* m_StatusBar;
+
+ // Virtual event handlers, overide them in your derived class
+ virtual void OnQuitClicked( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnAboutClicked( wxCommandEvent& event ) { event.Skip(); }
+ virtual void OnTypeSelected( wxTreeEvent& event ) { event.Skip(); }
+ virtual void OnObjectSelected( wxListEvent& event ) { event.Skip(); }
+
+
+ public:
+
+ MainFormBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Icinga Studio"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 800,569 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
+
+ ~MainFormBase();
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class ConnectFormBase
+///////////////////////////////////////////////////////////////////////////////
+class ConnectFormBase : public wxDialog
+{
+ private:
+
+ protected:
+ wxTextCtrl* m_HostText;
+ wxTextCtrl* m_PortText;
+ wxTextCtrl* m_UserText;
+ wxTextCtrl* m_PasswordText;
+
+ public:
+
+ ConnectFormBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Icinga Studio - Connect"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE );
+ ~ConnectFormBase();
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class AboutFormBase
+///////////////////////////////////////////////////////////////////////////////
+class AboutFormBase : public wxDialog
+{
+ private:
+
+ protected:
+ wxStaticText* m_VersionLabel;
+
+ public:
+
+ AboutFormBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("About Icinga Studio"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE );
+ ~AboutFormBase();
+
+};
+
+#endif //__FORMS_H__
diff --git a/icinga-studio/icinga-studio.cpp b/icinga-studio/icinga-studio.cpp
new file mode 100644
index 000000000..ff98cfccd
--- /dev/null
+++ b/icinga-studio/icinga-studio.cpp
@@ -0,0 +1,66 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "icinga-studio/connectform.hpp"
+#include "icinga-studio/mainform.hpp"
+#include "base/application.hpp"
+#include
+#include
+#include
+
+using namespace icinga;
+
+class IcingaStudio : public wxApp
+{
+public:
+ virtual bool OnInit(void) override
+ {
+ Application::InitializeBase();
+
+ Url::Ptr pUrl;
+
+ if (argc < 2) {
+ wxConfig config("IcingaStudio");
+ wxString wUrl;
+
+ if (!config.Read("url", &wUrl))
+ wUrl = "https://localhost:5665/";
+
+ std::string url = wUrl.ToStdString();
+
+ ConnectForm f(NULL, new Url(url));
+ if (f.ShowModal() != wxID_OK)
+ return false;
+
+ pUrl = f.GetUrl();
+ url = pUrl->Format();
+ wUrl = url;
+ config.Write("url", wUrl);
+ } else {
+ pUrl = new Url(argv[1].ToStdString());
+ }
+
+ MainForm *m = new MainForm(NULL, pUrl);
+ m->Show();
+
+ return true;
+ }
+};
+
+wxIMPLEMENT_APP(IcingaStudio);
diff --git a/icinga-studio/icinga.icns b/icinga-studio/icinga.icns
new file mode 100644
index 000000000..67f009e32
Binary files /dev/null and b/icinga-studio/icinga.icns differ
diff --git a/icinga-studio/icinga.ico b/icinga-studio/icinga.ico
new file mode 100644
index 000000000..6ff7e9029
Binary files /dev/null and b/icinga-studio/icinga.ico differ
diff --git a/icinga-studio/icinga.rc b/icinga-studio/icinga.rc
new file mode 100644
index 000000000..abcda3f4e
--- /dev/null
+++ b/icinga-studio/icinga.rc
@@ -0,0 +1,34 @@
+#include
+#include "icinga-version.h"
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+icinga ICON "icinga.ico"
+
+VS_VERSION_INFO VERSIONINFO
+FILEVERSION 1,0,0,0
+PRODUCTVERSION 1,0,0,0
+FILEOS VOS__WINDOWS32
+FILETYPE VFT_APP
+FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "Icinga Development Team"
+ VALUE "FileDescription", "Icinga Studio"
+ VALUE "FileVersion", VERSION
+ VALUE "InternalName", "icinga-studio.exe"
+ VALUE "LegalCopyright", "© Icinga Development Team"
+ VALUE "OriginalFilename", "icinga-studio.exe"
+ VALUE "ProductName", "Icinga 2"
+ VALUE "ProductVersion", VERSION
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 0x04E4
+ END
+END
\ No newline at end of file
diff --git a/icinga-studio/icinga.xpm b/icinga-studio/icinga.xpm
new file mode 100644
index 000000000..271363db7
--- /dev/null
+++ b/icinga-studio/icinga.xpm
@@ -0,0 +1,40 @@
+/* XPM */
+static const char *icinga_xpm[] = {
+"32 32 5 1",
+" c None",
+". c #808080",
+"+ c #000000",
+"@ c #C0C0C0",
+"# c #FFFFFF",
+" ",
+" .++++++++++++++++++++++++. ",
+" .+++++++++++++++..+++++++++. ",
+" .+++++++++++++++@##@+++++++++. ",
+" +++++++++++++++.####.+++++++++ ",
+" +++++++++++++++.####.+++++++++ ",
+" ++++++++++++++++####++++++++++ ",
+" ++++++++++++++++@@..++++++++++ ",
+" +++++..+++++++++#.++++++++++++ ",
+" ++++.##@+++++++@#+++++++++++++ ",
+" ++++.###+++++++#.+++++++++.+++ ",
+" ++++.###@++.@@@#+++++++++@##.+ ",
+" +++++++.@#######.+++++++.###@+ ",
+" +++++++++########++++..@####.+ ",
+" ++++++++.########@@###@...@.++ ",
+" ++++++++.#########@..+++++++++ ",
+" ++++++++.########@++++++++++++ ",
+" ++++++++.########.++++++++++++ ",
+" +++++++++########+++++++++++++ ",
+" +++++++++.######.+++++++++++++ ",
+" +++++++++.#....#.+++++++++++++ ",
+" ++++++++.#.++++.#.++++++++++++ ",
+" ++++++++@@++++++##.+++++++++++ ",
+" ++++@##@#+++++++##@+++++++++++ ",
+" +++@####@+++++++..++++++++++++ ",
+" +++######.++++++++++++++++++++ ",
+" +++######.++++++++++++++++++++ ",
+" +++######+++++++++++++++++++++ ",
+" .++.####.++++++++++++++++++++. ",
+" .+++..+++++++++++++++++++++. ",
+" .++++++++++++++++++++++++. ",
+" "};
diff --git a/icinga-studio/mainform.cpp b/icinga-studio/mainform.cpp
new file mode 100644
index 000000000..18aea2674
--- /dev/null
+++ b/icinga-studio/mainform.cpp
@@ -0,0 +1,258 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "icinga-studio/mainform.hpp"
+#include "icinga-studio/aboutform.hpp"
+#include
+#include
+#include
+
+using namespace icinga;
+
+MainForm::MainForm(wxWindow *parent, const Url::Ptr& url)
+ : MainFormBase(parent)
+{
+#ifdef _WIN32
+ SetIcon(wxICON(icinga));
+ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
+#endif /* _WIN32 */
+
+ String host, port, user, pass;
+
+ std::string authority = url->GetAuthority();
+
+ std::vector tokens;
+ boost::algorithm::split(tokens, authority, boost::is_any_of("@"));
+
+ if (tokens.size() > 1) {
+ std::vector userinfo;
+ boost::algorithm::split(userinfo, tokens[0], boost::is_any_of(":"));
+
+ user = userinfo[0];
+ pass = userinfo[1];
+ }
+
+ std::vector hostport;
+ boost::algorithm::split(hostport, tokens.size() > 1 ? tokens[1] : tokens[0], boost::is_any_of(":"));
+
+ host = hostport[0];
+
+ if (hostport.size() > 1)
+ port = hostport[1];
+ else
+ port = "5665";
+
+ m_ApiClient = new ApiClient(host, port, user, pass);
+ m_ApiClient->GetTypes(boost::bind(&MainForm::TypesCompletionHandler, this, _1, true));
+
+ std::string title = host;
+
+ if (port != "5665")
+ title += +":" + port;
+
+ title += " - Icinga Studio";
+ SetTitle(title);
+
+ m_ObjectsList->InsertColumn(0, "Name", 0, 300);
+}
+
+void MainForm::TypesCompletionHandler(const std::vector& types, bool forward)
+{
+ if (forward) {
+ CallAfter(boost::bind(&MainForm::TypesCompletionHandler, this, types, false));
+ return;
+ }
+
+ m_TypesTree->DeleteAllItems();
+ wxTreeItemId rootNode = m_TypesTree->AddRoot("root");
+
+ bool all = false;
+ std::map items;
+
+ m_Types.clear();
+
+ while (!all) {
+ all = true;
+
+ BOOST_FOREACH(const ApiType::Ptr& type, types) {
+ std::string name = type->Name;
+
+ if (items.find(name) != items.end())
+ continue;
+
+ all = false;
+
+ wxTreeItemId parent;
+
+ if (type->BaseName.IsEmpty())
+ parent = rootNode;
+ else {
+ std::map::const_iterator it = items.find(type->BaseName);
+
+ if (it == items.end())
+ continue;
+
+ parent = it->second;
+ }
+
+ m_Types[name] = type;
+ items[name] = m_TypesTree->AppendItem(parent, name, 0);
+ }
+ }
+}
+
+void MainForm::OnTypeSelected(wxTreeEvent& event)
+{
+ wxTreeItemId selectedId = m_TypesTree->GetSelection();
+ wxString typeName = m_TypesTree->GetItemText(selectedId);
+ ApiType::Ptr type = m_Types[typeName.ToStdString()];
+
+ std::vector attrs;
+ attrs.push_back(type->Name.ToLower() + ".__name");
+
+ m_ApiClient->GetObjects(type->PluralName, boost::bind(&MainForm::ObjectsCompletionHandler, this, _1, true),
+ std::vector(), attrs);
+}
+
+void MainForm::ObjectsCompletionHandler(const std::vector& objects, bool forward)
+{
+ if (forward) {
+ CallAfter(boost::bind(&MainForm::ObjectsCompletionHandler, this, objects, false));
+ return;
+ }
+
+ wxTreeItemId selectedId = m_TypesTree->GetSelection();
+ wxString typeName = m_TypesTree->GetItemText(selectedId);
+ ApiType::Ptr type = m_Types[typeName.ToStdString()];
+
+ String nameAttr = type->Name.ToLower() + ".__name";
+
+ m_ObjectsList->DeleteAllItems();
+
+ BOOST_FOREACH(const ApiObject::Ptr& object, objects) {
+ std::map::const_iterator it = object->Attrs.find(nameAttr);
+ if (it == object->Attrs.end())
+ continue;
+ String name = it->second;
+ m_ObjectsList->InsertItem(0, name.GetData());
+ }
+}
+
+void MainForm::OnObjectSelected(wxListEvent& event)
+{
+ wxTreeItemId selectedId = m_TypesTree->GetSelection();
+ wxString typeName = m_TypesTree->GetItemText(selectedId);
+ ApiType::Ptr type = m_Types[typeName.ToStdString()];
+
+ long itemIndex = -1;
+ std::string objectName;
+
+ while ((itemIndex = m_ObjectsList->GetNextItem(itemIndex,
+ wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != wxNOT_FOUND) {
+ objectName = m_ObjectsList->GetItemText(itemIndex);
+ break;
+ }
+
+ if (objectName.empty())
+ return;
+
+ std::vector names;
+ names.push_back(objectName);
+
+ m_ApiClient->GetObjects(type->PluralName, boost::bind(&MainForm::ObjectDetailsCompletionHandler, this, _1, true), names);
+}
+
+wxPGProperty *MainForm::ValueToProperty(const String& name, const Value& value)
+{
+ wxPGProperty *prop;
+
+ if (value.IsNumber()) {
+ double val = value;
+ return new wxFloatProperty(name.GetData(), wxPG_LABEL, value);
+ } else if (value.IsBoolean()) {
+ bool val = value;
+ return new wxBoolProperty(name.GetData(), wxPG_LABEL, value);
+ } else if (value.IsObjectType()) {
+ wxArrayString val;
+ Array::Ptr arr = value;
+ ObjectLock olock(arr);
+ BOOST_FOREACH(const Value& aitem, arr)
+ {
+ String val1 = aitem;
+ val.Add(val1.GetData());
+ }
+
+ return new wxArrayStringProperty(name.GetData(), wxPG_LABEL, val);
+ } else if (value.IsObjectType()) {
+ wxStringProperty *prop = new wxStringProperty(name.GetData(), wxPG_LABEL, "");
+
+ Dictionary::Ptr dict = value;
+ ObjectLock olock(dict);
+ BOOST_FOREACH(const Dictionary::Pair& kv, dict) {
+ prop->AppendChild(ValueToProperty(kv.first, kv.second));
+ }
+
+ return prop;
+ } else {
+ String val = value;
+ return new wxStringProperty(name.GetData(), wxPG_LABEL, val.GetData());
+ }
+}
+
+void MainForm::ObjectDetailsCompletionHandler(const std::vector& objects, bool forward)
+{
+ if (forward) {
+ CallAfter(boost::bind(&MainForm::ObjectDetailsCompletionHandler, this, objects, false));
+ return;
+ }
+
+ wxTreeItemId selectedId = m_TypesTree->GetSelection();
+ wxString typeName = m_TypesTree->GetItemText(selectedId);
+ ApiType::Ptr type = m_Types[typeName.ToStdString()];
+
+ String nameAttr = type->Name.ToLower() + ".__name";
+
+ m_PropertyGrid->Clear();
+
+ if (objects.empty())
+ return;
+
+ ApiObject::Ptr object = objects[0];
+
+ typedef std::pair kv_pair;
+ BOOST_FOREACH(const kv_pair& kv, object->Attrs) {
+ std::vector tokens;
+ boost::algorithm::split(tokens, kv.first, boost::is_any_of("."));
+
+ wxPGProperty *prop = ValueToProperty(tokens[1], kv.second);
+ m_PropertyGrid->Append(prop);
+ m_PropertyGrid->SetPropertyReadOnly(prop);
+ }
+}
+
+void MainForm::OnQuitClicked(wxCommandEvent& event)
+{
+ Close();
+}
+
+void MainForm::OnAboutClicked(wxCommandEvent& event)
+{
+ AboutForm form(this);
+ form.ShowModal();
+}
diff --git a/icinga-studio/mainform.hpp b/icinga-studio/mainform.hpp
new file mode 100644
index 000000000..a961edba0
--- /dev/null
+++ b/icinga-studio/mainform.hpp
@@ -0,0 +1,53 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef MAINFORM_H
+#define MAINFORM_H
+
+#include "icinga-studio/api.hpp"
+#include "remote/url.hpp"
+#include "icinga-studio/forms.h"
+
+namespace icinga
+{
+
+class MainForm : public MainFormBase
+{
+public:
+ MainForm(wxWindow *parent, const Url::Ptr& url);
+
+ virtual void OnQuitClicked(wxCommandEvent& event) override;
+ virtual void OnAboutClicked(wxCommandEvent& event) override;
+ virtual void OnTypeSelected(wxTreeEvent& event) override;
+ virtual void OnObjectSelected(wxListEvent& event) override;
+
+private:
+ ApiClient::Ptr m_ApiClient;
+ std::map m_Types;
+
+ void TypesCompletionHandler(const std::vector& types, bool forward);
+ void ObjectsCompletionHandler(const std::vector& objects, bool forward);
+ void ObjectDetailsCompletionHandler(const std::vector& objects, bool forward);
+
+ wxPGProperty *ValueToProperty(const String& name, const Value& value);
+};
+
+}
+
+#endif /* MAINFORM_H */
\ No newline at end of file
diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt
index 2754ea39a..0adaa2fbd 100644
--- a/lib/base/CMakeLists.txt
+++ b/lib/base/CMakeLists.txt
@@ -86,4 +86,11 @@ install(
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2
)
+if(APPLE)
+ install(
+ TARGETS base
+ LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR}/icinga-studio.app/Contents
+ )
+endif()
+
set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}" PARENT_SCOPE)
diff --git a/lib/base/exception.hpp b/lib/base/exception.hpp
index 7ceec1ff3..90e219239 100644
--- a/lib/base/exception.hpp
+++ b/lib/base/exception.hpp
@@ -147,7 +147,15 @@ typedef boost::error_info errinfo_getadd
inline std::string to_string(const errinfo_getaddrinfo_error& e)
{
- return "[errinfo_getaddrinfo_error] = " + String(gai_strerror(e.value())) + "\n";
+ String msg;
+
+#ifdef _WIN32
+ msg = gai_strerrorA(e.value());
+#else /* _WIN32 */
+ msg = gai_strerror(e.value());
+#endif /* _WIN32 */
+
+ return "[errinfo_getaddrinfo_error] = " + String(msg) + "\n";
}
struct errinfo_message_;
diff --git a/lib/base/socketevents.cpp b/lib/base/socketevents.cpp
index 045563ba2..8c420e709 100644
--- a/lib/base/socketevents.cpp
+++ b/lib/base/socketevents.cpp
@@ -207,6 +207,8 @@ void SocketEvents::Register(Object *lifesupportObject)
l_SocketIOSockets[m_FD] = desc;
+ m_Events = true;
+
/* There's no need to wake up the I/O thread here. */
}
@@ -220,6 +222,8 @@ void SocketEvents::Unregister(void)
l_SocketIOSockets.erase(m_FD);
m_FD = INVALID_SOCKET;
+
+ m_Events = false;
}
WakeUpThread(true);
@@ -244,6 +248,12 @@ void SocketEvents::ChangeEvents(int events)
WakeUpThread();
}
+bool SocketEvents::IsHandlingEvents(void) const
+{
+ boost::mutex::scoped_lock lock(l_SocketIOMutex);
+ return m_Events;
+}
+
void SocketEvents::OnEvent(int revents)
{
diff --git a/lib/base/socketevents.hpp b/lib/base/socketevents.hpp
index 2523d6543..82a565727 100644
--- a/lib/base/socketevents.hpp
+++ b/lib/base/socketevents.hpp
@@ -42,11 +42,14 @@ public:
void ChangeEvents(int events);
+ bool IsHandlingEvents(void) const;
+
protected:
SocketEvents(const Socket::Ptr& socket, Object *lifesupportObject);
private:
SOCKET m_FD;
+ bool m_Events;
static void InitializeThread(void);
static void ThreadProc(void);
diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp
index 2430ef29a..9bcb5c6eb 100644
--- a/lib/base/tlsstream.cpp
+++ b/lib/base/tlsstream.cpp
@@ -191,7 +191,7 @@ void TlsStream::OnEvent(int revents)
lock.unlock();
- while (m_RecvQ->IsDataAvailable())
+ while (m_RecvQ->IsDataAvailable() && IsHandlingEvents())
SignalDataAvailable();
if (m_Shutdown && !m_SendQ->IsDataAvailable())
@@ -318,6 +318,8 @@ void TlsStream::Close(void)
boost::mutex::scoped_lock lock(m_Mutex);
+ m_Eof = true;
+
if (!m_SSL)
return;
@@ -326,8 +328,6 @@ void TlsStream::Close(void)
m_Socket->Close();
m_Socket.reset();
-
- m_Eof = true;
}
bool TlsStream::IsEof(void) const
diff --git a/lib/base/tlsstream.hpp b/lib/base/tlsstream.hpp
index e0e90a57c..669e67c7b 100644
--- a/lib/base/tlsstream.hpp
+++ b/lib/base/tlsstream.hpp
@@ -48,7 +48,7 @@ class I2_BASE_API TlsStream : public Stream, private SocketEvents
public:
DECLARE_PTR_TYPEDEFS(TlsStream);
- TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const boost::shared_ptr& sslContext);
+ TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const boost::shared_ptr& sslContext = MakeSSLContext());
~TlsStream(void);
boost::shared_ptr GetClientCertificate(void) const;
diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp
index 37f3328ce..eccc343fe 100644
--- a/lib/base/tlsutility.cpp
+++ b/lib/base/tlsutility.cpp
@@ -88,30 +88,34 @@ boost::shared_ptr MakeSSLContext(const String& pubkey, const String& pr
SSL_CTX_set_mode(sslContext.get(), SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_session_id_context(sslContext.get(), (const unsigned char *)"Icinga 2", 8);
- if (!SSL_CTX_use_certificate_chain_file(sslContext.get(), pubkey.CStr())) {
- Log(LogCritical, "SSL")
- << "Error with public key file '" << pubkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- BOOST_THROW_EXCEPTION(openssl_error()
- << boost::errinfo_api_function("SSL_CTX_use_certificate_chain_file")
- << errinfo_openssl_error(ERR_peek_error())
- << boost::errinfo_file_name(pubkey));
+ if (!pubkey.IsEmpty()) {
+ if (!SSL_CTX_use_certificate_chain_file(sslContext.get(), pubkey.CStr())) {
+ Log(LogCritical, "SSL")
+ << "Error with public key file '" << pubkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+ BOOST_THROW_EXCEPTION(openssl_error()
+ << boost::errinfo_api_function("SSL_CTX_use_certificate_chain_file")
+ << errinfo_openssl_error(ERR_peek_error())
+ << boost::errinfo_file_name(pubkey));
+ }
}
- if (!SSL_CTX_use_PrivateKey_file(sslContext.get(), privkey.CStr(), SSL_FILETYPE_PEM)) {
- Log(LogCritical, "SSL")
- << "Error with private key file '" << privkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- BOOST_THROW_EXCEPTION(openssl_error()
- << boost::errinfo_api_function("SSL_CTX_use_PrivateKey_file")
- << errinfo_openssl_error(ERR_peek_error())
- << boost::errinfo_file_name(privkey));
- }
+ if (!privkey.IsEmpty()) {
+ if (!SSL_CTX_use_PrivateKey_file(sslContext.get(), privkey.CStr(), SSL_FILETYPE_PEM)) {
+ Log(LogCritical, "SSL")
+ << "Error with private key file '" << privkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+ BOOST_THROW_EXCEPTION(openssl_error()
+ << boost::errinfo_api_function("SSL_CTX_use_PrivateKey_file")
+ << errinfo_openssl_error(ERR_peek_error())
+ << boost::errinfo_file_name(privkey));
+ }
- if (!SSL_CTX_check_private_key(sslContext.get())) {
- Log(LogCritical, "SSL")
- << "Error checking private key '" << privkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
- BOOST_THROW_EXCEPTION(openssl_error()
- << boost::errinfo_api_function("SSL_CTX_check_private_key")
- << errinfo_openssl_error(ERR_peek_error()));
+ if (!SSL_CTX_check_private_key(sslContext.get())) {
+ Log(LogCritical, "SSL")
+ << "Error checking private key '" << privkey << "': " << ERR_peek_error() << ", \"" << ERR_error_string(ERR_peek_error(), errbuf) << "\"";
+ BOOST_THROW_EXCEPTION(openssl_error()
+ << boost::errinfo_api_function("SSL_CTX_check_private_key")
+ << errinfo_openssl_error(ERR_peek_error()));
+ }
}
if (!cakey.IsEmpty()) {
diff --git a/lib/base/tlsutility.hpp b/lib/base/tlsutility.hpp
index d994754c9..68129c3ec 100644
--- a/lib/base/tlsutility.hpp
+++ b/lib/base/tlsutility.hpp
@@ -38,7 +38,7 @@ namespace icinga
{
void I2_BASE_API InitializeOpenSSL(void);
-boost::shared_ptr I2_BASE_API MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey = String());
+boost::shared_ptr I2_BASE_API MakeSSLContext(const String& pubkey = String(), const String& privkey = String(), const String& cakey = String());
void I2_BASE_API AddCRLToSSLContext(const boost::shared_ptr& context, const String& crlPath);
String I2_BASE_API GetCertificateCN(const boost::shared_ptr& certificate);
boost::shared_ptr I2_BASE_API GetX509Certificate(const String& pemfile);
diff --git a/lib/base/win32.hpp b/lib/base/win32.hpp
index c121911f9..76d3efc41 100644
--- a/lib/base/win32.hpp
+++ b/lib/base/win32.hpp
@@ -21,10 +21,12 @@
#define WIN32_H
#define WIN32_LEAN_AND_MEAN
+#ifndef _WIN32_WINNT
#define _WIN32_WINNT _WIN32_WINNT_VISTA
+#endif /* _WIN32_WINNT */
#define NOMINMAX
-#include
#include
+#include
#include
#include
#include
diff --git a/lib/config/CMakeLists.txt b/lib/config/CMakeLists.txt
index f60b1b852..048250cf4 100644
--- a/lib/config/CMakeLists.txt
+++ b/lib/config/CMakeLists.txt
@@ -61,3 +61,10 @@ install(
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2
)
+
+if(APPLE)
+ install(
+ TARGETS config
+ LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR}/icinga-studio.app/Contents
+ )
+endif()
diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt
index 0d35f445c..a728a4aa6 100644
--- a/lib/remote/CMakeLists.txt
+++ b/lib/remote/CMakeLists.txt
@@ -27,7 +27,7 @@ set(remote_SOURCES
configfileshandler.cpp configmoduleshandler.cpp configmoduleutility.cpp configobjectutility.cpp
configstageshandler.cpp createobjecthandler.cpp deleteobjecthandler.cpp
endpoint.cpp endpoint.thpp filterutility.cpp
- httpchunkedencoding.cpp httpconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
+ httpchunkedencoding.cpp httpclientconnection.cpp httpserverconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
httputility.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
messageorigin.cpp modifyobjecthandler.cpp statusqueryhandler.cpp typequeryhandler.cpp
url.cpp zone.cpp zone.thpp
@@ -55,6 +55,13 @@ install(
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2
)
+if(APPLE)
+ install(
+ TARGETS remote
+ LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR}/icinga-studio.app/Contents
+ )
+endif()
+
#install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api\")")
install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api/log\")")
install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/icinga2/api/repository\")")
diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp
index 3c27a38c5..ed53f2478 100644
--- a/lib/remote/apilistener.cpp
+++ b/lib/remote/apilistener.cpp
@@ -386,7 +386,7 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri
} else {
Log(LogInformation, "ApiListener", "New HTTP client");
- HttpConnection::Ptr aclient = new HttpConnection(identity, verify_ok, tlsStream);
+ HttpServerConnection::Ptr aclient = new HttpServerConnection(identity, verify_ok, tlsStream);
aclient->Start();
AddHttpClient(aclient);
}
@@ -908,19 +908,19 @@ std::set ApiListener::GetAnonymousClients(void) const
return m_AnonymousClients;
}
-void ApiListener::AddHttpClient(const HttpConnection::Ptr& aclient)
+void ApiListener::AddHttpClient(const HttpServerConnection::Ptr& aclient)
{
ObjectLock olock(this);
m_HttpClients.insert(aclient);
}
-void ApiListener::RemoveHttpClient(const HttpConnection::Ptr& aclient)
+void ApiListener::RemoveHttpClient(const HttpServerConnection::Ptr& aclient)
{
ObjectLock olock(this);
m_HttpClients.erase(aclient);
}
-std::set ApiListener::GetHttpClients(void) const
+std::set ApiListener::GetHttpClients(void) const
{
ObjectLock olock(this);
return m_HttpClients;
diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp
index 03559a1e1..4990b4101 100644
--- a/lib/remote/apilistener.hpp
+++ b/lib/remote/apilistener.hpp
@@ -22,7 +22,7 @@
#include "remote/apilistener.thpp"
#include "remote/jsonrpcconnection.hpp"
-#include "remote/httpconnection.hpp"
+#include "remote/httpserverconnection.hpp"
#include "remote/endpoint.hpp"
#include "remote/messageorigin.hpp"
#include "base/configobject.hpp"
@@ -69,9 +69,9 @@ public:
void RemoveAnonymousClient(const JsonRpcConnection::Ptr& aclient);
std::set GetAnonymousClients(void) const;
- void AddHttpClient(const HttpConnection::Ptr& aclient);
- void RemoveHttpClient(const HttpConnection::Ptr& aclient);
- std::set GetHttpClients(void) const;
+ void AddHttpClient(const HttpServerConnection::Ptr& aclient);
+ void RemoveHttpClient(const HttpServerConnection::Ptr& aclient);
+ std::set GetHttpClients(void) const;
static Value ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
@@ -85,7 +85,7 @@ private:
boost::shared_ptr m_SSLContext;
std::set m_Servers;
std::set m_AnonymousClients;
- std::set m_HttpClients;
+ std::set m_HttpClients;
Timer::Ptr m_Timer;
void ApiTimerHandler(void);
diff --git a/lib/remote/httpchunkedencoding.cpp b/lib/remote/httpchunkedencoding.cpp
index 4d8aa7ca8..5fd3bc8fd 100644
--- a/lib/remote/httpchunkedencoding.cpp
+++ b/lib/remote/httpchunkedencoding.cpp
@@ -28,6 +28,7 @@ StreamReadStatus HttpChunkedEncoding::ReadChunkFromStream(const Stream::Ptr& str
if (context.LengthIndicator == -1) {
String line;
StreamReadStatus status = stream->ReadLine(&line, context.StreamContext, may_wait);
+ may_wait = false;
if (status != StatusNewItem)
return status;
@@ -36,35 +37,36 @@ StreamReadStatus HttpChunkedEncoding::ReadChunkFromStream(const Stream::Ptr& str
msgbuf << std::hex << line;
msgbuf >> context.LengthIndicator;
- return StatusNeedData;
- } else {
- StreamReadContext& scontext = context.StreamContext;
- if (scontext.Eof)
- return StatusEof;
-
- if (scontext.MustRead) {
- if (!scontext.FillFromStream(stream, may_wait)) {
- scontext.Eof = true;
- return StatusEof;
- }
-
- scontext.MustRead = false;
- }
-
- if (scontext.Size < (size_t)context.LengthIndicator) {
- scontext.MustRead = true;
- return StatusNeedData;
- }
-
- *data = new char[context.LengthIndicator];
- *size = context.LengthIndicator;
- memcpy(data, scontext.Buffer, context.LengthIndicator);
-
- scontext.DropData(context.LengthIndicator);
- context.LengthIndicator = -1;
-
- return StatusNewItem;
}
+
+ StreamReadContext& scontext = context.StreamContext;
+ if (scontext.Eof)
+ return StatusEof;
+
+ if (scontext.MustRead) {
+ if (!scontext.FillFromStream(stream, may_wait)) {
+ scontext.Eof = true;
+ return StatusEof;
+ }
+
+ scontext.MustRead = false;
+ }
+
+ size_t NewlineLength = context.LengthIndicator ? 2 : 0;
+
+ if (scontext.Size < (size_t)context.LengthIndicator + NewlineLength) {
+ scontext.MustRead = true;
+ return StatusNeedData;
+ }
+
+ *data = new char[context.LengthIndicator];
+ *size = context.LengthIndicator;
+ memcpy(*data, scontext.Buffer, context.LengthIndicator);
+
+ scontext.DropData(context.LengthIndicator + NewlineLength);
+ context.LengthIndicator = -1;
+
+ return StatusNewItem;
}
void HttpChunkedEncoding::WriteChunkToStream(const Stream::Ptr& stream, const char *data, size_t count)
diff --git a/lib/remote/httpclientconnection.cpp b/lib/remote/httpclientconnection.cpp
new file mode 100644
index 000000000..4837ed3ec
--- /dev/null
+++ b/lib/remote/httpclientconnection.cpp
@@ -0,0 +1,156 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "remote/httpclientconnection.hpp"
+#include "remote/base64.hpp"
+#include "base/configtype.hpp"
+#include "base/objectlock.hpp"
+#include "base/utility.hpp"
+#include "base/logger.hpp"
+#include "base/exception.hpp"
+#include "base/convert.hpp"
+#include "base/tcpsocket.hpp"
+#include "base/tlsstream.hpp"
+#include "base/networkstream.hpp"
+#include
+
+using namespace icinga;
+
+HttpClientConnection::HttpClientConnection(const String& host, const String& port, bool tls)
+ : m_Host(host), m_Port(port), m_Tls(tls)
+{ }
+
+void HttpClientConnection::Start(void)
+{
+ /* Nothing to do here atm. */
+}
+
+void HttpClientConnection::Reconnect(void)
+{
+ if (m_Stream)
+ m_Stream->Close();
+
+ m_Context.~StreamReadContext();
+ new (&m_Context) StreamReadContext();
+
+ TcpSocket::Ptr socket = new TcpSocket();
+ socket->Connect(m_Host, m_Port);
+
+ if (m_Tls)
+ m_Stream = new TlsStream(socket, m_Host, RoleClient);
+ else
+ ASSERT(!"Non-TLS HTTP connections not supported.");
+ //m_Stream = new NetworkStream(socket); -- does not currently work because the NetworkStream class doesn't support async I/O
+
+ m_Stream->RegisterDataHandler(boost::bind(&HttpClientConnection::DataAvailableHandler, this));
+ if (m_Stream->IsDataAvailable())
+ DataAvailableHandler();
+}
+
+Stream::Ptr HttpClientConnection::GetStream(void) const
+{
+ return m_Stream;
+}
+
+String HttpClientConnection::GetHost(void) const
+{
+ return m_Host;
+}
+
+String HttpClientConnection::GetPort(void) const
+{
+ return m_Port;
+}
+
+bool HttpClientConnection::GetTls(void) const
+{
+ return m_Tls;
+}
+
+void HttpClientConnection::Disconnect(void)
+{
+ Log(LogDebug, "HttpClientConnection", "Http client disconnected");
+
+ m_Stream->Shutdown();
+}
+
+bool HttpClientConnection::ProcessMessage(void)
+{
+ bool res;
+
+ if (m_Requests.empty())
+ return false;
+
+ const std::pair, HttpCompletionCallback>& currentRequest = *m_Requests.begin();
+ HttpRequest& request = *currentRequest.first.get();
+ const HttpCompletionCallback& callback = currentRequest.second;
+
+ if (!m_CurrentResponse)
+ m_CurrentResponse = boost::make_shared(m_Stream, request);
+
+ boost::shared_ptr currentResponse = m_CurrentResponse;
+ HttpResponse& response = *currentResponse.get();
+
+ try {
+ res = response.Parse(m_Context, false);
+ } catch (const std::exception& ex) {
+ callback(request, response);
+
+ m_Stream->Shutdown();
+ return false;
+ }
+
+ if (response.Complete) {
+ callback(request, response);
+
+ m_Requests.pop_front();
+ m_CurrentResponse.reset();
+
+ return true;
+ }
+
+ return res;
+}
+
+void HttpClientConnection::DataAvailableHandler(void)
+{
+ boost::mutex::scoped_lock lock(m_DataHandlerMutex);
+
+ try {
+ while (ProcessMessage())
+ ; /* empty loop body */
+ } catch (const std::exception& ex) {
+ Log(LogWarning, "HttpClientConnection")
+ << "Error while reading Http request: " << DiagnosticInformation(ex);
+
+ Disconnect();
+ }
+}
+
+boost::shared_ptr HttpClientConnection::NewRequest(void)
+{
+ Reconnect();
+ return boost::make_shared(m_Stream);
+}
+
+void HttpClientConnection::SubmitRequest(const boost::shared_ptr& request, const HttpCompletionCallback& callback)
+{
+ m_Requests.push_back(std::make_pair(request, callback));
+ request->Finish();
+}
diff --git a/lib/remote/httpclientconnection.hpp b/lib/remote/httpclientconnection.hpp
new file mode 100644
index 000000000..72fcfeef9
--- /dev/null
+++ b/lib/remote/httpclientconnection.hpp
@@ -0,0 +1,78 @@
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef HTTPCLIENTCONNECTION_H
+#define HTTPCLIENTCONNECTION_H
+
+#include "remote/httprequest.hpp"
+#include "remote/httpresponse.hpp"
+#include "base/stream.hpp"
+#include "base/timer.hpp"
+#include
+
+namespace icinga
+{
+
+/**
+ * An HTTP client connection.
+ *
+ * @ingroup remote
+ */
+class I2_REMOTE_API HttpClientConnection : public Object
+{
+public:
+ DECLARE_PTR_TYPEDEFS(HttpClientConnection);
+
+ HttpClientConnection(const String& host, const String& port, bool tls = true);
+
+ void Start(void);
+
+ Stream::Ptr GetStream(void) const;
+ String GetHost(void) const;
+ String GetPort(void) const;
+ bool GetTls(void) const;
+
+ void Disconnect(void);
+
+ boost::shared_ptr NewRequest(void);
+
+ typedef boost::function HttpCompletionCallback;
+ void SubmitRequest(const boost::shared_ptr& request, const HttpCompletionCallback& callback);
+
+private:
+ String m_Host;
+ String m_Port;
+ bool m_Tls;
+ Stream::Ptr m_Stream;
+ std::deque, HttpCompletionCallback> > m_Requests;
+ boost::shared_ptr m_CurrentResponse;
+ boost::mutex m_DataHandlerMutex;
+
+ StreamReadContext m_Context;
+
+ void Reconnect(void);
+ bool ProcessMessage(void);
+ void DataAvailableHandler(void);
+
+ void ProcessMessageAsync(HttpRequest& request);
+};
+
+}
+
+#endif /* HTTPCLIENTCONNECTION_H */
diff --git a/lib/remote/httprequest.cpp b/lib/remote/httprequest.cpp
index 951169e9d..28634563f 100644
--- a/lib/remote/httprequest.cpp
+++ b/lib/remote/httprequest.cpp
@@ -19,27 +19,29 @@
#include "remote/httprequest.hpp"
#include "base/logger.hpp"
+#include "base/application.hpp"
#include "base/convert.hpp"
#include
#include
#include
+#include
+#include
using namespace icinga;
-HttpRequest::HttpRequest(StreamReadContext& src)
+HttpRequest::HttpRequest(const Stream::Ptr& stream)
: Complete(false),
- ProtocolVersion(HttpVersion10),
+ ProtocolVersion(HttpVersion11),
Headers(new Dictionary()),
- m_Context(src),
- m_ChunkContext(m_Context),
+ m_Stream(stream),
m_State(HttpRequestStart)
{ }
-bool HttpRequest::Parse(const Stream::Ptr& stream, StreamReadContext& src, bool may_wait)
+bool HttpRequest::Parse(StreamReadContext& src, bool may_wait)
{
if (m_State != HttpRequestBody) {
String line;
- StreamReadStatus srs = stream->ReadLine(&line, src, may_wait);
+ StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
if (srs != StatusNewItem)
return false;
@@ -95,9 +97,12 @@ bool HttpRequest::Parse(const Stream::Ptr& stream, StreamReadContext& src, bool
}
} else if (m_State == HttpRequestBody) {
if (Headers->Get("transfer-encoding") == "chunked") {
+ if (!m_ChunkContext)
+ m_ChunkContext = boost::make_shared(src);
+
char *data;
size_t size;
- StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(stream, &data, &size, m_ChunkContext, false);
+ StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
if (srs != StatusNewItem)
return false;
@@ -114,27 +119,27 @@ bool HttpRequest::Parse(const Stream::Ptr& stream, StreamReadContext& src, bool
return true;
}
} else {
- if (m_Context.Eof)
+ if (src.Eof)
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
- if (m_Context.MustRead) {
- if (!m_Context.FillFromStream(stream, false)) {
- m_Context.Eof = true;
+ if (src.MustRead) {
+ if (!src.FillFromStream(m_Stream, false)) {
+ src.Eof = true;
BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
}
- m_Context.MustRead = false;
+ src.MustRead = false;
}
size_t length_indicator = Convert::ToLong(Headers->Get("content-length"));
- if (m_Context.Size < length_indicator) {
- m_Context.MustRead = true;
+ if (src.Size < length_indicator) {
+ src.MustRead = true;
return false;
}
- m_Body->Write(m_Context.Buffer, length_indicator);
- m_Context.DropData(length_indicator);
+ m_Body->Write(src.Buffer, length_indicator);
+ src.DropData(length_indicator);
Complete = true;
return true;
}
@@ -151,3 +156,77 @@ size_t HttpRequest::ReadBody(char *data, size_t count)
return m_Body->Read(data, count, true);
}
+void HttpRequest::AddHeader(const String& key, const String& value)
+{
+ ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders);
+ Headers->Set(key.ToLower(), value);
+}
+
+void HttpRequest::FinishHeaders(void)
+{
+ if (m_State == HttpRequestStart) {
+ String rqline = RequestMethod + " " + RequestUrl->Format() + " HTTP/1." + (ProtocolVersion == HttpVersion10 ? "0" : "1") + "\n";
+ m_Stream->Write(rqline.CStr(), rqline.GetLength());
+ m_State = HttpRequestHeaders;
+ }
+
+ if (m_State == HttpRequestHeaders) {
+ AddHeader("User-Agent", "Icinga/" + Application::GetVersion());
+
+ if (ProtocolVersion == HttpVersion11)
+ AddHeader("Transfer-Encoding", "chunked");
+
+ ObjectLock olock(Headers);
+ BOOST_FOREACH(const Dictionary::Pair& kv, Headers)
+ {
+ String header = kv.first + ": " + kv.second + "\n";
+ m_Stream->Write(header.CStr(), header.GetLength());
+ }
+
+ m_Stream->Write("\n", 1);
+
+ m_State = HttpRequestBody;
+ }
+}
+
+void HttpRequest::WriteBody(const char *data, size_t count)
+{
+ ASSERT(m_State == HttpRequestStart || m_State == HttpRequestHeaders || m_State == HttpRequestBody);
+
+ if (ProtocolVersion == HttpVersion10) {
+ if (!m_Body)
+ m_Body = new FIFO();
+
+ m_Body->Write(data, count);
+ } else {
+ FinishHeaders();
+
+ HttpChunkedEncoding::WriteChunkToStream(m_Stream, data, count);
+ }
+}
+
+void HttpRequest::Finish(void)
+{
+ ASSERT(m_State != HttpRequestEnd);
+
+ if (ProtocolVersion == HttpVersion10) {
+ if (m_Body)
+ AddHeader("Content-Length", Convert::ToString(m_Body->GetAvailableBytes()));
+
+ FinishHeaders();
+
+ while (m_Body && m_Body->IsDataAvailable()) {
+ char buffer[1024];
+ size_t rc = m_Body->Read(buffer, sizeof(buffer), true);
+ m_Stream->Write(buffer, rc);
+ }
+ } else {
+ if (m_State == HttpRequestStart || m_State == HttpRequestHeaders)
+ FinishHeaders();
+
+ WriteBody(NULL, 0);
+ m_Stream->Write("\r\n", 2);
+ }
+
+ m_State = HttpRequestEnd;
+}
diff --git a/lib/remote/httprequest.hpp b/lib/remote/httprequest.hpp
index b04dfd09c..9eb1a0b21 100644
--- a/lib/remote/httprequest.hpp
+++ b/lib/remote/httprequest.hpp
@@ -40,7 +40,8 @@ enum HttpRequestState
{
HttpRequestStart,
HttpRequestHeaders,
- HttpRequestBody
+ HttpRequestBody,
+ HttpRequestEnd
};
/**
@@ -59,17 +60,22 @@ public:
Dictionary::Ptr Headers;
- HttpRequest(StreamReadContext& ctx);
-
- bool Parse(const Stream::Ptr& stream, StreamReadContext& src, bool may_wait);
+ HttpRequest(const Stream::Ptr& stream);
+ bool Parse(StreamReadContext& src, bool may_wait);
size_t ReadBody(char *data, size_t count);
+ void AddHeader(const String& key, const String& value);
+ void WriteBody(const char *data, size_t count);
+ void Finish(void);
+
private:
- StreamReadContext& m_Context;
- ChunkReadContext m_ChunkContext;
+ Stream::Ptr m_Stream;
+ boost::shared_ptr m_ChunkContext;
HttpRequestState m_State;
FIFO::Ptr m_Body;
+
+ void FinishHeaders(void);
};
}
diff --git a/lib/remote/httpresponse.cpp b/lib/remote/httpresponse.cpp
index 631c13a35..fdd5d663d 100644
--- a/lib/remote/httpresponse.cpp
+++ b/lib/remote/httpresponse.cpp
@@ -20,13 +20,16 @@
#include "remote/httpresponse.hpp"
#include "remote/httpchunkedencoding.hpp"
#include "base/logger.hpp"
+#include
+#include
#include "base/application.hpp"
#include "base/convert.hpp"
+#include
using namespace icinga;
HttpResponse::HttpResponse(const Stream::Ptr& stream, const HttpRequest& request)
- : m_State(HttpResponseStart), m_Request(request), m_Stream(stream)
+ : Complete(false), m_State(HttpResponseStart), m_Request(request), m_Stream(stream)
{ }
void HttpResponse::SetStatus(int code, const String& message)
@@ -109,3 +112,123 @@ void HttpResponse::Finish(void)
if (m_Request.ProtocolVersion == HttpVersion10 || m_Request.Headers->Get("connection") == "close")
m_Stream->Shutdown();
}
+
+bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
+{
+ if (m_State != HttpResponseBody) {
+ String line;
+ StreamReadStatus srs = m_Stream->ReadLine(&line, src, may_wait);
+
+ if (srs != StatusNewItem)
+ return false;
+
+ if (m_State == HttpResponseStart) {
+ /* ignore trailing new-lines */
+ if (line == "")
+ return true;
+
+ std::vector tokens;
+ boost::algorithm::split(tokens, line, boost::is_any_of(" "));
+ Log(LogDebug, "HttpRequest")
+ << "line: " << line << ", tokens: " << tokens.size();
+ if (tokens.size() < 3)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
+
+ if (tokens[0] == "HTTP/1.0")
+ ProtocolVersion = HttpVersion10;
+ else if (tokens[0] == "HTTP/1.1") {
+ ProtocolVersion = HttpVersion11;
+ } else
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Unsupported HTTP version"));
+
+ StatusCode = Convert::ToLong(tokens[1]);
+ StatusMessage = tokens[2]; // TODO: Join tokens[2..end]
+
+ m_State = HttpResponseHeaders;
+ } else if (m_State == HttpResponseHeaders) {
+ if (!Headers)
+ Headers = new Dictionary();
+
+ if (line == "") {
+ m_State = HttpResponseBody;
+
+ /* we're done if the request doesn't contain a message body */
+ if (!Headers->Contains("content-length") && !Headers->Contains("transfer-encoding"))
+ Complete = true;
+ else
+ m_Body = new FIFO();
+
+ return true;
+
+ } else {
+ String::SizeType pos = line.FindFirstOf(":");
+ if (pos == String::NPos)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid HTTP request"));
+ String key = line.SubStr(0, pos).ToLower().Trim();
+
+ String value = line.SubStr(pos + 1).Trim();
+ Headers->Set(key, value);
+ }
+ } else {
+ VERIFY(!"Invalid HTTP request state.");
+ }
+ } else if (m_State == HttpResponseBody) {
+ if (Headers->Get("transfer-encoding") == "chunked") {
+ if (!m_ChunkContext)
+ m_ChunkContext = boost::make_shared(src);
+
+ char *data;
+ size_t size;
+ StreamReadStatus srs = HttpChunkedEncoding::ReadChunkFromStream(m_Stream, &data, &size, *m_ChunkContext.get(), may_wait);
+
+ if (srs != StatusNewItem)
+ return false;
+
+ Log(LogInformation, "HttpResponse")
+ << "Read " << size << " bytes";
+
+ m_Body->Write(data, size);
+
+ delete[] data;
+
+ if (size == 0) {
+ Complete = true;
+ return true;
+ }
+ } else {
+ if (src.Eof)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
+
+ if (src.MustRead) {
+ if (!src.FillFromStream(m_Stream, false)) {
+ src.Eof = true;
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Unexpected EOF in HTTP body"));
+ }
+
+ src.MustRead = false;
+ }
+
+ size_t length_indicator = Convert::ToLong(Headers->Get("content-length"));
+
+ if (src.Size < length_indicator) {
+ src.MustRead = true;
+ return false;
+ }
+
+ m_Body->Write(src.Buffer, length_indicator);
+ src.DropData(length_indicator);
+ Complete = true;
+ return true;
+ }
+ }
+
+ return true;
+}
+
+size_t HttpResponse::ReadBody(char *data, size_t count)
+{
+ if (!m_Body)
+ return 0;
+ else
+ return m_Body->Read(data, count, true);
+}
\ No newline at end of file
diff --git a/lib/remote/httpresponse.hpp b/lib/remote/httpresponse.hpp
index 6255ac134..94c11d3f0 100644
--- a/lib/remote/httpresponse.hpp
+++ b/lib/remote/httpresponse.hpp
@@ -43,8 +43,19 @@ enum HttpResponseState
struct I2_REMOTE_API HttpResponse
{
public:
+ bool Complete;
+
+ HttpVersion ProtocolVersion;
+ int StatusCode;
+ String StatusMessage;
+
+ Dictionary::Ptr Headers;
+
HttpResponse(const Stream::Ptr& stream, const HttpRequest& request);
+ bool Parse(StreamReadContext& src, bool may_wait);
+ size_t ReadBody(char *data, size_t count);
+
void SetStatus(int code, const String& message);
void AddHeader(const String& key, const String& value);
void WriteBody(const char *data, size_t count);
@@ -52,6 +63,7 @@ public:
private:
HttpResponseState m_State;
+ boost::shared_ptr m_ChunkContext;
const HttpRequest& m_Request;
Stream::Ptr m_Stream;
FIFO::Ptr m_Body;
diff --git a/lib/remote/httpconnection.cpp b/lib/remote/httpserverconnection.cpp
similarity index 70%
rename from lib/remote/httpconnection.cpp
rename to lib/remote/httpserverconnection.cpp
index 48c1ac663..e97d9c2c7 100644
--- a/lib/remote/httpconnection.cpp
+++ b/lib/remote/httpserverconnection.cpp
@@ -17,7 +17,7 @@
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
-#include "remote/httpconnection.hpp"
+#include "remote/httpserverconnection.hpp"
#include "remote/httphandler.hpp"
#include "remote/apilistener.hpp"
#include "remote/apifunction.hpp"
@@ -33,47 +33,46 @@
using namespace icinga;
-static boost::once_flag l_HttpConnectionOnceFlag = BOOST_ONCE_INIT;
-static Timer::Ptr l_HttpConnectionTimeoutTimer;
+static boost::once_flag l_HttpServerConnectionOnceFlag = BOOST_ONCE_INIT;
+static Timer::Ptr l_HttpServerConnectionTimeoutTimer;
-HttpConnection::HttpConnection(const String& identity, bool authenticated, const TlsStream::Ptr& stream)
- : m_Stream(stream), m_Seen(Utility::GetTime()),
- m_CurrentRequest(m_Context), m_PendingRequests(0)
+HttpServerConnection::HttpServerConnection(const String& identity, bool authenticated, const TlsStream::Ptr& stream)
+ : m_Stream(stream), m_CurrentRequest(stream), m_Seen(Utility::GetTime()), m_PendingRequests(0)
{
- boost::call_once(l_HttpConnectionOnceFlag, &HttpConnection::StaticInitialize);
+ boost::call_once(l_HttpServerConnectionOnceFlag, &HttpServerConnection::StaticInitialize);
if (authenticated)
m_ApiUser = ApiUser::GetByClientCN(identity);
}
-void HttpConnection::StaticInitialize(void)
+void HttpServerConnection::StaticInitialize(void)
{
- l_HttpConnectionTimeoutTimer = new Timer();
- l_HttpConnectionTimeoutTimer->OnTimerExpired.connect(boost::bind(&HttpConnection::TimeoutTimerHandler));
- l_HttpConnectionTimeoutTimer->SetInterval(15);
- l_HttpConnectionTimeoutTimer->Start();
+ l_HttpServerConnectionTimeoutTimer = new Timer();
+ l_HttpServerConnectionTimeoutTimer->OnTimerExpired.connect(boost::bind(&HttpServerConnection::TimeoutTimerHandler));
+ l_HttpServerConnectionTimeoutTimer->SetInterval(15);
+ l_HttpServerConnectionTimeoutTimer->Start();
}
-void HttpConnection::Start(void)
+void HttpServerConnection::Start(void)
{
- m_Stream->RegisterDataHandler(boost::bind(&HttpConnection::DataAvailableHandler, this));
+ m_Stream->RegisterDataHandler(boost::bind(&HttpServerConnection::DataAvailableHandler, this));
if (m_Stream->IsDataAvailable())
DataAvailableHandler();
}
-ApiUser::Ptr HttpConnection::GetApiUser(void) const
+ApiUser::Ptr HttpServerConnection::GetApiUser(void) const
{
return m_ApiUser;
}
-TlsStream::Ptr HttpConnection::GetStream(void) const
+TlsStream::Ptr HttpServerConnection::GetStream(void) const
{
return m_Stream;
}
-void HttpConnection::Disconnect(void)
+void HttpServerConnection::Disconnect(void)
{
- Log(LogDebug, "HttpConnection", "Http client disconnected");
+ Log(LogDebug, "HttpServerConnection", "Http client disconnected");
ApiListener::Ptr listener = ApiListener::GetInstance();
listener->RemoveHttpClient(this);
@@ -81,12 +80,12 @@ void HttpConnection::Disconnect(void)
m_Stream->Shutdown();
}
-bool HttpConnection::ProcessMessage(void)
+bool HttpServerConnection::ProcessMessage(void)
{
bool res;
try {
- res = m_CurrentRequest.Parse(m_Stream, m_Context, false);
+ res = m_CurrentRequest.Parse(m_Context, false);
} catch (const std::exception& ex) {
HttpResponse response(m_Stream, m_CurrentRequest);
response.SetStatus(400, "Bad request");
@@ -99,13 +98,13 @@ bool HttpConnection::ProcessMessage(void)
}
if (m_CurrentRequest.Complete) {
- m_RequestQueue.Enqueue(boost::bind(&HttpConnection::ProcessMessageAsync, HttpConnection::Ptr(this), m_CurrentRequest));
+ m_RequestQueue.Enqueue(boost::bind(&HttpServerConnection::ProcessMessageAsync, HttpServerConnection::Ptr(this), m_CurrentRequest));
m_Seen = Utility::GetTime();
m_PendingRequests++;
m_CurrentRequest.~HttpRequest();
- new (&m_CurrentRequest) HttpRequest(m_Context);
+ new (&m_CurrentRequest) HttpRequest(m_Stream);
return true;
}
@@ -113,9 +112,9 @@ bool HttpConnection::ProcessMessage(void)
return res;
}
-void HttpConnection::ProcessMessageAsync(HttpRequest& request)
+void HttpServerConnection::ProcessMessageAsync(HttpRequest& request)
{
- Log(LogInformation, "HttpConnection", "Processing Http message");
+ Log(LogInformation, "HttpServerConnection", "Processing Http message");
String auth_header = request.Headers->Get("authorization");
@@ -169,7 +168,7 @@ void HttpConnection::ProcessMessageAsync(HttpRequest& request)
m_PendingRequests--;
}
-void HttpConnection::DataAvailableHandler(void)
+void HttpServerConnection::DataAvailableHandler(void)
{
boost::mutex::scoped_lock lock(m_DataHandlerMutex);
@@ -177,27 +176,27 @@ void HttpConnection::DataAvailableHandler(void)
while (ProcessMessage())
; /* empty loop body */
} catch (const std::exception& ex) {
- Log(LogWarning, "HttpConnection")
+ Log(LogWarning, "HttpServerConnection")
<< "Error while reading Http request: " << DiagnosticInformation(ex);
Disconnect();
}
}
-void HttpConnection::CheckLiveness(void)
+void HttpServerConnection::CheckLiveness(void)
{
if (m_Seen < Utility::GetTime() - 10 && m_PendingRequests == 0) {
- Log(LogInformation, "HttpConnection")
+ Log(LogInformation, "HttpServerConnection")
<< "No messages for Http connection have been received in the last 10 seconds.";
Disconnect();
}
}
-void HttpConnection::TimeoutTimerHandler(void)
+void HttpServerConnection::TimeoutTimerHandler(void)
{
ApiListener::Ptr listener = ApiListener::GetInstance();
- BOOST_FOREACH(const HttpConnection::Ptr& client, listener->GetHttpClients()) {
+ BOOST_FOREACH(const HttpServerConnection::Ptr& client, listener->GetHttpClients()) {
client->CheckLiveness();
}
}
diff --git a/lib/remote/httpconnection.hpp b/lib/remote/httpserverconnection.hpp
similarity index 88%
rename from lib/remote/httpconnection.hpp
rename to lib/remote/httpserverconnection.hpp
index a1ca8cf7d..f684d5b6e 100644
--- a/lib/remote/httpconnection.hpp
+++ b/lib/remote/httpserverconnection.hpp
@@ -17,8 +17,8 @@
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
-#ifndef HTTPCONNECTION_H
-#define HTTPCONNECTION_H
+#ifndef HTTPSERVERCONNECTION_H
+#define HTTPSERVERCONNECTION_H
#include "remote/httprequest.hpp"
#include "remote/apiuser.hpp"
@@ -34,12 +34,12 @@ namespace icinga
*
* @ingroup remote
*/
-class I2_REMOTE_API HttpConnection : public Object
+class I2_REMOTE_API HttpServerConnection : public Object
{
public:
- DECLARE_PTR_TYPEDEFS(HttpConnection);
+ DECLARE_PTR_TYPEDEFS(HttpServerConnection);
- HttpConnection(const String& identity, bool authenticated, const TlsStream::Ptr& stream);
+ HttpServerConnection(const String& identity, bool authenticated, const TlsStream::Ptr& stream);
void Start(void);
@@ -72,4 +72,4 @@ private:
}
-#endif /* HTTPCONNECTION_H */
+#endif /* HTTPSERVERCONNECTION_H */
diff --git a/third-party/execvpe/CMakeLists.txt b/third-party/execvpe/CMakeLists.txt
index 4d5538006..47d07fe7c 100644
--- a/third-party/execvpe/CMakeLists.txt
+++ b/third-party/execvpe/CMakeLists.txt
@@ -29,3 +29,9 @@ install(
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2
)
+if(APPLE)
+ install(
+ TARGETS execvpe
+ LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR}/icinga-studio.app/Contents
+ )
+endif()
diff --git a/third-party/mmatch/CMakeLists.txt b/third-party/mmatch/CMakeLists.txt
index 8298a0424..0cd1cc914 100644
--- a/third-party/mmatch/CMakeLists.txt
+++ b/third-party/mmatch/CMakeLists.txt
@@ -28,3 +28,10 @@ install(
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2
)
+
+if(APPLE)
+ install(
+ TARGETS mmatch
+ LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR}/icinga-studio.app/Contents
+ )
+endif()
diff --git a/third-party/socketpair/CMakeLists.txt b/third-party/socketpair/CMakeLists.txt
index 5dd113890..9c149cb50 100644
--- a/third-party/socketpair/CMakeLists.txt
+++ b/third-party/socketpair/CMakeLists.txt
@@ -33,3 +33,9 @@ install(
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2
)
+if(APPLE)
+ install(
+ TARGETS socketpair
+ LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR}/icinga-studio.app/Contents
+ )
+endif()
diff --git a/third-party/yajl/src/CMakeLists.txt b/third-party/yajl/src/CMakeLists.txt
index 249b4783d..c5de3539d 100644
--- a/third-party/yajl/src/CMakeLists.txt
+++ b/third-party/yajl/src/CMakeLists.txt
@@ -60,3 +60,10 @@ INCLUDE_DIRECTORIES(${incDir}/..)
INSTALL(TARGETS yajl
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/icinga2)
+
+if(APPLE)
+ install(
+ TARGETS yajl
+ LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR}/icinga-studio.app/Contents
+ )
+endif()