diff --git a/tornjak-frontend/src/components/dashboard/agents-dashboard-table.js b/tornjak-frontend/src/components/dashboard/agents-dashboard-table.js index f05c0340..a51cb204 100644 --- a/tornjak-frontend/src/components/dashboard/agents-dashboard-table.js +++ b/tornjak-frontend/src/components/dashboard/agents-dashboard-table.js @@ -76,12 +76,11 @@ class AgentDashboardTable extends React.Component { } agentList() { - if ((typeof this.props.globalEntries.globalEntriesList === 'undefined') || - (typeof this.props.globalAgents.globalAgentsList === 'undefined')) { + if ((typeof this.props.globalAgents.globalAgentsList === 'undefined')) { return []; } - let agentEntriesDict = this.SpiffeHelper.getAgentsEntries(this.props.globalAgents.globalAgentsList, this.props.globalEntries.globalEntriesList) + let agentEntriesDict = this.SpiffeHelper.getAgentsEntries(this.props.globalAgents.globalAgentsList, this.props.globalEntries.globalEntriesList) // TODO this.props.globalEntries.globalEntriesList may be undefined if there are no entries return this.props.globalAgents.globalAgentsList.map(currentAgent => { return this.getChildEntries(currentAgent, agentEntriesDict); }) diff --git a/tornjak-frontend/src/components/entry-create.js b/tornjak-frontend/src/components/entry-create.js index c2c41df3..60db40d0 100644 --- a/tornjak-frontend/src/components/entry-create.js +++ b/tornjak-frontend/src/components/entry-create.js @@ -32,7 +32,13 @@ class CreateEntry extends Component { this.prepareSelectorsList = this.prepareSelectorsList.bind(this); this.onChangeSelectorsRecommended = this.onChangeSelectorsRecommended.bind(this); this.onChangeTtl = this.onChangeTtl.bind(this); - this.onChangeExpiresAt = this.onChangeExpiresAt.bind(this); + this.onChangeExpiryOption = this.onChangeExpiryOption.bind(this); + this.expiryTimeUpdate = this.expiryTimeUpdate.bind(this); + this.onChangeExpiresAtSeconds = this.onChangeExpiresAtSeconds.bind(this); + this.isValidDate = this.isValidDate.bind(this); + this.updateValidDateAndTime = this.updateValidDateAndTime.bind(this); + this.onChangeExpiresAtTime = this.onChangeExpiresAtTime.bind(this); + this.onChangeExpiresAtDate = this.onChangeExpiresAtDate.bind(this); this.onChangeFederatesWith = this.onChangeFederatesWith.bind(this); this.onChangeDownStream = this.onChangeDownStream.bind(this); this.onChangeDnsNames = this.onChangeDnsNames.bind(this); @@ -57,7 +63,13 @@ class CreateEntry extends Component { adminFlag: false, ttl: 0, + expiryOption: "None", + expiryOptionList: ["None", "Seconds Since Epoch", "Date/Time"], + expiryDate: "1/1/2021", + expiryTime: "00:00", expiresAt: 0, + expiryUnsafe: false, + expiryInvalid: false, dnsNames: [], federatesWith: [], downstream: false, @@ -204,12 +216,65 @@ class CreateEntry extends Component { }); } - onChangeExpiresAt(e) { + onChangeExpiryOption(e) { this.setState({ - expiresAt: Number(e.imaginaryTarget.value) + expiresAt: 0, + expiryOption: e.selectedItem, + expiryUnsafe: false, + expiryInvalid: false }); } + isValidExpiryTime(seconds) { + const JSMaxSafeTime = 8640000000000 // JS cannot represent times safely larger than this + return seconds > 0 && seconds <= JSMaxSafeTime + } + + expiryTimeUpdate(seconds) { + this.setState({ + expiresAt: seconds, + expiryUnsafe: !this.isValidExpiryTime(seconds) + }) + } + + onChangeExpiresAtSeconds(e) { + var seconds = Number(e.imaginaryTarget.value) + this.expiryTimeUpdate(seconds) + } + + // TODO some odd behavior with dates like February 33 exists + isValidDate(d) { // date is successfully translated in Javascript + return d instanceof Date && isFinite(d) + } + + updateValidDateAndTime(d, t) { + var testDate = new Date(d + ", " + t) + this.setState({ // should always reflect what the user sees + expiryDate: d, + expiryTime: t + }) + if (this.isValidDate(testDate)) { + this.setState({ + expiryInvalid: false, + }) + var seconds = Math.round(testDate / 1000) + this.expiryTimeUpdate(seconds) + console.log(d, t, this.state.expiryDate, this.state.expiryTime) + return + } + this.setState({ + expiryInvalid: true, + }) + } + + onChangeExpiresAtDate(e) { + this.updateValidDateAndTime(e.target.value, this.state.expiryTime) + } + + onChangeExpiresAtTime(e) { + this.updateValidDateAndTime(this.state.expiryDate, e.target.value) + } + onChangeDownStream = selected => { var sid = selected; this.setState({ @@ -408,6 +473,7 @@ class CreateEntry extends Component { newTags.splice(currPos, 1); newTags.splice(newPos, 0, tag); + // re-render this.setState({ tags: newTags }); } @@ -605,17 +671,80 @@ class CreateEntry extends Component { />
- +
+ +
+ {this.state.expiryOption !== "None" &&
+ {this.state.expiryOption === "Seconds Since Epoch" && +
+
+ +
+
+ } + {this.state.expiryOption === "Date/Time" && +
+
+ +
+
+ +
+
+ } +
+ + + } + {(this.state.expiryUnsafe || this.state.expiryInvalid) && +
+

Warning: expiry time either in invalid format, is negative, or is too large. Submitting this time may result in undefined behavior.

+ {this.state.expiryOption === "Seconds Since Epoch" && this.state.expiryUnsafe && +

Seconds must be positive and less than 8640000000000

+ } + {this.state.expiryOption === "Date/Time" && this.state.expiryUnsafe && +

Date must be past January 1, 1970 @ 12:00AM GMT

+ } + {this.state.expiryOption === "Date/Time" && this.state.expiryInvalid && +

Date or time format is invalid

+ } +
+ } +
e.parent_id.path === "/spire/server"); var lambdas = []; var agentEntriesDict = {}; diff --git a/tornjak-frontend/src/components/style.css b/tornjak-frontend/src/components/style.css index 306accd0..506ffd36 100644 --- a/tornjak-frontend/src/components/style.css +++ b/tornjak-frontend/src/components/style.css @@ -13,152 +13,179 @@ .dropdown-container { margin-left: 200px; } + .dropbtn { color: white; padding: 30px; font-size: 16px; border: none; - } - - .dropdown { - position: relative; - display: inline-block; - padding: 15px; - } - - .dropdown-content { - margin-top: 10px; - position: absolute; - background-color: #f1f1f1; - display: none; - min-width: 130px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 3; - } - - .dropdown-content a { - color: black; - padding: 12px 16px; - z-index: 3; - } - - .dropdown-content a:hover {background-color: #ddd;} - .dropdown:hover .dropdown-content {display: block;} - .dropdown:hover .dropbtn {color: #ffffff; text-decoration: none;} - - .indvidual-list-table { - margin-top: 10px; - width: 98%; - margin-bottom: 20px; - } - - .create-entry-title { - margin-left: auto; - margin-right: auto; - } - - .entry-form { - margin-left: auto; - margin-right: auto; - width: 60%; - border-width:1px; - border-style:solid; - border-color: rgb(180, 178, 178); - padding: 30px 30px 30px 30px; - } - .parentId-drop-down { - margin-bottom: 20px; - } - - .clustertype-drop-down { - margin-bottom: 20px; - } - - .parentId-manual-input-field { - margin-bottom: 20px; - margin-left: 40px; - } - - .clustertype-manual-input-field { - margin-bottom: 20px; - margin-left: 40px; - } - - .spiffeId-input-field { - margin-bottom: 20px; - } - - .cluster-domain-name-input-field { - margin-bottom: 20px; - } - - .cluster-managed-by-input-field { - margin-bottom: 20px; - } - - .clustername-input-field { - margin-bottom: 20px; - } - - .parentId-helper { - font-size: 12px; - color:rgb(82, 79, 79); - margin-top: 2px; - } - - .cluster-helper { - font-size: 12px; - color:rgb(82, 79, 79); - margin-top: 2px; - } - .selectors-multiselect { - margin-bottom: 20px; - } - - .agents-multiselect { - margin-bottom: 20px; - } - .selectors-textArea { - margin-bottom: 20px; - } - - .ttl-input { - display: inline-block; - margin-right: 20px; - margin-bottom: 20px; - width: 160px - } - - .expiresAt-input { - display: inline-block; - margin-bottom: 20px; - width: 160px; - } +} - .federates-with-input-field { - margin-bottom: 20px; - width: 55%; - } - .dnsnames-input-field { - margin-bottom: 20px; - width: 55%; - } - - .servers-drp-dwn { - margin-top: 5px; - } - - .success-message { - font-weight: bold; - color: green; - } - - .failed-message { - font-weight: bold; - color:red; - } - - .no-data { - margin-top: 120px; - margin-left: 200px; - } \ No newline at end of file +.dropdown { + position: relative; + display: inline-block; + padding: 15px; +} + +.dropdown-content { + margin-top: 10px; + position: absolute; + background-color: #f1f1f1; + display: none; + min-width: 130px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 3; +} + +.dropdown-content a { + color: black; + padding: 12px 16px; + z-index: 3; +} + +.dropdown-content a:hover {background-color: #ddd;} +.dropdown:hover .dropdown-content {display: block;} +.dropdown:hover .dropbtn {color: #ffffff; text-decoration: none;} + +.indvidual-list-table { + margin-top: 10px; + width: 98%; + margin-bottom: 20px; +} + +.create-entry-title { + margin-left: auto; + margin-right: auto; +} + +.entry-form { + margin-left: auto; + margin-right: auto; + width: 60%; + border-width:1px; + border-style:solid; + border-color: rgb(180, 178, 178); + padding: 30px 30px 30px 30px; +} +.parentId-drop-down { + margin-bottom: 20px; +} + +.clustertype-drop-down { + margin-bottom: 20px; +} + +.parentId-manual-input-field { + margin-bottom: 20px; + margin-left: 40px; +} + +.clustertype-manual-input-field { + margin-bottom: 20px; + margin-left: 40px; +} + +.spiffeId-input-field { + margin-bottom: 20px; +} + +.cluster-domain-name-input-field { + margin-bottom: 20px; +} + +.cluster-managed-by-input-field { + margin-bottom: 20px; +} + +.clustername-input-field { + margin-bottom: 20px; +} + +.parentId-helper { + font-size: 12px; + color:rgb(82, 79, 79); + margin-top: 2px; +} + +.cluster-helper { + font-size: 12px; + color:rgb(82, 79, 79); + margin-top: 2px; +} +.selectors-multiselect { + margin-bottom: 20px; +} + +.agents-multiselect { + margin-bottom: 20px; +} +.selectors-textArea { + margin-bottom: 20px; +} + +.ttl-input { + margin-right: 20px; + margin-bottom: 20px; + width: 200px +} + +.expiresAt-input { + margin-right: 20px; + margin-bottom: 20px; + width: 600px; +} + +.expiry-drop-down { + display: inline-block; + vertical-align: top; + margin-right: 20px; + width: 200px; +} + +.expiryEntryFields { + display: inline-block; + vertical-align: top; + width: 200px; +} + +.expiryOption-field { + display: inline-block; + vertical-align: top; + width: 500px; +} + +.expiryOption-entry { + margin-right: 20px; + display: inline-block; + vertical-align: top; + width: 200px; +} + +.federates-with-input-field { + margin-bottom: 20px; + width: 420px; +} +.dnsnames-input-field { + margin-bottom: 20px; + width: 420px; +} + +.servers-drp-dwn { + margin-top: 5px; +} + +.success-message { + font-weight: bold; + color: green; +} + +.failed-message { + font-weight: bold; + color:red; +} + +.no-data { + margin-top: 120px; + margin-left: 200px; +} +