Skip to content

Commit

Permalink
Federation Experience Frontend - View (#525)
Browse files Browse the repository at this point in the history
  • Loading branch information
mamy-CS authored Nov 1, 2024
2 parents 69c4e63 + e3ada1d commit 1bbbc34
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 4 deletions.
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import NavigationBar from "./components/navbar";
import SelectServer from "./components/select-server";
import ClusterList from "./components/cluster-list";
import ClusterManagement from "./components/cluster-management";
import FederationList from "./components/federation-list";
import AgentList from "./components/agent-list";
import CreateJoinToken from "./components/agent-create-join-token";
import EntryList from "./components/entry-list";
Expand Down Expand Up @@ -44,6 +45,7 @@ function App() {
{IsManager && <br />}
<Route path="/" exact component={AgentList} />
<Route path="/clusters" exact component={ClusterList} />
<Route path="/federations" exact component={FederationList} />
<Route path="/agents" exact component={AgentList} />
<Route path="/entries" exact component={EntryList} />
<RenderOnAdminRole>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/apiConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const apiEndpoints = {
spireAgentsBanApi: `${API_BASE_URL}/spire/agents/ban`,
spireJoinTokenApi: `${API_BASE_URL}/spire/agents/jointoken`,
spireEntriesApi: `${API_BASE_URL}/spire/entries`,
spireFederationsApi: `${API_BASE_URL}/spire/federations`,
tornjakServerInfoApi: `${API_BASE_URL}/tornjak/serverinfo`,
tornjakSelectorsApi: `${API_BASE_URL}/tornjak/selectors`,
tornjakAgentsApi: `${API_BASE_URL}/tornjak/agents`,
Expand Down
136 changes: 136 additions & 0 deletions frontend/src/components/federation-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Table from "tables/federations-list-table";
import TornjakApi from './tornjak-api-helpers';
import {
serverSelectedFunc,
agentsListUpdateFunc,
tornjakServerInfoUpdateFunc,
serverInfoUpdateFunc,
selectorInfoFunc,
tornjakMessageFunc,
workloadSelectorInfoFunc,
agentworkloadSelectorInfoFunc,
clustersListUpdateFunc,
federationsListUpdateFunc
} from 'redux/actions';
import { RootState } from 'redux/reducers';
import { FederationsList, ServerInfo, TornjakServerInfo } from './types'

type FederationsListProp = {
// dispatches a payload for list of federations with their metadata info as an array of FederationsList Type and has a return type of void
federationsListUpdateFunc: (globalFederationsList: FederationsList[]) => void,
// dispatches a payload for the tornjak error messsege and has a return type of void
tornjakMessageFunc: (globalErrorMessage: string) => void,
// dispatches a payload for the server trust domain and nodeAttestorPlugin as a ServerInfoType and has a return type of void
serverInfoUpdateFunc: (globalServerInfo: ServerInfo) => void,
// the selected server for manager mode
globalServerSelected: string,
// error/ success messege returned for a specific function
globalErrorMessage: string,
// tornjak server info of the selected server
globalTornjakServerInfo: TornjakServerInfo,
// list of federations with their metadata info as an array of FederationsList Type
globalFederationsList: FederationsList[],
}

type FederationsListState = {
message: string // error/ success messege returned for a specific function for this specific component
}

const Federation = (props: { federation: FederationsList }) => (
<tr>
<td>{props.federation.trust_domain}</td>
<td>{props.federation.bundle_endpoint_url}</td>
<td>{props.federation.BundleEndpointProfile.HttpsSpiffe ? 'https_spiffe' : 'https_web'}</td>
<td><div style={{ overflowX: 'auto', width: "400px" }}>
<pre>{JSON.stringify(props.federation, null, ' ')}</pre>
</div></td>
</tr>
)

class FederationList extends Component<FederationsListProp, FederationsListState> {
TornjakApi: TornjakApi;
constructor(props: FederationsListProp) {
super(props);
this.TornjakApi = new TornjakApi(props);
this.state = {
message: "",
};
}

componentDidMount() {
this.TornjakApi.populateLocalFederationsUpdate(this.props.federationsListUpdateFunc, this.props.tornjakMessageFunc);
if (this.props.globalTornjakServerInfo && Object.keys(this.props.globalTornjakServerInfo).length) {
this.TornjakApi.populateServerInfo(this.props.globalTornjakServerInfo, this.props.serverInfoUpdateFunc);
}
}

componentDidUpdate(prevProps: FederationsListProp) {
if (prevProps.globalTornjakServerInfo !== this.props.globalTornjakServerInfo) {
this.TornjakApi.populateServerInfo(this.props.globalTornjakServerInfo, this.props.serverInfoUpdateFunc);
}
}

federationList() {
if (typeof this.props.globalFederationsList !== 'undefined') {
return this.props.globalFederationsList.map((currentFederation: FederationsList, index) => {
return <Federation key={`federation-${index}`} federation={currentFederation} />;
})
} else {
return ""
}
}

render() {
return (
<div>
<h3>Federations List</h3>
{this.props.globalErrorMessage !== "OK" &&
<div className="alert-primary" role="alert">
<pre>
{this.props.globalErrorMessage}
</pre>
</div>
}
<br /><br />
<div className="indvidual-list-table">
<Table data={this.federationList()} id="table-1" />
</div>
</div>
)
}
}

// Note: Needed for UI testing - will be removed after
// FederationsList.propTypes = {
// globalServerSelected: PropTypes.string,
// globalClustersList: PropTypes.array,
// globalTornjakServerInfo: PropTypes.object,
// globalErrorMessage: PropTypes.string,
// serverSelectedFunc: PropTypes.func,
// agentsListUpdateFunc: PropTypes.func,
// tornjakServerInfoUpdateFunc: PropTypes.func,
// serverInfoUpdateFunc: PropTypes.func,
// clusterTypeList: PropTypes.array,
// agentsList: PropTypes.array,
// selectorInfoFunc: PropTypes.func,
// tornjakMessageFunc: PropTypes.func,
// workloadSelectorInfoFunc: PropTypes.func,
// agentworkloadSelectorInfoFunc: PropTypes.func,
// clustersListUpdateFunc: PropTypes.func
// };

const mapStateToProps = (state: RootState) => ({
globalServerSelected: state.servers.globalServerSelected,
globalFederationsList: state.federations.globalFederationsList,
globalTornjakServerInfo: state.servers.globalTornjakServerInfo,
globalErrorMessage: state.tornjak.globalErrorMessage,
})

export default connect(
mapStateToProps,
{ serverSelectedFunc, agentsListUpdateFunc, tornjakServerInfoUpdateFunc, serverInfoUpdateFunc, selectorInfoFunc, tornjakMessageFunc, workloadSelectorInfoFunc, agentworkloadSelectorInfoFunc, clustersListUpdateFunc, federationsListUpdateFunc }
)(FederationList)

export { FederationList }
10 changes: 10 additions & 0 deletions frontend/src/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ class NavigationBar extends Component<NavigationBarProp, NavigationBarState> {
}
</div>
</div>
<div className="dropdown">
<a href="/federations" className="dropbtn">Federations </a>
<div className="dropdown-content">
<a href="/federations" className="nav-link">Federations List</a>
{/* To be added */}
{/*{(isAdmin || !withAuth) &&*/}
{/* <a href="" className="nav-link">Create Federation</a>*/}
{/*}*/}
</div>
</div>
<div className="dropdown">
<a href="/tornjak/serverinfo" className="dropbtn">Tornjak ServerInfo</a>
</div>
Expand Down
22 changes: 21 additions & 1 deletion frontend/src/components/tornjak-api-helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
ServerInfo,
EntriesList,
ClustersList,
DebugServerInfo
DebugServerInfo,
FederationsList
} from './types';
import KeycloakService from "auth/KeycloakAuth";
import { showResponseToast } from './error-api';
Expand Down Expand Up @@ -228,6 +229,25 @@ class TornjakApi extends Component<TornjakApiProp, TornjakApiState> {
})
}

// populateLocalFederationsUpdate - returns the list of federations with their info in Local mode for the server
populateLocalFederationsUpdate = (federationsListUpdateFunc: {
(globalFederationsList: FederationsList[]): void;
},
tornjakMessageFunc: { (globalErrorMessage: string): void; }) => {
axios.get(GetApiServerUri(apiEndpoints.spireFederationsApi), { crossdomain: true })
.then(response => {
if (!response.data["federation_relationships"]) {
federationsListUpdateFunc([]);
} else { federationsListUpdateFunc(response.data["federation_relationships"]); }
tornjakMessageFunc(response.statusText);
})
.catch((error) => {
showResponseToast(error, { caption: "Could not populate local federations." })
federationsListUpdateFunc([]);
tornjakMessageFunc("Error retrieving: " + error.message);
})
}

// populateLocalClustersUpdate - returns the list of clusters with their info in Local mode for the server
populateLocalClustersUpdate = (
clustersListUpdateFunc: { (globalClustersList: ClustersList[]): void },
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,38 @@ export interface ClustersList {
agentsList: Array<string>; // List of agents associated with the cluster
}

// federations
export interface FederationProfileSpiffeResponse {
endpoint_spiffe_id: string;
}

export interface BundleEndpointProfile {
HttpsSpiffe?: FederationProfileSpiffeResponse
HttpsWeb?: object
}

export interface x509Authority {
asn1: string
}

export interface JwtAuthority {
public_key: string
key_id: string
}

export interface TrustDomainBundle {
trust_domain: string
x509_authorities: Array<x509Authority>
jwt_authorities: Array<JwtAuthority>
}

export interface FederationsList {
trust_domain: string;
bundle_endpoint_url: string;
BundleEndpointProfile: BundleEndpointProfile;
trust_domain_bundle: TrustDomainBundle;
}

// entries
export interface EntriesList {
// From https://github.com/spiffe/spire-api-sdk/blob/main/proto/spire/api/types/entry.pb.go
Expand Down
15 changes: 13 additions & 2 deletions frontend/src/redux/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {
GLOBAL_SPIRE_HEALTH_CHECK_TIME,
SpireHealthCheckTimeAction,
GLOBAL_DEBUG_SERVER_INFO,
DebugServerInfoAction
DebugServerInfoAction, GLOBAL_FEDERATIONS_LIST, FederationsListAction
} from './types';

import {
Expand All @@ -58,7 +58,7 @@ import {
TornjakServerInfo,
WorkloadSelectorInfoLabels,
SpireHealthCheckFreq,
DebugServerInfo
DebugServerInfo, FederationsList
} from 'components/types';

// Expected input - spire debug server info
Expand Down Expand Up @@ -319,6 +319,17 @@ export function agentsListUpdateFunc(globalAgentsList: AgentsList[]): ThunkActio
}
}

// Expected input - List of federations with their info
// federationsListUpdateFunc returns the list of federations with their info
export function federationsListUpdateFunc(globalFederationsList: FederationsList[]): ThunkAction<void, RootState, undefined, FederationsListAction> {
return dispatch => {
dispatch({
type: GLOBAL_FEDERATIONS_LIST,
payload: globalFederationsList
});
}
}

// Expected input -
// [
// "workloadselector1": [
Expand Down
13 changes: 12 additions & 1 deletion frontend/src/redux/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
TornjakServerInfo,
WorkloadSelectorInfoLabels,
SpireHealthCheckFreq,
DebugServerInfo
DebugServerInfo, FederationsList
} from "components/types";

// auth
Expand Down Expand Up @@ -50,6 +50,17 @@ export interface AgentWorkloadSelectorInfoAction extends Action<typeof GLOBAL_AG
payload: AgentsWorkLoadAttestorInfo[];
}

// federations
export const GLOBAL_FEDERATIONS_LIST = 'GLOBAL_FEDERATIONS_LIST';

export interface FederationsReducerState {
globalFederationsList: FederationsList[],
}

export interface FederationsListAction extends Action<typeof GLOBAL_FEDERATIONS_LIST> {
payload: FederationsList[];
}

// clusters
export const GLOBAL_CLUSTERS_LIST = 'GLOBAL_CLUSTERS_LIST';
export const GLOBAL_CLUSTER_TYPE_INFO = 'GLOBAL_CLUSTER_TYPE_INFO';
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/redux/reducers/federationsReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
FederationsListAction,
FederationsReducerState,
GLOBAL_FEDERATIONS_LIST,
} from '../actions/types';

const initialState: FederationsReducerState = {
globalFederationsList: [],
};

export default function federationsReducer(state: FederationsReducerState = initialState, action: FederationsListAction) {
switch (action.type) {
case GLOBAL_FEDERATIONS_LIST:
return {
...state,
globalFederationsList: action.payload
};
default:
return state;
}
}
2 changes: 2 additions & 0 deletions frontend/src/redux/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import entriesReducer from './entriesReducer';
import tornjakReducer from './tornjakReducer';
import {combineReducers} from 'redux';
import authReducer from './authReducer';
import federationsReducer from "./federationsReducer";

const allReducers = combineReducers({
servers : serversReducer,
clusters : clustersReducer,
federations: federationsReducer,
agents : agentsReducer,
entries : entriesReducer,
tornjak: tornjakReducer,
Expand Down
Loading

0 comments on commit 1bbbc34

Please sign in to comment.