Compare commits

...

16 Commits

Author SHA1 Message Date
bat 913deecf1c Merge pull request 'use custom class for storage' (#7) from storage-class into pages
Reviewed-on: https://codeberg.org/macchiato/settings/pulls/7
3 years ago
bat 6906520550 use custom class for storage 3 years ago
bat e33b7195b9 Merge pull request 'add CodeMirror and Template options' (#6) from codemirror-option into pages
Reviewed-on: https://codeberg.org/macchiato/settings/pulls/6
3 years ago
bat fb8a817cf7 add CodeMirror and Template options 3 years ago
bat ade99cd5a3 Merge pull request 'fix revert of CSPs' (#5) from fix-revert-csps into pages
Reviewed-on: https://codeberg.org/macchiato/settings/pulls/5
3 years ago
bat ceaa047f94 fix revert of CSPs 3 years ago
bat bcafd8f9b3 Merge pull request 'access settings' (#4) from access-settings into pages
Reviewed-on: https://codeberg.org/macchiato/settings/pulls/4
3 years ago
bat 298437bc39 support put and get messages 3 years ago
bat 58a57df33d save connections and apply inverse connections 3 years ago
bat c2c9ec6a80 add inbound and outbound connections 3 years ago
bat 3f39582900 move heading to parent component 3 years ago
bat b6309210a3 move network settings to separate component 3 years ago
bat b92352565a Merge pull request 'simplify CSP and clean up layout' (#2) from simplify-csp-cleanup-layout into pages
Reviewed-on: https://codeberg.org/macchiato/settings/pulls/2
3 years ago
bat 6e040151fc simplify and clean up layout 3 years ago
bat 1e1195eff1 Merge pull request 'add page settings w/ csp' (#1) from csp-settings into pages
Reviewed-on: https://codeberg.org/macchiato/settings/pulls/1
3 years ago
Benjamin Atkin 5cf8a3f2eb add page settings w/ csp 3 years ago

@ -0,0 +1,123 @@
export class ConnectionEdit extends HTMLElement {
textEn = {
page: 'Page',
access: 'Access',
read: 'read',
readWrite: 'read and write',
doesntExist: "Error: The page doesn't exist",
samePage: "Error: Cannot connect to same page",
alreadyConnected: "Error: This page is already connected",
}
textEs = {
page: 'Página',
access: 'Accesso',
read: 'leer',
readWrite: 'leer y escribir',
doesntExist: 'Error: La página no existe',
samePage: "Error: no se puede conectar a la misma página",
alreadyConnected: "Error: esta página ya está conectada",
}
constructor() {
super()
this.attachShadow({mode: 'open'})
this.language = navigator.language
const pageLabel = document.createElement('label')
pageLabel.innerText = this.text.page
this.pageInput = document.createElement('input')
const accessLabel = document.createElement('label')
accessLabel.innerText = this.text.access
this.accessSelect = document.createElement('select')
const wrap = document.createElement('div')
wrap.append(this.accessSelect)
const opts = ['read', 'readWrite'].map(value => {
const el = document.createElement('option')
el.value = value
el.innerText = this.text[value]
return el
})
this.accessSelect.append(...opts)
this.accessSelect.value = 'read'
const fields = document.createElement('div')
fields.classList.add('fields')
fields.append(
pageLabel,
this.pageInput,
accessLabel,
wrap,
)
this.shadowRoot.append(
fields,
)
}
connectedCallback() {
const style = document.createElement('style')
style.textContent = `
:host {
display: flex;
flex-direction: column;
align-items: stretch;
}
.fields {
display: grid;
grid-template-columns: fit-content(50%) 1fr;
gap: 5px 10px;
}
.error {
color: red;
}
`
this.shadowRoot.append(style)
}
get data() {
return {
networkAccess: this.netSelect.value,
}
}
set data(value) {
this.netText.innerText = JSON.stringify(value)
this.netSelect.value = value.networkAccess ?? 'local'
}
set error(error) {
this._error = error
if (error === undefined) {
if (this.errorEl) {
this.errorEl.remove()
this.errorEl = undefined
}
} else {
if (!this.errorEl) {
this.errorEl = document.createElement('p')
this.errorEl.classList.add('error')
this.shadowRoot.append(this.errorEl)
}
this.errorEl.innerText = this.text[error]
}
}
get error() {
return this._error
}
get language() {
return this._language
}
set language(language) {
this._language = language
this.text = this.langEs ? this.textEs : this.textEn
}
get langEs() {
return /^es\b/.test(this.language)
}
get lang() {
return this.language.split('-')[0]
}
}

@ -0,0 +1,174 @@
export class Connections extends HTMLElement {
icons = {
del: `
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-trash3-fill" viewBox="0 0 16 16">
<path d="M11 1.5v1h3.5a.5.5 0 0 1 0 1h-.538l-.853 10.66A2 2 0 0 1 11.115 16h-6.23a2 2 0 0 1-1.994-1.84L2.038 3.5H1.5a.5.5 0 0 1 0-1H5v-1A1.5 1.5 0 0 1 6.5 0h3A1.5 1.5 0 0 1 11 1.5Zm-5 0v1h4v-1a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5ZM4.5 5.029l.5 8.5a.5.5 0 1 0 .998-.06l-.5-8.5a.5.5 0 1 0-.998.06Zm6.53-.528a.5.5 0 0 0-.528.47l-.5 8.5a.5.5 0 0 0 .998.058l.5-8.5a.5.5 0 0 0-.47-.528ZM8 4.5a.5.5 0 0 0-.5.5v8.5a.5.5 0 0 0 1 0V5a.5.5 0 0 0-.5-.5Z"/>
</svg>
`
}
textEn = {
add: 'Add connection',
cancel: 'Cancel',
read: 'read',
readWrite: 'read and write',
}
textEs = {
add: 'Añadir conexión',
cancel: 'Cancelar',
read: 'leer',
readWrite: 'leer y escribir',
}
constructor() {
super()
this.attachShadow({mode: 'open'})
this.language = navigator.language
this.content = document.createElement('div')
const bGroup = document.createElement(
'm-forms-button-group'
)
bGroup.addPrimary(this.text.add, () => {
this.add()
})
this.shadowRoot.append(
this.content,
bGroup,
)
}
connectedCallback() {
const style = document.createElement('style')
style.textContent = `
:host {
display: flex;
flex-direction: column;
align-items: stretch;
margin-bottom: 5px;
}
m-dialog::part(footer) {
padding-top: 15px;
}
button.icon {
border: none;
background: inherit;
color: #555;
}
button.icon svg {
width: 12px;
height: 12px;
}
.connection {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.access {
font-size: 80%;
background: #ccc;
border-radius: 3px;
padding: 5px;
}
`
this.shadowRoot.append(style)
}
add() {
const dialog = document.createElement('m-dialog')
dialog.top = 180
const edit = document.createElement(
'm-settings-connection-edit'
)
dialog.bodyEl.append(edit)
const bGroup = document.createElement(
'm-forms-button-group'
)
bGroup.addPrimary(this.text.add, () => {
const path = edit.pageInput.value
const access = edit.accessSelect.value
const exists = this.checkExists(path)
if (!exists) {
edit.error = 'doesntExist'
return
} else if (path === this.path) {
edit.error = 'samePage'
return
} else if (this.data[path] ?? true !== true) {
edit.error = 'alreadyConnected'
return
}
this.data = {
...this.data,
[path]: access,
}
dialog.close()
})
bGroup.addCancel(this.text.cancel, () => {
dialog.close()
})
dialog.footerEl.appendChild(bGroup)
this.shadowRoot.append(dialog)
dialog.open()
}
display() {
const entries = Object.entries(this.data).filter(
([k, v]) => v !== undefined
)
const content = entries.map(([path, access]) => {
const el = document.createElement('div')
el.classList.add('connection')
const pathEl = document.createElement('span')
pathEl.innerText = path
el.append(pathEl)
const accessEl = document.createElement('span')
accessEl.innerText = this.text[access]
accessEl.classList.add('access')
const delBtn = document.createElement('button')
delBtn.classList.add('delete', 'icon')
delBtn.innerHTML = this.icons.del
delBtn.addEventListener('click', () => {
this.data = {...this.data, [path]: undefined}
})
el.append(accessEl, delBtn)
return el
})
this.content.replaceChildren(...content)
}
set type(value) {
this._type = value
}
get type() {
return this._type
}
get data() {
return this._data
}
set data(value) {
this._data = value
this.display()
}
get language() {
return this._language
}
set language(language) {
this._language = language
this.text = this.langEs ? this.textEs : this.textEn
}
get langEs() {
return /^es\b/.test(this.language)
}
get lang() {
return this.language.split('-')[0]
}
}

@ -0,0 +1,159 @@
export class NetworkSettings extends HTMLElement {
textEn = {
csp: 'Content Security Policy',
}
textEs = {
csp: 'Política de Seguridad de Contenido',
}
accessOptions = {
local: {
option: {
en: "network access off",
es: "acceso a la red desactivado",
},
text: {
en: 'direct network access off',
es: 'acceso directo a la red desactivado',
},
details: {
en: "Direct network access is off and the page can't send data. This prevents data from being exposed even if the code of the page isn't trusted. It is a good option if this page will contain private data.",
es: "El acceso directo a la red está desactivado y la página no puede enviar datos. Esto evita que los datos queden expuestos incluso si el código de la página no es de confianza. Es una buena opción si esta página contendrá datos privados.",
},
},
unpkg: {
option: {
en: 'UNPKG',
es: 'UNPKG',
},
text: {
en: 'access to UNPKG only',
es: 'acceso a UNPKG solamente',
},
details: {
en: "The page can make requests to UNPKG. This could allow data from your page to be sent to the servers if the code in the page sends it. This means that if you have private data in the page, you should either trust the code on the page not to send it to these servers, or you should trust these servers, or both.",
es: "La página puede realizar solicitudes a UNPKG. Esto podría permitir que los datos de su página se envíen a los servidores si el código de la página los envía. Esto significa que si tiene datos privados en la página, debe confiar en el código de la página para no enviarlos a estos servidores, o debe confiar en estos servidores, o en ambos.",
},
},
open: {
option: {
en: 'network open',
es: 'red abierta',
},
text: {
en: 'network open (direct access to any site)',
es: 'red abierta (acceso directo a cualquier sitio)',
},
details: {
en: "Network access is open, so the page can send or receive requests to any site. You should either A) have no private data in the page, or B) trust the code completely. This makes the page similar to major code playgrounds like codesandbox.com, stackblitz.com, jsbin.com, and codepen.io.",
es: "El acceso a la red está abierto, por lo que la página puede enviar o recibir solicitudes a cualquier sitio. Debería A) no tener datos privados en la página, o B) confiar completamente en el código. Esto hace que la página sea similar a los principales juegos de código como codesandbox.com, stackblitz.com, jsbin.com y codepen.io.",
},
},
}
constructor() {
super()
this.attachShadow({mode: 'open'})
this.language = navigator.language
const netSelectField = document.createElement('div')
netSelectField.classList.add('field')
this.netSelect = document.createElement('select')
this.netSelect.addEventListener(
'change', () => this.display()
)
netSelectField.append(this.netSelect)
const netOptions = Object.entries(
this.accessOptions
).map(([value, {option}]) => {
const el = document.createElement('option')
el.value = value
el.innerText = option[this.lang]
return el
})
this.netSelect.append(...netOptions)
this.netHeading = document.createElement('h3')
this.netText = document.createElement('p')
this.cspLabel = document.createElement(
'div'
)
this.cspLabel.classList.add('csp-label')
this.cspLabel.innerText = (
this.text.csp + ' (CSP):'
)
this.netCsp = document.createElement('div')
this.netCsp.classList.add('csp')
this.shadowRoot.append(
netSelectField,
this.netHeading,
this.netText,
this.cspLabel,
this.netCsp,
)
}
connectedCallback() {
const style = document.createElement('style')
style.textContent = `
:host {
display: flex;
flex-direction: column;
align-items: stretch;
}
div.field {
display: flex;
flex-direction: row;
}
h1, h2, h3, p {
margin: 3px 0;
}
.csp-label {
font-weight: bold;
}
.csp {
font-family: monospace;
}
`
this.shadowRoot.append(style)
}
display() {
const value = this.netSelect.value
const opt = this.accessOptions[value]
const l = this.lang
this.netHeading.innerText = opt.text[l]
this.netText.innerText = opt.details[l]
this.netCsp.innerText = (
this.cspProfiles[value]
)
}
get data() {
return {
networkAccess: this.netSelect.value,
}
}
set data(value) {
this.netText.innerText = JSON.stringify(value)
this.netSelect.value = value.networkAccess ?? 'local'
this.display()
}
get language() {
return this._language
}
set language(language) {
this._language = language
this.text = this.langEs ? this.textEs : this.textEn
}
get langEs() {
return /^es\b/.test(this.language)
}
get lang() {
return this.language.split('-')[0]
}
}

@ -0,0 +1,165 @@
export class PageSettings extends HTMLElement {
textEn = {
general: 'General',
template: 'Template',
codeMirror: 'CodeMirror',
outbound: 'Outbound connections',
inbound: 'Inbound connections',
netAccess: 'Direct network access (CSP)',
}
textEs = {
general: 'General',
template: 'Template',
codeMirror: 'CodeMirror',
outbound: 'Conexiones salientes',
inbound: 'Conexiones entrantes',
netAccess: 'Acceso directo a la red (CSP)',
}
constructor() {
super()
this.attachShadow({mode: 'open'})
this.language = navigator.language
const generalHeading = document.createElement('div')
generalHeading.classList.add('heading')
generalHeading.innerText = this.text.general
const templateLabel = document.createElement('label')
templateLabel.innerText = this.text.template
this.templateEl = document.createElement('input')
this.templateEl.type = 'checkbox'
templateLabel.prepend(this.templateEl)
const codeMirrorLabel = document.createElement('label')
codeMirrorLabel.innerText = this.text.codeMirror
this.codeMirrorEl = document.createElement('input')
this.codeMirrorEl.type = 'checkbox'
codeMirrorLabel.prepend(this.codeMirrorEl)
const generalDiv = document.createElement('div')
generalDiv.append(templateLabel, codeMirrorLabel)
const outboundHeading = document.createElement('div')
outboundHeading.classList.add('heading')
outboundHeading.innerText = this.text.outbound
this.outbound = document.createElement(
'm-settings-connections'
)
this.outbound.type = 'outbound'
const inboundHeading = document.createElement('div')
inboundHeading.classList.add('heading')
inboundHeading.innerText = this.text.inbound
this.inbound = document.createElement(
'm-settings-connections'
)
this.inbound.type = 'inbound'
const netSelectHeading = document.createElement('div')
netSelectHeading.classList.add('heading')
netSelectHeading.innerText = this.text.netAccess
this.network = document.createElement(
'm-settings-network-settings'
)
this.shadowRoot.append(
generalHeading,
generalDiv,
outboundHeading,
this.outbound,
inboundHeading,
this.inbound,
netSelectHeading,
this.network,
)
}
connectedCallback() {
const style = document.createElement('style')
style.textContent = `
:host {
max-height: 55vh;
display: flex;
flex-direction: column;
align-items: stretch;
overflow-y: auto;
}
* {
padding-left: 10px;
}
div.heading {
display: flex;
flex-direction: row;
justify-content: flex-start;
background: #f2dbd8;
padding: 3px;
margin-bottom: 10px;
margin-top: 10px;
font-weight: bold;
}
`
this.shadowRoot.append(style)
}
get data() {
return {
...this.network.data,
connections: {
outbound: this.outbound.data,
inbound: this.inbound.data,
},
template: this.templateEl.checked,
codeMirror: this.codeMirrorEl.checked,
}
}
set data(value) {
this.templateEl.checked = !!value.template
this.codeMirrorEl.checked = !!value.codeMirror
this.outbound.data = value.connections?.outbound || {}
this.inbound.data = value.connections?.inbound || {}
this.network.data = {
networkAccess: value.networkAccess
}
}
set path(value) {
this._path = value
this.outbound.path = value
this.inbound.path = value
}
get path() {
return this._path
}
set cspProfiles(value) {
this.network.cspProfiles = value
}
set checkExists(value) {
this._checkExists = value
this.outbound.checkExists = value
this.inbound.checkExists = value
}
get checkExists() {
return this._checkExists
}
get language() {
return this._language
}
set language(language) {
this._language = language
this.text = this.langEs ? this.textEs : this.textEn
}
get langEs() {
return /^es\b/.test(this.language)
}
get lang() {
return this.language.split('-')[0]
}
}
Loading…
Cancel
Save