Add UUID to apps w/ add option to use App's id for virtual display (resolves #48)
This commit is contained in:
@@ -395,38 +395,8 @@ namespace confighttp {
|
||||
pt::read_json(ss, inputTree);
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
|
||||
if (inputTree.get_child("prep-cmd").empty()) {
|
||||
inputTree.erase("prep-cmd");
|
||||
}
|
||||
proc::migrate_apps(&fileTree, &inputTree);
|
||||
|
||||
if (inputTree.get_child("detached").empty()) {
|
||||
inputTree.erase("detached");
|
||||
}
|
||||
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = inputTree.get<int>("index");
|
||||
|
||||
inputTree.erase("index");
|
||||
|
||||
if (index == -1) {
|
||||
apps_node.push_back(std::make_pair("", inputTree));
|
||||
}
|
||||
else {
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for (const auto &kv : apps_node) {
|
||||
if (i == index) {
|
||||
newApps.push_back(std::make_pair("", inputTree));
|
||||
}
|
||||
else {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
@@ -450,33 +420,38 @@ namespace confighttp {
|
||||
pt::ptree outputTree;
|
||||
auto g = util::fail_guard([&]() {
|
||||
std::ostringstream data;
|
||||
|
||||
pt::write_json(data, outputTree);
|
||||
response->write(data.str());
|
||||
});
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
if (
|
||||
args.find("uuid"s) == std::end(args)
|
||||
) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Missing a required launch parameter");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto uuid = nvhttp::get_arg(args, "uuid");
|
||||
|
||||
pt::ptree fileTree;
|
||||
try {
|
||||
pt::read_json(config::stream.file_apps, fileTree);
|
||||
auto &apps_node = fileTree.get_child("apps"s);
|
||||
int index = stoi(request->path_match[1]);
|
||||
|
||||
if (index < 0) {
|
||||
outputTree.put("status", "false");
|
||||
outputTree.put("error", "Invalid Index");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
int i = 0;
|
||||
for (const auto &kv : apps_node) {
|
||||
if (i++ != index) {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
// Unfortunately Boost PT does not allow to directly edit the array, copy should do the trick
|
||||
pt::ptree newApps;
|
||||
for (const auto &kv : apps_node) {
|
||||
auto app_uuid = kv.second.get_optional<std::string>("uuid"s);
|
||||
if (!app_uuid || app_uuid.value() != uuid) {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
fileTree.erase("apps");
|
||||
fileTree.push_back(std::make_pair("apps", newApps));
|
||||
|
||||
pt::write_json(config::stream.file_apps, fileTree);
|
||||
}
|
||||
catch (std::exception &e) {
|
||||
@@ -951,7 +926,7 @@ namespace confighttp {
|
||||
|
||||
auto args = request->parse_query_string();
|
||||
if (
|
||||
args.find("id"s) == std::end(args)
|
||||
args.find("uuid"s) == std::end(args)
|
||||
) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Missing a required launch parameter");
|
||||
@@ -959,41 +934,42 @@ namespace confighttp {
|
||||
return;
|
||||
}
|
||||
|
||||
auto idx_str = nvhttp::get_arg(args, "id");
|
||||
auto idx = util::from_view(idx_str);
|
||||
auto uuid = nvhttp::get_arg(args, "uuid");
|
||||
|
||||
const auto& apps = proc::proc.get_apps();
|
||||
|
||||
if (idx >= apps.size()) {
|
||||
BOOST_LOG(error) << "Couldn't find app with index ["sv << idx_str << ']';
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Cannot find requested application");
|
||||
return;
|
||||
for (auto& app : apps) {
|
||||
if (app.uuid == uuid) {
|
||||
auto appid = util::from_view(app.id);
|
||||
|
||||
crypto::named_cert_t named_cert {
|
||||
.name = "",
|
||||
.uuid = http::unique_id,
|
||||
.perm = crypto::PERM::_all,
|
||||
};
|
||||
|
||||
BOOST_LOG(info) << "Launching app ["sv << app.name << "] from web UI"sv;
|
||||
|
||||
auto launch_session = nvhttp::make_launch_session(true, appid, args, &named_cert);
|
||||
auto err = proc::proc.execute(appid, app, launch_session);
|
||||
if (err) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error",
|
||||
err == 503
|
||||
? "Failed to initialize video capture/encoding. Is a display connected and turned on?"
|
||||
: "Failed to start the specified application");
|
||||
return;
|
||||
} else {
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto& app = apps[idx];
|
||||
auto appid = util::from_view(app.id);
|
||||
|
||||
crypto::named_cert_t named_cert {
|
||||
.name = "",
|
||||
.uuid = http::unique_id,
|
||||
.perm = crypto::PERM::_all,
|
||||
};
|
||||
|
||||
BOOST_LOG(info) << "Launching app ["sv << app.name << "] from web UI"sv;
|
||||
|
||||
auto launch_session = nvhttp::make_launch_session(true, appid, args, &named_cert);
|
||||
auto err = proc::proc.execute(appid, app, launch_session);
|
||||
if (err) {
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error",
|
||||
err == 503
|
||||
? "Failed to initialize video capture/encoding. Is a display connected and turned on?"
|
||||
: "Failed to start the specified application");
|
||||
return;
|
||||
} else {
|
||||
outputTree.put("status", true);
|
||||
}
|
||||
BOOST_LOG(error) << "Couldn't find app with uuid ["sv << uuid << ']';
|
||||
outputTree.put("status", false);
|
||||
outputTree.put("error", "Cannot find requested application");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1086,22 +1062,22 @@ namespace confighttp {
|
||||
server.resource["^/api/pin$"]["POST"] = savePin;
|
||||
server.resource["^/api/otp$"]["GET"] = getOTP;
|
||||
server.resource["^/api/apps$"]["GET"] = getApps;
|
||||
server.resource["^/api/logs$"]["GET"] = getLogs;
|
||||
server.resource["^/api/apps$"]["POST"] = saveApp;
|
||||
server.resource["^/api/apps/delete$"]["POST"] = deleteApp;
|
||||
server.resource["^/api/apps/launch$"]["POST"] = launchApp;
|
||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||
server.resource["^/api/logs$"]["GET"] = getLogs;
|
||||
server.resource["^/api/config$"]["GET"] = getConfig;
|
||||
server.resource["^/api/config$"]["POST"] = saveConfig;
|
||||
server.resource["^/api/configLocale$"]["GET"] = getLocale;
|
||||
server.resource["^/api/restart$"]["POST"] = restart;
|
||||
server.resource["^/api/quit$"]["POST"] = quit;
|
||||
server.resource["^/api/password$"]["POST"] = savePassword;
|
||||
server.resource["^/api/apps/([0-9]+)$"]["DELETE"] = deleteApp;
|
||||
server.resource["^/api/clients/unpair-all$"]["POST"] = unpairAll;
|
||||
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/disconnect$"]["POST"] = disconnect;
|
||||
server.resource["^/api/apps/launch$"]["POST"] = launchApp;
|
||||
server.resource["^/api/apps/close$"]["POST"] = closeApp;
|
||||
server.resource["^/api/covers/upload$"]["POST"] = uploadCover;
|
||||
server.resource["^/images/apollo.ico$"]["GET"] = getFaviconImage;
|
||||
server.resource["^/images/logo-apollo-45.png$"]["GET"] = getSunshineLogoImage;
|
||||
|
||||
@@ -211,13 +211,16 @@ namespace proc {
|
||||
VDISPLAY::setRenderAdapterByName(platf::from_utf8(config::video.adapter_name));
|
||||
}
|
||||
|
||||
auto device_uuid = uuid_util::uuid_t::parse(launch_session->unique_id);
|
||||
auto device_uuid_str = _app.use_app_identity ? _app.uuid : launch_session->unique_id;
|
||||
auto device_name = _app.use_app_identity ? _app.name : launch_session->device_name;
|
||||
|
||||
auto device_uuid = uuid_util::uuid_t::parse(device_uuid_str);
|
||||
|
||||
memcpy(&launch_session->display_guid, &device_uuid, sizeof(GUID));
|
||||
|
||||
std::wstring vdisplayName = VDISPLAY::createVirtualDisplay(
|
||||
launch_session->unique_id.c_str(),
|
||||
launch_session->device_name.c_str(),
|
||||
device_uuid_str.c_str(),
|
||||
device_name.c_str(),
|
||||
render_width,
|
||||
render_height,
|
||||
launch_session->fps ? launch_session->fps : 60,
|
||||
@@ -794,6 +797,63 @@ namespace proc {
|
||||
return std::make_tuple(id_no_index, id_with_index);
|
||||
}
|
||||
|
||||
void
|
||||
migrate_apps(pt::ptree* fileTree_p, pt::ptree* inputTree_p) {
|
||||
std::string new_app_uuid;
|
||||
|
||||
if (inputTree_p) {
|
||||
auto inputTree = *inputTree_p;
|
||||
auto input_uuid = inputTree_p->get_optional<std::string>("uuid"s);
|
||||
if (input_uuid && !input_uuid.value().empty()) {
|
||||
new_app_uuid = input_uuid.value();
|
||||
} else {
|
||||
new_app_uuid = uuid_util::uuid_t::generate().string();
|
||||
inputTree_p->erase("uuid");
|
||||
inputTree_p->put("uuid", new_app_uuid);
|
||||
}
|
||||
|
||||
if (inputTree_p->get_child("prep-cmd").empty()) {
|
||||
inputTree_p->erase("prep-cmd");
|
||||
}
|
||||
|
||||
if (inputTree_p->get_child("detached").empty()) {
|
||||
inputTree_p->erase("detached");
|
||||
}
|
||||
|
||||
inputTree_p->erase("launching");
|
||||
inputTree_p->erase("index");
|
||||
}
|
||||
|
||||
auto &apps_node = fileTree_p->get_child("apps"s);
|
||||
|
||||
pt::ptree newApps;
|
||||
for (auto &kv : apps_node) {
|
||||
// Check if we have apps that have not got an uuid assigned
|
||||
auto app_uuid = kv.second.get_optional<std::string>("uuid"s);
|
||||
if (!app_uuid || app_uuid.value().empty()) {
|
||||
kv.second.erase("uuid");
|
||||
kv.second.put("uuid", uuid_util::uuid_t::generate().string());
|
||||
kv.second.erase("launching");
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
} else {
|
||||
if (!new_app_uuid.empty() && app_uuid.value() == new_app_uuid) {
|
||||
newApps.push_back(std::make_pair("", *inputTree_p));
|
||||
new_app_uuid.clear();
|
||||
} else {
|
||||
newApps.push_back(std::make_pair("", kv.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally add the new app
|
||||
if (!new_app_uuid.empty()) {
|
||||
newApps.push_back(std::make_pair("", *inputTree_p));
|
||||
}
|
||||
|
||||
fileTree_p->erase("apps");
|
||||
fileTree_p->push_back(std::make_pair("apps", newApps));
|
||||
}
|
||||
|
||||
std::optional<proc::proc_t>
|
||||
parse(const std::string &file_name) {
|
||||
pt::ptree tree;
|
||||
@@ -816,6 +876,20 @@ namespace proc {
|
||||
for (auto &[_, app_node] : apps_node) {
|
||||
proc::ctx_t ctx;
|
||||
|
||||
auto app_uuid = app_node.get_optional<std::string>("uuid"s);
|
||||
|
||||
if (!app_uuid) {
|
||||
// We need an upgrade to the app list
|
||||
try {
|
||||
migrate_apps(&tree, nullptr);
|
||||
pt::write_json(file_name, tree);
|
||||
return parse(file_name);
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG(warning) << "Error happened wilie migrating the app list: "sv << e.what();
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
auto prep_nodes_opt = app_node.get_child_optional("prep-cmd"s);
|
||||
auto detached_nodes_opt = app_node.get_child_optional("detached"s);
|
||||
auto exclude_global_prep = app_node.get_optional<bool>("exclude-global-prep-cmd"s);
|
||||
@@ -831,6 +905,9 @@ namespace proc {
|
||||
auto virtual_display = app_node.get_optional<bool>("virtual-display"s);
|
||||
auto virtual_display_primary = app_node.get_optional<bool>("virtual-display-primary"s);
|
||||
auto resolution_scale_factor = app_node.get_optional<int>("scale-factor"s);
|
||||
auto use_app_identity = app_node.get_optional<bool>("use-app-identity"s);
|
||||
|
||||
ctx.uuid = app_uuid.value();
|
||||
|
||||
std::vector<proc::cmd_t> prep_cmds;
|
||||
if (!exclude_global_prep.value_or(false)) {
|
||||
@@ -901,6 +978,7 @@ namespace proc {
|
||||
ctx.virtual_display = virtual_display.value_or(false);
|
||||
ctx.virtual_display_primary = virtual_display_primary.value_or(true);
|
||||
ctx.scale_factor = resolution_scale_factor.value_or(100);
|
||||
ctx.use_app_identity = use_app_identity.value_or(false);
|
||||
|
||||
auto possible_ids = calculate_app_id(name, ctx.image_path, i++);
|
||||
if (ids.count(std::get<0>(possible_ids)) == 0) {
|
||||
@@ -923,11 +1001,13 @@ namespace proc {
|
||||
#ifdef _WIN32
|
||||
if (vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK) {
|
||||
proc::ctx_t ctx;
|
||||
// ctx.uuid = ""; // We're not using uuid for this special entry
|
||||
ctx.name = "Virtual Display";
|
||||
ctx.image_path = parse_env_val(this_env, "virtual_desktop.png");
|
||||
ctx.virtual_display = true;
|
||||
ctx.virtual_display_primary = true;
|
||||
ctx.scale_factor = 100;
|
||||
ctx.use_app_identity = false;
|
||||
|
||||
ctx.elevated = false;
|
||||
ctx.auto_detach = true;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include "config.h"
|
||||
#include "platform/common.h"
|
||||
@@ -59,6 +60,7 @@ namespace proc {
|
||||
*/
|
||||
std::vector<std::string> detached;
|
||||
|
||||
std::string uuid;
|
||||
std::string name;
|
||||
std::string cmd;
|
||||
std::string working_dir;
|
||||
@@ -70,6 +72,7 @@ namespace proc {
|
||||
bool wait_all;
|
||||
bool virtual_display;
|
||||
bool virtual_display_primary;
|
||||
bool use_app_identity;
|
||||
int scale_factor;
|
||||
std::chrono::seconds exit_timeout;
|
||||
};
|
||||
@@ -150,6 +153,8 @@ namespace proc {
|
||||
validate_app_image_path(std::string app_image_path);
|
||||
void
|
||||
refresh(const std::string &file_name);
|
||||
void
|
||||
migrate_apps(boost::property_tree::ptree* fileTree_p, boost::property_tree::ptree* inputTree_p);
|
||||
std::optional<proc::proc_t>
|
||||
parse(const std::string &file_name);
|
||||
|
||||
|
||||
@@ -86,16 +86,16 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(app,i) in apps" :key="i">
|
||||
<tr v-for="(app,i) in apps" :key="app.uuid">
|
||||
<td>{{app.name}}</td>
|
||||
<td>
|
||||
<button class="btn btn-success me-2" :disabled="app.launching" @click="launchApp(i)">
|
||||
<button class="btn btn-success me-2" :disabled="app.launching" @click="launchApp(app)">
|
||||
<i class="fas fa-play"></i> {{ $t('apps.launch') }}
|
||||
</button>
|
||||
<button class="btn btn-primary me-2" :disabled="app.launching" @click="editApp(i)">
|
||||
<button class="btn btn-primary me-2" :disabled="app.launching" @click="editApp(app)">
|
||||
<i class="fas fa-edit"></i> {{ $t('apps.edit') }}
|
||||
</button>
|
||||
<button class="btn btn-danger" :disabled="app.launching" @click="showDeleteForm(i)">
|
||||
<button class="btn btn-danger" :disabled="app.launching" @click="showDeleteForm(app)">
|
||||
<i class="fas fa-trash"></i> {{ $t('apps.delete') }}
|
||||
</button>
|
||||
</td>
|
||||
@@ -274,16 +274,23 @@
|
||||
<div class="mb-3 form-check" v-if="platform === 'windows'">
|
||||
<label for="virtualDisplay" class="form-check-label">{{ $t('apps.virtual_display') }}</label>
|
||||
<input type="checkbox" class="form-check-input" id="virtualDisplay" v-model="editForm['virtual-display']"
|
||||
true-value="true" false-value="false" />
|
||||
true-value="true" false-value="false" />
|
||||
<div class="form-text">{{ $t('apps.virtual_display_desc') }}</div>
|
||||
</div>
|
||||
<!-- set virtual display to primary -->
|
||||
<div class="mb-3 form-check" v-if="platform === 'windows' && editForm['virtual-display'] == 'true'">
|
||||
<label for="virtualDisplayPrimary" class="form-check-label">{{ $t('apps.virtual_display_primary') }}</label>
|
||||
<input type="checkbox" class="form-check-input" id="virtualDisplayPrimary" v-model="editForm['virtual-display-primary']"
|
||||
true-value="true" false-value="false" />
|
||||
true-value="true" false-value="false" />
|
||||
<div class="form-text">{{ $t('apps.virtual_display_primary_desc') }}</div>
|
||||
</div>
|
||||
<!-- use app identity -->
|
||||
<div class="mb-3 form-check">
|
||||
<label for="useAppIdentity" class="form-check-label">{{ $t('apps.use_app_identity') }}</label>
|
||||
<input type="checkbox" class="form-check-input" id="useAppIdentity" v-model="editForm['use-app-identity']"
|
||||
true-value="true" false-value="false" />
|
||||
<div class="form-text">{{ $t('apps.use_app_identity_desc') }}</div>
|
||||
</div>
|
||||
<!-- resolution scale factor -->
|
||||
<div class="mb-3" v-if="platform === 'windows'">
|
||||
<label for="resolutionScaleFactor" class="form-label">{{ $t('apps.resolution_scale_factor') }}: {{editForm['scale-factor']}}%</label>
|
||||
@@ -392,6 +399,23 @@
|
||||
import PlatformLayout from './PlatformLayout.vue'
|
||||
import { Dropdown } from 'bootstrap/dist/js/bootstrap'
|
||||
|
||||
const newApp = {
|
||||
name: "",
|
||||
output: "",
|
||||
cmd: [],
|
||||
"exclude-global-prep-cmd": false,
|
||||
elevated: false,
|
||||
"auto-detach": true,
|
||||
"wait-all": true,
|
||||
"exit-timeout": 5,
|
||||
"prep-cmd": [],
|
||||
detached: [],
|
||||
"image-path": "",
|
||||
"virtual-display-primary": true,
|
||||
"scale-factor": "100",
|
||||
"use-app-identity": false
|
||||
}
|
||||
|
||||
const app = createApp({
|
||||
components: {
|
||||
Navbar,
|
||||
@@ -429,30 +453,13 @@
|
||||
});
|
||||
},
|
||||
newApp() {
|
||||
this.editForm = {
|
||||
name: "",
|
||||
output: "",
|
||||
cmd: [],
|
||||
index: -1,
|
||||
"exclude-global-prep-cmd": false,
|
||||
elevated: false,
|
||||
"auto-detach": true,
|
||||
"wait-all": true,
|
||||
"exit-timeout": 5,
|
||||
"prep-cmd": [],
|
||||
detached: [],
|
||||
"image-path": "",
|
||||
"virtual-display-primary": true,
|
||||
"scale-factor": "100"
|
||||
};
|
||||
this.editForm.index = -1;
|
||||
this.editForm = Object.assign({}, newApp);
|
||||
this.showEditForm = true;
|
||||
},
|
||||
launchApp(id) {
|
||||
const app = this.apps[id];
|
||||
launchApp(app) {
|
||||
if (confirm(this.$t('apps.launch_warning'))) {
|
||||
app.launching = true;
|
||||
fetch("/api/apps/launch?id=" + id, {
|
||||
fetch("/api/apps/launch?uuid=" + app.uuid, {
|
||||
credentials: 'include',
|
||||
method: "POST",
|
||||
})
|
||||
@@ -467,56 +474,31 @@
|
||||
.finally(() => app.launching = false);
|
||||
}
|
||||
},
|
||||
editApp(id) {
|
||||
this.editForm = JSON.parse(JSON.stringify(this.apps[id]));
|
||||
this.editForm.index = id;
|
||||
if (this.editForm["prep-cmd"] === undefined)
|
||||
this.editForm["prep-cmd"] = [];
|
||||
if (this.editForm["detached"] === undefined)
|
||||
this.editForm["detached"] = [];
|
||||
if (this.editForm["exclude-global-prep-cmd"] === undefined)
|
||||
this.editForm["exclude-global-prep-cmd"] = [];
|
||||
if (this.editForm["elevated"] === undefined && this.platform === 'windows') {
|
||||
this.editForm["elevated"] = [];
|
||||
}
|
||||
if (this.editForm["auto-detach"] === undefined) {
|
||||
this.editForm["auto-detach"] = true;
|
||||
}
|
||||
if (this.editForm["wait-all"] === undefined) {
|
||||
this.editForm["wait-all"] = true;
|
||||
}
|
||||
if (this.editForm["exit-timeout"] === undefined) {
|
||||
this.editForm["exit-timeout"] = 5;
|
||||
}
|
||||
if (typeof this.editForm["virtual-display-primary"] === "undefined") {
|
||||
this.editForm["virtual-display-primary"] = true;
|
||||
}
|
||||
if (typeof this.editForm["scale-factor"] === "undefined") {
|
||||
this.editForm["scale-factor"] = "100"
|
||||
}
|
||||
editApp(app) {
|
||||
this.editForm = Object.assign({}, newApp, JSON.parse(JSON.stringify(app)));
|
||||
this.showEditForm = true;
|
||||
},
|
||||
showDeleteForm(id) {
|
||||
let resp = confirm(
|
||||
"Are you sure to delete " + this.apps[id].name + "?"
|
||||
showDeleteForm(app) {
|
||||
const resp = confirm(
|
||||
"Are you sure to delete " + app.name + "?"
|
||||
);
|
||||
if (resp) {
|
||||
fetch("/api/apps/" + id, {
|
||||
fetch("/api/apps/delete?uuid=" + app.uuid, {
|
||||
credentials: 'include',
|
||||
method: "DELETE"
|
||||
method: "POST"
|
||||
}).then((r) => {
|
||||
if (r.status == 200) document.location.reload();
|
||||
});
|
||||
}
|
||||
},
|
||||
addPrepCmd() {
|
||||
let template = {
|
||||
const template = {
|
||||
do: "",
|
||||
undo: ""
|
||||
};
|
||||
|
||||
if (this.platform === 'windows') {
|
||||
template = { ...template, elevated: false };
|
||||
template.elevated = false;
|
||||
}
|
||||
|
||||
this.editForm["prep-cmd"].push(template);
|
||||
@@ -618,6 +600,8 @@
|
||||
},
|
||||
save() {
|
||||
this.editForm["image-path"] = this.editForm["image-path"].toString().replace(/"/g, '');
|
||||
delete this.editForm["launching"];
|
||||
delete this.editForm["id"];
|
||||
fetch("/api/apps", {
|
||||
credentials: 'include',
|
||||
method: "POST",
|
||||
|
||||
@@ -427,9 +427,11 @@
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
location.reload();
|
||||
return
|
||||
console.error(e);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000);
|
||||
return;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
"virtual_display_primary": "Enforce Virtual Display Primary",
|
||||
"virtual_display_primary_desc": "Automatically set the virtual display as primary display when the app starts. Virtual display will always be set to primary when client requests to use virtual display. (Recommended to keep on)",
|
||||
"resolution_scale_factor": "Resolution Scale Factor",
|
||||
"resolution_scale_factor_desc": "Scale the client requested resolution based on this factor. e.g. 2000x1000 with a factor of 120% will become 2400x1200. Overrides client requested factor when the number isn't 100%. This option won't affect client requested streaming resolution."
|
||||
"resolution_scale_factor_desc": "Scale the client requested resolution based on this factor. e.g. 2000x1000 with a factor of 120% will become 2400x1200. Overrides client requested factor when the number isn't 100%. This option won't affect client requested streaming resolution.",
|
||||
"use_app_identity": "Use App Identity",
|
||||
"use_app_identity_desc": "Use the app's own identity while creating virtual displays instead of client's. This is useful when you want display configuration for each APP separately."
|
||||
},
|
||||
"client_card": {
|
||||
"clients": "Clients",
|
||||
|
||||
@@ -86,11 +86,13 @@
|
||||
"working_dir": "工作目录",
|
||||
"working_dir_desc": "应传递给进程的工作目录。例如,某些应用程序使用工作目录搜索配置文件。如果不设置,Apollo 将默认使用命令的父目录",
|
||||
"virtual_display": "总是使用虚拟显示器",
|
||||
"virtual_display_desc": "在使用这个App的时候总是使用虚拟显示器,覆盖客户端请求。请确保 SudoVDA 虚拟显示器驱动已安装并启用。",
|
||||
"virtual_display_desc": "在使用这个 App 的时候总是使用虚拟显示器,覆盖客户端请求。请确保 SudoVDA 虚拟显示器驱动已安装并启用。",
|
||||
"virtual_display_primary": "强制设置虚拟显示器为主显示器",
|
||||
"virtual_display_primary_desc": "在App启动时强制将虚拟显示器设为主显示器。当客户端请求使用虚拟显示器时将无条件设为主显示器。(覆盖全局设置,推荐保持开启)",
|
||||
"virtual_display_primary_desc": "在 App 启动时强制将虚拟显示器设为主显示器。当客户端请求使用虚拟显示器时将无条件设为主显示器。(覆盖全局设置,推荐保持开启)",
|
||||
"resolution_scale_factor": "分辨率缩放比例",
|
||||
"resolution_scale_factor_desc": "基于此比例缩放客户端请求的分辨率。例如 2000x1000 缩放 120% 将变成 2400x1200。当此项为非 100% 时覆盖客户端请求的缩放比例。此选项不会影响客户端请求的串流分辨率。"
|
||||
"resolution_scale_factor_desc": "基于此比例缩放客户端请求的分辨率。例如 2000x1000 缩放 120% 将变成 2400x1200。当此项为非 100% 时覆盖客户端请求的缩放比例。此选项不会影响客户端请求的串流分辨率。",
|
||||
"use_app_identity": "使用 App 身份",
|
||||
"use_app_identity_desc": "在创建虚拟显示器时使用 App 自身的身份,而非客户端的。这样可以针对 APP 进行单独的显示器组合配置。"
|
||||
},
|
||||
"client_card": {
|
||||
"clients": "客户端",
|
||||
|
||||
@@ -189,15 +189,19 @@
|
||||
})
|
||||
.then((resp) => {
|
||||
if (resp.status !== 200) {
|
||||
location.reload();
|
||||
return
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
this.serverRestarting = false
|
||||
console.error(e)
|
||||
location.reload();
|
||||
return
|
||||
console.error(e);
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000);
|
||||
return;
|
||||
});
|
||||
},
|
||||
quit() {
|
||||
|
||||
Reference in New Issue
Block a user