diff --git a/public/i18n/bg/api-errors.json b/public/i18n/bg/api-errors.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/public/i18n/bg/api-errors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/i18n/ca/api-errors.json b/public/i18n/ca/api-errors.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/public/i18n/ca/api-errors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/i18n/de/api-errors.json b/public/i18n/de/api-errors.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/public/i18n/de/api-errors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/i18n/en/api-errors.json b/public/i18n/en/api-errors.json
new file mode 100644
index 0000000..1466eba
--- /dev/null
+++ b/public/i18n/en/api-errors.json
@@ -0,0 +1,22 @@
+{
+ "unknown": "Unknown error",
+ "gravity_error": "Failed to create the blocklist",
+ "ftl_connection_fail": "Failed to connect to FTL",
+ "ftl_read_error": "Error reading from FTL",
+ "ftl_eom_error": "Read unexpected EOM from FTL",
+ "not_found": "Not found",
+ "already_exists": "Item already exists",
+ "invalid_domain": "Invalid domain",
+ "bad_request": "Bad request",
+ "unauthorized": "Unauthorized",
+ "file_read": "Error reading from {{file}}",
+ "file_write": "Error writing to {{file}}",
+ "config_parsing_error": "Error parsing the config",
+ "invalid_setting_value": "Invalid setting value",
+ "restart_dns_error": "Failed to restart the DNS server",
+ "dnsmasq_config_write": "Error generating the dnsmasq config",
+ "shared_memory_open": "Failed to open shared memory",
+ "shared_memory_read": "Failed to read from shared memory",
+ "shared_memory_lock": "Failed to lock shared memory",
+ "API Error: {{error}}": "API Error: {{error}}"
+}
\ No newline at end of file
diff --git a/public/i18n/en/settings.json b/public/i18n/en/settings.json
index 66c6b1e..60197be 100644
--- a/public/i18n/en/settings.json
+++ b/public/i18n/en/settings.json
@@ -22,5 +22,8 @@
"Branch": "Branch",
"Hash": "Hash",
"Tag": "Tag",
- "Only forward public reverse lookups": "Only forward public reverse lookups"
+ "Only forward public reverse lookups": "Only forward public reverse lookups",
+ "Processing...": "Processing...",
+ "Successfully saved settings": "Successfully saved settings",
+ "Apply": "Apply"
}
\ No newline at end of file
diff --git a/public/i18n/es/api-errors.json b/public/i18n/es/api-errors.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/public/i18n/es/api-errors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/i18n/fr/api-errors.json b/public/i18n/fr/api-errors.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/public/i18n/fr/api-errors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/public/i18n/pt/api-errors.json b/public/i18n/pt/api-errors.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/public/i18n/pt/api-errors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/src/components/common/Alert.js b/src/components/common/Alert.js
index c39ae93..8669ee3 100644
--- a/src/components/common/Alert.js
+++ b/src/components/common/Alert.js
@@ -27,7 +27,11 @@ const Alert = props => {
Alert.propTypes = {
message: PropTypes.node.isRequired,
type: PropTypes.string.isRequired,
- onClick: PropTypes.func.isRequired
+ onClick: PropTypes.func
+};
+
+Alert.defaultProps = {
+ onClick: () => {}
};
export default Alert;
diff --git a/src/components/settings/DHCPInfo.js b/src/components/settings/DHCPInfo.js
index 9867a41..5cb3166 100644
--- a/src/components/settings/DHCPInfo.js
+++ b/src/components/settings/DHCPInfo.js
@@ -23,45 +23,46 @@ import {
Label
} from "reactstrap";
import { isPositiveNumber, isValidHostname, isValidIpv4 } from "../../validate";
+import Alert from "../common/Alert";
class DHCPInfo extends Component {
state = {
- active: false,
- ip_start: "",
- ip_end: "",
- router_ip: "",
- lease_time: "",
- domain: "",
- ipv6_support: false
+ alertMessage: "",
+ alertType: "",
+ showAlert: false,
+ processing: false,
+ settings: {
+ active: false,
+ ip_start: "",
+ ip_end: "",
+ router_ip: "",
+ lease_time: "",
+ domain: "",
+ ipv6_support: false
+ }
};
- constructor(props) {
- super(props);
- this.updateDHCPInfo = this.updateDHCPInfo.bind(this);
- }
-
- updateDHCPInfo() {
- this.updateHandler = makeCancelable(api.getDHCPInfo(), {
- repeat: this.updateDHCPInfo,
- interval: 600000
- });
+ loadDHCPInfo = () => {
+ this.updateHandler = makeCancelable(api.getDHCPInfo());
this.updateHandler.promise
.then(res => {
this.setState({
- active: res.active,
- ip_start: res.ip_start,
- ip_end: res.ip_end,
- router_ip: res.router_ip,
- lease_time: res.lease_time,
- domain: res.domain,
- ipv6_support: res.ipv6_support
+ settings: {
+ active: res.active,
+ ip_start: res.ip_start,
+ ip_end: res.ip_end,
+ router_ip: res.router_ip,
+ lease_time: res.lease_time,
+ domain: res.domain,
+ ipv6_support: res.ipv6_support
+ }
});
})
.catch(ignoreCancel);
- }
+ };
componentDidMount() {
- this.updateDHCPInfo();
+ this.loadDHCPInfo();
}
componentWillUnmount() {
@@ -77,10 +78,16 @@ class DHCPInfo extends Component {
* @returns {function(Event)}
*/
onChange = (key, attr) => {
- return e =>
- this.setState({
- [key]: e.target[attr]
- });
+ return e => {
+ const value = e.target[attr];
+
+ this.setState(oldState => ({
+ settings: {
+ ...oldState.settings,
+ [key]: value
+ }
+ }));
+ };
};
/**
@@ -91,7 +98,44 @@ class DHCPInfo extends Component {
saveSettings = e => {
e.preventDefault();
- // TODO: send settings to API
+ const { t } = this.props;
+
+ this.setState({
+ alertMessage: t("Processing..."),
+ alertType: "info",
+ showAlert: true,
+ processing: true
+ });
+
+ api
+ .updateDHCPInfo(this.state.settings)
+ .then(() => {
+ this.setState({
+ alertMessage: t("Successfully saved settings"),
+ alertType: "success",
+ showAlert: true,
+ processing: false
+ });
+ })
+ .catch(error => {
+ let message = "";
+
+ if (error instanceof Error) {
+ message = error.message;
+ } else {
+ // Translate the API's error message
+ message = t("API Error: {{error}}", {
+ error: t(error.key, error.data)
+ });
+ }
+
+ this.setState({
+ alertMessage: message,
+ alertType: "danger",
+ showAlert: true,
+ processing: false
+ });
+ });
};
/**
@@ -101,37 +145,55 @@ class DHCPInfo extends Component {
* @param validator the validation function
*/
isSettingValid = (value, validator) => {
- return (!this.state.active && value.length === 0) || validator(value);
+ return (
+ (!this.state.settings.active && value.length === 0) || validator(value)
+ );
+ };
+
+ hideAlert = () => {
+ this.setState({ showAlert: false });
};
render() {
const { t } = this.props;
const isIpStartValid = this.isSettingValid(
- this.state.ip_start,
+ this.state.settings.ip_start,
+ isValidIpv4
+ );
+ const isIpEndValid = this.isSettingValid(
+ this.state.settings.ip_end,
isValidIpv4
);
- const isIpEndValid = this.isSettingValid(this.state.ip_end, isValidIpv4);
const isRouterIpValid = this.isSettingValid(
- this.state.router_ip,
+ this.state.settings.router_ip,
isValidIpv4
);
const isLeaseTimeValid = this.isSettingValid(
- this.state.lease_time,
+ this.state.settings.lease_time,
isPositiveNumber
);
const isDomainValid = this.isSettingValid(
- this.state.domain,
+ this.state.settings.domain,
isValidHostname
);
+ const alert = this.state.showAlert ? (
+