Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Websocket support for Dashboard #24

Merged
merged 17 commits into from
Apr 8, 2024
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "libs/yaml-cpp"]
path = libs/yaml-cpp
url = https://github.com/jbeder/yaml-cpp.git
[submodule "libs/json"]
path = libs/json
url = https://github.com/nlohmann/json.git
1 change: 1 addition & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)

# Include OpenSSL.
find_package(OpenSSL REQUIRED)
include_directories(${OPENSSL_INCLUDE_DIR})

# Include libuv.
add_subdirectory(libs/libuv)
include_directories(libs/libuv/include)

# Include uvw (libuv C++ wrapper).
include_directories(libs/uvw/src)

# Include JSON.
add_subdirectory(libs/json)
include_directories(libs/json/include)

# Include YAML-CPP.
add_subdirectory(libs/yaml-cpp)
include_directories(libs/yaml-cpp/include)
Expand Down Expand Up @@ -63,7 +71,7 @@ add_executable(ardos ${ARDOS_SOURCES} ${ARDOS_HEADERS})
add_subdirectory(libs/dclass)
include_directories(libs/dclass)

target_link_libraries(ardos PRIVATE uv yaml-cpp amqpcpp prometheus-cpp::pull)
target_link_libraries(ardos PRIVATE uv OpenSSL::SSL nlohmann_json::nlohmann_json yaml-cpp amqpcpp prometheus-cpp::pull)

if (ARDOS_WANT_DB_SERVER)
target_link_libraries(ardos PRIVATE mongo::mongocxx_shared mongo::bsoncxx_shared)
Expand Down
19 changes: 19 additions & 0 deletions config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ want-db-state-server: true
# Do we want metrics collection on this instance?
want-metrics: true

# Do we want a web interface running on this instance?
want-web-panel: true

# UberDOG definitions.
# Some example ones follow:
uberdogs:
Expand All @@ -36,6 +39,22 @@ uberdogs:
- id: 4667
class: FriendsManager

# Web Panel configuration.
# Can be accessed via the Ardos Web panel for debugging.
web-panel:
name: Ardos # The cluster name to appear in the dashboard.
port: 7781 # Port the WS connection listens on.

# Auth options.
# Make sure to change these in PROD environments.
username: ardos
password: ardos

# SSL/TLS config for web panel.
# The below two options can be omitted to disable SSL/TLS.
certificate: cert.pem
private-key: key.pem

# Metrics (Prometheus) configuration.
# This should be configured as a target in your Prometheus config.
metrics:
Expand Down
1 change: 1 addition & 0 deletions libs/json
Submodule json added at 9cca28
98 changes: 94 additions & 4 deletions src/clientagent/client_agent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "../util/globals.h"
#include "../util/logger.h"
#include "../util/metrics.h"
#include "../web/web_panel.h"
#include "client_participant.h"

namespace Ardos {
Expand All @@ -27,7 +28,7 @@ ClientAgent::ClientAgent() {
_version = config["version"].as<std::string>();

// DC hash configuration.
// Can be manually overriden in CA config.
// Can be manually overridden in CA config.
_dcHash = g_dc_file->get_hash();
if (auto manualHash = config["manual-dc-hash"]) {
_dcHash = manualHash.as<uint32_t>();
Expand Down Expand Up @@ -123,8 +124,7 @@ ClientAgent::ClientAgent() {
srv.accept(*client);

// Create a new client for this connected participant.
// TODO: These should be tracked in a vector.
new ClientParticipant(this, client);
_participants.insert(new ClientParticipant(this, client));
});

// Initialize metrics.
Expand Down Expand Up @@ -259,10 +259,12 @@ void ClientAgent::ParticipantJoined() {
/**
* Called when a participant disconnects.
*/
void ClientAgent::ParticipantLeft() {
void ClientAgent::ParticipantLeft(ClientParticipant *client) {
if (_participantsGauge) {
_participantsGauge->Decrement();
}

_participants.erase(client);
}

/**
Expand Down Expand Up @@ -354,4 +356,92 @@ void ClientAgent::InitMetrics() {
_freeChannelsGauge->Set((double)(_channelsMax - _nextChannel));
}

void ClientAgent::HandleWeb(ws28::Client *client, nlohmann::json &data) {
if (data["msg"] == "init") {
// Build up an array of connected clients.
nlohmann::json clientInfo = nlohmann::json::array();
for (const auto &participant : _participants) {
clientInfo.push_back({
{"channel", std::to_string(participant->GetChannel())},
{"ip", participant->GetRemoteAddress().ip},
{"port", participant->GetRemoteAddress().port},
{"state", participant->GetAuthState()},
{"channels", participant->GetLocalChannels().size()},
{"postRemoves", participant->GetPostRemoves().size()},
});
}

WebPanel::Send(client, {
{"type", "ca:init"},
{"success", true},
{"listenIp", _host},
{"listenPort", _port},
#ifdef ARDOS_USE_LEGACY_CLIENT
{"legacy", true},
#else
{"legacy", false},
#endif
{"clients", clientInfo},
});
} else if (data["msg"] == "client") {
// We have to do this terribleness because JavaScript doesn't support
// uint64's.
auto channel = std::stoull(data["channel"].template get<std::string>());

// Try to find a matching client for the provided channel.
auto participant =
std::find_if(_participants.begin(), _participants.end(),
[&channel](ClientParticipant *participant) {
return participant->GetChannel() == channel;
});
if (participant == _participants.end()) {
WebPanel::Send(client, {
{"type", "ca:client"},
{"success", false},
});
return;
}

// Build an owned object array.
nlohmann::json ownedObjs = nlohmann::json::array();
for (const auto &obj : (*participant)->GetOwnedObjects()) {
ownedObjs.push_back({{"doId", obj.first},
{"clsName", obj.second.dcc->get_name()},
{"parent", obj.second.parent},
{"zone", obj.second.zone}});
}

// Build a session object array.
nlohmann::json sessionObjs = nlohmann::json::array();
for (const auto &doId : (*participant)->GetSessionObjects()) {
sessionObjs.push_back({{"doId", doId}});
}

// Build an active interests array.
nlohmann::json interests = nlohmann::json::array();
for (const auto &interest : (*participant)->GetInterests()) {
interests.push_back({{"id", interest.first},
{"parent", interest.second.parent},
{"zones", interest.second.zones}});
}

WebPanel::Send(
client,
{
{"type", "ca:client"},
{"success", true},
{"ip", (*participant)->GetRemoteAddress().ip},
{"port", (*participant)->GetRemoteAddress().port},
{"state", (*participant)->GetAuthState()},
{"channelHi", ((*participant)->GetChannel() >> 32) & 0xFFFFFFFF},
{"channelLo", (*participant)->GetChannel() & 0xFFFFFFFF},
{"channels", (*participant)->GetLocalChannels().size()},
{"postRemoves", (*participant)->GetPostRemoves().size()},
{"owned", ownedObjs},
{"session", sessionObjs},
{"interests", interests},
});
}
}

} // namespace Ardos
12 changes: 11 additions & 1 deletion src/clientagent/client_agent.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@

#include <memory>
#include <queue>
#include <unordered_set>

#include <dcClass.h>
#include <nlohmann/json.hpp>
#include <prometheus/counter.h>
#include <prometheus/gauge.h>
#include <prometheus/histogram.h>
#include <uvw.hpp>

#include "../net/ws/Client.h"

namespace Ardos {

struct Uberdog {
Expand All @@ -24,6 +28,8 @@ enum InterestsPermission {
INTERESTS_DISABLED,
};

class ClientParticipant;

class ClientAgent {
public:
ClientAgent();
Expand All @@ -43,11 +49,13 @@ class ClientAgent {
[[nodiscard]] unsigned long GetInterestTimeout() const;

void ParticipantJoined();
void ParticipantLeft();
void ParticipantLeft(ClientParticipant *client);
void RecordDatagram(const uint16_t &size);
void RecordInterestTimeout();
void RecordInterestTime(const double &seconds);

void HandleWeb(ws28::Client *client, nlohmann::json &data);

private:
void InitMetrics();

Expand All @@ -66,6 +74,8 @@ class ClientAgent {

std::unordered_map<uint32_t, Uberdog> _uberdogs;

std::unordered_set<ClientParticipant *> _participants;

uint64_t _nextChannel;
uint64_t _channelsMax;
std::queue<uint64_t> _freedChannels;
Expand Down
10 changes: 7 additions & 3 deletions src/clientagent/client_participant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ ClientParticipant::~ClientParticipant() {
// Call shutdown just in-case (most likely redundant.)
Shutdown();

_clientAgent->ParticipantLeft();
_clientAgent->ParticipantLeft(this);
}

/**
Expand Down Expand Up @@ -338,8 +338,9 @@ void ClientParticipant::HandleDatagram(const std::shared_ptr<Datagram> &dg) {
break;
}
case CLIENTAGENT_GET_NETWORK_ADDRESS: {
auto resp = std::make_shared<Datagram>(sender, _channel, CLIENTAGENT_GET_NETWORK_ADDRESS_RESP);
resp->AddUint32(dgi.GetUint32()); // Context.
auto resp = std::make_shared<Datagram>(
sender, _channel, CLIENTAGENT_GET_NETWORK_ADDRESS_RESP);
resp->AddUint32(dgi.GetUint32()); // Context.
resp->AddString(GetRemoteAddress().ip);
resp->AddUint16(GetRemoteAddress().port);
resp->AddString(GetLocalAddress().ip);
Expand Down Expand Up @@ -517,6 +518,9 @@ void ClientParticipant::HandleDatagram(const std::shared_ptr<Datagram> &dg) {
case STATESERVER_OBJECT_CHANGING_LOCATION: {
uint32_t doId = dgi.GetUint32();
if (TryQueuePending(doId, dgi.GetUnderlyingDatagram())) {
// The object that's changing location is currently generating inside an
// active InterestOperation. Queue this message to be handled after it
// generates.
return;
}

Expand Down
16 changes: 16 additions & 0 deletions src/clientagent/client_participant.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ class ClientParticipant final : public NetworkClient, public ChannelSubscriber {

friend class InterestOperation;

[[nodiscard]] uint64_t GetChannel() const { return _channel; }
[[nodiscard]] uint8_t GetAuthState() const { return _authState; }
[[nodiscard]] std::vector<std::shared_ptr<Datagram>> GetPostRemoves() const {
return _postRemoves;
}
[[nodiscard]] std::unordered_map<uint32_t, OwnedObject>
GetOwnedObjects() const {
return _ownedObjects;
}
[[nodiscard]] std::unordered_set<uint32_t> GetSessionObjects() const {
return _sessionObjects;
}
[[nodiscard]] std::unordered_map<uint16_t, Interest> GetInterests() const {
return _interests;
}

private:
void Shutdown() override;

Expand Down
15 changes: 13 additions & 2 deletions src/database/database_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "../util/globals.h"
#include "../util/logger.h"
#include "../util/metrics.h"
#include "../web/web_panel.h"
#include "database_utils.h"

// For document, finalize, et al.
Expand Down Expand Up @@ -528,8 +529,7 @@ void DatabaseServer::HandleGetField(DatagramIterator &dgi,
auto dbField = fields[field->get_name()];
if (dbField) {
// Pack the field into our object datagram.
DatabaseUtils::PackField(field, dbField.get_value(),
objectDg);
DatabaseUtils::PackField(field, dbField.get_value(), objectDg);
} else {
// Pack a default value.
objectDg.AddData(field->get_default_value());
Expand Down Expand Up @@ -1004,4 +1004,15 @@ void DatabaseServer::ReportFailed(const DatabaseServer::OperationType &type) {
}
}

void DatabaseServer::HandleWeb(ws28::Client *client, nlohmann::json &data) {
WebPanel::Send(client, {
{"type", "db"},
{"success", true},
{"host", _uri.to_string()},
{"channel", _channel},
{"minDoId", _minDoId},
{"maxDoId", _maxDoId},
});
}

} // namespace Ardos
4 changes: 4 additions & 0 deletions src/database/database_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@

#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <nlohmann/json.hpp>
#include <prometheus/counter.h>
#include <prometheus/histogram.h>
#include <uvw/timer.h>

#include "../messagedirector/channel_subscriber.h"
#include "../net/datagram_iterator.h"
#include "../net/message_types.h"
#include "../net/ws/Client.h"

namespace Ardos {

class DatabaseServer final : public ChannelSubscriber {
public:
DatabaseServer();

void HandleWeb(ws28::Client *client, nlohmann::json &data);

private:
void HandleDatagram(const std::shared_ptr<Datagram> &dg) override;

Expand Down
4 changes: 4 additions & 0 deletions src/messagedirector/channel_subscriber.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class ChannelSubscriber {
*/
void PublishDatagram(const std::shared_ptr<Datagram> &dg);

[[nodiscard]] std::vector<std::string> GetLocalChannels() const {
return _localChannels;
}

protected:
virtual void HandleDatagram(const std::shared_ptr<Datagram> &dg) = 0;

Expand Down
Loading
Loading