Implement permission manage web UI
This commit is contained in:
@@ -866,6 +866,38 @@ namespace confighttp {
|
|||||||
outputTree.put("status", true);
|
outputTree.put("status", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
updateClient(resp_https_t response, req_https_t request) {
|
||||||
|
if (!authenticate(response, request)) return;
|
||||||
|
|
||||||
|
print_req(request);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << request->content.rdbuf();
|
||||||
|
|
||||||
|
pt::ptree inputTree, outputTree;
|
||||||
|
|
||||||
|
auto g = util::fail_guard([&]() {
|
||||||
|
std::ostringstream data;
|
||||||
|
pt::write_json(data, outputTree);
|
||||||
|
response->write(data.str());
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
pt::read_json(ss, inputTree);
|
||||||
|
std::string uuid = inputTree.get<std::string>("uuid");
|
||||||
|
std::string name = inputTree.get<std::string>("name");
|
||||||
|
auto perm = (crypto::PERM)inputTree.get<uint32_t>("perm") & crypto::PERM::_all;
|
||||||
|
outputTree.put("status", nvhttp::update_device_info(uuid, name, perm));
|
||||||
|
}
|
||||||
|
catch (std::exception &e) {
|
||||||
|
BOOST_LOG(warning) << "Update Client: "sv << e.what();
|
||||||
|
outputTree.put("status", false);
|
||||||
|
outputTree.put("error", e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
unpair(resp_https_t response, req_https_t request) {
|
unpair(resp_https_t response, req_https_t request) {
|
||||||
if (!authenticate(response, request)) return;
|
if (!authenticate(response, request)) return;
|
||||||
@@ -897,6 +929,35 @@ namespace confighttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
disconnect(resp_https_t response, req_https_t request) {
|
||||||
|
if (!authenticate(response, request)) return;
|
||||||
|
|
||||||
|
print_req(request);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << request->content.rdbuf();
|
||||||
|
|
||||||
|
pt::ptree inputTree, outputTree;
|
||||||
|
|
||||||
|
auto g = util::fail_guard([&]() {
|
||||||
|
std::ostringstream data;
|
||||||
|
pt::write_json(data, outputTree);
|
||||||
|
response->write(data.str());
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
pt::read_json(ss, inputTree);
|
||||||
|
std::string uuid = inputTree.get<std::string>("uuid");
|
||||||
|
outputTree.put("status", nvhttp::find_and_stop_session(uuid, true));
|
||||||
|
}
|
||||||
|
catch (std::exception &e) {
|
||||||
|
BOOST_LOG(warning) << "Disconnect: "sv << e.what();
|
||||||
|
outputTree.put("status", false);
|
||||||
|
outputTree.put("error", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
listClients(resp_https_t response, req_https_t request) {
|
listClients(resp_https_t response, req_https_t request) {
|
||||||
if (!authenticate(response, request)) return;
|
if (!authenticate(response, request)) return;
|
||||||
@@ -969,7 +1030,9 @@ namespace confighttp {
|
|||||||
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
||||||
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
||||||
server.resource["^/api/clients/list$"]["GET"] = listClients;
|
server.resource["^/api/clients/list$"]["GET"] = listClients;
|
||||||
|
server.resource["^/api/clients/update$"]["POST"] = updateClient;
|
||||||
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
|
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
|
||||||
|
server.resource["^/api/clients/disconnect$"]["POST"] = disconnect;
|
||||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||||
server.resource["^/images/apollo.ico$"]["GET"] = getFaviconImage;
|
server.resource["^/images/apollo.ico$"]["GET"] = getFaviconImage;
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ namespace crypto {
|
|||||||
list = _action << 0, // Allow list apps
|
list = _action << 0, // Allow list apps
|
||||||
view = _action << 1, // Allow view streams
|
view = _action << 1, // Allow view streams
|
||||||
launch = _action << 2, // Allow launch apps
|
launch = _action << 2, // Allow launch apps
|
||||||
|
_allow_view = view | launch, // If no view permission is granted, disconnect the device upon permission update
|
||||||
_all_actions = list | view | launch,
|
_all_actions = list | view | launch,
|
||||||
|
|
||||||
_default = view | list, // Default permissions for new clients
|
_default = view | list, // Default permissions for new clients
|
||||||
|
|||||||
104
src/nvhttp.cpp
104
src/nvhttp.cpp
@@ -31,6 +31,7 @@
|
|||||||
#include "platform/common.h"
|
#include "platform/common.h"
|
||||||
#include "process.h"
|
#include "process.h"
|
||||||
#include "rtsp.h"
|
#include "rtsp.h"
|
||||||
|
#include "stream.h"
|
||||||
#include "system_tray.h"
|
#include "system_tray.h"
|
||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
#include "uuid.h"
|
#include "uuid.h"
|
||||||
@@ -331,7 +332,12 @@ namespace nvhttp {
|
|||||||
void
|
void
|
||||||
add_authorized_client(const p_named_cert_t& named_cert_p) {
|
add_authorized_client(const p_named_cert_t& named_cert_p) {
|
||||||
client_t &client = client_root;
|
client_t &client = client_root;
|
||||||
client.named_devices.emplace_back(named_cert_p);
|
client.named_devices.push_back(named_cert_p);
|
||||||
|
|
||||||
|
|
||||||
|
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
||||||
|
system_tray::update_tray_paired(named_cert_p->name);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!config::sunshine.flags[config::flag::FRESH_STATE]) {
|
if (!config::sunshine.flags[config::flag::FRESH_STATE]) {
|
||||||
save_state();
|
save_state();
|
||||||
@@ -657,10 +663,6 @@ namespace nvhttp {
|
|||||||
|
|
||||||
if (hash.to_string_view() == it->second) {
|
if (hash.to_string_view() == it->second) {
|
||||||
|
|
||||||
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
|
||||||
system_tray::update_tray_otp_pair(ptr->second.client.name);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!otp_device_name.empty()) {
|
if (!otp_device_name.empty()) {
|
||||||
ptr->second.client.name = std::move(otp_device_name);
|
ptr->second.client.name = std::move(otp_device_name);
|
||||||
}
|
}
|
||||||
@@ -823,7 +825,7 @@ namespace nvhttp {
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
tree.put("root.VirtualDisplayCapable", true);
|
tree.put("root.VirtualDisplayCapable", true);
|
||||||
if (!!(named_cert_p->perm & PERM::list)) {
|
if (!!(named_cert_p->perm & PERM::_all_actions)) {
|
||||||
tree.put("root.VirtualDisplayDriverReady", proc::vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK);
|
tree.put("root.VirtualDisplayDriverReady", proc::vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK);
|
||||||
} else {
|
} else {
|
||||||
tree.put("root.VirtualDisplayDriverReady", true);
|
tree.put("root.VirtualDisplayDriverReady", true);
|
||||||
@@ -902,11 +904,29 @@ namespace nvhttp {
|
|||||||
get_all_clients() {
|
get_all_clients() {
|
||||||
pt::ptree named_cert_nodes;
|
pt::ptree named_cert_nodes;
|
||||||
client_t &client = client_root;
|
client_t &client = client_root;
|
||||||
|
|
||||||
|
std::list<std::string> connected_uuids = rtsp_stream::get_all_session_uuids();
|
||||||
|
|
||||||
for (auto &named_cert_p : client.named_devices) {
|
for (auto &named_cert_p : client.named_devices) {
|
||||||
pt::ptree named_cert_node;
|
pt::ptree named_cert_node;
|
||||||
named_cert_node.put("name"s, named_cert_p->name);
|
named_cert_node.put("name"s, named_cert_p->name);
|
||||||
named_cert_node.put("uuid"s, named_cert_p->uuid);
|
named_cert_node.put("uuid"s, named_cert_p->uuid);
|
||||||
named_cert_node.put("perm", (uint32_t)named_cert_p->perm);
|
named_cert_node.put("perm", (uint32_t)named_cert_p->perm);
|
||||||
|
|
||||||
|
if (connected_uuids.empty()) {
|
||||||
|
named_cert_node.put("connected"s, false);
|
||||||
|
} else {
|
||||||
|
bool connected = false;
|
||||||
|
for (auto it = connected_uuids.begin(); it != connected_uuids.end(); ++it) {
|
||||||
|
if (*it == named_cert_p->uuid) {
|
||||||
|
connected = true;
|
||||||
|
connected_uuids.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
named_cert_node.put("connected"s, connected);
|
||||||
|
}
|
||||||
|
|
||||||
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -932,7 +952,7 @@ namespace nvhttp {
|
|||||||
apps.put("<xmlattr>.status_code", 200);
|
apps.put("<xmlattr>.status_code", 200);
|
||||||
|
|
||||||
auto named_cert_p = get_verified_cert(request);
|
auto named_cert_p = get_verified_cert(request);
|
||||||
if (!!(named_cert_p->perm & PERM::list)) {
|
if (!!(named_cert_p->perm & PERM::_all_actions)) {
|
||||||
for (auto &proc : proc::proc.get_apps()) {
|
for (auto &proc : proc::proc.get_apps()) {
|
||||||
pt::ptree app;
|
pt::ptree app;
|
||||||
|
|
||||||
@@ -949,7 +969,7 @@ namespace nvhttp {
|
|||||||
|
|
||||||
app.put("IsHdrSupported"s, 0);
|
app.put("IsHdrSupported"s, 0);
|
||||||
app.put("AppTitle"s, "Permission Denied");
|
app.put("AppTitle"s, "Permission Denied");
|
||||||
app.put("ID", "PERMISSION_DENIED");
|
app.put("ID", "1145141919810");
|
||||||
|
|
||||||
apps.push_back(std::make_pair("App", std::move(app)));
|
apps.push_back(std::make_pair("App", std::move(app)));
|
||||||
|
|
||||||
@@ -1080,7 +1100,7 @@ namespace nvhttp {
|
|||||||
});
|
});
|
||||||
|
|
||||||
auto named_cert_p = get_verified_cert(request);
|
auto named_cert_p = get_verified_cert(request);
|
||||||
if (!(named_cert_p->perm & PERM::view)) {
|
if (!(named_cert_p->perm & PERM::_allow_view)) {
|
||||||
BOOST_LOG(debug) << "Permission ViewApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
BOOST_LOG(debug) << "Permission ViewApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||||
|
|
||||||
tree.put("root.resume", 0);
|
tree.put("root.resume", 0);
|
||||||
@@ -1161,6 +1181,10 @@ namespace nvhttp {
|
|||||||
tree.put("root.resume", 1);
|
tree.put("root.resume", 1);
|
||||||
|
|
||||||
rtsp_stream::launch_session_raise(launch_session);
|
rtsp_stream::launch_session_raise(launch_session);
|
||||||
|
|
||||||
|
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
|
||||||
|
system_tray::update_tray_client_connected(named_cert_p->name);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1216,7 +1240,7 @@ namespace nvhttp {
|
|||||||
|
|
||||||
auto named_cert_p = get_verified_cert(request);
|
auto named_cert_p = get_verified_cert(request);
|
||||||
|
|
||||||
if (!(named_cert_p->perm & PERM::list)) {
|
if (!(named_cert_p->perm & PERM::_all_actions)) {
|
||||||
BOOST_LOG(debug) << "Permission Get AppAsset denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
BOOST_LOG(debug) << "Permission Get AppAsset denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||||
|
|
||||||
fg.disable();
|
fg.disable();
|
||||||
@@ -1383,6 +1407,54 @@ namespace nvhttp {
|
|||||||
load_state();
|
load_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stop_session(stream::session_t& session, bool graceful) {
|
||||||
|
if (graceful) {
|
||||||
|
stream::session::graceful_stop(session);
|
||||||
|
} else {
|
||||||
|
stream::session::stop(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool find_and_stop_session(const std::string& uuid, bool graceful) {
|
||||||
|
auto session = rtsp_stream::find_session(uuid);
|
||||||
|
if (session) {
|
||||||
|
stop_session(*session, graceful);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_session_info(stream::session_t& session, const std::string& name, const crypto::PERM newPerm) {
|
||||||
|
stream::session::update_device_info(session, name, newPerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool find_and_udpate_session_info(const std::string& uuid, const std::string& name, const crypto::PERM newPerm) {
|
||||||
|
auto session = rtsp_stream::find_session(uuid);
|
||||||
|
if (session) {
|
||||||
|
update_session_info(*session, name, newPerm);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool update_device_info(const std::string& uuid, const std::string& name, const crypto::PERM newPerm) {
|
||||||
|
find_and_udpate_session_info(uuid, name, newPerm);
|
||||||
|
|
||||||
|
client_t &client = client_root;
|
||||||
|
auto it = client.named_devices.begin();
|
||||||
|
for (; it != client.named_devices.end(); ++it) {
|
||||||
|
auto named_cert_p = *it;
|
||||||
|
if (named_cert_p->uuid == uuid) {
|
||||||
|
named_cert_p->name = name;
|
||||||
|
named_cert_p->perm = newPerm;
|
||||||
|
save_state();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
unpair_client(std::string uuid) {
|
unpair_client(std::string uuid) {
|
||||||
int removed = 0;
|
int removed = 0;
|
||||||
@@ -1399,6 +1471,18 @@ namespace nvhttp {
|
|||||||
|
|
||||||
save_state();
|
save_state();
|
||||||
load_state();
|
load_state();
|
||||||
|
|
||||||
|
if (removed) {
|
||||||
|
auto session = rtsp_stream::find_session(uuid);
|
||||||
|
if (session) {
|
||||||
|
stop_session(*session, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.named_devices.empty()) {
|
||||||
|
proc::proc.terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
} // namespace nvhttp
|
} // namespace nvhttp
|
||||||
|
|||||||
45
src/nvhttp.h
45
src/nvhttp.h
@@ -13,6 +13,8 @@
|
|||||||
#include <boost/property_tree/ptree.hpp>
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
|
||||||
// local includes
|
// local includes
|
||||||
|
#include "crypto.h"
|
||||||
|
#include "rtsp.h"
|
||||||
#include "thread_safe.h"
|
#include "thread_safe.h"
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
@@ -96,4 +98,47 @@ namespace nvhttp {
|
|||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
erase_all_clients();
|
erase_all_clients();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stops a session.
|
||||||
|
*
|
||||||
|
* @param session The session
|
||||||
|
* @param[in] graceful Whether to stop gracefully
|
||||||
|
*/
|
||||||
|
void stop_session(stream::session_t& session, bool graceful);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds and stop session.
|
||||||
|
*
|
||||||
|
* @param[in] uuid The uuid string
|
||||||
|
* @param[in] graceful Whether to stop gracefully
|
||||||
|
*/
|
||||||
|
bool find_and_stop_session(const std::string& uuid, bool graceful);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update device info associated to the session
|
||||||
|
*
|
||||||
|
* @param session The session
|
||||||
|
* @param[in] name New name
|
||||||
|
* @param[in] newPerm New permission
|
||||||
|
*/
|
||||||
|
void update_session_info(stream::session_t& session, const std::string& name, const crypto::PERM newPerm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds and udpate session information.
|
||||||
|
*
|
||||||
|
* @param[in] uuid The uuid string
|
||||||
|
* @param[in] name New name
|
||||||
|
* @param[in] newPerm New permission
|
||||||
|
*/
|
||||||
|
bool find_and_udpate_session_info(const std::string& uuid, const std::string& name, const crypto::PERM newPerm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update device info
|
||||||
|
*
|
||||||
|
* @param[in] uuid The uuid string
|
||||||
|
* @param[in] name New name
|
||||||
|
* @param[in] newPerm New permission
|
||||||
|
*/
|
||||||
|
bool update_device_info(const std::string& uuid, const std::string& name, const crypto::PERM newPerm);
|
||||||
} // namespace nvhttp
|
} // namespace nvhttp
|
||||||
|
|||||||
35
src/rtsp.cpp
35
src/rtsp.cpp
@@ -618,6 +618,31 @@ namespace rtsp_stream {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<stream::session_t>
|
||||||
|
find_session(const std::string& uuid) {
|
||||||
|
auto lg = _session_slots.lock();
|
||||||
|
|
||||||
|
for (auto &slot : *_session_slots) {
|
||||||
|
if (slot && stream::session::uuid_match(*slot, uuid)) {
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<std::string>
|
||||||
|
get_all_session_uuids() {
|
||||||
|
std::list<std::string> uuids;
|
||||||
|
auto lg = _session_slots.lock();
|
||||||
|
for (auto &slot : *_session_slots) {
|
||||||
|
if (slot) {
|
||||||
|
uuids.push_back(stream::session::uuid(*slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuids;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string_view, cmd_func_t> _map_cmd_cb;
|
std::unordered_map<std::string_view, cmd_func_t> _map_cmd_cb;
|
||||||
|
|
||||||
@@ -652,6 +677,16 @@ namespace rtsp_stream {
|
|||||||
return server.session_count();
|
return server.session_count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<stream::session_t>
|
||||||
|
find_session(const std::string& uuid) {
|
||||||
|
return server.find_session(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<std::string>
|
||||||
|
get_all_session_uuids() {
|
||||||
|
return server.get_all_session_uuids();
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
send(tcp::socket &sock, const std::string_view &sv) {
|
send(tcp::socket &sock, const std::string_view &sv) {
|
||||||
std::size_t bytes_send = 0;
|
std::size_t bytes_send = 0;
|
||||||
|
|||||||
12
src/rtsp.h
12
src/rtsp.h
@@ -5,10 +5,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "crypto.h"
|
#include "crypto.h"
|
||||||
#include "thread_safe.h"
|
#include "thread_safe.h"
|
||||||
|
|
||||||
|
// Resolve circular dependencies
|
||||||
|
namespace stream {
|
||||||
|
struct session_t;
|
||||||
|
}
|
||||||
|
|
||||||
namespace rtsp_stream {
|
namespace rtsp_stream {
|
||||||
constexpr auto RTSP_SETUP_PORT = 21;
|
constexpr auto RTSP_SETUP_PORT = 21;
|
||||||
|
|
||||||
@@ -60,6 +66,12 @@ namespace rtsp_stream {
|
|||||||
int
|
int
|
||||||
session_count();
|
session_count();
|
||||||
|
|
||||||
|
std::shared_ptr<stream::session_t>
|
||||||
|
find_session(const std::string& uuid);
|
||||||
|
|
||||||
|
std::list<std::string>
|
||||||
|
get_all_session_uuids();
|
||||||
|
|
||||||
void
|
void
|
||||||
rtpThread();
|
rtpThread();
|
||||||
|
|
||||||
|
|||||||
@@ -1969,6 +1969,40 @@ namespace stream {
|
|||||||
return session.state.load(std::memory_order_relaxed);
|
return session.state.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
send(session_t& session, const std::string_view &payload) {
|
||||||
|
return session.broadcast_ref->control_server.send(payload, session.control.peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
uuid(const session_t& session) {
|
||||||
|
return session.device_uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
uuid_match(const session_t &session, const std::string& uuid) {
|
||||||
|
return session.device_uuid == uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
update_device_info(session_t& session, const std::string& name, const crypto::PERM& newPerm) {
|
||||||
|
session.permission = newPerm;
|
||||||
|
if (!(newPerm & crypto::PERM::_allow_view)) {
|
||||||
|
BOOST_LOG(debug) << "Session: View permission revoked for [" << session.device_name << "], disconnecting...";
|
||||||
|
graceful_stop(session);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(debug) << "Session: Permission updated for [" << session.device_name << "]";
|
||||||
|
|
||||||
|
if (session.device_name != name) {
|
||||||
|
BOOST_LOG(debug) << "Session: Device name changed from [" << session.device_name << "] to [" << name << "]";
|
||||||
|
session.device_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
stop(session_t &session) {
|
stop(session_t &session) {
|
||||||
while_starting_do_nothing(session.state);
|
while_starting_do_nothing(session.state);
|
||||||
@@ -1981,6 +2015,40 @@ namespace stream {
|
|||||||
session.shutdown_event->raise(true);
|
session.shutdown_event->raise(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
graceful_stop(session_t& session) {
|
||||||
|
while_starting_do_nothing(session.state);
|
||||||
|
auto expected = state_e::RUNNING;
|
||||||
|
auto already_stopping = !session.state.compare_exchange_strong(expected, state_e::STOPPING);
|
||||||
|
if (already_stopping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reason: graceful termination
|
||||||
|
std::uint32_t reason = 0x80030023;
|
||||||
|
|
||||||
|
control_terminate_t plaintext;
|
||||||
|
plaintext.header.type = packetTypes[IDX_TERMINATION];
|
||||||
|
plaintext.header.payloadLength = sizeof(plaintext.ec);
|
||||||
|
plaintext.ec = util::endian::big<uint32_t>(reason);
|
||||||
|
|
||||||
|
// We may not have gotten far enough to have an ENet connection yet
|
||||||
|
if (session.control.peer) {
|
||||||
|
std::array<std::uint8_t,
|
||||||
|
sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
|
||||||
|
encrypted_payload;
|
||||||
|
auto payload = stream::encode_control(&session, util::view(plaintext), encrypted_payload);
|
||||||
|
|
||||||
|
if (send(session, payload)) {
|
||||||
|
TUPLE_2D(port, addr, platf::from_sockaddr_ex((sockaddr *) &session.control.peer->address.address));
|
||||||
|
BOOST_LOG(warning) << "Couldn't send termination code to ["sv << addr << ':' << port << ']';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session.shutdown_event->raise(true);
|
||||||
|
session.controlEnd.raise(true);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
join(session_t &session) {
|
join(session_t &session) {
|
||||||
// Current Nvidia drivers have a bug where NVENC can deadlock the encoder thread with hardware-accelerated
|
// Current Nvidia drivers have a bug where NVENC can deadlock the encoder thread with hardware-accelerated
|
||||||
|
|||||||
10
src/stream.h
10
src/stream.h
@@ -43,13 +43,23 @@ namespace stream {
|
|||||||
|
|
||||||
std::shared_ptr<session_t>
|
std::shared_ptr<session_t>
|
||||||
alloc(config_t &config, rtsp_stream::launch_session_t &launch_session);
|
alloc(config_t &config, rtsp_stream::launch_session_t &launch_session);
|
||||||
|
std::string
|
||||||
|
uuid(const session_t& session);
|
||||||
|
bool
|
||||||
|
uuid_match(const session_t& session, const std::string& uuid);
|
||||||
|
bool
|
||||||
|
update_device_info(session_t& session, const std::string& name, const crypto::PERM& newPerm);
|
||||||
int
|
int
|
||||||
start(session_t &session, const std::string &addr_string);
|
start(session_t &session, const std::string &addr_string);
|
||||||
void
|
void
|
||||||
stop(session_t &session);
|
stop(session_t &session);
|
||||||
void
|
void
|
||||||
|
graceful_stop(session_t& session);
|
||||||
|
void
|
||||||
join(session_t &session);
|
join(session_t &session);
|
||||||
state_e
|
state_e
|
||||||
state(session_t &session);
|
state(session_t &session);
|
||||||
|
inline bool
|
||||||
|
send(session_t& session);
|
||||||
} // namespace session
|
} // namespace session
|
||||||
} // namespace stream
|
} // namespace stream
|
||||||
|
|||||||
@@ -268,8 +268,8 @@ namespace system_tray {
|
|||||||
snprintf(msg, std::size(msg), "Streaming started for %s", app_name.c_str());
|
snprintf(msg, std::size(msg), "Streaming started for %s", app_name.c_str());
|
||||||
snprintf(force_close_msg, std::size(force_close_msg), "Force close [%s]", app_name.c_str());
|
snprintf(force_close_msg, std::size(force_close_msg), "Force close [%s]", app_name.c_str());
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
strcpy(msg, convertUtf8ToCurrentCodepage(msg).c_str());
|
strncpy(msg, convertUtf8ToCurrentCodepage(msg).c_str(), std::size(msg) - 1);
|
||||||
strcpy(force_close_msg, convertUtf8ToCurrentCodepage(force_close_msg).c_str());
|
strncpy(force_close_msg, convertUtf8ToCurrentCodepage(force_close_msg).c_str(), std::size(force_close_msg) - 1);
|
||||||
#endif
|
#endif
|
||||||
tray.notification_text = msg;
|
tray.notification_text = msg;
|
||||||
tray.notification_icon = TRAY_ICON_PLAYING;
|
tray.notification_icon = TRAY_ICON_PLAYING;
|
||||||
@@ -293,7 +293,7 @@ namespace system_tray {
|
|||||||
char msg[256];
|
char msg[256];
|
||||||
snprintf(msg, std::size(msg), "Streaming paused for %s", app_name.c_str());
|
snprintf(msg, std::size(msg), "Streaming paused for %s", app_name.c_str());
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
strcpy(msg, convertUtf8ToCurrentCodepage(msg).c_str());
|
strncpy(msg, convertUtf8ToCurrentCodepage(msg).c_str(), std::size(msg) - 1);
|
||||||
#endif
|
#endif
|
||||||
tray.icon = TRAY_ICON_PAUSING;
|
tray.icon = TRAY_ICON_PAUSING;
|
||||||
tray.notification_title = "Stream Paused";
|
tray.notification_title = "Stream Paused";
|
||||||
@@ -318,7 +318,7 @@ namespace system_tray {
|
|||||||
char msg[256];
|
char msg[256];
|
||||||
snprintf(msg, std::size(msg), "Streaming stopped for %s", app_name.c_str());
|
snprintf(msg, std::size(msg), "Streaming stopped for %s", app_name.c_str());
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
strcpy(msg, convertUtf8ToCurrentCodepage(msg).c_str());
|
strncpy(msg, convertUtf8ToCurrentCodepage(msg).c_str(), std::size(msg) - 1);
|
||||||
#endif
|
#endif
|
||||||
tray.icon = TRAY_ICON;
|
tray.icon = TRAY_ICON;
|
||||||
tray.notification_icon = TRAY_ICON;
|
tray.notification_icon = TRAY_ICON;
|
||||||
@@ -344,7 +344,7 @@ namespace system_tray {
|
|||||||
char msg[256];
|
char msg[256];
|
||||||
snprintf(msg, std::size(msg), "Application %s exited too fast with code %d. Click here to terminate the stream.", app_name.c_str(), exit_code);
|
snprintf(msg, std::size(msg), "Application %s exited too fast with code %d. Click here to terminate the stream.", app_name.c_str(), exit_code);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
strcpy(msg, convertUtf8ToCurrentCodepage(msg).c_str());
|
strncpy(msg, convertUtf8ToCurrentCodepage(msg).c_str(), std::size(msg) - 1);
|
||||||
#endif
|
#endif
|
||||||
tray.icon = TRAY_ICON;
|
tray.icon = TRAY_ICON;
|
||||||
tray.notification_icon = TRAY_ICON;
|
tray.notification_icon = TRAY_ICON;
|
||||||
@@ -382,7 +382,30 @@ namespace system_tray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
update_tray_otp_pair(std::string device_name) {
|
update_tray_paired(std::string device_name) {
|
||||||
|
if (!tray_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tray.notification_title = NULL;
|
||||||
|
tray.notification_text = NULL;
|
||||||
|
tray.notification_cb = NULL;
|
||||||
|
tray.notification_icon = NULL;
|
||||||
|
tray_update(&tray);
|
||||||
|
char msg[256];
|
||||||
|
snprintf(msg, std::size(msg), "Device %s paired Succesfully. Please make sure you have access to the device.", device_name.c_str());
|
||||||
|
#ifdef _WIN32
|
||||||
|
strncpy(msg, convertUtf8ToCurrentCodepage(msg).c_str(), std::size(msg) - 1 - 1);
|
||||||
|
#endif
|
||||||
|
tray.notification_title = "Device Paired Succesfully";
|
||||||
|
tray.notification_text = msg;
|
||||||
|
tray.notification_icon = TRAY_ICON;
|
||||||
|
tray.tooltip = PROJECT_NAME;
|
||||||
|
tray_update(&tray);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
update_tray_client_connected(std::string client_name) {
|
||||||
if (!tray_initialized) {
|
if (!tray_initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -394,14 +417,13 @@ namespace system_tray {
|
|||||||
tray.icon = TRAY_ICON;
|
tray.icon = TRAY_ICON;
|
||||||
tray_update(&tray);
|
tray_update(&tray);
|
||||||
char msg[256];
|
char msg[256];
|
||||||
snprintf(msg, std::size(msg), "OTP Pairing started for device \"%s\". Please make sure you have access to the device initiating the pairing request.", device_name.c_str());
|
snprintf(msg, std::size(msg), "%s has connected to the session.", client_name.c_str());
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
strcpy(msg, convertUtf8ToCurrentCodepage(msg).c_str());
|
strncpy(msg, convertUtf8ToCurrentCodepage(msg).c_str(), std::size(msg) - 1);
|
||||||
#endif
|
#endif
|
||||||
tray.icon = TRAY_ICON;
|
tray.notification_title = "Client Connected";
|
||||||
tray.notification_title = "Incoming OTP Pairing Request";
|
|
||||||
tray.notification_text = msg;
|
tray.notification_text = msg;
|
||||||
tray.notification_icon = TRAY_ICON_LOCKED;
|
tray.notification_icon = TRAY_ICON;
|
||||||
tray.tooltip = PROJECT_NAME;
|
tray.tooltip = PROJECT_NAME;
|
||||||
tray_update(&tray);
|
tray_update(&tray);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,5 +86,8 @@ namespace system_tray {
|
|||||||
update_tray_require_pin();
|
update_tray_require_pin();
|
||||||
|
|
||||||
void
|
void
|
||||||
update_tray_otp_pair(std::string device_name);
|
update_tray_paired(std::string device_name);
|
||||||
|
|
||||||
|
void
|
||||||
|
update_tray_client_connected(std::string client_name);
|
||||||
} // namespace system_tray
|
} // namespace system_tray
|
||||||
|
|||||||
@@ -57,6 +57,71 @@
|
|||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
<b>{{ $t('_common.warning') }}</b> {{ $t('pin.warning_msg') }}
|
<b>{{ $t('_common.warning') }}</b> {{ $t('pin.warning_msg') }}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Unpair Clients -->
|
||||||
|
<div class="card my-4 align-self-stretch">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="p-2">
|
||||||
|
<div class="d-flex justify-content-end align-items-center">
|
||||||
|
<h2 id="unpair" class="me-auto">{{ $t('pin.device_management') }}</h2>
|
||||||
|
<button class="btn btn-danger" :disabled="unpairAllPressed" @click="unpairAll">
|
||||||
|
{{ $t('pin.unpair_all') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<p class="mb-0">{{ $t('pin.device_management_desc') }}</p>
|
||||||
|
<div id="apply-alert" class="alert alert-success d-flex align-items-center mt-3" :style="{ 'display': (showApplyMessage ? 'flex !important': 'none !important') }">
|
||||||
|
<div class="me-2"><b>{{ $t('_common.success') }}</b> {{ $t('pin.unpair_single_success') }}</div>
|
||||||
|
<button class="btn btn-success ms-auto apply" @click="clickedApplyBanner">{{ $t('_common.dismiss') }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-success mt-3" v-if="unpairAllStatus === true">
|
||||||
|
{{ $t('pin.unpair_all_success') }}
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger mt-3" v-if="unpairAllStatus === false">
|
||||||
|
{{ $t('pin.unpair_all_error') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul id="client-list" class="list-group list-group-flush list-group-item-light" v-if="clients && clients.length > 0">
|
||||||
|
<template v-for="client in clients" class="list-group-item d-flex align-items-center">
|
||||||
|
<div v-if="client.editing" class="list-group-item d-flex align-items-stretch flex-column">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="p-2 flex-grow-1 d-flex align-items-center">
|
||||||
|
<span class="badge" :class="client.editPerm >= 0x04000000 ? 'bg-danger' : 'bg-primary'">
|
||||||
|
[ {{permToStr(client.editPerm)}} ]
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<input v-model="client.editName" @keyup.enter="saveClient(client)" class="form-control flex-grow-1" type="text" :placeholder="$t('pin.device_name')">
|
||||||
|
</div>
|
||||||
|
<div class="me-2 btn btn-success" @click="saveClient(client)"><i class="fas fa-check"></i></div>
|
||||||
|
<div class="me-2 btn btn-secondary" @click="cancelEdit(client)"><i class="fas fa-times"></i></div>
|
||||||
|
</div>
|
||||||
|
<div class="align-items-top d-flex flex-row justify-content-center">
|
||||||
|
<div v-for="group in permissionGroups" class="d-flex flex-column mx-2">
|
||||||
|
<div class="mx-2">{{ group.name }}:</div>
|
||||||
|
<button v-for="perm in group.permissions" class="my-1 btn btn-sm" :disabled="isSupressed(client.editPerm, perm.name, perm.supressed_by)" :class="(isSupressed(client.editPerm, perm.name, perm.supressed_by) || checkPermission(client.editPerm, perm.name)) ? 'btn-success' : 'btn-outline-secondary'" @click="togglePermission(client, perm.name)">
|
||||||
|
{{ $t(`permissions.${perm.name}`) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="list-group-item d-flex align-items-center">
|
||||||
|
<div class="p-2 flex-grow-1 d-flex align-items-center">
|
||||||
|
<span class="badge" :class="client.perm >= 0x04000000 ? 'bg-danger' : 'bg-primary'">
|
||||||
|
[ {{permToStr(client.perm)}} ]
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="me-2">{{client.name != "" ? client.name : $t('pin.unpair_single_unknown')}}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="client.connected" class="me-2 btn btn-warning" @click="disconnectClient(client.uuid)"><i class="fas fa-link-slash"></i></div>
|
||||||
|
<div class="me-2 btn btn-primary" @click="editClient(client)"><i class="fas fa-edit"></i></div>
|
||||||
|
<div class="me-2 btn btn-danger" @click="unpairSingle(client.uuid)"><i class="fas fa-trash"></i></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
<ul v-else class="list-group list-group-flush list-group-item-light">
|
||||||
|
<div class="list-group-item p-3 text-center"><em>{{ $t('pin.unpair_single_no_devices') }}</em></div>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
@@ -91,6 +156,106 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permissions:
|
||||||
|
enum class PERM: uint32_t {
|
||||||
|
_reserved = 1,
|
||||||
|
|
||||||
|
_input = _reserved << 8, // Input permission group
|
||||||
|
input_controller = _input << 0, // Allow controller input
|
||||||
|
input_touch = _input << 1, // Allow touch input
|
||||||
|
input_pen = _input << 2, // Allow pen input
|
||||||
|
input_kbdm = _input << 3, // Allow kbd/mouse input
|
||||||
|
_all_inputs = input_controller | input_touch | input_pen | input_kbdm,
|
||||||
|
|
||||||
|
_operation = _input << 8, // Operation permission group
|
||||||
|
clipboard_set = _operation << 0, // Allow set clipboard from client
|
||||||
|
clipboard_read = _operation << 1, // Allow read clipboard from host
|
||||||
|
file_upload = _operation << 2, // Allow upload files to host
|
||||||
|
file_dwnload = _operation << 3, // Allow download files from host
|
||||||
|
server_cmd = _operation << 4, // Allow execute server cmd
|
||||||
|
_all_opeiations = clipboard_set | clipboard_read | file_upload | file_dwnload | server_cmd,
|
||||||
|
|
||||||
|
_action = _operation << 8, // Action permission group
|
||||||
|
list = _action << 0, // Allow list apps
|
||||||
|
view = _action << 1, // Allow view streams
|
||||||
|
launch = _action << 2, // Allow launch apps
|
||||||
|
_allow_view = view | launch, // Launch contains view permission
|
||||||
|
_all_actions = list | view | launch,
|
||||||
|
|
||||||
|
_default = view | list, // Default permissions for new clients
|
||||||
|
_no = 0, // No permissions are granted
|
||||||
|
_all = _all_inputs | _all_opeiations | _all_actions, // All current permissions
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
const permissionMapping = {
|
||||||
|
// Input permission group
|
||||||
|
input_controller: 0x00000100,
|
||||||
|
input_touch: 0x00000200,
|
||||||
|
input_pen: 0x00000400,
|
||||||
|
input_kbdm: 0x00000800,
|
||||||
|
_all_inputs: 0x00000F00,
|
||||||
|
|
||||||
|
// Operation permission group
|
||||||
|
clipboard_set: 0x00010000,
|
||||||
|
clipboard_read: 0x00020000,
|
||||||
|
file_upload: 0x00040000,
|
||||||
|
file_dwnload: 0x00080000,
|
||||||
|
server_cmd: 0x00100000,
|
||||||
|
_all_operations: 0x001F0000,
|
||||||
|
|
||||||
|
// Action permission group
|
||||||
|
list: 0x01000000,
|
||||||
|
view: 0x02000000,
|
||||||
|
launch: 0x04000000,
|
||||||
|
_allow_view: 0x06000000,
|
||||||
|
_all_actions: 0x07000000,
|
||||||
|
|
||||||
|
// Special permissions
|
||||||
|
_default: 0x03000000,
|
||||||
|
_no: 0x00000000,
|
||||||
|
_all: 0x071F0F00
|
||||||
|
};
|
||||||
|
|
||||||
|
const permissionGroups = [
|
||||||
|
{ name: 'Action', permissions: [
|
||||||
|
{
|
||||||
|
name: 'list',
|
||||||
|
supressed_by: ['view', 'launch']
|
||||||
|
}, {
|
||||||
|
name: 'view',
|
||||||
|
supressed_by: ['launch']
|
||||||
|
}, {
|
||||||
|
name: 'launch',
|
||||||
|
supressed_by: []
|
||||||
|
}
|
||||||
|
] },
|
||||||
|
{ name: 'Operation', permissions: [
|
||||||
|
{
|
||||||
|
name: 'server_cmd',
|
||||||
|
supressed_by: []
|
||||||
|
}
|
||||||
|
] },
|
||||||
|
{ name: 'Input', permissions: [
|
||||||
|
{
|
||||||
|
name: 'input_controller',
|
||||||
|
supressed_by: []
|
||||||
|
}, {
|
||||||
|
name: 'input_touch',
|
||||||
|
supressed_by: []
|
||||||
|
}, {
|
||||||
|
name: 'input_pen',
|
||||||
|
supressed_by: []
|
||||||
|
}, {
|
||||||
|
name: 'input_kbdm',
|
||||||
|
supressed_by: []
|
||||||
|
}
|
||||||
|
] },
|
||||||
|
];
|
||||||
|
|
||||||
|
let currentEditingClient = null;
|
||||||
|
|
||||||
const data = () => {
|
const data = () => {
|
||||||
return {
|
return {
|
||||||
editingHost: false,
|
editingHost: false,
|
||||||
@@ -102,7 +267,12 @@
|
|||||||
deviceName: '',
|
deviceName: '',
|
||||||
hostAddr: '',
|
hostAddr: '',
|
||||||
hostPort: '',
|
hostPort: '',
|
||||||
hostName: ''
|
hostName: '',
|
||||||
|
permissionGroups,
|
||||||
|
clients: [],
|
||||||
|
showApplyMessage: false,
|
||||||
|
unpairAllPressed: false,
|
||||||
|
unpairAllStatus: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,10 +290,14 @@
|
|||||||
return !!(this.hostAddr && this.hostPort);
|
return !!(this.hostAddr && this.hostPort);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
this.refreshClients();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
switchTab(currentTab) {
|
switchTab(currentTab) {
|
||||||
location.hash = currentTab;
|
location.hash = currentTab;
|
||||||
Object.assign(this, data());
|
const clients = this.clients;
|
||||||
|
Object.assign(this, data(), { clients });
|
||||||
hostInfoCache = null;
|
hostInfoCache = null;
|
||||||
clearTimeout(resetOTPTimeout);
|
clearTimeout(resetOTPTimeout);
|
||||||
},
|
},
|
||||||
@@ -155,6 +329,8 @@
|
|||||||
).innerHTML = `<div class="alert alert-success" role="alert">${this.i18n.t('pin.pair_success')}</div>`;
|
).innerHTML = `<div class="alert alert-success" role="alert">${this.i18n.t('pin.pair_success')}</div>`;
|
||||||
document.querySelector("#pin-input").value = "";
|
document.querySelector("#pin-input").value = "";
|
||||||
document.querySelector("#name-input").value = "";
|
document.querySelector("#name-input").value = "";
|
||||||
|
|
||||||
|
setTimeout(() => this.refreshClients(), 1000);
|
||||||
} else {
|
} else {
|
||||||
document.querySelector(
|
document.querySelector(
|
||||||
"#status"
|
"#status"
|
||||||
@@ -217,7 +393,128 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
clickedApplyBanner() {
|
||||||
|
this.showApplyMessage = false;
|
||||||
|
},
|
||||||
|
editClient(client) {
|
||||||
|
if (currentEditingClient) {
|
||||||
|
this.cancelEdit(currentEditingClient);
|
||||||
}
|
}
|
||||||
|
currentEditingClient = client;
|
||||||
|
client.editing = true;
|
||||||
|
client.editPerm = client.perm;
|
||||||
|
client.editName = client.name;
|
||||||
|
},
|
||||||
|
cancelEdit(client) {
|
||||||
|
client.editing = false;
|
||||||
|
client.editPerm = client.perm;
|
||||||
|
client.editName = client.name;
|
||||||
|
currentEditingClient = null;
|
||||||
|
},
|
||||||
|
saveClient(client) {
|
||||||
|
client.editing = false;
|
||||||
|
currentEditingClient = null;
|
||||||
|
const editedClient = {
|
||||||
|
uuid: client.uuid,
|
||||||
|
name: client.editName,
|
||||||
|
perm: client.editPerm & permissionMapping._all
|
||||||
|
}
|
||||||
|
fetch("/api/clients/update", {
|
||||||
|
credentials: 'include',
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(editedClient)
|
||||||
|
}).finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshClients();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
permToStr(perm) {
|
||||||
|
const permSegments = [];
|
||||||
|
permSegments.push((perm >> 24) & 0xFF);
|
||||||
|
permSegments.push((perm >> 16) & 0xFF);
|
||||||
|
permSegments.push((perm >> 8) & 0xFF);
|
||||||
|
return permSegments.map(seg => seg.toString(16).toUpperCase().padStart(2, '0')).join(' ');
|
||||||
|
},
|
||||||
|
checkPermission(perm, permission) {
|
||||||
|
return (perm & permissionMapping[permission]) !== 0;
|
||||||
|
},
|
||||||
|
isSupressed(perm, permission, supressed_by) {
|
||||||
|
for (const supressed of supressed_by) {
|
||||||
|
if (this.checkPermission(perm, supressed)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
togglePermission(client, permission) {
|
||||||
|
client.editPerm ^= permissionMapping[permission];
|
||||||
|
},
|
||||||
|
disconnectClient(uuid) {
|
||||||
|
fetch("/api/clients/disconnect", {
|
||||||
|
credentials: 'include',
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ uuid })
|
||||||
|
}).finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshClients();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unpairAll() {
|
||||||
|
this.unpairAllPressed = true;
|
||||||
|
fetch("/api/clients/unpair-all", {
|
||||||
|
credentials: 'include',
|
||||||
|
method: "POST"
|
||||||
|
})
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((r) => {
|
||||||
|
this.unpairAllPressed = false;
|
||||||
|
this.unpairAllStatus = r.status.toString() === "true";
|
||||||
|
setTimeout(() => {
|
||||||
|
this.unpairAllStatus = null;
|
||||||
|
}, 5000);
|
||||||
|
this.refreshClients();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
unpairSingle(uuid) {
|
||||||
|
fetch("/api/clients/unpair", {
|
||||||
|
credentials: 'include',
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ uuid })
|
||||||
|
}).then(() => {
|
||||||
|
this.showApplyMessage = true;
|
||||||
|
this.refreshClients();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refreshClients() {
|
||||||
|
if (currentEditingClient) {
|
||||||
|
this.cancelEdit(currentEditingClient);
|
||||||
|
}
|
||||||
|
fetch("/api/clients/list", { credentials: 'include' })
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((response) => {
|
||||||
|
const clientList = document.querySelector("#client-list");
|
||||||
|
if (response.status === 'true' && response.named_certs && response.named_certs.length) {
|
||||||
|
this.clients = response.named_certs.map(({name, uuid, perm, connected}) => {
|
||||||
|
const permInt = parseInt(perm, 10);
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
uuid,
|
||||||
|
perm: permInt,
|
||||||
|
connected: connected === 'true',
|
||||||
|
editing: false,
|
||||||
|
editPerm: permInt,
|
||||||
|
editName: name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
currentEditingClient = null;
|
||||||
|
} else {
|
||||||
|
this.clients = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -388,6 +388,20 @@
|
|||||||
"password_change": "Password Change",
|
"password_change": "Password Change",
|
||||||
"success_msg": "Password has been changed successfully! This page will reload soon, your browser will ask you for the new credentials."
|
"success_msg": "Password has been changed successfully! This page will reload soon, your browser will ask you for the new credentials."
|
||||||
},
|
},
|
||||||
|
"permissions": {
|
||||||
|
"input_controller": "Controller Input",
|
||||||
|
"input_touch": "Touch Input",
|
||||||
|
"input_pen": "Pen Input",
|
||||||
|
"input_kbdm": "Keyboard & Mouse Input",
|
||||||
|
"clipboard_set": "Clipboard Set",
|
||||||
|
"clipboard_read": "Clipboard Read",
|
||||||
|
"file_upload": "File Upload",
|
||||||
|
"file_dwnload": "File Download",
|
||||||
|
"server_cmd": "Server Command",
|
||||||
|
"list": "List Apps",
|
||||||
|
"view": "View Streams",
|
||||||
|
"launch": "Launch Apps"
|
||||||
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"device_name": "Optional: Device Name",
|
"device_name": "Optional: Device Name",
|
||||||
"pair_failure": "Pairing Failed: Check if the PIN is typed correctly",
|
"pair_failure": "Pairing Failed: Check if the PIN is typed correctly",
|
||||||
@@ -402,7 +416,15 @@
|
|||||||
"otp_expired_msg": "OTP expired. Please request a new one.",
|
"otp_expired_msg": "OTP expired. Please request a new one.",
|
||||||
"otp_success": "PIN request success, the PIN is available within 3 minutes.",
|
"otp_success": "PIN request success, the PIN is available within 3 minutes.",
|
||||||
"otp_msg": "OTP pairing is only available for the latest Artemis clients. Please use legacy pairing method for other clients.",
|
"otp_msg": "OTP pairing is only available for the latest Artemis clients. Please use legacy pairing method for other clients.",
|
||||||
"otp_pair_now": "PIN generated successfully, do you want to pair now?"
|
"otp_pair_now": "PIN generated successfully, do you want to pair now?",
|
||||||
|
"device_management": "Device Management",
|
||||||
|
"device_management_desc": "Manage your paired devices.",
|
||||||
|
"unpair_all": "Unpair All",
|
||||||
|
"unpair_all_success": "All devices unpaired.",
|
||||||
|
"unpair_all_error": "Error while unpairing",
|
||||||
|
"unpair_single_no_devices": "There are no paired devices.",
|
||||||
|
"unpair_single_success": "However, the device(s) may still be in an active session. Use the 'Force Close' button above to end any open sessions.",
|
||||||
|
"unpair_single_unknown": "Unknown Client"
|
||||||
},
|
},
|
||||||
"resource_card": {
|
"resource_card": {
|
||||||
"github_discussions": "GitHub Discussions",
|
"github_discussions": "GitHub Discussions",
|
||||||
@@ -430,15 +452,7 @@
|
|||||||
"quit_apollo_success": "Apollo has exited.",
|
"quit_apollo_success": "Apollo has exited.",
|
||||||
"quit_apollo_success_ongoing": "Apollo is quitting...",
|
"quit_apollo_success_ongoing": "Apollo is quitting...",
|
||||||
"quit_apollo_confirm": "Do you really want to quit Apollo? You'll not be able to start Apollo again if you have no other methods to operate your computer.",
|
"quit_apollo_confirm": "Do you really want to quit Apollo? You'll not be able to start Apollo again if you have no other methods to operate your computer.",
|
||||||
"troubleshooting": "Troubleshooting",
|
"troubleshooting": "Troubleshooting"
|
||||||
"unpair_all": "Unpair All",
|
|
||||||
"unpair_all_error": "Error while unpairing",
|
|
||||||
"unpair_all_success": "All devices unpaired.",
|
|
||||||
"unpair_desc": "Remove your paired devices. Individually unpaired devices with an active session will remain connected, but cannot start or resume a session.",
|
|
||||||
"unpair_single_no_devices": "There are no paired devices.",
|
|
||||||
"unpair_single_success": "However, the device(s) may still be in an active session. Use the 'Force Close' button above to end any open sessions.",
|
|
||||||
"unpair_single_unknown": "Unknown Client",
|
|
||||||
"unpair_title": "Unpair Devices"
|
|
||||||
},
|
},
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"confirm_password": "Confirm password",
|
"confirm_password": "Confirm password",
|
||||||
|
|||||||
@@ -388,6 +388,20 @@
|
|||||||
"password_change": "更改密码",
|
"password_change": "更改密码",
|
||||||
"success_msg": "密码已成功更改!此页面即将重新加载,您的浏览器将要求您输入新的账户信息。"
|
"success_msg": "密码已成功更改!此页面即将重新加载,您的浏览器将要求您输入新的账户信息。"
|
||||||
},
|
},
|
||||||
|
"permissions": {
|
||||||
|
"input_controller": "手柄输入",
|
||||||
|
"input_touch": "触摸输入",
|
||||||
|
"input_pen": "笔输入",
|
||||||
|
"input_kbdm": "键鼠输入",
|
||||||
|
"clipboard_set": "上传剪贴板",
|
||||||
|
"clipboard_read": "获取剪贴板",
|
||||||
|
"file_upload": "上传文件",
|
||||||
|
"file_dwnload": "下载文件",
|
||||||
|
"server_cmd": "服务端命令",
|
||||||
|
"list": "列出APP",
|
||||||
|
"view": "查看串流",
|
||||||
|
"launch": "启动APP"
|
||||||
|
},
|
||||||
"pin": {
|
"pin": {
|
||||||
"device_name": "设备名称",
|
"device_name": "设备名称",
|
||||||
"pair_failure": "配对失败:请检查 PIN 码是否正确输入",
|
"pair_failure": "配对失败:请检查 PIN 码是否正确输入",
|
||||||
@@ -402,7 +416,15 @@
|
|||||||
"otp_expired_msg": "口令已过期,请重新请求。",
|
"otp_expired_msg": "口令已过期,请重新请求。",
|
||||||
"otp_success": "一次性 PIN 请求成功,3分钟内有效。",
|
"otp_success": "一次性 PIN 请求成功,3分钟内有效。",
|
||||||
"otp_msg": "一次性口令目前仅支持最新的 Artemis 客户端使用。其他客户端请使用传统配对方式。",
|
"otp_msg": "一次性口令目前仅支持最新的 Artemis 客户端使用。其他客户端请使用传统配对方式。",
|
||||||
"otp_pair_now": "PIN 请求成功,是否一键配对?"
|
"otp_pair_now": "PIN 请求成功,是否一键配对?",
|
||||||
|
"device_management": "设备管理",
|
||||||
|
"device_management_desc": "管理已配对的设备。",
|
||||||
|
"unpair_all": "全部取消配对",
|
||||||
|
"unpair_all_success": "全部取消配对成功!",
|
||||||
|
"unpair_all_error": "取消配对时出错",
|
||||||
|
"unpair_single_no_devices": "没有配对的设备。",
|
||||||
|
"unpair_single_success": "然而,设备可能仍然处于活动会话中,使用上面的“强制关闭”按钮结束任何打开的会话。",
|
||||||
|
"unpair_single_unknown": "未知客户端"
|
||||||
},
|
},
|
||||||
"resource_card": {
|
"resource_card": {
|
||||||
"github_discussions": "Github 讨论区",
|
"github_discussions": "Github 讨论区",
|
||||||
@@ -430,15 +452,7 @@
|
|||||||
"quit_apollo_success": "Apollo 已成功退出。",
|
"quit_apollo_success": "Apollo 已成功退出。",
|
||||||
"quit_apollo_success_ongoing": "Apollo 正在退出...",
|
"quit_apollo_success_ongoing": "Apollo 正在退出...",
|
||||||
"quit_apollo_confirm": "确定要退出 Apollo 吗?如果没有其他操作方式,你将无法再次启动 Apollo。",
|
"quit_apollo_confirm": "确定要退出 Apollo 吗?如果没有其他操作方式,你将无法再次启动 Apollo。",
|
||||||
"troubleshooting": "故障排除",
|
"troubleshooting": "故障排除"
|
||||||
"unpair_all": "全部取消配对",
|
|
||||||
"unpair_all_error": "取消配对时出错",
|
|
||||||
"unpair_all_success": "取消配对成功!",
|
|
||||||
"unpair_desc": "删除您已配对的设备。未配对的单独设备与活动会话将保持连接,但不能启动或继续会话。",
|
|
||||||
"unpair_single_no_devices": "没有配对的设备。",
|
|
||||||
"unpair_single_success": "然而,设备可能仍然处于活动会话中,使用上面的“强制关闭”按钮结束任何打开的会话。",
|
|
||||||
"unpair_single_unknown": "未知客户端",
|
|
||||||
"unpair_title": "取消配对设备"
|
|
||||||
},
|
},
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"confirm_password": "确认密码",
|
"confirm_password": "确认密码",
|
||||||
|
|||||||
@@ -94,40 +94,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Unpair Clients -->
|
|
||||||
<div class="card my-4">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="p-2">
|
|
||||||
<div class="d-flex justify-content-end align-items-center">
|
|
||||||
<h2 id="unpair" class="text-center me-auto">{{ $t('troubleshooting.unpair_title') }}</h2>
|
|
||||||
<button class="btn btn-danger" :disabled="unpairAllPressed" @click="unpairAll">
|
|
||||||
{{ $t('troubleshooting.unpair_all') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<p class="mb-0">{{ $t('troubleshooting.unpair_desc') }}</p>
|
|
||||||
<div id="apply-alert" class="alert alert-success d-flex align-items-center mt-3" :style="{ 'display': (showApplyMessage ? 'flex !important': 'none !important') }">
|
|
||||||
<div class="me-2"><b>{{ $t('_common.success') }}</b> {{ $t('troubleshooting.unpair_single_success') }}</div>
|
|
||||||
<button class="btn btn-success ms-auto apply" @click="clickedApplyBanner">{{ $t('_common.dismiss') }}</button>
|
|
||||||
</div>
|
|
||||||
<div class="alert alert-success mt-3" v-if="unpairAllStatus === true">
|
|
||||||
{{ $t('troubleshooting.unpair_all_success') }}
|
|
||||||
</div>
|
|
||||||
<div class="alert alert-danger mt-3" v-if="unpairAllStatus === false">
|
|
||||||
{{ $t('troubleshooting.unpair_all_error') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul id="client-list" class="list-group list-group-flush list-group-item-light" v-if="clients && clients.length > 0">
|
|
||||||
<div v-for="client in clients" class="list-group-item d-flex">
|
|
||||||
<div class="p-2 flex-grow-1">{{client.name != "" ? client.name : $t('troubleshooting.unpair_single_unknown')}}</div><div class="me-2 ms-auto btn btn-danger" @click="unpairSingle(client.uuid)"><i class="fas fa-trash"></i></div>
|
|
||||||
</div>
|
|
||||||
</ul>
|
|
||||||
<ul v-else class="list-group list-group-flush list-group-item-light">
|
|
||||||
<div class="list-group-item p-3 text-center"><em>{{ $t('troubleshooting.unpair_single_no_devices') }}</em></div>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- Logs -->
|
<!-- Logs -->
|
||||||
<div class="card p-2 my-4">
|
<div class="card p-2 my-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -166,10 +132,7 @@
|
|||||||
logInterval: null,
|
logInterval: null,
|
||||||
serverRestarting: false,
|
serverRestarting: false,
|
||||||
serverQuitting: false,
|
serverQuitting: false,
|
||||||
serverQuit: false,
|
serverQuit: false
|
||||||
showApplyMessage: false,
|
|
||||||
unpairAllPressed: false,
|
|
||||||
unpairAllStatus: null,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -213,48 +176,6 @@
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
unpairAll() {
|
|
||||||
this.unpairAllPressed = true;
|
|
||||||
fetch("/api/clients/unpair-all", {
|
|
||||||
credentials: 'include',
|
|
||||||
method: "POST"
|
|
||||||
})
|
|
||||||
.then((r) => r.json())
|
|
||||||
.then((r) => {
|
|
||||||
this.unpairAllPressed = false;
|
|
||||||
this.unpairAllStatus = r.status.toString() === "true";
|
|
||||||
setTimeout(() => {
|
|
||||||
this.unpairAllStatus = null;
|
|
||||||
}, 5000);
|
|
||||||
this.refreshClients();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
unpairSingle(uuid) {
|
|
||||||
fetch("/api/clients/unpair", { credentials: 'include',
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ uuid })
|
|
||||||
}).then(() => {
|
|
||||||
this.showApplyMessage = true;
|
|
||||||
this.refreshClients();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
refreshClients() {
|
|
||||||
fetch("/api/clients/list", { credentials: 'include' })
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((response) => {
|
|
||||||
const clientList = document.querySelector("#client-list");
|
|
||||||
if (response.status === 'true' && response.named_certs && response.named_certs.length) {
|
|
||||||
this.clients = response.named_certs.sort((a, b) => {
|
|
||||||
return (a.name.toLowerCase() > b.name.toLowerCase() || a.name == "" ? 1 : -1)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.clients = [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
clickedApplyBanner() {
|
|
||||||
this.showApplyMessage = false;
|
|
||||||
},
|
|
||||||
copyLogs() {
|
copyLogs() {
|
||||||
navigator.clipboard.writeText(this.actualLogs);
|
navigator.clipboard.writeText(this.actualLogs);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user