Bugzilla – Attachment 189238 Details for
Bug 39224
Move Shibboleth config from koha-conf to the DB
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 39224: add Vue UI for shibboleth configuration
Bug-39224-add-Vue-UI-for-shibboleth-configuration.patch (text/plain), 26.05 KB, created by
Jacob O'Mara
on 2025-11-06 22:11:54 UTC
(
hide
)
Description:
Bug 39224: add Vue UI for shibboleth configuration
Filename:
MIME Type:
Creator:
Jacob O'Mara
Created:
2025-11-06 22:11:54 UTC
Size:
26.05 KB
patch
obsolete
>From 83dbeca44d685cc3c635977aa4881550ec77cd03 Mon Sep 17 00:00:00 2001 >From: Jacob O'Mara <Jacob.omara@openfifth.co.uk> >Date: Tue, 4 Nov 2025 15:22:19 +0000 >Subject: [PATCH] Bug 39224: add Vue UI for shibboleth configuration > >Add staff interface pages for managing Shibboleth configuration >including settings and field mappings. Includes API client, Vue >components, store, and routes. >--- > .../prog/en/modules/shibboleth/shibboleth.tt | 27 +++ > .../prog/js/fetch/shibboleth-api-client.js | 60 +++++++ > .../js/vue/components/Shibboleth/Home.vue | 18 ++ > .../js/vue/components/Shibboleth/Main.vue | 95 ++++++++++ > .../Shibboleth/ShibbolethConfigResource.vue | 155 ++++++++++++++++ > .../Shibboleth/ShibbolethMappingResource.vue | 166 ++++++++++++++++++ > .../prog/js/vue/modules/shibboleth.ts | 72 ++++++++ > .../prog/js/vue/routes/shibboleth.js | 69 ++++++++ > .../prog/js/vue/stores/shibboleth.js | 16 ++ > shibboleth/shibboleth.pl | 41 +++++ > 10 files changed, 719 insertions(+) > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/shibboleth/shibboleth.tt > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/fetch/shibboleth-api-client.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/Home.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/Main.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/ShibbolethConfigResource.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/ShibbolethMappingResource.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/modules/shibboleth.ts > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/routes/shibboleth.js > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/stores/shibboleth.js > create mode 100755 shibboleth/shibboleth.pl > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/shibboleth/shibboleth.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/shibboleth/shibboleth.tt >new file mode 100644 >index 00000000000..2f9a1c66d25 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/shibboleth/shibboleth.tt >@@ -0,0 +1,27 @@ >+[% USE raw %] >+[% USE To %] >+[% SET footerjs = 1 %] >+[% INCLUDE 'doc-head-open.inc' %] >+<title> Shibboleth › Koha </title> >+[% INCLUDE 'doc-head-close.inc' %] >+</head> >+ >+<body id="admin_shibboleth" class="admin"> >+[% WRAPPER 'header.inc' %] >+ [% INCLUDE 'prefs-admin-search.inc' %] >+[% END %] >+ >+<div id="shibboleth"> >+ <!-- this is closed in intranet-bottom.inc --> >+ >+ [% MACRO jsinclude BLOCK %] >+ [% INCLUDE 'calendar.inc' %] >+ [% INCLUDE 'datatables.inc' %] >+ <script> >+ const logged_in_user = [% To.json(logged_in_user.unblessed) | $raw %]; >+ window.borrower_columns = [% To.json(borrower_columns) | $raw %]; >+ </script> >+ [% Asset.js("js/vue/dist/shibboleth.js") | $raw %] >+ [% END %] >+ [% INCLUDE 'intranet-bottom.inc' %] >+</div> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/fetch/shibboleth-api-client.js b/koha-tmpl/intranet-tmpl/prog/js/fetch/shibboleth-api-client.js >new file mode 100644 >index 00000000000..e8d9314c0ea >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/fetch/shibboleth-api-client.js >@@ -0,0 +1,60 @@ >+export class ShibbolethAPIClient { >+ constructor(HttpClient) { >+ this.httpClient = new HttpClient({ >+ baseURL: "/api/v1/shibboleth/", >+ }); >+ } >+ >+ get config() { >+ return { >+ get: () => >+ this.httpClient.get({ >+ endpoint: "config", >+ }), >+ update: config => >+ this.httpClient.put({ >+ endpoint: "config", >+ body: config, >+ }), >+ }; >+ } >+ >+ get mappings() { >+ return { >+ get: id => >+ this.httpClient.get({ >+ endpoint: "mappings/" + id, >+ }), >+ getAll: params => >+ this.httpClient.getAll({ >+ endpoint: "mappings", >+ }), >+ delete: id => >+ this.httpClient.delete({ >+ endpoint: "mappings/" + id, >+ }), >+ create: mapping => >+ this.httpClient.post({ >+ endpoint: "mappings", >+ body: mapping, >+ }), >+ update: (mapping, id) => >+ this.httpClient.put({ >+ endpoint: "mappings/" + id, >+ body: mapping, >+ }), >+ count: (query = {}) => >+ this.httpClient.count({ >+ endpoint: >+ "mappings?" + >+ new URLSearchParams({ >+ _page: 1, >+ _per_page: 1, >+ ...(query && { q: JSON.stringify(query) }), >+ }), >+ }), >+ }; >+ } >+} >+ >+export default ShibbolethAPIClient; >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/Home.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/Home.vue >new file mode 100644 >index 00000000000..7326d5b878c >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/Home.vue >@@ -0,0 +1,18 @@ >+<template> >+ <div> >+ <h2>{{ $__("Shibboleth Configuration") }}</h2> >+ <p> >+ {{ >+ $__( >+ "Configure Shibboleth authentication for your Koha installation" >+ ) >+ }} >+ </p> >+ </div> >+</template> >+ >+<script> >+export default { >+ name: "ShibbolethHome", >+}; >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/Main.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/Main.vue >new file mode 100644 >index 00000000000..13e69bd8c38 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/Main.vue >@@ -0,0 +1,95 @@ >+<template> >+ <div v-if="initialized"> >+ <div id="sub-header"> >+ <Breadcrumbs /> >+ <Help /> >+ </div> >+ <div class="main container-fluid"> >+ <div class="row"> >+ <div class="col-md-10 order-md-2 order-sm-1"> >+ <main> >+ <Dialog /> >+ <router-view /> >+ </main> >+ </div> >+ <div class="col-md-2 order-sm-2 order-md-1"> >+ <LeftMenu :title="$__('Shibboleth')"></LeftMenu> >+ </div> >+ </div> >+ </div> >+ </div> >+</template> >+ >+<script> >+import { inject, onBeforeMount, ref } from "vue"; >+import Breadcrumbs from "../Breadcrumbs.vue"; >+import { storeToRefs } from "pinia"; >+import Help from "../Help.vue"; >+import LeftMenu from "../LeftMenu.vue"; >+import Dialog from "../Dialog.vue"; >+import "vue-select/dist/vue-select.css"; >+ >+export default { >+ setup() { >+ const mainStore = inject("mainStore"); >+ const { loading, loaded } = mainStore; >+ >+ const initialized = ref(false); >+ >+ onBeforeMount(() => { >+ loading(); >+ setTimeout(() => { >+ loaded(); >+ initialized.value = true; >+ }, 0); >+ }); >+ >+ return { >+ initialized, >+ }; >+ }, >+ components: { >+ Breadcrumbs, >+ Dialog, >+ Help, >+ LeftMenu, >+ }, >+}; >+</script> >+ >+<style> >+#menu ul ul, >+#navmenulist ul ul { >+ padding-left: 2em; >+ font-size: 100%; >+} >+ >+form .v-select { >+ display: inline-block; >+ background-color: white; >+ width: 30%; >+} >+ >+.v-select, >+input:not([type="submit"]):not([type="search"]):not([type="button"]):not( >+ [type="checkbox"] >+ ):not([type="radio"]), >+textarea { >+ border-color: rgba(60, 60, 60, 0.26); >+ border-width: 1px; >+ border-radius: 4px; >+ min-width: 30%; >+} >+ >+#navmenulist ul li a.current.disabled { >+ background-color: inherit; >+ border-left: 5px solid #e6e6e6; >+ color: #000; >+} >+ >+#navmenulist ul li a.disabled { >+ color: #666; >+ pointer-events: none; >+ font-weight: 700; >+} >+</style> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/ShibbolethConfigResource.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/ShibbolethConfigResource.vue >new file mode 100644 >index 00000000000..a06130bc8f0 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/ShibbolethConfigResource.vue >@@ -0,0 +1,155 @@ >+<template> >+ <div v-if="!initialized">{{ $__("Loading") }}</div> >+ <BaseResource v-else :routeAction="routeAction" :instancedResource="this" /> >+</template> >+ >+<script> >+import { inject, ref, onMounted, reactive } from "vue"; >+import BaseResource from "./../BaseResource.vue"; >+import { useBaseResource } from "../../composables/base-resource.js"; >+import { storeToRefs } from "pinia"; >+import { APIClient } from "../../fetch/api-client.js"; >+import { $__ } from "@koha-vue/i18n"; >+ >+export default { >+ name: "ShibbolethConfigResource", >+ components: { >+ BaseResource, >+ }, >+ props: { >+ routeAction: String, >+ }, >+ emits: ["select-resource"], >+ setup(props) { >+ const initialized = ref(false); >+ const configData = ref(null); >+ const resourceAttrs = [ >+ { >+ name: "force_opac_sso", >+ type: "boolean", >+ label: __("Force OPAC SSO"), >+ group: "SSO Settings", >+ toolTip: __( >+ "Automatically redirect OPAC users to Shibboleth login" >+ ), >+ }, >+ { >+ name: "force_staff_sso", >+ type: "boolean", >+ label: __("Force staff SSO"), >+ group: "SSO Settings", >+ toolTip: __( >+ "Automatically redirect staff users to Shibboleth login" >+ ), >+ }, >+ { >+ name: "autocreate", >+ type: "boolean", >+ label: __("Auto create users"), >+ group: "User Management", >+ toolTip: __( >+ "Automatically create patron records for new Shibboleth users" >+ ), >+ }, >+ { >+ name: "sync", >+ type: "boolean", >+ label: __("Sync user attributes"), >+ group: "User Management", >+ toolTip: __( >+ "Update patron attributes from Shibboleth on each login" >+ ), >+ }, >+ { >+ name: "welcome", >+ type: "boolean", >+ label: __("Send welcome email"), >+ group: "User Management", >+ toolTip: __( >+ "Send welcome email to new users created via Shibboleth" >+ ), >+ }, >+ ]; >+ >+ const additionalToolbarButtons = (resource, componentData) => { >+ const buttons = { >+ form: [ >+ { >+ title: $__("Submit"), >+ form: componentData.resourceForm, >+ }, >+ { >+ to: { >+ name: "ShibbolethHome", >+ }, >+ title: $__("Cancel"), >+ cssClass: "btn btn-link", >+ }, >+ ], >+ }; >+ return buttons; >+ }; >+ >+ const baseResource = useBaseResource({ >+ resourceName: "config", >+ nameAttr: "shibboleth_config_id", >+ idAttr: "shibboleth_config_id", >+ components: { >+ show: "ShibbolethConfigShow", >+ list: "ShibbolethHome", >+ add: "ShibbolethConfigFormAdd", >+ edit: "ShibbolethConfigFormEdit", >+ }, >+ apiClient: APIClient.shibboleth.config, >+ i18n: { >+ displayName: $__("Shibboleth Configuration"), >+ editLabel: $__("Edit Shibboleth Configuration"), >+ }, >+ additionalToolbarButtons, >+ stickyToolbar: ["Form"], >+ embedded: props.embedded, >+ formGroupsDisplayMode: "accordion", >+ resourceAttrs, >+ props, >+ moduleStore: "ShibbolethStore", >+ }); >+ >+ const onFormSave = async (e, configToSave) => { >+ e.preventDefault(); >+ >+ const config = JSON.parse(JSON.stringify(configToSave)); >+ delete config.shibboleth_config_id; >+ >+ const client = APIClient.shibboleth.config; >+ >+ try { >+ await client.update(config); >+ baseResource.setMessage(__("Configuration updated")); >+ baseResource.router.push({ name: "ShibbolethHome" }); >+ } catch (error) { >+ // Errors handled by base resource >+ } >+ }; >+ >+ // Fetch the singleton config on mount and replace newResource getter >+ onMounted(async () => { >+ try { >+ configData.value = await APIClient.shibboleth.config.get(); >+ initialized.value = true; >+ } catch (error) { >+ console.error("Failed to load config:", error); >+ initialized.value = true; >+ } >+ }); >+ >+ return { >+ ...baseResource, >+ initialized, >+ onFormSave, >+ get newResource() { >+ return configData.value || baseResource.newResource; >+ }, >+ }; >+ }, >+}; >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/ShibbolethMappingResource.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/ShibbolethMappingResource.vue >new file mode 100644 >index 00000000000..1bda24b8015 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Shibboleth/ShibbolethMappingResource.vue >@@ -0,0 +1,166 @@ >+<template> >+ <BaseResource :routeAction="routeAction" :instancedResource="this" /> >+</template> >+ >+<script> >+import { inject } from "vue"; >+import BaseResource from "./../BaseResource.vue"; >+import { useBaseResource } from "../../composables/base-resource.js"; >+import { storeToRefs } from "pinia"; >+import { APIClient } from "../../fetch/api-client.js"; >+import { $__ } from "@koha-vue/i18n"; >+ >+export default { >+ name: "ShibbolethMappingResource", >+ components: { >+ BaseResource, >+ }, >+ props: { >+ routeAction: String, >+ }, >+ emits: ["select-resource"], >+ setup(props) { >+ const getBorrowerColumns = () => { >+ return window.borrower_columns || []; >+ }; >+ >+ const borrowerColumnsArray = getBorrowerColumns(); >+ >+ const resourceAttrs = [ >+ { >+ name: "koha_field", >+ required: true, >+ type: "select", >+ options: borrowerColumnsArray, >+ requiredKey: "value", >+ selectLabel: "label", >+ label: __("Koha field"), >+ group: "Details", >+ toolTip: __("The field name in the Koha borrowers table"), >+ }, >+ { >+ name: "idp_field", >+ type: "text", >+ label: __("Identity Provider attribute"), >+ group: "Details", >+ toolTip: __( >+ "The attribute name provided by the Shibboleth Identity Provider" >+ ), >+ }, >+ { >+ name: "default_content", >+ type: "text", >+ label: __("Default value"), >+ group: "Details", >+ toolTip: __( >+ "Default value to use if the IdP doesn't provide this attribute" >+ ), >+ }, >+ { >+ name: "is_matchpoint", >+ type: "boolean", >+ label: __("Use as matchpoint"), >+ group: "Details", >+ toolTip: __( >+ "Use this field to match existing users (only one matchpoint allowed)" >+ ), >+ }, >+ ]; >+ >+ const additionalToolbarButtons = (resource, componentData) => { >+ const buttons = { >+ form: [ >+ { >+ title: $__("Submit"), >+ form: componentData.resourceForm, >+ }, >+ { >+ to: { >+ name: "ShibbolethMappingsList", >+ }, >+ title: $__("Cancel"), >+ cssClass: "btn btn-link", >+ }, >+ ], >+ }; >+ return buttons; >+ }; >+ >+ const baseResource = useBaseResource({ >+ resourceName: "mapping", >+ nameAttr: "koha_field", >+ idAttr: "mapping_id", >+ components: { >+ show: "ShibbolethMappingsShow", >+ list: "ShibbolethMappingsList", >+ add: "ShibbolethMappingsFormAdd", >+ edit: "ShibbolethMappingsFormEdit", >+ }, >+ apiClient: APIClient.shibboleth.mappings, >+ i18n: { >+ deleteConfirmationMessage: $__( >+ "Are you sure you want to remove this field mapping?" >+ ), >+ deleteSuccessMessage: $__("Mapping deleted"), >+ displayName: $__("Field Mapping"), >+ editLabel: $__("Edit field mapping"), >+ emptyListMessage: $__("There are no field mappings defined"), >+ newLabel: $__("New field mapping"), >+ }, >+ table: { >+ resourceTableUrl: >+ APIClient.shibboleth.httpClient._baseURL + "mappings", >+ options: {}, >+ }, >+ additionalToolbarButtons, >+ stickyToolbar: ["Form"], >+ embedded: props.embedded, >+ formGroupsDisplayMode: "accordion", >+ resourceAttrs, >+ props, >+ moduleStore: "ShibbolethStore", >+ }); >+ >+ const tableOptions = { >+ url: () => baseResource.getResourceTableUrl(), >+ options: {}, >+ table_settings: null, >+ actions: { >+ 0: ["show"], >+ "-1": ["edit", "delete"], >+ }, >+ }; >+ >+ const onFormSave = async (e, mappingToSave) => { >+ e.preventDefault(); >+ >+ const mapping = JSON.parse(JSON.stringify(mappingToSave)); >+ const mapping_id = mapping.mapping_id; >+ >+ delete mapping.mapping_id; >+ >+ const client = APIClient.shibboleth.mappings; >+ >+ try { >+ if (mapping_id) { >+ await client.update(mapping, mapping_id); >+ baseResource.setMessage(__("Mapping updated")); >+ } else { >+ await client.create(mapping); >+ baseResource.setMessage(__("Mapping created")); >+ } >+ >+ baseResource.router.push({ name: "ShibbolethMappingsList" }); >+ } catch (error) { >+ // Errors handled by base resource >+ } >+ }; >+ >+ return { >+ ...baseResource, >+ tableOptions, >+ onFormSave, >+ }; >+ }, >+}; >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/modules/shibboleth.ts b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/shibboleth.ts >new file mode 100644 >index 00000000000..82fe78e6ca3 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/modules/shibboleth.ts >@@ -0,0 +1,72 @@ >+import { createApp } from "vue"; >+import { createWebHistory, createRouter } from "vue-router"; >+import { createPinia } from "pinia"; >+ >+import { library } from "@fortawesome/fontawesome-svg-core"; >+import { >+ faPlus, >+ faMinus, >+ faPencil, >+ faTrash, >+ faSpinner, >+ faSave, >+ faCog, >+ faExchangeAlt, >+} from "@fortawesome/free-solid-svg-icons"; >+import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; >+import vSelect from "vue-select"; >+ >+library.add( >+ faPlus, >+ faMinus, >+ faPencil, >+ faTrash, >+ faSpinner, >+ faSave, >+ faCog, >+ faExchangeAlt >+); >+ >+import App from "../components/Shibboleth/Main.vue"; >+ >+import { routes as routesDef } from "../routes/shibboleth"; >+ >+import { useMainStore } from "../stores/main"; >+import { useShibbolethStore } from "../stores/shibboleth"; >+import { useNavigationStore } from "../stores/navigation"; >+import i18n from "../i18n"; >+ >+const pinia = createPinia(); >+ >+const mainStore = useMainStore(pinia); >+const navigationStore = useNavigationStore(pinia); >+const routes = navigationStore.setRoutes(routesDef); >+ >+const router = createRouter({ >+ history: createWebHistory(), >+ linkActiveClass: "current", >+ routes, >+}); >+ >+const app = createApp(App); >+ >+const rootComponent = app >+ .use(i18n) >+ .use(pinia) >+ .use(router) >+ .component("font-awesome-icon", FontAwesomeIcon) >+ .component("v-select", vSelect); >+ >+app.config.unwrapInjectedRef = true; >+app.provide("mainStore", mainStore); >+app.provide("navigationStore", navigationStore); >+const ShibbolethStore = useShibbolethStore(pinia); >+app.provide("ShibbolethStore", ShibbolethStore); >+ >+app.mount("#shibboleth"); >+ >+const { removeMessages } = mainStore; >+router.beforeEach((to, from) => { >+ navigationStore.$patch({ current: to.matched, params: to.params || {} }); >+ removeMessages(); >+}); >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/shibboleth.js b/koha-tmpl/intranet-tmpl/prog/js/vue/routes/shibboleth.js >new file mode 100644 >index 00000000000..c55707e2cb4 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/routes/shibboleth.js >@@ -0,0 +1,69 @@ >+import { markRaw } from "vue"; >+ >+import Home from "../components/Shibboleth/Home.vue"; >+import ResourceWrapper from "../components/ResourceWrapper.vue"; >+import { $__ } from "../i18n"; >+ >+export const routes = [ >+ { >+ path: "/cgi-bin/koha/shibboleth/shibboleth.pl", >+ is_default: true, >+ is_base: true, >+ title: $__("Shibboleth"), >+ children: [ >+ { >+ path: "", >+ name: "ShibbolethHome", >+ component: markRaw(Home), >+ is_navigation_item: false, >+ }, >+ { >+ path: "/cgi-bin/koha/shibboleth/config", >+ title: $__("Configuration"), >+ icon: "fa fa-cog", >+ is_end_node: true, >+ resource: "Shibboleth/ShibbolethConfigResource.vue", >+ children: [ >+ { >+ path: "", >+ name: "ShibbolethConfigFormEdit", >+ component: markRaw(ResourceWrapper), >+ title: $__("Edit configuration"), >+ }, >+ ], >+ }, >+ { >+ path: "/cgi-bin/koha/shibboleth/mappings", >+ title: $__("Field Mappings"), >+ icon: "fa fa-exchange-alt", >+ is_end_node: true, >+ resource: "Shibboleth/ShibbolethMappingResource.vue", >+ children: [ >+ { >+ path: "", >+ name: "ShibbolethMappingsList", >+ component: markRaw(ResourceWrapper), >+ }, >+ { >+ path: ":mapping_id", >+ name: "ShibbolethMappingsShow", >+ component: markRaw(ResourceWrapper), >+ title: $__("Show field mapping"), >+ }, >+ { >+ path: "add", >+ name: "ShibbolethMappingsFormAdd", >+ component: markRaw(ResourceWrapper), >+ title: $__("Add field mapping"), >+ }, >+ { >+ path: "edit/:mapping_id", >+ name: "ShibbolethMappingsFormEdit", >+ component: markRaw(ResourceWrapper), >+ title: $__("Edit field mapping"), >+ }, >+ ], >+ }, >+ ], >+ }, >+]; >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/stores/shibboleth.js b/koha-tmpl/intranet-tmpl/prog/js/vue/stores/shibboleth.js >new file mode 100644 >index 00000000000..ed7a4989516 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/stores/shibboleth.js >@@ -0,0 +1,16 @@ >+import { defineStore } from "pinia"; >+ >+export const useShibbolethStore = defineStore("shibboleth", { >+ state: () => ({ >+ config: null, >+ mappings: [], >+ }), >+ actions: { >+ setConfig(config) { >+ this.config = config; >+ }, >+ setMappings(mappings) { >+ this.mappings = mappings; >+ }, >+ }, >+}); >diff --git a/shibboleth/shibboleth.pl b/shibboleth/shibboleth.pl >new file mode 100755 >index 00000000000..205e76eb514 >--- /dev/null >+++ b/shibboleth/shibboleth.pl >@@ -0,0 +1,41 @@ >+#!/usr/bin/perl >+ >+use Modern::Perl; >+ >+use CGI qw ( -utf8 ); >+use C4::Auth qw( get_template_and_user ); >+use C4::Output qw( output_html_with_http_headers ); >+use Koha::Patrons; >+ >+my $input = CGI->new; >+ >+my ( $template, $loggedinuser, $cookie ) = get_template_and_user( >+ { >+ template_name => "shibboleth/shibboleth.tt", >+ query => $input, >+ type => "intranet", >+ flagsrequired => { parameters => 'manage_identity_providers' }, >+ } >+); >+ >+my $borrowers_source = Koha::Patrons->_resultset->result_source; >+ >+my @borrower_columns; >+my %skip_columns = map { $_ => 1 } qw( password updated_on timestamp ); >+ >+foreach my $column ( sort $borrowers_source->columns ) { >+ next if $skip_columns{$column}; >+ >+ my $column_info = $borrowers_source->column_info($column); >+ my $label = $column_info->{comments} || $column; >+ >+ push @borrower_columns, >+ { >+ value => $column, >+ label => $label, >+ }; >+} >+ >+$template->param( borrower_columns => \@borrower_columns ); >+ >+output_html_with_http_headers $input, $cookie, $template->output; >-- >2.39.5
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 39224
:
189234
|
189235
|
189236
|
189237
| 189238 |
189239
|
189240
|
189241
|
189242
|
189243
|
189244