inputspec and required instead of nullable

This commit is contained in:
Matt Hill
2023-03-31 10:31:23 -06:00
committed by Aiden McClelland
parent 5675fc51a0
commit e4cd4d64d7
14 changed files with 143 additions and 96 deletions

View File

@@ -49,7 +49,7 @@
"patch-db-client": "file: ../../../patch-db/client", "patch-db-client": "file: ../../../patch-db/client",
"pbkdf2": "^3.1.2", "pbkdf2": "^3.1.2",
"rxjs": "^7.5.6", "rxjs": "^7.5.6",
"start-sdk": "^0.4.0-lib0.beta5", "start-sdk": "^0.4.0-lib0.beta6",
"swiper": "^8.2.4", "swiper": "^8.2.4",
"ts-matches": "^5.2.1", "ts-matches": "^5.2.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",
@@ -13771,9 +13771,9 @@
} }
}, },
"node_modules/start-sdk": { "node_modules/start-sdk": {
"version": "0.4.0-lib0.beta5", "version": "0.4.0-lib0.beta6",
"resolved": "https://registry.npmjs.org/start-sdk/-/start-sdk-0.4.0-lib0.beta5.tgz", "resolved": "https://registry.npmjs.org/start-sdk/-/start-sdk-0.4.0-lib0.beta6.tgz",
"integrity": "sha512-dHWn7urjTXtS+CujkRp7U/CiSk/mTuvXjmFt8WMhnPExu2FFdmn72LDOICn5hf8kS0oi1qcYtjzq4juD8HlVFQ==", "integrity": "sha512-9dR2noD/rJ4u/Xuhs5S0+lv95nqpERWzxbXlpXDbtjXzMKzX8v1zmKKMNfTir1oxaZC820llLlaCeuPG2VGNpg==",
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@@ -74,7 +74,7 @@
"patch-db-client": "file: ../../../patch-db/client", "patch-db-client": "file: ../../../patch-db/client",
"pbkdf2": "^3.1.2", "pbkdf2": "^3.1.2",
"rxjs": "^7.5.6", "rxjs": "^7.5.6",
"start-sdk": "^0.4.0-lib0.beta5", "start-sdk": "^0.4.0-lib0.beta6",
"swiper": "^8.2.4", "swiper": "^8.2.4",
"ts-matches": "^5.2.1", "ts-matches": "^5.2.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",

View File

@@ -280,10 +280,11 @@ const CifsSpec: InputSpec = {
name: 'Hostname', name: 'Hostname',
description: description:
'The hostname of your target device on the Local Area Network.', 'The hostname of your target device on the Local Area Network.',
inputmode: 'text',
placeholder: `e.g. 'My Computer' OR 'my-computer.local'`, placeholder: `e.g. 'My Computer' OR 'my-computer.local'`,
pattern: '^[a-zA-Z0-9._-]+( [a-zA-Z0-9]+)*$', pattern: '^[a-zA-Z0-9._-]+( [a-zA-Z0-9]+)*$',
patternDescription: `Must be a valid hostname. e.g. 'My Computer' OR 'my-computer.local'`, patternDescription: `Must be a valid hostname. e.g. 'My Computer' OR 'my-computer.local'`,
nullable: false, required: true,
masked: false, masked: false,
default: null, default: null,
warning: null, warning: null,
@@ -292,10 +293,11 @@ const CifsSpec: InputSpec = {
type: 'string', type: 'string',
name: 'Path', name: 'Path',
description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`, description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`,
inputmode: 'text',
placeholder: 'e.g. my-shared-folder or /Desktop/my-folder', placeholder: 'e.g. my-shared-folder or /Desktop/my-folder',
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
nullable: false, required: true,
masked: false, masked: false,
default: null, default: null,
warning: null, warning: null,
@@ -304,10 +306,11 @@ const CifsSpec: InputSpec = {
type: 'string', type: 'string',
name: 'Username', name: 'Username',
description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`, description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`,
inputmode: 'text',
placeholder: null, placeholder: null,
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
nullable: false, required: true,
masked: false, masked: false,
default: null, default: null,
warning: null, warning: null,
@@ -316,10 +319,11 @@ const CifsSpec: InputSpec = {
type: 'string', type: 'string',
name: 'Password', name: 'Password',
description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`, description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`,
inputmode: 'text',
placeholder: null, placeholder: null,
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
nullable: true, required: false,
masked: true, masked: true,
default: null, default: null,
warning: null, warning: null,

View File

@@ -4,7 +4,7 @@
name: spec.name, name: spec.name,
description: spec.description, description: spec.description,
edited: control.dirty, edited: control.dirty,
required: !spec.nullable required: spec.required
}" }"
></form-label> ></form-label>
<ion-item [color]="(theme$ | async) === 'Light' ? 'light' : 'dark'"> <ion-item [color]="(theme$ | async) === 'Light' ? 'light' : 'dark'">

View File

@@ -53,11 +53,11 @@
</div> </div>
<div class="ion-text-right"> <div class="ion-text-right">
<ion-button fill="clear" (click)="cancel()"> Cancel </ion-button> <ion-button fill="clear" (click)="cancel()">Cancel</ion-button>
<ion-button <ion-button
fill="clear" fill="clear"
type="submit" type="submit"
[disabled]="!value && !options.nullable" [disabled]="!value && !options.required"
> >
{{ options.buttonText }} {{ options.buttonText }}
</ion-button> </ion-button>

View File

@@ -31,8 +31,7 @@ export class GenericInputComponent {
ngOnInit() { ngOnInit() {
const defaultOptions: Partial<GenericInputOptions> = { const defaultOptions: Partial<GenericInputOptions> = {
buttonText: 'Submit', buttonText: 'Submit',
placeholder: 'Enter value', required: true,
nullable: false,
useMask: false, useMask: false,
initialValue: '', initialValue: '',
} }
@@ -69,7 +68,7 @@ export class GenericInputComponent {
async submit() { async submit() {
const value = this.value.trim() const value = this.value.trim()
if (!value && !this.options.nullable) return if (!value && this.options.required) return
try { try {
await this.options.submitFn(value) await this.options.submitFn(value)
@@ -84,13 +83,13 @@ export interface GenericInputOptions {
// required // required
title: string title: string
message: string message: string
label: string
submitFn: (value: string) => Promise<any> submitFn: (value: string) => Promise<any>
// optional // optional
label?: string
warning?: string warning?: string
buttonText?: string buttonText?: string
placeholder?: string placeholder?: string
nullable?: boolean required?: boolean
useMask?: boolean useMask?: boolean
initialValue?: string | null initialValue?: string | null
} }

View File

@@ -273,7 +273,8 @@ function getMarketplaceValueSpec(): ValueSpecObject {
type: 'string', type: 'string',
name: 'URL', name: 'URL',
description: 'A fully-qualified URL of the custom registry', description: 'A fully-qualified URL of the custom registry',
nullable: false, inputmode: 'url',
required: true,
masked: false, masked: false,
pattern: `https?:\/\/[a-zA-Z0-9][a-zA-Z0-9-\.]+[a-zA-Z0-9]\.[^\s]{2,}`, pattern: `https?:\/\/[a-zA-Z0-9][a-zA-Z0-9-\.]+[a-zA-Z0-9]\.[^\s]{2,}`,
patternDescription: 'Must be a valid URL', patternDescription: 'Must be a valid URL',

View File

@@ -57,7 +57,7 @@ export class DeveloperListPage {
label: 'New project', label: 'New project',
useMask: false, useMask: false,
placeholder: `Project ${projNumber}`, placeholder: `Project ${projNumber}`,
nullable: true, required: false,
initialValue: `Project ${projNumber}`, initialValue: `Project ${projNumber}`,
buttonText: 'Save', buttonText: 'Save',
submitFn: (value: string) => this.createProject(value), submitFn: (value: string) => this.createProject(value),
@@ -112,7 +112,7 @@ export class DeveloperListPage {
label: 'Name', label: 'Name',
useMask: false, useMask: false,
placeholder: curName, placeholder: curName,
nullable: true, required: false,
initialValue: curName, initialValue: curName,
buttonText: 'Save', buttonText: 'Save',
submitFn: (value: string) => this.editName(id, value), submitFn: (value: string) => this.editName(id, value),
@@ -221,7 +221,8 @@ const SAMPLE_CONFIG: InputSpec = {
'sample-string': { 'sample-string': {
type: 'string', type: 'string',
name: 'Example String Input', name: 'Example String Input',
nullable: false, inputmode: 'text',
required: true,
masked: false, masked: false,
// optional // optional
description: 'Example description for required string input.', description: 'Example description for required string input.',
@@ -234,7 +235,8 @@ const SAMPLE_CONFIG: InputSpec = {
'sample-number': { 'sample-number': {
type: 'number', type: 'number',
name: 'Example Number Input', name: 'Example Number Input',
nullable: false, inputmode: 'decimal',
required: true,
range: '[5,1000000]', range: '[5,1000000]',
integral: true, integral: true,
// optional // optional

View File

@@ -22,10 +22,11 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
return { return {
id: { id: {
type: 'string', type: 'string',
inputmode: 'text',
name: 'ID', name: 'ID',
description: 'The package identifier used by the OS', description: 'The package identifier used by the OS',
placeholder: 'e.g. bitcoind', placeholder: 'e.g. bitcoind',
nullable: false, required: true,
masked: false, masked: false,
pattern: '^([a-z][a-z0-9]*)(-[a-z0-9]+)*$', pattern: '^([a-z][a-z0-9]*)(-[a-z0-9]+)*$',
patternDescription: 'Must be kebab case', patternDescription: 'Must be kebab case',
@@ -34,10 +35,11 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
}, },
title: { title: {
type: 'string', type: 'string',
inputmode: 'text',
name: 'Service Name', name: 'Service Name',
description: 'A human readable service title', description: 'A human readable service title',
placeholder: 'e.g. Bitcoin Core', placeholder: 'e.g. Bitcoin Core',
nullable: false, required: true,
masked: false, masked: false,
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
@@ -46,11 +48,12 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
}, },
'service-version-number': { 'service-version-number': {
type: 'string', type: 'string',
inputmode: 'text',
name: 'Service Version', name: 'Service Version',
description: description:
'Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOS - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of the service', 'Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOS - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of the service',
placeholder: 'e.g. 0.1.2.3', placeholder: 'e.g. 0.1.2.3',
nullable: false, required: true,
masked: false, masked: false,
pattern: '^([0-9]+).([0-9]+).([0-9]+).([0-9]+)$', pattern: '^([0-9]+).([0-9]+).([0-9]+).([0-9]+)$',
patternDescription: 'Must be valid Emver version', patternDescription: 'Must be valid Emver version',
@@ -65,11 +68,12 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
spec: { spec: {
short: { short: {
type: 'string', type: 'string',
inputmode: 'text',
name: 'Short Description', name: 'Short Description',
description: description:
'This is the first description visible to the user in the marketplace', 'This is the first description visible to the user in the marketplace',
placeholder: null, placeholder: null,
nullable: false, required: true,
masked: false, masked: false,
default: basicInfo?.description?.short || '', default: basicInfo?.description?.short || '',
pattern: '^.{1,320}$', pattern: '^.{1,320}$',
@@ -81,18 +85,19 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
name: 'Long Description', name: 'Long Description',
description: `This description will display with additional details in the service's individual marketplace page`, description: `This description will display with additional details in the service's individual marketplace page`,
placeholder: null, placeholder: null,
nullable: false, required: true,
warning: null, warning: null,
}, },
}, },
}, },
'release-notes': { 'release-notes': {
type: 'string', type: 'string',
inputmode: 'text',
name: 'Release Notes', name: 'Release Notes',
description: description:
'Markdown supported release notes for this version of this service.', 'Markdown supported release notes for this version of this service.',
placeholder: 'e.g. Markdown _release notes_ for **Bitcoin Core**', placeholder: 'e.g. Markdown _release notes_ for **Bitcoin Core**',
nullable: false, required: true,
masked: false, masked: false,
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
@@ -115,54 +120,58 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
custom: 'Custom', custom: 'Custom',
}, },
description: 'Example description for select', description: 'Example description for select',
nullable: false, required: true,
default: 'mit', default: 'mit',
}, },
'wrapper-repo': { 'wrapper-repo': {
type: 'string', type: 'string',
inputmode: 'url',
name: 'Wrapper Repo', name: 'Wrapper Repo',
description: description:
'The Start9 wrapper repository URL for the package. This repo contains the manifest file (this), any scripts necessary for configuration, backups, actions, or health checks', 'The Start9 wrapper repository URL for the package. This repo contains the manifest file (this), any scripts necessary for configuration, backups, actions, or health checks',
placeholder: 'e.g. www.github.com/example', placeholder: 'e.g. www.github.com/example',
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
nullable: false, required: true,
masked: false, masked: false,
default: basicInfo?.['wrapper-repo'] || '', default: basicInfo?.['wrapper-repo'] || '',
warning: null, warning: null,
}, },
'upstream-repo': { 'upstream-repo': {
type: 'string', type: 'string',
inputmode: 'url',
name: 'Upstream Repo', name: 'Upstream Repo',
description: 'The original project repository URL', description: 'The original project repository URL',
placeholder: 'e.g. www.github.com/example', placeholder: 'e.g. www.github.com/example',
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
nullable: true, required: false,
masked: false, masked: false,
default: basicInfo?.['upstream-repo'] || '', default: basicInfo?.['upstream-repo'] || '',
warning: null, warning: null,
}, },
'support-site': { 'support-site': {
type: 'string', type: 'string',
inputmode: 'url',
name: 'Support Site', name: 'Support Site',
description: 'URL to the support site / channel for the project', description: 'URL to the support site / channel for the project',
placeholder: 'e.g. start9.com/support', placeholder: 'e.g. start9.com/support',
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
nullable: true, required: false,
masked: false, masked: false,
default: basicInfo?.['support-site'] || '', default: basicInfo?.['support-site'] || '',
warning: null, warning: null,
}, },
'marketing-site': { 'marketing-site': {
type: 'string', type: 'string',
inputmode: 'url',
name: 'Website', name: 'Website',
description: 'URL to the marketing site / channel for the project', description: 'URL to the marketing site / channel for the project',
placeholder: 'e.g. start9.com', placeholder: 'e.g. start9.com',
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
nullable: true, required: false,
masked: false, masked: false,
default: basicInfo?.['marketing-site'] || '', default: basicInfo?.['marketing-site'] || '',
warning: null, warning: null,

View File

@@ -84,7 +84,7 @@ export class ServerShowPage {
label: 'Device Name', label: 'Device Name',
useMask: false, useMask: false,
placeholder: 'StartOS', placeholder: 'StartOS',
nullable: true, required: false,
initialValue: chosenName, initialValue: chosenName,
buttonText: 'Save', buttonText: 'Save',
submitFn: (name: string) => this.setName(name || null), submitFn: (name: string) => this.setName(name || null),

View File

@@ -45,12 +45,11 @@ export class SSHKeysPage {
} }
async presentModalAdd() { async presentModalAdd() {
const { name, description } = sshSpec
const options: GenericInputOptions = { const options: GenericInputOptions = {
title: name, title: 'SSH Key',
message: description, message:
label: name, 'Enter the SSH public key you would like to authorize for root access to your Embassy.',
label: '',
submitFn: (pk: string) => this.add(pk), submitFn: (pk: string) => this.add(pk),
} }
@@ -114,13 +113,3 @@ export class SSHKeysPage {
} }
} }
} }
const sshSpec = {
type: 'string',
name: 'SSH Key',
description:
'Enter the SSH public key you would like to authorize for root access to your server.',
nullable: false,
masked: false,
copyable: false,
}

View File

@@ -359,10 +359,11 @@ function getWifiValueSpec(
type: 'string', type: 'string',
name: 'Network SSID', name: 'Network SSID',
description: null, description: null,
inputmode: 'text',
placeholder: null, placeholder: null,
pattern: null, pattern: null,
patternDescription: null, patternDescription: null,
nullable: false, required: true,
masked: false, masked: false,
default: ssid || null, default: ssid || null,
warning: null, warning: null,
@@ -371,8 +372,9 @@ function getWifiValueSpec(
type: 'string', type: 'string',
name: 'Password', name: 'Password',
description: null, description: null,
inputmode: 'text',
placeholder: null, placeholder: null,
nullable: !needsPW, required: needsPW,
masked: true, masked: true,
pattern: '^.{8,}$', pattern: '^.{8,}$',
patternDescription: 'Must be longer than 8 characters', patternDescription: 'Must be longer than 8 characters',

View File

@@ -657,6 +657,7 @@ export module Mock {
data: { data: {
lndconnect: { lndconnect: {
type: 'string', type: 'string',
inputmode: 'text',
description: 'This is some information about the thing.', description: 'This is some information about the thing.',
copyable: true, copyable: true,
qr: true, qr: true,
@@ -670,6 +671,7 @@ export module Mock {
value: { value: {
'Last Name': { 'Last Name': {
type: 'string', type: 'string',
inputmode: 'text',
description: 'The last name of the user', description: 'The last name of the user',
copyable: true, copyable: true,
qr: true, qr: true,
@@ -678,6 +680,7 @@ export module Mock {
}, },
Age: { Age: {
type: 'string', type: 'string',
inputmode: 'text',
description: 'The age of the user', description: 'The age of the user',
copyable: false, copyable: false,
qr: false, qr: false,
@@ -686,6 +689,7 @@ export module Mock {
}, },
Password: { Password: {
type: 'string', type: 'string',
inputmode: 'text',
description: 'A secret password', description: 'A secret password',
copyable: true, copyable: true,
qr: false, qr: false,
@@ -696,6 +700,7 @@ export module Mock {
}, },
'Another Value': { 'Another Value': {
type: 'string', type: 'string',
inputmode: 'text',
description: 'Some more information about the service.', description: 'Some more information about the service.',
copyable: false, copyable: false,
qr: true, qr: true,
@@ -720,7 +725,7 @@ export module Mock {
'<p>The Bitcoin Core node to connect to over the peer-to-peer (P2P) interface:</p><ul><li><strong>Bitcoin Core</strong>: The Bitcoin Core service installed on this device</li><li><strong>External Node</strong>: A Bitcoin node running on a different device</li></ul>', '<p>The Bitcoin Core node to connect to over the peer-to-peer (P2P) interface:</p><ul><li><strong>Bitcoin Core</strong>: The Bitcoin Core service installed on this device</li><li><strong>External Node</strong>: A Bitcoin node running on a different device</li></ul>',
warning: null, warning: null,
default: null, default: null,
nullable: false, required: true,
variants: { variants: {
internal: { name: 'Internal', spec: {} }, internal: { name: 'Internal', spec: {} },
external: { external: {
@@ -728,9 +733,10 @@ export module Mock {
spec: { spec: {
'p2p-host': { 'p2p-host': {
type: 'string', type: 'string',
inputmode: 'text',
name: 'Public Address', name: 'Public Address',
description: 'The public address of your Bitcoin Core server', description: 'The public address of your Bitcoin Core server',
nullable: false, required: true,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -740,10 +746,11 @@ export module Mock {
}, },
'p2p-port': { 'p2p-port': {
type: 'number', type: 'number',
inputmode: 'numeric',
name: 'P2P Port', name: 'P2P Port',
description: description:
'The port that your Bitcoin Core P2P server is bound to', 'The port that your Bitcoin Core P2P server is bound to',
nullable: false, required: true,
range: '[0,65535]', range: '[0,65535]',
integral: true, integral: true,
default: 8333, default: 8333,
@@ -773,10 +780,11 @@ export module Mock {
rpcuser2: { rpcuser2: {
name: 'RPC Username', name: 'RPC Username',
type: 'string', type: 'string',
inputmode: 'text',
description: 'rpc username', description: 'rpc username',
warning: null, warning: null,
placeholder: null, placeholder: null,
nullable: false, required: true,
default: 'defaultrpcusername', default: 'defaultrpcusername',
pattern: '^[a-zA-Z]+$', pattern: '^[a-zA-Z]+$',
patternDescription: 'must contain only letters.', patternDescription: 'must contain only letters.',
@@ -785,10 +793,11 @@ export module Mock {
rpcuser: { rpcuser: {
name: 'RPC Username', name: 'RPC Username',
type: 'string', type: 'string',
inputmode: 'text',
description: 'rpc username', description: 'rpc username',
warning: null, warning: null,
placeholder: null, placeholder: null,
nullable: false, required: true,
default: 'defaultrpcusername', default: 'defaultrpcusername',
pattern: '^[a-zA-Z]+$', pattern: '^[a-zA-Z]+$',
patternDescription: 'must contain only letters.', patternDescription: 'must contain only letters.',
@@ -797,10 +806,11 @@ export module Mock {
rpcpass: { rpcpass: {
name: 'RPC User Password', name: 'RPC User Password',
type: 'string', type: 'string',
inputmode: 'text',
description: 'rpc password', description: 'rpc password',
placeholder: null, placeholder: null,
warning: null, warning: null,
nullable: false, required: true,
default: { default: {
charset: 'a-z,A-Z,2-9', charset: 'a-z,A-Z,2-9',
len: 20, len: 20,
@@ -812,10 +822,11 @@ export module Mock {
rpcpass2: { rpcpass2: {
name: 'RPC User Password', name: 'RPC User Password',
type: 'string', type: 'string',
inputmode: 'text',
description: 'rpc password', description: 'rpc password',
warning: null, warning: null,
placeholder: null, placeholder: null,
nullable: false, required: true,
default: { default: {
charset: 'a-z,A-Z,2-9', charset: 'a-z,A-Z,2-9',
len: 20, len: 20,
@@ -834,7 +845,7 @@ export module Mock {
description: 'Your personal bio', description: 'Your personal bio',
placeholder: 'Tell the world about yourself', placeholder: 'Tell the world about yourself',
warning: null, warning: null,
nullable: true, required: false,
}, },
testnet: { testnet: {
name: 'Testnet', name: 'Testnet',
@@ -849,7 +860,7 @@ export module Mock {
type: 'file', type: 'file',
description: 'A file we need', description: 'A file we need',
warning: 'Testing warning', warning: 'Testing warning',
nullable: false, required: true,
extensions: ['.png'], extensions: ['.png'],
}, },
'object-list': { 'object-list': {
@@ -880,8 +891,9 @@ export module Mock {
'first-name': { 'first-name': {
name: 'First Name', name: 'First Name',
type: 'string', type: 'string',
inputmode: 'text',
description: 'User first name', description: 'User first name',
nullable: true, required: false,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -892,8 +904,9 @@ export module Mock {
'last-name': { 'last-name': {
name: 'Last Name', name: 'Last Name',
type: 'string', type: 'string',
inputmode: 'text',
description: 'User first name', description: 'User first name',
nullable: true, required: false,
default: { default: {
charset: 'a-g,2-9', charset: 'a-g,2-9',
len: 12, len: 12,
@@ -906,9 +919,10 @@ export module Mock {
}, },
age: { age: {
name: 'Age', name: 'Age',
inputmode: 'numeric',
type: 'number', type: 'number',
description: 'The age of the user', description: 'The age of the user',
nullable: true, required: false,
integral: false, integral: false,
warning: 'User must be at least 18.', warning: 'User must be at least 18.',
range: '[18,*)', range: '[18,*)',
@@ -930,7 +944,7 @@ export module Mock {
default: 'sup', default: 'sup',
description: 'This is not even real.', description: 'This is not even real.',
warning: 'Be careful changing this!', warning: 'Be careful changing this!',
nullable: false, required: true,
}, },
notifications: { notifications: {
name: 'Notification Preferences', name: 'Notification Preferences',
@@ -949,13 +963,14 @@ export module Mock {
}, },
'favorite-number': { 'favorite-number': {
name: 'Favorite Number', name: 'Favorite Number',
inputmode: 'decimal',
type: 'number', type: 'number',
integral: false, integral: false,
description: 'Your favorite number of all time', description: 'Your favorite number of all time',
placeholder: null, placeholder: null,
warning: warning:
'Once you set this number, it can never be changed without severe consequences.', 'Once you set this number, it can never be changed without severe consequences.',
nullable: true, required: false,
default: 7, default: 7,
range: '(-100,100]', range: '(-100,100]',
units: 'BTC', units: 'BTC',
@@ -967,6 +982,7 @@ export module Mock {
description: 'Numbers that you like but are not your top favorite.', description: 'Numbers that you like but are not your top favorite.',
warning: null, warning: null,
spec: { spec: {
inputmode: 'decimal',
integral: false, integral: false,
range: '[-100,200)', range: '[-100,200)',
units: null, units: null,
@@ -990,8 +1006,9 @@ export module Mock {
law1: { law1: {
name: 'First Law', name: 'First Law',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the first law', description: 'the first law',
nullable: true, required: false,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1002,8 +1019,9 @@ export module Mock {
law2: { law2: {
name: 'Second Law', name: 'Second Law',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the second law', description: 'the second law',
nullable: true, required: false,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1028,8 +1046,9 @@ export module Mock {
rulemakername: { rulemakername: {
name: 'Rulemaker Name', name: 'Rulemaker Name',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the name of the rule maker', description: 'the name of the rule maker',
nullable: false, required: true,
default: { default: {
charset: 'a-g,2-9', charset: 'a-g,2-9',
len: 12, len: 12,
@@ -1043,8 +1062,9 @@ export module Mock {
rulemakerip: { rulemakerip: {
name: 'Rulemaker IP', name: 'Rulemaker IP',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the ip of the rule maker', description: 'the ip of the rule maker',
nullable: false, required: true,
default: '192.168.1.0', default: '192.168.1.0',
pattern: pattern:
'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$', '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$',
@@ -1059,8 +1079,9 @@ export module Mock {
rpcuser: { rpcuser: {
name: 'RPC Username', name: 'RPC Username',
type: 'string', type: 'string',
inputmode: 'text',
description: 'rpc username', description: 'rpc username',
nullable: false, required: true,
default: 'defaultrpcusername', default: 'defaultrpcusername',
pattern: '^[a-zA-Z]+$', pattern: '^[a-zA-Z]+$',
patternDescription: 'must contain only letters.', patternDescription: 'must contain only letters.',
@@ -1071,8 +1092,9 @@ export module Mock {
rpcpass: { rpcpass: {
name: 'RPC User Password', name: 'RPC User Password',
type: 'string', type: 'string',
inputmode: 'text',
description: 'rpc password', description: 'rpc password',
nullable: false, required: true,
default: { default: {
charset: 'a-z,A-Z,2-9', charset: 'a-z,A-Z,2-9',
len: 20, len: 20,
@@ -1091,7 +1113,7 @@ export module Mock {
name: 'Bitcoin Node Settings', name: 'Bitcoin Node Settings',
description: 'Options<ul><li>Item 1</li><li>Item 2</li></ul>', description: 'Options<ul><li>Item 1</li><li>Item 2</li></ul>',
warning: 'Careful changing this', warning: 'Careful changing this',
nullable: false, required: true,
variants: { variants: {
internal: { name: 'Internal', spec: {} }, internal: { name: 'Internal', spec: {} },
external: { external: {
@@ -1105,9 +1127,10 @@ export module Mock {
spec: { spec: {
name: { name: {
type: 'string', type: 'string',
inputmode: 'text',
name: 'Name', name: 'Name',
description: null, description: null,
nullable: false, required: true,
masked: false, masked: false,
pattern: '^[a-zA-Z]+$', pattern: '^[a-zA-Z]+$',
patternDescription: 'Must contain only letters.', patternDescription: 'Must contain only letters.',
@@ -1117,9 +1140,10 @@ export module Mock {
}, },
email: { email: {
type: 'string', type: 'string',
inputmode: 'text',
name: 'Email', name: 'Email',
description: null, description: null,
nullable: false, required: true,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1132,8 +1156,9 @@ export module Mock {
'public-domain': { 'public-domain': {
name: 'Public Domain', name: 'Public Domain',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the public address of the node', description: 'the public address of the node',
nullable: false, required: true,
default: 'bitcoinnode.com', default: 'bitcoinnode.com',
pattern: '.*', pattern: '.*',
patternDescription: 'anything', patternDescription: 'anything',
@@ -1144,8 +1169,9 @@ export module Mock {
'private-domain': { 'private-domain': {
name: 'Private Domain', name: 'Private Domain',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the private address of the node', description: 'the private address of the node',
nullable: false, required: true,
masked: true, masked: true,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1159,12 +1185,13 @@ export module Mock {
}, },
port: { port: {
name: 'Port', name: 'Port',
inputmode: 'numeric',
type: 'number', type: 'number',
integral: true, integral: true,
description: description:
'the default port for your Bitcoin node. default: 8333, testnet: 18333, regtest: 18444', 'the default port for your Bitcoin node. default: 8333, testnet: 18333, regtest: 18444',
warning: null, warning: null,
nullable: false, required: true,
default: 8333, default: 8333,
range: '(0, 9998]', range: '(0, 9998]',
units: null, units: null,
@@ -1173,9 +1200,10 @@ export module Mock {
'favorite-slogan': { 'favorite-slogan': {
name: 'Favorite Slogan', name: 'Favorite Slogan',
type: 'string', type: 'string',
inputmode: 'text',
description: description:
'You most favorite slogan in the whole world, used for paying you.', 'You most favorite slogan in the whole world, used for paying you.',
nullable: true, required: false,
masked: true, masked: true,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1194,6 +1222,7 @@ export module Mock {
range: '[1,10]', range: '[1,10]',
default: ['192.168.1.1'], default: ['192.168.1.1'],
spec: { spec: {
inputmode: 'text',
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: pattern:
@@ -1210,6 +1239,7 @@ export module Mock {
range: '[0,*)', range: '[0,*)',
default: [], default: [],
spec: { spec: {
inputmode: 'text',
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1238,8 +1268,9 @@ export module Mock {
law1: { law1: {
name: 'First Law', name: 'First Law',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the first law', description: 'the first law',
nullable: true, required: false,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1250,8 +1281,9 @@ export module Mock {
law2: { law2: {
name: 'Second Law', name: 'Second Law',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the second law', description: 'the second law',
nullable: true, required: false,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1262,8 +1294,9 @@ export module Mock {
law4: { law4: {
name: 'Fourth Law', name: 'Fourth Law',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the fourth law', description: 'the fourth law',
nullable: true, required: false,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1286,8 +1319,9 @@ export module Mock {
lawname: { lawname: {
name: 'Law Name', name: 'Law Name',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the name of the law maker', description: 'the name of the law maker',
nullable: false, required: true,
default: { default: {
charset: 'a-g,2-9', charset: 'a-g,2-9',
len: 12, len: 12,
@@ -1301,8 +1335,9 @@ export module Mock {
lawagency: { lawagency: {
name: 'Law agency', name: 'Law agency',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the ip of the law maker', description: 'the ip of the law maker',
nullable: false, required: true,
default: '192.168.1.0', default: '192.168.1.0',
pattern: pattern:
'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$', '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$',
@@ -1318,8 +1353,9 @@ export module Mock {
law5: { law5: {
name: 'Fifth Law', name: 'Fifth Law',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the fifth law', description: 'the fifth law',
nullable: true, required: false,
masked: false, masked: false,
placeholder: null, placeholder: null,
pattern: null, pattern: null,
@@ -1344,8 +1380,9 @@ export module Mock {
rulemakername: { rulemakername: {
name: 'Rulemaker Name', name: 'Rulemaker Name',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the name of the rule maker', description: 'the name of the rule maker',
nullable: false, required: true,
default: { default: {
charset: 'a-g,2-9', charset: 'a-g,2-9',
len: 12, len: 12,
@@ -1359,8 +1396,9 @@ export module Mock {
rulemakerip: { rulemakerip: {
name: 'Rulemaker IP', name: 'Rulemaker IP',
type: 'string', type: 'string',
inputmode: 'text',
description: 'the ip of the rule maker', description: 'the ip of the rule maker',
nullable: false, required: true,
default: '192.168.1.0', default: '192.168.1.0',
pattern: pattern:
'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$', '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$',
@@ -1375,8 +1413,9 @@ export module Mock {
rpcuser: { rpcuser: {
name: 'RPC Username', name: 'RPC Username',
type: 'string', type: 'string',
inputmode: 'text',
description: 'rpc username', description: 'rpc username',
nullable: false, required: true,
default: 'defaultrpcusername', default: 'defaultrpcusername',
pattern: '^[a-zA-Z]+$', pattern: '^[a-zA-Z]+$',
patternDescription: 'must contain only letters.', patternDescription: 'must contain only letters.',
@@ -1387,8 +1426,9 @@ export module Mock {
rpcpass: { rpcpass: {
name: 'RPC User Password', name: 'RPC User Password',
type: 'string', type: 'string',
inputmode: 'text',
description: 'rpc password', description: 'rpc password',
nullable: false, required: true,
default: { default: {
charset: 'a-z,A-Z,2-9', charset: 'a-z,A-Z,2-9',
len: 20, len: 20,
@@ -1498,10 +1538,11 @@ export module Mock {
'input-spec': { 'input-spec': {
reason: { reason: {
type: 'string', type: 'string',
inputmode: 'text',
name: 'Re-sync Reason', name: 'Re-sync Reason',
description: 'Your reason for re-syncing. Why are you doing this?', description: 'Your reason for re-syncing. Why are you doing this?',
placeholder: null, placeholder: null,
nullable: false, required: true,
masked: false, masked: false,
pattern: '^[a-zA-Z]+$', pattern: '^[a-zA-Z]+$',
patternDescription: 'Must contain only letters.', patternDescription: 'Must contain only letters.',

View File

@@ -47,7 +47,7 @@ export class FormService {
spec: ValueSpecUnion, spec: ValueSpecUnion,
selection: string | null, selection: string | null,
): UntypedFormGroup { ): UntypedFormGroup {
const { name, description, warning, variants, nullable } = spec const { name, description, warning, variants, required } = spec
const selectSpec: ValueSpecSelect = { const selectSpec: ValueSpecSelect = {
type: 'select', type: 'select',
@@ -55,7 +55,7 @@ export class FormService {
description, description,
warning, warning,
default: selection, default: selection,
nullable, required,
values: Object.keys(variants).reduce( values: Object.keys(variants).reduce(
(prev, curr) => ({ (prev, curr) => ({
...prev, ...prev,
@@ -172,7 +172,7 @@ function stringValidators(
): ValidatorFn[] { ): ValidatorFn[] {
const validators: ValidatorFn[] = [] const validators: ValidatorFn[] = []
if (!(spec as ValueSpecString).nullable) { if ((spec as ValueSpecString).required) {
validators.push(Validators.required) validators.push(Validators.required)
} }
@@ -186,7 +186,7 @@ function stringValidators(
function textareaValidators(spec: ValueSpecTextarea): ValidatorFn[] { function textareaValidators(spec: ValueSpecTextarea): ValidatorFn[] {
const validators: ValidatorFn[] = [] const validators: ValidatorFn[] = []
if (!spec.nullable) { if (spec.required) {
validators.push(Validators.required) validators.push(Validators.required)
} }
@@ -200,7 +200,7 @@ function numberValidators(
validators.push(isNumber()) validators.push(isNumber())
if (!(spec as ValueSpecNumber).nullable) { if ((spec as ValueSpecNumber).required) {
validators.push(Validators.required) validators.push(Validators.required)
} }
@@ -216,7 +216,7 @@ function numberValidators(
function selectValidators(spec: ValueSpecSelect): ValidatorFn[] { function selectValidators(spec: ValueSpecSelect): ValidatorFn[] {
const validators: ValidatorFn[] = [] const validators: ValidatorFn[] = []
if (!spec.nullable) { if (spec.required) {
validators.push(Validators.required) validators.push(Validators.required)
} }
@@ -239,7 +239,7 @@ function listValidators(spec: ValueSpecList): ValidatorFn[] {
function fileValidators(spec: ValueSpecFile): ValidatorFn[] { function fileValidators(spec: ValueSpecFile): ValidatorFn[] {
const validators: ValidatorFn[] = [] const validators: ValidatorFn[] = []
if (!spec.nullable) { if (spec.required) {
validators.push(Validators.required) validators.push(Validators.required)
} }