diff --git a/agent/agentUi/src/AccessCard.jsx b/agent/agentUi/src/AccessCard.jsx index 9879fe53..efd156bc 100644 --- a/agent/agentUi/src/AccessCard.jsx +++ b/agent/agentUi/src/AccessCard.jsx @@ -1,19 +1,20 @@ import LanIcon from "@mui/icons-material/Lan"; +import {Button, Card, Chip} from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; -import {Card} from "@mui/material"; +import {releaseAccess} from "./model/handler.js"; const AccessCard = (props) => { - const releaseClicked = () => { - props.releaseAccess({frontendToken: props.access.frontendToken}, (err, data) => { console.log("releaseClicked", data); }); + const deleteHandler = () => { + releaseAccess({ frontendToken: props.access.frontendToken }); } return ( -

{props.access.frontendToken} []

+

{props.access.frontendToken}

{props.access.token} → {props.access.bindAddress}

-

+
); } diff --git a/agent/agentUi/src/AgentUi.jsx b/agent/agentUi/src/AgentUi.jsx index ea3ef709..0cbf414d 100644 --- a/agent/agentUi/src/AgentUi.jsx +++ b/agent/agentUi/src/AgentUi.jsx @@ -1,36 +1,34 @@ -import {createBrowserRouter, RouterProvider} from "react-router-dom"; -import Overview from "./Overview.jsx"; -import ShareDetail from "./ShareDetail.jsx"; import {useEffect, useState} from "react"; -import buildOverview from "./model/overview.js"; import NavBar from "./NavBar.jsx"; -import NewShareModal from "./NewShareModal.jsx"; +import {getAgentApi} from "./model/handler.js"; +import {buildOverview} from "./model/overview.js"; +import Overview from "./Overview.jsx"; import NewAccessModal from "./NewAccessModal.jsx"; -import {accessHandler, getAgentApi, releaseAccess, releaseShare, shareHandler} from "./model/handler.js"; +import NewShareModal from "./NewShareModal.jsx"; const AgentUi = () => { const [version, setVersion] = useState(""); const [overview, setOverview] = useState([]); + const [newAccessOpen, setNewAccessOpen] = useState(false); + const [newShareOpen, setNewShareOpen] = useState(false); - const [newShare, setNewShare] = useState(false); - const openNewShare = () => { - setNewShare(true); - } - const closeNewShare = () => { - setNewShare(false); - } - - const [newAccess, setNewAccess] = useState(false); const openNewAccess = () => { - setNewAccess(true); + setNewAccessOpen(true); } const closeNewAccess = () => { - setNewAccess(false); + setNewAccessOpen(false); + } + + const openNewShare = () => { + setNewShareOpen(true); + } + const closeNewShare = () => { + setNewShareOpen(false); } useEffect(() => { - getAgentApi().agentVersion((err, data) => { - setVersion(data.v); + getAgentApi().agentVersion((e, d) => { + setVersion(d.v); }); return () => { setVersion(""); @@ -39,12 +37,12 @@ const AgentUi = () => { useEffect(() => { let interval = setInterval(() => { - getAgentApi().agentStatus((err, data) => { - if(err) { - console.log("agentStatus", err); + getAgentApi().agentStatus((e, d) => { + if(e) { setOverview([]); + console.log("agentStatus", e); } else { - setOverview(structuredClone(buildOverview(data))); + setOverview(buildOverview(d)); } }); }, 1000); @@ -54,30 +52,13 @@ const AgentUi = () => { } }, []); - const router = createBrowserRouter([ - { - path: "/", - element: - }, - { - path: "/share/:token", - element: - } - ]); return ( <> - - - + + + ); } diff --git a/agent/agentUi/src/NewAccessModal.jsx b/agent/agentUi/src/NewAccessModal.jsx index eca5c188..10a5eaf3 100644 --- a/agent/agentUi/src/NewAccessModal.jsx +++ b/agent/agentUi/src/NewAccessModal.jsx @@ -1,6 +1,7 @@ import {Box, Button, Modal, TextField} from "@mui/material"; import {useFormik} from "formik"; import {modalStyle} from "./model/theme.js"; +import {createAccess} from "./model/handler.js"; const NewAccessModal = (props) => { const newAccessForm = useFormik({ @@ -9,14 +10,14 @@ const NewAccessModal = (props) => { bindAddress: "", }, onSubmit: (v) => { - props.handler(v); + createAccess(v); props.close(); }, }); return ( diff --git a/agent/agentUi/src/NewShareModal.jsx b/agent/agentUi/src/NewShareModal.jsx index bfe0dc58..cf5beaf3 100644 --- a/agent/agentUi/src/NewShareModal.jsx +++ b/agent/agentUi/src/NewShareModal.jsx @@ -1,6 +1,7 @@ import {Box, Button, MenuItem, Modal, TextField} from "@mui/material"; import {useFormik} from "formik"; import {modalStyle} from "./model/theme.js"; +import {createShare} from "./model/handler.js"; const NewShareModal = (props) => { const newShareForm = useFormik({ @@ -10,14 +11,14 @@ const NewShareModal = (props) => { target: "", }, onSubmit: (v) => { - props.handler(v); + createShare(v); props.close(); }, }); return ( diff --git a/agent/agentUi/src/Overview.jsx b/agent/agentUi/src/Overview.jsx index 649929bc..de5dbef7 100644 --- a/agent/agentUi/src/Overview.jsx +++ b/agent/agentUi/src/Overview.jsx @@ -1,30 +1,38 @@ import "bootstrap/dist/css/bootstrap.min.css"; -import ShareCard from "./ShareCard.jsx"; -import AccessCard from "./AccessCard.jsx"; import LanIcon from "@mui/icons-material/Lan"; import ShareIcon from "@mui/icons-material/Share"; -import {Card} from "@mui/material"; -import buildOverview from "./model/overview.js"; +import {Box, Card, Stack} from "@mui/material"; +import AccessCard from "./AccessCard.jsx"; +import ShareCard from "./ShareCard.jsx"; +import React from "react"; const Overview = (props) => { let cards = []; if(props.overview.length > 0) { props.overview.forEach((row) => { switch(row.type) { - case "share": - cards.push(); + case "access": + cards.push(); break; - case "access": - cards.push(); + case "share": + cards.push(); break; } }); } else { cards.push(
zrok Agent is empty! Add a share or access share to get started.
); } - - return <>{cards}; + return ( + + {cards} + + ); } export default Overview; diff --git a/agent/agentUi/src/ShareCard.jsx b/agent/agentUi/src/ShareCard.jsx index 5d36d184..6c8c999f 100644 --- a/agent/agentUi/src/ShareCard.jsx +++ b/agent/agentUi/src/ShareCard.jsx @@ -1,6 +1,7 @@ import ShareIcon from "@mui/icons-material/Share"; +import {Button, Card} from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; -import {Card} from "@mui/material"; +import {releaseShare} from "./model/handler.js"; const ShareCard = (props) => { let frontends = []; @@ -8,18 +9,18 @@ const ShareCard = (props) => { frontends.push({fe}); }) - const releaseClicked = () => { - props.releaseShare({token: props.share.token}, (err, data) => { console.log("releaseClicked", data); }); + const deleteHandler = () => { + releaseShare({ token: props.share.token }); } return ( -

{props.share.token} []

+

{props.share.token}

({props.share.shareMode}, {props.share.backendMode})
{props.share.backendEndpoint} → {frontends}
-

+
); } diff --git a/agent/agentUi/src/api/Agent.js b/agent/agentUi/src/api/Agent.js deleted file mode 100644 index a1d8d7f5..00000000 --- a/agent/agentUi/src/api/Agent.js +++ /dev/null @@ -1,155 +0,0 @@ -/** @module Agent */ -// Auto-generated, edits will be overwritten -import * as gateway from './gateway' - -/** - * @param {object} options Optional options - * @param {string} [options.token] - * @param {string} [options.bindAddress] - * @param {string[]} [options.responseHeaders] - * @return {Promise} A successful response. - */ -export function Agent_AccessPrivate(options) { - if (!options) options = {} - const parameters = { - query: { - token: options.token, - bindAddress: options.bindAddress, - responseHeaders: gateway.formatArrayParam(options.responseHeaders, 'multi', 'responseHeaders') - } - } - return gateway.request(Agent_AccessPrivateOperation, parameters) -} - -/** - * @param {object} options Optional options - * @param {string} [options.frontendToken] - * @return {Promise} A successful response. - */ -export function Agent_ReleaseAccess(options) { - if (!options) options = {} - const parameters = { - query: { - frontendToken: options.frontendToken - } - } - return gateway.request(Agent_ReleaseAccessOperation, parameters) -} - -/** - * @param {object} options Optional options - * @param {string} [options.token] - * @return {Promise} A successful response. - */ -export function Agent_ReleaseShare(options) { - if (!options) options = {} - const parameters = { - query: { - token: options.token - } - } - return gateway.request(Agent_ReleaseShareOperation, parameters) -} - -/** - * @param {object} options Optional options - * @param {string} [options.target] - * @param {string} [options.backendMode] - * @param {boolean} [options.insecure] - * @param {boolean} [options.closed] - * @param {string[]} [options.accessGrants] - * @return {Promise} A successful response. - */ -export function Agent_SharePrivate(options) { - if (!options) options = {} - const parameters = { - query: { - target: options.target, - backendMode: options.backendMode, - insecure: options.insecure, - closed: options.closed, - accessGrants: gateway.formatArrayParam(options.accessGrants, 'multi', 'accessGrants') - } - } - return gateway.request(Agent_SharePrivateOperation, parameters) -} - -/** - * @param {object} options Optional options - * @param {string} [options.target] - * @param {string[]} [options.basicAuth] - * @param {string[]} [options.frontendSelection] - * @param {string} [options.backendMode] - * @param {boolean} [options.insecure] - * @param {string} [options.oauthProvider] - * @param {string[]} [options.oauthEmailAddressPatterns] - * @param {string} [options.oauthCheckInterval] - * @param {boolean} [options.closed] - * @param {string[]} [options.accessGrants] - * @return {Promise} A successful response. - */ -export function Agent_SharePublic(options) { - if (!options) options = {} - const parameters = { - query: { - target: options.target, - basicAuth: gateway.formatArrayParam(options.basicAuth, 'multi', 'basicAuth'), - frontendSelection: gateway.formatArrayParam(options.frontendSelection, 'multi', 'frontendSelection'), - backendMode: options.backendMode, - insecure: options.insecure, - oauthProvider: options.oauthProvider, - oauthEmailAddressPatterns: gateway.formatArrayParam(options.oauthEmailAddressPatterns, 'multi', 'oauthEmailAddressPatterns'), - oauthCheckInterval: options.oauthCheckInterval, - closed: options.closed, - accessGrants: gateway.formatArrayParam(options.accessGrants, 'multi', 'accessGrants') - } - } - return gateway.request(Agent_SharePublicOperation, parameters) -} - -/** - */ -export function Agent_Status() { - return gateway.request(Agent_StatusOperation) -} - -/** - */ -export function Agent_Version() { - return gateway.request(Agent_VersionOperation) -} - -const Agent_AccessPrivateOperation = { - path: '/v1/agent/accessPrivate', - method: 'post' -} - -const Agent_ReleaseAccessOperation = { - path: '/v1/agent/releaseAccess', - method: 'post' -} - -const Agent_ReleaseShareOperation = { - path: '/v1/agent/releaseShare', - method: 'post' -} - -const Agent_SharePrivateOperation = { - path: '/v1/agent/sharePrivate', - method: 'post' -} - -const Agent_SharePublicOperation = { - path: '/v1/agent/sharePublic', - method: 'post' -} - -const Agent_StatusOperation = { - path: '/v1/agent/status', - method: 'get' -} - -const Agent_VersionOperation = { - path: '/v1/agent/version', - method: 'get' -} diff --git a/agent/agentUi/src/api/gateway/index.js b/agent/agentUi/src/api/gateway/index.js deleted file mode 100644 index 881798d7..00000000 --- a/agent/agentUi/src/api/gateway/index.js +++ /dev/null @@ -1,281 +0,0 @@ -// Auto-generated, edits will be overwritten -import spec from './spec' - -export class ServiceError extends Error {} - -let options = {} - -export function init(serviceOptions) { - options = serviceOptions -} - -export function request(op, parameters, attempt) { - if (!attempt) attempt = 1; - return acquireRights(op, spec, options) - .then(rights => { - parameters = parameters || {} - const baseUrl = getBaseUrl(spec) - let reqInfo = { parameters, baseUrl } - if (options.processRequest) { - reqInfo = options.processRequest(op, reqInfo) - } - const req = buildRequest(op, reqInfo.baseUrl, reqInfo.parameters, rights) - return makeFetchRequest(req) - .then(res => processResponse(req, res, attempt, options), e => processError(req, e)) - .then(outcome => outcome.retry ? request(op, parameters, attempt + 1) : outcome.res) - }) -} - -function acquireRights(op, spec, options) { - if (op.security && options.getAuthorization) { - return op.security.reduce((promise, security) => { - return promise.then(rights => { - const securityDefinition = spec.securityDefinitions[security.id] - return options.getAuthorization(security, securityDefinition, op) - .then(auth => { - rights[security.id] = auth - return rights - }) - }) - }, Promise.resolve({})) - } - return Promise.resolve({}) -} - -function makeFetchRequest(req) { - let fetchOptions = { - compress: true, - method: (req.method || 'get').toUpperCase(), - headers: req.headers, - body: req.body ? JSON.stringify(req.body) : undefined - } - - if (options.fetchOptions) { - const opts = options.fetchOptions - const headers = opts.headers - ? Object.assign(fetchOptions.headers, opts.headers) - : fetchOptions.headers - - fetchOptions = Object.assign({}, fetchOptions, opts) - fetchOptions.headers = headers - } - - let promise = fetch(req.url, fetchOptions) - return promise -} - -function buildRequest(op, baseUrl, parameters, rights) { - let paramGroups = groupParams(op, parameters) - paramGroups = applyAuthorization(paramGroups, rights, spec) - const url = buildUrl(op, baseUrl, paramGroups, spec) - const headers = buildHeaders(op, paramGroups) - const body = buildBody(parameters.body) - return { - method: op.method, - url, - headers, - body - } -} - -function groupParams(op, parameters) { - const groups = ['header', 'path', 'query', 'formData'].reduce((groups, name) => { - groups[name] = formatParamsGroup(groups[name]) - return groups - }, parameters) - if (!groups.header) groups.header = {} - return groups -} - -function formatParamsGroup(groups) { - return Object.keys(groups || {}).reduce((g, name) => { - const param = groups[name] - if (param !== undefined) { - g[name] = formatParam(param) - } - return g - }, {}) -} - -function formatParam(param) { - if (param === undefined || param === null) return '' - else if (param instanceof Date) return param.toJSON() - else if (Array.isArray(param)) return param - else return param.toString() -} - -function buildUrl(op, baseUrl, parameters, spec) { - let url = `${baseUrl}${op.path}` - if (parameters.path) { - url = Object.keys(parameters.path) - .reduce((url, name) => url.replace(`{${name}}`, parameters.path[name]), url) - } - const query = createQueryString(parameters.query) - return url + query -} - -function getBaseUrl(spec) { - return options.url || `${spec.schemes[0] || 'https'}://${spec.host}${spec.basePath}` -} - -function createQueryParam(name, value) { - const v = formatParam(value) - if (v && typeof v === 'string') return `${name}=${encodeURIComponent(v)}` - return name; -} - -function createQueryString(query) { - const names = Object.keys(query || {}) - if (!names.length) return '' - const params = names.map(name => ({name, value: query[name]})) - .reduce((acc, value) => { - if (Array.isArray(value.value)) { - return acc.concat(value.value) - } else { - acc.push(createQueryParam(value.name, value.value)) - return acc - } - }, []) - return '?' + params.sort().join('&') -} - -function buildHeaders(op, parameters) { - const headers = {} - - let accepts - if (op.accepts && op.accepts.length) accepts = op.accepts - else if (spec.accepts && spec.accepts.length) accepts = spec.accepts - else accepts = [ 'application/json' ] - - headers.Accept = accepts.join(', ') - - let contentType - if (op.contentTypes && op.contentTypes[0]) contentType = op.contentTypes[0] - else if (spec.contentTypes && spec.contentTypes[0]) contentType = spec.contentTypes[0] - if (contentType) headers['Content-Type'] = contentType - - return Object.assign(headers, parameters.header) -} - -function buildBody(bodyParams) { - if (bodyParams) { - if (bodyParams.body) return bodyParams.body - const key = Object.keys(bodyParams)[0] - if (key) return bodyParams[key] - } - return undefined -} - -function resolveAuthHeaderName(headerName){ - if (options.authorizationHeader && headerName.toLowerCase() === 'authorization') { - return options.authorizationHeader - } else { - return headerName - } -} - -function applyAuthorization(req, rights, spec) { - Object.keys(rights).forEach(name => { - const rightsInfo = rights[name] - const definition = spec.securityDefinitions[name] - switch (definition.type) { - case 'basic': - const creds = `${rightsInfo.username}:${rightsInfo.password}` - const token = (typeof window !== 'undefined' && window.btoa) - ? window.btoa(creds) - : new Buffer(creds).toString('base64') - req.header[resolveAuthHeaderName('Authorization')] = `Basic ${token}` - break - case 'oauth2': - req.header[resolveAuthHeaderName('Authorization')] = `Bearer ${rightsInfo.token}` - break - case 'apiKey': - if (definition.in === 'header') { - req.header[resolveAuthHeaderName(definition.name)] = rightsInfo.apiKey - } else if (definition.in === 'query') { - req.query[definition.name] = rightsInfo.apiKey - } else { - throw new Error(`Api key must be in header or query not '${definition.in}'`) - } - break - default: - throw new Error(`Security definition type '${definition.type}' not supported`) - } - }) - return req -} - -function processResponse(req, response, attempt, options) { - const format = response.ok ? formatResponse : formatServiceError - const contentType = response.headers.get('content-type') || '' - - let parse - if (response.status === 204) { - parse = Promise.resolve() - } else if (~contentType.indexOf('json')) { - parse = response.json() - } else if (~contentType.indexOf('octet-stream')) { - parse = response.blob() - } else if (~contentType.indexOf('text')) { - parse = response.text() - } else { - parse = Promise.resolve() - } - - return parse - .then(data => format(response, data, options)) - .then(res => { - if (options.processResponse) return options.processResponse(req, res, attempt) - else return Promise.resolve({ res }) - }) -} - -function formatResponse(response, data, options) { - return { raw: response, data } -} - -function formatServiceError(response, data, options) { - if (options.formatServiceError) { - data = options.formatServiceError(response, data) - } else { - const serviceError = new ServiceError() - if (data) { - if (typeof data === 'string') serviceError.message = data - else { - if (data.message) serviceError.message = data.message - if (data.body) serviceError.body = data.body - else serviceError.body = data - } - - if (data.code) serviceError.code = data.code - } else { - serviceError.message = response.statusText - } - serviceError.status = response.status - data = serviceError - } - return { raw: response, data, error: true } -} - -function processError(req, error) { - const { processError } = options - const res = { res: { raw: {}, data: error, error: true } } - - return Promise.resolve(processError ? processError(req, res) : res) -} - -const COLLECTION_DELIM = { csv: ',', multi: '&', pipes: '|', ssv: ' ', tsv: '\t' } - -export function formatArrayParam(array, format, name) { - if (!array) return - if (format === 'multi') return array.map(value => createQueryParam(name, value)) - const delim = COLLECTION_DELIM[format] - if (!delim) throw new Error(`Invalid collection format '${format}'`) - return array.map(formatParam).join(delim) -} - -export function formatDate(date, format) { - if (!date) return - const str = date.toISOString() - return (format === 'date') ? str.split('T')[0] : str -} diff --git a/agent/agentUi/src/api/gateway/spec.js b/agent/agentUi/src/api/gateway/spec.js deleted file mode 100644 index d59e7fb4..00000000 --- a/agent/agentUi/src/api/gateway/spec.js +++ /dev/null @@ -1,16 +0,0 @@ - -// Auto-generated, edits will be overwritten -const spec = { - 'host': 'localhost', - 'schemes': [ - 'http' - ], - 'basePath': '', - 'contentTypes': [ - 'application/json' - ], - 'accepts': [ - 'application/json' - ] -} -export default spec diff --git a/agent/agentUi/src/model/handler.js b/agent/agentUi/src/model/handler.js index 0fa6c19a..9731a41c 100644 --- a/agent/agentUi/src/model/handler.js +++ b/agent/agentUi/src/model/handler.js @@ -1,49 +1,39 @@ import {AgentApi, ApiClient} from "../api/src/index.js"; export const getAgentApi = () => { - return new AgentApi(new ApiClient(window.location.protocol+'//'+window.location.host)); + return new AgentApi(new ApiClient("http://localhost:5173")); } -export const shareHandler = (values) => { - let api = getAgentApi(); - switch(values.shareMode) { +export const createShare = (opts) => { + switch(opts.shareMode) { case "public": - api.agentSharePublic({ - target: values.target, - backendMode: values.backendMode, - }, (err, data) => { - console.log(err, data); + getAgentApi().agentSharePublic(opts, (e, d) => { + console.log("createShare", e, d); }); break; case "private": - api.agentSharePrivate({ - target: values.target, - backendMode: values.backendMode, - }, (err, data) => { - console.log(err, data); - }); + getAgentApi().agentSharePrivate(opts, (e, d) => { + console.log("createShare", e, d); + }) break; } } -export const accessHandler = (values) => { - getAgentApi().agentAccessPrivate({ - token: values.token, - bindAddress: values.bindAddress, - }, (err, data) => { - console.log(err, data); - }); +export const releaseShare = (opts) => { + getAgentApi().agentReleaseShare(opts, (e, d) => { + console.log("releaseShare", e, d); + }) } -export const releaseShare = (opts) => { - getAgentApi().agentReleaseShare(opts, (err, data) => { - console.log(data); - }); +export const createAccess = (opts) => { + getAgentApi().agentAccessPrivate(opts, (e, d) => { + console.log("createAccess", e, d); + }) } export const releaseAccess = (opts) => { - getAgentApi().agentReleaseAccess(opts, (err, data) => { - console.log(data); - }); + getAgentApi().agentReleaseAccess(opts, (e, d) => { + console.log("releaseAccess", e, d); + }) } \ No newline at end of file diff --git a/agent/agentUi/src/model/overview.js b/agent/agentUi/src/model/overview.js index d307ff71..fdfac9c8 100644 --- a/agent/agentUi/src/model/overview.js +++ b/agent/agentUi/src/model/overview.js @@ -1,24 +1,20 @@ -const buildOverview = (status) => { +export const buildOverview = (status) => { let overview = []; if(status) { if(status.accesses) { status.accesses.forEach(acc => { - overview.push({ - type: "access", - v: structuredClone(acc) - }); + let o = structuredClone(acc); + o["type"] = "access"; + overview.push(o); }); } if(status.shares) { status.shares.forEach(shr => { - overview.push({ - type: "share", - v: structuredClone(shr) - }); + let o = structuredClone(shr); + o["type"] = "share"; + overview.push(o); }); } } return overview; -} - -export default buildOverview; \ No newline at end of file +} \ No newline at end of file