Skip to content

Commit 0758f22

Browse files
dbernsteinclaude
andcommitted
Add Global Settings tab to system configuration page (PP-4127)
Adds a new "Global Settings" tab visible to system admins in the System Configuration section. The tab fetches GET /admin/global_settings and renders a schema-driven form (backed by the Pydantic GlobalSettings model) pre-populated with the current values. Submitting POSTs the updated values back. Changes: - GlobalSettingsData interface (interfaces.ts) - GLOBAL_SETTINGS / EDIT_GLOBAL_SETTINGS action constants and fetchGlobalSettings / editGlobalSettings action methods (actions.ts) - globalSettings reducer via createFetchEditReducer factory (reducers/) - GlobalSettings component extending EditableConfigList with a custom render() that uses ProtocolFormField for schema-driven field rendering - ConfigTabContainer wired with import, COMPONENT_CLASSES entry, and DISPLAY_NAMES entry (system-admin only via SYSTEM_ADMIN_TABS) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 21e711f commit 0758f22

6 files changed

Lines changed: 138 additions & 0 deletions

File tree

src/actions.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
DiagnosticsData,
2323
FeatureFlags,
2424
SitewideAnnouncementsData,
25+
GlobalSettingsData,
2526
StatisticsData,
2627
QuickSightEmbeddedURLData,
2728
} from "./interfaces";
@@ -75,6 +76,8 @@ export default class ActionCreator extends BaseActionCreator {
7576
static readonly DELETE_PATRON_AUTH_SERVICE = "DELETE_PATRON_AUTH_SERVICE";
7677
static readonly SITEWIDE_ANNOUNCEMENTS = "SITEWIDE_ANNOUNCEMENTS";
7778
static readonly EDIT_SITEWIDE_ANNOUNCEMENTS = "EDIT_SITEWIDE_ANNOUNCEMENTS";
79+
static readonly GLOBAL_SETTINGS = "GLOBAL_SETTINGS";
80+
static readonly EDIT_GLOBAL_SETTINGS = "EDIT_GLOBAL_SETTINGS";
7881
static readonly METADATA_SERVICES = "METADATA_SERVICES";
7982
static readonly EDIT_METADATA_SERVICE = "EDIT_METADATA_SERVICE";
8083
static readonly DELETE_METADATA_SERVICE = "DELETE_METADATA_SERVICE";
@@ -994,6 +997,21 @@ export default class ActionCreator extends BaseActionCreator {
994997
).bind(this);
995998
}
996999

1000+
fetchGlobalSettings() {
1001+
const url = "/admin/global_settings";
1002+
return this.fetchJSON<GlobalSettingsData>(
1003+
ActionCreator.GLOBAL_SETTINGS,
1004+
url
1005+
).bind(this);
1006+
}
1007+
1008+
editGlobalSettings(data: FormData) {
1009+
const url = "/admin/global_settings";
1010+
return this.postForm(ActionCreator.EDIT_GLOBAL_SETTINGS, url, data).bind(
1011+
this
1012+
);
1013+
}
1014+
9971015
setFeatureFlags(featureFlags: FeatureFlags) {
9981016
return {
9991017
type: ActionCreator.SET_FEATURE_FLAGS,

src/components/ConfigTabContainer.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Collections from "./Collections";
55
import IndividualAdmins from "./IndividualAdmins";
66
import PatronAuthServices from "./PatronAuthServices";
77
import SitewideAnnouncements from "./SitewideAnnouncements";
8+
import GlobalSettings from "./GlobalSettings";
89
import MetadataServices from "./MetadataServices";
910
import CatalogServices from "./CatalogServices";
1011
import DiscoveryServices from "./DiscoveryServices";
@@ -46,6 +47,7 @@ export default class ConfigTabContainer extends TabContainer<
4647
catalogServices: CatalogServices,
4748
discovery: DiscoveryServices,
4849
sitewideAnnouncements: SitewideAnnouncements,
50+
globalSettings: GlobalSettings,
4951
};
5052

5153
LIBRARIAN_TABS = ["libraries"];
@@ -56,6 +58,7 @@ export default class ConfigTabContainer extends TabContainer<
5658
individualAdmins: "Admins",
5759
patronAuth: "Patron Authentication",
5860
sitewideAnnouncements: "Sitewide Announcements",
61+
globalSettings: "Global Settings",
5962
catalogServices: "External Catalogs",
6063
};
6164

src/components/GlobalSettings.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as React from "react";
2+
import { connect } from "react-redux";
3+
import { Alert } from "react-bootstrap";
4+
import { Form } from "library-simplified-reusable-components";
5+
import LoadingIndicator from "@thepalaceproject/web-opds-client/lib/components/LoadingIndicator";
6+
import ActionCreator from "../actions";
7+
import { GlobalSettingsData } from "../interfaces";
8+
import EditableConfigList, {
9+
EditableConfigListStateProps,
10+
EditableConfigListDispatchProps,
11+
EditableConfigListOwnProps,
12+
EditableConfigListProps,
13+
} from "./EditableConfigList";
14+
import ErrorMessage from "./ErrorMessage";
15+
import ProtocolFormField from "./ProtocolFormField";
16+
17+
/** Settings panel for sitewide global configuration (country, state, etc.). */
18+
export class GlobalSettings extends EditableConfigList<
19+
GlobalSettingsData,
20+
never
21+
> {
22+
EditForm = null;
23+
listDataKey = "schema";
24+
itemTypeName = "global setting";
25+
urlBase = "/admin/web/config/globalSettings/";
26+
identifierKey = "key";
27+
labelKey = "label";
28+
29+
render() {
30+
const {
31+
data,
32+
fetchError,
33+
formError,
34+
isFetching,
35+
responseBody,
36+
} = this.props;
37+
38+
const canEdit = this.context.admin.isSystemAdmin();
39+
40+
return (
41+
<div className="global-settings">
42+
<h2>Global Settings</h2>
43+
{responseBody && (
44+
<Alert bsStyle="success">Global settings saved successfully.</Alert>
45+
)}
46+
{fetchError && <ErrorMessage error={fetchError} />}
47+
{formError && <ErrorMessage error={formError} />}
48+
{isFetching && <LoadingIndicator />}
49+
{!isFetching && data?.schema && (
50+
<Form
51+
onSubmit={this.editItem}
52+
className="no-border edit-form"
53+
disableButton={!canEdit}
54+
content={data.schema.map((setting) => (
55+
<ProtocolFormField
56+
key={setting.key}
57+
setting={setting}
58+
disabled={!canEdit}
59+
value={data.settings?.[setting.key]}
60+
readOnly={!canEdit}
61+
/>
62+
))}
63+
/>
64+
)}
65+
</div>
66+
);
67+
}
68+
}
69+
70+
function mapStateToProps(state) {
71+
return {
72+
data: state.editor.globalSettings.data,
73+
responseBody: state.editor.globalSettings.successMessage,
74+
fetchError: state.editor.globalSettings.fetchError,
75+
formError: state.editor.globalSettings.formError,
76+
isFetching:
77+
state.editor.globalSettings.isFetching ||
78+
state.editor.globalSettings.isEditing,
79+
};
80+
}
81+
82+
function mapDispatchToProps(dispatch, ownProps) {
83+
const actions = new ActionCreator(null, ownProps.csrfToken);
84+
return {
85+
fetchData: () => dispatch(actions.fetchGlobalSettings()),
86+
editItem: (data: FormData) => dispatch(actions.editGlobalSettings(data)),
87+
};
88+
}
89+
90+
const ConnectedGlobalSettings = connect<
91+
EditableConfigListStateProps<GlobalSettingsData>,
92+
EditableConfigListDispatchProps<GlobalSettingsData>,
93+
EditableConfigListOwnProps
94+
>(
95+
mapStateToProps,
96+
mapDispatchToProps
97+
)(GlobalSettings);
98+
99+
export default ConnectedGlobalSettings;

src/interfaces.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,13 @@ export interface SitewideAnnouncementsData {
531531
settings: SettingData[];
532532
}
533533

534+
export interface GlobalSettingsData {
535+
/** Current setting values keyed by field name. */
536+
settings: { [key: string]: string };
537+
/** Form schema returned by the backend, one entry per configurable field. */
538+
schema: SettingData[];
539+
}
540+
534541
export interface AdvancedSearchQuery {
535542
id?: string;
536543
key?: string;

src/reducers/globalSettings.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { GlobalSettingsData } from "../interfaces";
2+
import ActionCreator from "../actions";
3+
import createFetchEditReducer from "./createFetchEditReducer";
4+
5+
export default createFetchEditReducer<GlobalSettingsData>(
6+
ActionCreator.GLOBAL_SETTINGS
7+
);

src/reducers/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import collections from "./collections";
1010
import individualAdmins from "./individualAdmins";
1111
import patronAuthServices from "./patronAuthServices";
1212
import sitewideAnnouncements from "./sitewideAnnouncements";
13+
import globalSettings from "./globalSettings";
1314
import metadataServices from "./metadataServices";
1415
import catalogServices from "./catalogServices";
1516
import discoveryServices from "./discoveryServices";
@@ -43,6 +44,7 @@ import {
4344
IndividualAdminsData,
4445
PatronAuthServicesData,
4546
SitewideAnnouncementsData,
47+
GlobalSettingsData,
4648
MetadataServicesData,
4749
CatalogServicesData,
4850
DiscoveryServicesData,
@@ -70,6 +72,7 @@ export interface State {
7072
individualAdmins: FetchEditState<IndividualAdminsData>;
7173
patronAuthServices: FetchEditState<PatronAuthServicesData>;
7274
sitewideAnnouncements: FetchEditState<SitewideAnnouncementsData>;
75+
globalSettings: FetchEditState<GlobalSettingsData>;
7376
metadataServices: FetchEditState<MetadataServicesData>;
7477
catalogServices: FetchEditState<CatalogServicesData>;
7578
discoveryServices: FetchEditState<DiscoveryServicesData>;
@@ -106,6 +109,7 @@ export default combineReducers<State>({
106109
individualAdmins,
107110
patronAuthServices,
108111
sitewideAnnouncements,
112+
globalSettings,
109113
metadataServices,
110114
catalogServices,
111115
discoveryServices,

0 commit comments

Comments
 (0)