diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx index 7e9aca4a..ee24dbec 100644 --- a/frontend/src/components/navbar.tsx +++ b/frontend/src/components/navbar.tsx @@ -62,6 +62,10 @@ type NavigationBarProp = { spireDebugServerInfoUpdateFunc: (globalDebugServerInfo: DebugServerInfo) => void, // dispatches a payload for an Error Message/ Success Message of an executed function as a string and has a return type of void tornjakMessageFunc: (globalErrorMessage: string) => void, + // the selected server for manager mode + globalServerSelected: string, + // tornjak error messege + globalErrorMessage: string, } type NavigationBarState = {} @@ -89,9 +93,18 @@ class NavigationBar extends Component { } } } - this.TornjakApi.populateLocalTornjakServerInfo(this.props.tornjakServerInfoUpdateFunc, this.props.tornjakMessageFunc); - this.TornjakApi.populateLocalTornjakDebugServerInfo(this.props.spireDebugServerInfoUpdateFunc, this.props.tornjakMessageFunc); - this.TornjakApi.populateServerInfo(this.props.globalTornjakServerInfo, this.props.serverInfoUpdateFunc); + if (IsManager) { + if (this.props.globalServerSelected !== "" && (this.props.globalErrorMessage === "OK" || this.props.globalErrorMessage === "")) { + // this.TornjakApi.populateAgentsUpdate(this.props.globalServerSelected, this.props.agentsListUpdateFunc, this.props.tornjakMessageFunc) + // this.TornjakApi.populateEntriesUpdate(this.props.globalServerSelected, this.props.entriesListUpdateFunc, this.props.tornjakMessageFunc) + // this.TornjakApi.refreshSelectorsState(this.props.globalServerSelected, this.props.agentworkloadSelectorInfoFunc); + // this.setState({ selectedServer: this.props.globalServerSelected }); + } + } else { + this.TornjakApi.populateLocalTornjakServerInfo(this.props.tornjakServerInfoUpdateFunc, this.props.tornjakMessageFunc); + this.TornjakApi.populateLocalTornjakDebugServerInfo(this.props.spireDebugServerInfoUpdateFunc, this.props.tornjakMessageFunc); + this.TornjakApi.populateServerInfo(this.props.globalTornjakServerInfo, this.props.serverInfoUpdateFunc); + } } render() { @@ -188,6 +201,8 @@ const mapStateToProps = (state: RootState) => ({ globalServerInfo: state.servers.globalServerInfo, globalDebugServerInfo: state.servers.globalDebugServerInfo, globalTornjakServerInfo: state.servers.globalTornjakServerInfo, + globalServerSelected: state.servers.globalServerSelected, + globalErrorMessage: state.tornjak.globalErrorMessage, }) export default connect( diff --git a/frontend/src/components/select-server.tsx b/frontend/src/components/select-server.tsx index 197b6dc2..5a05c973 100644 --- a/frontend/src/components/select-server.tsx +++ b/frontend/src/components/select-server.tsx @@ -17,6 +17,7 @@ import { import { RootState } from 'redux/reducers'; import { AgentsList, + ServersList, ServerInfo, TornjakServerInfo, DebugServerInfo, @@ -27,7 +28,7 @@ type SelectServerProp = { // tornjak server debug info of the selected server globalDebugServerInfo: DebugServerInfo, // dispatches a payload for the list of available servers and their basic info as array of strings and has a return type of void - serversListUpdateFunc: (globalServersList: Array) => void, + serversListUpdateFunc: (globalServersList: ServersList[]) => void, // dispatches a payload for the server selected in the redux state as a string and has a return type of void serverSelectedFunc: (globalServerSelected: string) => void, // dispatches a payload for the server trust domain and nodeAttestorPlugin and has a return type of void @@ -43,7 +44,7 @@ type SelectServerProp = { // tornjak server info of the selected server globalTornjakServerInfo: TornjakServerInfo, // list of avialable servers - globalServersList: Array, + globalServersList: ServersList[], // error/ success messege returned for a specific function globalErrorMessage: string, } diff --git a/frontend/src/components/server-management.js b/frontend/src/components/server-management.js deleted file mode 100644 index 3b84c083..00000000 --- a/frontend/src/components/server-management.js +++ /dev/null @@ -1,251 +0,0 @@ -import { Component } from 'react'; -import { connect } from 'react-redux'; -import axios from 'axios' -import GetApiServerUri from './helpers'; -import IsManager from './is_manager'; -import { - serversListUpdateFunc -} from 'redux/actions'; -import { showResponseToast } from './error-api'; - -const Server = props => ( - - {props.server.name} - {props.server.address} - {(props.server.mtls && "mTLS") || (props.server.tls && "TLS") || "None"} - -) - -class ServerManagement extends Component { - constructor(props) { - super(props); - this.state = { - formServerName: "", - formServerAddress: "", - formTLS: false, - formMTLS: false, - formCAData: null, - formCertData: null, - formKeyData: null, - CAFileText: "", - certFileText: "", - keyFileText: "", - }; - this.onCertFileChange = this.onCertFileChange.bind(this); - this.onCAFileChange = this.onCAFileChange.bind(this); - this.onKeyFileChange = this.onKeyFileChange.bind(this); - this.handleInputChange = this.handleInputChange.bind(this); - this.onSubmit = this.onSubmit.bind(this); - } - - componentDidMount() { - this.refreshServerState() - } - - refreshServerState () { - axios.get(GetApiServerUri("/manager-api/server/list"), { crossdomain: true }) - .then(response => { - console.log(response.data); - this.props.serversListUpdateFunc(response.data["servers"]); - }) - .catch((error) => showResponseToast(error, {caption: "Could not refresh server state."})) - } - - serverList() { - if (typeof this.props.globalServersList !== 'undefined') { - return this.props.globalServersList.map(s => { - return ; - }) - } else { - return "" - } - } - - handleInputChange(e) { - const target = e.target; - const value = target.type === 'checkbox' ? target.checked : target.value; - const name = target.name; - this.setState({ - [name]: value - }); - } - - - onCAFileChange = event => { - // Update the state - const reader = new FileReader(); - reader.onload = e => { - this.setState({ - formCAData: (new Buffer(e.target.result)).toString("base64"), - CAFileText: "CA file load success", - }); - } - reader.readAsText(event.target.files[0]) - }; - - - onCertFileChange = event => { - // Update the state - const reader = new FileReader(); - reader.onload = e => { - this.setState({ - formCertData: (new Buffer(e.target.result)).toString("base64"), - certFileText: "cert file load success", - }); - } - reader.readAsText(event.target.files[0]) - }; - - onKeyFileChange = event => { - // Update the state - const reader = new FileReader(); - reader.onload = e => { - this.setState({ - formKeyData: (new Buffer(e.target.result)).toString("base64"), - keyFileText: "key file load success", - }); - } - reader.readAsText(event.target.files[0]) - }; - - - onSubmit(e) { - e.preventDefault(); - - console.log("onSubmit"); - var cjtData = { - "name": this.state.formServerName, - "address": this.state.formServerAddress, - "tls": this.state.formTLS, - "mtls": this.state.formMTLS, - "ca": this.state.formCAData, - "cert": this.state.formCertData, - "key": this.state.formKeyData, - }; - axios.post(GetApiServerUri('/manager-api/server/register'), cjtData) - .then(res => { - this.setState({ message: "Requst:" + JSON.stringify(cjtData,null, ' ')+ "\n\nSuccess:" + JSON.stringify(res.data, null, ' ')}); - this.refreshServerState(); - } - ) - .catch(err => this.setState({ message: "ERROR:" + err + (typeof (err.response) !== "undefined" ? err.response.data : "")})) - - } - - - render() { - if (!IsManager) { - return

Only manager deployments have use of this page

- } - - const tlsFormOptions = ( -
CA File (for (m)TLS): - - {this.state.CAFileText} -
- ) - - const mtlsFormOptions = ( -
-
- Cert File (for mTLS): - - {this.state.certFileText} -
-
Key File (for mTLS): - - {this.state.keyFileText} -
-
- ) - - return ( -
-

Register New Server

-
-
- - -
- - -
- - -
- -
- - TLS Enabled -
- {this.state.formTLS && tlsFormOptions} - -
- - mTLS Enabled -
- {this.state.formMTLS && mtlsFormOptions} - -
-

- -
-
- -
-
-           {this.state.message}
-        
-
- - -

Server List

- - - - - - - - - - {this.serverList()} - -
Server NameAddressTLS?
- -
- ) - } -} - -const mapStateToProps = (state) => ({ - globalServersList: state.servers.globalServersList -}) - -export default connect( - mapStateToProps, - { serversListUpdateFunc } -)(ServerManagement) diff --git a/frontend/src/components/server-management.tsx b/frontend/src/components/server-management.tsx new file mode 100644 index 00000000..0118ba1a --- /dev/null +++ b/frontend/src/components/server-management.tsx @@ -0,0 +1,320 @@ +import { Component } from 'react'; +import { connect } from 'react-redux'; +import axios from 'axios' +import GetApiServerUri from './helpers'; +import IsManager from './is_manager'; +import { + serversListUpdateFunc +} from 'redux/actions'; +import { showResponseToast } from './error-api'; +import { ServersList } from './types' +import { RootState } from 'redux/reducers'; + +type ServerManagementProp = { + // returns the list of available servers and their basic info + serversListUpdateFunc: (globalServersList: ServersList[]) => void, + // the list of available servers and their basic info + globalServersList: ServersList[], + +} + +type ServerManagementState = { + formServerName: string, + formServerAddress: string, + formTLS: boolean, + formMTLS: boolean, + formCAData: string | null, + formCertData: string | null, + formKeyData: string | null, + CAFileText?: string, + certFileText?: string, + keyFileText?: string, + message: string, +} + +const Server = (props: { server: ServersList }) => ( + + {props.server.name} + {props.server.address} + {(props.server.mtls && "mTLS") || (props.server.tls && "TLS") || "None"} + +) + +class ServerManagement extends Component { + constructor(props: ServerManagementProp) { + super(props); + this.state = { + formServerName: "", + formServerAddress: "", + formTLS: false, + formMTLS: false, + formCAData: null, + formCertData: null, + formKeyData: null, + CAFileText: "", + certFileText: "", + keyFileText: "", + message: "", + }; + this.onCertFileChange = this.onCertFileChange.bind(this); + this.onCAFileChange = this.onCAFileChange.bind(this); + this.onKeyFileChange = this.onKeyFileChange.bind(this); + this.handleInputChange = this.handleInputChange.bind(this); + this.onSubmit = this.onSubmit.bind(this); + } + + componentDidMount() { + this.refreshServerState() + } + + refreshServerState() { + axios.get(GetApiServerUri("/manager-api/server/list"), { crossdomain: true }) + .then(response => { + console.log(response.data); + this.props.serversListUpdateFunc(response.data["servers"]); + }) + .catch((error) => showResponseToast(error, { caption: "Could not refresh server state." })) + } + + serverList() { + if (typeof this.props.globalServersList !== 'undefined') { + return this.props.globalServersList.map(s => { + return ; + }) + } else { + return "" + } + } + + handleInputChange(e: { target: any; }) { + const target = e.target; + const value = target.type === 'checkbox' ? target.checked : target.value; + const name = target.name; + this.setState((prevState: ServerManagementState) => ({ + ...prevState, + [name]: value + })); + } + + + onCAFileChange = (event: React.ChangeEvent) => { + const files = event.target.files; + if (files && files[0]) { + const reader = new FileReader(); + reader.onload = (e: ProgressEvent) => { + if (e.target && e.target.result) { + const result = e.target.result; + + // Handle string and ArrayBuffer cases + if (typeof result === "string") { + this.setState({ + formCAData: Buffer.from(result).toString("base64"), + CAFileText: "CA file load success", + }); + } else if (result instanceof ArrayBuffer) { + this.setState({ + formCAData: Buffer.from(new Uint8Array(result)).toString("base64"), + CAFileText: "CA file load success", + }); + } + } + }; + reader.readAsText(files[0]); + } + }; + + onCertFileChange = (event: React.ChangeEvent) => { + const files = event.target.files; + if (files && files[0]) { + const reader = new FileReader(); + reader.onload = (e: ProgressEvent) => { + if (e.target && e.target.result) { + const result = e.target.result; + + // Handle string and ArrayBuffer cases + if (typeof result === "string") { + this.setState({ + formCertData: Buffer.from(result).toString("base64"), + certFileText: "cert file load success", + }); + } else if (result instanceof ArrayBuffer) { + this.setState({ + formCertData: Buffer.from(new Uint8Array(result)).toString("base64"), + certFileText: "cert file load success", + }); + } + } + }; + reader.readAsText(files[0]); + } + }; + + onKeyFileChange = (event: React.ChangeEvent) => { + const files = event.target.files; + if (files && files[0]) { + const reader = new FileReader(); + reader.onload = (e: ProgressEvent) => { + if (e.target && e.target.result) { + const result = e.target.result; + + // Handle string and ArrayBuffer cases + if (typeof result === "string") { + this.setState({ + formKeyData: Buffer.from(result).toString("base64"), + keyFileText: "key file load success", + }); + } else if (result instanceof ArrayBuffer) { + this.setState({ + formKeyData: Buffer.from(new Uint8Array(result)).toString("base64"), + keyFileText: "key file load success", + }); + } + } + }; + reader.readAsText(files[0]); + } + }; + + + + + onSubmit(e: { preventDefault: () => void; }) { + e.preventDefault(); + + console.log("onSubmit"); + var cjtData = { + "name": this.state.formServerName, + "address": this.state.formServerAddress, + "tls": this.state.formTLS, + "mtls": this.state.formMTLS, + "ca": this.state.formCAData, + "cert": this.state.formCertData, + "key": this.state.formKeyData, + }; + axios.post(GetApiServerUri('/manager-api/server/register'), cjtData) + .then(res => { + this.setState({ message: "Requst:" + JSON.stringify(cjtData, null, ' ') + "\n\nSuccess:" + JSON.stringify(res.data, null, ' ') }); + this.refreshServerState(); + } + ) + .catch(err => this.setState({ message: "ERROR:" + err + (typeof (err.response) !== "undefined" ? err.response.data : "") })) + + } + + + render() { + if (!IsManager) { + return

Only manager deployments have use of this page

+ } + + const tlsFormOptions = ( +
CA File (for (m)TLS): + + {this.state.CAFileText} +
+ ) + + const mtlsFormOptions = ( +
+
+ Cert File (for mTLS): + + {this.state.certFileText} +
+
Key File (for mTLS): + + {this.state.keyFileText} +
+
+ ) + + return ( +
+

Register New Server

+
+
+ + +
+ + +
+ + +
+ +
+ + TLS Enabled +
+ {this.state.formTLS && tlsFormOptions} + +
+ + mTLS Enabled +
+ {this.state.formMTLS && mtlsFormOptions} + +
+

+ +
+
+ +
+
+            {this.state.message}
+          
+
+ + +

Server List

+ + + + + + + + + + {this.serverList()} + +
Server NameAddressTLS?
+ +
+ ) + } +} + +const mapStateToProps = (state: RootState) => ({ + globalServersList: state.servers.globalServersList +}) + +export default connect( + mapStateToProps, + { serversListUpdateFunc } +)(ServerManagement) diff --git a/frontend/src/components/tornjak-api-helpers.tsx b/frontend/src/components/tornjak-api-helpers.tsx index 5a1d4f64..d58f1016 100644 --- a/frontend/src/components/tornjak-api-helpers.tsx +++ b/frontend/src/components/tornjak-api-helpers.tsx @@ -164,7 +164,7 @@ class TornjakApi extends Component { }) .catch((error) => { showResponseToast(error, { caption: "Could not populate local tornjak server info." }) - tornjakMessageFunc(error.response.statusText) + tornjakMessageFunc(error) }) } diff --git a/frontend/src/components/types.ts b/frontend/src/components/types.ts index a91caf2f..61fdec20 100644 --- a/frontend/src/components/types.ts +++ b/frontend/src/components/types.ts @@ -128,6 +128,14 @@ export interface SpireHealthCheckFreq { SpireHealthCheckFreqDisplay: string; // SPIRE health check dropdown display/ for persistence } +// servers +export interface ServersList { + name: string; // Name of Server + address: string; // url + mtls: string; // mtls + tls: string; // tls +} + // tornjak export interface StringLabels { label: string; diff --git a/frontend/src/redux/actions/index.ts b/frontend/src/redux/actions/index.ts index dc0ba4ca..3a9b2f8a 100644 --- a/frontend/src/redux/actions/index.ts +++ b/frontend/src/redux/actions/index.ts @@ -51,6 +51,7 @@ import { AgentsList, AgentsWorkLoadAttestorInfo, ClustersList, + ServersList, EntriesList, SelectorInfoLabels, ServerInfo, @@ -233,7 +234,7 @@ export function serverInfoUpdateFunc(globalServerInfo: ServerInfo): ThunkAction< // } // ] // serversListUpdateFunc returns the list of available servers and their basic info -export function serversListUpdateFunc(globalServersList: Array): ThunkAction { +export function serversListUpdateFunc(globalServersList: ServersList[]): ThunkAction { return dispatch => { dispatch({ type: GLOBAL_SERVERS_LIST, diff --git a/frontend/src/redux/actions/types.ts b/frontend/src/redux/actions/types.ts index 58b8586c..d4541e09 100644 --- a/frontend/src/redux/actions/types.ts +++ b/frontend/src/redux/actions/types.ts @@ -3,6 +3,7 @@ import { AgentsList, AgentsWorkLoadAttestorInfo, ClustersList, + ServersList, EntriesList, SelectorInfoLabels, ServerInfo, @@ -104,7 +105,7 @@ export interface ServersReducerState { globalServerSelected: string, globalServerInfo: ServerInfo, globalTornjakServerInfo: TornjakServerInfo, - globalServersList: Array, + globalServersList: ServersList[], globalSelectorInfo: SelectorInfoLabels, globalWorkloadSelectorInfo: WorkloadSelectorInfoLabels, globalSpireHealthCheck: boolean, @@ -126,7 +127,7 @@ export interface TornjakServerInfoAction extends Action { - payload: Array; + payload: ServersList[]; } export interface SelectorInfoAction extends Action { payload: SelectorInfoLabels; diff --git a/frontend/src/tables/clusters-list-table.tsx b/frontend/src/tables/clusters-list-table.tsx index 276fe130..2e81cef2 100644 --- a/frontend/src/tables/clusters-list-table.tsx +++ b/frontend/src/tables/clusters-list-table.tsx @@ -1,8 +1,6 @@ import React from "react"; import { connect } from 'react-redux'; import IsManager from 'components/is_manager'; -import GetApiServerUri from 'components/helpers'; -import axios from 'axios'; import { clustersListUpdateFunc } from 'redux/actions'; @@ -10,7 +8,6 @@ import Table from './list-table'; import { ClustersList } from "components/types"; import { DenormalizedRow } from "carbon-components-react"; import { RootState } from "redux/reducers"; -import { showResponseToast } from "components/error-api"; import TornjakApi from 'components/tornjak-api-helpers'; // ClusterListTable takes in diff --git a/frontend/src/tables/entries-list-table.tsx b/frontend/src/tables/entries-list-table.tsx index 1dd33e83..8b6f422b 100644 --- a/frontend/src/tables/entries-list-table.tsx +++ b/frontend/src/tables/entries-list-table.tsx @@ -1,8 +1,6 @@ import React from "react"; import { connect } from 'react-redux'; -import GetApiServerUri from 'components/helpers'; import IsManager from 'components/is_manager'; -import axios from 'axios' import { entriesListUpdateFunc } from 'redux/actions'; @@ -11,8 +9,6 @@ import { EntriesList } from "components/types"; import { RootState } from "redux/reducers"; import { DenormalizedRow } from "carbon-components-react"; import { saveAs } from "file-saver"; -import { showResponseToast } from "components/error-api"; -import apiEndpoints from 'components/apiConfig'; import TornjakApi from 'components/tornjak-api-helpers'; // EntriesListTable takes in diff --git a/go.mod b/go.mod index 76cb820c..07cdd025 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/spiffe/tornjak go 1.22 +toolchain go1.22.2 + require ( github.com/MicahParks/keyfunc/v2 v2.1.0 github.com/cenkalti/backoff/v4 v4.2.0