@crombolo/backoffice-host 0.0.0-stage → 0.10.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.
- package/LICENSE +21 -0
- package/NOTICE +69 -0
- package/assets/login.js +352 -0
- package/package.json +35 -4
- package/plugin/assistant/client.js +53 -0
- package/plugin/assistant/drawer.js +865 -0
- package/plugin/assistant/umbraco-package.json +16 -0
- package/plugin/branding/backdrop.svg +1 -0
- package/plugin/branding/branding.js +100 -0
- package/plugin/branding/bundle-builder-labels.js +63 -0
- package/plugin/branding/client-credentials.js +49 -0
- package/plugin/branding/components-tree.js +40 -0
- package/plugin/branding/content-type-labels.js +27 -0
- package/plugin/branding/favicon.svg +1 -0
- package/plugin/branding/help-menu.js +46 -0
- package/plugin/branding/icon-mark.js +15 -0
- package/plugin/branding/icon-registry.js +40 -0
- package/plugin/branding/localization-en.js +115 -0
- package/plugin/branding/log-viewer-menu.js +70 -0
- package/plugin/branding/logo-header.svg +1 -0
- package/plugin/branding/logo-surface.svg +1 -0
- package/plugin/branding/query-builder.js +28 -0
- package/plugin/branding/sign-in.js +80 -0
- package/plugin/branding/sysinfo.js +89 -0
- package/plugin/branding/theme.css +102 -0
- package/plugin/bundles-carries.js +16 -0
- package/plugin/bundles-client.js +40 -0
- package/plugin/bundles-created-overview.js +244 -0
- package/plugin/bundles-created.js +70 -0
- package/plugin/bundles-installed.js +176 -0
- package/plugin/bundles-marketplace.js +236 -0
- package/plugin/changes-dashboard.js +169 -0
- package/plugin/element-type-hint.js +58 -0
- package/plugin/form-picker.js +116 -0
- package/plugin/forms-client.js +52 -0
- package/plugin/forms-entries.js +244 -0
- package/plugin/forms-overview.js +380 -0
- package/plugin/localizations/ar.js +97 -0
- package/plugin/localizations/bs.js +86 -0
- package/plugin/localizations/cs.js +73 -0
- package/plugin/localizations/cy.js +92 -0
- package/plugin/localizations/da.js +101 -0
- package/plugin/localizations/de.js +79 -0
- package/plugin/localizations/en.js +71 -0
- package/plugin/localizations/es.js +52 -0
- package/plugin/localizations/fr.js +78 -0
- package/plugin/localizations/he.js +24 -0
- package/plugin/localizations/hr.js +89 -0
- package/plugin/localizations/it.js +86 -0
- package/plugin/localizations/ja.js +43 -0
- package/plugin/localizations/ko.js +24 -0
- package/plugin/localizations/nb.js +44 -0
- package/plugin/localizations/nl.js +85 -0
- package/plugin/localizations/pl.js +44 -0
- package/plugin/localizations/pt-br.js +25 -0
- package/plugin/localizations/pt.js +100 -0
- package/plugin/localizations/ro.js +9 -0
- package/plugin/localizations/ru.js +46 -0
- package/plugin/localizations/sv.js +46 -0
- package/plugin/localizations/tr.js +80 -0
- package/plugin/localizations/uk.js +46 -0
- package/plugin/localizations/umbraco-package.json +279 -0
- package/plugin/localizations/vi.js +104 -0
- package/plugin/localizations/zh-tw.js +38 -0
- package/plugin/localizations/zh.js +38 -0
- package/plugin/read-only-banner.js +48 -0
- package/plugin/report-client.js +13 -0
- package/plugin/seo/client.js +35 -0
- package/plugin/seo/dashboard.js +242 -0
- package/plugin/seo/document-view.js +161 -0
- package/plugin/seo/type-hint.js +45 -0
- package/plugin/seo/umbraco-package.json +61 -0
- package/plugin/settings-dashboard.js +123 -0
- package/plugin/tsx-editors.js +337 -0
- package/plugin/tsx-snippets.js +63 -0
- package/plugin/umbraco-package.json +298 -0
- package/plugin/welcome-dashboard.js +106 -0
- package/src/branding.ts +59 -0
- package/src/bundle-package.ts +266 -0
- package/src/extensions.ts +189 -0
- package/src/graphics.ts +87 -0
- package/src/index.ts +8 -0
- package/src/login-assets.ts +18 -0
- package/src/manifests.ts +154 -0
- package/src/paths.ts +117 -0
- package/src/shell.ts +139 -0
- package/src/static.ts +107 -0
- package/src/text-imports.d.ts +23 -0
- package/README.md +0 -3
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Installed view, in place of Umbraco's.
|
|
3
|
+
*
|
|
4
|
+
* Its manifest declares `overwrites: ['Umb.SectionView.Packages.Installed']`.
|
|
5
|
+
* Umbraco's view is built around package migrations — it lists installed
|
|
6
|
+
* packages so that pending C# migrations can be run. Nothing carries those here
|
|
7
|
+
* (`docs/bundles.md`), so the view would be a list of nothing with a button
|
|
8
|
+
* that cannot apply.
|
|
9
|
+
*
|
|
10
|
+
* What it lists instead is the site's bundle dependencies, which is what
|
|
11
|
+
* "installed" means here: what each one adds, whether its content has been
|
|
12
|
+
* added to this site, and whether its server features are switched on.
|
|
13
|
+
*/
|
|
14
|
+
import { css, html, nothing, repeat } from '@umbraco-cms/backoffice/external/lit'
|
|
15
|
+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
|
|
16
|
+
import { carriesTags } from './bundles-carries.js'
|
|
17
|
+
import { listInstalled, uninstallBundle } from './bundles-client.js'
|
|
18
|
+
|
|
19
|
+
/** Where each part of a bundle stands on this site, in the words of someone running it. */
|
|
20
|
+
function status(item) {
|
|
21
|
+
const parts = []
|
|
22
|
+
if (item.carries?.content)
|
|
23
|
+
parts.push(
|
|
24
|
+
item.contentApplied
|
|
25
|
+
? html`<span>Content added (${item.contentApplied})</span>`
|
|
26
|
+
: html`<span>Content not added yet</span>`,
|
|
27
|
+
)
|
|
28
|
+
if (item.carries?.server)
|
|
29
|
+
parts.push(
|
|
30
|
+
item.wired
|
|
31
|
+
? html`<span>Server features on</span>`
|
|
32
|
+
: html`<span>Server features off until the site's configuration includes it</span>`,
|
|
33
|
+
)
|
|
34
|
+
return parts.length > 0 ? parts : html`<span>Ready</span>`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default class CromboloBundlesInstalledElement extends UmbLitElement {
|
|
38
|
+
static properties = {
|
|
39
|
+
_items: { state: true },
|
|
40
|
+
_loading: { state: true },
|
|
41
|
+
_busy: { state: true },
|
|
42
|
+
_notice: { state: true },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
super()
|
|
47
|
+
this._items = []
|
|
48
|
+
this._loading = true
|
|
49
|
+
this._busy = ''
|
|
50
|
+
this._notice = ''
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
connectedCallback() {
|
|
54
|
+
super.connectedCallback()
|
|
55
|
+
this.#load()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async #load() {
|
|
59
|
+
this._loading = true
|
|
60
|
+
try {
|
|
61
|
+
this._items = (await listInstalled()).items ?? []
|
|
62
|
+
} catch (error) {
|
|
63
|
+
this._notice = error instanceof Error ? error.message : String(error)
|
|
64
|
+
} finally {
|
|
65
|
+
this._loading = false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async #uninstall(item) {
|
|
70
|
+
this._busy = item.packageName
|
|
71
|
+
try {
|
|
72
|
+
const outcome = await uninstallBundle(item.packageName)
|
|
73
|
+
this._notice = outcome.message ?? ''
|
|
74
|
+
if (outcome.ok) await this.#load()
|
|
75
|
+
} catch (error) {
|
|
76
|
+
this._notice = error instanceof Error ? error.message : String(error)
|
|
77
|
+
} finally {
|
|
78
|
+
this._busy = ''
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
render() {
|
|
83
|
+
if (this._loading) return html`<div id="loader"><uui-loader></uui-loader></div>`
|
|
84
|
+
|
|
85
|
+
return html`
|
|
86
|
+
${this._notice ? html`<div id="notice">${this._notice}</div>` : nothing}
|
|
87
|
+
${
|
|
88
|
+
this._items.length === 0
|
|
89
|
+
? html`
|
|
90
|
+
<uui-box headline="No bundles installed">
|
|
91
|
+
<p>
|
|
92
|
+
A bundle is one of this site's npm dependencies. Install one from the
|
|
93
|
+
Marketplace tab, or with <code>bun add</code> in the site directory, and it
|
|
94
|
+
appears here.
|
|
95
|
+
</p>
|
|
96
|
+
</uui-box>
|
|
97
|
+
`
|
|
98
|
+
: html`
|
|
99
|
+
<uui-box headline="Installed bundles">
|
|
100
|
+
<uui-table>
|
|
101
|
+
<uui-table-head>
|
|
102
|
+
<uui-table-head-cell>Name</uui-table-head-cell>
|
|
103
|
+
<uui-table-head-cell>npm package</uui-table-head-cell>
|
|
104
|
+
<uui-table-head-cell>Version</uui-table-head-cell>
|
|
105
|
+
<uui-table-head-cell>Adds</uui-table-head-cell>
|
|
106
|
+
<uui-table-head-cell>On this site</uui-table-head-cell>
|
|
107
|
+
<uui-table-head-cell></uui-table-head-cell>
|
|
108
|
+
</uui-table-head>
|
|
109
|
+
${repeat(
|
|
110
|
+
this._items,
|
|
111
|
+
(item) => item.packageName,
|
|
112
|
+
(item) => html`
|
|
113
|
+
<uui-table-row>
|
|
114
|
+
<uui-table-cell>${item.name}</uui-table-cell>
|
|
115
|
+
<uui-table-cell><code>${item.packageName}</code></uui-table-cell>
|
|
116
|
+
<uui-table-cell>${item.version}</uui-table-cell>
|
|
117
|
+
<uui-table-cell class="carries">${carriesTags(item.carries)}</uui-table-cell>
|
|
118
|
+
<uui-table-cell class="status">${status(item)}</uui-table-cell>
|
|
119
|
+
<uui-table-cell>
|
|
120
|
+
<uui-button
|
|
121
|
+
look="secondary"
|
|
122
|
+
color="danger"
|
|
123
|
+
label="Uninstall"
|
|
124
|
+
.state=${this._busy === item.packageName ? 'waiting' : undefined}
|
|
125
|
+
@click=${() => this.#uninstall(item)}></uui-button>
|
|
126
|
+
</uui-table-cell>
|
|
127
|
+
</uui-table-row>
|
|
128
|
+
`,
|
|
129
|
+
)}
|
|
130
|
+
</uui-table>
|
|
131
|
+
<p class="note">
|
|
132
|
+
An install writes this site's <code>package.json</code> and
|
|
133
|
+
<code>bun.lock</code>. Commit both to keep it across deployments.
|
|
134
|
+
</p>
|
|
135
|
+
</uui-box>
|
|
136
|
+
`
|
|
137
|
+
}
|
|
138
|
+
`
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static styles = css`
|
|
142
|
+
:host {
|
|
143
|
+
display: block;
|
|
144
|
+
padding: var(--uui-size-layout-1, 24px);
|
|
145
|
+
}
|
|
146
|
+
#loader {
|
|
147
|
+
display: flex;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
padding: var(--uui-size-layout-3, 48px);
|
|
150
|
+
}
|
|
151
|
+
#notice {
|
|
152
|
+
margin-bottom: var(--uui-size-space-4, 12px);
|
|
153
|
+
padding: var(--uui-size-space-4, 12px);
|
|
154
|
+
border-left: 3px solid var(--uui-color-positive, #2bc37c);
|
|
155
|
+
background: var(--uui-color-surface-alt, #f3f3f5);
|
|
156
|
+
}
|
|
157
|
+
p {
|
|
158
|
+
max-width: 60ch;
|
|
159
|
+
line-height: 1.5;
|
|
160
|
+
}
|
|
161
|
+
.carries {
|
|
162
|
+
display: flex;
|
|
163
|
+
gap: var(--uui-size-space-2, 6px);
|
|
164
|
+
}
|
|
165
|
+
.status span {
|
|
166
|
+
display: block;
|
|
167
|
+
}
|
|
168
|
+
.note {
|
|
169
|
+
margin-top: var(--uui-size-space-5, 18px);
|
|
170
|
+
opacity: 0.8;
|
|
171
|
+
font-size: 0.9em;
|
|
172
|
+
}
|
|
173
|
+
`
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
customElements.define('crombolo-bundles-installed', CromboloBundlesInstalledElement)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Marketplace view, in place of Umbraco's.
|
|
3
|
+
*
|
|
4
|
+
* Its manifest declares `overwrites: ['Umb.SectionView.Packages.Marketplace']`.
|
|
5
|
+
* What it replaces is an iframe of marketplace.umbraco.com, which cannot work
|
|
6
|
+
* here twice over: npm refuses to be framed (`x-frame-options: SAMEORIGIN`), and
|
|
7
|
+
* an Umbraco package does not install into this CMS anyway.
|
|
8
|
+
*
|
|
9
|
+
* What it shows instead is the npm registry, searched for the keyword a crombolo
|
|
10
|
+
* bundle publishes, and filtered to the ones whose manifest is a valid bundle.
|
|
11
|
+
* Installing runs `bun add` on the server, then adds the content the bundle
|
|
12
|
+
* carries unless the site or the person installing declines it.
|
|
13
|
+
*/
|
|
14
|
+
import { css, html, nothing, repeat } from '@umbraco-cms/backoffice/external/lit'
|
|
15
|
+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
|
|
16
|
+
import { carriesTags } from './bundles-carries.js'
|
|
17
|
+
import { installBundle, searchMarketplace } from './bundles-client.js'
|
|
18
|
+
|
|
19
|
+
export default class CromboloBundlesMarketplaceElement extends UmbLitElement {
|
|
20
|
+
static properties = {
|
|
21
|
+
_items: { state: true },
|
|
22
|
+
_loading: { state: true },
|
|
23
|
+
_error: { state: true },
|
|
24
|
+
_busy: { state: true },
|
|
25
|
+
_notice: { state: true },
|
|
26
|
+
_keyword: { state: true },
|
|
27
|
+
_url: { state: true },
|
|
28
|
+
_appliesContent: { state: true },
|
|
29
|
+
_declined: { state: true },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
super()
|
|
34
|
+
this._items = []
|
|
35
|
+
this._loading = true
|
|
36
|
+
this._error = ''
|
|
37
|
+
this._busy = ''
|
|
38
|
+
this._notice = ''
|
|
39
|
+
this._keyword = 'crombolo-bundle'
|
|
40
|
+
this._url = ''
|
|
41
|
+
this._query = ''
|
|
42
|
+
this._appliesContent = true
|
|
43
|
+
this._declined = new Set()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
connectedCallback() {
|
|
47
|
+
super.connectedCallback()
|
|
48
|
+
this.#load()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async #load() {
|
|
52
|
+
this._loading = true
|
|
53
|
+
this._error = ''
|
|
54
|
+
try {
|
|
55
|
+
const result = await searchMarketplace(this._query)
|
|
56
|
+
this._items = result.items ?? []
|
|
57
|
+
this._keyword = result.keyword ?? this._keyword
|
|
58
|
+
this._url = result.url ?? ''
|
|
59
|
+
this._appliesContent = result.appliesContent !== false
|
|
60
|
+
} catch (error) {
|
|
61
|
+
this._error = error instanceof Error ? error.message : String(error)
|
|
62
|
+
} finally {
|
|
63
|
+
this._loading = false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#search(event) {
|
|
68
|
+
this._query = event.target.value ?? ''
|
|
69
|
+
this.#load()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async #install(item) {
|
|
73
|
+
this._busy = item.name
|
|
74
|
+
this._notice = ''
|
|
75
|
+
try {
|
|
76
|
+
const outcome = await installBundle(item.name, item.version, !this._declined.has(item.name))
|
|
77
|
+
// The dependency line is what there is to commit, so it is shown rather
|
|
78
|
+
// than described: the install lives in this node's node_modules until it
|
|
79
|
+
// is in the site's repository.
|
|
80
|
+
const installed = outcome.dependency
|
|
81
|
+
? `${outcome.message} — "${outcome.dependency.name}": "${outcome.dependency.range}"`
|
|
82
|
+
: (outcome.message ?? '')
|
|
83
|
+
this._notice = outcome.content ? `${installed} ${outcome.content.message}` : installed
|
|
84
|
+
if (outcome.ok) await this.#load()
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this._notice = error instanceof Error ? error.message : String(error)
|
|
87
|
+
} finally {
|
|
88
|
+
this._busy = ''
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#toggleContent(item, event) {
|
|
93
|
+
const declined = new Set(this._declined)
|
|
94
|
+
if (event.target.checked) declined.delete(item.name)
|
|
95
|
+
else declined.add(item.name)
|
|
96
|
+
this._declined = declined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#contentChoice(item) {
|
|
100
|
+
if (!item.carries?.content || item.installedVersion) return nothing
|
|
101
|
+
if (!this._appliesContent)
|
|
102
|
+
return html`<p class="meta">Its content is added separately on this site.</p>`
|
|
103
|
+
return html`
|
|
104
|
+
<uui-toggle
|
|
105
|
+
label="Add its content when installing"
|
|
106
|
+
.checked=${!this._declined.has(item.name)}
|
|
107
|
+
@change=${(event) => this.#toggleContent(item, event)}></uui-toggle>
|
|
108
|
+
`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#empty() {
|
|
112
|
+
return html`
|
|
113
|
+
<uui-box headline="No bundles published yet">
|
|
114
|
+
<p>Nothing on npm carries the <code>${this._keyword}</code> keyword yet.</p>
|
|
115
|
+
</uui-box>
|
|
116
|
+
`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
render() {
|
|
120
|
+
if (this._loading && this._items.length === 0)
|
|
121
|
+
return html`<div id="loader"><uui-loader></uui-loader></div>`
|
|
122
|
+
|
|
123
|
+
return html`
|
|
124
|
+
<div id="head">
|
|
125
|
+
<uui-input
|
|
126
|
+
type="search"
|
|
127
|
+
label="Search bundles"
|
|
128
|
+
placeholder="Search npm for bundles"
|
|
129
|
+
@change=${this.#search}></uui-input>
|
|
130
|
+
${
|
|
131
|
+
this._url
|
|
132
|
+
? html`<a href=${this._url} target="_blank" rel="noopener">Browse on npm</a>`
|
|
133
|
+
: nothing
|
|
134
|
+
}
|
|
135
|
+
</div>
|
|
136
|
+
${this._error ? html`<uui-box headline="The registry could not be reached"><p>${this._error}</p></uui-box>` : nothing}
|
|
137
|
+
${this._notice ? html`<div id="notice">${this._notice}</div>` : nothing}
|
|
138
|
+
${
|
|
139
|
+
this._items.length === 0 && !this._error
|
|
140
|
+
? this.#empty()
|
|
141
|
+
: html`
|
|
142
|
+
<div id="list">
|
|
143
|
+
${repeat(
|
|
144
|
+
this._items,
|
|
145
|
+
(item) => item.name,
|
|
146
|
+
(item) => html`
|
|
147
|
+
<uui-box>
|
|
148
|
+
<div slot="headline">${item.name} <small>${item.version}</small></div>
|
|
149
|
+
<p>${item.description || 'No description.'}</p>
|
|
150
|
+
<p class="carries">${carriesTags(item.carries)}</p>
|
|
151
|
+
<p class="meta">
|
|
152
|
+
${item.publisher ? html`<span>by ${item.publisher}</span>` : nothing}
|
|
153
|
+
<a href=${item.links.npm} target="_blank" rel="noopener">npm</a>
|
|
154
|
+
${
|
|
155
|
+
item.links.repository
|
|
156
|
+
? html`<a href=${item.links.repository} target="_blank" rel="noopener">source</a>`
|
|
157
|
+
: nothing
|
|
158
|
+
}
|
|
159
|
+
</p>
|
|
160
|
+
${this.#contentChoice(item)}
|
|
161
|
+
${
|
|
162
|
+
item.installedVersion
|
|
163
|
+
? html`<uui-tag look="positive">Installed ${item.installedVersion}</uui-tag>`
|
|
164
|
+
: html`
|
|
165
|
+
<uui-button
|
|
166
|
+
look="primary"
|
|
167
|
+
label="Install"
|
|
168
|
+
.state=${this._busy === item.name ? 'waiting' : undefined}
|
|
169
|
+
@click=${() => this.#install(item)}></uui-button>
|
|
170
|
+
`
|
|
171
|
+
}
|
|
172
|
+
</uui-box>
|
|
173
|
+
`,
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
`
|
|
177
|
+
}
|
|
178
|
+
`
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
static styles = css`
|
|
182
|
+
:host {
|
|
183
|
+
display: block;
|
|
184
|
+
padding: var(--uui-size-layout-1, 24px);
|
|
185
|
+
}
|
|
186
|
+
#loader {
|
|
187
|
+
display: flex;
|
|
188
|
+
justify-content: center;
|
|
189
|
+
padding: var(--uui-size-layout-3, 48px);
|
|
190
|
+
}
|
|
191
|
+
#head {
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
gap: var(--uui-size-space-4, 12px);
|
|
195
|
+
margin-bottom: var(--uui-size-space-5, 18px);
|
|
196
|
+
}
|
|
197
|
+
#head uui-input {
|
|
198
|
+
flex: 1;
|
|
199
|
+
}
|
|
200
|
+
#notice {
|
|
201
|
+
margin-bottom: var(--uui-size-space-4, 12px);
|
|
202
|
+
padding: var(--uui-size-space-4, 12px);
|
|
203
|
+
border-left: 3px solid var(--uui-color-positive, #2bc37c);
|
|
204
|
+
background: var(--uui-color-surface-alt, #f3f3f5);
|
|
205
|
+
}
|
|
206
|
+
#list {
|
|
207
|
+
display: grid;
|
|
208
|
+
gap: var(--uui-size-space-5, 18px);
|
|
209
|
+
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
210
|
+
}
|
|
211
|
+
p {
|
|
212
|
+
max-width: 60ch;
|
|
213
|
+
line-height: 1.5;
|
|
214
|
+
}
|
|
215
|
+
.meta {
|
|
216
|
+
display: flex;
|
|
217
|
+
gap: var(--uui-size-space-4, 12px);
|
|
218
|
+
font-size: 0.9em;
|
|
219
|
+
opacity: 0.8;
|
|
220
|
+
}
|
|
221
|
+
small {
|
|
222
|
+
opacity: 0.7;
|
|
223
|
+
font-weight: normal;
|
|
224
|
+
}
|
|
225
|
+
.carries {
|
|
226
|
+
display: flex;
|
|
227
|
+
gap: var(--uui-size-space-2, 6px);
|
|
228
|
+
}
|
|
229
|
+
uui-toggle {
|
|
230
|
+
display: block;
|
|
231
|
+
margin-bottom: var(--uui-size-space-4, 12px);
|
|
232
|
+
}
|
|
233
|
+
`
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
customElements.define('crombolo-bundles-marketplace', CromboloBundlesMarketplaceElement)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Changes dashboard: counts by kind, each finding with its link, resolved
|
|
3
|
+
* ones greyed. Reads a stable table, so whatever version is live can show what
|
|
4
|
+
* the next version needs. docs/10, "The upgrade dashboard ships in the first release".
|
|
5
|
+
*
|
|
6
|
+
* Findings arrive from two sources — a pending upgrade and an arriving content
|
|
7
|
+
* bundle — so they are grouped rather than pooled: the counts of one say nothing
|
|
8
|
+
* about the other, and the actions that resolve them are different.
|
|
9
|
+
*
|
|
10
|
+
* Below them, each installed bundle that keeps data of its own lists the
|
|
11
|
+
* changes it has made to its storage here, and any still to make.
|
|
12
|
+
*/
|
|
13
|
+
import { css, html, LitElement, nothing } from '@umbraco-cms/backoffice/external/lit'
|
|
14
|
+
import { fetchReport } from './report-client.js'
|
|
15
|
+
|
|
16
|
+
const KIND_LABEL = { blocking: 'Blocking', person: 'Needs a person', auto: 'Automatic' }
|
|
17
|
+
const KIND_COLOR = { blocking: 'danger', person: 'warning', auto: 'positive' }
|
|
18
|
+
const SOURCE_LABEL = { upgrade: 'Schema and upgrade', transfer: 'Content transfer' }
|
|
19
|
+
|
|
20
|
+
export default class CromboloChangesDashboardElement extends LitElement {
|
|
21
|
+
static properties = {
|
|
22
|
+
_report: { state: true },
|
|
23
|
+
_error: { state: true },
|
|
24
|
+
_showResolved: { state: true },
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super()
|
|
29
|
+
this._report = undefined
|
|
30
|
+
this._error = undefined
|
|
31
|
+
this._showResolved = false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
connectedCallback() {
|
|
35
|
+
super.connectedCallback()
|
|
36
|
+
this.#load()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async #load() {
|
|
40
|
+
try {
|
|
41
|
+
this._report = await fetchReport()
|
|
42
|
+
this._error = undefined
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this._error = error.message
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#group(findings) {
|
|
49
|
+
const groups = new Map()
|
|
50
|
+
for (const f of findings) {
|
|
51
|
+
const source = f.source ?? 'upgrade'
|
|
52
|
+
const key = `${source}|${f.scope ?? ''}`
|
|
53
|
+
const group = groups.get(key) ?? { source, scope: f.scope ?? null, findings: [] }
|
|
54
|
+
group.findings.push(f)
|
|
55
|
+
groups.set(key, group)
|
|
56
|
+
}
|
|
57
|
+
return [...groups.values()]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
render() {
|
|
61
|
+
if (this._error) return html`<uui-box headline="Changes"><p>${this._error}</p></uui-box>`
|
|
62
|
+
if (!this._report) return html`<uui-box headline="Changes"><uui-loader></uui-loader></uui-box>`
|
|
63
|
+
const { health, findings, counts, bundles = [] } = this._report
|
|
64
|
+
const shown = findings.filter((f) => this._showResolved || f.status === 'open')
|
|
65
|
+
return html`
|
|
66
|
+
<uui-box headline="Changes">
|
|
67
|
+
<p>
|
|
68
|
+
This server runs schema <strong>${health.version}+${health.revision}</strong>
|
|
69
|
+
${
|
|
70
|
+
health.readOnly
|
|
71
|
+
? html`<uui-tag color="warning">read-only: the database is ahead of this server</uui-tag>`
|
|
72
|
+
: html`<uui-tag color="positive">current</uui-tag>`
|
|
73
|
+
}
|
|
74
|
+
</p>
|
|
75
|
+
<div class="counts">
|
|
76
|
+
${Object.entries(KIND_LABEL).map(
|
|
77
|
+
([kind, label]) =>
|
|
78
|
+
html`<uui-tag color=${KIND_COLOR[kind]}>${label}: ${counts[kind] ?? 0}</uui-tag>`,
|
|
79
|
+
)}
|
|
80
|
+
<uui-button look="secondary" compact @click=${() => this.#load()} label="Refresh">Refresh</uui-button>
|
|
81
|
+
<uui-toggle
|
|
82
|
+
label="Show resolved"
|
|
83
|
+
.checked=${this._showResolved}
|
|
84
|
+
@change=${(e) => (this._showResolved = e.target.checked)}
|
|
85
|
+
></uui-toggle>
|
|
86
|
+
</div>
|
|
87
|
+
${
|
|
88
|
+
shown.length === 0
|
|
89
|
+
? html`<p class="empty">Nothing outstanding. <code>crombolo upgrade check</code> and <code>crombolo content check</code> write findings here.</p>`
|
|
90
|
+
: this.#group(shown).map((group) => this.#renderGroup(group))
|
|
91
|
+
}
|
|
92
|
+
${bundles.map((bundle) => this.#renderBundle(bundle))}
|
|
93
|
+
</uui-box>
|
|
94
|
+
`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#renderBundle(bundle) {
|
|
98
|
+
const steps = [
|
|
99
|
+
...bundle.pending.map((step) => ({ ...step, done: false })),
|
|
100
|
+
...bundle.applied.map((step) => ({ ...step, done: true })).reverse(),
|
|
101
|
+
]
|
|
102
|
+
return html`
|
|
103
|
+
<h4>${bundle.name} <small>data storage</small></h4>
|
|
104
|
+
${
|
|
105
|
+
bundle.pending.length > 0
|
|
106
|
+
? html`<p><uui-tag color="warning">${bundle.pending.length} change(s) waiting for the next upgrade</uui-tag></p>`
|
|
107
|
+
: nothing
|
|
108
|
+
}
|
|
109
|
+
<uui-table>
|
|
110
|
+
<uui-table-head>
|
|
111
|
+
<uui-table-head-cell>Change</uui-table-head-cell>
|
|
112
|
+
<uui-table-head-cell>Release</uui-table-head-cell>
|
|
113
|
+
<uui-table-head-cell>State</uui-table-head-cell>
|
|
114
|
+
</uui-table-head>
|
|
115
|
+
${steps.map(
|
|
116
|
+
(step) => html`<uui-table-row>
|
|
117
|
+
<uui-table-cell>${step.name}${step.kind === 'contract' ? html`<small>removes data</small>` : nothing}</uui-table-cell>
|
|
118
|
+
<uui-table-cell>${step.release ?? ''}</uui-table-cell>
|
|
119
|
+
<uui-table-cell>${
|
|
120
|
+
step.done
|
|
121
|
+
? html`<uui-tag color="positive">done ${step.appliedAt?.slice(0, 10) ?? ''}</uui-tag>`
|
|
122
|
+
: html`<uui-tag color="warning">waiting</uui-tag>`
|
|
123
|
+
}</uui-table-cell>
|
|
124
|
+
</uui-table-row>`,
|
|
125
|
+
)}
|
|
126
|
+
</uui-table>
|
|
127
|
+
`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#renderGroup(group) {
|
|
131
|
+
return html`
|
|
132
|
+
<h4>
|
|
133
|
+
${SOURCE_LABEL[group.source] ?? group.source}
|
|
134
|
+
${group.scope ? html`<code>${group.scope}</code>` : nothing}
|
|
135
|
+
</h4>
|
|
136
|
+
<uui-table>
|
|
137
|
+
<uui-table-head>
|
|
138
|
+
<uui-table-head-cell>Kind</uui-table-head-cell>
|
|
139
|
+
<uui-table-head-cell>What</uui-table-head-cell>
|
|
140
|
+
<uui-table-head-cell>Where</uui-table-head-cell>
|
|
141
|
+
</uui-table-head>
|
|
142
|
+
${group.findings.map(
|
|
143
|
+
(f) => html`<uui-table-row class=${f.status}>
|
|
144
|
+
<uui-table-cell><uui-tag color=${KIND_COLOR[f.kind]}>${KIND_LABEL[f.kind]}</uui-tag></uui-table-cell>
|
|
145
|
+
<uui-table-cell>${f.message}</uui-table-cell>
|
|
146
|
+
<uui-table-cell>
|
|
147
|
+
${f.link ? html`<a href=${f.link}>${f.subjectName}</a>` : f.subjectName}
|
|
148
|
+
${f.propertyAlias ? html`<code>${f.propertyAlias}</code>` : nothing}
|
|
149
|
+
${f.culture ? html`<small>${f.culture}</small>` : nothing}
|
|
150
|
+
</uui-table-cell>
|
|
151
|
+
</uui-table-row>`,
|
|
152
|
+
)}
|
|
153
|
+
</uui-table>
|
|
154
|
+
`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static styles = css`
|
|
158
|
+
:host { display: block; padding: var(--uui-size-layout-1); }
|
|
159
|
+
.counts { display: flex; gap: var(--uui-size-space-3); align-items: center; margin-bottom: var(--uui-size-space-4); }
|
|
160
|
+
h4 { margin: var(--uui-size-space-5) 0 var(--uui-size-space-2); }
|
|
161
|
+
h4:first-of-type { margin-top: 0; }
|
|
162
|
+
uui-table-row.resolved { opacity: 0.5; text-decoration: line-through; }
|
|
163
|
+
code { margin-left: var(--uui-size-space-2); }
|
|
164
|
+
small { margin-left: var(--uui-size-space-2); }
|
|
165
|
+
.empty { color: var(--uui-color-text-alt); }
|
|
166
|
+
`
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
customElements.define('crombolo-changes-dashboard', CromboloChangesDashboardElement)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A hint in the document-type workspace footer, for the one thing about element
|
|
3
|
+
* types that nobody finds on their own: making a type an element type does not put
|
|
4
|
+
* it in the Library. That takes a second toggle, **Allow in Library**, on a
|
|
5
|
+
* different tab — and Umbraco's own "no allowed Element Types" message in the
|
|
6
|
+
* Library's create dialog never appears, because Folder is always an option there,
|
|
7
|
+
* so the dialog is never empty enough to explain itself.
|
|
8
|
+
*
|
|
9
|
+
* Both flags are worth keeping: most element types exist for block editors and
|
|
10
|
+
* should stay out of the Library. This says so rather than changing it.
|
|
11
|
+
*
|
|
12
|
+
* Registered as a `workspaceFooterApp` conditioned on the document-type workspace,
|
|
13
|
+
* not patched into Umbraco's own view — a manifest is the supported extension
|
|
14
|
+
* point, so an upstream bump cannot silently drop it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document-type'
|
|
18
|
+
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'
|
|
19
|
+
import { css, html, LitElement, nothing } from '@umbraco-cms/backoffice/external/lit'
|
|
20
|
+
|
|
21
|
+
export default class CromboloElementTypeHintElement extends UmbElementMixin(LitElement) {
|
|
22
|
+
static properties = { _isElement: { state: true }, _allowedInLibrary: { state: true } }
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
super()
|
|
26
|
+
this._isElement = false
|
|
27
|
+
this._allowedInLibrary = false
|
|
28
|
+
this.consumeContext(UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT, (context) => {
|
|
29
|
+
if (!context) return
|
|
30
|
+
this.observe(context.isElement, (value) => {
|
|
31
|
+
this._isElement = Boolean(value)
|
|
32
|
+
})
|
|
33
|
+
this.observe(context.allowedInLibrary, (value) => {
|
|
34
|
+
this._allowedInLibrary = Boolean(value)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
render() {
|
|
40
|
+
if (!this._isElement || this._allowedInLibrary) return nothing
|
|
41
|
+
return html`<uui-tag
|
|
42
|
+
color="default"
|
|
43
|
+
look="secondary"
|
|
44
|
+
title="An element type is only offered in the Library's Create dialog when 'Allow in Library' is on, under the Structure tab. Leave it off for an element type that exists for block editors."
|
|
45
|
+
>
|
|
46
|
+
Not in the Library — see Structure
|
|
47
|
+
</uui-tag>`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static styles = css`
|
|
51
|
+
:host {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
}
|
|
55
|
+
`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
customElements.define('crombolo-element-type-hint', CromboloElementTypeHintElement)
|