Skip to content

Commit

Permalink
NEW SudoModePasswordField component
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Feb 5, 2025
1 parent db4002f commit 10a7494
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 3 deletions.
4 changes: 2 additions & 2 deletions client/dist/js/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/dist/styles/bundle.css

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions client/src/boot/registerComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import PopoverOptionSet from 'components/PopoverOptionSet/PopoverOptionSet';
import ToastsContainer from 'containers/ToastsContainer/ToastsContainer';
import ListboxField from 'components/ListboxField/ListboxField';
import SearchableDropdownField from 'components/SearchableDropdownField/SearchableDropdownField';
import SudoModePasswordField from 'components/SudoModePasswordField/SudoModePasswordField';

export default () => {
Injector.component.registerMany({
Expand Down Expand Up @@ -106,5 +107,6 @@ export default () => {
ToastsContainer,
ListboxField,
SearchableDropdownField,
SudoModePasswordField,
});
};
2 changes: 2 additions & 0 deletions client/src/bundles/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import 'expose-loader?exposes=withDragDropContext!lib/withDragDropContext';
import 'expose-loader?exposes=withRouter!lib/withRouter';
import 'expose-loader?exposes=ssUrlLib!lib/urls';
import 'expose-loader?exposes=SearchableDropdownField!components/SearchableDropdownField/SearchableDropdownField';
import 'expose-loader?exposes=SudoModePasswordField!components/SudoModePasswordField/SudoModePasswordField';

// Legacy CMS
import '../legacy/jquery.changetracker';
Expand Down Expand Up @@ -113,6 +114,7 @@ import '../legacy/ConfirmedPasswordField';
import '../legacy/SelectionGroup';
import '../legacy/DateField';
import '../legacy/ToggleCompositeField';
import '../legacy/SudoModePasswordField/SudoModePasswordFieldEntwine';
import '../legacy/TreeDropdownField/TreeDropdownFieldEntwine';
import '../legacy/UsedOnTable/UsedOnTableEntwine';
import '../legacy/DatetimeField';
Expand Down
112 changes: 112 additions & 0 deletions client/src/components/SudoModePasswordField/SudoModePasswordField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import classNames from 'classnames';
import Button from 'components/Button/Button';
import i18n from 'i18n';
import Config from 'lib/Config';
import backend from 'lib/Backend';
import PropTypes from 'prop-types';
import qs from 'qs';
import React, { createRef, useState } from 'react';

function SudoModePasswordField(props) {
const {
className,
panelPadded,
} = props;

const passwordFieldRef = createRef();
const [responseMessage, setResponseMessage] = useState('');

function reloadPage() {
// Add a ?reload=1 query parameter to the URL to force the browser to reload the page
// window.location.reload() does not work as expected
const query = qs.parse(window.location.search, { ignoreQueryPrefix: true });
const reload = query.reload ? parseInt(query.reload, 10) + 1 : 1;
const hrefNoQuery = window.location.href.split('?')[0];
window.location.href = hrefNoQuery + qs.stringify({ ...query, reload }, { addQueryPrefix: true });
}

async function handleClick() {
const fetcher = backend.createEndpointFetcher({
url: Config.getSection('SilverStripe\\Admin\\SudoModeController').endpoints.activate,
method: 'post',
payloadFormat: 'urlencoded',
responseFormat: 'json',
});
const data = {
Password: passwordFieldRef.current.value,
};
const headers = {
'X-SecurityID': Config.get('SecurityID'),
};
const responseJson = await fetcher(data, headers);
if (responseJson.result) {
reloadPage();
} else {
setResponseMessage(responseJson.message);
}
}

function handleKeyDown(evt) {
if (evt.key === 'Enter') {
handleClick();
}
}

const text = i18n._t(
'Admin.SUDO_MODE_PASSWORD_FIELD_TEXT',
'This section is protected and is currently in read only mode. Please enter your password to make changes.'
);
const placeholder = i18n._t(
'Admin.SUDO_MODE_PASSWORD_FIELD_PLACEHOLDER',
'Your password'
);
const submit = i18n._t(
'Admin.SUDO_MODE_PASSWORD_FIELD_SUBMIT',
'Submit'
);

const classes = [
'sudo-mode-password-field',
className,
'flexbox-area-grow',
'panel',
];
if (panelPadded) {
classes.push('panel--padded');
}
return <div className={classNames(classes)}>
<div className="alert alert-info">
<p>{text}</p>
<div className="sudo-mode-password-field__container">
<div className="sudo-mode-password-field__input-container">
<input
className="form-control no-change-track"
type="password"
ref={passwordFieldRef}
placeholder={placeholder}
onKeyDown={(evt) => handleKeyDown(evt)}
/>
</div>
<div>
<Button
className="form-control"
color="info"
onClick={() => handleClick()}
>
{submit}
</Button>
</div>
</div>
{{ responseMessage } &&
<p className="sudo-mode-password-field__message">{responseMessage}</p>
}
</div>
</div>;
}

SudoModePasswordField.propTypes = {
className: PropTypes.string,
panelPadded: PropTypes.bool,
};

export default SudoModePasswordField;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.sudo-mode-password-field {

// The react component has the `panel` class on it
// this is to stop it from collapsing if the `panel--padded` class is not also present
padding: 0.01px;

&__container {
display: flex;
}

&__input-container {
padding-right: 10px;
width: 100%;
max-width: 350px;
}

&__message {
margin-top: 10px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* global window */
import jQuery from 'jquery';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { loadComponent } from 'lib/Injector';

jQuery.entwine('ss', ($) => {
$('.js-injector-boot .SudoModePasswordField').entwine({
Component: null,
ReactRoot: null,

onmatch() {
// onmatch will match both the field holder and the field
// we only want to run this on the field holder
if (this.is('.sudo-mode-password-field__input')) {
return;
}
this._super();
const cmsContent = this.closest('.cms-content').attr('id');
const context = (cmsContent)
? { context: cmsContent }
: {};
const SudoModePasswordField = loadComponent('SudoModePasswordField', context);
this.setComponent(SudoModePasswordField);
this.refresh();
},

onunmatch() {
this._super();
const root = this.getReactRoot();
if (root) {
root.unmount();
this.setReactRoot(null);
}
},

refresh() {
const props = this.getProps();
const SudoModePasswordField = this.getComponent();
let root = this.getReactRoot();
if (!root) {
root = createRoot(this[0]);
}
root.render(<SudoModePasswordField {...props} />);
this.setReactRoot(root);
},

getProps() {
const inputField = this.getInputField();
return {
panelPadded: inputField.is('[data-panel-padded]'),
};
},

getInputField() {
return this.find('.sudo-mode-password-field__input');
}
});
});
1 change: 1 addition & 0 deletions client/src/styles/bundle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
@import "../components/Search/SearchForm";
@import "../components/Search/SearchToggle";
@import "../components/Search/SearchBox";
@import "../components/SudoModePasswordField/SudoModePasswordField";
@import "../components/Tabs/Tabs";
@import "../components/Tag/Tag";
@import "../components/Tag/TagList";
Expand Down
7 changes: 7 additions & 0 deletions code/LeftAndMain.php
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,13 @@ public function getEditForm($id = null, $fields = null)
$readonlyFields = $form->Fields()->makeReadonly();
$form->setFields($readonlyFields);
}

// Check if the the record is a DataObject and if that DataObject requires sudo mode
// If so then require sudo mode for the edit form
if (is_a($record, DataObject::class) && $record->getRequireSudoMode()) {
$form->requireSudoMode();
}

return $form;
}

Expand Down
7 changes: 7 additions & 0 deletions code/ModelAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,13 @@ public function getEditForm($id = null, $fields = null)
$form->setFormAction($editFormAction);
$form->setAttribute('data-pjax-fragment', 'CurrentForm');

// Check if the the record requires sudo mode, If so then require sudo mode for the edit form
$list = $this->getList();
$record = Injector::inst()->create($list->dataClass());
if ($record->getRequireSudoMode()) {
$form->requireSudoMode();
}

$this->extend('updateEditForm', $form);

return $form;
Expand Down

0 comments on commit 10a7494

Please sign in to comment.