forked from Ocean/datum_gateway
api: Configuration modification support (if enabled in conf file)
This commit is contained in:
parent
befc30777c
commit
85fd02c5e1
@ -27,6 +27,9 @@ set(WEB_RESOURCES
|
|||||||
www/home.html
|
www/home.html
|
||||||
www/clients_top.html
|
www/clients_top.html
|
||||||
www/coinbaser_top.html
|
www/coinbaser_top.html
|
||||||
|
www/config.html
|
||||||
|
www/config_errors.html
|
||||||
|
www/config_restart.html
|
||||||
www/threads_top.html
|
www/threads_top.html
|
||||||
www/foot.html
|
www/foot.html
|
||||||
www/assets/style.css
|
www/assets/style.css
|
||||||
|
568
src/datum_api.c
568
src/datum_api.c
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <microhttpd.h>
|
#include <microhttpd.h>
|
||||||
@ -49,6 +50,8 @@
|
|||||||
#include "datum_api.h"
|
#include "datum_api.h"
|
||||||
#include "datum_blocktemplates.h"
|
#include "datum_blocktemplates.h"
|
||||||
#include "datum_conf.h"
|
#include "datum_conf.h"
|
||||||
|
#include "datum_gateway.h"
|
||||||
|
#include "datum_jsonrpc.h"
|
||||||
#include "datum_utils.h"
|
#include "datum_utils.h"
|
||||||
#include "datum_stratum.h"
|
#include "datum_stratum.h"
|
||||||
#include "datum_sockets.h"
|
#include "datum_sockets.h"
|
||||||
@ -853,6 +856,564 @@ int datum_api_client_dashboard(struct MHD_Connection *connection) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t datum_api_fill_config_var(const char *var_start, const size_t var_name_len, char * const replacement, const size_t replacement_max_len, const T_DATUM_API_DASH_VARS * const vardata) {
|
||||||
|
const char *colon_pos = memchr(var_start, ':', var_name_len);
|
||||||
|
const char *var_start_2 = colon_pos ? &colon_pos[1] : var_start;
|
||||||
|
const char * const var_end = &var_start[var_name_len];
|
||||||
|
const size_t var_name_len_2 = var_end - var_start_2;
|
||||||
|
const char * const underscore_pos = memchr(var_start_2, '_', var_name_len_2);
|
||||||
|
int val;
|
||||||
|
if (var_name_len_2 == 3 && 0 == strncmp(var_start_2, "*ro", 3)) {
|
||||||
|
val = !datum_config.api_modify_conf;
|
||||||
|
if (!colon_pos) {
|
||||||
|
var_start = "readonly:";
|
||||||
|
colon_pos = &var_start[8];
|
||||||
|
}
|
||||||
|
} else if (var_name_len_2 == 24 && 0 == strncmp(var_start_2, "*datum_pool_pass_workers", 24)) {
|
||||||
|
val = datum_config.datum_pool_pass_workers && !datum_config.datum_pool_pass_full_users;
|
||||||
|
} else if (var_name_len_2 == 16 && 0 == strncmp(var_start_2, "*datum_pool_host", 16)) {
|
||||||
|
const char *s = NULL;
|
||||||
|
if (datum_config.datum_pool_host[0]) {
|
||||||
|
s = datum_config.datum_pool_host;
|
||||||
|
} else if (datum_config.config_json) {
|
||||||
|
const json_t * const config = datum_config.config_json;
|
||||||
|
json_t *j = json_object_get(config, "datum");
|
||||||
|
if (j) j = json_is_object(j) ? json_object_get(j, "pool_host(old)") : NULL;
|
||||||
|
if (j && json_is_string(j) && json_string_length(j) <= 1023) {
|
||||||
|
s = json_string_value(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!s) {
|
||||||
|
const T_DATUM_CONFIG_ITEM * const cfginfo = datum_config_get_option_info("datum", 5, "pool_host", 9);
|
||||||
|
s = cfginfo->default_string[0];
|
||||||
|
}
|
||||||
|
size_t copy_sz = strlen(s);
|
||||||
|
if (copy_sz >= replacement_max_len) copy_sz = replacement_max_len - 1;
|
||||||
|
memcpy(replacement, s, copy_sz);
|
||||||
|
return copy_sz;
|
||||||
|
} else if (var_name_len_2 == 27 && 0 == strncmp(var_start_2, "*username_behaviour_private", 27)) {
|
||||||
|
val = !(datum_config.datum_pool_pass_workers || datum_config.datum_pool_pass_full_users);
|
||||||
|
} else if (var_name_len_2 == 22 && 0 == strncmp(var_start_2, "*reward_sharing_prefer", 22)) {
|
||||||
|
val = (!datum_config.datum_pooled_mining_only) && datum_config.datum_pool_host[0];
|
||||||
|
} else if (var_name_len_2 == 21 && 0 == strncmp(var_start_2, "*reward_sharing_never", 21)) {
|
||||||
|
val = (!datum_config.datum_pooled_mining_only) && !datum_config.datum_pool_host[0];
|
||||||
|
} else if (var_name_len_2 == 34 && 0 == strncmp(var_start_2, "*mining_coinbase_tag_secondary_max", 34)) {
|
||||||
|
val = 88 - strlen(datum_config.mining_coinbase_tag_primary);
|
||||||
|
if (val > 60) val = 60;
|
||||||
|
} else if (var_name_len_2 == 11 && 0 == strncmp(var_start_2, "*CSRF_TOKEN", 11)) {
|
||||||
|
size_t copy_sz = strlen(datum_config.api_csrf_token);
|
||||||
|
if (copy_sz >= replacement_max_len) copy_sz = replacement_max_len - 1;
|
||||||
|
memcpy(replacement, datum_config.api_csrf_token, copy_sz);
|
||||||
|
return copy_sz;
|
||||||
|
} else if (underscore_pos) {
|
||||||
|
const T_DATUM_CONFIG_ITEM * const item = datum_config_get_option_info(var_start_2, underscore_pos - var_start_2, &underscore_pos[1], var_end - &underscore_pos[1]);
|
||||||
|
if (item) {
|
||||||
|
switch (item->var_type) {
|
||||||
|
case DATUM_CONF_INT: {
|
||||||
|
val = *((int *)item->ptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DATUM_CONF_BOOL: {
|
||||||
|
val = *((bool *)item->ptr);
|
||||||
|
if ((!colon_pos) && replacement_max_len > 5) {
|
||||||
|
const size_t len = val ? 4 : 5;
|
||||||
|
memcpy(replacement, val ? "true" : "false", len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DATUM_CONF_STRING: {
|
||||||
|
const char * const s = (char *)item->ptr;
|
||||||
|
if (colon_pos) {
|
||||||
|
DLOG_ERROR("%s: '%.*s' modifier not implemented for %s", __func__, (int)(colon_pos - var_start), var_start, "DATUM_CONF_STRING");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size_t copy_sz = strlen(s);
|
||||||
|
if (copy_sz >= replacement_max_len) copy_sz = replacement_max_len - 1;
|
||||||
|
memcpy(replacement, s, copy_sz);
|
||||||
|
return copy_sz;
|
||||||
|
}
|
||||||
|
case DATUM_CONF_STRING_ARRAY: {
|
||||||
|
DLOG_ERROR("%s: %s not implemented", __func__, "DATUM_CONF_STRING_ARRAY");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DLOG_ERROR("%s: '%.*s' not implemented", __func__, (int)(var_end - var_start_2), var_start_2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DLOG_ERROR("%s: '%.*s' not implemented", __func__, (int)(var_end - var_start_2), var_start_2);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(replacement_max_len > 0);
|
||||||
|
|
||||||
|
if (colon_pos) {
|
||||||
|
if (0 == strncmp(var_start, "readonly:", 9) || 0 == strncmp(var_start, "selected:", 9) || 0 == strncmp(var_start, "checked:", 8) || 0 == strncmp(var_start, "disabled:", 9)) {
|
||||||
|
size_t attr_len;
|
||||||
|
if (val) {
|
||||||
|
attr_len = colon_pos - var_start;
|
||||||
|
if (attr_len + 2 > replacement_max_len) attr_len = replacement_max_len - 2;
|
||||||
|
replacement[0] = ' ';
|
||||||
|
memcpy(&replacement[1], var_start, attr_len);
|
||||||
|
++attr_len;
|
||||||
|
} else {
|
||||||
|
attr_len = 0;
|
||||||
|
}
|
||||||
|
return attr_len;
|
||||||
|
} else if (0 == strncmp(var_start, "msg:", 4)) {
|
||||||
|
if (val) {
|
||||||
|
static const char * const msg = "<br/><em>Config file disallows editing</em>";
|
||||||
|
const size_t len = strlen(msg);
|
||||||
|
memcpy(replacement, msg, len);
|
||||||
|
return len;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DLOG_ERROR("%s: '%.*s' modifier not implemented", __func__, (int)(colon_pos - var_start), var_start);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return snprintf(replacement, replacement_max_len, "%d", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
int datum_api_config_dashboard(struct MHD_Connection *connection) {
|
||||||
|
struct MHD_Response *response;
|
||||||
|
size_t sz = 0, max_sz = 0;
|
||||||
|
int ret;
|
||||||
|
char *output = NULL;
|
||||||
|
|
||||||
|
max_sz = www_config_html_sz * 2;
|
||||||
|
output = malloc(max_sz);
|
||||||
|
if (!output) {
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
sz += datum_api_fill_vars(www_config_html, output, max_sz, datum_api_fill_config_var, NULL);
|
||||||
|
|
||||||
|
response = MHD_create_response_from_buffer(sz, output, MHD_RESPMEM_MUST_FREE);
|
||||||
|
MHD_add_response_header(response, "Content-Type", "text/html");
|
||||||
|
http_resp_prevent_caching(response);
|
||||||
|
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
|
||||||
|
MHD_destroy_response(response);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef JSON_PRESERVE_ORDER
|
||||||
|
#define JSON_PRESERVE_ORDER 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Only modifies config_json; writing is done later
|
||||||
|
void datum_api_json_modify_new(const char * const category, const char * const key, json_t * const val) {
|
||||||
|
json_t * const config = datum_config.config_json;
|
||||||
|
assert(config);
|
||||||
|
|
||||||
|
json_t *j = json_object_get(config, category);
|
||||||
|
if (!j) {
|
||||||
|
j = json_object();
|
||||||
|
json_object_set_new(config, category, j);
|
||||||
|
}
|
||||||
|
json_object_set_new(j, key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool datum_api_json_write() {
|
||||||
|
json_t * const config = datum_config.config_json;
|
||||||
|
assert(config);
|
||||||
|
assert(datum_gateway_config_filename);
|
||||||
|
|
||||||
|
char buf[0x100];
|
||||||
|
int rv = snprintf(buf, sizeof(buf) - 4, "%s", datum_gateway_config_filename);
|
||||||
|
assert(rv > 0);
|
||||||
|
strcpy(&buf[rv], ".new");
|
||||||
|
|
||||||
|
if (json_dump_file(config, buf, JSON_PRESERVE_ORDER | JSON_INDENT(4))) {
|
||||||
|
DLOG_ERROR("Failed to write new config to %s", buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (rename(buf, datum_gateway_config_filename)) {
|
||||||
|
DLOG_ERROR("Failed to rename new config %s to %s", buf, datum_gateway_config_filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DLOG_INFO("Wrote new config to %s", datum_gateway_config_filename);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct datum_api_config_set_status {
|
||||||
|
json_t *errors;
|
||||||
|
bool modified_config;
|
||||||
|
bool need_restart;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This does several steps:
|
||||||
|
// - If the value is unchanged, return true without doing anything
|
||||||
|
// - Validate the value without changing anything
|
||||||
|
// - Change the runtime dataum_config (and ensure it goes live)
|
||||||
|
// - Modify the config file
|
||||||
|
// If anything fails (including validation), errors is appended and false is returned
|
||||||
|
bool datum_api_config_set(const char * const key, const char * const val, struct datum_api_config_set_status * const status) {
|
||||||
|
json_t * const errors = status->errors;
|
||||||
|
if (0 == strcmp(key, "mining_pool_address")) {
|
||||||
|
if (0 == strcmp(val, datum_config.mining_pool_address)) return true;
|
||||||
|
unsigned char dummy[64];
|
||||||
|
if (!addr_2_output_script(val, &dummy[0], 64)) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Invalid Bitcoin Address"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strcpy(datum_config.mining_pool_address, val);
|
||||||
|
datum_api_json_modify_new("mining", "pool_address", json_string(val));
|
||||||
|
} else if (0 == strcmp(key, "username_behaviour")) {
|
||||||
|
if (0 == strcmp(val, "datum_pool_pass_full_users")) {
|
||||||
|
if (datum_config.datum_pool_pass_full_users) return true;
|
||||||
|
datum_config.datum_pool_pass_full_users = true;
|
||||||
|
// datum_pool_pass_workers doesn't matter with datum_pool_pass_full_users enabled
|
||||||
|
} else if (0 == strcmp(val, "datum_pool_pass_workers")) {
|
||||||
|
if (datum_config.datum_pool_pass_workers && !datum_config.datum_pool_pass_full_users) return true;
|
||||||
|
datum_config.datum_pool_pass_full_users = false;
|
||||||
|
datum_config.datum_pool_pass_workers = true;
|
||||||
|
} else if (0 == strcmp(val, "private")) {
|
||||||
|
if (!(datum_config.datum_pool_pass_workers || datum_config.datum_pool_pass_full_users)) return true;
|
||||||
|
datum_config.datum_pool_pass_full_users = false;
|
||||||
|
datum_config.datum_pool_pass_workers = false;
|
||||||
|
} else {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Invalid option for \"Send Miner Usernames To Pool\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
datum_api_json_modify_new("datum", "pool_pass_full_users", json_boolean(datum_config.datum_pool_pass_full_users));
|
||||||
|
if (!datum_config.datum_pool_pass_full_users) {
|
||||||
|
datum_api_json_modify_new("datum", "pool_pass_workers", json_boolean(datum_config.datum_pool_pass_workers));
|
||||||
|
}
|
||||||
|
} else if (0 == strcmp(key, "mining_coinbase_tag_secondary")) {
|
||||||
|
if (0 == strcmp(val, datum_config.mining_coinbase_tag_secondary)) return true;
|
||||||
|
size_t len_limit = 88 - strlen(datum_config.mining_coinbase_tag_primary);
|
||||||
|
if (len_limit > 60) len_limit = 60;
|
||||||
|
if (strlen(val) > len_limit) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Coinbase Tag is too long"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strcpy(datum_config.mining_coinbase_tag_secondary, val);
|
||||||
|
datum_api_json_modify_new("mining", "coinbase_tag_secondary", json_string(val));
|
||||||
|
} else if (0 == strcmp(key, "mining_coinbase_unique_id")) {
|
||||||
|
const int val_int = datum_atoi_strict(val, strlen(val));
|
||||||
|
if (val_int == datum_config.coinbase_unique_id) return true;
|
||||||
|
if (val_int > 65535 || val_int < 0) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Unique Gateway ID must be between 0 and 65535"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
datum_config.coinbase_unique_id = val_int;
|
||||||
|
datum_api_json_modify_new("mining", "coinbase_unique_id", json_integer(val_int));
|
||||||
|
} else if (0 == strcmp(key, "reward_sharing")) {
|
||||||
|
json_t * const config = datum_config.config_json;
|
||||||
|
assert(config);
|
||||||
|
|
||||||
|
bool want_datum_pool_host = false;
|
||||||
|
if (0 == strcmp(val, "require")) {
|
||||||
|
if (datum_config.datum_pool_host[0] && datum_config.datum_pooled_mining_only) return true;
|
||||||
|
datum_config.datum_pooled_mining_only = true;
|
||||||
|
want_datum_pool_host = true;
|
||||||
|
} else if (0 == strcmp(val, "prefer")) {
|
||||||
|
if (datum_config.datum_pool_host[0] && !datum_config.datum_pooled_mining_only) return true;
|
||||||
|
datum_config.datum_pooled_mining_only = false;
|
||||||
|
want_datum_pool_host = true;
|
||||||
|
} else if (0 == strcmp(val, "never")) {
|
||||||
|
if (!(datum_config.datum_pool_host[0] || datum_config.datum_pooled_mining_only)) return true;
|
||||||
|
datum_config.datum_pooled_mining_only = false;
|
||||||
|
datum_config.datum_pool_host[0] = '\0';
|
||||||
|
|
||||||
|
// Only copy the old value if it's in the config file
|
||||||
|
json_t *j = json_object_get(config, "datum");
|
||||||
|
if (j) j = json_is_object(j) ? json_object_get(j, "pool_host") : NULL;
|
||||||
|
if (j) {
|
||||||
|
datum_api_json_modify_new("datum", "pool_host(old)", json_incref(j));
|
||||||
|
}
|
||||||
|
|
||||||
|
datum_api_json_modify_new("datum", "pool_host", json_string_nocheck(""));
|
||||||
|
} else {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Invalid option for \"Collaborative reward sharing\""));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (want_datum_pool_host && !datum_config.datum_pool_host[0]) {
|
||||||
|
json_t *j = json_object_get(config, "datum");
|
||||||
|
if (j) j = json_is_object(j) ? json_object_get(j, "pool_host(old)") : NULL;
|
||||||
|
if (j && json_is_string(j) && json_string_length(j) <= 1023) {
|
||||||
|
strcpy(datum_config.datum_pool_host, json_string_value(j));
|
||||||
|
json_object_del(j, "pool_host(old)");
|
||||||
|
datum_api_json_modify_new("datum", "pool_host", json_string(datum_config.datum_pool_host));
|
||||||
|
} else {
|
||||||
|
const T_DATUM_CONFIG_ITEM * const cfginfo = datum_config_get_option_info("datum", 5, "pool_host", 9);
|
||||||
|
strcpy(datum_config.datum_pool_host, cfginfo->default_string[0]);
|
||||||
|
|
||||||
|
// Avoiding using null here because older versions handled it poorly
|
||||||
|
json_t *j = json_object_get(config, "datum");
|
||||||
|
if (j) json_object_del(j, "pool_host");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
datum_api_json_modify_new("datum", "pooled_mining_only", json_boolean(datum_config.datum_pooled_mining_only));
|
||||||
|
// TODO: apply change without restarting
|
||||||
|
status->need_restart = true;
|
||||||
|
} else if (0 == strcmp(key, "datum_pool_host")) {
|
||||||
|
if (0 == strcmp(val, datum_config.datum_pool_host)) return true;
|
||||||
|
if (strlen(val) > 1023) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Pool Host is too long"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (datum_config.datum_pool_host[0]) {
|
||||||
|
strcpy(datum_config.datum_pool_host, val);
|
||||||
|
datum_api_json_modify_new("datum", "pool_host", json_string(val));
|
||||||
|
// TODO: apply change without restarting
|
||||||
|
// TODO: switch pools smoother (keep old connection alive for share submissions until those jobs expire)
|
||||||
|
status->need_restart = true;
|
||||||
|
} else {
|
||||||
|
json_t * const config = datum_config.config_json;
|
||||||
|
assert(config);
|
||||||
|
json_t *j = json_object_get(config, "datum");
|
||||||
|
if (j) j = json_is_object(j) ? json_object_get(j, "pool_host(old)") : NULL;
|
||||||
|
const T_DATUM_CONFIG_ITEM * const cfginfo = datum_config_get_option_info("datum", 5, "pool_host", 9);
|
||||||
|
// Avoid setting the default host in the config file, unless something else was already there
|
||||||
|
if (0 != strcmp(val, cfginfo->default_string[0]) || json_is_string(j)) {
|
||||||
|
datum_api_json_modify_new("datum", "pool_host(old)", json_string(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (0 == strcmp(key, "datum_pool_port")) {
|
||||||
|
const int val_int = datum_atoi_strict(val, strlen(val));
|
||||||
|
if (val_int == datum_config.datum_pool_port) return true;
|
||||||
|
if (val_int > 65535 || val_int < 1) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Pool Port must be between 1 and 65535"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
datum_config.datum_pool_port = val_int;
|
||||||
|
datum_api_json_modify_new("datum", "pool_port", json_integer(val_int));
|
||||||
|
// TODO: apply change without restarting
|
||||||
|
// TODO: switch pools smoother (keep old connection alive for share submissions until those jobs expire)
|
||||||
|
status->need_restart = true;
|
||||||
|
} else if (0 == strcmp(key, "datum_pool_pubkey")) {
|
||||||
|
if (0 == strcmp(val, datum_config.datum_pool_pubkey)) return true;
|
||||||
|
if (strlen(val) > 1023) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Pool Pubkey is too long"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strcpy(datum_config.datum_pool_pubkey, val);
|
||||||
|
datum_api_json_modify_new("datum", "pool_pubkey", json_string(val));
|
||||||
|
// TODO: apply change without restarting
|
||||||
|
// TODO: switch pools smoother (keep old connection alive for share submissions until those jobs expire)
|
||||||
|
status->need_restart = true;
|
||||||
|
} else if (0 == strcmp(key, "stratum_fingerprint_miners")) {
|
||||||
|
bool val_bool;
|
||||||
|
if (!datum_str_to_bool_strict(val, &val_bool)) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("\"Fingerprint and workaround known miner bugs\" must be 0 or 1"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (val_bool == datum_config.stratum_v1_fingerprint_miners) return true;
|
||||||
|
datum_config.stratum_v1_fingerprint_miners = val_bool;
|
||||||
|
datum_api_json_modify_new("stratum", "fingerprint_miners", json_boolean(val_bool));
|
||||||
|
// TODO: apply change to connected miners?
|
||||||
|
} else if (0 == strcmp(key, "datum_always_pay_self")) {
|
||||||
|
bool val_bool;
|
||||||
|
if (!datum_str_to_bool_strict(val, &val_bool)) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("\"Always pay self\" must be 0 or 1"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (val_bool == datum_config.datum_always_pay_self) return true;
|
||||||
|
datum_config.datum_always_pay_self = val_bool;
|
||||||
|
datum_api_json_modify_new("datum", "always_pay_self", json_boolean(val_bool));
|
||||||
|
} else if (0 == strcmp(key, "bitcoind_work_update_seconds")) {
|
||||||
|
const int val_int = datum_atoi_strict(val, strlen(val));
|
||||||
|
if (val_int == datum_config.bitcoind_work_update_seconds) return true;
|
||||||
|
if (val_int > 120 || val_int < 5) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("bitcoind work update interval must be between 5 and 120"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
datum_config.bitcoind_work_update_seconds = val_int;
|
||||||
|
datum_api_json_modify_new("bitcoind", "work_update_seconds", json_integer(val_int));
|
||||||
|
if (datum_config.bitcoind_work_update_seconds >= datum_config.datum_protocol_global_timeout - 5) {
|
||||||
|
datum_config.datum_protocol_global_timeout = val_int + 5;
|
||||||
|
datum_api_json_modify_new("datum", "protocol_global_timeout", json_integer(val_int + 5));
|
||||||
|
}
|
||||||
|
// TODO: apply change without restarting (and don't interfere with existing jobs)
|
||||||
|
status->need_restart = true;
|
||||||
|
} else if (0 == strcmp(key, "bitcoind_rpcurl")) {
|
||||||
|
if (0 == strcmp(val, datum_config.bitcoind_rpcurl)) return true;
|
||||||
|
if (strlen(val) > 128) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("bitcoind RPC URL is too long"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strcpy(datum_config.bitcoind_rpcurl, val);
|
||||||
|
datum_api_json_modify_new("bitcoind", "rpcurl", json_string(val));
|
||||||
|
} else if (0 == strcmp(key, "bitcoind_rpcuser")) {
|
||||||
|
if (0 == strcmp(val, datum_config.bitcoind_rpcuser)) return true;
|
||||||
|
if (strlen(val) > 128) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("bitcoind RPC user is too long"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strcpy(datum_config.bitcoind_rpcuser, val);
|
||||||
|
datum_api_json_modify_new("bitcoind", "rpcuser", json_string(val));
|
||||||
|
update_rpc_auth(&datum_config);
|
||||||
|
} else if (0 == strcmp(key, "bitcoind_rpcpassword")) {
|
||||||
|
if (0 == strcmp(val, datum_config.bitcoind_rpcpassword)) return true;
|
||||||
|
if (!val[0]) return true; // no password change
|
||||||
|
if (strlen(val) > 128) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("bitcoind RPC password is too long"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strcpy(datum_config.bitcoind_rpcpassword, val);
|
||||||
|
datum_api_json_modify_new("bitcoind", "rpcpassword", json_string(val));
|
||||||
|
update_rpc_auth(&datum_config);
|
||||||
|
} else {
|
||||||
|
char err[0x100];
|
||||||
|
snprintf(err, sizeof(err), "Unknown setting '%s'", key);
|
||||||
|
json_array_append_new(errors, json_string_nocheck(err));
|
||||||
|
DLOG_ERROR("%s: '%s' not implemented", __func__, key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
status->modified_config = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char datum_api_config_errors_fmt[] = "<div class='err'>%s</div>";
|
||||||
|
|
||||||
|
size_t datum_api_fill_config_errors(const char *var_start, const size_t var_name_len, char * const replacement, const size_t replacement_max_len, const T_DATUM_API_DASH_VARS * const vardata) {
|
||||||
|
const json_t * const errors = (void*)vardata;
|
||||||
|
size_t index, sz = 0;
|
||||||
|
json_t *j_it;
|
||||||
|
|
||||||
|
json_array_foreach(errors, index, j_it) {
|
||||||
|
sz += snprintf(&replacement[sz], replacement_max_len, datum_api_config_errors_fmt, json_string_value(j_it));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *datum_restart_thread(void *ptr) {
|
||||||
|
// Give logger some time
|
||||||
|
usleep(500000);
|
||||||
|
|
||||||
|
// Wait for the response to actually get delivered
|
||||||
|
// FIXME: css/svg/etc might fail (we don't support caching them yet)
|
||||||
|
struct MHD_Daemon * const mhd = ptr;
|
||||||
|
MHD_quiesce_daemon(mhd);
|
||||||
|
while (MHD_get_daemon_info(mhd, MHD_DAEMON_INFO_CURRENT_CONNECTIONS)->num_connections > 0) {
|
||||||
|
usleep(100);
|
||||||
|
}
|
||||||
|
MHD_stop_daemon(mhd);
|
||||||
|
|
||||||
|
datum_reexec();
|
||||||
|
abort(); // impossible to get here
|
||||||
|
}
|
||||||
|
|
||||||
|
int datum_api_config_post(struct MHD_Connection * const connection, char * const post, const int len) {
|
||||||
|
struct MHD_Response *response;
|
||||||
|
int ret;
|
||||||
|
const char *key;
|
||||||
|
json_t *j_it;
|
||||||
|
|
||||||
|
if (!datum_config.api_modify_conf) {
|
||||||
|
return datum_api_do_error(connection, MHD_HTTP_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t * const j = json_object();
|
||||||
|
if (!datum_api_formdata_to_json(connection, post, len, j)) {
|
||||||
|
json_decref(j);
|
||||||
|
return datum_api_do_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!datum_api_check_admin_password(connection, j)) {
|
||||||
|
json_decref(j);
|
||||||
|
return MHD_YES;
|
||||||
|
}
|
||||||
|
json_object_del(j, "csrf");
|
||||||
|
json_object_del(j, "password");
|
||||||
|
|
||||||
|
{
|
||||||
|
// Unchecked checkboxes are simply omitted, so a hidden field is used to convey them
|
||||||
|
const json_t * const j_checkboxes = json_object_get(j, "checkboxes");
|
||||||
|
const char * const checkboxes = json_string_value(j_checkboxes);
|
||||||
|
const size_t checkboxes_len = json_string_length(j_checkboxes);
|
||||||
|
const char *p = checkboxes;
|
||||||
|
char buf[0x100];
|
||||||
|
while (p[0] != '\0') {
|
||||||
|
const char *p2 = strchr(p, ' ');
|
||||||
|
if (!p2) p2 = &checkboxes[checkboxes_len];
|
||||||
|
const size_t i_len = p2 - p;
|
||||||
|
if (i_len < sizeof(buf)) {
|
||||||
|
memcpy(buf, p, i_len);
|
||||||
|
buf[i_len] = '\0';
|
||||||
|
|
||||||
|
json_t * const j_cb = json_object_get(j, buf);
|
||||||
|
if ((!j_cb) || json_is_null(j_cb)) {
|
||||||
|
json_object_set_new_nocheck(j, buf, json_string_nocheck("0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = *p2 ? &p2[1] : p2;
|
||||||
|
}
|
||||||
|
json_object_del(j, "checkboxes");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_t * const errors = json_array();
|
||||||
|
struct datum_api_config_set_status status = {
|
||||||
|
.errors = errors,
|
||||||
|
};
|
||||||
|
json_object_foreach(j, key, j_it) {
|
||||||
|
datum_api_config_set(key, json_string_value(j_it), &status);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_decref(j);
|
||||||
|
|
||||||
|
if (status.modified_config) {
|
||||||
|
if (!datum_api_json_write()) {
|
||||||
|
if (status.need_restart) {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Error writing new config file (changes will be lost)"));
|
||||||
|
} else {
|
||||||
|
json_array_append_new(errors, json_string_nocheck("Error writing new config file (changes will be lost at restart)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_array_size(errors) > 0) {
|
||||||
|
if (status.need_restart) {
|
||||||
|
json_array_insert_new(errors, 0, json_string_nocheck("NOTE: Other changes require a gateway restart. Please wait a few seconds before trying again."));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index, max_sz;
|
||||||
|
max_sz = www_config_errors_html_sz;
|
||||||
|
json_array_foreach(errors, index, j_it) {
|
||||||
|
max_sz += json_string_length(j_it) + sizeof(datum_api_config_errors_fmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
char * const output = malloc(max_sz);
|
||||||
|
if (!output) {
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
const size_t sz = datum_api_fill_vars(www_config_errors_html, output, max_sz, datum_api_fill_config_errors, (void*)errors);
|
||||||
|
|
||||||
|
response = MHD_create_response_from_buffer(sz, output, MHD_RESPMEM_MUST_FREE);
|
||||||
|
MHD_add_response_header(response, "Content-Type", "text/html");
|
||||||
|
http_resp_prevent_caching(response);
|
||||||
|
} else if (status.need_restart) {
|
||||||
|
response = MHD_create_response_from_buffer(www_config_restart_html_sz, (void*)www_config_restart_html, MHD_RESPMEM_PERSISTENT);
|
||||||
|
MHD_add_response_header(response, "Content-Type", "text/html");
|
||||||
|
http_resp_prevent_caching(response);
|
||||||
|
} else {
|
||||||
|
response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT);
|
||||||
|
http_resp_prevent_caching(response);
|
||||||
|
MHD_add_response_header(response, "Location", "/config");
|
||||||
|
}
|
||||||
|
json_decref(errors);
|
||||||
|
|
||||||
|
ret = MHD_queue_response(connection, MHD_HTTP_FOUND, response);
|
||||||
|
MHD_destroy_response(response);
|
||||||
|
|
||||||
|
if (status.need_restart) {
|
||||||
|
DLOG_INFO("Config change requires restarting gateway, proceeding");
|
||||||
|
struct MHD_Daemon * const mhd = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_DAEMON)->daemon;
|
||||||
|
pthread_t pthread_datum_restart_thread;
|
||||||
|
pthread_create(&pthread_datum_restart_thread, NULL, datum_restart_thread, mhd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
int datum_api_homepage(struct MHD_Connection *connection) {
|
int datum_api_homepage(struct MHD_Connection *connection) {
|
||||||
struct MHD_Response *response;
|
struct MHD_Response *response;
|
||||||
char output[DATUM_API_HOMEPAGE_MAX_SIZE];
|
char output[DATUM_API_HOMEPAGE_MAX_SIZE];
|
||||||
@ -1088,6 +1649,13 @@ enum MHD_Result datum_api_answer(void *cls, struct MHD_Connection *connection, c
|
|||||||
if (!strcmp(url, "/coinbaser")) {
|
if (!strcmp(url, "/coinbaser")) {
|
||||||
return datum_api_coinbaser(connection);
|
return datum_api_coinbaser(connection);
|
||||||
}
|
}
|
||||||
|
if (!strcmp(url, "/config")) {
|
||||||
|
if (int_method == 2 && con_info) {
|
||||||
|
return datum_api_config_post(connection, con_info->data, con_info->data_size);
|
||||||
|
} else {
|
||||||
|
return datum_api_config_dashboard(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
if ((int_method==2) && (!strcmp(url, "/cmd"))) {
|
if ((int_method==2) && (!strcmp(url, "/cmd"))) {
|
||||||
if (con_info) {
|
if (con_info) {
|
||||||
return datum_api_cmd(connection, con_info->data, con_info->data_size);
|
return datum_api_cmd(connection, con_info->data, con_info->data_size);
|
||||||
|
@ -404,8 +404,15 @@ void *datum_gateway_template_thread(void *args) {
|
|||||||
{
|
{
|
||||||
unsigned char dummy[64];
|
unsigned char dummy[64];
|
||||||
if (!addr_2_output_script(datum_config.mining_pool_address, &dummy[0], 64)) {
|
if (!addr_2_output_script(datum_config.mining_pool_address, &dummy[0], 64)) {
|
||||||
DLOG_FATAL("Could not generate output script for pool addr! Perhaps invalid? This is bad.");
|
if (datum_config.api_modify_conf) {
|
||||||
panic_from_thread(__LINE__);
|
DLOG_ERROR("Could not generate output script for pool addr! Perhaps invalid? Configure via API/dashboard.");
|
||||||
|
} else {
|
||||||
|
DLOG_FATAL("Could not generate output script for pool addr! Perhaps invalid? This is bad.");
|
||||||
|
panic_from_thread(__LINE__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!addr_2_output_script(datum_config.mining_pool_address, &dummy[0], 64)) {
|
||||||
|
usleep(50000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +111,8 @@ const T_DATUM_CONFIG_ITEM datum_config_options[] = {
|
|||||||
.required = false, .ptr = datum_config.api_admin_password, .default_string[0] = "", .max_string_len = sizeof(datum_config.api_admin_password) },
|
.required = false, .ptr = datum_config.api_admin_password, .default_string[0] = "", .max_string_len = sizeof(datum_config.api_admin_password) },
|
||||||
{ .var_type = DATUM_CONF_INT, .category = "api", .name = "listen_port", .description = "Port to listen for API/dashboard requests (0=disabled)",
|
{ .var_type = DATUM_CONF_INT, .category = "api", .name = "listen_port", .description = "Port to listen for API/dashboard requests (0=disabled)",
|
||||||
.required = false, .ptr = &datum_config.api_listen_port, .default_int = 0 },
|
.required = false, .ptr = &datum_config.api_listen_port, .default_int = 0 },
|
||||||
|
{ .var_type = DATUM_CONF_BOOL, .category = "api", .name = "modify_conf", .description = "Enable modifying the config file from API/dashboard",
|
||||||
|
.required = false, .ptr = &datum_config.api_modify_conf, .default_int = 0 },
|
||||||
|
|
||||||
// extra block submissions list
|
// extra block submissions list
|
||||||
{ .var_type = DATUM_CONF_STRING_ARRAY, .category = "extra_block_submissions", .name = "urls", .description = "Array of bitcoind RPC URLs to submit our blocks to directly. Include auth info: http://user:pass@IP",
|
{ .var_type = DATUM_CONF_STRING_ARRAY, .category = "extra_block_submissions", .name = "urls", .description = "Array of bitcoind RPC URLs to submit our blocks to directly. Include auth info: http://user:pass@IP",
|
||||||
@ -322,7 +324,9 @@ int datum_read_config(const char *conffile) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config) {
|
if (datum_config.api_modify_conf) {
|
||||||
|
datum_config.config_json = config;
|
||||||
|
} else {
|
||||||
json_decref(config);
|
json_decref(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <jansson.h>
|
||||||
|
|
||||||
#define DATUM_CONF_BOOL 1
|
#define DATUM_CONF_BOOL 1
|
||||||
#define DATUM_CONF_INT 2
|
#define DATUM_CONF_INT 2
|
||||||
#define DATUM_CONF_STRING 3
|
#define DATUM_CONF_STRING 3
|
||||||
@ -107,6 +109,8 @@ typedef struct {
|
|||||||
size_t api_admin_password_len;
|
size_t api_admin_password_len;
|
||||||
char api_csrf_token[65];
|
char api_csrf_token[65];
|
||||||
int api_listen_port;
|
int api_listen_port;
|
||||||
|
bool api_modify_conf;
|
||||||
|
json_t *config_json;
|
||||||
|
|
||||||
int extra_block_submissions_count;
|
int extra_block_submissions_count;
|
||||||
char extra_block_submissions_urls[DATUM_MAX_BLOCK_SUBMITS][DATUM_MAX_SUBMIT_URL_LEN];
|
char extra_block_submissions_urls[DATUM_MAX_BLOCK_SUBMITS][DATUM_MAX_SUBMIT_URL_LEN];
|
||||||
|
@ -250,6 +250,14 @@ bool update_rpc_cookie(global_config_t * const cfg) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update_rpc_auth(global_config_t * const cfg) {
|
||||||
|
if (datum_config.bitcoind_rpccookiefile[0] && !cfg->bitcoind_rpcuser[0]) {
|
||||||
|
update_rpc_cookie(cfg);
|
||||||
|
} else {
|
||||||
|
snprintf(datum_config.bitcoind_rpcuserpass, sizeof(datum_config.bitcoind_rpcuserpass), "%s:%s", datum_config.bitcoind_rpcuser, datum_config.bitcoind_rpcpassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
json_t *bitcoind_json_rpc_call(CURL * const curl, global_config_t * const cfg, const char * const rpc_req) {
|
json_t *bitcoind_json_rpc_call(CURL * const curl, global_config_t * const cfg, const char * const rpc_req) {
|
||||||
long http_resp_code = -1;
|
long http_resp_code = -1;
|
||||||
json_t *j = json_rpc_call_full(curl, cfg->bitcoind_rpcurl, cfg->bitcoind_rpcuserpass, rpc_req, NULL, &http_resp_code);
|
json_t *j = json_rpc_call_full(curl, cfg->bitcoind_rpcurl, cfg->bitcoind_rpcuserpass, rpc_req, NULL, &http_resp_code);
|
||||||
|
@ -66,6 +66,7 @@ struct upload_buffer {
|
|||||||
json_t *json_rpc_call(CURL *curl, const char *url, const char *userpass, const char *rpc_req);
|
json_t *json_rpc_call(CURL *curl, const char *url, const char *userpass, const char *rpc_req);
|
||||||
char *basic_http_call(CURL *curl, const char *url);
|
char *basic_http_call(CURL *curl, const char *url);
|
||||||
bool update_rpc_cookie(global_config_t *cfg);
|
bool update_rpc_cookie(global_config_t *cfg);
|
||||||
|
void update_rpc_auth(global_config_t *cfg);
|
||||||
json_t *bitcoind_json_rpc_call(CURL *curl, global_config_t *cfg, const char *rpc_req);
|
json_t *bitcoind_json_rpc_call(CURL *curl, global_config_t *cfg, const char *rpc_req);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "datum_gateway.h"
|
#include "datum_gateway.h"
|
||||||
#include "datum_logger.h"
|
#include "datum_logger.h"
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="menu-container">
|
<div class="menu-container">
|
||||||
<a href="/">Status</a>
|
<a href="/">Status</a>
|
||||||
|
<a href="/config">Config</a>
|
||||||
<a href="/clients" style="background-color: darkslategrey;">Clients</a>
|
<a href="/clients" style="background-color: darkslategrey;">Clients</a>
|
||||||
<a href="/threads">Threads</a>
|
<a href="/threads">Threads</a>
|
||||||
<a href="/coinbaser">Coinbaser</a>
|
<a href="/coinbaser">Coinbaser</a>
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="menu-container">
|
<div class="menu-container">
|
||||||
<a href="/">Status</a>
|
<a href="/">Status</a>
|
||||||
|
<a href="/config">Config</a>
|
||||||
<a href="/clients">Clients</a>
|
<a href="/clients">Clients</a>
|
||||||
<a href="/threads">Threads</a>
|
<a href="/threads">Threads</a>
|
||||||
<a href="/coinbaser" style="background-color: darkslategrey;">Coinbaser</a>
|
<a href="/coinbaser" style="background-color: darkslategrey;">Coinbaser</a>
|
||||||
|
224
www/config.html
Normal file
224
www/config.html
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>DATUM Gateway Configuration</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/assets/icons/favicon.ico">
|
||||||
|
<link rel="stylesheet" type="text/css" href="./assets/style.css">
|
||||||
|
<style type="text/css">
|
||||||
|
.table-wrapper {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #444;
|
||||||
|
color: #3498db;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
button[disabled] {
|
||||||
|
color: #777;
|
||||||
|
background-color: #400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.label {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #2a2a3b;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.flex-row input:not([type=checkbox]) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.flex-row input[type=checkbox] {
|
||||||
|
zoom: 2;
|
||||||
|
}
|
||||||
|
input,select {
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
input[readonly],input[disabled],select[readonly] {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
label.tip {
|
||||||
|
display: block;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
background-color: #2a2a3b;
|
||||||
|
font-size: 80%;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1><img src="/assets/icons/datum_logo.svg" alt="(DATUM Logo)" style="vertical-align: text-top" width="28" height="33"> DATUM <span>GATEWAY</span></h1>
|
||||||
|
</div>
|
||||||
|
<div class="menu-container">
|
||||||
|
<a href="/">Status</a>
|
||||||
|
<a href="/config" style="background-color: darkslategrey;">Config</a>
|
||||||
|
<a href="/clients">Clients</a>
|
||||||
|
<a href="/threads">Threads</a>
|
||||||
|
<a href="/coinbaser">Coinbaser</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action='/config' method='post'>
|
||||||
|
<input type='hidden' name='csrf' value='${*CSRF_TOKEN}' />
|
||||||
|
<div class="tables-container">
|
||||||
|
<div style="margin-top: 20px; text-align: center;">
|
||||||
|
<button${disabled:*ro}>Save</button>
|
||||||
|
${msg:*ro}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Basic</h2>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="mining_pool_address" class="label">Bitcoin Address:</label>
|
||||||
|
<input maxlength="100" name="mining_pool_address" id="mining_pool_address" value="${mining_pool_address}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
<label for="mining_pool_address" class="tip">Mining rewards will be received by this Bitcoin address, by default.</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="username_behaviour" class="label">Send Miner Usernames To Pool:</label>
|
||||||
|
<select name="username_behaviour" id="username_behaviour"${*ro}>
|
||||||
|
<option value="datum_pool_pass_full_users"${selected:datum_pool_pass_full_users}${disabled:*ro}>Override Bitcoin Address</option>
|
||||||
|
<option value="datum_pool_pass_workers"${selected:*datum_pool_pass_workers}${disabled:*ro}>Send as worker names</option>
|
||||||
|
<option value="private"${selected:*username_behaviour_private}${disabled:*ro}>Keep private</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label for="username_behaviour" class="tip">The username configured in miners can be handled a few different ways.</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="mining_coinbase_tag_secondary" class="label">Coinbase Tag:</label>
|
||||||
|
<input maxlength="${*mining_coinbase_tag_secondary_max}" name="mining_coinbase_tag_secondary" id="mining_coinbase_tag_secondary" value="${mining_coinbase_tag_secondary}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
<label for="coinbase_tag_secondary" id="coinbase_tag_secondary" class="tip">Arbitrary name displayed as the block creator on block explorers.</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="mining_coinbase_unique_id" class="label">Unique Gateway ID:</label>
|
||||||
|
<input maxlength="5" name="mining_coinbase_unique_id" id="mining_coinbase_unique_id" value="${mining_coinbase_unique_id}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
<label for="mining_coinbase_unique_id" class="tip">A number between 1 and 65535 that must be unique per Coinbase Tag.</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="reward_sharing" class="label">Collaborative reward sharing (pooled mining):</label>
|
||||||
|
<select name="reward_sharing" id="reward_sharing"${*ro}>
|
||||||
|
<option value="require"${selected:datum_pooled_mining_only}${disabled:*ro}>require (pooled mining only)</option>
|
||||||
|
<option value="prefer"${selected:*reward_sharing_prefer}${disabled:*ro}>prefer (failover to non-pooled)</option>
|
||||||
|
<option value="never"${selected:*reward_sharing_never}${disabled:*ro}>never (non-pooled only)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label for="reward_sharing" class="tip">You can share rewards and share in others' rewards - or only get rewarded when you find a block yourself.</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Pool</h2>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="datum_pool_host" class="label">Host:</label>
|
||||||
|
<input maxlength="1023" name="datum_pool_host" id="datum_pool_host" value="${*datum_pool_host}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="datum_pool_port" class="label">Port:</label>
|
||||||
|
<input maxlength="5" name="datum_pool_port" id="datum_pool_port" value="${datum_pool_port}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="datum_pool_pubkey" class="label">Pubkey:</label>
|
||||||
|
<input maxlength="1023" name="datum_pool_pubkey" id="datum_pool_pubkey" value="${datum_pool_pubkey}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Advanced</h2>
|
||||||
|
<input type="hidden" name="checkboxes" value="stratum_fingerprint_miners datum_always_pay_self"></input>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<input type="checkbox" name="stratum_fingerprint_miners" id="stratum_fingerprint_miners" value="1"${checked:stratum_fingerprint_miners}${disabled:*ro}></input>
|
||||||
|
<label for="stratum_fingerprint_miners" class="label" style="flex:1">Fingerprint and workaround known miner bugs</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<input type="checkbox" name="datum_always_pay_self" id="datum_always_pay_self" value="1"${checked:datum_always_pay_self}${disabled:*ro}></input>
|
||||||
|
<label for="datum_always_pay_self" class="label" style="flex:1">Always include your own "Bitcoin Address" above in generated payouts if possible</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="bitcoind_work_update_seconds" class="label">Typical interval between job updates:</label>
|
||||||
|
<input maxlength="3" name="bitcoind_work_update_seconds" id="bitcoind_work_update_seconds" value="${bitcoind_work_update_seconds}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
<label for="bitcoind_work_update_seconds" class="tip">5-120 seconds. 40 suggested.</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="bitcoind_rpcurl" class="label">bitcoind RPC URL:</label>
|
||||||
|
<input maxlength="128" name="bitcoind_rpcurl" id="bitcoind_rpcurl" value="${bitcoind_rpcurl}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="bitcoind_rpcuser" class="label">bitcoind RPC username:</label>
|
||||||
|
<input maxlength="128" name="bitcoind_rpcuser" id="bitcoind_rpcuser" value="${bitcoind_rpcuser}"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="flex-row">
|
||||||
|
<label for="bitcoind_rpcpassword" class="label">bitcoind RPC password:</label>
|
||||||
|
<input maxlength="128" name="bitcoind_rpcpassword" id="bitcoind_rpcpassword" placeholder="****************" type="password"${*ro}></input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 20px; text-align: center;">
|
||||||
|
<button${disabled:*ro}>Save</button>
|
||||||
|
${msg:*ro}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
72
www/config_errors.html
Normal file
72
www/config_errors.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>DATUM Gateway Configuration - ERROR</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/assets/icons/favicon.ico">
|
||||||
|
<link rel="stylesheet" type="text/css" href="./assets/style.css">
|
||||||
|
<style type="text/css">
|
||||||
|
.table-wrapper {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #444;
|
||||||
|
color: #3498db;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err {
|
||||||
|
border-bottom: 1px solid #444;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #2a2a3b;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.err:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1><img src="/assets/icons/datum_logo.svg" alt="(DATUM Logo)" style="vertical-align: text-top" width="28" height="33"> DATUM <span>GATEWAY</span></h1>
|
||||||
|
</div>
|
||||||
|
<div class="menu-container">
|
||||||
|
<a href="/">Status</a>
|
||||||
|
<a href="/config" style="background-color: darkslategrey;">Config</a>
|
||||||
|
<a href="/clients">Clients</a>
|
||||||
|
<a href="/threads">Threads</a>
|
||||||
|
<a href="/coinbaser">Coinbaser</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tables-container">
|
||||||
|
<script>
|
||||||
|
document.write('<div style="margin-top: 20px; text-align: center;"><button onclick="javascript:history.back(); return false;">Go Back</button></div>')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Errors Occurred</h2>
|
||||||
|
${*errors}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.write('<div style="margin-bottom: 20px; text-align: center;"><button onclick="javascript:history.back(); return false;">Go Back</button></div>')
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
74
www/config_restart.html
Normal file
74
www/config_restart.html
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>DATUM Gateway Configuration - Restarting</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/assets/icons/favicon.ico">
|
||||||
|
<link rel="stylesheet" type="text/css" href="./assets/style.css">
|
||||||
|
<style type="text/css">
|
||||||
|
.table-wrapper {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #444;
|
||||||
|
color: #3498db;
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #2a2a3b;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1><img src="/assets/icons/datum_logo.svg" alt="(DATUM Logo)" style="vertical-align: text-top" width="28" height="33"> DATUM <span>GATEWAY</span></h1>
|
||||||
|
</div>
|
||||||
|
<div class="menu-container">
|
||||||
|
<a href="/">Status</a>
|
||||||
|
<a href="/config" style="background-color: darkslategrey;">Config</a>
|
||||||
|
<a href="/clients">Clients</a>
|
||||||
|
<a href="/threads">Threads</a>
|
||||||
|
<a href="/coinbaser">Coinbaser</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action='/config' method='get' name="continue_form">
|
||||||
|
<div class="tables-container">
|
||||||
|
<div style="margin-top: 20px; text-align: center;">
|
||||||
|
<button>Continue</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Changes Successful</h2>
|
||||||
|
<div class="notice">
|
||||||
|
DATUM Gateway is restarting... Please wait a few seconds before continuing.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-bottom: 20px; text-align: center;">
|
||||||
|
<button>Continue</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
setTimeout(function() { document.continue_form.submit(); }, 10000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -35,6 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="menu-container">
|
<div class="menu-container">
|
||||||
<a href="/" style="background-color: darkslategrey;">Status</a>
|
<a href="/" style="background-color: darkslategrey;">Status</a>
|
||||||
|
<a href="/config">Config</a>
|
||||||
<a href="/clients">Clients</a>
|
<a href="/clients">Clients</a>
|
||||||
<a href="/threads">Threads</a>
|
<a href="/threads">Threads</a>
|
||||||
<a href="/coinbaser">Coinbaser</a>
|
<a href="/coinbaser">Coinbaser</a>
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="menu-container">
|
<div class="menu-container">
|
||||||
<a href="/">Status</a>
|
<a href="/">Status</a>
|
||||||
|
<a href="/config">Config</a>
|
||||||
<a href="/clients">Clients</a>
|
<a href="/clients">Clients</a>
|
||||||
<a href="/threads" style="background-color: darkslategrey;">Threads</a>
|
<a href="/threads" style="background-color: darkslategrey;">Threads</a>
|
||||||
<a href="/coinbaser">Coinbaser</a>
|
<a href="/coinbaser">Coinbaser</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user