Skip to content

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

configbyte-screens

The shared screens of a deployment's admin app: the sign-in page, the frame, Export / Import, which carries a workspace's configuration out as one file and back in, History, every change a person made to the workspace's setup across its services, and People & access, who is in the workspace and what each person holds, with its roles and its user types. They are Vue 3 screens that a product's own single-page application builds into a second app of its own, served at its own address and talking to one backend only: the configuration coordinator, configbyte.

The screens name no product. The host application hands over its name, what its configuration's sections and parts are called, the lines its Export card lists, and its own admin screens; these screens draw them.

What a person sees

State When What is shown
Signed out the coordinator answers 401 to GET /me the sign-in page: one button per way the deployment offers (GET /login/ways), each by its own name, a sentence saying what each does, the language menu, and a link to everyday work when the deployment names its address
Waiting for the card an ID-card way was chosen that way's button says so, and every way is held until the card answers
No card software the card software or its browser extension is not on this computer what to install, with a link, above the buttons
Came back unfinished a sign-in returns with ?error= and a marker why, in plain words: cancelled, not a member of this workspace (and to ask its administrator for an invitation), took too long, could not be matched, the identity provider failed, or could not be completed
Not a member the card's sign-in completes with 403 err:membership:notMember the same sentence as a sign-in that came back not a member
Signed in the person holds a scope the host marks as configuring a section this deployment runs: a setup box, or the administrator's the frame: the deployment's name, the entries in their groups, the screen opened, and at the foot of the menu the language globe above who is signed in, with the way's own name
Language not kept a language picked at the globe, and PUT /me/language gets no answer or a refusal the language last kept back, and a sentence under the globe saying so
Nothing to configure signed in, and no such scope is held a plain sentence saying so, a link to everyday work, and signing out
Signed out, just now after Sign out, back on the sign-in page that the person is signed out of this app, and that a company sign-in stays signed in on the computer
Sign-out failed POST /logout gets no answer or a refusal a sentence saying so; the person stays signed in
Sign-in ended signed in, and a read is answered 401 (a screen's, the download, or the host's own, which tells the session) the sign-in page in place of the whole frame, saying Your sign-in has ended. Sign in again., once, however many reads were out; signing in with the card comes back to the screen that was open
Not answering GET /me gets no answer, 502, 503 or 504, or any failure other than not being signed in; or nobody is signed in and GET /login/ways fails the same way a sentence saying nothing was changed, and Try again, which asks both again. Someone signed in works on without the ways, missing only the way's name beside their own
Not open from here the coordinator answers 403 err:configbyte:networkNotAllowed, or a sign-in comes back with ?error=network in the sign-in page's place: a sentence saying the address answers only inside the networks the organisation set, the language menu, no sign-in, and a link to everyday work when the refusal carries its address (Link: <…>; rel="related")
Sign-in not strong enough a sign-in comes back with ?error=assurance, or the card's completes with 403 err:session:assuranceTooLow in the sign-in page's place: a sentence saying so, every way the deployment offers, and Back to the sign-in

The language before anyone is signed in is the one chosen on the page in this browser, kept there; else the first of the browser's own languages the app carries; else the deployment's (language in GET /login/ways). Once someone is signed in, the language GET /me answers decides: the person's own, else the deployment's.

The person's own language is chosen at the globe at the foot of the menu, above their name — and at the foot of the menu a phone opens. It names the language the page speaks in itself, and lists every language the app carries, each in its own name, the deployment's marked everyone here. Picking one changes the page at once; the coordinator then keeps the choice for the person, on the server (PUT /me/language), so it is the same on every computer they sign in on, and its answer is what the page goes on with. Picking the language everyone here has is sent by its code like any other, and the coordinator keeps nothing of the person's own for it. A choice that cannot be kept puts back the language last kept and says so. The globe keeps nothing in the browser: the sign-in page's choice stays the browser's, so on a shared computer the next person's sign-in page does not open in this person's language. The document's lang follows the page's language throughout, before sign-in and after.

An entry shows when two things are true: its screen was built into this admin app, and the coordinator found its section's owner when it started (GET /sections). What the frame offers is display only: every call is judged by the service it reaches.

The sidebar is grouped. The shared screens open the first group: Export / Import, History, and People & access when the membership register's owner answered. A host screen sits in the group it names, or after the shared screens when it names none; the host's groups follow in the order their first screen arrives. A screen whose subject the workspace lacks can be shown locked, marked not included, and does not open.

The screens

History is one list, newest first, of every change a person made to the workspace's setup, whichever way it was made: a screen, an import, this app. Each service keeps its own history; the screen reads the membership register's and each stream the host names, a page at a time, each with its own place, and keeps no copy. Pages are laid side by side only as far as every stream has been read, since a stream with older lines unread may still hold a line newer than another stream's oldest; Show older reads the next page of each stream that has more. The register's lines, and the lines every owner writes for an export and an import of its part (with the part's and the file's hash), are worded by the screens; the host words its own services' lines, and a line nobody words shows its kind. A role's or a position's rename line whose name stayed records a change of its names in other languages alone, and is said that way, never as a rename to the same name. People are named from the register's list. A filter per stream the host names, one for the register, and a person filter over what has been read; a service that does not answer is named in a sentence above the list.

People & access reads and writes the membership register, in three tabs:

  • Users: the people by what they hold, the ones who signed in and were given nothing yet, and one person open beside the list, with their user type, their roles across the workspace, the Administrator checkbox and taking away all access (after a confirm). The day each last signed in. Machines that act in the workspace are listed apart and are not changed here; people who lost access show on request.
  • Roles: one role at a time, its boxes in the groups the host names and the rest under their service's own name, each in the words the service declares. Setting the workspace up is never offered: it comes only with the Administrator checkbox. A restricted field brings a box of its own, listed by the host; what ticking it hands out is said before Save. Save writes the role's whole set: the boxes shown as ticked, and every box the role holds that the screen does not show, except a field's box from an earlier restriction, which grants nothing any more (dropped only when the restricted fields could be read).
  • User types: each with what it holds, from the boxes granted across the whole workspace only, and how many hold it; and what someone arriving through the corporate login gets.

Roles, user types and positions are shown by their names as entered. The names in other languages the register may keep beside them (labels: a language's code and the name in it) are read but not edited here, and a Save leaves them as they are; a register that keeps none reads the same.

Every act saves at once and says what it did. A refusal is worded by the act that was made and the status that came back, never by the register's own text. A role's or a user type's name the register refuses as taken (409) is said one of two ways, worked out from the list the screen holds: another is already called that, or another already reads that way in one of the languages these screens carry words for — then the sentence names that one and the language. When the list cannot tell, the first.

Using it

Pin a tag, and the same uibyte tag the screens are built on:

"configbyte-screens": "github:go-make-bytes/configbyte-screens#v0.7.5",
"uibyte": "github:gmb-lib/uibyte#v0.16.0"

Move both pins together, then read the result back with npm ls uibyte: one copy.

The package ships source, like uibyte, so the host's build compiles it. Keep Vite's dependency optimiser away from both, and point Tailwind at both:

// vite.config.ts
optimizeDeps: { exclude: ['uibyte', 'configbyte-screens'] },
@import "tailwindcss";
@import "uibyte/theme.css";
@source "../node_modules/uibyte/src";
@source "../node_modules/configbyte-screens/src";

Peers the host provides: Vue 3, Vue Router 4, Pinia 2 and vue-i18n 11 (Composition API).

The admin app's entry, in the host:

import { createPinia } from 'pinia'
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
import { createRouter, createWebHistory } from 'vue-router'
import { adminRoutes, configbyteScreens } from 'configbyte-screens'

import AdminApp from './AdminApp.vue' // renders <AdminFrame> with the host's mark in its `mark` slot
import en from './locales/en.json'
import lv from './locales/lv.json'

const i18n = createI18n({ legacy: false, locale: 'en', fallbackLocale: 'en', messages: { en, lv } })
const router = createRouter({ history: createWebHistory(), routes: [...adminRoutes()] })

createApp(AdminApp)
  .use(createPinia())
  .use(router)
  .use(i18n)
  .use(
    configbyteScreens(i18n, {
      product: 'admin.product',
      never: 'admin.export.never',
      sections: {
        orders: {
          title: 'admin.sections.orders',
          parts: [{ key: 'kinds', word: 'admin.parts.kinds' }],
          configures: ['orders/setup:import'],
        },
      },
      carries: [{ section: 'orders', line: 'admin.export.kinds' }],
    }),
  )
  .mount('#app')
<!-- AdminApp.vue -->
<script setup lang="ts">
import { AdminFrame } from 'configbyte-screens'
</script>

<template>
  <AdminFrame>
    <template #mark="{ size }"><svg :width="size" :height="size" viewBox="0 0 24 24">…</svg></template>
  </AdminFrame>
</template>

A host's own screens that make their own calls (rather than through these screens) end the same session when one of their reads is answered 401: useAdminSession().signInEnded(). It does nothing while nobody is signed in. Only a read: a change refused that way is said in its own form, where what was typed still is. A 403 and every other failure stay where they happened.

A host that offers the language elsewhere too sets it the way the globe does, so both always show the same: useAdminSession().setLanguage(code), or null to clear the person's own.

What the host hands over

Every text is a message key in the host's own translator, so it reads in the person's language.

Option Meaning
product the product's name, on the sign-in page and wherever the deployment's own name is not yet known
sections per section name: its title, its parts in the host's order with their words, and the scopes that mark a person who may configure it
carries the Export card's lines, in order, each naming the section it describes; a line shows only when the deployment runs that section
never the Export card's last line: what the file never carries
entries the host's own admin screens: a label, the route it opens, the section whose owner serves it, and optionally the group it sits in, its icon, and locked for a subject the workspace lacks
register the section of the membership register; People & access and its History filter show only when the coordinator found it
roles how the Roles screen shows boxes: its groups (each a word and the starts of the permission names it gathers), the restricted fields the host reads from their owners, and the guard sentence for a ticked field's box
history History's filters and sources (a section, the query that keeps its lines, its filter and its service's short word), and describe, the sentence for a line of the host's own services
words replacements for any of the screens' own words, per language, for the sentences better said in the host's own terms
onApplied called after an import that may have changed something (applied, partial, rolledBack), so the host re-reads what it shows

A section the host does not describe keeps the name it arrived with, and so does a part.

Words

The screens carry their own words in English and Latvian, merged into the host's translator under configbyte.* when the plugin is installed, then the host's words over them. The frame switches to the language GET /me answers (presentation.locale: the person's own, else the deployment's) when it carries that language.

The ID card

Signing in with a card loads the card software's browser library from the host's own origin, at /web-eid.js. The host serves that file; it is never fetched from elsewhere. A browser that cannot load it is told the card software is not there.

The sign-in page in another app

The admin app's sign-in page is also the one page for the host's own everyday app, so a deployment has one sign-in look. SignInPage draws and asks; it calls nothing. The host gives its name, the heading and lead, the ways it read from its own backend, the languages it carries, and what to say, and runs the way a person chooses:

<SignInPage
  :product="t('app.product')"
  :title="t('app.signIn.title')"
  :lead="t('app.signIn.lead')"
  :ways="ways"
  :languages="[{ code: 'en', name: 'English' }, { code: 'lv', name: 'Latviešu' }]"
  :language="locale"
  :message="message"
  :waiting-for="waitingFor"
  :software-missing="softwareMissing"
  @update:language="choose"
  @start="signIn"
>
  <template #mark="{ size }"><svg :width="size" :height="size">…</svg></template>
</SignInPage>
Prop Meaning
product, tag the name beside the mark, and an optional tag after it
title, lead, link the heading, the sentence under it, and an optional { label, href } link at its end
ways { key, flow: 'card' | 'redirect', name } per way, drawn in order, each button carrying name exactly
languages, language the language menu's choices, each in its own name, and the one in use
message what to say above the buttons: cancelled, notMember, providerError, incomplete, expired, unmatched, failed, unknown, cardFailed, signedOut (with signed-out-of naming the app), ended
waiting-for, software-missing the way waited on, and whether the card software is missing
offers, explain whether a way in is offered at all, and whether the sentence under the buttons says what each does

What goes with it: messageOfMarker turns a ?error= marker into the message; languageBeforeSignIn, keepLanguage and keptLanguage settle the language before sign-in as above; noteSignedOut and takeSignedOut carry the signed-out sentence across a sign-out that ends at the authority; wayName finds a way's name by its key; signChallenge, isCardSoftwareMissing, CardError and CARD_SOFTWARE_URL are the ID card.

What it calls

Everything goes to the coordinator, same-origin, under /api/configbyte/v1, with its session cookie. Every POST, PUT and DELETE echoes the anti-forgery token from the configbyte_csrf cookie in X-CSRF-Token.

Method Path For
GET /login/ways before anyone is signed in: the ways to sign in, the everyday app's address, the deployment's language
GET /me who is signed in, the deployment's name, the person's scopes per section, and in presentation the language to speak (locale), the person's own (personLocale, null when none) and the deployment's (deploymentLocale)
PUT /me/language keeps the language picked at the globe for the person: {"language": "lv"}, answered with the three members above
GET /sections the deployment's sections, in the order an import writes them
POST /login/start the redirect sign-in; answers where to send the browser
POST /login/webeid/start · /login/webeid/complete the ID card: a challenge out, the card's signed answer back, unchanged
POST /logout ends the session; answers where to go next
GET /config/export the whole configuration as one file
POST /config/import?dryRun=true · /config/import what a file would do, then applying exactly that
any /sections/{section}/{path} relayed to that section's owner: People & access calls the register's tenants/{id}/access, …/roles, …/user-types, …/corporate-login-default, …/administrators/{user}, …/chart/positions, users/{user} and config; History calls history on the register and on each stream's owner

An Apply sends the file's own text with the versions its preview answered added as its last member, so what lands is exactly what the person read, or nothing.

Developing

You need Node at the version in .nvmrc.

npm ci
npm run gate      # typecheck, tests, the library build

src/hygiene.test.ts fails the build on a product's name or vocabulary in the source, and on references a reader of this repository cannot follow. src/locales.test.ts fails it on a word missing from either language.

Licence

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages