de-leaked (#221)

This commit is contained in:
Michael Quigley 2024-10-28 14:15:37 -04:00
parent d2556b2251
commit 9867067a14
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
11 changed files with 87 additions and 560 deletions

View File

@ -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 (
<Card>
<h2>{props.access.frontendToken} [<LanIcon/>]</h2>
<h2><LanIcon /> {props.access.frontendToken}</h2>
<p>
{props.access.token} &rarr; {props.access.bindAddress}
</p>
<p><DeleteIcon onClick={releaseClicked}/></p>
<Button variant="outlined" onClick={deleteHandler}><DeleteIcon /></Button>
</Card>
);
}

View File

@ -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: <Overview
releaseShare={releaseShare}
releaseAccess={releaseAccess}
version={version}
overview={overview}
shareClick={openNewShare}
accessClick={openNewAccess}
/>
},
{
path: "/share/:token",
element: <ShareDetail version={version} />
}
]);
return (
<>
<NavBar version={version} shareClick={openNewShare} accessClick={openNewAccess} />
<RouterProvider router={router} />
<NewShareModal show={newShare} close={closeNewShare} handler={shareHandler} />
<NewAccessModal show={newAccess} close={closeNewAccess} handler={accessHandler} />
<Overview overview={overview} />
<NewAccessModal isOpen={newAccessOpen} close={closeNewAccess} />
<NewShareModal isOpen={newShareOpen} close={closeNewShare} />
</>
);
}

View File

@ -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 (
<Modal
open={props.show}
open={props.isOpen}
onClose={props.close}
>
<Box sx={{ ...modalStyle }}>

View File

@ -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 (
<Modal
open={props.show}
open={props.isOpen}
onClose={props.close}
>
<Box sx={{ ...modalStyle }}>

View File

@ -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(<ShareCard key={row.v.token} releaseShare={props.releaseShare} share={row.v} />);
case "access":
cards.push(<AccessCard key={row.frontendToken} access={row} />);
break;
case "access":
cards.push(<AccessCard key={row.v.frontendToken} releaseAccess={props.releaseAccess} access={row.v} />);
case "share":
cards.push(<ShareCard key={row.token} share={row} />);
break;
}
});
} else {
cards.push(<Card key="empty"><h5>zrok Agent is empty! Add a <a href="#" onClick={props.shareClick}>share <ShareIcon /></a> or <a href={"#"} onClick={props.accessClick}>access <LanIcon /></a> share to get started.</h5></Card>);
}
return <>{cards}</>;
return (
<Box sx={{ display: "flex",
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "space-between",
flexGrow: 1
}}>
{cards}
</Box>
);
}
export default Overview;

View File

@ -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(<a key={props.share.token} href={fe.toString()} target={"_"}>{fe}</a>);
})
const releaseClicked = () => {
props.releaseShare({token: props.share.token}, (err, data) => { console.log("releaseClicked", data); });
const deleteHandler = () => {
releaseShare({ token: props.share.token });
}
return (
<Card>
<h2>{props.share.token} [<ShareIcon />]</h2>
<h2><ShareIcon /> {props.share.token}</h2>
<p>
({props.share.shareMode}, {props.share.backendMode}) <br/>
{props.share.backendEndpoint} &rarr; {frontends} <br/>
<DeleteIcon onClick={releaseClicked}/>
</p>
<Button variant="outlined" onClick={deleteHandler} ><DeleteIcon /></Button>
</Card>
);
}

View File

@ -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<module:types.AccessPrivateResponse>} 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<module:types.ReleaseAccessResponse>} 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<module:types.ReleaseShareResponse>} 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<module:types.SharePrivateResponse>} 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<module:types.SharePublicResponse>} 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'
}

View File

@ -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
}

View File

@ -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

View File

@ -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);
})
}

View File

@ -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;
}