Skip to content

Commit

Permalink
feat: link software assets to hardware assets (1-many)
Browse files Browse the repository at this point in the history
  • Loading branch information
LiamTownsley committed Nov 19, 2023
1 parent 438764d commit 855dca5
Show file tree
Hide file tree
Showing 24 changed files with 434 additions and 132 deletions.
105 changes: 105 additions & 0 deletions electron/api/routes/AssetLinkRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Request, Response } from "express";
import { wrapper } from "..";
import { APIResponse } from "../../../src/interfaces/APIRequests";
import { Collection } from "mongodb";

const express = require('express');
const mongo = require('mongodb');
const router = express.Router();

const DATABASE = "asset-links";

// @ROUTE: GET api/asset-links/
// @DESCRIPTION: Used for getting ALL Asset Links.
router.get('/', async (_: Request, res: Response) => {
await wrapper(async (db: any) => {
const collection: Collection = db.collection(DATABASE);
const resp = await collection.find({}).toArray();
return res.send(resp);
});
});

// @ROUTE: POST api/asset-links/
// @DESCRIPTION: Used for creating an Asset Link.
router.post('/', async (req: Request, res: Response) => {
await wrapper(async (db: any) => {
console.log('request got at PSOT asset-links/')
const { hardware_id, software_id, date, created_by }: APIResponse.CreateAssetLink = req.body as APIResponse.CreateAssetLink;
const collection: Collection = db.collection(DATABASE);

const HardwareID = new mongo.ObjectId(hardware_id);
const SoftwareID = new mongo.ObjectId(software_id)

const isExisting = await collection.find({
hardware_id: HardwareID,
software_id: SoftwareID
}).toArray()

if (isExisting.length > 0) {
return res.send({ status: false })
}

const _date = new Date(date);
const resp = await collection.insertOne({
hardware_id: HardwareID,
software_id: SoftwareID,
date: _date,
created_by
})

if (resp.acknowledged) return res.send(resp);
return res.send({ status: false })
});
});

// @ROUTE: GET api/asset-links/hardware/:id
// @DESCRIPTION: Used for getting ALL Asset Links for a specific Hardware Asset.
router.get('/hardware/:id', async (req: Request, res: Response) => {
await wrapper(async (db: any) => {
const collection: Collection = db.collection(DATABASE);
const id = req.params.id;

const resp = await collection.find({ hardware_id: new mongo.ObjectId(id) }).toArray();

let resp_arr = [];
for (const [i, link] of resp.entries()) {
const subresp = await db.collection('software').findOne({ _id: link.software_id }) ?? undefined;
resp_arr.push({
software: subresp,
link: resp[i]
});
}
return res.json(resp_arr.filter(x => !!x));
});
});
// @ROUTE: DELETE api/asset-links/hardware/:id
// @DESCRIPTION: Used for getting ALL Asset Links for a specific Hardware Asset.
router.delete('/hardware/:hwid/:swid', async (req: Request, res: Response) => {
await wrapper(async (db: any) => {
console.log('api response recieved!!!!', req.params.link_id)
const collection: Collection = db.collection(DATABASE);
const hwid = req.params.hwid;
const swid = req.params.swid;

const resp = await collection.deleteMany({
hardware_id: new mongo.ObjectId(hwid),
software_id: new mongo.ObjectId(swid)
});
console.log(resp);
return res.json(resp);
});
});

// @ROUTE: GET api/asset-links/hardware/:id
// @DESCRIPTION: Used for getting ALL Asset Links for a specific Software Asset.
router.get('/software/:id', async (req: Request, res: Response) => {
await wrapper(async (db: any) => {
const collection: Collection = db.collection(DATABASE);
const id = req.params.id;

const resp = await collection.find({ software_id: new mongo.ObjectId(id) }).toArray();
return res.send(resp);
});
});

export default router;
18 changes: 8 additions & 10 deletions electron/api/routes/SoftwareAssetRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,15 @@ router.post('/', async (req: Request, res: Response) => {
await wrapper(async (db: any) => {
console.log(req.body);
const collection = db.collection(DATABASE);
const { name, manufacturer, version, parent_id } = req.body;
const { name, manufacturer, version, risk_level } = req.body;

collection.insertOne({
name,
manufacturer,
version,
parent_hardware: {
id: new mongo.ObjectId(parent_id),
date: new Date().toISOString()
}
risk_level: risk_level ?? 'N/A',
created_at: new Date().toISOString(),
last_edit_at: new Date().toISOString()
})

res.send({ status: true })
Expand All @@ -95,16 +94,15 @@ router.patch('/:id', async (req: Request, res: Response) => {
console.log(req.body);
const collection = db.collection(DATABASE);
const id = req.params.id;
const { name, manufacturer, version, parent_id } = req.body;
const { name, manufacturer, version, riskLevel, created_at } = req.body;

await collection.replaceOne({ _id: new mongo.ObjectId(id) }, {
name,
manufacturer,
version,
parent_hardware: {
id: new mongo.ObjectId(parent_id),
date: new Date().toISOString()
}
risk_level: riskLevel ?? 'N/A',
created_at,
last_edit_at: new Date().toISOString()
}, { upsert: true });

res.send({ status: true })
Expand Down
2 changes: 2 additions & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ server.use(bodyParser.urlencoded({ extended: false }))
import HardwareAssetRoutes from './api/routes/HardwareAssetRoutes';
import SoftwareAssetRoutes from './api/routes/SoftwareAssetRoutes';
import EmployeeRoutes from './api/routes/EmployeeRoutes';
import AssetLinkRoutes from './api/routes/AssetLinkRoutes';


server.use('/api/assets/hardware', HardwareAssetRoutes);
server.use('/api/assets/software', SoftwareAssetRoutes);
server.use('/api/asset-link', AssetLinkRoutes);
server.use('/api/employees', EmployeeRoutes);

// ======================== GENERATED CODE ========================
Expand Down
54 changes: 35 additions & 19 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
import './styles/index.scss';

import { useState } from 'react';
import { Routes, Route, HashRouter } from 'react-router-dom';

import Home from './pages/Home';
import Login from './pages/Login';

import HardwareAssets from './pages/assets/hardware/HardwareAssets';
import Employees from './pages/employee/Employees';
import CreateAssets from './pages/assets/hardware/CreateHardwareAsset';
import Error404 from './pages/Error404';
import HardwareAsset from './pages/assets/hardware/HardwareAsset';
import Employee from './pages/employee/Employee';
import Login from './pages/Login';
import Versions from './pages/Versions';
import CreateHardwareAsset from './pages/assets/hardware/CreateHardwareAsset';
import EditHardwareAsset from './pages/assets/hardware/EditHardwareAsset';
import InstallSoftware from './pages/assets/hardware/InstallSoftware';

import SoftwareAssets from './pages/assets/software/SoftwareAssets';
import CreateEmployee from './pages/employee/CreateEmployee';
import CreateSoftwareAsset from './pages/assets/software/CreateSoftwareAsset';
import SoftwareAsset from './pages/assets/software/SoftwareAsset';
import CreateSoftwareAsset from './pages/assets/software/CreateSoftwareAsset';
import EditSoftwareAsset from './pages/assets/software/EditSoftwareAsset';
import EditHardwareAsset from './pages/assets/hardware/EditHardwareAsset';

import './styles/index.scss';
import Employees from './pages/employee/Employees';
import Employee from './pages/employee/Employee';
import CreateEmployee from './pages/employee/CreateEmployee';

import Error404 from './pages/Error404';
import Versions from './pages/Versions';

function App() {
const [user, setUser] = useState(undefined as any);
if (!user) {
try {
const storage_user = JSON.parse(localStorage.getItem('user')!);
if(storage_user && storage_user !== '[object Object]')
setUser(storage_user);
if (storage_user && storage_user !== '[object Object]')
setUser(storage_user);
} catch (error) {
console.log('Could not retrieve previous session.')
}
Expand All @@ -34,24 +39,35 @@ function App() {
return <Login setUser={setUser} user={user} />
}
}

return (
<>
<HashRouter>
<Routes>
{/* Index Route */}
<Route index element={<Home setUser={setUser} user={user} />} />
<Route path="/versions" element={<Versions setUser={setUser} user={user} />} />
<Route path="/assets" element={<HardwareAssets setUser={setUser} user={user} />} />
<Route path="/assets/:id" element={<HardwareAsset setUser={setUser} user={user} />} />
<Route path="/assets/create" element={<CreateAssets setUser={setUser} user={user} />} />

{/* Hardware Routes */}
<Route path="/hardware" element={<HardwareAssets setUser={setUser} user={user} />} />
<Route path="/hardware/:id" element={<HardwareAsset setUser={setUser} user={user} />} />
<Route path="/hardware/create" element={<CreateHardwareAsset setUser={setUser} user={user} />} />
<Route path="/hardware/:id/edit" element={<EditHardwareAsset setUser={setUser} user={user} />} />
<Route path="/hardware/:id/install" element={<InstallSoftware setUser={setUser} user={user} />} />

{/* Software Routes */}
<Route path="/software" element={<SoftwareAssets setUser={setUser} user={user} />} />
<Route path="/software/:id" element={<SoftwareAsset setUser={setUser} user={user} />} />
<Route path="/software/create" element={<CreateSoftwareAsset setUser={setUser} user={user} />} />
<Route path="/edit/software/:id" element={<EditSoftwareAsset setUser={setUser} user={user} />} />
<Route path="/edit/assets/:id" element={<EditHardwareAsset setUser={setUser} user={user} />} />
<Route path="/software/:id/edit" element={<EditSoftwareAsset setUser={setUser} user={user} />} />

{/* Employee Routes */}
<Route path="/employees" element={<Employees setUser={setUser} user={user} />} />
<Route path="/employees/:id" element={<Employee setUser={setUser} user={user} />} />
<Route path="/employees/create" element={<CreateEmployee setUser={setUser} user={user} />} />
{/* <Route path="/edit/employees/:id" element={<EditEmployee setUser={setUser} user={user} />} /> */}
{/* <Route path="/employees/:id/edit" element={<EditEmployee setUser={setUser} user={user} />} /> */}

{/* Misc. Routes */}
<Route path="/versions" element={<Versions setUser={setUser} user={user} />} />
<Route path='*' element={<Error404 setUser={setUser} user={user} />} />
</Routes>
</HashRouter>
Expand Down
6 changes: 6 additions & 0 deletions src/components/PillButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ class PillButton extends Component<{ label: string }> {
style = '#f5cd79'
break;
case 'critical':
style = '#c0392b'
break;
case 'high':
style = '#e74c3c'
break;
case 'medium':
Expand All @@ -53,6 +56,9 @@ class PillButton extends Component<{ label: string }> {
case 'low':
style = '#3498db'
break;
case 'n/a':
style = '#bdc3c7'
break;
default:
style = '#a29bfe'
break;
Expand Down
4 changes: 2 additions & 2 deletions src/components/assets/hardware/HardwareAssetTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ class HardwareAssetTable extends Component<{ assets: HardwareAsset[] }> {
<td><PillButton label={item.type} /></td>
<td>{item.ip}</td>
<td>
<Link to={`/assets/${item._id}`} role="button" id="blue-button" className="btn btn-outline-primary"><i className="fa fa-eye" /> View Asset</Link>
<Link to={`/edit/assets/${item._id}`} role="button" id="blue-button" className="btn btn-outline-primary"><i className="fa fa-edit" /> Edit Asset</Link>
<Link to={`/hardware/${item._id}`} role="button" id="blue-button" className="btn btn-outline-primary"><i className="fa fa-eye" /> View Asset</Link>
<Link to={`/hardware/${item._id}/edit`} role="button" id="blue-button" className="btn btn-outline-primary"><i className="fa fa-edit" /> Edit Asset</Link>
<button onClick={() => {
fetch(`http://127.0.0.1:3001/api/assets/hardware/${item._id}`, { method: 'DELETE' }).then(() => {
this.refreshPage();
Expand Down
13 changes: 7 additions & 6 deletions src/components/assets/software/SoftwareAsset.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { ObjectId } from "mongodb";
import { Asset } from "../Asset";
import { IParentHardware, ISoftwareAsset } from "../../../interfaces/Asset";
import { ISoftwareAsset } from "../../../interfaces/Asset";

export class SoftwareAsset extends Asset implements ISoftwareAsset {
public _id: ObjectId;
public name: string;
public version: string;
public manufacturer: string;
public parent_hardware: IParentHardware | undefined;
public risk_level: string;
public created_at: string;
public last_edit_at: string;

constructor(props: SoftwareAsset) {
super(props);
this._id = props._id;
this.name = props.name;
this.manufacturer = props.manufacturer;
this.version = props.version;
this.parent_hardware = (props.parent_hardware) ? {
id: props.parent_hardware.id,
date: props.parent_hardware.date
} : undefined;
this.risk_level = props.risk_level;
this.created_at = props.created_at;
this.last_edit_at = props.last_edit_at;
}
}
45 changes: 20 additions & 25 deletions src/components/assets/software/SoftwareAssetInfoTable.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
import { Component } from "react";
import { SoftwareAsset } from "./SoftwareAsset";
import { HardwareAsset } from "../hardware/HardwareAsset";
import { Link } from "react-router-dom";
import PillButton from "../../PillButton";

class SoftwareAssetInfoTable extends Component<{ asset: SoftwareAsset }, { isLoaded: boolean, data?: HardwareAsset }> {
private date: Date;
constructor(props: any) {
super(props);
this.state = {
isLoaded: false,
data: undefined
};
this.date = new Date(this.props.asset.parent_hardware?.date ?? new Date().toISOString());
this.render = this.render.bind(this);
}

componentDidMount() {
if (this.props.asset.parent_hardware?.id) {
fetch(`http://127.0.0.1:3001/api/assets/hardware/${this.props.asset.parent_hardware?.id.toString()}`)
.then((res) => res.json())
.then((res: any) => {
if (res) {
const parent = new HardwareAsset(res);
this.setState({ isLoaded: true, data: parent });
}
});
}
}

getLinkDate() {
const timestring = [
this.date.getHours().toString().padStart(2, '0'),
this.date.getMinutes().toString().padStart(2, '0')
].join(':');
return `${this.date.toLocaleDateString()} (${timestring})`
console.log(this.props.asset);
fetch(`http://127.0.0.1:3001/api/asset-link/software/${this.props.asset._id.toString()}`)
.then((res) => res.json())
.then((res: any) => {
this.setState({ isLoaded: true, data: new HardwareAsset(res) });
});
}

render() {
Expand All @@ -55,12 +42,20 @@ class SoftwareAssetInfoTable extends Component<{ asset: SoftwareAsset }, { isLoa
<td>{this.props.asset.version ?? '-'}</td>
</tr>
<tr>
<th><i className="fa fa-wifi" /> Parent Hardware</th>
<td><Link to={`/assets/${this.state.data?._id}`}>{this.state.data?.name}</Link> (IP: <code>{this.state.data?.ip}</code>)</td>
<th><i className="fa fa-exclamation-circle" /> Risk Level</th>
<td><PillButton label={this.props.asset?.risk_level ?? 'N/A'} /></td>
</tr>
<tr>
<th><i className="fa fa-calendar" /> Created At</th>
<td>{(new Date(this.props.asset?.created_at).toLocaleDateString() + ` (${new Date(this.props.asset.created_at).toLocaleTimeString()})`) ?? 'Loading...'}</td>
</tr>
{/* <tr>
<th><i className="fa fa-user" /> Created By</th>
<td>{(new Date(this.props.asset?.created_at).toLocaleDateString() + ` (${new Date(this.props.asset.created_at).toLocaleTimeString()})`) ?? 'Loading...'}</td>
</tr> */}
<tr>
<th><i className="fa fa-plus-circle" /> Created At</th>
<td>{this.getLinkDate()}</td>
<th><i className="fa fa-calendar" /> Last Edited At</th>
<td>{(new Date(this.props.asset?.last_edit_at).toLocaleDateString() + ` (${new Date(this.props.asset.last_edit_at).toLocaleTimeString()})`) ?? 'Loading...'}</td>
</tr>
</table>
)
Expand Down
Loading

0 comments on commit 855dca5

Please sign in to comment.