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 (
)
}
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(
(),
document.getElementById('react-base'))
})
// for (let el of document.getElementsByClassName('class-search')) {
// ReactDOM.render((), el)
// }
}