Bug 35919: Add record sources admin page
This patch introduces a Vue.js based record sources managing page. To test it: 1. Apply this patch 2. Build the Vue.js stuff: $ ktd --shell k$ yarn js:build k$ restart_all 3. On the staff interface, go to Administration > Record sources 4. Play with the interface and the offered actions => SUCCESS: Things go well 5. Sign off :-D Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> Signed-off-by: Matt Blenkinsop <matt.blenkinsop@ptfs-europe.com> Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org> Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
This commit is contained in:
parent
07a12da11a
commit
149a6da9ec
12 changed files with 517 additions and 0 deletions
37
admin/record_sources.pl
Executable file
37
admin/record_sources.pl
Executable file
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# Copyright 2023 Theke Solutions
|
||||
#
|
||||
# This file is part of Koha.
|
||||
#
|
||||
# Koha is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Koha is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Koha; if not, see <http://www.gnu.org/licenses>.
|
||||
|
||||
use Modern::Perl;
|
||||
|
||||
use CGI qw ( -utf8 );
|
||||
use C4::Auth qw( get_template_and_user );
|
||||
use C4::Output qw( output_html_with_http_headers );
|
||||
|
||||
my $query = CGI->new;
|
||||
|
||||
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
|
||||
{
|
||||
template_name => "admin/record_sources.tt",
|
||||
query => $query,
|
||||
type => "intranet",
|
||||
flagsrequired => { parameters => 'manage_record_sources' },
|
||||
}
|
||||
);
|
||||
|
||||
output_html_with_http_headers $query, $cookie, $template->output;
|
1
debian/templates/apache-shared-intranet.conf
vendored
1
debian/templates/apache-shared-intranet.conf
vendored
|
@ -23,6 +23,7 @@ RewriteRule ^(.*)_[0-9]{2}\.[0-9]{7}\.(js|css)$ $1.$2 [L]
|
|||
RewriteRule ^/cgi-bin/koha/erm/.*$ /cgi-bin/koha/erm/erm.pl [PT]
|
||||
RewriteCond %{REQUEST_URI} !^/cgi-bin/koha/preservation/.*.pl$
|
||||
RewriteRule ^/cgi-bin/koha/preservation/.*$ /cgi-bin/koha/preservation/home.pl [PT]
|
||||
RewriteRule ^/cgi-bin/koha/admin/record_sources(.*)?$ /cgi-bin/koha/admin/record_sources.pl$1 [PT]
|
||||
|
||||
Alias "/api" "/usr/share/koha/api"
|
||||
<Directory "/usr/share/koha/api">
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
<dt><a href="/cgi-bin/koha/admin/searchengine/elasticsearch/mappings.pl">Search engine configuration (Elasticsearch)</a></dt>
|
||||
<dd>Manage indexes, facets, and their mappings to MARC fields and subfields</dd>
|
||||
[% END %]
|
||||
[% IF ( CAN_user_parameters_manage_record_sources ) %]
|
||||
<dt><a href="/cgi-bin/koha/admin/record_sources">Record sources</a></dt>
|
||||
<dd>Define record sources to import from</dd>
|
||||
[% END %]
|
||||
</dl>
|
||||
[% END %]
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
[% USE raw %]
|
||||
[% USE To %]
|
||||
[% USE Asset %]
|
||||
[% USE KohaDates %]
|
||||
[% USE TablesSettings %]
|
||||
[% USE AuthorisedValues %]
|
||||
[% SET footerjs = 1 %]
|
||||
[% PROCESS 'i18n.inc' %]
|
||||
[% INCLUDE 'doc-head-open.inc' %]
|
||||
<title>
|
||||
Record sources › Koha
|
||||
</title>
|
||||
[% INCLUDE 'doc-head-close.inc' %]
|
||||
</head>
|
||||
|
||||
<body id="record_sources" class="record_sources">
|
||||
[% WRAPPER 'header.inc' %]
|
||||
[% INCLUDE 'prefs-admin-search.inc' %]
|
||||
[% END %]
|
||||
|
||||
<div id="record-source"> <!-- this is closed in intranet-bottom.inc -->
|
||||
|
||||
[% MACRO jsinclude BLOCK %]
|
||||
[% INCLUDE 'calendar.inc' %] <!-- FIXME: this shouldn't be needed -->
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
[% INCLUDE 'columns_settings.inc' %]
|
||||
[% INCLUDE 'js-patron-format.inc' %]
|
||||
[% INCLUDE 'js-date-format.inc' %]
|
||||
|
||||
[% Asset.js("js/vue/dist/admin/record_sources.js") | $raw %]
|
||||
|
||||
[% END %]
|
||||
|
||||
[% INCLUDE 'intranet-bottom.inc' %]
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<div v-if="!initialized">{{ $__("Loading") }}</div>
|
||||
<div v-else id="record_source_edit">
|
||||
<h1 v-if="record_source.record_source_id">
|
||||
{{ $__("Edit '%s'").format(record_source.name) }}
|
||||
</h1>
|
||||
<h1 v-else>{{ $__("Add record source") }}</h1>
|
||||
<form @submit="onSubmit($event)">
|
||||
<fieldset class="rows">
|
||||
<ol>
|
||||
<li>
|
||||
<label class="required" for="name">
|
||||
{{ $__("Name") }}:
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
v-model="record_source.name"
|
||||
required
|
||||
/>
|
||||
<span class="required">{{ $__("Required") }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<label for="can_be_edited">
|
||||
{{ $__("Can be edited") }}:
|
||||
</label>
|
||||
<input
|
||||
id="can_be_edited"
|
||||
type="checkbox"
|
||||
v-model="record_source.can_be_edited"
|
||||
/>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
<fieldset class="action">
|
||||
<input type="submit" :value="$__('Submit')" />
|
||||
<router-link
|
||||
:to="{ name: 'RecordSourcesList' }"
|
||||
role="button"
|
||||
class="cancel"
|
||||
>{{ $__("Cancel") }}</router-link
|
||||
>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject } from "vue"
|
||||
import { setMessage, setError, setWarning } from "../../../messages"
|
||||
import { APIClient } from "../../../fetch/api-client.js"
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const { setMessage } = inject("mainStore")
|
||||
return {
|
||||
setMessage,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
record_source: {
|
||||
record_source_id: null,
|
||||
name: "",
|
||||
can_be_edited: false,
|
||||
},
|
||||
initialized: false,
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
next(vm => {
|
||||
if (to.params.record_source_id) {
|
||||
vm.getRecordSource(to.params.record_source_id)
|
||||
} else {
|
||||
vm.initialized = true
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async getRecordSource(record_source_id) {
|
||||
const client = APIClient.record_sources
|
||||
client.record_sources.get(record_source_id).then(
|
||||
record_source => {
|
||||
this.record_source = record_source
|
||||
this.record_source_id = record_source_id
|
||||
this.initialized = true
|
||||
},
|
||||
error => {}
|
||||
)
|
||||
},
|
||||
onSubmit(e) {
|
||||
e.preventDefault()
|
||||
const client = APIClient.record_sources
|
||||
let response
|
||||
// RO attribute
|
||||
delete this.record_source.record_source_id
|
||||
if (this.record_source_id) {
|
||||
// update
|
||||
response = client.record_sources
|
||||
.update(this.record_source, this.record_source_id)
|
||||
.then(
|
||||
success => {
|
||||
setMessage(this.$__("Record source updated!"))
|
||||
this.$router.push({ name: "RecordSourcesList" })
|
||||
},
|
||||
error => {}
|
||||
)
|
||||
} else {
|
||||
response = client.record_sources
|
||||
.create(this.record_source)
|
||||
.then(
|
||||
success => {
|
||||
setMessage(this.$__("Record source created!"))
|
||||
this.$router.push({ name: "RecordSourcesList" })
|
||||
},
|
||||
error => {}
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div v-if="!initialized">{{ $__("Loading") }}</div>
|
||||
<div v-else id="record_sources_list">
|
||||
<Toolbar>
|
||||
<ToolbarButton
|
||||
:to="{ name: 'RecordSourcesFormAdd' }"
|
||||
icon="plus"
|
||||
:title="$__('New record source')"
|
||||
/>
|
||||
</Toolbar>
|
||||
<h1>{{ title }}</h1>
|
||||
<div v-if="record_sources_count > 0" class="page-section">
|
||||
<KohaTable
|
||||
ref="table"
|
||||
v-bind="tableOptions"
|
||||
@edit="doEdit"
|
||||
@delete="doDelete"
|
||||
></KohaTable>
|
||||
</div>
|
||||
<div v-else class="dialog message">
|
||||
{{ $__("There are no record sources defined") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Toolbar from "../../Toolbar.vue"
|
||||
import ToolbarButton from "../../ToolbarButton.vue"
|
||||
import { inject } from "vue"
|
||||
import { APIClient } from "../../../fetch/api-client.js"
|
||||
import KohaTable from "../../KohaTable.vue"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: this.$__("Record sources"),
|
||||
tableOptions: {
|
||||
columns: [
|
||||
{
|
||||
title: this.$__("ID"),
|
||||
data: "record_source_id",
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
title: this.$__("Name"),
|
||||
data: "name",
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
title: __("Can be edited"),
|
||||
data: "can_be_edited",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
return escape_str(
|
||||
row.can_be_edited ? __("Yes") : __("No")
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
actions: {
|
||||
"-1": ["edit", "delete"],
|
||||
},
|
||||
url: "/api/v1/record_sources",
|
||||
},
|
||||
initialized: false,
|
||||
record_sources_count: 0,
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const { setWarning, setMessage, setError, setConfirmationDialog } =
|
||||
inject("mainStore")
|
||||
return {
|
||||
setWarning,
|
||||
setMessage,
|
||||
setError,
|
||||
setConfirmationDialog,
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
next(vm => {
|
||||
vm.getRecordSourcesCount().then(() => (vm.initialized = true))
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async getRecordSourcesCount() {
|
||||
const client = APIClient.record_sources
|
||||
await client.record_sources.count().then(
|
||||
count => {
|
||||
this.record_sources_count = count
|
||||
},
|
||||
error => {}
|
||||
)
|
||||
},
|
||||
newRecordSource() {
|
||||
this.$router.push({ name: "RecordSourcesFormAdd" })
|
||||
},
|
||||
doEdit: function ({ record_source_id }, dt, event) {
|
||||
this.$router.push({
|
||||
name: "RecordSourcesFormAddEdit",
|
||||
params: { record_source_id },
|
||||
})
|
||||
},
|
||||
doDelete: function (record_source, dt, event) {
|
||||
this.setConfirmationDialog(
|
||||
{
|
||||
title: this.$__(
|
||||
"Are you sure you want to remove this record source?"
|
||||
),
|
||||
message: record_source.name,
|
||||
accept_label: this.$__("Yes, delete"),
|
||||
cancel_label: this.$__("No, do not delete"),
|
||||
},
|
||||
() => {
|
||||
const client = APIClient.record_sources
|
||||
client.record_sources
|
||||
.delete(record_source.record_source_id)
|
||||
.then(
|
||||
success => {
|
||||
this.setMessage(
|
||||
this.$__("Record source %s deleted").format(
|
||||
record_source.name
|
||||
),
|
||||
true
|
||||
)
|
||||
dt.draw()
|
||||
},
|
||||
error => {}
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
components: {
|
||||
KohaTable,
|
||||
Toolbar,
|
||||
ToolbarButton,
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div>
|
||||
<div id="sub-header">
|
||||
<Breadcrumbs></Breadcrumbs>
|
||||
<Help />
|
||||
</div>
|
||||
<div class="main container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
|
||||
<main>
|
||||
<Dialog></Dialog>
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Breadcrumbs from "../../Breadcrumbs.vue"
|
||||
import Help from "../../Help.vue"
|
||||
import Dialog from "../../Dialog.vue"
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Breadcrumbs,
|
||||
Dialog,
|
||||
Help,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -3,6 +3,7 @@ import PatronAPIClient from "./patron-api-client";
|
|||
import AcquisitionAPIClient from "./acquisition-api-client";
|
||||
import AVAPIClient from "./authorised-values-api-client";
|
||||
import ItemAPIClient from "./item-api-client";
|
||||
import RecordSourcesAPIClient from "./record-sources-api-client";
|
||||
import SysprefAPIClient from "./system-preferences-api-client";
|
||||
import PreservationAPIClient from "./preservation-api-client";
|
||||
|
||||
|
@ -14,4 +15,5 @@ export const APIClient = {
|
|||
item: new ItemAPIClient(),
|
||||
sysprefs: new SysprefAPIClient(),
|
||||
preservation: new PreservationAPIClient(),
|
||||
record_sources: new RecordSourcesAPIClient(),
|
||||
};
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import HttpClient from "./http-client";
|
||||
|
||||
export class RecordSourcesAPIClient extends HttpClient {
|
||||
constructor() {
|
||||
super({
|
||||
baseURL: "/api/v1/record_sources",
|
||||
});
|
||||
}
|
||||
|
||||
get record_sources() {
|
||||
return {
|
||||
create: record_source =>
|
||||
this.post({
|
||||
endpoint: "",
|
||||
body: record_source,
|
||||
}),
|
||||
delete: id =>
|
||||
this.delete({
|
||||
endpoint: "/" + id,
|
||||
}),
|
||||
update: (record_source, id) =>
|
||||
this.put({
|
||||
endpoint: "/" + id,
|
||||
body: record_source,
|
||||
}),
|
||||
get: id =>
|
||||
this.get({
|
||||
endpoint: "/" + id,
|
||||
}),
|
||||
getAll: (query, params) =>
|
||||
this.getAll({
|
||||
endpoint: "/",
|
||||
query,
|
||||
params,
|
||||
headers: {},
|
||||
}),
|
||||
count: (query = {}) =>
|
||||
this.count({
|
||||
endpoint:
|
||||
"?" +
|
||||
new URLSearchParams({
|
||||
_page: 1,
|
||||
_per_page: 1,
|
||||
...(query && { q: JSON.stringify(query) }),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default RecordSourcesAPIClient;
|
|
@ -0,0 +1,54 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { createWebHistory, createRouter } from "vue-router";
|
||||
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
faPlus,
|
||||
faMinus,
|
||||
faPencil,
|
||||
faTrash,
|
||||
faSpinner,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import vSelect from "vue-select";
|
||||
import { useNavigationStore } from "../../stores/navigation";
|
||||
import { useMainStore } from "../../stores/main";
|
||||
import routesDef from "../../routes/admin/record_sources";
|
||||
|
||||
library.add(faPlus, faMinus, faPencil, faTrash, faSpinner);
|
||||
|
||||
const pinia = createPinia();
|
||||
const navigationStore = useNavigationStore(pinia);
|
||||
const mainStore = useMainStore(pinia);
|
||||
const { removeMessages } = mainStore;
|
||||
const { setRoutes } = navigationStore;
|
||||
const routes = setRoutes(routesDef);
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
linkExactActiveClass: "current",
|
||||
routes,
|
||||
});
|
||||
|
||||
import App from "../../components/Admin/RecordSources/Main.vue";
|
||||
import i18n from "../../i18n";
|
||||
|
||||
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);
|
||||
app.mount("#record-source");
|
||||
|
||||
router.beforeEach(to => {
|
||||
navigationStore.$patch({ current: to.matched, params: to.params || {} });
|
||||
removeMessages(); // This will actually flag the messages as displayed already
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
import { markRaw } from "vue";
|
||||
import RecordSourcesFormAdd from "../../components/Admin/RecordSources/FormAdd.vue";
|
||||
import RecordSourcesList from "../../components/Admin/RecordSources/List.vue";
|
||||
import { $__ } from "../../i18n";
|
||||
|
||||
export default {
|
||||
title: $__("Administration"),
|
||||
path: "",
|
||||
href: "/cgi-bin/koha/admin/admin-home.pl",
|
||||
is_base: true,
|
||||
is_default: true,
|
||||
children: [
|
||||
{
|
||||
title: $__("Record sources"),
|
||||
path: "/cgi-bin/koha/admin/record_sources",
|
||||
is_end_node: true,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "RecordSourcesList",
|
||||
component: markRaw(RecordSourcesList),
|
||||
},
|
||||
{
|
||||
component: markRaw(RecordSourcesFormAdd),
|
||||
name: "RecordSourcesFormAdd",
|
||||
path: "add",
|
||||
title: $__("Add record source"),
|
||||
},
|
||||
{
|
||||
component: markRaw(RecordSourcesFormAdd),
|
||||
name: "RecordSourcesFormAddEdit",
|
||||
path: "edit/:record_source_id",
|
||||
title: $__("Edit record source"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
|
@ -7,6 +7,7 @@ module.exports = {
|
|||
entry: {
|
||||
erm: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/erm.ts",
|
||||
preservation: "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/preservation.ts",
|
||||
"admin/record_sources": "./koha-tmpl/intranet-tmpl/prog/js/vue/modules/admin/record_sources.ts",
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
|
|
Loading…
Reference in a new issue