mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-01-15 22:53:52 +00:00
Name and unpair individual clients (#2042)
This commit is contained in:
parent
287ac4c0fb
commit
5fcd07ecb1
@ -693,7 +693,8 @@ namespace confighttp {
|
||||
// TODO: Input Validation
|
||||
pt::read_json(ss, inputTree);
|
||||
std::string pin = inputTree.get<std::string>("pin");
|
||||
outputTree.put("status", nvhttp::pin(pin));
|
||||
std::string name = inputTree.get<std::string>("name");
|
||||
outputTree.put("status", nvhttp::pin(pin, name));
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "SavePin: "sv << e.what();
|
||||
@ -717,6 +718,60 @@ namespace confighttp {
|
||||
response->write(data.str());
|
||||
});
|
||||
nvhttp::erase_all_clients();
|
||||
proc::proc.terminate();
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
|
||||
void
|
||||
unpair(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 {
|
||||
// TODO: Input Validation
|
||||
pt::read_json(ss, inputTree);
|
||||
std::string uuid = inputTree.get<std::string>("uuid");
|
||||
outputTree.put("status", nvhttp::unpair_client(uuid));
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "Unpair: "sv << e.what();
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
listClients(resp_https_t response, req_https_t request) {
|
||||
if (!authenticate(response, request)) return;
|
||||
|
||||
print_req(request);
|
||||
|
||||
pt::ptree named_certs = nvhttp::get_all_clients();
|
||||
|
||||
pt::ptree outputTree;
|
||||
|
||||
outputTree.put("status", false);
|
||||
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
outputTree.add_child("named_certs", named_certs);
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
|
||||
@ -765,7 +820,9 @@ namespace confighttp {
|
||||
server.resource["^/api/restart$"]["POST"] = restart;
|
||||
server.resource["^/api/password$"]["POST"] = savePassword;
|
||||
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
||||
server.resource["^/api/clients/unpair$"]["POST"] = unpairAll;
|
||||
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
||||
server.resource["^/api/clients/list$"]["GET"] = listClients;
|
||||
server.resource["^/api/clients/unpair$"]["POST"] = unpair;
|
||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||
server.resource["^/images/sunshine.ico$"]["GET"] = getFaviconImage;
|
||||
|
172
src/nvhttp.cpp
172
src/nvhttp.cpp
@ -117,9 +117,15 @@ namespace nvhttp {
|
||||
std::string pkey;
|
||||
} conf_intern;
|
||||
|
||||
struct named_cert_t {
|
||||
std::string name;
|
||||
std::string uuid;
|
||||
std::string cert;
|
||||
};
|
||||
|
||||
struct client_t {
|
||||
std::string uniqueID;
|
||||
std::vector<std::string> certs;
|
||||
std::vector<named_cert_t> named_devices;
|
||||
};
|
||||
|
||||
struct pair_session_t {
|
||||
@ -145,7 +151,7 @@ namespace nvhttp {
|
||||
|
||||
// uniqueID, session
|
||||
std::unordered_map<std::string, pair_session_t> map_id_sess;
|
||||
std::unordered_map<std::string, client_t> map_id_client;
|
||||
client_t client_root;
|
||||
std::atomic<uint32_t> session_id_counter;
|
||||
|
||||
using args_t = SimpleWeb::CaseInsensitiveMultimap;
|
||||
@ -189,22 +195,18 @@ namespace nvhttp {
|
||||
root.erase("root"s);
|
||||
|
||||
root.put("root.uniqueid", http::unique_id);
|
||||
auto &nodes = root.add_child("root.devices", pt::ptree {});
|
||||
for (auto &[_, client] : map_id_client) {
|
||||
pt::ptree node;
|
||||
client_t &client = client_root;
|
||||
pt::ptree node;
|
||||
|
||||
node.put("uniqueid"s, client.uniqueID);
|
||||
|
||||
pt::ptree cert_nodes;
|
||||
for (auto &cert : client.certs) {
|
||||
pt::ptree cert_node;
|
||||
cert_node.put_value(cert);
|
||||
cert_nodes.push_back(std::make_pair(""s, cert_node));
|
||||
}
|
||||
node.add_child("certs"s, cert_nodes);
|
||||
|
||||
nodes.push_back(std::make_pair(""s, node));
|
||||
pt::ptree named_cert_nodes;
|
||||
for (auto &named_cert : client.named_devices) {
|
||||
pt::ptree named_cert_node;
|
||||
named_cert_node.put("name"s, named_cert.name);
|
||||
named_cert_node.put("cert"s, named_cert.cert);
|
||||
named_cert_node.put("uuid"s, named_cert.uuid);
|
||||
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
||||
}
|
||||
root.add_child("root.named_devices"s, named_cert_nodes);
|
||||
|
||||
try {
|
||||
pt::write_json(config::nvhttp.file_state, root);
|
||||
@ -223,9 +225,9 @@ namespace nvhttp {
|
||||
return;
|
||||
}
|
||||
|
||||
pt::ptree root;
|
||||
pt::ptree tree;
|
||||
try {
|
||||
pt::read_json(config::nvhttp.file_state, root);
|
||||
pt::read_json(config::nvhttp.file_state, tree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what();
|
||||
@ -233,7 +235,7 @@ namespace nvhttp {
|
||||
return;
|
||||
}
|
||||
|
||||
auto unique_id_p = root.get_optional<std::string>("root.uniqueid");
|
||||
auto unique_id_p = tree.get_optional<std::string>("root.uniqueid");
|
||||
if (!unique_id_p) {
|
||||
// This file doesn't contain moonlight credentials
|
||||
http::unique_id = uuid_util::uuid_t::generate().string();
|
||||
@ -241,30 +243,61 @@ namespace nvhttp {
|
||||
}
|
||||
http::unique_id = std::move(*unique_id_p);
|
||||
|
||||
auto device_nodes = root.get_child("root.devices");
|
||||
auto root = tree.get_child("root");
|
||||
client_t client;
|
||||
|
||||
for (auto &[_, device_node] : device_nodes) {
|
||||
auto uniqID = device_node.get<std::string>("uniqueid");
|
||||
auto &client = map_id_client.emplace(uniqID, client_t {}).first->second;
|
||||
// Import from old format
|
||||
if (root.get_child_optional("devices")) {
|
||||
auto device_nodes = root.get_child("devices");
|
||||
for (auto &[_, device_node] : device_nodes) {
|
||||
auto uniqID = device_node.get<std::string>("uniqueid");
|
||||
|
||||
client.uniqueID = uniqID;
|
||||
|
||||
for (auto &[_, el] : device_node.get_child("certs")) {
|
||||
client.certs.emplace_back(el.get_value<std::string>());
|
||||
if (device_node.count("certs")) {
|
||||
for (auto &[_, el] : device_node.get_child("certs")) {
|
||||
named_cert_t named_cert;
|
||||
named_cert.name = ""s;
|
||||
named_cert.cert = el.get_value<std::string>();
|
||||
named_cert.uuid = uuid_util::uuid_t::generate().string();
|
||||
client.named_devices.emplace_back(named_cert);
|
||||
client.certs.emplace_back(named_cert.cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (root.count("named_devices")) {
|
||||
for (auto &[_, el] : root.get_child("named_devices")) {
|
||||
named_cert_t named_cert;
|
||||
named_cert.name = el.get_child("name").get_value<std::string>();
|
||||
named_cert.cert = el.get_child("cert").get_value<std::string>();
|
||||
named_cert.uuid = el.get_child("uuid").get_value<std::string>();
|
||||
client.named_devices.emplace_back(named_cert);
|
||||
client.certs.emplace_back(named_cert.cert);
|
||||
}
|
||||
}
|
||||
|
||||
// Empty certificate chain and import certs from file
|
||||
cert_chain.clear();
|
||||
for (auto &cert : client.certs) {
|
||||
cert_chain.add(crypto::x509(cert));
|
||||
}
|
||||
for (auto &named_cert : client.named_devices) {
|
||||
cert_chain.add(crypto::x509(named_cert.cert));
|
||||
}
|
||||
|
||||
client_root = client;
|
||||
}
|
||||
|
||||
void
|
||||
update_id_client(const std::string &uniqueID, std::string &&cert, op_e op) {
|
||||
switch (op) {
|
||||
case op_e::ADD: {
|
||||
auto &client = map_id_client[uniqueID];
|
||||
client_t &client = client_root;
|
||||
client.certs.emplace_back(std::move(cert));
|
||||
client.uniqueID = uniqueID;
|
||||
} break;
|
||||
case op_e::REMOVE:
|
||||
map_id_client.erase(uniqueID);
|
||||
client_t client;
|
||||
client_root = client;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -579,15 +612,16 @@ namespace nvhttp {
|
||||
/**
|
||||
* @brief Compare the user supplied pin to the Moonlight pin.
|
||||
* @param pin The user supplied pin.
|
||||
* @param name The user supplied name.
|
||||
* @return `true` if the pin is correct, `false` otherwise.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* bool pin_status = nvhttp::pin("1234");
|
||||
* bool pin_status = nvhttp::pin("1234", "laptop");
|
||||
* ```
|
||||
*/
|
||||
bool
|
||||
pin(std::string pin) {
|
||||
pin(std::string pin, std::string name) {
|
||||
pt::ptree tree;
|
||||
if (map_id_sess.empty()) {
|
||||
return false;
|
||||
@ -613,6 +647,14 @@ namespace nvhttp {
|
||||
auto &sess = std::begin(map_id_sess)->second;
|
||||
getservercert(sess, tree, pin);
|
||||
|
||||
// set up named cert
|
||||
client_t &client = client_root;
|
||||
named_cert_t named_cert;
|
||||
named_cert.name = name;
|
||||
named_cert.cert = sess.client.cert;
|
||||
named_cert.uuid = uuid_util::uuid_t::generate().string();
|
||||
client.named_devices.emplace_back(named_cert);
|
||||
|
||||
// response to the request for pin
|
||||
std::ostringstream data;
|
||||
pt::write_xml(data, tree);
|
||||
@ -645,9 +687,7 @@ namespace nvhttp {
|
||||
auto clientID = args.find("uniqueid"s);
|
||||
|
||||
if (clientID != std::end(args)) {
|
||||
if (auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) {
|
||||
pair_status = 1;
|
||||
}
|
||||
pair_status = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -742,6 +782,20 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
pt::ptree
|
||||
get_all_clients() {
|
||||
pt::ptree named_cert_nodes;
|
||||
client_t &client = client_root;
|
||||
for (auto &named_cert : client.named_devices) {
|
||||
pt::ptree named_cert_node;
|
||||
named_cert_node.put("name"s, named_cert.name);
|
||||
named_cert_node.put("uuid"s, named_cert.uuid);
|
||||
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
||||
}
|
||||
|
||||
return named_cert_nodes;
|
||||
}
|
||||
|
||||
void
|
||||
applist(resp_https_t response, req_https_t request) {
|
||||
print_req<SimpleWeb::HTTPS>(request);
|
||||
@ -1020,12 +1074,6 @@ namespace nvhttp {
|
||||
conf_intern.pkey = file_handler::read_file(config::nvhttp.pkey.c_str());
|
||||
conf_intern.servercert = file_handler::read_file(config::nvhttp.cert.c_str());
|
||||
|
||||
for (auto &[_, client] : map_id_client) {
|
||||
for (auto &cert : client.certs) {
|
||||
cert_chain.add(crypto::x509(cert));
|
||||
}
|
||||
}
|
||||
|
||||
auto add_cert = std::make_shared<safe::queue_t<crypto::x509_t>>(30);
|
||||
|
||||
// resume doesn't always get the parameter "localAudioPlayMode"
|
||||
@ -1149,8 +1197,48 @@ namespace nvhttp {
|
||||
*/
|
||||
void
|
||||
erase_all_clients() {
|
||||
map_id_client.clear();
|
||||
client_t client;
|
||||
client_root = client;
|
||||
cert_chain.clear();
|
||||
save_state();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove single client.
|
||||
*
|
||||
* EXAMPLES:
|
||||
* ```cpp
|
||||
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
|
||||
* ```
|
||||
*/
|
||||
int
|
||||
unpair_client(std::string uuid) {
|
||||
int removed = 0;
|
||||
client_t &client = client_root;
|
||||
for (auto it = client.named_devices.begin(); it != client.named_devices.end();) {
|
||||
if ((*it).uuid == uuid) {
|
||||
// Find matching cert and remove it
|
||||
for (auto cert = client.certs.begin(); cert != client.certs.end();) {
|
||||
if ((*cert) == (*it).cert) {
|
||||
cert = client.certs.erase(cert);
|
||||
removed++;
|
||||
}
|
||||
else {
|
||||
++cert;
|
||||
}
|
||||
}
|
||||
|
||||
// And then remove the named cert
|
||||
it = client.named_devices.erase(it);
|
||||
removed++;
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
save_state();
|
||||
load_state();
|
||||
return removed;
|
||||
}
|
||||
} // namespace nvhttp
|
||||
|
@ -9,6 +9,9 @@
|
||||
// standard includes
|
||||
#include <string>
|
||||
|
||||
// lib includes
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
// local includes
|
||||
#include "thread_safe.h"
|
||||
|
||||
@ -43,7 +46,11 @@ namespace nvhttp {
|
||||
void
|
||||
start();
|
||||
bool
|
||||
pin(std::string pin);
|
||||
pin(std::string pin, std::string name);
|
||||
int
|
||||
unpair_client(std::string uniqueid);
|
||||
boost::property_tree::ptree
|
||||
get_all_clients();
|
||||
void
|
||||
erase_all_clients();
|
||||
} // namespace nvhttp
|
||||
|
@ -8,10 +8,11 @@
|
||||
<body id="app" v-cloak>
|
||||
<Navbar></Navbar>
|
||||
<div id="content" class="container">
|
||||
<h1 class="my-4">{{ $t('pin.pin_pairing') }}</h1>
|
||||
<form action="" class="form d-flex flex-column align-items-center" id="form">
|
||||
<h1 class="my-4 text-center">{{ $t('pin.pin_pairing') }}</h1>
|
||||
<form class="form d-flex flex-column align-items-center" id="form" @submit.prevent="registerDevice">
|
||||
<div class="card flex-column d-flex p-4 mb-4">
|
||||
<input type="text" pattern="\d*" placeholder="PIN" autofocus id="pin-input" class="form-control my-4" />
|
||||
<input type="text" pattern="\d*" :placeholder="`${$t('navbar.pin')}`" autofocus id="pin-input" class="form-control mt-2" required />
|
||||
<input type="text" :placeholder="`${$t('pin.device_name')}`" id="name-input" class="form-control my-4" required />
|
||||
<button class="btn btn-primary">{{ $t('pin.send') }}</button>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
@ -24,37 +25,38 @@
|
||||
|
||||
<script type="module">
|
||||
import { createApp } from 'vue'
|
||||
import i18n from './locale.js'
|
||||
import { initApp } from './init'
|
||||
import Navbar from './Navbar.vue'
|
||||
import {initApp} from "./init";
|
||||
|
||||
let app = createApp({
|
||||
components: {
|
||||
Navbar
|
||||
},
|
||||
inject: ['i18n'],
|
||||
methods: {
|
||||
registerDevice(e) {
|
||||
let pin = document.querySelector("#pin-input").value;
|
||||
let name = document.querySelector("#name-input").value;
|
||||
document.querySelector("#status").innerHTML = "";
|
||||
let b = JSON.stringify({pin: pin, name: name});
|
||||
fetch("/api/pin", {method: "POST", body: b})
|
||||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
if (response.status.toString().toLowerCase() === "true") {
|
||||
document.querySelector(
|
||||
"#status"
|
||||
).innerHTML = `<div class="alert alert-success" role="alert">${this.i18n.t('pin.pair_success')}</div>`;
|
||||
document.querySelector("#pin-input").value = "";
|
||||
document.querySelector("#name-input").value = "";
|
||||
} else {
|
||||
document.querySelector(
|
||||
"#status"
|
||||
).innerHTML = `<div class="alert alert-danger" role="alert">${this.i18n.t('pin.pair_failure')}</div>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initApp(app, (app => {
|
||||
// this must be after mounting the app
|
||||
document.querySelector("#form").addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
let pin = document.querySelector("#pin-input").value;
|
||||
document.querySelector("#status").innerHTML = "";
|
||||
let b = JSON.stringify({ pin: pin });
|
||||
fetch("/api/pin", { method: "POST", body: b })
|
||||
.then((response) => response.json())
|
||||
.then((response) => {
|
||||
if (response.status.toString().toLowerCase() === "true") {
|
||||
document.querySelector(
|
||||
"#status"
|
||||
).innerHTML = `<div class="alert alert-success" role="alert">${i18n.global.t('pin.pair_success')}</div>`;
|
||||
document.querySelector("#pin-input").value = "";
|
||||
} else {
|
||||
document.querySelector(
|
||||
"#status"
|
||||
).innerHTML = `<div class="alert alert-danger" role="alert">${i18n.global.t('pin.pair_failure')}</div>`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
initApp(app);
|
||||
</script>
|
||||
|
@ -7,6 +7,7 @@
|
||||
"cancel": "Cancel",
|
||||
"disabled": "Disabled",
|
||||
"disabled_def": "Disabled (default)",
|
||||
"dismiss": "Dismiss",
|
||||
"do_cmd": "Do Command",
|
||||
"elevated": "Elevated",
|
||||
"enabled": "Enabled",
|
||||
@ -353,6 +354,7 @@
|
||||
"success_msg": "Password has been changed successfully! This page will reload soon, your browser will ask you for the new credentials."
|
||||
},
|
||||
"pin": {
|
||||
"device_name": "Device Name",
|
||||
"pair_failure": "Pairing Failed: Check if the PIN is typed correctly",
|
||||
"pair_success": "Success! Please check Moonlight to continue",
|
||||
"pin_pairing": "PIN Pairing",
|
||||
@ -382,9 +384,13 @@
|
||||
"restart_sunshine_success": "Sunshine is restarting",
|
||||
"troubleshooting": "Troubleshooting",
|
||||
"unpair_all": "Unpair All",
|
||||
"unpair_all_desc": "Remove all your paired devices",
|
||||
"unpair_all_error": "Error while unpairing",
|
||||
"unpair_all_success": "Unpair Successful!"
|
||||
"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": {
|
||||
"confirm_password": "Confirm password",
|
||||
|
@ -75,24 +75,39 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Unpair all Clients -->
|
||||
<div class="card p-2 my-4">
|
||||
<!-- Unpair Clients -->
|
||||
<div class="card my-4">
|
||||
<div class="card-body">
|
||||
<h2 id="unpair">{{ $t('troubleshooting.unpair_all') }}</h2>
|
||||
<br>
|
||||
<p>{{ $t('troubleshooting.unpair_all_desc') }}</p>
|
||||
<div class="alert alert-success" v-if="unpairAllStatus === true">
|
||||
{{ $t('troubleshooting.unpair_all_success') }}
|
||||
</div>
|
||||
<div class="alert alert-danger" v-if="unpairAllStatus === false">
|
||||
{{ $t('troubleshooting.unpair_all_error') }}
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-danger" :disabled="unpairAllPressed" @click="unpairAll">
|
||||
{{ $t('troubleshooting.unpair_all') }}
|
||||
</button>
|
||||
<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 -->
|
||||
<div class="card p-2 my-4">
|
||||
@ -123,14 +138,16 @@
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
clients: [],
|
||||
closeAppPressed: false,
|
||||
closeAppStatus: null,
|
||||
unpairAllPressed: false,
|
||||
unpairAllStatus: null,
|
||||
restartPressed: false,
|
||||
logs: 'Loading...',
|
||||
logFilter: null,
|
||||
logInterval: null,
|
||||
restartPressed: false,
|
||||
showApplyMessage: false,
|
||||
unpairAllPressed: false,
|
||||
unpairAllStatus: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -146,6 +163,7 @@
|
||||
this.refreshLogs();
|
||||
}, 5000);
|
||||
this.refreshLogs();
|
||||
this.refreshClients();
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.logInterval);
|
||||
@ -172,7 +190,7 @@
|
||||
},
|
||||
unpairAll() {
|
||||
this.unpairAllPressed = true;
|
||||
fetch("/api/clients/unpair", { method: "POST" })
|
||||
fetch("/api/clients/unpair-all", { method: "POST" })
|
||||
.then((r) => r.json())
|
||||
.then((r) => {
|
||||
this.unpairAllPressed = false;
|
||||
@ -180,8 +198,32 @@
|
||||
setTimeout(() => {
|
||||
this.unpairAllStatus = null;
|
||||
}, 5000);
|
||||
this.refreshClients();
|
||||
});
|
||||
},
|
||||
unpairSingle(uuid) {
|
||||
fetch("/api/clients/unpair", { method: "POST", body: JSON.stringify({ uuid }) }).then(() => {
|
||||
this.showApplyMessage = true;
|
||||
this.refreshClients();
|
||||
});
|
||||
},
|
||||
refreshClients() {
|
||||
fetch("/api/clients/list")
|
||||
.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() {
|
||||
navigator.clipboard.writeText(this.actualLogs);
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user