diff --git a/docs/configuring.md b/docs/configuring.md index 62b876f7ab..9902742015 100644 --- a/docs/configuring.md +++ b/docs/configuring.md @@ -243,6 +243,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)** **`items`** | `array` | _Optional_ | An array of items to be displayed within the section. See [`item`](#sectionitem). Sections must include either 1 or more items, or 1 or more widgets. **`widgets`** | `array` | _Optional_ | An array of widgets to be displayed within the section. See [`widget`](#sectionwidget-optional) **`displayData`** | `object` | _Optional_ | Meta-data to optionally override display settings for a given section. See [`displayData`](#sectiondisplaydata-optional) +**`pin`** | `string` | _Optional_ | The PIN code for unlocking this section if `secret` under `displayData` is true. Provide at the section root (e.g., pin: 2749). Validated client-side and remembered only for the current browser tab/session. Not intended for protecting highly sensitive data. Only for Child-proofing. if not entered but section is locked then default pin will be 0000. optionally user can input SHA256 hash of their pin here. **[⬆️ Back to Top](#configuring)** @@ -317,6 +318,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)** **`hideForGuests`** | `boolean` | _Optional_ | Current section will be visible for logged in users, but not for guests (see `appConfig.enableGuestAccess`). Defaults to `false` **`hideForKeycloakUsers`** | `object` | _Optional_ | Current section will be visible to all keycloak users, except for those configured via these groups and roles. See `hideForKeycloakUsers` **`showForKeycloakUsers`** | `object` | _Optional_ | Current section will be hidden from all keycloak users, except for those configured via these groups and roles. See `showForKeycloakUsers` +**`secret`** | `boolean` | _Optional_ | When true, the section is hidden behind a PIN gate. The PIN must be provided at the section root via `pin`. While locked, the section shows a PIN input instead of its items. On successful entry, the section unlocks for the current browser tab/session (not persisted across tab closes). In Edit Mode the gate is bypassed so you can configure/unhide the section. **[⬆️ Back to Top](#configuring)** diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index adb9319dd7..d042c799d0 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -251,6 +251,13 @@ "remove-section": "Remove" } }, + "pin": { + "unlock": "Unlock", + "lock": "Lock", + "lockedSection": "Locked section", + "enter-pin": "Enter PIN", + "incorrect-pin": "Incorrect PIN" + }, "footer": { "dev-by": "Developed by", "licensed-under": "Licensed under", diff --git a/src/components/InteractiveEditor/PinInput.vue b/src/components/InteractiveEditor/PinInput.vue new file mode 100644 index 0000000000..5834f0546a --- /dev/null +++ b/src/components/InteractiveEditor/PinInput.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/components/LinkItems/Section.vue b/src/components/LinkItems/Section.vue index f1659ed582..aee4418a85 100644 --- a/src/components/LinkItems/Section.vue +++ b/src/components/LinkItems/Section.vue @@ -14,63 +14,77 @@ :id="sectionRef" :ref="sectionRef" > - -
- {{ $t('home.no-items-section') }} -
- -
- - - -
-
- +
+
+ +
+
+ +
@import '@/styles/media-queries.scss'; @import '@/styles/style-helpers.scss'; +@import '@/styles/pin-input.scss'; .no-items { width: 100px; diff --git a/src/styles/pin-input.scss b/src/styles/pin-input.scss new file mode 100644 index 0000000000..00bf3a5cf7 --- /dev/null +++ b/src/styles/pin-input.scss @@ -0,0 +1,69 @@ +.btn-icon { + margin-right: .4rem; + font-size: .95em; + line-height: 0; + opacity: .9; +} + +.pin-gate { + padding: .9rem; + border-radius: var(--curve-factor); + border: 1px solid var(--border); + background: var(--item-background); + box-shadow: var(--item-shadow); + color: var(--primary); +} + +.pin-head { + display: flex; + align-items: center; + gap: .5rem; + margin-bottom: .6rem; + font-weight: 600; +} + +.pin-form { + margin-bottom: .6rem; +} +.pin-actions { + display: flex; + align-items: center; + justify-content: right; + gap: .5rem; + flex-wrap: wrap; +} + +.pin-btn { + padding: .5rem .9rem; + border-radius: .6rem; + border: 1px solid var(--border); + background: var(--button-bg); + color: var(--primary); + cursor: pointer; + transition: transform .06s ease; + + &:active { transform: translateY(1px); } +} + +.pin-reset { + background: transparent; + border: none; + padding: .3rem .2rem; + color: var(--primary); + opacity: .8; + text-decoration: underline; + cursor: pointer; + + &:hover { opacity: 1; } +} + +.pin-unlocked-bar{ + display: flex; + align-items: center; + width: 100%; + justify-content: flex-end; +} + +.pin-error { + color: var(--error, #d9534f); +} \ No newline at end of file diff --git a/src/utils/ConfigSchema.json b/src/utils/ConfigSchema.json index 92039798ab..0c3b4fc4b7 100644 --- a/src/utils/ConfigSchema.json +++ b/src/utils/ConfigSchema.json @@ -756,6 +756,11 @@ "type": "string", "description": "Icon will be displayed next to title" }, + "pin":{ + "title": "Section Pin", + "type": ["number", "string"], + "description": "PIN to unlock this section" + }, "displayData": { "title": "Display Data", "type": "object", @@ -776,6 +781,12 @@ "default": "default", "description": "How to sort items within the section. By default items are displayed in the order in which they are listed in within the config" }, + "secret": { + "title": "Pin Locked", + "type": "boolean", + "default": false, + "description": "If true, section will be hidden by default, and can be revealed by inputing pin" + }, "collapsed": { "title": "Is Collapsed?", "type": "boolean", diff --git a/src/utils/SectionHelpers.js b/src/utils/SectionHelpers.js index dfad7e6780..3d4cf350f1 100644 --- a/src/utils/SectionHelpers.js +++ b/src/utils/SectionHelpers.js @@ -1,5 +1,5 @@ /* Helper functions for Sections and Items */ - +import sha256 from 'crypto-js/sha256'; import { hideFurnitureOn } from '@/utils/defaults'; /* Returns false if page furniture should be hidden on said route */ @@ -39,3 +39,8 @@ export const applyItemId = (inputSections) => { }); return sections; }; + +export const pinHash = (pin) => { + if (!pin) return null; + return sha256(pin).toString().toUpperCase(); +}; diff --git a/src/views/Home.vue b/src/views/Home.vue index 004b181ac9..0b7240b99e 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -26,6 +26,7 @@ :index="index" :title="section.name" :icon="section.icon || undefined" + :pin="section.pin || undefined" :displayData="getDisplayData(section)" :groupId="makeSectionId(section)" :items="section.filteredItems" diff --git a/user-data/conf.yml b/user-data/conf.yml index 5f0b012ad0..ccd8bedf07 100644 --- a/user-data/conf.yml +++ b/user-data/conf.yml @@ -43,5 +43,4 @@ sections: - title: Support description: Get help with Dashy, raise a bug, or get in contact url: https://github.com/Lissy93/dashy/blob/master/.github/SUPPORT.md - icon: far fa-hands-helping - \ No newline at end of file + icon: far fa-hands-helping \ No newline at end of file