descrambler: add constcw DES & AES clients
This commit is contained in:
parent
e6b1d4ba2a
commit
4f7e9284af
11 changed files with 455 additions and 9 deletions
4
Makefile
4
Makefile
|
@ -296,6 +296,10 @@ SRCS-${CONFIG_CWC} += \
|
|||
SRCS-${CONFIG_CAPMT} += \
|
||||
src/descrambler/capmt.c
|
||||
|
||||
# CONSTCW
|
||||
SRCS-${CONFIG_CONSTCW} += \
|
||||
src/descrambler/constcw.c
|
||||
|
||||
# FFdecsa
|
||||
ifneq ($(CONFIG_DVBCSA),yes)
|
||||
FFDECSA-$(CONFIG_CAPMT) = yes
|
||||
|
|
3
configure
vendored
3
configure
vendored
|
@ -18,6 +18,7 @@ ROOTDIR=$(cd "$(dirname "$0")"; pwd)
|
|||
OPTIONS=(
|
||||
"cwc:yes"
|
||||
"capmt:yes"
|
||||
"constcw:yes"
|
||||
"v4l:no"
|
||||
"linuxdvb:yes"
|
||||
"satip_client:yes"
|
||||
|
@ -315,7 +316,7 @@ fi
|
|||
#
|
||||
# libdvbcsa, tvhcsa
|
||||
#
|
||||
if enabled cwc || enabled capmt; then
|
||||
if enabled cwc || enabled capmt || enabled constcw; then
|
||||
enable tvhcsa
|
||||
if enabled dvbcsa; then
|
||||
(check_cc_header "dvbcsa/dvbcsa" dvbcsa_h &&\
|
||||
|
|
|
@ -26,6 +26,10 @@ const idclass_t *caclient_classes[] = {
|
|||
#endif
|
||||
#if ENABLE_CAPMT
|
||||
&caclient_capmt_class,
|
||||
#endif
|
||||
#if ENABLE_CONSTCW
|
||||
&caclient_ccw_des_class,
|
||||
&caclient_ccw_aes_class,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
@ -97,6 +101,12 @@ caclient_create
|
|||
#if ENABLE_CAPMT
|
||||
if (c == &caclient_capmt_class)
|
||||
cac = capmt_create();
|
||||
#endif
|
||||
#if ENABLE_CONSTCW
|
||||
if (c == &caclient_ccw_des_class)
|
||||
cac = constcw_create();
|
||||
if (c == &caclient_ccw_aes_class)
|
||||
cac = constcw_create();
|
||||
#endif
|
||||
if (cac == NULL)
|
||||
abort();
|
||||
|
|
|
@ -27,6 +27,8 @@ struct mpegts_mux;
|
|||
extern const idclass_t caclient_class;
|
||||
extern const idclass_t caclient_cwc_class;
|
||||
extern const idclass_t caclient_capmt_class;
|
||||
extern const idclass_t caclient_ccw_des_class;
|
||||
extern const idclass_t caclient_ccw_aes_class;
|
||||
|
||||
TAILQ_HEAD(caclient_entry_queue, caclient);
|
||||
|
||||
|
@ -75,5 +77,6 @@ void caclient_done(void);
|
|||
|
||||
caclient_t *cwc_create(void);
|
||||
caclient_t *capmt_create(void);
|
||||
caclient_t *constcw_create(void);
|
||||
|
||||
#endif /* __TVH_CACLIENT_H__ */
|
||||
|
|
411
src/descrambler/constcw.c
Normal file
411
src/descrambler/constcw.c
Normal file
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* tvheadend, constant code word interface
|
||||
* Copyright (C) 2014 Jaroslav Kysela
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include "tvheadend.h"
|
||||
#include "caclient.h"
|
||||
#include "service.h"
|
||||
#include "input.h"
|
||||
#include "tvhcsa.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef struct constcw_service {
|
||||
th_descrambler_t;
|
||||
LIST_ENTRY(constcw_service) cs_link;
|
||||
tvhcsa_t cs_csa;
|
||||
} constcw_service_t;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef struct constcw {
|
||||
caclient_t;
|
||||
|
||||
/* From configuration */
|
||||
uint16_t ccw_caid; /* CA ID */
|
||||
uint32_t ccw_providerid; /* CA provider ID */
|
||||
uint16_t ccw_tsid; /* transponder ID */
|
||||
uint16_t ccw_sid; /* service ID */
|
||||
uint8_t ccw_key_even[16]; /* DES or AES key */
|
||||
uint8_t ccw_key_odd [16]; /* DES or AES key */
|
||||
LIST_HEAD(, constcw_service) ccw_services; /* active services */
|
||||
} constcw_t;
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static const char *
|
||||
constcw_name(constcw_t *ccw)
|
||||
{
|
||||
return idnode_get_title(&ccw->cac_id);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
constcw_key_size(caclient_t *cac)
|
||||
{
|
||||
constcw_t *ccw = (constcw_t *)cac;
|
||||
|
||||
if (idnode_is_instance(&ccw->cac_id, &caclient_ccw_des_class))
|
||||
return 8;
|
||||
return 16;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static int
|
||||
constcw_ecm_reset(th_descrambler_t *th)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* s_stream_mutex is held
|
||||
*/
|
||||
static void
|
||||
constcw_service_destroy(th_descrambler_t *td)
|
||||
{
|
||||
constcw_service_t *ct = (constcw_service_t *)td;
|
||||
|
||||
LIST_REMOVE(td, td_service_link);
|
||||
LIST_REMOVE(ct, cs_link);
|
||||
tvhcsa_destroy(&ct->cs_csa);
|
||||
free(ct->td_nicename);
|
||||
free(ct);
|
||||
}
|
||||
|
||||
/**
|
||||
* global_lock is held. Not that we care about that, but either way, it is.
|
||||
*/
|
||||
static void
|
||||
constcw_service_start(caclient_t *cac, service_t *t)
|
||||
{
|
||||
constcw_t *ccw = (constcw_t *)cac;
|
||||
constcw_service_t *ct;
|
||||
th_descrambler_t *td;
|
||||
elementary_stream_t *st;
|
||||
mpegts_service_t *mt;
|
||||
char buf[128];
|
||||
caid_t *c;
|
||||
|
||||
extern const idclass_t mpegts_service_class;
|
||||
if (!idnode_is_instance(&t->s_id, &mpegts_service_class))
|
||||
return;
|
||||
mt = (mpegts_service_t *)t;
|
||||
|
||||
if (mt->s_dvb_service_id != ccw->ccw_sid)
|
||||
return;
|
||||
|
||||
if (mt->s_dvb_mux->mm_tsid != ccw->ccw_tsid)
|
||||
return;
|
||||
|
||||
LIST_FOREACH(ct, &ccw->ccw_services, cs_link)
|
||||
if (ct->td_service == t)
|
||||
break;
|
||||
if (ct)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&t->s_stream_mutex);
|
||||
TAILQ_FOREACH(st, &t->s_filt_components, es_filt_link) {
|
||||
LIST_FOREACH(c, &st->es_caids, link) {
|
||||
if (c->use && c->caid == ccw->ccw_caid &&
|
||||
c->providerid == ccw->ccw_providerid)
|
||||
break;
|
||||
}
|
||||
if (c) break;
|
||||
}
|
||||
pthread_mutex_unlock(&t->s_stream_mutex);
|
||||
if (st == NULL)
|
||||
return;
|
||||
|
||||
ct = calloc(1, sizeof(constcw_service_t));
|
||||
td = (th_descrambler_t *)ct;
|
||||
tvhcsa_init(td->td_csa = &ct->cs_csa);
|
||||
snprintf(buf, sizeof(buf), "constcw-%s", constcw_name(ccw));
|
||||
td->td_nicename = strdup(buf);
|
||||
td->td_service = t;
|
||||
td->td_stop = constcw_service_destroy;
|
||||
td->td_ecm_reset = constcw_ecm_reset;
|
||||
LIST_INSERT_HEAD(&t->s_descramblers, td, td_service_link);
|
||||
|
||||
LIST_INSERT_HEAD(&ccw->ccw_services, ct, cs_link);
|
||||
|
||||
descrambler_keys(td, constcw_key_size(cac) == 8 ?
|
||||
DESCRAMBLER_DES : DESCRAMBLER_AES,
|
||||
ccw->ccw_key_even, ccw->ccw_key_odd);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
constcw_free(caclient_t *cac)
|
||||
{
|
||||
constcw_t *ccw = (constcw_t *)cac;
|
||||
constcw_service_t *ct;
|
||||
|
||||
while((ct = LIST_FIRST(&ccw->ccw_services)) != NULL) {
|
||||
service_t *t = ct->td_service;
|
||||
pthread_mutex_lock(&t->s_stream_mutex);
|
||||
constcw_service_destroy((th_descrambler_t *)&ct);
|
||||
pthread_mutex_unlock(&t->s_stream_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
nibble(char c)
|
||||
{
|
||||
switch(c) {
|
||||
case '0' ... '9':
|
||||
return c - '0';
|
||||
case 'a' ... 'f':
|
||||
return c - 'a' + 10;
|
||||
case 'A' ... 'F':
|
||||
return c - 'A' + 10;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
constcw_conf_changed(caclient_t *cac)
|
||||
{
|
||||
if (cac->cac_enabled) {
|
||||
caclient_set_status(cac, CACLIENT_STATUS_CONNECTED);
|
||||
} else {
|
||||
caclient_set_status(cac, CACLIENT_STATUS_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
constcw_class_key_set(void *o, const void *v, uint8_t *dkey)
|
||||
{
|
||||
const char *s = v ?: "";
|
||||
int keysize = constcw_key_size(o);
|
||||
char key[16];
|
||||
int i, u, l;
|
||||
|
||||
for(i = 0; i < keysize; i++) {
|
||||
while(*s != 0 && !isxdigit(*s)) s++;
|
||||
u = *s ? nibble(*s++) : 0;
|
||||
while(*s != 0 && !isxdigit(*s)) s++;
|
||||
l = *s ? nibble(*s++) : 0;
|
||||
key[i] = (u << 4) | l;
|
||||
}
|
||||
i = memcmp(dkey, key, keysize) != 0;
|
||||
memcpy(dkey, key, keysize);
|
||||
return i;
|
||||
}
|
||||
|
||||
static int
|
||||
constcw_class_key_even_set(void *o, const void *v)
|
||||
{
|
||||
constcw_t *ccw = o;
|
||||
return constcw_class_key_set(o, v, ccw->ccw_key_even);
|
||||
}
|
||||
|
||||
static int
|
||||
constcw_class_key_odd_set(void *o, const void *v)
|
||||
{
|
||||
constcw_t *ccw = o;
|
||||
return constcw_class_key_set(o, v, ccw->ccw_key_odd);
|
||||
}
|
||||
|
||||
static const void *
|
||||
constcw_class_key_get(void *o, const uint8_t *key)
|
||||
{
|
||||
static char buf[64];
|
||||
static const char *ret = buf;
|
||||
if (constcw_key_size(o) == 8) {
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
key[0x0], key[0x1], key[0x2], key[0x3],
|
||||
key[0x4], key[0x5], key[0x6], key[0x7]);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:"
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
key[0x0], key[0x1], key[0x2], key[0x3],
|
||||
key[0x4], key[0x5], key[0x6], key[0x7],
|
||||
key[0x8], key[0x9], key[0xa], key[0xb],
|
||||
key[0xc], key[0xd], key[0xe], key[0xf]);
|
||||
}
|
||||
return &ret;
|
||||
}
|
||||
|
||||
static const void *
|
||||
constcw_class_key_even_get(void *o)
|
||||
{
|
||||
constcw_t *ccw = o;
|
||||
return constcw_class_key_get(o, ccw->ccw_key_even);
|
||||
}
|
||||
|
||||
static const void *
|
||||
constcw_class_key_odd_get(void *o)
|
||||
{
|
||||
constcw_t *ccw = o;
|
||||
return constcw_class_key_get(o, ccw->ccw_key_odd);
|
||||
}
|
||||
|
||||
const idclass_t caclient_ccw_des_class =
|
||||
{
|
||||
.ic_super = &caclient_class,
|
||||
.ic_class = "caclient_ccw_des",
|
||||
.ic_caption = "DES Constant Code Word Client",
|
||||
.ic_properties = (const property_t[]){
|
||||
{
|
||||
.type = PT_U16,
|
||||
.id = "caid",
|
||||
.name = "CA ID",
|
||||
.off = offsetof(constcw_t, ccw_caid),
|
||||
.opts = PO_HEXA,
|
||||
.def.u16 = 0x2600
|
||||
},
|
||||
{
|
||||
.type = PT_U32,
|
||||
.id = "providerid",
|
||||
.name = "Provider ID",
|
||||
.off = offsetof(constcw_t, ccw_providerid),
|
||||
.opts = PO_HEXA,
|
||||
.def.u32 = 0
|
||||
},
|
||||
{
|
||||
.type = PT_U16,
|
||||
.id = "tsid",
|
||||
.name = "Transponder ID",
|
||||
.off = offsetof(constcw_t, ccw_tsid),
|
||||
.opts = PO_HEXA,
|
||||
.def.u16 = 1,
|
||||
},
|
||||
{
|
||||
.type = PT_U16,
|
||||
.id = "sid",
|
||||
.name = "Service ID",
|
||||
.off = offsetof(constcw_t, ccw_sid),
|
||||
.opts = PO_HEXA,
|
||||
.def.u16 = 1,
|
||||
},
|
||||
{
|
||||
.type = PT_STR,
|
||||
.id = "key_even",
|
||||
.name = "Even Key",
|
||||
.set = constcw_class_key_even_set,
|
||||
.get = constcw_class_key_even_get,
|
||||
.opts = PO_PASSWORD,
|
||||
.def.s = "00:00:00:00:00:00:00:00",
|
||||
},
|
||||
{
|
||||
.type = PT_STR,
|
||||
.id = "key_odd",
|
||||
.name = "Odd Key",
|
||||
.set = constcw_class_key_odd_set,
|
||||
.get = constcw_class_key_odd_get,
|
||||
.opts = PO_PASSWORD,
|
||||
.def.s = "00:00:00:00:00:00:00:00",
|
||||
},
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
const idclass_t caclient_ccw_aes_class =
|
||||
{
|
||||
.ic_super = &caclient_class,
|
||||
.ic_class = "caclient_ccw_aes",
|
||||
.ic_caption = "AES Constant Code Word Client",
|
||||
.ic_properties = (const property_t[]){
|
||||
{
|
||||
.type = PT_U16,
|
||||
.id = "caid",
|
||||
.name = "CA ID",
|
||||
.off = offsetof(constcw_t, ccw_caid),
|
||||
.opts = PO_HEXA,
|
||||
.def.u16 = 0x2600,
|
||||
},
|
||||
{
|
||||
.type = PT_U32,
|
||||
.id = "providerid",
|
||||
.name = "Provider ID",
|
||||
.off = offsetof(constcw_t, ccw_providerid),
|
||||
.opts = PO_HEXA,
|
||||
.def.u32 = 0
|
||||
},
|
||||
{
|
||||
.type = PT_U16,
|
||||
.id = "tsid",
|
||||
.name = "Transponder ID",
|
||||
.off = offsetof(constcw_t, ccw_tsid),
|
||||
.opts = PO_HEXA,
|
||||
.def.u16 = 1,
|
||||
},
|
||||
{
|
||||
.type = PT_U16,
|
||||
.id = "sid",
|
||||
.name = "Service ID",
|
||||
.off = offsetof(constcw_t, ccw_sid),
|
||||
.opts = PO_HEXA,
|
||||
.def.u16 = 1,
|
||||
},
|
||||
{
|
||||
.type = PT_STR,
|
||||
.id = "key_even",
|
||||
.name = "Even Key",
|
||||
.set = constcw_class_key_even_set,
|
||||
.get = constcw_class_key_even_get,
|
||||
.opts = PO_PASSWORD,
|
||||
.def.s = "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
|
||||
},
|
||||
{
|
||||
.type = PT_STR,
|
||||
.id = "key_odd",
|
||||
.name = "Odd Key",
|
||||
.set = constcw_class_key_odd_set,
|
||||
.get = constcw_class_key_odd_get,
|
||||
.opts = PO_PASSWORD,
|
||||
.def.s = "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
|
||||
},
|
||||
{ }
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
caclient_t *constcw_create(void)
|
||||
{
|
||||
constcw_t *ccw = calloc(1, sizeof(*ccw));
|
||||
|
||||
ccw->cac_free = constcw_free;
|
||||
ccw->cac_start = constcw_service_start;
|
||||
ccw->cac_conf_changed = constcw_conf_changed;
|
||||
return (caclient_t *)ccw;
|
||||
}
|
|
@ -112,12 +112,12 @@ descrambler_service_start ( service_t *t )
|
|||
return;
|
||||
|
||||
((mpegts_service_t *)t)->s_dvb_mux->mm_descrambler_flush = 0;
|
||||
caclient_start(t);
|
||||
if (t->s_descramble == NULL) {
|
||||
t->s_descramble = dr = calloc(1, sizeof(th_descrambler_runtime_t));
|
||||
sbuf_init(&dr->dr_buf);
|
||||
dr->dr_key_index = 0xff;
|
||||
}
|
||||
caclient_start(t);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -134,7 +134,7 @@ int tvheadend_htsp_port_extra;
|
|||
const char *tvheadend_cwd;
|
||||
const char *tvheadend_webroot;
|
||||
const tvh_caps_t tvheadend_capabilities[] = {
|
||||
#if ENABLE_CWC || ENABLE_CAPMT
|
||||
#if ENABLE_CWC || ENABLE_CAPMT || ENABLE_CONSTCW
|
||||
{ "caclient", NULL },
|
||||
#endif
|
||||
#if ENABLE_V4L
|
||||
|
|
|
@ -451,6 +451,8 @@ prop_serialize_value
|
|||
htsmsg_add_bool(m, "password", 1);
|
||||
if (opts & PO_DURATION)
|
||||
htsmsg_add_bool(m, "duration", 1);
|
||||
if (opts & PO_HEXA)
|
||||
htsmsg_add_bool(m, "hexa", 1);
|
||||
|
||||
/* Enum list */
|
||||
if (pl->list)
|
||||
|
|
|
@ -54,6 +54,7 @@ typedef enum {
|
|||
#define PO_SORTKEY 0x0040 // Sort using key (not display value)
|
||||
#define PO_PASSWORD 0x0080 // String is a password
|
||||
#define PO_DURATION 0x0100 // For PT_TIME - differentiate between duration and datetime
|
||||
#define PO_HEXA 0x0200 // Hexadecimal value
|
||||
|
||||
/*
|
||||
* Property definition
|
||||
|
|
|
@ -20,7 +20,8 @@ tvheadend.caclient = function(panel, index) {
|
|||
});
|
||||
|
||||
var list = 'enabled,name,username,password,hostname,mode,camdfilename,' +
|
||||
'port,deskey,emm,emmex,comment';
|
||||
'port,deskey,emm,emmex,caid,providerid,tsid,sid,' +
|
||||
'key_even,key_odd,comment';
|
||||
|
||||
tvheadend.idnode_form_grid(panel, {
|
||||
url: 'api/caclient',
|
||||
|
|
|
@ -183,6 +183,7 @@ tvheadend.IdNodeField = function(conf)
|
|||
this.password = conf.showpwd ? false : conf.password;
|
||||
this.duration = conf.duration;
|
||||
this.intsplit = conf.intsplit;
|
||||
this.hexa = conf.hexa;
|
||||
this.group = conf.group;
|
||||
this.enum = conf.enum;
|
||||
this.store = null;
|
||||
|
@ -217,7 +218,7 @@ tvheadend.IdNodeField = function(conf)
|
|||
} else if (this.type === 'int' || this.type === 'u32' ||
|
||||
this.type === 'u16' || this.type === 's64' ||
|
||||
this.type === 'dbl') {
|
||||
ftype = 'numeric';
|
||||
ftype = this.hexa ? 'string' : 'numeric';
|
||||
w = 80;
|
||||
} else if (this.type === 'time') {
|
||||
w = 120;
|
||||
|
@ -371,7 +372,9 @@ tvheadend.IdNodeField = function(conf)
|
|||
case 's32':
|
||||
case 'dbl':
|
||||
case 'time':
|
||||
if (this.intsplit) {
|
||||
if (this.hexa) {
|
||||
cons = Ext.form.TextField;
|
||||
} else if (this.intsplit) {
|
||||
c['maskRe'] = /[0-9\.]/;
|
||||
cons = Ext.form.TextField;
|
||||
} else
|
||||
|
@ -559,6 +562,16 @@ tvheadend.idnode_editor_field = function(f, conf)
|
|||
case 'u16':
|
||||
case 's64':
|
||||
case 'dbl':
|
||||
if (f.hexa) {
|
||||
return new Ext.form.TextField({
|
||||
fieldLabel: f.caption,
|
||||
name: f.id,
|
||||
value: '0x' + value.toString(16),
|
||||
disabled: d,
|
||||
width: 300,
|
||||
maskRe: /[xX0-9a-fA-F\.]/,
|
||||
});
|
||||
}
|
||||
if (f.intsplit) {
|
||||
/* this should be improved */
|
||||
return new Ext.form.TextField({
|
||||
|
@ -866,7 +879,7 @@ tvheadend.idnode_create = function(conf, onlyDefault)
|
|||
pclass = r.get(conf.select.valueField);
|
||||
win.setTitle('Add ' + s.lastSelectionText);
|
||||
panel.remove(s);
|
||||
tvheadend.idnode_editor_form(d, null, panel, { create: true });
|
||||
tvheadend.idnode_editor_form(d, null, panel, { create: true, showpwd: true });
|
||||
saveBtn.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
@ -881,7 +894,7 @@ tvheadend.idnode_create = function(conf, onlyDefault)
|
|||
success: function(d) {
|
||||
panel.remove(s);
|
||||
d = json_decode(d);
|
||||
tvheadend.idnode_editor_form(d.props, d, panel, { create: true });
|
||||
tvheadend.idnode_editor_form(d.props, d, panel, { create: true, showpwd: true });
|
||||
saveBtn.setVisible(true);
|
||||
}
|
||||
});
|
||||
|
@ -912,7 +925,7 @@ tvheadend.idnode_create = function(conf, onlyDefault)
|
|||
params: conf.params,
|
||||
success: function(d) {
|
||||
d = json_decode(d);
|
||||
tvheadend.idnode_editor_form(d.props, d, panel, { create: true });
|
||||
tvheadend.idnode_editor_form(d.props, d, panel, { create: true, showpwd: true });
|
||||
saveBtn.setVisible(true);
|
||||
if (onlyDefault) {
|
||||
saveBtn.handler();
|
||||
|
|
Loading…
Add table
Reference in a new issue