let React = require('react') let ReactDOM = require('react-dom') /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set * 2022-03-02 * Under CC0 */ function isSuperset(set, subset) { for (let elem of subset) { if (!set.has(elem)) { return false } } return true } function union(setA, setB) { let _union = new Set(setA) for (let elem of setB) { _union.add(elem) } return _union } function intersection(setA, setB) { let _intersection = new Set() for (let elem of setB) { if (setA.has(elem)) { _intersection.add(elem) } } return _intersection } function symmetricDifference(setA, setB) { let _difference = new Set(setA) for (let elem of setB) { if (_difference.has(elem)) { _difference.delete(elem) } else { _difference.add(elem) } } return _difference } function difference(setA, setB) { let _difference = new Set(setA) for (let elem of setB) { _difference.delete(elem) } return _difference } /* End borrowed code */ // npx babel --watch src --out-dir . --presets react-app/prod function iterator_to_list(iterator) { let object = iterator.next() let lst = []; while (! object.done) { lst.push(object.value); object = iterator.next(); } return lst; } /* * properties: * classList: Array of String * addedClasses: Set of String * removedClasses: Set of String * onAddClass: string => () * onRemoveClass: string => () * abandonChange: string => () * submit: () => () */ class ClassListForm extends React.Component { constructor (props) { super(props); this.state = { shownClasses: [], } } /* Callback for search box */ onChange = (e) => { if (e.target.value == '') { this.setState({shownClasses: []}) } else { fetch('/api/list-classes?q=' + e.target.value) .then(resp => resp.json()) .then(data => this.setState({shownClasses: data})) } } render() { let added = iterator_to_list(this.props.addedClasses.values()) let removed = iterator_to_list(this.props.removedClasses.values()) return (

Current classes


) } removeClass = (e) => { this.props.onRemoveClass(e.target.value); } addClass = (e) => { this.props.onAddClass(e.target.value); } abandonChange = (e) => { this.props.abandonChange(e.target.value); } } /* * Information box about a single node, containing its name, * environment, and current classes. Also includes options for adding * and removing classes. * * Properties: * environment: String * fqdn: String * */ class PuppetNode extends React.Component { constructor(props) { super(props); this.state = { /* Nodes puppet environment */ environment: props.environment, /* List of puppet classes */ classes: [], /* Classes to be added on commit */ addedClasses: new Set(), /* Classes to be removed on commit */ removedClasses: new Set(), } /* Fetch initial set of classes */ fetch(`/api/classes-for?fqdn=${this.props.fqdn}`) .then(r => r.json()) .then(data => { this.setState({ classes: data }) }) } render() { // console.log(this.state.addedClasses) // console.log(this.state.addedClasses.values()) // console.log(iterator_to_list(this.state.addedClasses.values())) return (

{this.props.fqdn}

Environment
{this.state.environment}
) } submitChanges = () => { let changes = { fqdn: this.props.fqdn, added: iterator_to_list(this.state.addedClasses.values()), removed: iterator_to_list(this.state.removedClasses.values()), } console.log("Submitting", changes) fetch('/api/change-classes', { method: 'POST', headers: { 'Content-Type': 'application/json', }, redirect: 'follow', body: JSON.stringify(changes), }).then(response => { if (response.ok) { response.json().then(data => { console.log('Submit succeeded, updating classes', data) this.setState((state, props) => ({ addedClasses: new Set(), removedClasses: new Set(), classes: data, })) }) } else { response.text().then(data => { console.log("Submit failed:") console.log(response.status) console.log(data) }) } }) } abandonChange = (name) => { this.setState((state, props) => ({ removedClasses: difference(state.removedClasses, new Set([name])), addedClasses: difference(state.addedClasses, new Set([name])), })) } removeClass = (name) => { this.setState((state, props) => ({ removedClasses: union(state.removedClasses, new Set([name])), addedClasses: difference(state.addedClasses, new Set([name])), })) } addClass = (name) => { /* Only add if not alreaddy there */ if (this.state.classes.indexOf(name) !== -1) return; this.setState((state, props) => ({ addedClasses: union(state.addedClasses, new Set([name])), removedClasses: difference(state.removedClasses, new Set([name])), })) } } window.onload = function() { fetch('/api/hosts') .then(resp => resp.json()) .then(data => { ReactDOM.render( (
{data.map(d => )}
), document.getElementById('react-base')) }) // for (let el of document.getElementsByClassName('class-search')) { // ReactDOM.render((), el) // } }