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 ? ( + + ) : null; + return (
+ {alert} @@ -203,8 +265,8 @@ class DHCPInfo extends Component { @@ -214,8 +276,8 @@ class DHCPInfo extends Component {