From d9e65dceb38cdb8dc4e464d388755f9456620566 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sun, 16 May 2021 17:07:58 +0200 Subject: ui: Restructure OpenDC frontend This change updates the structure of the OpenDC frontend in order to improve the maintainability of the frontend. --- opendc-web/opendc-web-ui/src/redux/sagas/index.js | 80 ++++++ .../opendc-web-ui/src/redux/sagas/objects.js | 229 +++++++++++++++ .../opendc-web-ui/src/redux/sagas/portfolios.js | 131 +++++++++ .../opendc-web-ui/src/redux/sagas/prefabs.js | 15 + .../opendc-web-ui/src/redux/sagas/profile.js | 12 + .../opendc-web-ui/src/redux/sagas/projects.js | 48 ++++ .../opendc-web-ui/src/redux/sagas/scenarios.js | 65 +++++ .../opendc-web-ui/src/redux/sagas/topology.js | 311 +++++++++++++++++++++ opendc-web/opendc-web-ui/src/redux/sagas/users.js | 44 +++ 9 files changed, 935 insertions(+) create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/index.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/objects.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/profile.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/projects.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/topology.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/users.js (limited to 'opendc-web/opendc-web-ui/src/redux/sagas') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js new file mode 100644 index 00000000..6332b2fb --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -0,0 +1,80 @@ +import { takeEvery } from 'redux-saga/effects' +import { LOG_IN } from '../actions/auth' +import { ADD_PORTFOLIO, DELETE_PORTFOLIO, OPEN_PORTFOLIO_SUCCEEDED, UPDATE_PORTFOLIO } from '../actions/portfolios' +import { ADD_PROJECT, DELETE_PROJECT, OPEN_PROJECT_SUCCEEDED } from '../actions/projects' +import { + ADD_TILE, + CANCEL_NEW_ROOM_CONSTRUCTION, + DELETE_TILE, + START_NEW_ROOM_CONSTRUCTION, +} from '../actions/topology/building' +import { ADD_UNIT, DELETE_MACHINE, DELETE_UNIT } from '../actions/topology/machine' +import { ADD_MACHINE, DELETE_RACK, EDIT_RACK_NAME } from '../actions/topology/rack' +import { ADD_RACK_TO_TILE, DELETE_ROOM, EDIT_ROOM_NAME } from '../actions/topology/room' +import { DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER } from '../actions/users' +import { onAddPortfolio, onDeletePortfolio, onOpenPortfolioSucceeded, onUpdatePortfolio } from './portfolios' +import { onDeleteCurrentUser } from './profile' +import { onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects' +import { + onAddMachine, + onAddRackToTile, + onAddTile, + onAddTopology, + onAddUnit, + onCancelNewRoomConstruction, + onDeleteMachine, + onDeleteRack, + onDeleteRoom, + onDeleteTile, + onDeleteTopology, + onDeleteUnit, + onEditRackName, + onEditRoomName, + onStartNewRoomConstruction, +} from './topology' +import { onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser } from './users' +import { ADD_TOPOLOGY, DELETE_TOPOLOGY } from '../actions/topologies' +import { ADD_SCENARIO, DELETE_SCENARIO, OPEN_SCENARIO_SUCCEEDED, UPDATE_SCENARIO } from '../actions/scenarios' +import { onAddScenario, onDeleteScenario, onOpenScenarioSucceeded, onUpdateScenario } from './scenarios' +import { onAddPrefab } from './prefabs' +import { ADD_PREFAB } from '../actions/prefabs' + +export default function* rootSaga() { + yield takeEvery(LOG_IN, onFetchLoggedInUser) + + yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser) + yield takeEvery(ADD_PROJECT, onProjectAdd) + yield takeEvery(DELETE_PROJECT, onProjectDelete) + + yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser) + + yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) + yield takeEvery(OPEN_PORTFOLIO_SUCCEEDED, onOpenPortfolioSucceeded) + yield takeEvery(OPEN_SCENARIO_SUCCEEDED, onOpenScenarioSucceeded) + + yield takeEvery(ADD_TOPOLOGY, onAddTopology) + yield takeEvery(DELETE_TOPOLOGY, onDeleteTopology) + yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction) + yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction) + yield takeEvery(ADD_TILE, onAddTile) + yield takeEvery(DELETE_TILE, onDeleteTile) + yield takeEvery(EDIT_ROOM_NAME, onEditRoomName) + yield takeEvery(DELETE_ROOM, onDeleteRoom) + yield takeEvery(EDIT_RACK_NAME, onEditRackName) + yield takeEvery(DELETE_RACK, onDeleteRack) + yield takeEvery(ADD_RACK_TO_TILE, onAddRackToTile) + yield takeEvery(ADD_MACHINE, onAddMachine) + yield takeEvery(DELETE_MACHINE, onDeleteMachine) + yield takeEvery(ADD_UNIT, onAddUnit) + yield takeEvery(DELETE_UNIT, onDeleteUnit) + + yield takeEvery(ADD_PORTFOLIO, onAddPortfolio) + yield takeEvery(UPDATE_PORTFOLIO, onUpdatePortfolio) + yield takeEvery(DELETE_PORTFOLIO, onDeletePortfolio) + + yield takeEvery(ADD_SCENARIO, onAddScenario) + yield takeEvery(UPDATE_SCENARIO, onUpdateScenario) + yield takeEvery(DELETE_SCENARIO, onDeleteScenario) + + yield takeEvery(ADD_PREFAB, onAddPrefab) +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js new file mode 100644 index 00000000..82dbb935 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -0,0 +1,229 @@ +import { call, put, select } from 'redux-saga/effects' +import { addToStore } from '../actions/objects' +import { getAllSchedulers } from '../../api/schedulers' +import { getProject } from '../../api/projects' +import { getAllTraces } from '../../api/traces' +import { getUser } from '../../api/users' +import { getTopology, updateTopology } from '../../api/topologies' +import { uuid } from 'uuidv4' + +export const OBJECT_SELECTORS = { + project: (state) => state.objects.project, + user: (state) => state.objects.user, + authorization: (state) => state.objects.authorization, + portfolio: (state) => state.objects.portfolio, + scenario: (state) => state.objects.scenario, + cpu: (state) => state.objects.cpu, + gpu: (state) => state.objects.gpu, + memory: (state) => state.objects.memory, + storage: (state) => state.objects.storage, + machine: (state) => state.objects.machine, + rack: (state) => state.objects.rack, + tile: (state) => state.objects.tile, + room: (state) => state.objects.room, + topology: (state) => state.objects.topology, +} + +function* fetchAndStoreObject(objectType, id, apiCall) { + const objectStore = yield select(OBJECT_SELECTORS[objectType]) + let object = objectStore[id] + if (!object) { + object = yield apiCall + yield put(addToStore(objectType, object)) + } + return object +} + +function* fetchAndStoreObjects(objectType, apiCall) { + const objects = yield apiCall + for (let object of objects) { + yield put(addToStore(objectType, object)) + } + return objects +} + +export const fetchAndStoreProject = (id) => fetchAndStoreObject('project', id, call(getProject, id)) + +export const fetchAndStoreUser = (id) => fetchAndStoreObject('user', id, call(getUser, id)) + +export const fetchAndStoreTopology = function* (id) { + const topologyStore = yield select(OBJECT_SELECTORS['topology']) + const roomStore = yield select(OBJECT_SELECTORS['room']) + const tileStore = yield select(OBJECT_SELECTORS['tile']) + const rackStore = yield select(OBJECT_SELECTORS['rack']) + const machineStore = yield select(OBJECT_SELECTORS['machine']) + + let topology = topologyStore[id] + if (!topology) { + const fullTopology = yield call(getTopology, id) + + for (let roomIdx in fullTopology.rooms) { + const fullRoom = fullTopology.rooms[roomIdx] + + generateIdIfNotPresent(fullRoom) + + if (!roomStore[fullRoom._id]) { + for (let tileIdx in fullRoom.tiles) { + const fullTile = fullRoom.tiles[tileIdx] + + generateIdIfNotPresent(fullTile) + + if (!tileStore[fullTile._id]) { + if (fullTile.rack) { + const fullRack = fullTile.rack + + generateIdIfNotPresent(fullRack) + + if (!rackStore[fullRack._id]) { + for (let machineIdx in fullRack.machines) { + const fullMachine = fullRack.machines[machineIdx] + + generateIdIfNotPresent(fullMachine) + + if (!machineStore[fullMachine._id]) { + let machine = (({ _id, position, cpus, gpus, memories, storages }) => ({ + _id, + rackId: fullRack._id, + position, + cpuIds: cpus.map((u) => u._id), + gpuIds: gpus.map((u) => u._id), + memoryIds: memories.map((u) => u._id), + storageIds: storages.map((u) => u._id), + }))(fullMachine) + yield put(addToStore('machine', machine)) + } + } + + const filledSlots = new Array(fullRack.capacity).fill(null) + fullRack.machines.forEach( + (machine) => (filledSlots[machine.position - 1] = machine._id) + ) + let rack = (({ _id, name, capacity, powerCapacityW }) => ({ + _id, + name, + capacity, + powerCapacityW, + machineIds: filledSlots, + }))(fullRack) + yield put(addToStore('rack', rack)) + } + } + + let tile = (({ _id, positionX, positionY, rack }) => ({ + _id, + roomId: fullRoom._id, + positionX, + positionY, + rackId: rack ? rack._id : undefined, + }))(fullTile) + yield put(addToStore('tile', tile)) + } + } + + let room = (({ _id, name, tiles }) => ({ _id, name, tileIds: tiles.map((t) => t._id) }))(fullRoom) + yield put(addToStore('room', room)) + } + } + + topology = (({ _id, name, rooms }) => ({ _id, name, roomIds: rooms.map((r) => r._id) }))(fullTopology) + yield put(addToStore('topology', topology)) + + // TODO consider pushing the IDs + } + + return topology +} + +const generateIdIfNotPresent = (obj) => { + if (!obj._id) { + obj._id = uuid() + } +} + +export const updateTopologyOnServer = function* (id) { + const topology = yield getTopologyAsObject(id, true) + + yield call(updateTopology, topology) +} + +export const getTopologyAsObject = function* (id, keepIds) { + const topologyStore = yield select(OBJECT_SELECTORS['topology']) + const rooms = yield getAllRooms(topologyStore[id].roomIds, keepIds) + return { + _id: keepIds ? id : undefined, + name: topologyStore[id].name, + rooms: rooms, + } +} + +export const getAllRooms = function* (roomIds, keepIds) { + const roomStore = yield select(OBJECT_SELECTORS['room']) + + let rooms = [] + + for (let id of roomIds) { + let tiles = yield getAllRoomTiles(roomStore[id], keepIds) + rooms.push({ + _id: keepIds ? id : undefined, + name: roomStore[id].name, + tiles: tiles, + }) + } + return rooms +} + +export const getAllRoomTiles = function* (roomStore, keepIds) { + let tiles = [] + + for (let id of roomStore.tileIds) { + tiles.push(yield getTileById(id, keepIds)) + } + return tiles +} + +export const getTileById = function* (id, keepIds) { + const tileStore = yield select(OBJECT_SELECTORS['tile']) + return { + _id: keepIds ? id : undefined, + positionX: tileStore[id].positionX, + positionY: tileStore[id].positionY, + rack: !tileStore[id].rackId ? undefined : yield getRackById(tileStore[id].rackId, keepIds), + } +} + +export const getRackById = function* (id, keepIds) { + const rackStore = yield select(OBJECT_SELECTORS['rack']) + const machineStore = yield select(OBJECT_SELECTORS['machine']) + const cpuStore = yield select(OBJECT_SELECTORS['cpu']) + const gpuStore = yield select(OBJECT_SELECTORS['gpu']) + const memoryStore = yield select(OBJECT_SELECTORS['memory']) + const storageStore = yield select(OBJECT_SELECTORS['storage']) + + return { + _id: keepIds ? rackStore[id]._id : undefined, + name: rackStore[id].name, + capacity: rackStore[id].capacity, + powerCapacityW: rackStore[id].powerCapacityW, + machines: rackStore[id].machineIds + .filter((m) => m !== null) + .map((machineId) => ({ + _id: keepIds ? machineId : undefined, + position: machineStore[machineId].position, + cpus: machineStore[machineId].cpuIds.map((id) => cpuStore[id]), + gpus: machineStore[machineId].gpuIds.map((id) => gpuStore[id]), + memories: machineStore[machineId].memoryIds.map((id) => memoryStore[id]), + storages: machineStore[machineId].storageIds.map((id) => storageStore[id]), + })), + } +} + +export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(getAllTraces)) + +export const fetchAndStoreAllSchedulers = function* () { + const objects = yield call(getAllSchedulers) + for (let object of objects) { + object._id = object.name + yield put(addToStore('scheduler', object)) + } + return objects +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js new file mode 100644 index 00000000..8ddf888d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js @@ -0,0 +1,131 @@ +import { call, put, select, delay } from 'redux-saga/effects' +import { addPropToStoreObject, addToStore } from '../actions/objects' +import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../../api/portfolios' +import { getProject } from '../../api/projects' +import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' +import { fetchAndStoreAllTopologiesOfProject } from './topology' +import { getScenario } from '../../api/scenarios' + +export function* onOpenPortfolioSucceeded(action) { + try { + const project = yield call(getProject, action.projectId) + yield put(addToStore('project', project)) + yield fetchAndStoreAllTopologiesOfProject(project._id) + yield fetchPortfoliosOfProject() + yield fetchAndStoreAllSchedulers() + yield fetchAndStoreAllTraces() + + yield watchForPortfolioResults() + } catch (error) { + console.error(error) + } +} + +export function* watchForPortfolioResults() { + try { + const currentPortfolioId = yield select((state) => state.currentPortfolioId) + let unfinishedScenarios = yield getCurrentUnfinishedScenarios() + + while (unfinishedScenarios.length > 0) { + yield delay(3000) + yield fetchPortfolioWithScenarios(currentPortfolioId) + unfinishedScenarios = yield getCurrentUnfinishedScenarios() + } + } catch (error) { + console.error(error) + } +} + +export function* getCurrentUnfinishedScenarios() { + try { + const currentPortfolioId = yield select((state) => state.currentPortfolioId) + const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) + const scenarioObjects = yield select((state) => state.objects.scenario) + const scenarios = scenarioIds.map((s) => scenarioObjects[s]) + return scenarios.filter((s) => !s || s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING') + } catch (error) { + console.error(error) + } +} + +export function* fetchPortfoliosOfProject() { + try { + const currentProjectId = yield select((state) => state.currentProjectId) + const currentProject = yield select((state) => state.objects.project[currentProjectId]) + + yield fetchAndStoreAllSchedulers() + yield fetchAndStoreAllTraces() + + for (let i in currentProject.portfolioIds) { + yield fetchPortfolioWithScenarios(currentProject.portfolioIds[i]) + } + } catch (error) { + console.error(error) + } +} + +export function* fetchPortfolioWithScenarios(portfolioId) { + try { + const portfolio = yield call(getPortfolio, portfolioId) + yield put(addToStore('portfolio', portfolio)) + + for (let i in portfolio.scenarioIds) { + const scenario = yield call(getScenario, portfolio.scenarioIds[i]) + yield put(addToStore('scenario', scenario)) + } + return portfolio + } catch (error) { + console.error(error) + } +} + +export function* onAddPortfolio(action) { + try { + const currentProjectId = yield select((state) => state.currentProjectId) + + const portfolio = yield call( + addPortfolio, + currentProjectId, + Object.assign({}, action.portfolio, { + projectId: currentProjectId, + scenarioIds: [], + }) + ) + yield put(addToStore('portfolio', portfolio)) + + const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) + yield put( + addPropToStoreObject('project', currentProjectId, { + portfolioIds: portfolioIds.concat([portfolio._id]), + }) + ) + } catch (error) { + console.error(error) + } +} + +export function* onUpdatePortfolio(action) { + try { + const portfolio = yield call(updatePortfolio, action.portfolio._id, action.portfolio) + yield put(addToStore('portfolio', portfolio)) + } catch (error) { + console.error(error) + } +} + +export function* onDeletePortfolio(action) { + try { + yield call(deletePortfolio, action.id) + + const currentProjectId = yield select((state) => state.currentProjectId) + const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) + + yield put( + addPropToStoreObject('project', currentProjectId, { + portfolioIds: portfolioIds.filter((id) => id !== action.id), + }) + ) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js new file mode 100644 index 00000000..3158a219 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js @@ -0,0 +1,15 @@ +import { call, put, select } from 'redux-saga/effects' +import { addToStore } from '../actions/objects' +import { addPrefab } from '../../api/prefabs' +import { getRackById } from './objects' + +export function* onAddPrefab(action) { + try { + const currentRackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) + const currentRackJson = yield getRackById(currentRackId, false) + const prefab = yield call(addPrefab, { name: action.name, rack: currentRackJson }) + yield put(addToStore('prefab', prefab)) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/profile.js b/opendc-web/opendc-web-ui/src/redux/sagas/profile.js new file mode 100644 index 00000000..e187b765 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/profile.js @@ -0,0 +1,12 @@ +import { call, put } from 'redux-saga/effects' +import { deleteCurrentUserSucceeded } from '../actions/users' +import { deleteUser } from '../../api/users' + +export function* onDeleteCurrentUser(action) { + try { + yield call(deleteUser, action.userId) + yield put(deleteCurrentUserSucceeded()) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js new file mode 100644 index 00000000..ecd9a7c9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js @@ -0,0 +1,48 @@ +import { call, put } from 'redux-saga/effects' +import { addToStore } from '../actions/objects' +import { addProjectSucceeded, deleteProjectSucceeded } from '../actions/projects' +import { addProject, deleteProject, getProject } from '../../api/projects' +import { fetchAndStoreAllTopologiesOfProject } from './topology' +import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' +import { fetchPortfoliosOfProject } from './portfolios' + +export function* onOpenProjectSucceeded(action) { + try { + const project = yield call(getProject, action.id) + yield put(addToStore('project', project)) + + yield fetchAndStoreAllTopologiesOfProject(action.id, true) + yield fetchPortfoliosOfProject() + yield fetchAndStoreAllSchedulers() + yield fetchAndStoreAllTraces() + } catch (error) { + console.error(error) + } +} + +export function* onProjectAdd(action) { + try { + const project = yield call(addProject, { name: action.name }) + yield put(addToStore('project', project)) + + const authorization = { + projectId: project._id, + userId: action.userId, + authorizationLevel: 'OWN', + project, + } + yield put(addToStore('authorization', authorization)) + yield put(addProjectSucceeded([authorization.userId, authorization.projectId])) + } catch (error) { + console.error(error) + } +} + +export function* onProjectDelete(action) { + try { + yield call(deleteProject, action.id) + yield put(deleteProjectSucceeded(action.id)) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js new file mode 100644 index 00000000..a5463fa6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js @@ -0,0 +1,65 @@ +import { call, put, select } from 'redux-saga/effects' +import { addPropToStoreObject, addToStore } from '../actions/objects' +import { getProject } from '../../api/projects' +import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' +import { fetchAndStoreAllTopologiesOfProject } from './topology' +import { addScenario, deleteScenario, updateScenario } from '../../api/scenarios' +import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfolios' + +export function* onOpenScenarioSucceeded(action) { + try { + const project = yield call(getProject, action.projectId) + yield put(addToStore('project', project)) + yield fetchAndStoreAllTopologiesOfProject(project._id) + yield fetchAndStoreAllSchedulers() + yield fetchAndStoreAllTraces() + yield fetchPortfolioWithScenarios(action.portfolioId) + + // TODO Fetch scenario-specific metrics + } catch (error) { + console.error(error) + } +} + +export function* onAddScenario(action) { + try { + const scenario = yield call(addScenario, action.scenario.portfolioId, action.scenario) + yield put(addToStore('scenario', scenario)) + + const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds) + yield put( + addPropToStoreObject('portfolio', action.scenario.portfolioId, { + scenarioIds: scenarioIds.concat([scenario._id]), + }) + ) + yield watchForPortfolioResults() + } catch (error) { + console.error(error) + } +} + +export function* onUpdateScenario(action) { + try { + const scenario = yield call(updateScenario, action.scenario._id, action.scenario) + yield put(addToStore('scenario', scenario)) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteScenario(action) { + try { + yield call(deleteScenario, action.id) + + const currentPortfolioId = yield select((state) => state.currentPortfolioId) + const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) + + yield put( + addPropToStoreObject('scenario', currentPortfolioId, { + scenarioIds: scenarioIds.filter((id) => id !== action.id), + }) + ) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js new file mode 100644 index 00000000..65f97cc9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -0,0 +1,311 @@ +import { call, put, select } from 'redux-saga/effects' +import { goDownOneInteractionLevel } from '../actions/interaction-level' +import { + addIdToStoreObjectListProp, + addPropToStoreObject, + addToStore, + removeIdFromStoreObjectListProp, +} from '../actions/objects' +import { + cancelNewRoomConstructionSucceeded, + setCurrentTopology, + startNewRoomConstructionSucceeded, +} from '../actions/topology/building' +import { + DEFAULT_RACK_POWER_CAPACITY, + DEFAULT_RACK_SLOT_CAPACITY, + MAX_NUM_UNITS_PER_MACHINE, +} from '../../components/app/map/MapConstants' +import { fetchAndStoreTopology, getTopologyAsObject, updateTopologyOnServer } from './objects' +import { uuid } from 'uuidv4' +import { addTopology, deleteTopology } from '../../api/topologies' + +export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { + try { + const project = yield select((state) => state.objects.project[projectId]) + + for (let i in project.topologyIds) { + yield fetchAndStoreTopology(project.topologyIds[i]) + } + + if (setTopology) { + yield put(setCurrentTopology(project.topologyIds[0])) + } + } catch (error) { + console.error(error) + } +} + +export function* onAddTopology(action) { + try { + const currentProjectId = yield select((state) => state.currentProjectId) + + let topologyToBeCreated + if (action.duplicateId) { + topologyToBeCreated = yield getTopologyAsObject(action.duplicateId, false) + topologyToBeCreated = Object.assign({}, topologyToBeCreated, { + name: action.name, + }) + } else { + topologyToBeCreated = { name: action.name, rooms: [] } + } + + const topology = yield call( + addTopology, + Object.assign({}, topologyToBeCreated, { + projectId: currentProjectId, + }) + ) + yield fetchAndStoreTopology(topology._id) + + const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) + yield put( + addPropToStoreObject('project', currentProjectId, { + topologyIds: topologyIds.concat([topology._id]), + }) + ) + yield put(setCurrentTopology(topology._id)) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteTopology(action) { + try { + const currentProjectId = yield select((state) => state.currentProjectId) + const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) + const currentTopologyId = yield select((state) => state.currentTopologyId) + if (currentTopologyId === action.id) { + yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) + } + + yield call(deleteTopology, action.id) + + yield put( + addPropToStoreObject('project', currentProjectId, { + topologyIds: topologyIds.filter((id) => id !== action.id), + }) + ) + } catch (error) { + console.error(error) + } +} + +export function* onStartNewRoomConstruction() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const room = { + _id: uuid(), + name: 'Room', + topologyId, + tileIds: [], + } + yield put(addToStore('room', room)) + yield put(addIdToStoreObjectListProp('topology', topologyId, 'roomIds', room._id)) + yield updateTopologyOnServer(topologyId) + yield put(startNewRoomConstructionSucceeded(room._id)) + } catch (error) { + console.error(error) + } +} + +export function* onCancelNewRoomConstruction() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.construction.currentRoomInConstruction) + yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) + // TODO remove room from store, too + yield updateTopologyOnServer(topologyId) + yield put(cancelNewRoomConstructionSucceeded()) + } catch (error) { + console.error(error) + } +} + +export function* onAddTile(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.construction.currentRoomInConstruction) + const tile = { + _id: uuid(), + roomId, + positionX: action.positionX, + positionY: action.positionY, + } + yield put(addToStore('tile', tile)) + yield put(addIdToStoreObjectListProp('room', roomId, 'tileIds', tile._id)) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteTile(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.construction.currentRoomInConstruction) + yield put(removeIdFromStoreObjectListProp('room', roomId, 'tileIds', action.tileId)) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onEditRoomName(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.interactionLevel.roomId) + const room = Object.assign({}, yield select((state) => state.objects.room[roomId])) + room.name = action.name + yield put(addPropToStoreObject('room', roomId, { name: action.name })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteRoom() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.interactionLevel.roomId) + yield put(goDownOneInteractionLevel()) + yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onEditRackName(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) + const rack = Object.assign({}, yield select((state) => state.objects.rack[rackId])) + rack.name = action.name + yield put(addPropToStoreObject('rack', rackId, { name: action.name })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteRack() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const tileId = yield select((state) => state.interactionLevel.tileId) + yield put(goDownOneInteractionLevel()) + yield put(addPropToStoreObject('tile', tileId, { rackId: undefined })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onAddRackToTile(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const rack = { + _id: uuid(), + name: 'Rack', + capacity: DEFAULT_RACK_SLOT_CAPACITY, + powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, + } + rack.machineIds = new Array(rack.capacity).fill(null) + yield put(addToStore('rack', rack)) + yield put(addPropToStoreObject('tile', action.tileId, { rackId: rack._id })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onAddMachine(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) + const rack = yield select((state) => state.objects.rack[rackId]) + + const machine = { + _id: uuid(), + rackId, + position: action.position, + cpuIds: [], + gpuIds: [], + memoryIds: [], + storageIds: [], + } + yield put(addToStore('machine', machine)) + + const machineIds = [...rack.machineIds] + machineIds[machine.position - 1] = machine._id + yield put(addPropToStoreObject('rack', rackId, { machineIds })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteMachine() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const tileId = yield select((state) => state.interactionLevel.tileId) + const position = yield select((state) => state.interactionLevel.position) + const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rackId]) + const machineIds = [...rack.machineIds] + machineIds[position - 1] = null + yield put(goDownOneInteractionLevel()) + yield put(addPropToStoreObject('rack', rack._id, { machineIds })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onAddUnit(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const tileId = yield select((state) => state.interactionLevel.tileId) + const position = yield select((state) => state.interactionLevel.position) + const machine = yield select( + (state) => + state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] + ) + + if (machine[action.unitType + 'Ids'].length >= MAX_NUM_UNITS_PER_MACHINE) { + return + } + + const units = [...machine[action.unitType + 'Ids'], action.id] + yield put( + addPropToStoreObject('machine', machine._id, { + [action.unitType + 'Ids']: units, + }) + ) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteUnit(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const tileId = yield select((state) => state.interactionLevel.tileId) + const position = yield select((state) => state.interactionLevel.position) + const machine = yield select( + (state) => + state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] + ) + const unitIds = machine[action.unitType + 'Ids'].slice() + unitIds.splice(action.index, 1) + + yield put( + addPropToStoreObject('machine', machine._id, { + [action.unitType + 'Ids']: unitIds, + }) + ) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/users.js b/opendc-web/opendc-web-ui/src/redux/sagas/users.js new file mode 100644 index 00000000..88c424b5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/users.js @@ -0,0 +1,44 @@ +import { call, put } from 'redux-saga/effects' +import { logInSucceeded } from '../actions/auth' +import { addToStore } from '../actions/objects' +import { fetchAuthorizationsOfCurrentUserSucceeded } from '../actions/users' +import { performTokenSignIn } from '../../api/token-signin' +import { addUser } from '../../api/users' +import { saveAuthLocalStorage } from '../../auth' +import { fetchAndStoreProject, fetchAndStoreUser } from './objects' + +export function* onFetchLoggedInUser(action) { + try { + const tokenResponse = yield call(performTokenSignIn, action.payload.authToken) + + let userId = tokenResponse.userId + + if (tokenResponse.isNewUser) { + saveAuthLocalStorage({ authToken: action.payload.authToken }) + const newUser = yield call(addUser, action.payload) + userId = newUser._id + } + + yield put(logInSucceeded(Object.assign({ userId }, action.payload))) + } catch (error) { + console.error(error) + } +} + +export function* onFetchAuthorizationsOfCurrentUser(action) { + try { + const user = yield call(fetchAndStoreUser, action.userId) + + for (const authorization of user.authorizations) { + authorization.userId = action.userId + yield put(addToStore('authorization', authorization)) + yield fetchAndStoreProject(authorization.projectId) + } + + const authorizationIds = user.authorizations.map((authorization) => [action.userId, authorization.projectId]) + + yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds)) + } catch (error) { + console.error(error) + } +} -- cgit v1.2.3 From a6865b86cc8d710374fc0b6cfcbd2b863f1942a9 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sun, 16 May 2021 23:18:02 +0200 Subject: ui: Migrate to Auth0 as Identity Provider This change updates the frontend codebase to move away from the Google login and instead use Auth0 as generic Identity Provider. This allows users to login with other accounts as well. Since Auth0 has a free tier, users can experiment themselves with OpenDC locally without having to pay for the login functionality. The code has been written so that we should be able to migrate away from Auth0 once it is not a suitable Identity Provider for OpenDC anymore. --- opendc-web/opendc-web-ui/src/redux/sagas/index.js | 14 ++----- .../opendc-web-ui/src/redux/sagas/objects.js | 25 +++++++----- .../opendc-web-ui/src/redux/sagas/portfolios.js | 19 ++++++---- .../opendc-web-ui/src/redux/sagas/prefabs.js | 5 ++- .../opendc-web-ui/src/redux/sagas/profile.js | 12 ------ .../opendc-web-ui/src/redux/sagas/projects.js | 35 +++++++++-------- .../opendc-web-ui/src/redux/sagas/scenarios.js | 14 ++++--- .../opendc-web-ui/src/redux/sagas/topology.js | 7 +++- opendc-web/opendc-web-ui/src/redux/sagas/users.js | 44 ---------------------- 9 files changed, 67 insertions(+), 108 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/profile.js delete mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/users.js (limited to 'opendc-web/opendc-web-ui/src/redux/sagas') diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js index 6332b2fb..a8f44843 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -1,7 +1,6 @@ import { takeEvery } from 'redux-saga/effects' -import { LOG_IN } from '../actions/auth' import { ADD_PORTFOLIO, DELETE_PORTFOLIO, OPEN_PORTFOLIO_SUCCEEDED, UPDATE_PORTFOLIO } from '../actions/portfolios' -import { ADD_PROJECT, DELETE_PROJECT, OPEN_PROJECT_SUCCEEDED } from '../actions/projects' +import { ADD_PROJECT, DELETE_PROJECT, FETCH_PROJECTS, OPEN_PROJECT_SUCCEEDED } from '../actions/projects' import { ADD_TILE, CANCEL_NEW_ROOM_CONSTRUCTION, @@ -11,10 +10,8 @@ import { import { ADD_UNIT, DELETE_MACHINE, DELETE_UNIT } from '../actions/topology/machine' import { ADD_MACHINE, DELETE_RACK, EDIT_RACK_NAME } from '../actions/topology/rack' import { ADD_RACK_TO_TILE, DELETE_ROOM, EDIT_ROOM_NAME } from '../actions/topology/room' -import { DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER } from '../actions/users' import { onAddPortfolio, onDeletePortfolio, onOpenPortfolioSucceeded, onUpdatePortfolio } from './portfolios' -import { onDeleteCurrentUser } from './profile' -import { onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects' +import { onFetchProjects, onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects' import { onAddMachine, onAddRackToTile, @@ -32,7 +29,6 @@ import { onEditRoomName, onStartNewRoomConstruction, } from './topology' -import { onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser } from './users' import { ADD_TOPOLOGY, DELETE_TOPOLOGY } from '../actions/topologies' import { ADD_SCENARIO, DELETE_SCENARIO, OPEN_SCENARIO_SUCCEEDED, UPDATE_SCENARIO } from '../actions/scenarios' import { onAddScenario, onDeleteScenario, onOpenScenarioSucceeded, onUpdateScenario } from './scenarios' @@ -40,14 +36,10 @@ import { onAddPrefab } from './prefabs' import { ADD_PREFAB } from '../actions/prefabs' export default function* rootSaga() { - yield takeEvery(LOG_IN, onFetchLoggedInUser) - - yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser) + yield takeEvery(FETCH_PROJECTS, onFetchProjects) yield takeEvery(ADD_PROJECT, onProjectAdd) yield takeEvery(DELETE_PROJECT, onProjectDelete) - yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser) - yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) yield takeEvery(OPEN_PORTFOLIO_SUCCEEDED, onOpenPortfolioSucceeded) yield takeEvery(OPEN_SCENARIO_SUCCEEDED, onOpenScenarioSucceeded) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js index 82dbb935..e5fd092d 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -1,9 +1,8 @@ -import { call, put, select } from 'redux-saga/effects' +import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' import { getAllSchedulers } from '../../api/schedulers' import { getProject } from '../../api/projects' import { getAllTraces } from '../../api/traces' -import { getUser } from '../../api/users' import { getTopology, updateTopology } from '../../api/topologies' import { uuid } from 'uuidv4' @@ -42,9 +41,10 @@ function* fetchAndStoreObjects(objectType, apiCall) { return objects } -export const fetchAndStoreProject = (id) => fetchAndStoreObject('project', id, call(getProject, id)) - -export const fetchAndStoreUser = (id) => fetchAndStoreObject('user', id, call(getUser, id)) +export const fetchAndStoreProject = function* (id) { + const auth = yield getContext('auth') + return yield fetchAndStoreObject('project', id, call(getProject, auth, id)) +} export const fetchAndStoreTopology = function* (id) { const topologyStore = yield select(OBJECT_SELECTORS['topology']) @@ -52,10 +52,11 @@ export const fetchAndStoreTopology = function* (id) { const tileStore = yield select(OBJECT_SELECTORS['tile']) const rackStore = yield select(OBJECT_SELECTORS['rack']) const machineStore = yield select(OBJECT_SELECTORS['machine']) + const auth = yield getContext('auth') let topology = topologyStore[id] if (!topology) { - const fullTopology = yield call(getTopology, id) + const fullTopology = yield call(getTopology, auth, id) for (let roomIdx in fullTopology.rooms) { const fullRoom = fullTopology.rooms[roomIdx] @@ -142,8 +143,8 @@ const generateIdIfNotPresent = (obj) => { export const updateTopologyOnServer = function* (id) { const topology = yield getTopologyAsObject(id, true) - - yield call(updateTopology, topology) + const auth = yield getContext('auth') + yield call(updateTopology, auth, topology) } export const getTopologyAsObject = function* (id, keepIds) { @@ -217,10 +218,14 @@ export const getRackById = function* (id, keepIds) { } } -export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(getAllTraces)) +export const fetchAndStoreAllTraces = function* () { + const auth = yield getContext('auth') + return yield fetchAndStoreObjects('trace', call(getAllTraces, auth)) +} export const fetchAndStoreAllSchedulers = function* () { - const objects = yield call(getAllSchedulers) + const auth = yield getContext('auth') + const objects = yield call(getAllSchedulers, auth) for (let object of objects) { object._id = object.name yield put(addToStore('scheduler', object)) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js index 8ddf888d..340cb490 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js @@ -1,4 +1,4 @@ -import { call, put, select, delay } from 'redux-saga/effects' +import { call, put, select, delay, getContext } from 'redux-saga/effects' import { addPropToStoreObject, addToStore } from '../actions/objects' import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../../api/portfolios' import { getProject } from '../../api/projects' @@ -8,7 +8,8 @@ import { getScenario } from '../../api/scenarios' export function* onOpenPortfolioSucceeded(action) { try { - const project = yield call(getProject, action.projectId) + const auth = yield getContext('auth') + const project = yield call(getProject, auth, action.projectId) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(project._id) yield fetchPortfoliosOfProject() @@ -66,11 +67,12 @@ export function* fetchPortfoliosOfProject() { export function* fetchPortfolioWithScenarios(portfolioId) { try { - const portfolio = yield call(getPortfolio, portfolioId) + const auth = yield getContext('auth') + const portfolio = yield call(getPortfolio, auth, portfolioId) yield put(addToStore('portfolio', portfolio)) for (let i in portfolio.scenarioIds) { - const scenario = yield call(getScenario, portfolio.scenarioIds[i]) + const scenario = yield call(getScenario, auth, portfolio.scenarioIds[i]) yield put(addToStore('scenario', scenario)) } return portfolio @@ -82,9 +84,10 @@ export function* fetchPortfolioWithScenarios(portfolioId) { export function* onAddPortfolio(action) { try { const currentProjectId = yield select((state) => state.currentProjectId) - + const auth = yield getContext('auth') const portfolio = yield call( addPortfolio, + auth, currentProjectId, Object.assign({}, action.portfolio, { projectId: currentProjectId, @@ -106,7 +109,8 @@ export function* onAddPortfolio(action) { export function* onUpdatePortfolio(action) { try { - const portfolio = yield call(updatePortfolio, action.portfolio._id, action.portfolio) + const auth = yield getContext('auth') + const portfolio = yield call(updatePortfolio, auth, action.portfolio._id, action.portfolio) yield put(addToStore('portfolio', portfolio)) } catch (error) { console.error(error) @@ -115,7 +119,8 @@ export function* onUpdatePortfolio(action) { export function* onDeletePortfolio(action) { try { - yield call(deletePortfolio, action.id) + const auth = yield getContext('auth') + yield call(deletePortfolio, auth, action.id) const currentProjectId = yield select((state) => state.currentProjectId) const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js index 3158a219..ec679391 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js @@ -1,4 +1,4 @@ -import { call, put, select } from 'redux-saga/effects' +import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' import { addPrefab } from '../../api/prefabs' import { getRackById } from './objects' @@ -7,7 +7,8 @@ export function* onAddPrefab(action) { try { const currentRackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) const currentRackJson = yield getRackById(currentRackId, false) - const prefab = yield call(addPrefab, { name: action.name, rack: currentRackJson }) + const auth = yield getContext('auth') + const prefab = yield call(addPrefab, auth, { name: action.name, rack: currentRackJson }) yield put(addToStore('prefab', prefab)) } catch (error) { console.error(error) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/profile.js b/opendc-web/opendc-web-ui/src/redux/sagas/profile.js deleted file mode 100644 index e187b765..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/profile.js +++ /dev/null @@ -1,12 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { deleteCurrentUserSucceeded } from '../actions/users' -import { deleteUser } from '../../api/users' - -export function* onDeleteCurrentUser(action) { - try { - yield call(deleteUser, action.userId) - yield put(deleteCurrentUserSucceeded()) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js index ecd9a7c9..506df6ed 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js @@ -1,14 +1,15 @@ -import { call, put } from 'redux-saga/effects' +import { call, put, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' -import { addProjectSucceeded, deleteProjectSucceeded } from '../actions/projects' -import { addProject, deleteProject, getProject } from '../../api/projects' +import { addProjectSucceeded, deleteProjectSucceeded, fetchProjectsSucceeded } from '../actions/projects' +import { addProject, deleteProject, getProject, getProjects } from '../../api/projects' import { fetchAndStoreAllTopologiesOfProject } from './topology' import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' import { fetchPortfoliosOfProject } from './portfolios' export function* onOpenProjectSucceeded(action) { try { - const project = yield call(getProject, action.id) + const auth = yield getContext('auth') + const project = yield call(getProject, auth, action.id) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(action.id, true) @@ -22,17 +23,10 @@ export function* onOpenProjectSucceeded(action) { export function* onProjectAdd(action) { try { - const project = yield call(addProject, { name: action.name }) + const auth = yield getContext('auth') + const project = yield call(addProject, auth, { name: action.name }) yield put(addToStore('project', project)) - - const authorization = { - projectId: project._id, - userId: action.userId, - authorizationLevel: 'OWN', - project, - } - yield put(addToStore('authorization', authorization)) - yield put(addProjectSucceeded([authorization.userId, authorization.projectId])) + yield put(addProjectSucceeded(project)) } catch (error) { console.error(error) } @@ -40,9 +34,20 @@ export function* onProjectAdd(action) { export function* onProjectDelete(action) { try { - yield call(deleteProject, action.id) + const auth = yield getContext('auth') + yield call(deleteProject, auth, action.id) yield put(deleteProjectSucceeded(action.id)) } catch (error) { console.error(error) } } + +export function* onFetchProjects(action) { + try { + const auth = yield getContext('auth') + const projects = yield call(getProjects, auth) + yield put(fetchProjectsSucceeded(projects)) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js index a5463fa6..bdb7c45d 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js @@ -1,4 +1,4 @@ -import { call, put, select } from 'redux-saga/effects' +import { call, put, select, getContext } from 'redux-saga/effects' import { addPropToStoreObject, addToStore } from '../actions/objects' import { getProject } from '../../api/projects' import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' @@ -8,7 +8,8 @@ import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfol export function* onOpenScenarioSucceeded(action) { try { - const project = yield call(getProject, action.projectId) + const auth = yield getContext('auth') + const project = yield call(getProject, auth, action.projectId) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(project._id) yield fetchAndStoreAllSchedulers() @@ -23,7 +24,8 @@ export function* onOpenScenarioSucceeded(action) { export function* onAddScenario(action) { try { - const scenario = yield call(addScenario, action.scenario.portfolioId, action.scenario) + const auth = yield getContext('auth') + const scenario = yield call(addScenario, auth, action.scenario.portfolioId, action.scenario) yield put(addToStore('scenario', scenario)) const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds) @@ -40,7 +42,8 @@ export function* onAddScenario(action) { export function* onUpdateScenario(action) { try { - const scenario = yield call(updateScenario, action.scenario._id, action.scenario) + const auth = yield getContext('auth') + const scenario = yield call(updateScenario, auth, action.scenario._id, action.scenario) yield put(addToStore('scenario', scenario)) } catch (error) { console.error(error) @@ -49,7 +52,8 @@ export function* onUpdateScenario(action) { export function* onDeleteScenario(action) { try { - yield call(deleteScenario, action.id) + const auth = yield getContext('auth') + yield call(deleteScenario, auth, action.id) const currentPortfolioId = yield select((state) => state.currentPortfolioId) const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js index 65f97cc9..e5fd3d39 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -1,4 +1,4 @@ -import { call, put, select } from 'redux-saga/effects' +import { call, put, select, getContext } from 'redux-saga/effects' import { goDownOneInteractionLevel } from '../actions/interaction-level' import { addIdToStoreObjectListProp, @@ -50,8 +50,10 @@ export function* onAddTopology(action) { topologyToBeCreated = { name: action.name, rooms: [] } } + const auth = yield getContext('auth') const topology = yield call( addTopology, + auth, Object.assign({}, topologyToBeCreated, { projectId: currentProjectId, }) @@ -79,7 +81,8 @@ export function* onDeleteTopology(action) { yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) } - yield call(deleteTopology, action.id) + const auth = yield getContext('auth') + yield call(deleteTopology, auth, action.id) yield put( addPropToStoreObject('project', currentProjectId, { diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/users.js b/opendc-web/opendc-web-ui/src/redux/sagas/users.js deleted file mode 100644 index 88c424b5..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/users.js +++ /dev/null @@ -1,44 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { logInSucceeded } from '../actions/auth' -import { addToStore } from '../actions/objects' -import { fetchAuthorizationsOfCurrentUserSucceeded } from '../actions/users' -import { performTokenSignIn } from '../../api/token-signin' -import { addUser } from '../../api/users' -import { saveAuthLocalStorage } from '../../auth' -import { fetchAndStoreProject, fetchAndStoreUser } from './objects' - -export function* onFetchLoggedInUser(action) { - try { - const tokenResponse = yield call(performTokenSignIn, action.payload.authToken) - - let userId = tokenResponse.userId - - if (tokenResponse.isNewUser) { - saveAuthLocalStorage({ authToken: action.payload.authToken }) - const newUser = yield call(addUser, action.payload) - userId = newUser._id - } - - yield put(logInSucceeded(Object.assign({ userId }, action.payload))) - } catch (error) { - console.error(error) - } -} - -export function* onFetchAuthorizationsOfCurrentUser(action) { - try { - const user = yield call(fetchAndStoreUser, action.userId) - - for (const authorization of user.authorizations) { - authorization.userId = action.userId - yield put(addToStore('authorization', authorization)) - yield fetchAndStoreProject(authorization.projectId) - } - - const authorizationIds = user.authorizations.map((authorization) => [action.userId, authorization.projectId]) - - yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds)) - } catch (error) { - console.error(error) - } -} -- cgit v1.2.3