Implement the Icinga Studio application

fixes #10042
This commit is contained in:
Gunnar Beutner 2015-08-29 01:16:16 +02:00
parent 9b73205481
commit c37a23ccba
46 changed files with 4134 additions and 125 deletions

View File

@ -16,6 +16,8 @@ addons:
- libmysqlclient-dev
- libedit-dev
- libyajl-dev
- libwxbase3.0-dev
- libwxgtk3.0-dev
before_script:
- mkdir build

View File

@ -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})

View File

@ -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}
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<false/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

View File

@ -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);
}

View File

@ -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 */

165
icinga-studio/api.cpp Normal file
View File

@ -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 <boost/foreach.hpp>
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<HttpRequest> 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<ApiType::Ptr> 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<String>& names, const std::vector<String>& 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<HttpRequest> 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<ApiObject::Ptr> 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);
}

111
icinga-studio/api.hpp Normal file
View File

@ -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 <vector>
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<ApiType> Type;
};
class ApiType : public Object
{
public:
DECLARE_PTR_TYPEDEFS(ApiType);
String Name;
String PluralName;
String BaseName;
ApiType::Ptr Base;
bool Abstract;
std::map<String, ApiField> Fields;
std::vector<String> PrototypeKeys;
};
struct ApiObjectReference
{
public:
String Name;
String Type;
};
struct ApiObject : public Object
{
public:
DECLARE_PTR_TYPEDEFS(ApiObject);
std::map<String, Value> Attrs;
std::vector<ApiObjectReference> 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<void(const std::vector<ApiType::Ptr>&)> TypesCompletionCallback;
void GetTypes(const TypesCompletionCallback& callback) const;
typedef boost::function<void(const std::vector<ApiObject::Ptr>&)> ObjectsCompletionCallback;
void GetObjects(const String& pluralType, const ObjectsCompletionCallback& callback,
const std::vector<String>& names = std::vector<String>(),
const std::vector<String>& attrs = std::vector<String>()) 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 */

View File

@ -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 <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
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<std::string> tokens;
boost::algorithm::split(tokens, authority, boost::is_any_of("@"));
if (tokens.size() > 1) {
std::vector<std::string> userinfo;
boost::algorithm::split(userinfo, tokens[0], boost::is_any_of(":"));
m_UserText->SetValue(userinfo[0]);
m_PasswordText->SetValue(userinfo[1]);
}
std::vector<std::string> 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());
}

View File

@ -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 */

242
icinga-studio/forms.cpp Normal file
View File

@ -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()
{
}

105
icinga-studio/forms.h Normal file
View File

@ -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 <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/menu.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/treectrl.h>
#include <wx/listctrl.h>
#include <wx/propgrid/propgrid.h>
#include <wx/propgrid/advprops.h>
#include <wx/sizer.h>
#include <wx/statusbr.h>
#include <wx/frame.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/statbox.h>
#include <wx/panel.h>
#include <wx/button.h>
#include <wx/dialog.h>
#include <wx/statbmp.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// 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__

View File

@ -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 <wx/wx.h>
#include <wx/app.h>
#include <wx/config.h>
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);

BIN
icinga-studio/icinga.icns Normal file

Binary file not shown.

BIN
icinga-studio/icinga.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

34
icinga-studio/icinga.rc Normal file
View File

@ -0,0 +1,34 @@
#include <windows.h>
#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

40
icinga-studio/icinga.xpm Normal file
View File

@ -0,0 +1,40 @@
/* XPM */
static const char *icinga_xpm[] = {
"32 32 5 1",
" c None",
". c #808080",
"+ c #000000",
"@ c #C0C0C0",
"# c #FFFFFF",
" ",
" .++++++++++++++++++++++++. ",
" .+++++++++++++++..+++++++++. ",
" .+++++++++++++++@##@+++++++++. ",
" +++++++++++++++.####.+++++++++ ",
" +++++++++++++++.####.+++++++++ ",
" ++++++++++++++++####++++++++++ ",
" ++++++++++++++++@@..++++++++++ ",
" +++++..+++++++++#.++++++++++++ ",
" ++++.##@+++++++@#+++++++++++++ ",
" ++++.###+++++++#.+++++++++.+++ ",
" ++++.###@++.@@@#+++++++++@##.+ ",
" +++++++.@#######.+++++++.###@+ ",
" +++++++++########++++..@####.+ ",
" ++++++++.########@@###@...@.++ ",
" ++++++++.#########@..+++++++++ ",
" ++++++++.########@++++++++++++ ",
" ++++++++.########.++++++++++++ ",
" +++++++++########+++++++++++++ ",
" +++++++++.######.+++++++++++++ ",
" +++++++++.#....#.+++++++++++++ ",
" ++++++++.#.++++.#.++++++++++++ ",
" ++++++++@@++++++##.+++++++++++ ",
" ++++@##@#+++++++##@+++++++++++ ",
" +++@####@+++++++..++++++++++++ ",
" +++######.++++++++++++++++++++ ",
" +++######.++++++++++++++++++++ ",
" +++######+++++++++++++++++++++ ",
" .++.####.++++++++++++++++++++. ",
" .+++..+++++++++++++++++++++. ",
" .++++++++++++++++++++++++. ",
" "};

258
icinga-studio/mainform.cpp Normal file
View File

@ -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 <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/foreach.hpp>
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<std::string> tokens;
boost::algorithm::split(tokens, authority, boost::is_any_of("@"));
if (tokens.size() > 1) {
std::vector<std::string> userinfo;
boost::algorithm::split(userinfo, tokens[0], boost::is_any_of(":"));
user = userinfo[0];
pass = userinfo[1];
}
std::vector<std::string> 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<ApiType::Ptr>& 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<String, wxTreeItemId> 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<String, wxTreeItemId>::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<String> attrs;
attrs.push_back(type->Name.ToLower() + ".__name");
m_ApiClient->GetObjects(type->PluralName, boost::bind(&MainForm::ObjectsCompletionHandler, this, _1, true),
std::vector<String>(), attrs);
}
void MainForm::ObjectsCompletionHandler(const std::vector<ApiObject::Ptr>& 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<String, Value>::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<String> 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<Array>()) {
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<Dictionary>()) {
wxStringProperty *prop = new wxStringProperty(name.GetData(), wxPG_LABEL, "<dictionary>");
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<ApiObject::Ptr>& 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<String, Value> kv_pair;
BOOST_FOREACH(const kv_pair& kv, object->Attrs) {
std::vector<String> 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();
}

View File

@ -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<String, ApiType::Ptr> m_Types;
void TypesCompletionHandler(const std::vector<ApiType::Ptr>& types, bool forward);
void ObjectsCompletionHandler(const std::vector<ApiObject::Ptr>& objects, bool forward);
void ObjectDetailsCompletionHandler(const std::vector<ApiObject::Ptr>& objects, bool forward);
wxPGProperty *ValueToProperty(const String& name, const Value& value);
};
}
#endif /* MAINFORM_H */

View File

@ -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)

View File

@ -147,7 +147,15 @@ typedef boost::error_info<struct errinfo_getaddrinfo_error_, int> 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_;

View File

@ -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)
{

View File

@ -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);

View File

@ -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

View File

@ -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<SSL_CTX>& sslContext);
TlsStream(const Socket::Ptr& socket, const String& hostname, ConnectionRole role, const boost::shared_ptr<SSL_CTX>& sslContext = MakeSSLContext());
~TlsStream(void);
boost::shared_ptr<X509> GetClientCertificate(void) const;

View File

@ -88,30 +88,34 @@ boost::shared_ptr<SSL_CTX> 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()) {

View File

@ -38,7 +38,7 @@ namespace icinga
{
void I2_BASE_API InitializeOpenSSL(void);
boost::shared_ptr<SSL_CTX> I2_BASE_API MakeSSLContext(const String& pubkey, const String& privkey, const String& cakey = String());
boost::shared_ptr<SSL_CTX> 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<SSL_CTX>& context, const String& crlPath);
String I2_BASE_API GetCertificateCN(const boost::shared_ptr<X509>& certificate);
boost::shared_ptr<X509> I2_BASE_API GetX509Certificate(const String& pemfile);

View File

@ -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 <windows.h>
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <imagehlp.h>
#include <shlwapi.h>

View File

@ -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()

View File

@ -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\")")

View File

@ -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<JsonRpcConnection::Ptr> 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<HttpConnection::Ptr> ApiListener::GetHttpClients(void) const
std::set<HttpServerConnection::Ptr> ApiListener::GetHttpClients(void) const
{
ObjectLock olock(this);
return m_HttpClients;

View File

@ -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<JsonRpcConnection::Ptr> GetAnonymousClients(void) const;
void AddHttpClient(const HttpConnection::Ptr& aclient);
void RemoveHttpClient(const HttpConnection::Ptr& aclient);
std::set<HttpConnection::Ptr> GetHttpClients(void) const;
void AddHttpClient(const HttpServerConnection::Ptr& aclient);
void RemoveHttpClient(const HttpServerConnection::Ptr& aclient);
std::set<HttpServerConnection::Ptr> GetHttpClients(void) const;
static Value ConfigUpdateHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
@ -85,7 +85,7 @@ private:
boost::shared_ptr<SSL_CTX> m_SSLContext;
std::set<TcpSocket::Ptr> m_Servers;
std::set<JsonRpcConnection::Ptr> m_AnonymousClients;
std::set<HttpConnection::Ptr> m_HttpClients;
std::set<HttpServerConnection::Ptr> m_HttpClients;
Timer::Ptr m_Timer;
void ApiTimerHandler(void);

View File

@ -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)

View File

@ -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 <boost/smart_ptr/make_shared.hpp>
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<boost::shared_ptr<HttpRequest>, HttpCompletionCallback>& currentRequest = *m_Requests.begin();
HttpRequest& request = *currentRequest.first.get();
const HttpCompletionCallback& callback = currentRequest.second;
if (!m_CurrentResponse)
m_CurrentResponse = boost::make_shared<HttpResponse>(m_Stream, request);
boost::shared_ptr<HttpResponse> 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<HttpRequest> HttpClientConnection::NewRequest(void)
{
Reconnect();
return boost::make_shared<HttpRequest>(m_Stream);
}
void HttpClientConnection::SubmitRequest(const boost::shared_ptr<HttpRequest>& request, const HttpCompletionCallback& callback)
{
m_Requests.push_back(std::make_pair(request, callback));
request->Finish();
}

View File

@ -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 <deque>
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<HttpRequest> NewRequest(void);
typedef boost::function<void(HttpRequest&, HttpResponse&)> HttpCompletionCallback;
void SubmitRequest(const boost::shared_ptr<HttpRequest>& request, const HttpCompletionCallback& callback);
private:
String m_Host;
String m_Port;
bool m_Tls;
Stream::Ptr m_Stream;
std::deque<std::pair<boost::shared_ptr<HttpRequest>, HttpCompletionCallback> > m_Requests;
boost::shared_ptr<HttpResponse> 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 */

View File

@ -19,27 +19,29 @@
#include "remote/httprequest.hpp"
#include "base/logger.hpp"
#include "base/application.hpp"
#include "base/convert.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/foreach.hpp>
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<ChunkReadContext>(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;
}

View File

@ -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<ChunkReadContext> m_ChunkContext;
HttpRequestState m_State;
FIFO::Ptr m_Body;
void FinishHeaders(void);
};
}

View File

@ -20,13 +20,16 @@
#include "remote/httpresponse.hpp"
#include "remote/httpchunkedencoding.hpp"
#include "base/logger.hpp"
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "base/application.hpp"
#include "base/convert.hpp"
#include <boost/smart_ptr/make_shared.hpp>
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<String> 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<ChunkReadContext>(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);
}

View File

@ -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<ChunkReadContext> m_ChunkContext;
const HttpRequest& m_Request;
Stream::Ptr m_Stream;
FIFO::Ptr m_Body;

View File

@ -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();
}
}

View File

@ -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 */

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()