mgv-backoffice 1.36.0 → 1.36.2
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.
- package/README.md +1794 -1719
- package/dist/components/BaseBadge.vue.d.ts +1 -11
- package/dist/components/BaseDropdown.vue.d.ts +1 -1
- package/dist/ui-lib.js +942 -925
- package/dist/ui-lib.umd.cjs +1 -1
- package/dist/utils/specForm.d.ts +1 -1
- package/package.json +54 -54
- package/src/components/BaseAlert.vue +104 -104
- package/src/components/BaseBadge.vue +12 -2
- package/src/components/BaseBarDistribution.test.ts +51 -51
- package/src/components/BaseBarDistribution.vue +73 -73
- package/src/components/BaseChipButton.vue +42 -42
- package/src/components/BaseCodeBlock.test.ts +70 -70
- package/src/components/BaseCodeBlock.vue +74 -74
- package/src/components/BaseCollapsibleSection.vue +113 -113
- package/src/components/BaseConfirmModal.vue +114 -114
- package/src/components/BaseCredentialsForm.test.ts +108 -108
- package/src/components/BaseCredentialsForm.vue +241 -241
- package/src/components/BaseDropdown.test.ts +87 -87
- package/src/components/BaseDropdown.vue +172 -172
- package/src/components/BaseEntityPickerModal.vue +330 -330
- package/src/components/BaseFileDropzone.vue +94 -94
- package/src/components/BaseFilterChip.test.ts +44 -44
- package/src/components/BaseFilterChip.vue +67 -67
- package/src/components/BaseModalShell.vue +209 -209
- package/src/components/BasePageHeader.vue +120 -120
- package/src/components/BasePillPickerModal.test.ts +95 -95
- package/src/components/BaseRemoveButton.vue +22 -22
- package/src/components/BaseSpecFields.test.ts +39 -0
- package/src/components/BaseSpecFields.vue +27 -3
- package/src/components/BaseStatBreakdown.test.ts +56 -56
- package/src/components/BaseStatBreakdown.vue +49 -49
- package/src/components/BaseStatusPill.vue +54 -54
- package/src/components/BaseTextInputModal.vue +140 -140
- package/src/components/BaseToolbarButton.test.ts +48 -48
- package/src/components/BaseToolbarButton.vue +94 -94
- package/src/composables/useFieldClasses.test.ts +55 -55
- package/src/composables/useFieldClasses.ts +56 -56
- package/src/composables/usePolling.test.ts +112 -112
- package/src/composables/usePolling.ts +102 -102
- package/src/composables/useQueryParamSync.test.ts +56 -56
- package/src/composables/useQueryParamSync.ts +42 -42
- package/src/composables/useThemeClasses.test.ts +34 -34
- package/src/composables/useThemeClasses.ts +137 -137
- package/src/index.ts +138 -138
- package/src/style.css +32 -32
- package/src/types/distribution.ts +7 -7
- package/src/types/pillPicker.ts +14 -14
- package/src/types/specField.ts +30 -30
- package/src/types/statBreakdown.ts +6 -6
- package/src/utils/format.test.ts +264 -264
- package/src/utils/format.ts +216 -216
- package/src/utils/httpColors.test.ts +100 -100
- package/src/utils/httpColors.ts +99 -99
- package/src/utils/kvRows.test.ts +47 -47
- package/src/utils/kvRows.ts +34 -34
- package/src/utils/specForm.test.ts +64 -53
- package/src/utils/specForm.ts +49 -48
- package/src/utils/validate.test.ts +82 -82
- package/src/utils/validate.ts +76 -76
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
isValidAbsoluteUrl,
|
|
4
|
-
isValidJson,
|
|
5
|
-
isValidXml,
|
|
6
|
-
isValidBase64,
|
|
7
|
-
} from './validate'
|
|
8
|
-
|
|
9
|
-
describe('isValidAbsoluteUrl', () => {
|
|
10
|
-
it('accepts absolute http/https URLs', () => {
|
|
11
|
-
expect(isValidAbsoluteUrl('http://example.com')).toBe(true)
|
|
12
|
-
expect(isValidAbsoluteUrl('https://example.com/path?q=1')).toBe(true)
|
|
13
|
-
expect(isValidAbsoluteUrl(' https://example.com ')).toBe(true)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('rejects empty, relative and non-http schemes', () => {
|
|
17
|
-
expect(isValidAbsoluteUrl('')).toBe(false)
|
|
18
|
-
expect(isValidAbsoluteUrl(' ')).toBe(false)
|
|
19
|
-
expect(isValidAbsoluteUrl('/relative/path')).toBe(false)
|
|
20
|
-
expect(isValidAbsoluteUrl('example.com')).toBe(false)
|
|
21
|
-
expect(isValidAbsoluteUrl('ftp://example.com')).toBe(false)
|
|
22
|
-
expect(isValidAbsoluteUrl('mailto:a@b.c')).toBe(false)
|
|
23
|
-
expect(isValidAbsoluteUrl('not a url')).toBe(false)
|
|
24
|
-
})
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
describe('isValidJson', () => {
|
|
28
|
-
it('treats empty input as valid', () => {
|
|
29
|
-
expect(isValidJson('')).toBe(true)
|
|
30
|
-
expect(isValidJson(' \n ')).toBe(true)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('accepts well-formed JSON of any root type', () => {
|
|
34
|
-
expect(isValidJson('{"a":1}')).toBe(true)
|
|
35
|
-
expect(isValidJson('[1,2]')).toBe(true)
|
|
36
|
-
expect(isValidJson('"str"')).toBe(true)
|
|
37
|
-
expect(isValidJson('42')).toBe(true)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('rejects malformed JSON', () => {
|
|
41
|
-
expect(isValidJson('{a:1}')).toBe(false)
|
|
42
|
-
expect(isValidJson('{"a":1,}')).toBe(false)
|
|
43
|
-
expect(isValidJson('<xml/>')).toBe(false)
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
describe('isValidXml', () => {
|
|
48
|
-
it('treats empty input as valid', () => {
|
|
49
|
-
expect(isValidXml('')).toBe(true)
|
|
50
|
-
expect(isValidXml(' ')).toBe(true)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('accepts well-formed XML', () => {
|
|
54
|
-
expect(isValidXml('<root><child a="1"/></root>')).toBe(true)
|
|
55
|
-
expect(isValidXml('<?xml version="1.0"?><r/>')).toBe(true)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('rejects malformed XML', () => {
|
|
59
|
-
expect(isValidXml('<root><unclosed></root>')).toBe(false)
|
|
60
|
-
expect(isValidXml('just text')).toBe(false)
|
|
61
|
-
expect(isValidXml('<a><b></a></b>')).toBe(false)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
describe('isValidBase64', () => {
|
|
66
|
-
it('treats empty input as valid', () => {
|
|
67
|
-
expect(isValidBase64('')).toBe(true)
|
|
68
|
-
expect(isValidBase64(' \n ')).toBe(true)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('accepts well-formed base64, ignoring embedded whitespace', () => {
|
|
72
|
-
expect(isValidBase64('aGVsbG8=')).toBe(true)
|
|
73
|
-
expect(isValidBase64('aGVs\nbG8=')).toBe(true)
|
|
74
|
-
expect(isValidBase64('YQ==')).toBe(true)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('rejects bad lengths, bad alphabets and undecodable input', () => {
|
|
78
|
-
expect(isValidBase64('aGVsbG8')).toBe(false) // length % 4 !== 0
|
|
79
|
-
expect(isValidBase64('aGV$bG8=')).toBe(false) // bad character
|
|
80
|
-
expect(isValidBase64('====')).toBe(false) // padding only
|
|
81
|
-
})
|
|
82
|
-
})
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
isValidAbsoluteUrl,
|
|
4
|
+
isValidJson,
|
|
5
|
+
isValidXml,
|
|
6
|
+
isValidBase64,
|
|
7
|
+
} from './validate'
|
|
8
|
+
|
|
9
|
+
describe('isValidAbsoluteUrl', () => {
|
|
10
|
+
it('accepts absolute http/https URLs', () => {
|
|
11
|
+
expect(isValidAbsoluteUrl('http://example.com')).toBe(true)
|
|
12
|
+
expect(isValidAbsoluteUrl('https://example.com/path?q=1')).toBe(true)
|
|
13
|
+
expect(isValidAbsoluteUrl(' https://example.com ')).toBe(true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('rejects empty, relative and non-http schemes', () => {
|
|
17
|
+
expect(isValidAbsoluteUrl('')).toBe(false)
|
|
18
|
+
expect(isValidAbsoluteUrl(' ')).toBe(false)
|
|
19
|
+
expect(isValidAbsoluteUrl('/relative/path')).toBe(false)
|
|
20
|
+
expect(isValidAbsoluteUrl('example.com')).toBe(false)
|
|
21
|
+
expect(isValidAbsoluteUrl('ftp://example.com')).toBe(false)
|
|
22
|
+
expect(isValidAbsoluteUrl('mailto:a@b.c')).toBe(false)
|
|
23
|
+
expect(isValidAbsoluteUrl('not a url')).toBe(false)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('isValidJson', () => {
|
|
28
|
+
it('treats empty input as valid', () => {
|
|
29
|
+
expect(isValidJson('')).toBe(true)
|
|
30
|
+
expect(isValidJson(' \n ')).toBe(true)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('accepts well-formed JSON of any root type', () => {
|
|
34
|
+
expect(isValidJson('{"a":1}')).toBe(true)
|
|
35
|
+
expect(isValidJson('[1,2]')).toBe(true)
|
|
36
|
+
expect(isValidJson('"str"')).toBe(true)
|
|
37
|
+
expect(isValidJson('42')).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('rejects malformed JSON', () => {
|
|
41
|
+
expect(isValidJson('{a:1}')).toBe(false)
|
|
42
|
+
expect(isValidJson('{"a":1,}')).toBe(false)
|
|
43
|
+
expect(isValidJson('<xml/>')).toBe(false)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('isValidXml', () => {
|
|
48
|
+
it('treats empty input as valid', () => {
|
|
49
|
+
expect(isValidXml('')).toBe(true)
|
|
50
|
+
expect(isValidXml(' ')).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('accepts well-formed XML', () => {
|
|
54
|
+
expect(isValidXml('<root><child a="1"/></root>')).toBe(true)
|
|
55
|
+
expect(isValidXml('<?xml version="1.0"?><r/>')).toBe(true)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('rejects malformed XML', () => {
|
|
59
|
+
expect(isValidXml('<root><unclosed></root>')).toBe(false)
|
|
60
|
+
expect(isValidXml('just text')).toBe(false)
|
|
61
|
+
expect(isValidXml('<a><b></a></b>')).toBe(false)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('isValidBase64', () => {
|
|
66
|
+
it('treats empty input as valid', () => {
|
|
67
|
+
expect(isValidBase64('')).toBe(true)
|
|
68
|
+
expect(isValidBase64(' \n ')).toBe(true)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('accepts well-formed base64, ignoring embedded whitespace', () => {
|
|
72
|
+
expect(isValidBase64('aGVsbG8=')).toBe(true)
|
|
73
|
+
expect(isValidBase64('aGVs\nbG8=')).toBe(true)
|
|
74
|
+
expect(isValidBase64('YQ==')).toBe(true)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('rejects bad lengths, bad alphabets and undecodable input', () => {
|
|
78
|
+
expect(isValidBase64('aGVsbG8')).toBe(false) // length % 4 !== 0
|
|
79
|
+
expect(isValidBase64('aGV$bG8=')).toBe(false) // bad character
|
|
80
|
+
expect(isValidBase64('====')).toBe(false) // padding only
|
|
81
|
+
})
|
|
82
|
+
})
|
package/src/utils/validate.ts
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure, dependency-free input validators shared across back-office forms —
|
|
3
|
-
* extracted from WireMate's mock form.
|
|
4
|
-
*
|
|
5
|
-
* Semantics note: the payload validators (`isValidJson`, `isValidXml`,
|
|
6
|
-
* `isValidBase64`) treat empty/whitespace-only input as VALID — callers
|
|
7
|
-
* gate on "is there something to check" separately (required-ness is a
|
|
8
|
-
* different rule from well-formedness). `isValidAbsoluteUrl` is the
|
|
9
|
-
* opposite: it validates a value that must exist, so empty is invalid.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* `true` when `value` parses as an absolute http:// or https:// URL.
|
|
14
|
-
* Deliberately tightens the scheme list — backends' HTTP clients won't
|
|
15
|
-
* follow ftp:// or mailto: anyway, so a stray scheme is a typo.
|
|
16
|
-
*/
|
|
17
|
-
export function isValidAbsoluteUrl(value: string): boolean {
|
|
18
|
-
const trimmed = (value ?? '').trim()
|
|
19
|
-
if (!trimmed) return false
|
|
20
|
-
try {
|
|
21
|
-
const url = new URL(trimmed)
|
|
22
|
-
return url.protocol === 'http:' || url.protocol === 'https:'
|
|
23
|
-
} catch {
|
|
24
|
-
return false
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* `true` when `str` is empty or parses as JSON.
|
|
30
|
-
*/
|
|
31
|
-
export function isValidJson(str: string): boolean {
|
|
32
|
-
if (!str.trim()) return true
|
|
33
|
-
try {
|
|
34
|
-
JSON.parse(str)
|
|
35
|
-
return true
|
|
36
|
-
} catch {
|
|
37
|
-
return false
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* `true` when `str` is empty or is well-formed XML. Runs the browser's
|
|
43
|
-
* DOMParser and checks for a `<parsererror>` node — the only reliable
|
|
44
|
-
* signal DOMParser provides, since it never throws on bad input.
|
|
45
|
-
*/
|
|
46
|
-
export function isValidXml(str: string): boolean {
|
|
47
|
-
if (!str.trim()) return true
|
|
48
|
-
try {
|
|
49
|
-
const doc = new DOMParser().parseFromString(str, 'application/xml')
|
|
50
|
-
return !doc.getElementsByTagName('parsererror').length
|
|
51
|
-
} catch {
|
|
52
|
-
return false
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* `true` when `str` is empty or is well-formed base64 (after stripping
|
|
58
|
-
* whitespace/newlines, which are harmless in both RFC 4648 and most
|
|
59
|
-
* real-world payloads). Rejects lengths that can't possibly decode
|
|
60
|
-
* (`length % 4 !== 0` after padding) — the common "off by a character"
|
|
61
|
-
* copy/paste issue — then defers to the native decoder as the final
|
|
62
|
-
* authority.
|
|
63
|
-
*/
|
|
64
|
-
export function isValidBase64(str: string): boolean {
|
|
65
|
-
if (!str.trim()) return true
|
|
66
|
-
const compact = str.replace(/\s+/g, '')
|
|
67
|
-
if (!compact) return true
|
|
68
|
-
if (compact.length % 4 !== 0) return false
|
|
69
|
-
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(compact)) return false
|
|
70
|
-
try {
|
|
71
|
-
atob(compact)
|
|
72
|
-
return true
|
|
73
|
-
} catch {
|
|
74
|
-
return false
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Pure, dependency-free input validators shared across back-office forms —
|
|
3
|
+
* extracted from WireMate's mock form.
|
|
4
|
+
*
|
|
5
|
+
* Semantics note: the payload validators (`isValidJson`, `isValidXml`,
|
|
6
|
+
* `isValidBase64`) treat empty/whitespace-only input as VALID — callers
|
|
7
|
+
* gate on "is there something to check" separately (required-ness is a
|
|
8
|
+
* different rule from well-formedness). `isValidAbsoluteUrl` is the
|
|
9
|
+
* opposite: it validates a value that must exist, so empty is invalid.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `true` when `value` parses as an absolute http:// or https:// URL.
|
|
14
|
+
* Deliberately tightens the scheme list — backends' HTTP clients won't
|
|
15
|
+
* follow ftp:// or mailto: anyway, so a stray scheme is a typo.
|
|
16
|
+
*/
|
|
17
|
+
export function isValidAbsoluteUrl(value: string): boolean {
|
|
18
|
+
const trimmed = (value ?? '').trim()
|
|
19
|
+
if (!trimmed) return false
|
|
20
|
+
try {
|
|
21
|
+
const url = new URL(trimmed)
|
|
22
|
+
return url.protocol === 'http:' || url.protocol === 'https:'
|
|
23
|
+
} catch {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `true` when `str` is empty or parses as JSON.
|
|
30
|
+
*/
|
|
31
|
+
export function isValidJson(str: string): boolean {
|
|
32
|
+
if (!str.trim()) return true
|
|
33
|
+
try {
|
|
34
|
+
JSON.parse(str)
|
|
35
|
+
return true
|
|
36
|
+
} catch {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `true` when `str` is empty or is well-formed XML. Runs the browser's
|
|
43
|
+
* DOMParser and checks for a `<parsererror>` node — the only reliable
|
|
44
|
+
* signal DOMParser provides, since it never throws on bad input.
|
|
45
|
+
*/
|
|
46
|
+
export function isValidXml(str: string): boolean {
|
|
47
|
+
if (!str.trim()) return true
|
|
48
|
+
try {
|
|
49
|
+
const doc = new DOMParser().parseFromString(str, 'application/xml')
|
|
50
|
+
return !doc.getElementsByTagName('parsererror').length
|
|
51
|
+
} catch {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* `true` when `str` is empty or is well-formed base64 (after stripping
|
|
58
|
+
* whitespace/newlines, which are harmless in both RFC 4648 and most
|
|
59
|
+
* real-world payloads). Rejects lengths that can't possibly decode
|
|
60
|
+
* (`length % 4 !== 0` after padding) — the common "off by a character"
|
|
61
|
+
* copy/paste issue — then defers to the native decoder as the final
|
|
62
|
+
* authority.
|
|
63
|
+
*/
|
|
64
|
+
export function isValidBase64(str: string): boolean {
|
|
65
|
+
if (!str.trim()) return true
|
|
66
|
+
const compact = str.replace(/\s+/g, '')
|
|
67
|
+
if (!compact) return true
|
|
68
|
+
if (compact.length % 4 !== 0) return false
|
|
69
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(compact)) return false
|
|
70
|
+
try {
|
|
71
|
+
atob(compact)
|
|
72
|
+
return true
|
|
73
|
+
} catch {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
}
|