retold-data-service 2.0.43 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1817,6 +1817,626 @@
1817
1817
  "fable-serviceproviderbase": 2
1818
1818
  }],
1819
1819
  7: [function (require, module, exports) {
1820
+ /**
1821
+ * Default Pict-view configuration for PictSection-ConnectionForm.
1822
+ *
1823
+ * Host applications register the view with their own ContainerSelector
1824
+ * and field-id prefix (so multiple connection forms can coexist without
1825
+ * DOM-id collisions). All other defaults live here.
1826
+ *
1827
+ * The host owns:
1828
+ * - Where in the DOM the form lands (DefaultDestinationAddress / TargetSelector)
1829
+ * - Where in AppData the schemas + active provider live (SchemasAddress / ActiveAddress)
1830
+ * - The DOM-id prefix used to namespace per-field input ids (FieldIDPrefix)
1831
+ * - Whether the provider <select> is visible at all (ShowProviderSelect)
1832
+ * - Whether the "Advanced" <details> block is rendered (ShowAdvancedToggle)
1833
+ */
1834
+ 'use strict';
1835
+
1836
+ module.exports = {
1837
+ ViewIdentifier: 'PictSection-ConnectionForm',
1838
+ DefaultRenderable: 'PictSection-ConnectionForm-Main',
1839
+ DefaultDestinationAddress: '#PictSection-ConnectionForm-Slot',
1840
+ AutoRender: false,
1841
+ // Host-overridable knobs
1842
+ SchemasAddress: 'AppData.Connection.Schemas',
1843
+ ActiveAddress: 'AppData.Connection.ActiveProvider',
1844
+ FieldIDPrefix: 'pict-conn',
1845
+ ShowProviderSelect: true,
1846
+ ShowAdvancedToggle: true
1847
+ };
1848
+ }, {}],
1849
+ 8: [function (require, module, exports) {
1850
+ /**
1851
+ * PictSection-ConnectionForm
1852
+ *
1853
+ * Schema-driven Meadow connection-form view. Renders a provider
1854
+ * <select> + per-provider field block from the form schemas exported
1855
+ * by each `meadow-connection-*` module (and aggregated server-side via
1856
+ * `meadow-connection-manager#getAllProviderFormSchemas()`).
1857
+ *
1858
+ * Three host applications consume this:
1859
+ * - retold-data-service / DataCloner (single active provider, "connect/test" UX)
1860
+ * - retold-databeacon / Connection list (add/edit named saved connections)
1861
+ * - retold-facto / Store connections (add/edit named saved connections)
1862
+ *
1863
+ * Each host wires it with a different DOM destination + AppData
1864
+ * address + DOM-id prefix so multiple connection forms can coexist
1865
+ * without colliding on element ids.
1866
+ *
1867
+ * ── Wiring contract ────────────────────────────────────────────────
1868
+ * Host AppData (configurable via SchemasAddress / ActiveAddress):
1869
+ * AppData.<...>.Schemas array of schemas (see field shape)
1870
+ * AppData.<...>.ActiveProvider string — currently selected Provider
1871
+ *
1872
+ * Host options on the view (registered via pict.addView):
1873
+ * ContainerSelector — where to render (overrides DefaultDestinationAddress)
1874
+ * SchemasAddress — AppData address of the Schemas array
1875
+ * ActiveAddress — AppData address of the ActiveProvider string
1876
+ * FieldIDPrefix — DOM-id namespace ('pict-conn' default)
1877
+ * ShowProviderSelect — whether to render the <select> (false = single-provider mode)
1878
+ * ShowAdvancedToggle — whether the Advanced group is collapsible
1879
+ * OnProviderChange(p) — optional callback when the user picks a different provider
1880
+ *
1881
+ * Host calls (instance methods):
1882
+ * setSchemas(pSchemas) — replace schema list and re-render
1883
+ * setActiveProvider(pProvider) — switch active provider
1884
+ * getProviderConfig() — collect form values → { Provider, Config }
1885
+ * setValues(pProvider, pConfig) — populate fields from a saved config blob
1886
+ * clear() — reset all fields to schema defaults
1887
+ *
1888
+ * ── Field shape (from each meadow-connection-* schema) ─────────────
1889
+ * Name — canonical config key (lowercase for SQL drivers, dotted for nested)
1890
+ * Label — UI label
1891
+ * Type — String | Number | Password | Boolean | Path | Select
1892
+ * Default — initial value
1893
+ * Required — boolean
1894
+ * Placeholder, Help, Min, Max — UI hints
1895
+ * Group — 'Basic' (default) or 'Advanced' (rendered under <details>)
1896
+ * Multiplier — form value × multiplier = stored value (sec→ms via 1000)
1897
+ * MapTo — array of dotted-path targets (one input → multiple keys)
1898
+ * OmitIfFalsy — drop key from emitted config when value is 0/empty/false
1899
+ * Options — for Select: [{ Value, Label }]
1900
+ *
1901
+ * Pure presentation — does NOT fetch schemas itself. Host fetches
1902
+ * them however it likes (typical: GET /<app>/connection/schemas
1903
+ * backed by MCM) and calls setSchemas() once they arrive.
1904
+ */
1905
+ 'use strict';
1906
+
1907
+ const libPictView = require('pict-view');
1908
+ const _DefaultConfiguration = require('./Pict-Section-ConnectionForm-DefaultConfiguration.js');
1909
+ const _BaseCSS = /*css*/`
1910
+ .pict-conn-form {
1911
+ display: flex;
1912
+ flex-direction: column;
1913
+ gap: 10px;
1914
+ }
1915
+ .pict-conn-form__provider-row {
1916
+ display: flex;
1917
+ gap: 10px;
1918
+ align-items: flex-end;
1919
+ }
1920
+ .pict-conn-form__provider-row label {
1921
+ font-size: 12px;
1922
+ font-weight: 600;
1923
+ color: #475569;
1924
+ text-transform: uppercase;
1925
+ letter-spacing: 0.3px;
1926
+ display: flex;
1927
+ flex-direction: column;
1928
+ gap: 4px;
1929
+ flex: 0 0 200px;
1930
+ }
1931
+ .pict-conn-form__provider-row select {
1932
+ font-family: inherit;
1933
+ font-size: 14px;
1934
+ padding: 7px 10px;
1935
+ border: 1px solid #cbd5e1;
1936
+ border-radius: 6px;
1937
+ background: #fff;
1938
+ color: #0f172a;
1939
+ height: 36px;
1940
+ }
1941
+ .pict-conn-form__provider-form {
1942
+ display: grid;
1943
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
1944
+ gap: 10px 16px;
1945
+ }
1946
+ .pict-conn-form__field {
1947
+ display: flex;
1948
+ flex-direction: column;
1949
+ gap: 4px;
1950
+ }
1951
+ .pict-conn-form__field label {
1952
+ font-size: 12px;
1953
+ font-weight: 600;
1954
+ color: #475569;
1955
+ text-transform: uppercase;
1956
+ letter-spacing: 0.3px;
1957
+ }
1958
+ .pict-conn-form__field input,
1959
+ .pict-conn-form__field select {
1960
+ font-family: inherit;
1961
+ font-size: 14px;
1962
+ padding: 7px 10px;
1963
+ border: 1px solid #cbd5e1;
1964
+ border-radius: 6px;
1965
+ background: #fff;
1966
+ color: #0f172a;
1967
+ }
1968
+ .pict-conn-form__field input[type="checkbox"] {
1969
+ width: auto;
1970
+ height: auto;
1971
+ align-self: flex-start;
1972
+ }
1973
+ .pict-conn-form__field-help {
1974
+ font-size: 11px;
1975
+ color: #64748b;
1976
+ }
1977
+ .pict-conn-form__advanced {
1978
+ grid-column: 1 / -1;
1979
+ margin-top: 4px;
1980
+ }
1981
+ .pict-conn-form__advanced > summary {
1982
+ cursor: pointer;
1983
+ font-weight: 600;
1984
+ color: #475569;
1985
+ font-size: 12px;
1986
+ text-transform: uppercase;
1987
+ letter-spacing: 0.3px;
1988
+ padding: 4px 0;
1989
+ }
1990
+ .pict-conn-form__advanced > p {
1991
+ margin: 8px 0;
1992
+ font-size: 12px;
1993
+ color: #64748b;
1994
+ }
1995
+ .pict-conn-form__advanced > .pict-conn-form__advanced-fields {
1996
+ display: grid;
1997
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
1998
+ gap: 10px 16px;
1999
+ }
2000
+ .pict-conn-form__no-schemas {
2001
+ padding: 12px;
2002
+ background: #fef3c7;
2003
+ border: 1px solid #f59e0b;
2004
+ border-radius: 6px;
2005
+ color: #92400e;
2006
+ font-size: 13px;
2007
+ }
2008
+ `;
2009
+ const _BaseTemplates = [{
2010
+ Hash: 'PictSection-ConnectionForm-Main',
2011
+ Template: /*html*/`
2012
+ <div class="pict-conn-form" id="{~D:AppData.PictSectionConnectionForm.RootId~}">
2013
+ {~TS:PictSection-ConnectionForm-Selector:AppData.PictSectionConnectionForm.SelectorSlot~}
2014
+ <div class="pict-conn-form__forms" id="{~D:AppData.PictSectionConnectionForm.FormsId~}">
2015
+ {~TS:PictSection-ConnectionForm-ProviderForm:AppData.PictSectionConnectionForm.ProviderForms~}
2016
+ </div>
2017
+ {~TS:PictSection-ConnectionForm-NoSchemas:AppData.PictSectionConnectionForm.NoSchemasSlot~}
2018
+ </div>`
2019
+ }, {
2020
+ Hash: 'PictSection-ConnectionForm-Selector',
2021
+ Template: /*html*/`
2022
+ <div class="pict-conn-form__provider-row">
2023
+ <label>Provider
2024
+ <select id="{~D:Record.SelectId~}" onchange="{~P~}.views['{~D:Record.ViewHash~}'].onProviderSelectChange(this.value)">
2025
+ {~TS:PictSection-ConnectionForm-ProviderOption:Record.Options~}
2026
+ </select>
2027
+ </label>
2028
+ </div>`
2029
+ }, {
2030
+ Hash: 'PictSection-ConnectionForm-ProviderOption',
2031
+ Template: /*html*/`<option value="{~D:Record.Provider~}" {~D:Record.SelectedAttr~}>{~D:Record.DisplayName~}</option>`
2032
+ }, {
2033
+ Hash: 'PictSection-ConnectionForm-ProviderForm',
2034
+ Template: /*html*/`
2035
+ <div class="pict-conn-form__provider-form" id="{~D:Record.FormId~}" style="display:{~D:Record.DisplayStyle~}">
2036
+ {~TS:PictSection-ConnectionForm-Field:Record.BasicFields~}
2037
+ {~TS:PictSection-ConnectionForm-Advanced:Record.AdvancedSlot~}
2038
+ </div>`
2039
+ }, {
2040
+ Hash: 'PictSection-ConnectionForm-Field',
2041
+ Template: /*html*/`
2042
+ <div class="pict-conn-form__field">
2043
+ <label for="{~D:Record.DOMId~}">{~D:Record.Label~}</label>
2044
+ {~D:Record.InputHTML~}
2045
+ {~TS:PictSection-ConnectionForm-FieldHelp:Record.HelpSlot~}
2046
+ </div>`
2047
+ }, {
2048
+ Hash: 'PictSection-ConnectionForm-FieldHelp',
2049
+ Template: /*html*/`<small class="pict-conn-form__field-help">{~D:Record.Help~}</small>`
2050
+ }, {
2051
+ Hash: 'PictSection-ConnectionForm-Advanced',
2052
+ Template: /*html*/`
2053
+ <details class="pict-conn-form__advanced">
2054
+ <summary>Advanced settings</summary>
2055
+ <p>Optional tuning — leave blank or zero to use the connection driver's defaults.</p>
2056
+ <div class="pict-conn-form__advanced-fields">
2057
+ {~TS:PictSection-ConnectionForm-Field:Record.Fields~}
2058
+ </div>
2059
+ </details>`
2060
+ }, {
2061
+ Hash: 'PictSection-ConnectionForm-NoSchemas',
2062
+ Template: /*html*/`<div class="pict-conn-form__no-schemas">No connection providers detected. Either <code>meadow-connection-manager</code> is older than 1.1.0 or no provider modules are installed in the host environment.</div>`
2063
+ }];
2064
+ class PictSectionConnectionForm extends libPictView {
2065
+ constructor(pFable, pOptions, pServiceHash) {
2066
+ // Merge host-supplied options on top of the module defaults.
2067
+ // Templates + CSS come from this module; host can override the
2068
+ // AppData addresses, the DOM destination, and the field-id prefix.
2069
+ let tmpOptions = Object.assign({}, _DefaultConfiguration, pOptions || {});
2070
+ if (!tmpOptions.Templates) {
2071
+ tmpOptions.Templates = _BaseTemplates;
2072
+ }
2073
+ if (!tmpOptions.CSS) {
2074
+ tmpOptions.CSS = _BaseCSS;
2075
+ }
2076
+ if (!tmpOptions.Renderables) {
2077
+ tmpOptions.Renderables = [{
2078
+ RenderableHash: 'PictSection-ConnectionForm-Main',
2079
+ TemplateHash: 'PictSection-ConnectionForm-Main',
2080
+ ContentDestinationAddress: tmpOptions.ContainerSelector || tmpOptions.DefaultDestinationAddress,
2081
+ RenderMethod: 'replace'
2082
+ }];
2083
+ }
2084
+ super(pFable, tmpOptions, pServiceHash);
2085
+ this._Schemas = [];
2086
+ this._ActiveProvider = '';
2087
+ }
2088
+
2089
+ // ====================================================================
2090
+ // Public API — hosts call these to drive the view
2091
+ // ====================================================================
2092
+
2093
+ setSchemas(pSchemas) {
2094
+ this._Schemas = Array.isArray(pSchemas) ? pSchemas : [];
2095
+ // If no active provider yet, default to the first schema.
2096
+ if (!this._ActiveProvider && this._Schemas.length > 0) {
2097
+ this._ActiveProvider = this._Schemas[0].Provider;
2098
+ }
2099
+ this._writeAppData();
2100
+ this.render();
2101
+ }
2102
+ setActiveProvider(pProvider) {
2103
+ this._ActiveProvider = pProvider || '';
2104
+ this._writeAppData();
2105
+ this.render();
2106
+ this._invokeProviderChangeCallback();
2107
+ }
2108
+ getActiveProvider() {
2109
+ return this._ActiveProvider;
2110
+ }
2111
+
2112
+ /**
2113
+ * Read the active provider's form values out of the DOM and
2114
+ * collect them into the canonical wire-format config blob the
2115
+ * provider's connection driver expects. Honors:
2116
+ * - Multiplier (form value × multiplier = stored value)
2117
+ * - MapTo (one input → multiple dotted-path targets)
2118
+ * - OmitIfFalsy (drop key when value is 0/empty/false)
2119
+ * - Type-aware reads (Boolean→.checked, Number→parseInt, else trimmed string)
2120
+ *
2121
+ * @returns {{Provider: string, Config: object}}
2122
+ */
2123
+ getProviderConfig() {
2124
+ let tmpProvider = this._ActiveProvider;
2125
+ let tmpSchema = this._Schemas.find(pS => pS.Provider === tmpProvider);
2126
+ if (!tmpSchema) {
2127
+ return {
2128
+ Provider: tmpProvider,
2129
+ Config: {}
2130
+ };
2131
+ }
2132
+ let tmpConfig = {};
2133
+ (tmpSchema.Fields || []).forEach(pField => this._collectField(tmpProvider, pField, tmpConfig));
2134
+ return {
2135
+ Provider: tmpProvider,
2136
+ Config: tmpConfig
2137
+ };
2138
+ }
2139
+
2140
+ /**
2141
+ * Populate the form from a saved config blob. Used by edit
2142
+ * workflows (DataBeacon / Facto) that load a named connection
2143
+ * record and want its values pre-filled.
2144
+ *
2145
+ * @param {string} pProvider
2146
+ * @param {object} pConfig — wire-format config (same shape getProviderConfig returns)
2147
+ */
2148
+ setValues(pProvider, pConfig) {
2149
+ this._ActiveProvider = pProvider || '';
2150
+ this._writeAppData();
2151
+ this.render();
2152
+ let tmpSchema = this._Schemas.find(pS => pS.Provider === pProvider);
2153
+ if (!tmpSchema || typeof document === 'undefined') {
2154
+ return;
2155
+ }
2156
+ (tmpSchema.Fields || []).forEach(pField => {
2157
+ let tmpDOMId = this.fieldDOMId(pProvider, pField.Name);
2158
+ let tmpEl = document.getElementById(tmpDOMId);
2159
+ if (!tmpEl) {
2160
+ return;
2161
+ }
2162
+ let tmpVal = this._readNested(pConfig || {}, pField.MapTo && pField.MapTo[0] ? pField.MapTo[0] : pField.Name);
2163
+ // Reverse-apply Multiplier (storage unit → display unit).
2164
+ if (pField.Multiplier && typeof tmpVal === 'number') {
2165
+ tmpVal = Math.floor(tmpVal / pField.Multiplier);
2166
+ }
2167
+ if (pField.Type === 'Boolean') {
2168
+ tmpEl.checked = !!tmpVal;
2169
+ } else if (tmpVal === undefined || tmpVal === null) {/* leave default */} else {
2170
+ tmpEl.value = String(tmpVal);
2171
+ }
2172
+ });
2173
+ }
2174
+ clear() {
2175
+ this._ActiveProvider = this._Schemas.length > 0 ? this._Schemas[0].Provider : '';
2176
+ this._writeAppData();
2177
+ this.render();
2178
+ }
2179
+
2180
+ // ====================================================================
2181
+ // DOM-id helper — host code can use this to find a specific input
2182
+ // ====================================================================
2183
+
2184
+ fieldDOMId(pProvider, pFieldName) {
2185
+ let tmpPrefix = this.options.FieldIDPrefix || 'pict-conn';
2186
+ let tmpProvider = String(pProvider || '').toLowerCase();
2187
+ let tmpField = String(pFieldName || '').replace(/\./g, '_');
2188
+ return `${tmpPrefix}-${tmpProvider}-${tmpField}`;
2189
+ }
2190
+
2191
+ // ====================================================================
2192
+ // Lifecycle hooks
2193
+ // ====================================================================
2194
+
2195
+ onBeforeRender(pRenderable) {
2196
+ this._writeAppData();
2197
+ return super.onBeforeRender(pRenderable);
2198
+ }
2199
+ onAfterRender(pRenderable, pAddress, pRecord, pContent) {
2200
+ // Toggle visibility on the active provider's form (the templates
2201
+ // pre-render a wrapper for every schema so values persist when
2202
+ // the user switches between providers).
2203
+ if (typeof document !== 'undefined') {
2204
+ this._Schemas.forEach(pSchema => {
2205
+ let tmpEl = document.getElementById(this._formId(pSchema.Provider));
2206
+ if (tmpEl) {
2207
+ tmpEl.style.display = pSchema.Provider === this._ActiveProvider ? '' : 'none';
2208
+ }
2209
+ });
2210
+ }
2211
+ this.pict.CSSMap.injectCSS();
2212
+ return super.onAfterRender(pRenderable, pAddress, pRecord, pContent);
2213
+ }
2214
+
2215
+ // ====================================================================
2216
+ // Selector-change handler (called from the rendered <select>)
2217
+ // ====================================================================
2218
+
2219
+ onProviderSelectChange(pProvider) {
2220
+ this.setActiveProvider(pProvider);
2221
+ }
2222
+
2223
+ // ====================================================================
2224
+ // Internals
2225
+ // ====================================================================
2226
+
2227
+ /**
2228
+ * Push the computed render records into AppData under the address
2229
+ * the templates read from. Templates always read from
2230
+ * `AppData.PictSectionConnectionForm.*` regardless of where the
2231
+ * host's "real" Schemas / ActiveProvider live; that's because Pict
2232
+ * template addresses are static strings and we want one set of
2233
+ * templates to work for many host configurations. The real
2234
+ * SchemasAddress / ActiveAddress are also written so hosts can
2235
+ * read them out for their own state-tracking.
2236
+ */
2237
+ _writeAppData() {
2238
+ if (!this.pict.AppData) {
2239
+ this.pict.AppData = {};
2240
+ }
2241
+ let tmpRoot = this.pict.AppData.PictSectionConnectionForm = this.pict.AppData.PictSectionConnectionForm || {};
2242
+ let tmpPrefix = this.options.FieldIDPrefix || 'pict-conn';
2243
+ tmpRoot.RootId = `${tmpPrefix}-root`;
2244
+ tmpRoot.FormsId = `${tmpPrefix}-forms`;
2245
+
2246
+ // Selector slot — empty array hides the <select>, single-element
2247
+ // renders it once. Honors ShowProviderSelect.
2248
+ if (this.options.ShowProviderSelect && this._Schemas.length > 0) {
2249
+ tmpRoot.SelectorSlot = [{
2250
+ SelectId: `${tmpPrefix}-provider-select`,
2251
+ ViewHash: this.Hash,
2252
+ Options: this._Schemas.map(pS => ({
2253
+ Provider: pS.Provider,
2254
+ DisplayName: this._escape(pS.DisplayName || pS.Provider),
2255
+ SelectedAttr: pS.Provider === this._ActiveProvider ? 'selected' : ''
2256
+ }))
2257
+ }];
2258
+ } else {
2259
+ tmpRoot.SelectorSlot = [];
2260
+ }
2261
+ tmpRoot.ProviderForms = this._Schemas.map(pSchema => this._buildProviderForm(pSchema, pSchema.Provider === this._ActiveProvider));
2262
+ tmpRoot.NoSchemasSlot = this._Schemas.length === 0 ? [{}] : [];
2263
+
2264
+ // Mirror state into the host's configured AppData addresses (so
2265
+ // hosts that read AppData directly see live values).
2266
+ if (this.options.SchemasAddress) {
2267
+ this._writeAppDataAddress(this.options.SchemasAddress, this._Schemas);
2268
+ }
2269
+ if (this.options.ActiveAddress) {
2270
+ this._writeAppDataAddress(this.options.ActiveAddress, this._ActiveProvider);
2271
+ }
2272
+ }
2273
+ _buildProviderForm(pSchema, pIsActive) {
2274
+ let tmpFields = pSchema.Fields || [];
2275
+ let tmpBasic = [];
2276
+ let tmpAdvanced = [];
2277
+ tmpFields.forEach(pField => {
2278
+ let tmpRecord = this._buildFieldRecord(pField, pSchema.Provider);
2279
+ if (pField.Group === 'Advanced') {
2280
+ tmpAdvanced.push(tmpRecord);
2281
+ } else {
2282
+ tmpBasic.push(tmpRecord);
2283
+ }
2284
+ });
2285
+ return {
2286
+ Provider: pSchema.Provider,
2287
+ FormId: this._formId(pSchema.Provider),
2288
+ DisplayStyle: pIsActive ? '' : 'none',
2289
+ BasicFields: tmpBasic,
2290
+ AdvancedSlot: this.options.ShowAdvancedToggle && tmpAdvanced.length > 0 ? [{
2291
+ Fields: tmpAdvanced
2292
+ }] : []
2293
+ };
2294
+ }
2295
+ _buildFieldRecord(pField, pProvider) {
2296
+ let tmpDOMId = this.fieldDOMId(pProvider, pField.Name);
2297
+ return {
2298
+ DOMId: tmpDOMId,
2299
+ Label: this._escape(pField.Label || pField.Name),
2300
+ InputHTML: this._buildInputHTML(pField, tmpDOMId),
2301
+ HelpSlot: pField.Help ? [{
2302
+ Help: this._escape(pField.Help)
2303
+ }] : []
2304
+ };
2305
+ }
2306
+ _buildInputHTML(pField, pDOMId) {
2307
+ let tmpDefault = pField.Default !== undefined && pField.Default !== null ? String(pField.Default) : '';
2308
+ let tmpPlaceholder = pField.Placeholder ? this._escape(pField.Placeholder) : '';
2309
+ let tmpRequired = pField.Required ? ' required' : '';
2310
+ switch (pField.Type) {
2311
+ case 'Number':
2312
+ {
2313
+ let tmpMin = pField.Min !== undefined && pField.Min !== null ? ` min="${this._escape(String(pField.Min))}"` : '';
2314
+ let tmpMax = pField.Max !== undefined && pField.Max !== null ? ` max="${this._escape(String(pField.Max))}"` : '';
2315
+ return `<input type="number" id="${this._escape(pDOMId)}" value="${this._escape(tmpDefault)}" placeholder="${tmpPlaceholder}"${tmpMin}${tmpMax}${tmpRequired}>`;
2316
+ }
2317
+ case 'Password':
2318
+ return `<input type="password" id="${this._escape(pDOMId)}" placeholder="${tmpPlaceholder || '(optional)'}"${tmpRequired}>`;
2319
+ case 'Boolean':
2320
+ {
2321
+ let tmpChecked = pField.Default ? ' checked' : '';
2322
+ return `<input type="checkbox" id="${this._escape(pDOMId)}"${tmpChecked}>`;
2323
+ }
2324
+ case 'Select':
2325
+ {
2326
+ let tmpOptions = (pField.Options || []).map(pOpt => {
2327
+ let tmpVal = String(pOpt.Value);
2328
+ let tmpSel = tmpVal === tmpDefault ? ' selected' : '';
2329
+ return `<option value="${this._escape(tmpVal)}"${tmpSel}>${this._escape(pOpt.Label || tmpVal)}</option>`;
2330
+ }).join('');
2331
+ return `<select id="${this._escape(pDOMId)}"${tmpRequired}>${tmpOptions}</select>`;
2332
+ }
2333
+ case 'Path':
2334
+ case 'String':
2335
+ default:
2336
+ return `<input type="text" id="${this._escape(pDOMId)}" value="${this._escape(tmpDefault)}" placeholder="${tmpPlaceholder}"${tmpRequired}>`;
2337
+ }
2338
+ }
2339
+ _collectField(pProvider, pField, pConfigOut) {
2340
+ if (typeof document === 'undefined') {
2341
+ return;
2342
+ }
2343
+ let tmpDOMId = this.fieldDOMId(pProvider, pField.Name);
2344
+ let tmpEl = document.getElementById(tmpDOMId);
2345
+ if (!tmpEl) {
2346
+ return;
2347
+ }
2348
+ let tmpRaw;
2349
+ if (pField.Type === 'Boolean') {
2350
+ tmpRaw = !!tmpEl.checked;
2351
+ } else if (pField.Type === 'Number') {
2352
+ let tmpParsed = parseInt(tmpEl.value, 10);
2353
+ tmpRaw = isNaN(tmpParsed) ? 0 : tmpParsed;
2354
+ } else {
2355
+ tmpRaw = String(tmpEl.value || '').trim();
2356
+ }
2357
+ let tmpFinal = tmpRaw;
2358
+ if (pField.Multiplier && typeof tmpFinal === 'number') {
2359
+ tmpFinal = tmpFinal * pField.Multiplier;
2360
+ }
2361
+ if (pField.OmitIfFalsy && !tmpFinal) {
2362
+ return;
2363
+ }
2364
+ let tmpTargets = pField.MapTo && pField.MapTo.length ? pField.MapTo : [pField.Name];
2365
+ tmpTargets.forEach(pPath => this._setNested(pConfigOut, pPath, tmpFinal));
2366
+ }
2367
+ _setNested(pTarget, pPath, pValue) {
2368
+ let tmpParts = String(pPath).split('.');
2369
+ let tmpCursor = pTarget;
2370
+ for (let i = 0; i < tmpParts.length - 1; i++) {
2371
+ let tmpKey = tmpParts[i];
2372
+ if (typeof tmpCursor[tmpKey] !== 'object' || tmpCursor[tmpKey] === null) {
2373
+ tmpCursor[tmpKey] = {};
2374
+ }
2375
+ tmpCursor = tmpCursor[tmpKey];
2376
+ }
2377
+ tmpCursor[tmpParts[tmpParts.length - 1]] = pValue;
2378
+ }
2379
+ _readNested(pSource, pPath) {
2380
+ let tmpParts = String(pPath).split('.');
2381
+ let tmpCursor = pSource;
2382
+ for (let i = 0; i < tmpParts.length; i++) {
2383
+ if (tmpCursor === undefined || tmpCursor === null) {
2384
+ return undefined;
2385
+ }
2386
+ tmpCursor = tmpCursor[tmpParts[i]];
2387
+ }
2388
+ return tmpCursor;
2389
+ }
2390
+ _writeAppDataAddress(pAddress, pValue) {
2391
+ // Address is "AppData.X.Y" — drop the leading "AppData." prefix
2392
+ // before walking; if it's missing, fall through and treat the
2393
+ // whole string as a property chain off pict.AppData.
2394
+ let tmpRoot = this.pict.AppData;
2395
+ let tmpAddr = String(pAddress || '');
2396
+ if (tmpAddr.indexOf('AppData.') === 0) {
2397
+ tmpAddr = tmpAddr.substring('AppData.'.length);
2398
+ }
2399
+ if (!tmpAddr) {
2400
+ return;
2401
+ }
2402
+ let tmpParts = tmpAddr.split('.');
2403
+ let tmpCursor = tmpRoot;
2404
+ for (let i = 0; i < tmpParts.length - 1; i++) {
2405
+ let tmpKey = tmpParts[i];
2406
+ if (typeof tmpCursor[tmpKey] !== 'object' || tmpCursor[tmpKey] === null) {
2407
+ tmpCursor[tmpKey] = {};
2408
+ }
2409
+ tmpCursor = tmpCursor[tmpKey];
2410
+ }
2411
+ tmpCursor[tmpParts[tmpParts.length - 1]] = pValue;
2412
+ }
2413
+ _invokeProviderChangeCallback() {
2414
+ let tmpCb = this.options.OnProviderChange;
2415
+ if (typeof tmpCb === 'function') {
2416
+ try {
2417
+ tmpCb(this._ActiveProvider);
2418
+ } catch (pError) {
2419
+ if (this.log && this.log.warn) {
2420
+ this.log.warn(`PictSection-ConnectionForm: OnProviderChange callback threw: ${pError && pError.message}`);
2421
+ }
2422
+ }
2423
+ }
2424
+ }
2425
+ _formId(pProvider) {
2426
+ let tmpPrefix = this.options.FieldIDPrefix || 'pict-conn';
2427
+ return `${tmpPrefix}-form-${String(pProvider || '').toLowerCase()}`;
2428
+ }
2429
+ _escape(pStr) {
2430
+ return String(pStr == null ? '' : pStr).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
2431
+ }
2432
+ }
2433
+ module.exports = PictSectionConnectionForm;
2434
+ module.exports.default_configuration = _DefaultConfiguration;
2435
+ }, {
2436
+ "./Pict-Section-ConnectionForm-DefaultConfiguration.js": 7,
2437
+ "pict-view": 15
2438
+ }],
2439
+ 9: [function (require, module, exports) {
1820
2440
  module.exports = {
1821
2441
  "RenderOnLoad": true,
1822
2442
  "DefaultRenderable": "Histogram-Wrap",
@@ -2068,7 +2688,7 @@
2068
2688
  `
2069
2689
  };
2070
2690
  }, {}],
2071
- 8: [function (require, module, exports) {
2691
+ 10: [function (require, module, exports) {
2072
2692
  /**
2073
2693
  * Pict Section Histogram
2074
2694
  *
@@ -2447,13 +3067,13 @@
2447
3067
  cli: libRendererCLI
2448
3068
  };
2449
3069
  }, {
2450
- "./Pict-Section-Histogram-DefaultConfiguration.js": 7,
2451
- "./renderers/Pict-Histogram-Renderer-Browser.js": 9,
2452
- "./renderers/Pict-Histogram-Renderer-CLI.js": 10,
2453
- "./renderers/Pict-Histogram-Renderer-ConsoleUI.js": 11,
2454
- "pict-view": 13
3070
+ "./Pict-Section-Histogram-DefaultConfiguration.js": 9,
3071
+ "./renderers/Pict-Histogram-Renderer-Browser.js": 11,
3072
+ "./renderers/Pict-Histogram-Renderer-CLI.js": 12,
3073
+ "./renderers/Pict-Histogram-Renderer-ConsoleUI.js": 13,
3074
+ "pict-view": 15
2455
3075
  }],
2456
- 9: [function (require, module, exports) {
3076
+ 11: [function (require, module, exports) {
2457
3077
  /**
2458
3078
  * Browser renderer for pict-section-histogram.
2459
3079
  *
@@ -2756,7 +3376,7 @@
2756
3376
  wireEvents
2757
3377
  };
2758
3378
  }, {}],
2759
- 10: [function (require, module, exports) {
3379
+ 12: [function (require, module, exports) {
2760
3380
  (function (process) {
2761
3381
  (function () {
2762
3382
  /**
@@ -3065,9 +3685,9 @@
3065
3685
  }).call(this);
3066
3686
  }).call(this, require('_process'));
3067
3687
  }, {
3068
- "_process": 14
3688
+ "_process": 16
3069
3689
  }],
3070
- 11: [function (require, module, exports) {
3690
+ 13: [function (require, module, exports) {
3071
3691
  /**
3072
3692
  * Console UI (blessed) renderer for pict-section-histogram.
3073
3693
  *
@@ -3312,7 +3932,7 @@
3312
3932
  renderHorizontal
3313
3933
  };
3314
3934
  }, {}],
3315
- 12: [function (require, module, exports) {
3935
+ 14: [function (require, module, exports) {
3316
3936
  module.exports = {
3317
3937
  "name": "pict-view",
3318
3938
  "version": "1.0.68",
@@ -3366,7 +3986,7 @@
3366
3986
  }
3367
3987
  };
3368
3988
  }, {}],
3369
- 13: [function (require, module, exports) {
3989
+ 15: [function (require, module, exports) {
3370
3990
  const libFableServiceBase = require('fable-serviceproviderbase');
3371
3991
  const libPackage = require('../package.json');
3372
3992
  const defaultPictViewSettings = {
@@ -4553,10 +5173,10 @@
4553
5173
  }
4554
5174
  module.exports = PictView;
4555
5175
  }, {
4556
- "../package.json": 12,
5176
+ "../package.json": 14,
4557
5177
  "fable-serviceproviderbase": 2
4558
5178
  }],
4559
- 14: [function (require, module, exports) {
5179
+ 16: [function (require, module, exports) {
4560
5180
  // shim for using process in browser
4561
5181
  var process = module.exports = {};
4562
5182
 
@@ -4733,7 +5353,7 @@
4733
5353
  return 0;
4734
5354
  };
4735
5355
  }, {}],
4736
- 15: [function (require, module, exports) {
5356
+ 17: [function (require, module, exports) {
4737
5357
  module.exports = {
4738
5358
  "Name": "Retold Data Cloner",
4739
5359
  "Hash": "DataCloner",
@@ -4746,7 +5366,7 @@
4746
5366
  "AutoRenderMainViewportViewAfterInitialize": false
4747
5367
  };
4748
5368
  }, {}],
4749
- 16: [function (require, module, exports) {
5369
+ 18: [function (require, module, exports) {
4750
5370
  const libPictApplication = require('pict-application');
4751
5371
  const libProvider = require('./providers/Pict-Provider-DataCloner.js');
4752
5372
  const libViewLayout = require('./views/PictView-DataCloner-Layout.js');
@@ -4758,6 +5378,7 @@
4758
5378
  const libViewExport = require('./views/PictView-DataCloner-Export.js');
4759
5379
  const libViewViewData = require('./views/PictView-DataCloner-ViewData.js');
4760
5380
  const libViewHistogram = require('pict-section-histogram');
5381
+ const libViewConnectionForm = require('pict-section-connection-form');
4761
5382
  class DataClonerApplication extends libPictApplication {
4762
5383
  constructor(pFable, pOptions, pServiceHash) {
4763
5384
  super(pFable, pOptions, pServiceHash);
@@ -4788,9 +5409,31 @@
4788
5409
  BarColor: '#4a90d9',
4789
5410
  Bins: []
4790
5411
  }, libViewHistogram);
5412
+
5413
+ // Shared schema-driven connection form. Renders into the slot
5414
+ // the DataCloner-Connection accordion shell exposes; the
5415
+ // provider's bootstrapConnectionSchemas() pumps the schemas in
5416
+ // once the host's /clone/connection/schemas endpoint responds.
5417
+ this.pict.addView('PictSection-ConnectionForm', Object.assign({}, libViewConnectionForm.default_configuration, {
5418
+ ContainerSelector: '#DataCloner-Connection-FormSlot',
5419
+ DefaultDestinationAddress: '#DataCloner-Connection-FormSlot',
5420
+ SchemasAddress: 'AppData.DataCloner.Connection.Schemas',
5421
+ ActiveAddress: 'AppData.DataCloner.Connection.ActiveProvider',
5422
+ FieldIDPrefix: 'datacloner-conn'
5423
+ }), libViewConnectionForm);
4791
5424
  }
4792
5425
  onAfterInitializeAsync(fCallback) {
4793
- // Centralized state (replaces global variables)
5426
+ // Centralized state (replaces global variables).
5427
+ //
5428
+ // PersistFields covers the static, non-connection inputs only.
5429
+ // Connection-section fields (provider picker + per-provider
5430
+ // inputs) are schema-driven now: their DOM ids and
5431
+ // localStorage keys are derived at runtime from the host's
5432
+ // /clone/connection/schemas response and persistence is hooked
5433
+ // up by Pict-Provider-DataCloner#bootstrapConnectionSchemas
5434
+ // after the schema-driven Connection view re-renders. See
5435
+ // PictView-DataCloner-Connection.js for the field-id
5436
+ // convention.
4794
5437
  this.pict.AppData.DataCloner = {
4795
5438
  FetchedTables: [],
4796
5439
  DeployedTables: [],
@@ -4803,44 +5446,64 @@
4803
5446
  StatusDetailTimer: null,
4804
5447
  StatusDetailData: null,
4805
5448
  LastLiveStatus: null,
4806
- PersistFields: ['serverURL', 'authMethod', 'authURI', 'checkURI', 'cookieName', 'cookieValueAddr', 'cookieValueTemplate', 'loginMarker', 'userName', 'password', 'schemaURL', 'pageSize', 'dateTimePrecisionMS', 'connProvider', 'sqliteFilePath', 'mysqlServer', 'mysqlPort', 'mysqlUser', 'mysqlPassword', 'mysqlDatabase', 'mysqlConnectionLimit', 'mssqlServer', 'mssqlPort', 'mssqlUser', 'mssqlPassword', 'mssqlDatabase', 'mssqlConnectionLimit', 'mssqlRequestTimeoutSec', 'mssqlConnectionTimeoutSec', 'mssqlConnectMaxAttempts', 'mssqlDDLMaxAttempts', 'mssqlRetryInitialDelaySec', 'mssqlRetryMaxDelaySec', 'postgresqlHost', 'postgresqlPort', 'postgresqlUser', 'postgresqlPassword', 'postgresqlDatabase', 'postgresqlConnectionLimit', 'solrHost', 'solrPort', 'solrCore', 'solrPath', 'mongodbHost', 'mongodbPort', 'mongodbUser', 'mongodbPassword', 'mongodbDatabase', 'mongodbConnectionLimit', 'rocksdbFolder', 'bibliographFolder', 'syncMaxRecords']
5449
+ PersistFields: ['serverURL', 'authMethod', 'authURI', 'checkURI', 'cookieName', 'cookieValueAddr', 'cookieValueTemplate', 'loginMarker', 'userName', 'password', 'schemaURL', 'pageSize', 'dateTimePrecisionMS', 'syncMaxRecords'],
5450
+ // Connection state — populated by bootstrapConnectionSchemas().
5451
+ // Initialized empty here so the Connection view's first
5452
+ // onBeforeRender finds a valid (if empty) shape.
5453
+ Connection: {
5454
+ Schemas: [],
5455
+ ActiveProvider: '',
5456
+ ProviderOptions: [],
5457
+ ProviderForms: [],
5458
+ NoSchemasSlot: [{}],
5459
+ PreviewText: 'Loading providers…'
5460
+ }
4807
5461
  };
4808
5462
 
4809
5463
  // Make pict available for inline onclick handlers
4810
5464
  window.pict = this.pict;
4811
5465
 
4812
- // Render layout (which chains child view renders via onAfterRender)
5466
+ // Render layout (which chains child view renders via onAfterRender).
5467
+ // The Connection view renders an empty shell here — the schemas
5468
+ // arrive asynchronously and trigger a re-render once they land.
4813
5469
  this.pict.views['DataCloner-Layout'].render();
4814
5470
 
4815
- // Post-render initialization
5471
+ // Post-render initialization for the static (non-connection) UI.
4816
5472
  this.pict.providers.DataCloner.initPersistence();
4817
- this.pict.views['DataCloner-Connection'].onProviderChange();
4818
5473
  this.pict.providers.DataCloner.restoreDeployedTables();
4819
5474
  this.pict.providers.DataCloner.startLiveStatusPolling();
4820
5475
  this.pict.providers.DataCloner.initAccordionPreviews();
4821
5476
  this.pict.providers.DataCloner.updateAllPreviews();
4822
5477
  this.pict.views['DataCloner-Layout'].collapseAllSections();
4823
5478
  this.pict.providers.DataCloner.initAutoProcess();
5479
+
5480
+ // Async: fetch the host's connection-form schemas and re-render
5481
+ // the Connection section. bootstrapConnectionSchemas restores
5482
+ // localStorage values + hooks save listeners once the new DOM
5483
+ // is in place, then invokes onProviderChange() to surface the
5484
+ // active provider's form.
5485
+ this.pict.providers.DataCloner.bootstrapConnectionSchemas(function () {/* fire-and-forget */});
4824
5486
  return fCallback();
4825
5487
  }
4826
5488
  }
4827
5489
  module.exports = DataClonerApplication;
4828
5490
  module.exports.default_configuration = require('./Pict-Application-DataCloner-Configuration.json');
4829
5491
  }, {
4830
- "./Pict-Application-DataCloner-Configuration.json": 15,
4831
- "./providers/Pict-Provider-DataCloner.js": 18,
4832
- "./views/PictView-DataCloner-Connection.js": 19,
4833
- "./views/PictView-DataCloner-Deploy.js": 20,
4834
- "./views/PictView-DataCloner-Export.js": 21,
4835
- "./views/PictView-DataCloner-Layout.js": 22,
4836
- "./views/PictView-DataCloner-Schema.js": 23,
4837
- "./views/PictView-DataCloner-Session.js": 24,
4838
- "./views/PictView-DataCloner-Sync.js": 25,
4839
- "./views/PictView-DataCloner-ViewData.js": 26,
5492
+ "./Pict-Application-DataCloner-Configuration.json": 17,
5493
+ "./providers/Pict-Provider-DataCloner.js": 20,
5494
+ "./views/PictView-DataCloner-Connection.js": 21,
5495
+ "./views/PictView-DataCloner-Deploy.js": 22,
5496
+ "./views/PictView-DataCloner-Export.js": 23,
5497
+ "./views/PictView-DataCloner-Layout.js": 24,
5498
+ "./views/PictView-DataCloner-Schema.js": 25,
5499
+ "./views/PictView-DataCloner-Session.js": 26,
5500
+ "./views/PictView-DataCloner-Sync.js": 27,
5501
+ "./views/PictView-DataCloner-ViewData.js": 28,
4840
5502
  "pict-application": 4,
4841
- "pict-section-histogram": 8
5503
+ "pict-section-connection-form": 8,
5504
+ "pict-section-histogram": 10
4842
5505
  }],
4843
- 17: [function (require, module, exports) {
5506
+ 19: [function (require, module, exports) {
4844
5507
  module.exports = {
4845
5508
  DataClonerApplication: require('./Pict-Application-DataCloner.js')
4846
5509
  };
@@ -4848,9 +5511,9 @@
4848
5511
  window.DataClonerApplication = module.exports.DataClonerApplication;
4849
5512
  }
4850
5513
  }, {
4851
- "./Pict-Application-DataCloner.js": 16
5514
+ "./Pict-Application-DataCloner.js": 18
4852
5515
  }],
4853
- 18: [function (require, module, exports) {
5516
+ 20: [function (require, module, exports) {
4854
5517
  const libPictProvider = require('pict-provider');
4855
5518
  class DataClonerProvider extends libPictProvider {
4856
5519
  constructor(pFable, pOptions, pServiceHash) {
@@ -4914,45 +5577,24 @@
4914
5577
  // ================================================================
4915
5578
 
4916
5579
  updateAllPreviews() {
4917
- // Section 1 — Database Connection
4918
- let tmpProvider = document.getElementById('connProvider');
4919
- if (!tmpProvider) return;
4920
- tmpProvider = tmpProvider.value;
4921
- let tmpPreview1 = tmpProvider;
4922
- if (tmpProvider === 'SQLite') {
4923
- let tmpPath = document.getElementById('sqliteFilePath').value || '~/headlight-liveconnect-local/cloned.sqlite';
4924
- tmpPreview1 = 'SQLite at ' + tmpPath;
4925
- } else if (tmpProvider === 'MySQL') {
4926
- let tmpHost = document.getElementById('mysqlServer').value || '127.0.0.1';
4927
- let tmpPort = document.getElementById('mysqlPort').value || '3306';
4928
- let tmpUser = document.getElementById('mysqlUser').value || 'root';
4929
- tmpPreview1 = 'MySQL on ' + tmpHost + ':' + tmpPort + ' as ' + tmpUser;
4930
- } else if (tmpProvider === 'MSSQL') {
4931
- let tmpHost = document.getElementById('mssqlServer').value || '127.0.0.1';
4932
- let tmpPort = document.getElementById('mssqlPort').value || '1433';
4933
- let tmpUser = document.getElementById('mssqlUser').value || 'sa';
4934
- tmpPreview1 = 'MSSQL on ' + tmpHost + ':' + tmpPort + ' as ' + tmpUser;
4935
- } else if (tmpProvider === 'PostgreSQL') {
4936
- let tmpHost = document.getElementById('postgresqlHost').value || '127.0.0.1';
4937
- let tmpPort = document.getElementById('postgresqlPort').value || '5432';
4938
- let tmpUser = document.getElementById('postgresqlUser').value || 'postgres';
4939
- tmpPreview1 = 'PostgreSQL on ' + tmpHost + ':' + tmpPort + ' as ' + tmpUser;
4940
- } else if (tmpProvider === 'MongoDB') {
4941
- let tmpHost = document.getElementById('mongodbHost').value || '127.0.0.1';
4942
- let tmpPort = document.getElementById('mongodbPort').value || '27017';
4943
- tmpPreview1 = 'MongoDB on ' + tmpHost + ':' + tmpPort;
4944
- } else if (tmpProvider === 'Solr') {
4945
- let tmpHost = document.getElementById('solrHost').value || '127.0.0.1';
4946
- let tmpPort = document.getElementById('solrPort').value || '8983';
4947
- tmpPreview1 = 'Solr on ' + tmpHost + ':' + tmpPort;
4948
- } else if (tmpProvider === 'RocksDB') {
4949
- let tmpFolder = document.getElementById('rocksdbFolder').value || '~/headlight-liveconnect-local/rocksdb';
4950
- tmpPreview1 = 'RocksDB at ' + tmpFolder;
4951
- } else if (tmpProvider === 'Bibliograph') {
4952
- let tmpFolder = document.getElementById('bibliographFolder').value || '~/headlight-liveconnect-local/bibliograph';
4953
- tmpPreview1 = 'Bibliograph at ' + tmpFolder;
4954
- }
4955
- document.getElementById('preview1').textContent = tmpPreview1;
5580
+ // Section 1 — Database Connection (schema-driven; the
5581
+ // Connection view owns the heuristic that turns the active
5582
+ // schema's field values into preview text). The view's
5583
+ // _buildPreviewText reads live DOM values for the active
5584
+ // provider and falls back to schema Defaults if a field's
5585
+ // element doesn't exist yet.
5586
+ let tmpConnView = this.pict.views['DataCloner-Connection'];
5587
+ let tmpConnState = this.pict.AppData.DataCloner && this.pict.AppData.DataCloner.Connection || null;
5588
+ let tmpPreview1Text;
5589
+ if (tmpConnView && tmpConnState && (tmpConnState.Schemas || []).length > 0) {
5590
+ tmpPreview1Text = tmpConnView._buildPreviewText(tmpConnState);
5591
+ } else {
5592
+ tmpPreview1Text = tmpConnState && tmpConnState.PreviewText || 'Loading providers…';
5593
+ }
5594
+ let tmpPreview1El = document.getElementById('preview1');
5595
+ if (tmpPreview1El) {
5596
+ tmpPreview1El.textContent = tmpPreview1Text;
5597
+ }
4956
5598
 
4957
5599
  // Section 2 — Remote Session
4958
5600
  let tmpServerURL = document.getElementById('serverURL').value;
@@ -5014,7 +5656,12 @@
5014
5656
  }
5015
5657
  initAccordionPreviews() {
5016
5658
  let tmpSelf = this;
5017
- let tmpPreviewFields = ['connProvider', 'sqliteFilePath', 'mysqlServer', 'mysqlPort', 'mysqlUser', 'mssqlServer', 'mssqlPort', 'mssqlUser', 'postgresqlHost', 'postgresqlPort', 'postgresqlUser', 'mongodbHost', 'mongodbPort', 'solrHost', 'solrPort', 'rocksdbFolder', 'bibliographFolder', 'serverURL', 'userName', 'schemaURL', 'pageSize', 'dateTimePrecisionMS', 'syncMaxRecords', 'viewTable', 'viewLimit'];
5659
+
5660
+ // Static (non-connection) fields that drive accordion previews.
5661
+ // Connection-section fields hook updateAllPreviews via
5662
+ // _persistConnectionFields() once schemas load — see
5663
+ // bootstrapConnectionSchemas().
5664
+ let tmpPreviewFields = ['connProvider', 'serverURL', 'userName', 'schemaURL', 'pageSize', 'dateTimePrecisionMS', 'syncMaxRecords', 'viewTable', 'viewLimit'];
5018
5665
  let tmpHandler = function () {
5019
5666
  tmpSelf.updateAllPreviews();
5020
5667
  };
@@ -5043,7 +5690,17 @@
5043
5690
 
5044
5691
  saveField(pFieldId) {
5045
5692
  let tmpEl = document.getElementById(pFieldId);
5046
- if (tmpEl) {
5693
+ if (!tmpEl) {
5694
+ return;
5695
+ }
5696
+ // Checkboxes persist .checked, everything else persists .value.
5697
+ // Older code special-cased a small set of checkbox ids (solrSecure,
5698
+ // mssqlLegacyPagination, etc.); the schema-driven path no longer
5699
+ // needs those — the checkbox handler in bootstrapConnectionSchemas
5700
+ // stores 'true' / 'false' which restore picks up below.
5701
+ if (tmpEl.type === 'checkbox') {
5702
+ localStorage.setItem('dataCloner_' + pFieldId, tmpEl.checked ? 'true' : 'false');
5703
+ } else {
5047
5704
  localStorage.setItem('dataCloner_' + pFieldId, tmpEl.value);
5048
5705
  }
5049
5706
  }
@@ -5054,33 +5711,33 @@
5054
5711
  let tmpSaved = localStorage.getItem('dataCloner_' + tmpId);
5055
5712
  if (tmpSaved !== null) {
5056
5713
  let tmpEl = document.getElementById(tmpId);
5057
- if (tmpEl) tmpEl.value = tmpSaved;
5714
+ if (tmpEl) {
5715
+ if (tmpEl.type === 'checkbox') {
5716
+ tmpEl.checked = tmpSaved === 'true';
5717
+ } else {
5718
+ tmpEl.value = tmpSaved;
5719
+ }
5720
+ }
5058
5721
  }
5059
5722
  }
5060
5723
 
5061
- // Restore checkbox state
5724
+ // Restore checkbox state for non-connection checkboxes that
5725
+ // aren't in PersistFields (these all live outside the schema-
5726
+ // driven Connection section, so they stay hardcoded).
5062
5727
  let tmpSyncDeleted = localStorage.getItem('dataCloner_syncDeletedRecords');
5063
5728
  if (tmpSyncDeleted !== null) {
5064
- document.getElementById('syncDeletedRecords').checked = tmpSyncDeleted === 'true';
5729
+ let tmpEl = document.getElementById('syncDeletedRecords');
5730
+ if (tmpEl) tmpEl.checked = tmpSyncDeleted === 'true';
5065
5731
  }
5066
- // Restore sync mode
5067
5732
  let tmpSyncMode = localStorage.getItem('dataCloner_syncMode');
5068
5733
  if (tmpSyncMode === 'Ongoing') {
5069
- document.getElementById('syncModeOngoing').checked = true;
5070
- }
5071
- let tmpSolrSecure = localStorage.getItem('dataCloner_solrSecure');
5072
- if (tmpSolrSecure !== null) {
5073
- document.getElementById('solrSecure').checked = tmpSolrSecure === 'true';
5734
+ let tmpEl = document.getElementById('syncModeOngoing');
5735
+ if (tmpEl) tmpEl.checked = true;
5074
5736
  }
5075
- let tmpMssqlLegacyPagination = localStorage.getItem('dataCloner_mssqlLegacyPagination');
5076
- if (tmpMssqlLegacyPagination !== null) {
5077
- let tmpEl = document.getElementById('mssqlLegacyPagination');
5078
- if (tmpEl) tmpEl.checked = tmpMssqlLegacyPagination === 'true';
5079
- }
5080
- // Restore advanced ID pagination checkbox
5081
5737
  let tmpAdvancedIDPagination = localStorage.getItem('dataCloner_syncAdvancedIDPagination');
5082
5738
  if (tmpAdvancedIDPagination !== null) {
5083
- document.getElementById('syncAdvancedIDPagination').checked = tmpAdvancedIDPagination === 'true';
5739
+ let tmpEl = document.getElementById('syncAdvancedIDPagination');
5740
+ if (tmpEl) tmpEl.checked = tmpAdvancedIDPagination === 'true';
5084
5741
  }
5085
5742
  }
5086
5743
  initPersistence() {
@@ -5116,21 +5773,9 @@
5116
5773
  });
5117
5774
  });
5118
5775
 
5119
- // Persist solr secure checkbox
5120
- let tmpSolrSecureEl = document.getElementById('solrSecure');
5121
- if (tmpSolrSecureEl) {
5122
- tmpSolrSecureEl.addEventListener('change', function () {
5123
- localStorage.setItem('dataCloner_solrSecure', this.checked);
5124
- });
5125
- }
5126
-
5127
- // Persist MSSQL legacy pagination checkbox
5128
- let tmpMssqlLegacyPaginationEl = document.getElementById('mssqlLegacyPagination');
5129
- if (tmpMssqlLegacyPaginationEl) {
5130
- tmpMssqlLegacyPaginationEl.addEventListener('change', function () {
5131
- localStorage.setItem('dataCloner_mssqlLegacyPagination', this.checked);
5132
- });
5133
- }
5776
+ // (Connection-section checkboxes solrSecure, mssqlLegacyPagination —
5777
+ // are handled by bootstrapConnectionSchemas() which hooks change
5778
+ // listeners after the schema-driven form renders.)
5134
5779
 
5135
5780
  // Persist advanced ID pagination checkbox
5136
5781
  let tmpAdvancedIDPaginationEl = document.getElementById('syncAdvancedIDPagination');
@@ -5156,6 +5801,135 @@
5156
5801
  }
5157
5802
  }
5158
5803
 
5804
+ // ================================================================
5805
+ // Connection Schemas Bootstrap
5806
+ //
5807
+ // Fetches the host's aggregated connection-form schemas and re-
5808
+ // renders the Connection view so it shows the real provider list +
5809
+ // per-provider field blocks. Then restores localStorage values
5810
+ // for the new (schema-driven) DOM ids and hooks save listeners.
5811
+ // ================================================================
5812
+
5813
+ bootstrapConnectionSchemas(fCallback) {
5814
+ let tmpSelf = this;
5815
+ let tmpDone = typeof fCallback === 'function' ? fCallback : function () {};
5816
+ this.api('GET', '/clone/connection/schemas').then(function (pData) {
5817
+ let tmpSchemas = pData && Array.isArray(pData.Schemas) ? pData.Schemas : [];
5818
+ tmpSelf._applyConnectionSchemas(tmpSchemas);
5819
+ return tmpDone(null, tmpSchemas);
5820
+ }).catch(function (pError) {
5821
+ if (tmpSelf.fable && tmpSelf.fable.log && tmpSelf.fable.log.error) {
5822
+ tmpSelf.fable.log.error(`DataCloner: failed to fetch connection schemas: ${pError && pError.message}`);
5823
+ }
5824
+ // On failure, leave the empty-schema state in place — the
5825
+ // shared view's "no schemas detected" notice will surface.
5826
+ tmpSelf._applyConnectionSchemas([]);
5827
+ return tmpDone(pError);
5828
+ });
5829
+ }
5830
+ _applyConnectionSchemas(pSchemas) {
5831
+ let tmpAppData = this.pict.AppData.DataCloner;
5832
+ if (!tmpAppData.Connection) {
5833
+ tmpAppData.Connection = {
5834
+ Schemas: [],
5835
+ ActiveProvider: '',
5836
+ PreviewText: ''
5837
+ };
5838
+ }
5839
+ tmpAppData.Connection.Schemas = pSchemas;
5840
+
5841
+ // Pick an initial ActiveProvider — restore from localStorage
5842
+ // if the saved value matches one of the available providers,
5843
+ // otherwise default to the first schema (or stay empty).
5844
+ let tmpAvailable = pSchemas.map(function (pS) {
5845
+ return pS.Provider;
5846
+ });
5847
+ let tmpSavedProvider = localStorage.getItem('dataCloner_activeProvider');
5848
+ if (tmpSavedProvider && tmpAvailable.indexOf(tmpSavedProvider) >= 0) {
5849
+ tmpAppData.Connection.ActiveProvider = tmpSavedProvider;
5850
+ } else if (pSchemas.length > 0) {
5851
+ tmpAppData.Connection.ActiveProvider = pSchemas[0].Provider;
5852
+ }
5853
+
5854
+ // Hand the schemas to the shared view, which renders the form
5855
+ // into #DataCloner-Connection-FormSlot.
5856
+ let tmpForm = this.pict.views['PictSection-ConnectionForm'];
5857
+ if (tmpForm) {
5858
+ let tmpSelf = this;
5859
+ tmpForm.options.OnProviderChange = function (pProvider) {
5860
+ tmpAppData.Connection.ActiveProvider = pProvider;
5861
+ localStorage.setItem('dataCloner_activeProvider', pProvider);
5862
+ // Re-hook persistence on the new active form's inputs
5863
+ // (the shared view re-renders on provider change, so the
5864
+ // previous input listeners are gone).
5865
+ tmpSelf._persistConnectionFields(pSchemas);
5866
+ tmpSelf.updateAllPreviews();
5867
+ };
5868
+ if (tmpAppData.Connection.ActiveProvider) {
5869
+ tmpForm._ActiveProvider = tmpAppData.Connection.ActiveProvider;
5870
+ }
5871
+ tmpForm.setSchemas(pSchemas);
5872
+ }
5873
+
5874
+ // Re-render the DataCloner accordion shell so the preview text
5875
+ // reflects the active provider.
5876
+ let tmpAccordion = this.pict.views['DataCloner-Connection'];
5877
+ if (tmpAccordion) {
5878
+ tmpAccordion.render();
5879
+ }
5880
+
5881
+ // Restore values + hook input listeners on the freshly-rendered
5882
+ // shared-view inputs.
5883
+ this._persistConnectionFields(pSchemas);
5884
+ this.updateAllPreviews();
5885
+ }
5886
+ _persistConnectionFields(pSchemas) {
5887
+ let tmpForm = this.pict.views['PictSection-ConnectionForm'];
5888
+ if (!tmpForm) {
5889
+ return;
5890
+ }
5891
+ let tmpSelf = this;
5892
+
5893
+ // Restore + hook every per-provider field. saveField()
5894
+ // dispatches on element type internally so checkboxes and
5895
+ // text inputs share the same path.
5896
+ (pSchemas || []).forEach(function (pSchema) {
5897
+ (pSchema.Fields || []).forEach(function (pField) {
5898
+ let tmpId = tmpForm.fieldDOMId(pSchema.Provider, pField.Name);
5899
+ let tmpEl = document.getElementById(tmpId);
5900
+ if (!tmpEl) {
5901
+ return;
5902
+ }
5903
+ let tmpSaved = localStorage.getItem('dataCloner_' + tmpId);
5904
+ if (tmpSaved !== null) {
5905
+ if (tmpEl.type === 'checkbox') {
5906
+ tmpEl.checked = tmpSaved === 'true';
5907
+ } else {
5908
+ tmpEl.value = tmpSaved;
5909
+ }
5910
+ }
5911
+
5912
+ // Avoid double-binding when the shared view re-renders
5913
+ // on provider change. We tag the element so subsequent
5914
+ // runs of this method are no-ops.
5915
+ if (tmpEl.dataset && tmpEl.dataset.dataclonerHooked === '1') {
5916
+ return;
5917
+ }
5918
+ if (tmpEl.dataset) {
5919
+ tmpEl.dataset.dataclonerHooked = '1';
5920
+ }
5921
+ tmpEl.addEventListener('input', function () {
5922
+ tmpSelf.saveField(tmpId);
5923
+ tmpSelf.updateAllPreviews();
5924
+ });
5925
+ tmpEl.addEventListener('change', function () {
5926
+ tmpSelf.saveField(tmpId);
5927
+ tmpSelf.updateAllPreviews();
5928
+ });
5929
+ });
5930
+ });
5931
+ }
5932
+
5159
5933
  // ================================================================
5160
5934
  // Live Status Indicator
5161
5935
  // ================================================================
@@ -5828,103 +6602,204 @@
5828
6602
  }, {
5829
6603
  "pict-provider": 6
5830
6604
  }],
5831
- 19: [function (require, module, exports) {
6605
+ 21: [function (require, module, exports) {
6606
+ /**
6607
+ * DataCloner — Database Connection (section 1)
6608
+ *
6609
+ * Thin accordion shell + connect/test wiring around the shared
6610
+ * `pict-section-connection-form` view, which owns the schema-driven
6611
+ * provider <select> + per-provider field rendering. The shared view
6612
+ * is registered separately in Pict-Application-DataCloner.js and
6613
+ * renders into the FormSlot below.
6614
+ *
6615
+ * Flow:
6616
+ * 1. Layout renders, this view paints the accordion shell with an
6617
+ * empty FormSlot + "Loading providers…" preview text.
6618
+ * 2. Pict-Provider-DataCloner#bootstrapConnectionSchemas() fetches
6619
+ * GET /clone/connection/schemas and calls setSchemas() on the
6620
+ * shared view, which renders the per-provider form into FormSlot.
6621
+ * 3. User clicks Connect / Test → this view delegates to the shared
6622
+ * view's getProviderConfig() to assemble the wire-format payload,
6623
+ * then POSTs to /clone/connection/{configure,test}.
6624
+ *
6625
+ * Earlier versions of this view contained the schema rendering inline;
6626
+ * that logic has been lifted into pict-section-connection-form so
6627
+ * retold-databeacon and retold-facto can share it. See:
6628
+ * modules/pict/pict-section-connection-form/source/Pict-Section-ConnectionForm.js
6629
+ */
6630
+ 'use strict';
6631
+
5832
6632
  const libPictView = require('pict-view');
6633
+ const _ViewConfiguration = {
6634
+ ViewIdentifier: 'DataCloner-Connection',
6635
+ DefaultRenderable: 'DataCloner-Connection',
6636
+ DefaultDestinationAddress: '#DataCloner-Section-Connection',
6637
+ Templates: [{
6638
+ Hash: 'DataCloner-Connection',
6639
+ Template: /*html*/`
6640
+ <div class="accordion-row">
6641
+ <div class="accordion-number">1</div>
6642
+ <div class="accordion-card" id="section1" data-section="1">
6643
+ <div class="accordion-header" onclick="pict.views['DataCloner-Layout'].toggleSection('section1')">
6644
+ <label class="accordion-auto" onclick="event.stopPropagation()"><input type="checkbox" id="auto1"> <span class="auto-label">auto</span></label>
6645
+ <div class="accordion-title">Database Connection</div>
6646
+ <span class="accordion-phase" id="phase1"></span>
6647
+ <div class="accordion-preview" id="preview1">{~D:AppData.DataCloner.Connection.PreviewText~}</div>
6648
+ <div class="accordion-actions">
6649
+ <span class="accordion-go" onclick="event.stopPropagation(); pict.views['DataCloner-Connection'].connectProvider()">go</span>
6650
+ </div>
6651
+ <div class="accordion-toggle">&#9660;</div>
6652
+ </div>
6653
+ <div class="accordion-body">
6654
+ <p style="font-size:0.9em; color:#666; margin-bottom:10px">Configure the local database where cloned data will be stored. The provider list comes from the host's installed meadow-connection modules.</p>
6655
+
6656
+ <div class="inline-group" style="margin-bottom:10px">
6657
+ <div style="flex:1; display:flex; align-items:flex-end; gap:8px; justify-content:flex-end">
6658
+ <button class="primary" onclick="pict.views['DataCloner-Connection'].connectProvider()">Connect</button>
6659
+ <button class="secondary" onclick="pict.views['DataCloner-Connection'].testConnection()">Test Connection</button>
6660
+ </div>
6661
+ </div>
6662
+
6663
+ <!-- pict-section-connection-form renders here -->
6664
+ <div id="DataCloner-Connection-FormSlot"></div>
6665
+
6666
+ <div id="connectionStatus"></div>
6667
+ </div>
6668
+ </div>
6669
+ </div>`
6670
+ }],
6671
+ Renderables: [{
6672
+ RenderableHash: 'DataCloner-Connection',
6673
+ TemplateHash: 'DataCloner-Connection',
6674
+ DestinationAddress: '#DataCloner-Section-Connection'
6675
+ }]
6676
+ };
5833
6677
  class DataClonerConnectionView extends libPictView {
5834
6678
  constructor(pFable, pOptions, pServiceHash) {
5835
6679
  super(pFable, pOptions, pServiceHash);
5836
6680
  }
5837
- onProviderChange() {
5838
- let tmpProvider = document.getElementById('connProvider').value;
5839
- let tmpProviders = ['SQLite', 'MySQL', 'MSSQL', 'PostgreSQL', 'Solr', 'MongoDB', 'RocksDB', 'Bibliograph'];
5840
- for (let i = 0; i < tmpProviders.length; i++) {
5841
- let tmpEl = document.getElementById('config' + tmpProviders[i]);
5842
- if (tmpEl) {
5843
- tmpEl.style.display = tmpProvider === tmpProviders[i] ? '' : 'none';
6681
+
6682
+ // ====================================================================
6683
+ // Lifecycle
6684
+ // ====================================================================
6685
+
6686
+ onBeforeRender(pRenderable) {
6687
+ // Make sure AppData has the slot the accordion preview reads
6688
+ // from (the Provider's bootstrapConnectionSchemas() will fill in
6689
+ // the live values once schemas arrive).
6690
+ if (!this.pict.AppData.DataCloner) {
6691
+ this.pict.AppData.DataCloner = {};
6692
+ }
6693
+ if (!this.pict.AppData.DataCloner.Connection) {
6694
+ this.pict.AppData.DataCloner.Connection = {
6695
+ Schemas: [],
6696
+ ActiveProvider: '',
6697
+ PreviewText: 'Loading providers…'
6698
+ };
6699
+ }
6700
+ return super.onBeforeRender(pRenderable);
6701
+ }
6702
+
6703
+ // ====================================================================
6704
+ // Helpers used by the provider's preview / persistence layer
6705
+ // ====================================================================
6706
+
6707
+ /**
6708
+ * Build the section 1 accordion preview text. Reads live DOM
6709
+ * values via the shared view if it's mounted, otherwise falls back
6710
+ * to schema defaults.
6711
+ *
6712
+ * @param {{Schemas: object[], ActiveProvider: string}} pState
6713
+ * @returns {string}
6714
+ */
6715
+ _buildPreviewText(pState) {
6716
+ let tmpActive = pState.ActiveProvider;
6717
+ let tmpSchema = (pState.Schemas || []).find(pS => pS.Provider === tmpActive);
6718
+ if (!tmpSchema) {
6719
+ return tmpActive || '(no provider selected)';
6720
+ }
6721
+
6722
+ // Heuristics:
6723
+ // - file-based providers (single Path field) → "<DisplayName> at <path>"
6724
+ // - host/port/user providers → "<DisplayName> on host:port [as user]"
6725
+ // Host/port/user are matched by canonical schema field names.
6726
+ let tmpFields = tmpSchema.Fields || [];
6727
+ let tmpPath = tmpFields.find(pF => pF.Type === 'Path');
6728
+ if (tmpPath) {
6729
+ let tmpVal = this._readFieldValue(tmpSchema.Provider, tmpPath) || tmpPath.Default || '';
6730
+ return `${tmpSchema.DisplayName} at ${tmpVal}`;
6731
+ }
6732
+ let tmpHostField = tmpFields.find(pF => pF.Name === 'host' || pF.Name === 'server');
6733
+ let tmpPortField = tmpFields.find(pF => pF.Name === 'port');
6734
+ let tmpUserField = tmpFields.find(pF => pF.Name === 'user');
6735
+ if (tmpHostField && tmpPortField) {
6736
+ let tmpHost = this._readFieldValue(tmpSchema.Provider, tmpHostField) || tmpHostField.Default || '';
6737
+ let tmpPort = this._readFieldValue(tmpSchema.Provider, tmpPortField) || tmpPortField.Default || '';
6738
+ let tmpPreview = `${tmpSchema.DisplayName} on ${tmpHost}:${tmpPort}`;
6739
+ if (tmpUserField) {
6740
+ let tmpUser = this._readFieldValue(tmpSchema.Provider, tmpUserField) || tmpUserField.Default || '';
6741
+ if (tmpUser) {
6742
+ tmpPreview += ` as ${tmpUser}`;
6743
+ }
5844
6744
  }
6745
+ return tmpPreview;
5845
6746
  }
5846
- this.pict.providers.DataCloner.saveField('connProvider');
6747
+ return tmpSchema.DisplayName;
5847
6748
  }
5848
- getProviderConfig() {
5849
- let tmpProvider = document.getElementById('connProvider').value;
5850
- let tmpConfig = {};
5851
- if (tmpProvider === 'SQLite') {
5852
- tmpConfig.SQLiteFilePath = document.getElementById('sqliteFilePath').value.trim() || '~/headlight-liveconnect-local/cloned.sqlite';
5853
- } else if (tmpProvider === 'MySQL') {
5854
- tmpConfig.host = document.getElementById('mysqlServer').value.trim() || '127.0.0.1';
5855
- tmpConfig.port = parseInt(document.getElementById('mysqlPort').value, 10) || 3306;
5856
- tmpConfig.user = document.getElementById('mysqlUser').value.trim() || 'root';
5857
- tmpConfig.password = document.getElementById('mysqlPassword').value;
5858
- tmpConfig.database = document.getElementById('mysqlDatabase').value.trim();
5859
- tmpConfig.connectionLimit = parseInt(document.getElementById('mysqlConnectionLimit').value, 10) || 20;
5860
- } else if (tmpProvider === 'MSSQL') {
5861
- tmpConfig.server = document.getElementById('mssqlServer').value.trim() || '127.0.0.1';
5862
- tmpConfig.port = parseInt(document.getElementById('mssqlPort').value, 10) || 1433;
5863
- tmpConfig.user = document.getElementById('mssqlUser').value.trim() || 'sa';
5864
- tmpConfig.password = document.getElementById('mssqlPassword').value;
5865
- tmpConfig.database = document.getElementById('mssqlDatabase').value.trim();
5866
- tmpConfig.connectionLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
5867
- // Use ROW_NUMBER() pagination instead of OFFSET/FETCH for
5868
- // SQL Server 2008 R2 / 2012 or databases whose compatibility
5869
- // level is < 110 (the parser rejects OFFSET/FETCH syntax
5870
- // otherwise).
5871
- tmpConfig.LegacyPagination = document.getElementById('mssqlLegacyPagination').checked;
5872
-
5873
- // Reliability tuning — pull from the advanced settings block.
5874
- // All are optional; the connection provider falls back to
5875
- // sensible defaults when a value is missing or zero.
5876
- let tmpReqSec = parseInt(document.getElementById('mssqlRequestTimeoutSec').value, 10);
5877
- if (tmpReqSec > 0) tmpConfig.RequestTimeoutMs = tmpReqSec * 1000;
5878
- let tmpConnSec = parseInt(document.getElementById('mssqlConnectionTimeoutSec').value, 10);
5879
- if (tmpConnSec > 0) tmpConfig.ConnectionTimeoutMs = tmpConnSec * 1000;
5880
- let tmpConnectAttempts = parseInt(document.getElementById('mssqlConnectMaxAttempts').value, 10);
5881
- let tmpDDLAttempts = parseInt(document.getElementById('mssqlDDLMaxAttempts').value, 10);
5882
- let tmpInitialDelaySec = parseInt(document.getElementById('mssqlRetryInitialDelaySec').value, 10);
5883
- let tmpMaxDelaySec = parseInt(document.getElementById('mssqlRetryMaxDelaySec').value, 10);
5884
- if (tmpConnectAttempts > 0 || tmpInitialDelaySec > 0 || tmpMaxDelaySec > 0) {
5885
- tmpConfig.ConnectRetryOptions = {};
5886
- if (tmpConnectAttempts > 0) tmpConfig.ConnectRetryOptions.MaxAttempts = tmpConnectAttempts;
5887
- if (tmpInitialDelaySec > 0) tmpConfig.ConnectRetryOptions.InitialDelayMs = tmpInitialDelaySec * 1000;
5888
- if (tmpMaxDelaySec > 0) tmpConfig.ConnectRetryOptions.MaxDelayMs = tmpMaxDelaySec * 1000;
5889
- }
5890
- if (tmpDDLAttempts > 0 || tmpInitialDelaySec > 0 || tmpMaxDelaySec > 0) {
5891
- tmpConfig.DDLRetryOptions = {};
5892
- if (tmpDDLAttempts > 0) tmpConfig.DDLRetryOptions.MaxAttempts = tmpDDLAttempts;
5893
- if (tmpInitialDelaySec > 0) tmpConfig.DDLRetryOptions.InitialDelayMs = tmpInitialDelaySec * 1000;
5894
- if (tmpMaxDelaySec > 0) tmpConfig.DDLRetryOptions.MaxDelayMs = tmpMaxDelaySec * 1000;
5895
- }
5896
- } else if (tmpProvider === 'PostgreSQL') {
5897
- tmpConfig.host = document.getElementById('postgresqlHost').value.trim() || '127.0.0.1';
5898
- tmpConfig.port = parseInt(document.getElementById('postgresqlPort').value, 10) || 5432;
5899
- tmpConfig.user = document.getElementById('postgresqlUser').value.trim() || 'postgres';
5900
- tmpConfig.password = document.getElementById('postgresqlPassword').value;
5901
- tmpConfig.database = document.getElementById('postgresqlDatabase').value.trim();
5902
- tmpConfig.max = parseInt(document.getElementById('postgresqlConnectionLimit').value, 10) || 10;
5903
- } else if (tmpProvider === 'Solr') {
5904
- tmpConfig.host = document.getElementById('solrHost').value.trim() || 'localhost';
5905
- tmpConfig.port = parseInt(document.getElementById('solrPort').value, 10) || 8983;
5906
- tmpConfig.core = document.getElementById('solrCore').value.trim() || 'default';
5907
- tmpConfig.path = document.getElementById('solrPath').value.trim() || '/solr';
5908
- tmpConfig.secure = document.getElementById('solrSecure').checked;
5909
- } else if (tmpProvider === 'MongoDB') {
5910
- tmpConfig.host = document.getElementById('mongodbHost').value.trim() || '127.0.0.1';
5911
- tmpConfig.port = parseInt(document.getElementById('mongodbPort').value, 10) || 27017;
5912
- tmpConfig.user = document.getElementById('mongodbUser').value.trim();
5913
- tmpConfig.password = document.getElementById('mongodbPassword').value;
5914
- tmpConfig.database = document.getElementById('mongodbDatabase').value.trim() || 'test';
5915
- tmpConfig.maxPoolSize = parseInt(document.getElementById('mongodbConnectionLimit').value, 10) || 10;
5916
- } else if (tmpProvider === 'RocksDB') {
5917
- tmpConfig.RocksDBFolder = document.getElementById('rocksdbFolder').value.trim() || 'data/rocksdb';
5918
- } else if (tmpProvider === 'Bibliograph') {
5919
- tmpConfig.StorageFolder = document.getElementById('bibliographFolder').value.trim() || 'data/bibliograph';
6749
+ _readFieldValue(pProvider, pField) {
6750
+ if (typeof document === 'undefined') {
6751
+ return '';
5920
6752
  }
6753
+ let tmpForm = this.pict.views['PictSection-ConnectionForm'];
6754
+ if (!tmpForm) {
6755
+ return '';
6756
+ }
6757
+ let tmpEl = document.getElementById(tmpForm.fieldDOMId(pProvider, pField.Name));
6758
+ if (!tmpEl) {
6759
+ return '';
6760
+ }
6761
+ if (pField.Type === 'Boolean') {
6762
+ return tmpEl.checked ? 'true' : '';
6763
+ }
6764
+ return tmpEl.value;
6765
+ }
6766
+
6767
+ /**
6768
+ * DOM-id resolver for fields owned by the shared view. Forwarded
6769
+ * to PictSection-ConnectionForm so the provider's persistence
6770
+ * layer (saveField, restoreFields) can compute element ids without
6771
+ * needing to know the prefix.
6772
+ */
6773
+ fieldDOMId(pProvider, pFieldName) {
6774
+ let tmpForm = this.pict.views['PictSection-ConnectionForm'];
6775
+ if (tmpForm && typeof tmpForm.fieldDOMId === 'function') {
6776
+ return tmpForm.fieldDOMId(pProvider, pFieldName);
6777
+ }
6778
+ // Fallback before the shared view is mounted.
6779
+ let tmpProvider = String(pProvider || '').toLowerCase();
6780
+ let tmpField = String(pFieldName || '').replace(/\./g, '_');
6781
+ return `datacloner-conn-${tmpProvider}-${tmpField}`;
6782
+ }
6783
+
6784
+ // ====================================================================
6785
+ // Connect / Test — delegate field collection to the shared view
6786
+ // ====================================================================
6787
+
6788
+ getProviderConfig() {
6789
+ let tmpForm = this.pict.views['PictSection-ConnectionForm'];
6790
+ if (tmpForm && typeof tmpForm.getProviderConfig === 'function') {
6791
+ return tmpForm.getProviderConfig();
6792
+ }
6793
+ // Shared view not mounted yet (early bootstrap, or schemas
6794
+ // failed to load). Surface a plain empty payload — the
6795
+ // connect/test handlers downstream will report the failure
6796
+ // from the server side.
5921
6797
  return {
5922
- Provider: tmpProvider,
5923
- Config: tmpConfig
6798
+ Provider: '',
6799
+ Config: {}
5924
6800
  };
5925
6801
  }
5926
6802
  connectProvider() {
5927
- // Guard against re-entrant calls (e.g. rapid auto-connect polling)
5928
6803
  if (this._connectInFlight) {
5929
6804
  return;
5930
6805
  }
@@ -5967,288 +6842,15 @@
5967
6842
  this.pict.providers.DataCloner.setStatus('connectionStatus', 'Connected: ' + pData.Provider, 'ok');
5968
6843
  this.pict.providers.DataCloner.setSectionPhase(1, 'ok');
5969
6844
  }
5970
- }).catch(() => {
5971
- /* ignore */
5972
- });
6845
+ }).catch(() => {/* ignore */});
5973
6846
  }
5974
6847
  }
5975
6848
  module.exports = DataClonerConnectionView;
5976
- module.exports.default_configuration = {
5977
- ViewIdentifier: 'DataCloner-Connection',
5978
- DefaultRenderable: 'DataCloner-Connection',
5979
- DefaultDestinationAddress: '#DataCloner-Section-Connection',
5980
- Templates: [{
5981
- Hash: 'DataCloner-Connection',
5982
- Template: /*html*/`
5983
- <div class="accordion-row">
5984
- <div class="accordion-number">1</div>
5985
- <div class="accordion-card" id="section1" data-section="1">
5986
- <div class="accordion-header" onclick="pict.views['DataCloner-Layout'].toggleSection('section1')">
5987
- <label class="accordion-auto" onclick="event.stopPropagation()"><input type="checkbox" id="auto1"> <span class="auto-label">auto</span></label>
5988
- <div class="accordion-title">Database Connection</div>
5989
- <span class="accordion-phase" id="phase1"></span>
5990
- <div class="accordion-preview" id="preview1">SQLite at data/cloned.sqlite</div>
5991
- <div class="accordion-actions">
5992
- <span class="accordion-go" onclick="event.stopPropagation(); pict.views['DataCloner-Connection'].connectProvider()">go</span>
5993
- </div>
5994
- <div class="accordion-toggle">&#9660;</div>
5995
- </div>
5996
- <div class="accordion-body">
5997
- <p style="font-size:0.9em; color:#666; margin-bottom:10px">Configure the local database where cloned data will be stored. SQLite is connected by default.</p>
5998
-
5999
- <div class="inline-group">
6000
- <div style="flex:0 0 200px">
6001
- <label for="connProvider">Provider</label>
6002
- <select id="connProvider" onchange="pict.views['DataCloner-Connection'].onProviderChange()">
6003
- <option value="SQLite" selected>SQLite</option>
6004
- <option value="MySQL">MySQL</option>
6005
- <option value="MSSQL">MSSQL</option>
6006
- <option value="PostgreSQL">PostgreSQL</option>
6007
- <option value="Solr">Solr</option>
6008
- <option value="MongoDB">MongoDB</option>
6009
- <option value="RocksDB">RocksDB</option>
6010
- <option value="Bibliograph">Bibliograph</option>
6011
- </select>
6012
- </div>
6013
- <div style="flex:1; display:flex; align-items:flex-end; gap:8px">
6014
- <button class="primary" onclick="pict.views['DataCloner-Connection'].connectProvider()">Connect</button>
6015
- <button class="secondary" onclick="pict.views['DataCloner-Connection'].testConnection()">Test Connection</button>
6016
- </div>
6017
- </div>
6018
-
6019
- <!-- SQLite Config -->
6020
- <div id="configSQLite">
6021
- <label for="sqliteFilePath">SQLite File Path</label>
6022
- <input type="text" id="sqliteFilePath" placeholder="~/headlight-liveconnect-local/cloned.sqlite" value="~/headlight-liveconnect-local/cloned.sqlite">
6023
- </div>
6024
-
6025
- <!-- MySQL Config -->
6026
- <div id="configMySQL" style="display:none">
6027
- <div class="inline-group">
6028
- <div style="flex:2">
6029
- <label for="mysqlServer">Server</label>
6030
- <input type="text" id="mysqlServer" placeholder="127.0.0.1" value="127.0.0.1">
6031
- </div>
6032
- <div style="flex:1">
6033
- <label for="mysqlPort">Port</label>
6034
- <input type="number" id="mysqlPort" placeholder="3306" value="3306">
6035
- </div>
6036
- </div>
6037
- <div class="inline-group">
6038
- <div>
6039
- <label for="mysqlUser">User</label>
6040
- <input type="text" id="mysqlUser" placeholder="root" value="root">
6041
- </div>
6042
- <div>
6043
- <label for="mysqlPassword">Password</label>
6044
- <input type="password" id="mysqlPassword" placeholder="password">
6045
- </div>
6046
- </div>
6047
- <label for="mysqlDatabase">Database</label>
6048
- <input type="text" id="mysqlDatabase" placeholder="meadow_clone">
6049
- <div class="inline-group">
6050
- <div>
6051
- <label for="mysqlConnectionLimit">Connection Limit</label>
6052
- <input type="number" id="mysqlConnectionLimit" placeholder="20" value="20">
6053
- </div>
6054
- <div></div>
6055
- </div>
6056
- </div>
6057
-
6058
- <!-- MSSQL Config -->
6059
- <div id="configMSSQL" style="display:none">
6060
- <div class="inline-group">
6061
- <div style="flex:2">
6062
- <label for="mssqlServer">Server</label>
6063
- <input type="text" id="mssqlServer" placeholder="127.0.0.1" value="127.0.0.1">
6064
- </div>
6065
- <div style="flex:1">
6066
- <label for="mssqlPort">Port</label>
6067
- <input type="number" id="mssqlPort" placeholder="1433" value="1433">
6068
- </div>
6069
- </div>
6070
- <div class="inline-group">
6071
- <div>
6072
- <label for="mssqlUser">User</label>
6073
- <input type="text" id="mssqlUser" placeholder="sa" value="sa">
6074
- </div>
6075
- <div>
6076
- <label for="mssqlPassword">Password</label>
6077
- <input type="password" id="mssqlPassword" placeholder="password">
6078
- </div>
6079
- </div>
6080
- <label for="mssqlDatabase">Database</label>
6081
- <input type="text" id="mssqlDatabase" placeholder="meadow_clone">
6082
- <div class="inline-group">
6083
- <div>
6084
- <label for="mssqlConnectionLimit">Connection Limit</label>
6085
- <input type="number" id="mssqlConnectionLimit" placeholder="20" value="20">
6086
- </div>
6087
- <div></div>
6088
- </div>
6089
- <div style="margin-top:8px">
6090
- <input type="checkbox" id="mssqlLegacyPagination">
6091
- <label for="mssqlLegacyPagination" title="Enable for SQL Server 2008 R2 / 2012 or databases at compatibility_level &lt; 110. Uses ROW_NUMBER() pagination instead of OFFSET/FETCH.">Legacy pagination (SQL Server &lt; 2012 / compat level &lt; 110)</label>
6092
- </div>
6093
-
6094
- <details style="margin-top:8px">
6095
- <summary style="cursor:pointer; font-weight:600">Reliability &amp; timeouts (advanced)</summary>
6096
- <p style="margin:8px 0; font-size:0.9em; color:#555">Tune these for slow / flaky customer networks. Connection and DDL operations will retry with exponential backoff, classifying each failure (NetworkError / RequestTimeout / PoolDegraded / ServerError) in the logs so the failure mode is obvious.</p>
6097
- <div class="inline-group">
6098
- <div>
6099
- <label for="mssqlRequestTimeoutSec">Request timeout (sec)</label>
6100
- <input type="number" id="mssqlRequestTimeoutSec" placeholder="120" value="120">
6101
- </div>
6102
- <div>
6103
- <label for="mssqlConnectionTimeoutSec">Connection timeout (sec)</label>
6104
- <input type="number" id="mssqlConnectionTimeoutSec" placeholder="60" value="60">
6105
- </div>
6106
- </div>
6107
- <div class="inline-group">
6108
- <div>
6109
- <label for="mssqlConnectMaxAttempts">Connect retries (max attempts)</label>
6110
- <input type="number" id="mssqlConnectMaxAttempts" placeholder="5" value="5" min="1" max="20">
6111
- </div>
6112
- <div>
6113
- <label for="mssqlDDLMaxAttempts">DDL retries (max attempts)</label>
6114
- <input type="number" id="mssqlDDLMaxAttempts" placeholder="5" value="5" min="1" max="20">
6115
- </div>
6116
- </div>
6117
- <div class="inline-group">
6118
- <div>
6119
- <label for="mssqlRetryInitialDelaySec">Retry initial delay (sec)</label>
6120
- <input type="number" id="mssqlRetryInitialDelaySec" placeholder="3" value="3" min="1" max="60">
6121
- </div>
6122
- <div>
6123
- <label for="mssqlRetryMaxDelaySec">Retry max delay (sec)</label>
6124
- <input type="number" id="mssqlRetryMaxDelaySec" placeholder="30" value="30" min="1" max="600">
6125
- </div>
6126
- </div>
6127
- </details>
6128
- </div>
6129
-
6130
- <!-- PostgreSQL Config -->
6131
- <div id="configPostgreSQL" style="display:none">
6132
- <div class="inline-group">
6133
- <div style="flex:2">
6134
- <label for="postgresqlHost">Host</label>
6135
- <input type="text" id="postgresqlHost" placeholder="127.0.0.1" value="127.0.0.1">
6136
- </div>
6137
- <div style="flex:1">
6138
- <label for="postgresqlPort">Port</label>
6139
- <input type="number" id="postgresqlPort" placeholder="5432" value="5432">
6140
- </div>
6141
- </div>
6142
- <div class="inline-group">
6143
- <div>
6144
- <label for="postgresqlUser">User</label>
6145
- <input type="text" id="postgresqlUser" placeholder="postgres" value="postgres">
6146
- </div>
6147
- <div>
6148
- <label for="postgresqlPassword">Password</label>
6149
- <input type="password" id="postgresqlPassword" placeholder="password">
6150
- </div>
6151
- </div>
6152
- <label for="postgresqlDatabase">Database</label>
6153
- <input type="text" id="postgresqlDatabase" placeholder="meadow_clone">
6154
- <div class="inline-group">
6155
- <div>
6156
- <label for="postgresqlConnectionLimit">Connection Pool Limit</label>
6157
- <input type="number" id="postgresqlConnectionLimit" placeholder="10" value="10">
6158
- </div>
6159
- <div></div>
6160
- </div>
6161
- </div>
6162
-
6163
- <!-- Solr Config -->
6164
- <div id="configSolr" style="display:none">
6165
- <div class="inline-group">
6166
- <div style="flex:2">
6167
- <label for="solrHost">Host</label>
6168
- <input type="text" id="solrHost" placeholder="localhost" value="localhost">
6169
- </div>
6170
- <div style="flex:1">
6171
- <label for="solrPort">Port</label>
6172
- <input type="number" id="solrPort" placeholder="8983" value="8983">
6173
- </div>
6174
- </div>
6175
- <div class="inline-group">
6176
- <div style="flex:2">
6177
- <label for="solrCore">Core</label>
6178
- <input type="text" id="solrCore" placeholder="default" value="default">
6179
- </div>
6180
- <div style="flex:1">
6181
- <label for="solrPath">Path</label>
6182
- <input type="text" id="solrPath" placeholder="/solr" value="/solr">
6183
- </div>
6184
- </div>
6185
- <div class="checkbox-row">
6186
- <input type="checkbox" id="solrSecure">
6187
- <label for="solrSecure">Use HTTPS</label>
6188
- </div>
6189
- </div>
6190
-
6191
- <!-- MongoDB Config -->
6192
- <div id="configMongoDB" style="display:none">
6193
- <div class="inline-group">
6194
- <div style="flex:2">
6195
- <label for="mongodbHost">Host</label>
6196
- <input type="text" id="mongodbHost" placeholder="127.0.0.1" value="127.0.0.1">
6197
- </div>
6198
- <div style="flex:1">
6199
- <label for="mongodbPort">Port</label>
6200
- <input type="number" id="mongodbPort" placeholder="27017" value="27017">
6201
- </div>
6202
- </div>
6203
- <div class="inline-group">
6204
- <div>
6205
- <label for="mongodbUser">User</label>
6206
- <input type="text" id="mongodbUser" placeholder="(optional)">
6207
- </div>
6208
- <div>
6209
- <label for="mongodbPassword">Password</label>
6210
- <input type="password" id="mongodbPassword" placeholder="(optional)">
6211
- </div>
6212
- </div>
6213
- <label for="mongodbDatabase">Database</label>
6214
- <input type="text" id="mongodbDatabase" placeholder="test" value="test">
6215
- <div class="inline-group">
6216
- <div>
6217
- <label for="mongodbConnectionLimit">Max Pool Size</label>
6218
- <input type="number" id="mongodbConnectionLimit" placeholder="10" value="10">
6219
- </div>
6220
- <div></div>
6221
- </div>
6222
- </div>
6223
-
6224
- <!-- RocksDB Config -->
6225
- <div id="configRocksDB" style="display:none">
6226
- <label for="rocksdbFolder">RocksDB Folder Path</label>
6227
- <input type="text" id="rocksdbFolder" placeholder="data/rocksdb" value="data/rocksdb">
6228
- </div>
6229
-
6230
- <!-- Bibliograph Config -->
6231
- <div id="configBibliograph" style="display:none">
6232
- <label for="bibliographFolder">Storage Folder Path</label>
6233
- <input type="text" id="bibliographFolder" placeholder="data/bibliograph" value="data/bibliograph">
6234
- </div>
6235
-
6236
- <div id="connectionStatus"></div>
6237
- </div>
6238
- </div>
6239
- </div>
6240
- `
6241
- }],
6242
- Renderables: [{
6243
- RenderableHash: 'DataCloner-Connection',
6244
- TemplateHash: 'DataCloner-Connection',
6245
- DestinationAddress: '#DataCloner-Section-Connection'
6246
- }]
6247
- };
6849
+ module.exports.default_configuration = _ViewConfiguration;
6248
6850
  }, {
6249
- "pict-view": 13
6851
+ "pict-view": 15
6250
6852
  }],
6251
- 20: [function (require, module, exports) {
6853
+ 22: [function (require, module, exports) {
6252
6854
  const libPictView = require('pict-view');
6253
6855
  class DataClonerDeployView extends libPictView {
6254
6856
  constructor(pFable, pOptions, pServiceHash) {
@@ -6409,51 +7011,26 @@
6409
7011
  }]
6410
7012
  };
6411
7013
  }, {
6412
- "pict-view": 13
7014
+ "pict-view": 15
6413
7015
  }],
6414
- 21: [function (require, module, exports) {
7016
+ 23: [function (require, module, exports) {
6415
7017
  const libPictView = require('pict-view');
6416
7018
  class DataClonerExportView extends libPictView {
6417
7019
  constructor(pFable, pOptions, pServiceHash) {
6418
7020
  super(pFable, pOptions, pServiceHash);
6419
7021
  }
6420
7022
  buildConfigObject() {
6421
- let tmpProvider = document.getElementById('connProvider').value;
7023
+ // LocalDatabase block — schema-driven via the Connection view's
7024
+ // getProviderConfig(). The wire format produced there matches
7025
+ // what each meadow-connection provider expects (lowercase field
7026
+ // names for SQL drivers, PascalCase for SQLite path, etc.).
7027
+ let tmpConnInfo = this.pict.views['DataCloner-Connection'].getProviderConfig();
7028
+ let tmpProvider = tmpConnInfo.Provider;
6422
7029
  let tmpConfig = {};
6423
-
6424
- // ---- Local Database ----
6425
7030
  tmpConfig.LocalDatabase = {
6426
7031
  Provider: tmpProvider,
6427
- Config: {}
7032
+ Config: tmpConnInfo.Config || {}
6428
7033
  };
6429
- let tmpDbConfig = tmpConfig.LocalDatabase.Config;
6430
- if (tmpProvider === 'SQLite') {
6431
- tmpDbConfig.SQLiteFilePath = document.getElementById('sqliteFilePath').value.trim() || '~/headlight-liveconnect-local/cloned.sqlite';
6432
- } else if (tmpProvider === 'MySQL') {
6433
- tmpDbConfig.host = document.getElementById('mysqlServer').value.trim() || '127.0.0.1';
6434
- tmpDbConfig.port = parseInt(document.getElementById('mysqlPort').value, 10) || 3306;
6435
- tmpDbConfig.user = document.getElementById('mysqlUser').value.trim() || 'root';
6436
- tmpDbConfig.password = document.getElementById('mysqlPassword').value;
6437
- tmpDbConfig.database = document.getElementById('mysqlDatabase').value.trim();
6438
- tmpDbConfig.connectionLimit = parseInt(document.getElementById('mysqlConnectionLimit').value, 10) || 20;
6439
- } else if (tmpProvider === 'MSSQL') {
6440
- tmpDbConfig.server = document.getElementById('mssqlServer').value.trim() || '127.0.0.1';
6441
- tmpDbConfig.port = parseInt(document.getElementById('mssqlPort').value, 10) || 1433;
6442
- tmpDbConfig.user = document.getElementById('mssqlUser').value.trim() || 'sa';
6443
- tmpDbConfig.password = document.getElementById('mssqlPassword').value;
6444
- tmpDbConfig.database = document.getElementById('mssqlDatabase').value.trim();
6445
- tmpDbConfig.connectionLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
6446
- if (document.getElementById('mssqlLegacyPagination').checked) {
6447
- tmpDbConfig.LegacyPagination = true;
6448
- }
6449
- } else if (tmpProvider === 'PostgreSQL') {
6450
- tmpDbConfig.host = document.getElementById('postgresqlHost').value.trim() || '127.0.0.1';
6451
- tmpDbConfig.port = parseInt(document.getElementById('postgresqlPort').value, 10) || 5432;
6452
- tmpDbConfig.user = document.getElementById('postgresqlUser').value.trim() || 'postgres';
6453
- tmpDbConfig.password = document.getElementById('postgresqlPassword').value;
6454
- tmpDbConfig.database = document.getElementById('postgresqlDatabase').value.trim();
6455
- tmpDbConfig.max = parseInt(document.getElementById('postgresqlConnectionLimit').value, 10) || 10;
6456
- }
6457
7034
 
6458
7035
  // ---- Remote Session ----
6459
7036
  tmpConfig.RemoteSession = {};
@@ -6516,7 +7093,14 @@
6516
7093
  return tmpConfig;
6517
7094
  }
6518
7095
  buildMeadowIntegrationConfig() {
6519
- let tmpProvider = document.getElementById('connProvider').value;
7096
+ // Pull current connection values via the schema-driven Connection
7097
+ // view. buildConfigObject() above can use the result directly,
7098
+ // but meadow-integration's clone CLI expects a slightly different
7099
+ // destination shape — `server` instead of `host`, `ConnectionPoolLimit`
7100
+ // instead of `connectionLimit` for MSSQL — so we adapt here.
7101
+ let tmpConnInfo = this.pict.views['DataCloner-Connection'].getProviderConfig();
7102
+ let tmpProvider = tmpConnInfo.Provider;
7103
+ let tmpConfigConn = tmpConnInfo.Config || {};
6520
7104
  let tmpConfig = {};
6521
7105
 
6522
7106
  // ---- Source ----
@@ -6533,23 +7117,25 @@
6533
7117
  tmpConfig.Destination = {};
6534
7118
  if (tmpProvider === 'MySQL') {
6535
7119
  tmpConfig.Destination.Provider = 'MySQL';
6536
- tmpConfig.Destination.MySQL = {};
6537
- tmpConfig.Destination.MySQL.server = document.getElementById('mysqlServer').value.trim() || '127.0.0.1';
6538
- tmpConfig.Destination.MySQL.port = parseInt(document.getElementById('mysqlPort').value, 10) || 3306;
6539
- tmpConfig.Destination.MySQL.user = document.getElementById('mysqlUser').value.trim() || 'root';
6540
- tmpConfig.Destination.MySQL.password = document.getElementById('mysqlPassword').value || '';
6541
- tmpConfig.Destination.MySQL.database = document.getElementById('mysqlDatabase').value.trim() || 'meadow';
6542
- tmpConfig.Destination.MySQL.connectionLimit = parseInt(document.getElementById('mysqlConnectionLimit').value, 10) || 20;
7120
+ tmpConfig.Destination.MySQL = {
7121
+ server: tmpConfigConn.host || '127.0.0.1',
7122
+ port: tmpConfigConn.port || 3306,
7123
+ user: tmpConfigConn.user || 'root',
7124
+ password: tmpConfigConn.password || '',
7125
+ database: tmpConfigConn.database || 'meadow',
7126
+ connectionLimit: tmpConfigConn.connectionLimit || 20
7127
+ };
6543
7128
  } else if (tmpProvider === 'MSSQL') {
6544
7129
  tmpConfig.Destination.Provider = 'MSSQL';
6545
- tmpConfig.Destination.MSSQL = {};
6546
- tmpConfig.Destination.MSSQL.server = document.getElementById('mssqlServer').value.trim() || '127.0.0.1';
6547
- tmpConfig.Destination.MSSQL.port = parseInt(document.getElementById('mssqlPort').value, 10) || 1433;
6548
- tmpConfig.Destination.MSSQL.user = document.getElementById('mssqlUser').value.trim() || 'sa';
6549
- tmpConfig.Destination.MSSQL.password = document.getElementById('mssqlPassword').value || '';
6550
- tmpConfig.Destination.MSSQL.database = document.getElementById('mssqlDatabase').value.trim() || 'meadow';
6551
- tmpConfig.Destination.MSSQL.ConnectionPoolLimit = parseInt(document.getElementById('mssqlConnectionLimit').value, 10) || 20;
6552
- if (document.getElementById('mssqlLegacyPagination').checked) {
7130
+ tmpConfig.Destination.MSSQL = {
7131
+ server: tmpConfigConn.server || '127.0.0.1',
7132
+ port: tmpConfigConn.port || 1433,
7133
+ user: tmpConfigConn.user || 'sa',
7134
+ password: tmpConfigConn.password || '',
7135
+ database: tmpConfigConn.database || 'meadow',
7136
+ ConnectionPoolLimit: tmpConfigConn.connectionLimit || 20
7137
+ };
7138
+ if (tmpConfigConn.LegacyPagination) {
6553
7139
  tmpConfig.Destination.MSSQL.LegacyPagination = true;
6554
7140
  }
6555
7141
  } else {
@@ -6852,9 +7438,9 @@
6852
7438
  }]
6853
7439
  };
6854
7440
  }, {
6855
- "pict-view": 13
7441
+ "pict-view": 15
6856
7442
  }],
6857
- 22: [function (require, module, exports) {
7443
+ 24: [function (require, module, exports) {
6858
7444
  const libPictView = require('pict-view');
6859
7445
  class DataClonerLayoutView extends libPictView {
6860
7446
  constructor(pFable, pOptions, pServiceHash) {
@@ -7256,9 +7842,9 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
7256
7842
  }]
7257
7843
  };
7258
7844
  }, {
7259
- "pict-view": 13
7845
+ "pict-view": 15
7260
7846
  }],
7261
- 23: [function (require, module, exports) {
7847
+ 25: [function (require, module, exports) {
7262
7848
  const libPictView = require('pict-view');
7263
7849
  class DataClonerSchemaView extends libPictView {
7264
7850
  constructor(pFable, pOptions, pServiceHash) {
@@ -7444,9 +8030,9 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
7444
8030
  }]
7445
8031
  };
7446
8032
  }, {
7447
- "pict-view": 13
8033
+ "pict-view": 15
7448
8034
  }],
7449
- 24: [function (require, module, exports) {
8035
+ 26: [function (require, module, exports) {
7450
8036
  const libPictView = require('pict-view');
7451
8037
  class DataClonerSessionView extends libPictView {
7452
8038
  constructor(pFable, pOptions, pServiceHash) {
@@ -7639,9 +8225,9 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
7639
8225
  }]
7640
8226
  };
7641
8227
  }, {
7642
- "pict-view": 13
8228
+ "pict-view": 15
7643
8229
  }],
7644
- 25: [function (require, module, exports) {
8230
+ 27: [function (require, module, exports) {
7645
8231
  const libPictView = require('pict-view');
7646
8232
  class DataClonerSyncView extends libPictView {
7647
8233
  constructor(pFable, pOptions, pServiceHash) {
@@ -8175,9 +8761,9 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
8175
8761
  }]
8176
8762
  };
8177
8763
  }, {
8178
- "pict-view": 13
8764
+ "pict-view": 15
8179
8765
  }],
8180
- 26: [function (require, module, exports) {
8766
+ 28: [function (require, module, exports) {
8181
8767
  const libPictView = require('pict-view');
8182
8768
  class DataClonerViewDataView extends libPictView {
8183
8769
  constructor(pFable, pOptions, pServiceHash) {
@@ -8312,8 +8898,8 @@ select { background: #fff; width: 100%; padding: 8px 12px; border: 1px solid #cc
8312
8898
  }]
8313
8899
  };
8314
8900
  }, {
8315
- "pict-view": 13
8901
+ "pict-view": 15
8316
8902
  }]
8317
- }, {}, [17])(17);
8903
+ }, {}, [19])(19);
8318
8904
  });
8319
8905
  //# sourceMappingURL=data-cloner.js.map