Merge remote-tracking branch 'UPSTREAM/develop' into UPSTREAM-develop

# Conflicts:
#	.gitignore
#	yarn.lock
This commit is contained in:
tawoe 2024-05-06 13:12:04 +02:00
commit ec01a21078
40 changed files with 2908 additions and 1876 deletions

View File

@ -5,3 +5,10 @@ VITE_OBP_EXPLORER_HOST=http://localhost:5173
VITE_OBP_CONSUMER_KEY=your_consumer_key
VITE_OBP_CONSUMER_SECRET=your_consumer_secret
VITE_OBP_REDIRECT_URL=http://localhost:5173/api/callback
VITE_OPB_SERVER_SESSION_PASSWORD=very secret
# Product styling setting
#VITE_OBP_LINKS_COLOR="#52b165"
#VITE_OBP_HEADER_LINKS_COLOR="#39455f"
#VITE_OBP_HEADER_LINKS_HOVER_COLOR="#39455f"
#VITE_OBP_HEADER_LINKS_BACKGROUND_COLOR="#eef0f4"
#VITE_OBP_LOGO_URL=https://static.openbankproject.com/images/obp_logo.png

13
.gitignore vendored
View File

@ -7,7 +7,6 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
*.lock
node_modules
.DS_Store
dist
@ -29,4 +28,14 @@ coverage
*.njsproj
*.sln
*.sw?
.fleet
#files from npm-run-build
package-lock.json
tsconfig.app.tsbuildinfo
tsconfig.vitest.tsbuildinfo
tsconfig.node.tsbuildinfo
vite.config.d.ts
vite.config.js
vitest.config.d.ts
vitest.config.js
components.d.ts

View File

@ -5,6 +5,8 @@ Welcome to the OBP API Explorer II
This application is used to explore OBP APIs and interact with the data and services in the context of the logged in user.
This application will gradually replace the original API Explorer. Long live the API Explorer!
## Install the Prerequisite Software
@ -13,7 +15,7 @@ This application is used to explore OBP APIs and interact with the data and serv
### Development Project Setup
* Setup your .env (see .env.example)
* Setup your .env file (see .env.example)
##### Install dependencies
@ -52,52 +54,17 @@ npm test:unit
```
</strike>
##### Lint with [ESLint](https://eslint.org/)
```sh
yarn lint
```
or
```sh
npm lint
```
##### Format with [Prettier](https://prettier.io/)
```sh
yarn format
```
or
```sh
npm format
```
## Compile and Minify for Production
##### Build the frontend
##### Build
```sh
yarn build
```
or
```sh
npm build
```
##### Build the backend
```sh
yarn build-server
```
or
```sh
npm build-server
npm run build
```
##### Start the backend server
```sh
your-absolute-path-to-server-dist> node app.js
npx ts-node <path-to-your-install>/server/app.ts
```
##### Nginx deployment
@ -106,7 +73,7 @@ your-absolute-path-to-server-dist> node app.js
server {
# Frontend
location / {
root /your_absolute_path_to_dist/dist;
root /path_to_dist/dist;
try_files $uri $uri/ /index.html;
}
@ -117,11 +84,6 @@ server {
}
```
```sh
nginx -s reload //Restart your nginx
```
# LICENSE
This project is licensed under the AGPL V3 (see NOTICE) and a commercial license from TESOBE.

7
components.d.ts vendored
View File

@ -14,10 +14,13 @@ declare module '@vue/runtime-core' {
ElAside: typeof import('element-plus/es')['ElAside']
ElBacktop: typeof import('element-plus/es')['ElBacktop']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@ -30,9 +33,13 @@ declare module '@vue/runtime-core' {
ElInput: typeof import('element-plus/es')['ElInput']
ElMain: typeof import('element-plus/es')['ElMain']
ElRow: typeof import('element-plus/es')['ElRow']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
GlossarySearchNav: typeof import('./src/components/GlossarySearchNav.vue')['default']
HeaderNav: typeof import('./src/components/HeaderNav.vue')['default']
Menu: typeof import('./src/components/Menu.vue')['default']
MessageDocsSearchNav: typeof import('./src/components/MessageDocsSearchNav.vue')['default']
Preview: typeof import('./src/components/Preview.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']

View File

@ -1,17 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>API Explorer</title>
<link rel="stylesheet" href="/styles/default.min.css">
<link rel="stylesheet" href="/styles/androidstudio.min.css">
<link rel="stylesheet" href="/styles/default.min.css" />
<link rel="stylesheet" href="/styles/default.min.css" />
<link rel="stylesheet" href="/styles/androidstudio.min.css" />
<script src="/js/highlight.min.js"></script>
<script src="/js/highlightjs-line-numbers.min.js"></script>
<style>
.loading-page {
display: flex;
align-items: center;
flex-direction: column;
}
.messages {
width: 80%;
height: 100px;
background-color: #e3e3e3;
margin: 100px auto;
padding: 5px;
}
.progress-info {
width: 50%;
height: 100px;
padding: 5px;
margin: 50px auto;
text-align: center;
font-family: 'Roboto';
}
.lds-dual-ring {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
margin-top: 15px;
}
.lds-dual-ring:after {
content: ' ';
display: block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
border: 6px solid #50b164;
border-color: #50b164 transparent #50b164 transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div id="app"></div>
<div id="app">
<div class="loading-page" id="loading-api-spinner">
<img src="/src/assets/logo2x-1.png" style="width: 304px; margin-top: 50px" />
<div class="lds-dual-ring"></div>
<div class="progress-info" id="loading-api-info"></div>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"name": "api-explorer",
"version": "0.0.0",
"version": "1.0.14",
"private": true,
"scripts": {
"dev": "vite & ts-node server/app.ts",
@ -25,7 +25,7 @@
"express-session": "^1.17.3",
"highlight.js": "^11.7.0",
"oauth": "^0.10.0",
"obp-typescript": "^1.0.34",
"obp-typescript": "^1.0.36",
"pinia": "^2.0.32",
"reflect-metadata": "^0.1.13",
"routing-controllers": "^0.10.3",
@ -49,6 +49,7 @@
"jsdom": "^21.1.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.4",
"superagent": "^8.1.2",
"ts-node": "^10.9.1",
"typescript": "~4.8.4",
"unplugin-auto-import": "^0.15.1",

View File

@ -0,0 +1,3 @@
onmessage = (event) => {
postMessage(event.data);
};

View File

@ -8,25 +8,40 @@ import path from 'path'
const port = 8085
const app: Application = express()
const host = process.env.VITE_OBP_EXPLORER_HOST
const httpsOrNot = host ? host.indexOf("https://") == 0 ? true : false : true
app.use(express.json())
let sessionObject = {
secret: process.env.VITE_OPB_SERVER_SESSION_PASSWORD,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: false,
maxAge: 300*1000, // 5 minutes in milliseconds
}
}
if (app.get('env') === 'production') {
app.set('trust proxy', 1) // trust first proxy
sessionObject.cookie.secure = true // serve secure cookies
}
app.use(
session({
secret: 'very secret',
resave: false,
saveUninitialized: true
})
session(sessionObject)
)
useContainer(Container)
const routePrefix = '/api'
const server = useExpressServer(app, {
//routePrefix: '/api/v1',
routePrefix: '/api',
routePrefix: routePrefix,
controllers: [path.join(__dirname + '/controllers/*.ts')],
middlewares: [path.join(__dirname + '/middlewares/*.ts')]
})
export const instance = server.listen(port)
console.log('Server running at http://localhost:' + port)
console.log(`Backend is running. You can check a status at http://localhost:${port}${routePrefix}/status`)
export default app

View File

@ -0,0 +1,95 @@
import { Controller, Session, Req, Res, Get } from 'routing-controllers'
import { Request, Response } from 'express'
import OBPClientService from '../services/OBPClientService'
import OauthInjectedService from '../services/OauthInjectedService'
import { Service } from 'typedi'
import { OAuthConfig } from 'obp-typescript'
@Service()
@Controller('/status')
export class StatusController {
private obpExplorerHome = process.env.VITE_OBP_EXPLORER_HOST
private connectors = [
'kafka_vSept2018',
'akka_vDec2018',
'rest_vMar2019',
'stored_procedure_vDec2019'
]
constructor(
private obpClientService: OBPClientService,
private oauthInjectedService: OauthInjectedService
) {}
@Get('/')
async index(
@Session() session: any,
@Req() request: Request,
@Res() response: Response
): Response {
const oauthConfig = session['clientConfig']
const version = this.obpClientService.getOBPVersion()
const apiVersions = await this.checkApiVersions(oauthConfig, version)
const messageDocs = await this.checkMessagDocs(oauthConfig, version)
const resourceDocs = await this.checkResourceDocs(oauthConfig, version)
return response.json({
status: apiVersions && messageDocs && resourceDocs,
apiVersions,
messageDocs,
resourceDocs
})
}
isCodeError(response: any, path: string): boolean {
console.log(`Validating ${path} response...`)
if (!response || Object.keys(response).length == 0) return true
if (Object.keys(response).includes('code')) {
const code = response['code']
if (code >= 400) {
console.log(response) // Log error responce
return true
}
}
return false
}
async checkResourceDocs(oauthConfig: OAuthConfig, version: string): Promise<boolean> {
try {
const path = `/obp/${version}/resource-docs/${version}/obp`
const resourceDocs = await this.obpClientService.get(
path,
oauthConfig
)
return !this.isCodeError(resourceDocs, path)
} catch (error) {
return false
}
}
async checkMessagDocs(oauthConfig: OAuthConfig, version: string): Promise<boolean> {
try {
const messageDocsCodeResult = await Promise.all(
this.connectors.map(async (connector) => {
const path = `/obp/${version}/message-docs/${connector}`
return !this.isCodeError(
await this.obpClientService.get(
path,
oauthConfig
),
path
)
})
)
return messageDocsCodeResult.every((isCodeError: boolean) => isCodeError)
} catch (error) {
return false
}
}
async checkApiVersions(oauthConfig: OAuthConfig, version: string): Promise<boolean> {
try {
const path = `/obp/${version}/api/versions`
const versions = await this.obpClientService.get(path, oauthConfig)
return !this.isCodeError(versions, path)
} catch (error) {
return false
}
}
}

View File

@ -16,6 +16,7 @@ export default class OauthAccessTokenMiddleware implements ExpressMiddlewareInte
const consumer = oauthService.getConsumer()
const oauthVerifier = request.query.oauth_verifier
const session = request.session
console.log('OauthAccessTokenMiddleware.ts use says: Before consumer.getOAuthAccessToken')
consumer.getOAuthAccessToken(
oauthService.requestTokenKey,
oauthService.requestTokenSecret,
@ -34,6 +35,7 @@ export default class OauthAccessTokenMiddleware implements ExpressMiddlewareInte
secret: oauthTokenSecret
}
session['clientConfig'] = clientConfig
console.log('OauthAccessTokenMiddleware.ts use says: Seems OK, redirecting..')
response.redirect(`${process.env.VITE_OBP_EXPLORER_HOST}`)
}
}

View File

@ -19,6 +19,7 @@ export default class OauthRequestTokenMiddleware implements ExpressMiddlewareInt
} else {
oauthService.requestTokenKey = oauthTokenKey
oauthService.requestTokenSecret = oauthTokenSecret
console.log('OauthRequestTokenMiddleware.ts consumer.getOAuthRequestToken says: Redirecting to /oauth/authorize?oauth_token=XXX')
response.redirect(apiHost + '/oauth/authorize?oauth_token=' + oauthTokenKey)
}
})

View File

@ -50,10 +50,6 @@
font-family: Roboto;
}
.el-dropdown-menu__item:hover {
color: #52b165 !important;
}
.el-alert--info.is-light {
background-color: #253047;
}

View File

@ -34,3 +34,9 @@ main {
.favoriteButton:hover {
color: #fcc406;
}
.search-nav {
background-color: #f8f9fb;
padding: 8px;
border-right: solid 1px var(--el-menu-border-color);
}

View File

@ -0,0 +1,59 @@
<script setup lang="ts">
import { SEARCH_LINKS_COLOR as searchLinksColorSetting } from '../obp/style-setting'
import { inject, ref } from 'vue'
const searchLinksColor = ref(searchLinksColorSetting);
const collectionData = [
{
"url": "/?api-collection-id=341bf039-97e4-4803-aaae-2d1dc6a5b162",
"text": "Risk mgmt. & mitigation"
},
{
"url": "/?api-collection-id=fc3220e5-c891-4943-bbdb-6579ef3889c0",
"text": "Process mgmt. & enh."
},
{
"url": "/?api-collection-id=18fa516e-8e52-473c-98c4-430b1043ef4f",
"text": "Credit data & scoring"
},
{
"url": "/?api-collection-id=7d4b5f73-9bde-4556-a0ee-ce0f746f833f",
"text": "Innovative prods & svcs"
},
{
"url": "/?api-collection-id=cbd97e62-fb8a-42a4-ad5a-139729d11312",
"text": "Women-led/owned MSMEs prods & svcs"
}
]
</script>
<template>
<el-menu
class="collections-menu"
mode="horizontal"
background-color="#151d30"
text-color="#fff"
active-text-color="#52b165">
<el-menu-item v-for="(item, index) in collectionData" :index="index">
<RouterLink :to="item.url">{{ item.text }}</RouterLink>
</el-menu-item>
</el-menu>
</template>
<style scoped>
a {
font-size: 14px;
font-family: 'Roboto';
color: #7787a6;
text-decoration: none;
}
/* This is not working at the moment, but ideally we would like the color of the menu item to change when hovering.
a:hover {
color: v-bind(searchLinksColor);
}
*/
</style>

View File

@ -1,58 +1,69 @@
<script setup lang="ts">
import { ref, inject, provide, onActivated, onMounted } from 'vue'
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
import { getOperationDetails } from '../obp/resource-docs'
import { obpMyCollectionsEndpointKey, obpResourceDocsKey } from '@/obp/keys'
import { ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue'
import type { ElNotification } from 'element-plus'
import { ElNotification } from 'element-plus'
import { inject, onMounted, provide, ref } from 'vue'
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
import {
createMyAPICollection,
createMyAPICollectionEndpoint,
deleteMyAPICollectionEndpoint,
getCurrentUser
OBP_API_VERSION,
createMyAPICollection,
createMyAPICollectionEndpoint,
deleteMyAPICollectionEndpoint,
getCurrentUser
} from '../obp'
import { setTabActive, initializeAPICollections } from './SearchNav.vue'
import { getGroupedResourceDocs, getOperationDetails } from '../obp/resource-docs'
import { SUMMARY_PAGER_LINKS_COLOR as summaryPagerLinksColorSetting } from '../obp/style-setting'
import { initializeAPICollections, setTabActive } from './SearchNav.vue'
const route = useRoute()
const obpVersion = 'OBP' + OBP_API_VERSION
const description = ref('')
const summary = ref('')
const docs = inject('OBP-ResourceDocs')
const resourceDocs = inject(obpResourceDocsKey)
const docs = getGroupedResourceDocs(obpVersion, resourceDocs)
const displayPrev = ref(true)
const displayNext = ref(true)
const prev = ref({ id: 'prev' })
const next = ref({ id: 'next' })
const favoriteButtonStyle = ref('favorite favoriteButton')
const summaryPagerLinksColor = ref(summaryPagerLinksColorSetting)
let routeId = ''
let version = obpVersion
let isFavorite = false
let apiCollectionsEndpoint = inject('OBP-MyCollectionsEndpoint')!
let apiCollectionsEndpoint = inject(obpMyCollectionsEndpointKey)!
const setOperationDetails = (id: string): void => {
const operation = getOperationDetails(docs, id)
description.value = operation.description
summary.value = operation.summary
const setOperationDetails = (id: string, version: string): void => {
const operation = getOperationDetails(version, id, resourceDocs)
description.value = operation?.description
summary.value = operation?.summary
}
const setPager = (id: string): void => {
const target = document.getElementById(id).parentElement
const prevElement = target.previousSibling
const nextElement = target.nextSibling
const active = document.querySelector('.active-api-router-tab')
if (active) active.classList.remove('active-api-router-tab')
target.classList.add('active-api-router-tab')
if (prevElement.className && prevElement.className.startsWith('api-router-tab')) {
const prevItem = prevElement.children.item(0)
prev.value['title'] = prevItem.text
prev.value['id'] = prevItem.id
displayPrev.value = true
} else {
displayPrev.value = false
}
if (nextElement.className && nextElement.className.startsWith('api-router-tab')) {
const nextItem = nextElement.children.item(0)
next.value['title'] = nextItem.text
next.value['id'] = nextItem.id
displayNext.value = true
} else {
displayNext.value = false
const target = document.getElementById(id)?.parentElement
if (target) {
const prevElement = target.previousSibling
const nextElement = target.nextSibling
const active = document.querySelector('.active-api-router-tab')
if (active) active.classList.remove('active-api-router-tab')
target.classList.add('active-api-router-tab')
if (prevElement.className && prevElement.className.startsWith('api-router-tab')) {
const prevItem = prevElement.children.item(0)
prev.value['title'] = prevItem.text
prev.value['id'] = prevItem.id
prev.value['version'] = version
displayPrev.value = true
} else {
displayPrev.value = false
}
if (nextElement.className && nextElement.className.startsWith('api-router-tab')) {
const nextItem = nextElement.children.item(0)
next.value['title'] = nextItem.text
next.value['id'] = nextItem.id
next.value['version'] = version
displayNext.value = true
} else {
displayNext.value = false
}
}
}
@ -77,20 +88,21 @@ const createDeleteFavorite = async (): void => {
createMyAPICollection()
apiCollectionsEndpoint = []
}
if (isFavorite) {
await deleteMyAPICollectionEndpoint(routeId)
if (isFavorite) { // Add the API endpoint to favorite
const response = await deleteMyAPICollectionEndpoint(routeId)
favoriteButtonStyle.value = 'favorite favoriteButton'
showNotification('Removed from favourites.', 'success')
if (response) { // Success response returns <empty string>
showNotification(response, 'error')
}
isFavorite = false
apiCollectionsEndpoint = apiCollectionsEndpoint.filter((api) => api != routeId)
} else {
await createMyAPICollectionEndpoint(routeId)
} else { // Remove the API endpoint from favorite
const response = await createMyAPICollectionEndpoint(routeId)
favoriteButtonStyle.value = 'favorite activeFavoriteButton'
showNotification('Added to favourites.', 'success')
isFavorite = true
apiCollectionsEndpoint.push(routeId)
}
provide('OBP-MyCollectionsEndpoint', apiCollectionsEndpoint)
provide(obpMyCollectionsEndpointKey, apiCollectionsEndpoint)
await initializeAPICollections()
setTabActive(routeId)
}
@ -98,7 +110,6 @@ const createDeleteFavorite = async (): void => {
const showNotification = (message: string, type: string): void => {
ElNotification({
duration: 5500,
position: 'bottom-right',
message,
type
})
@ -106,13 +117,15 @@ const showNotification = (message: string, type: string): void => {
onMounted(async () => {
routeId = route.params.id
setOperationDetails(routeId)
version = route.query.version ? route.query.version : obpVersion
setOperationDetails(routeId, version)
setPager(routeId)
await tagFavoriteButton(routeId)
})
onBeforeRouteUpdate(async (to) => {
routeId = to.params.id
setOperationDetails(routeId)
version = route.query.version ? route.query.version : obpVersion
setOperationDetails(routeId, version)
setPager(routeId)
await tagFavoriteButton(routeId)
})
@ -137,22 +150,20 @@ onBeforeRouteUpdate(async (to) => {
<el-divider class="divider" />
<el-row>
<el-col :span="12" class="pager-left">
<el-icon v-show="displayPrev"><ArrowLeftBold /></el-icon>
<RouterLink
v-show="displayPrev"
class="pager-router-link"
:to="{ name: 'api', params: { id: prev.id } }"
>{{ prev.title }}</RouterLink
>
<el-icon v-show="displayPrev">
<ArrowLeftBold />
</el-icon>
<RouterLink v-show="displayPrev" class="pager-router-link"
:to="{ name: 'api', params: { id: prev.id }, query: { version: prev.version } }">{{ prev.title }}
</RouterLink>
</el-col>
<el-col :span="12" class="pager-right">
<RouterLink
v-show="displayNext"
class="pager-router-link"
:to="{ name: 'api', params: { id: next.id } }"
>{{ next.title }}</RouterLink
>
<el-icon v-show="displayNext"><ArrowRightBold /></el-icon>
<RouterLink v-show="displayNext" class="pager-router-link"
:to="{ name: 'api', params: { id: next.id }, query: { version: next.version } }">{{ next.title }}
</RouterLink>
<el-icon v-show="displayNext">
<ArrowRightBold />
</el-icon>
</el-col>
</el-row>
</el-footer>
@ -166,15 +177,19 @@ main {
color: #39455f;
font-family: 'Roboto';
}
span {
font-size: 28px;
}
div {
font-size: 14px;
}
.content :deep(strong) {
font-family: 'Roboto';
}
.content :deep(p a) {
line-height: 28px;
padding: 5px;
@ -186,38 +201,45 @@ div {
border-radius: 5px;
background-color: #eef0f4;
}
.pager {
position: absolute;
bottom: 0;
}
.pager-left {
display: flex;
justify-content: left;
align-items: center;
}
.pager-right {
display: flex;
justify-content: right;
align-items: center;
}
.footer {
max-height: 30px;
}
.divider {
margin-top: -15px;
}
.pager-router-link {
font-family: 'Roboto';
text-decoration: none;
color: #39455f;
}
.pager-router-link:hover,
.pager-left:hover,
.pager-right:hover {
color: #52b165;
color: v-bind(summaryPagerLinksColor);
}
.favorite {
cursor: pointer;
line-height: 1;
}
</style>
}</style>

View File

@ -1,12 +1,15 @@
<script setup lang="ts">
import { reactive, ref, onBeforeMount, inject } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import { obpGlossaryKey } from '@/obp/keys';
import { Search } from '@element-plus/icons-vue';
import { inject, onBeforeMount, onMounted, reactive, ref } from 'vue';
import { useRoute } from 'vue-router';
import { SEARCH_LINKS_COLOR as searchLinksColorSetting } from '../obp/style-setting';
const route = useRoute()
const activeKeys = ref([])
const glossaryKeys = ref([])
const alphabet = ref([])
const searchLinksColor = ref(searchLinksColorSetting)
const form = reactive({
search: ''
})
@ -15,7 +18,7 @@ const alphabetCharCodes = Array.from(Array(26)).map((e, i) => i + 65)
alphabet.value = alphabetCharCodes.map((x) => String.fromCharCode(x))
onBeforeMount(() => {
const glossary = inject('OBP-Glossary')!
const glossary = inject(obpGlossaryKey)!
for (const item of glossary.glossary_items) {
if (!activeKeys.value.includes(item.title)) {
activeKeys.value.push(item.title)
@ -24,6 +27,16 @@ onBeforeMount(() => {
glossaryKeys.value = activeKeys.value
})
onMounted(() => {
let hash = route.hash;
let elements = document.querySelectorAll(`a[href="${hash}"][class="glossary-router-link"]`)
if (elements.length == 1) {
elements[0].click()
}
})
const filterKeys = (keys, key) => {
return keys.filter((title) => {
return title.toLowerCase().includes(key.toLowerCase())
@ -40,21 +53,11 @@ const searchEvent = (event) => {
</script>
<template>
<el-input
v-model="form.search"
class="w-50 m-1"
placeholder="Search"
:prefix-icon="Search"
@input="searchEvent"
/>
<el-input v-model="form.search" class="w-50 m-1" placeholder="Search" :prefix-icon="Search" @input="searchEvent" />
<div class="tabs">
<div class="alphabet">
<div v-for="value of alphabet" :key="value">
<a
:id="value"
class="alphabet-router-link"
v-bind:href="`#${value.toLowerCase()}-quick-nav`"
>
<a :id="value" class="alphabet-router-link" v-bind:href="`#${value.toLowerCase()}-quick-nav`">
<div class="alphabet-link">
{{ value }}
</div>
@ -64,11 +67,7 @@ const searchEvent = (event) => {
<div class="tab-items">
<div class="el-tabs--right">
<div v-for="value of glossaryKeys" :key="value" class="glossary-router-tab">
<a
class="glossary-router-link"
:id="`${value.charAt(0).toLowerCase()}-quick-nav`"
v-bind:href="`#${value}`"
>
<a class="glossary-router-link" :id="`${value.charAt(0).toLowerCase()}-quick-nav`" v-bind:href="`#${value}`">
{{ value }}
</a>
</div>
@ -77,32 +76,30 @@ const searchEvent = (event) => {
</div>
</template>
<style>
<style scoped>
.tabs {
display: flex;
max-height: 90vh;
}
.alphabet {
padding: 10px 5px 5px 5px;
min-width: 25px;
}
.alphabet-link {
padding: 5px 0px 5px 0px;
width: 100%;
text-align: center;
cursor: pointer;
}
.alphabet-router-link {
font-size: 13px;
font-family: 'Roboto';
color: #39455f;
text-decoration: none;
}
.search-nav {
background-color: #f8f9fb;
padding: 8px;
border-right: solid 1px var(--el-menu-border-color);
}
.glossary-router-link {
margin-left: 15px;
@ -120,15 +117,16 @@ const searchEvent = (event) => {
.glossary-router-tab:hover,
.active-glossary-router-tab {
border-left: 2px solid #52b165;
border-left: 2px solid v-bind(searchLinksColor);
}
.glossary-router-tab:hover .glossary-router-link,
.active-glossary-router-link,
.alphabet-router-link:hover,
.alphabet-link:hover .alphabet-router-link {
color: #52b165;
color: v-bind(searchLinksColor);
}
.tab-items {
overflow: auto;
max-height: 100vh;

View File

@ -1,28 +1,58 @@
<script setup lang="ts">
import { ref, watchEffect, onMounted } from 'vue'
import { ref, inject, watchEffect, onMounted } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import { getCurrentUser } from '../obp'
import { useRoute, useRouter } from 'vue-router'
import { OBP_API_VERSION, getCurrentUser } from '../obp'
import { getOBPAPIVersions } from '../obp/api-version'
import {
LOGO_URL as logoSource,
HEADER_LINKS_COLOR,
HEADER_LINKS_HOVER_COLOR as headerLinksHoverColorSetting,
HEADER_LINKS_BACKGROUND_COLOR as headerLinksBackgroundColorSetting
} from '../obp/style-setting'
import { obpApiActiveVersionsKey, obpGroupedMessageDocsKey, obpMyCollectionsEndpointKey } from '@/obp/keys'
const route = useRoute()
const router = useRouter()
const obpApiHost = ref(import.meta.env.VITE_OBP_API_HOST)
const obpApiManagerHost = ref(import.meta.env.VITE_OBP_API_MANAGER_HOST)
const loginUsername = ref('')
const logOffUrl = ref('')
const logoffurl = ref('')
const obpApiVersions = ref(inject(obpApiActiveVersionsKey)!)
const obpMessageDocs = ref(Object.keys(inject(obpGroupedMessageDocsKey)!))
const isShowLoginButton = ref(true)
const isShowLogOffButton = ref(false)
const logo = ref(logoSource)
const headerLinksHoverColor = ref(headerLinksHoverColorSetting)
const headerLinksBackgroundColor = ref(headerLinksBackgroundColorSetting)
const clearActiveTab = () => {
const activeLinks = document.querySelectorAll('.router-link')
for (const active of activeLinks) {
if (active.id) active.style.backgroundColor = 'transparent'
if (active.id) {
active.style.backgroundColor = 'transparent'
active.style.color = '#39455f'
}
}
}
const setActive = (target) => {
if (target) {
clearActiveTab()
target.style.backgroundColor = '#eef0f4'
target.style.backgroundColor = headerLinksBackgroundColor.value
target.style.color = HEADER_LINKS_COLOR
}
}
const handleMore = (command: string) => {
let element = document.getElementById("selected-api-version")
if (element !== null) {
element.textContent = command;
}
if (command.includes('_')) {
router.push({ name: 'message-docs', params: { id: command } })
} else {
router.replace({ path: '/operationid', query: { version: command } })
}
}
@ -44,21 +74,25 @@ watchEffect(() => {
if (path && route.params && !route.params.id) {
setActive(document.getElementById('header-nav-' + path))
} else {
setActive(document.getElementById('header-nav-tags'))
if (path == 'message-docs') {
clearActiveTab()
} else {
setActive(document.getElementById('header-nav-tags'))
}
}
})
</script>
<template>
<img alt="OBP logo" class="logo" src="@/assets/logo2x-1.png" />
<img alt="OBP logo" class="logo" v-show="logo" :src="logo" />
<img alt="OBP logo" class="logo" v-show="!logo" src="@/assets/logo2x-1.png" />
<nav id="nav">
<RouterView name="header">
<a v-bind:href="obpApiHost" class="router-link" id="header-nav-home">
{{ $t('header.portal_home') }}
</a>
<RouterLink class="router-link" id="header-nav-tags" to="/operationid">{{
$t('header.api_explorer')
}}</RouterLink>
<RouterLink class="router-link" id="header-nav-tags" :to="'/operationid?version=OBP' + OBP_API_VERSION">{{
$t('header.api_explorer') }}</RouterLink>
<RouterLink class="router-link" id="header-nav-glossary" to="/glossary">{{
$t('header.glossary')
}}</RouterLink>
@ -66,7 +100,7 @@ watchEffect(() => {
{{ $t('header.api_manager') }}
</a>
<span class="el-dropdown-link">
<el-dropdown class="menu-right router-link" id="header-nav-spaces">
<el-dropdown class="menu-right router-link" id="header-nav-more" @command="handleMore">
<span class="el-dropdown-link">
{{ $t('header.more') }}
<el-icon class="el-icon--right">
@ -75,7 +109,11 @@ watchEffect(() => {
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item key="messageDocs">Message Docs</el-dropdown-item>
<el-dropdown-item v-for="value in obpApiVersions" :command="value" key="value">{{
value
}}</el-dropdown-item>
<el-dropdown-item v-for="value in obpMessageDocs" :command="value" key="value">
Message Docs for: {{ value }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -92,11 +130,7 @@ watchEffect(() => {
{{ $t('header.login') }}
</a>
<span v-show="isShowLogOffButton" class="login-user">{{ loginUsername }}</span>
<a
v-bind:href="'/api/user/logoff'"
v-show="isShowLogOffButton"
class="logoff-button router-link"
>
<a v-bind:href="'/api/user/logoff'" v-show="isShowLogOffButton" class="logoff-button router-link">
{{ $t('header.logoff') }}
</a>
</RouterView>
@ -152,7 +186,8 @@ nav {
}
.router-link:hover {
background-color: #eef0f4 !important;
background-color: v-bind(headerLinksBackgroundColor) !important;
color: v-bind(headerLinksHoverColor) !important;
}
.logo {
@ -173,4 +208,9 @@ nav {
.logoff-button:hover {
color: #39455f;
}
/*override element plus*/
.el-dropdown-menu__item:hover {
color: v-bind(headerLinksHoverColor) !important;
}
</style>

View File

@ -1,25 +1,38 @@
<script setup lang="ts">
import { ArrowDown } from '@element-plus/icons-vue'
import { inject } from 'vue'
import { SEARCH_LINKS_COLOR as searchLinksColorSetting } from '../obp/style-setting'
import { inject, ref } from 'vue'
import { updateServerStatus } from '@/obp/common-functions';
import { obpApiHostKey } from '@/obp/keys';
const APP_VERSION = ref(__APP_VERSION__)
const i18n = inject('i18n')
const host = inject('OBP-API-Host')
const OBP_API_HOST = inject(obpApiHostKey)
const searchLinksColor = ref(searchLinksColorSetting)
const handleLocale = (command: string) => {
i18n.global.locale.value = command
}
const updateStatus = (event: any) => {
updateServerStatus()
}
</script>
<template>
<el-row>
<el-col :span="12" class="menu-left"></el-col>
<el-col :span="12" class="menu-right">
<el-col :span="10" class="menu-left">
&nbsp;&nbsp;
<span id="selected-api-version" class="host">OBPv5.1.0</span>
</el-col>
<el-col :span="14" class="menu-right">
<span class="host">App Version: {{ APP_VERSION }}</span>
&nbsp;&nbsp;
<span class="host"
>API Host:
<a :href="host">
{{ host }}
><span id="backend-status" @click="updateStatus" >API Host: </span>
<a :href="OBP_API_HOST">
{{ OBP_API_HOST }}
</a>
</span>
&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;
<el-dropdown class="menu-right" @command="handleLocale">
<span class="el-dropdown-link">
{{ $i18n.locale }}
@ -50,14 +63,21 @@ a {
text-decoration: none;
}
a:hover {
color: #52b165;
color: v-bind(searchLinksColor);
}
.host {
font-size: 14px;
font-family: 'Roboto';
}
.menu-left,
.menu-right,
.el-dropdown-menu {
color: #7787a6;
}
.server-is-online {
color: v-bind(searchLinksColor);
}
.server-is-offline {
color: red;
}
</style>

View File

@ -0,0 +1,141 @@
<script setup lang="ts">
import { reactive, ref, onBeforeMount, inject, watch } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import { SEARCH_LINKS_COLOR as searchLinksColorSetting } from '../obp/style-setting'
import { connectors } from '../obp/message-docs'
import { obpGroupedMessageDocsKey } from '@/obp/keys'
let connector = connectors[0]
const route = useRoute()
const groupedMessageDocs = ref(inject(obpGroupedMessageDocsKey)!)
const docs = ref({})
const groups = ref({})
const sortedKeys = ref([])
const activeKeys = ref([])
const messageDocKeys = ref([])
const searchLinksColor = ref(searchLinksColorSetting)
const form = reactive({
search: ''
})
onBeforeMount(() => {
setDocs()
})
watch(
() => route.params.id,
async (id) => {
setDocs()
}
)
const isKeyFound = (keys, item) => keys.every((k) => item.toLowerCase().includes(k))
const filterKeys = (keys, key) => {
const splitKey = key.split(' ').map((k) => k.toLowerCase())
return keys.filter((title) => {
const isGroupFound = isKeyFound(splitKey, title)
const items = docs.value[title].filter((item) => isGroupFound || isKeyFound(splitKey, item))
groups.value[title] = items
return isGroupFound || items.length > 0
})
}
const searchEvent = (value) => {
if (value) {
messageDocKeys.value = filterKeys(activeKeys.value, value)
} else {
groups.value = JSON.parse(JSON.stringify(docs.value))
messageDocKeys.value = Object.keys(groups.value)
}
}
const setDocs = () => {
const paramConnector = route.params.id
if (connectors.includes(paramConnector)) {
connector = paramConnector
}
const messageDocs = groupedMessageDocs.value[connector]
docs.value = Object.keys(messageDocs).reduce((doc, key) => {
doc[key] = messageDocs[key].map((group) => group.process)
return doc
}, {})
groups.value = JSON.parse(JSON.stringify(docs.value))
messageDocKeys.value = Object.keys(groups.value)
activeKeys.value = Object.keys(groups.value)
}
</script>
<template>
<el-row>
<el-col :span="24">
<el-input
v-model="form.search"
placeholder="Search"
:prefix-icon="Search"
@input="searchEvent"
/>
</el-col>
</el-row>
<el-collapse v-model="activeKeys">
<el-collapse-item v-for="key in messageDocKeys" :title="key" :key="key" :name="key">
<div class="el-tabs--right">
<div v-for="(value, key) of groups[key]" :key="value" class="message-docs-router-tab">
<a class="message-docs-router-link" :id="`${value}-quick-nav`" v-bind:href="`#${value}`">
{{ value }}
</a>
</div>
</div>
</el-collapse-item>
</el-collapse>
</template>
<style scoped>
.api-router-link {
width: 100%;
margin-left: 15px;
font-family: 'Roboto';
text-decoration: none;
color: #39455f;
display: inline-block;
}
.api-router-tab {
border-left: 2px solid var(--el-menu-border-color);
}
.api-router-tab:hover,
.active-api-router-tab {
border-left: 2px solid v-bind(searchLinksColor);
}
.api-router-tab:hover .api-router-link,
.active-api-router-link {
color: v-bind(searchLinksColor);
}
.message-docs-router-link {
margin-left: 15px;
font-size: 13px;
font-family: 'Roboto';
text-decoration: none;
color: #39455f;
display: inline-block;
}
.message-docs-router-tab {
border-left: 2px solid var(--el-menu-border-color);
line-height: 30px;
}
.message-docs-router-tab:hover,
.active-message-docs-router-tab {
border-left: 2px solid v-bind(searchLinksColor);
}
.message-docs-router-tab:hover .message-docs-router-link {
color: v-bind(searchLinksColor);
}
</style>

View File

@ -3,9 +3,12 @@ import { ref, reactive, inject, onBeforeMount } from 'vue'
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
import { getOperationDetails } from '../obp/resource-docs'
import type { ElNotification, FormInstance } from 'element-plus'
import { get, create, update, discard, createEntitlement, getCurrentUser } from '../obp'
import { OBP_API_VERSION, get, create, update, discard, createEntitlement, getCurrentUser } from '../obp'
import { getGroupedResourceDocs } from '../obp/resource-docs'
import { obpResourceDocsKey } from '@/obp/keys'
const elMessageDuration = 5500
const configVersion = 'OBP' + OBP_API_VERSION
const url = ref('')
const roleName = ref('')
const method = ref('')
@ -23,7 +26,14 @@ const showPossibleErrors = ref(true)
const showConnectorMethods = ref(true)
const isUserLogon = ref(true)
const type = ref('')
const docs = inject('OBP-ResourceDocs')
const resourceDocs = inject(obpResourceDocsKey)
const docs = getGroupedResourceDocs(configVersion, resourceDocs)
const footNote = ref({
operationId: '',
version: '',
functionName: '',
messageTags: ''
})
const requestFormRef = reactive<FormInstance>({})
const requestForm = reactive({ url: '' })
@ -31,10 +41,10 @@ const requestForm = reactive({ url: '' })
const roleFormRef = reactive<FormInstance>({})
const roleForm = reactive({})
const setOperationDetails = (id: string): void => {
const operation = getOperationDetails(docs, id)
url.value = operation.specified_url
method.value = operation.request_verb
const setOperationDetails = (id: string, version: string): void => {
const operation = getOperationDetails(version, id, resourceDocs)
url.value = operation?.specified_url
method.value = operation?.request_verb
exampleRequestBody.value = JSON.stringify(operation.example_request_body)
requiredRoles.value = operation.roles || []
possibleErrors.value = operation.error_response_bodies
@ -43,6 +53,10 @@ const setOperationDetails = (id: string): void => {
showValidations.value = validations.value.length > 0
showPossibleErrors.value = possibleErrors.value.length > 0
showConnectorMethods.value = connectorMethods.value.length > 0
footNote.value.version = operation.operation_id
footNote.value.version = operation.implemented_by.version
footNote.value.functionName = operation.implemented_by.function
footNote.value.messageTags = operation.tags.join(',')
highlightCode(operation.success_response_body)
setType(method.value)
@ -103,8 +117,7 @@ const submitRequest = async () => {
ElNotification({
duration: elMessageDuration,
message: 'URL path is required.',
type: 'error',
position: 'bottom-right'
type: 'error'
})
}
}
@ -113,7 +126,9 @@ const submit = async (form: FormInstance, fn: () => void) => {
fn(form).then(() => {})
}
const highlightCode = (json) => {
if (json) {
if (json.error) {
successResponseBody.value = json.error.message
} else if (json) {
successResponseBody.value = hljs.lineNumbersValue(
hljs.highlightAuto(JSON.stringify(json, null, 4), ['JSON']).value
)
@ -151,14 +166,16 @@ const submitEntitlement = async () => {
}
onBeforeMount(async () => {
const route = useRoute()
setOperationDetails(route.params.id)
const version = route.query.version ? route.query.version : configVersion
setOperationDetails(route.params.id, version)
const currentUser = await getCurrentUser()
isUserLogon.value = currentUser.username
setRoleForm()
})
onBeforeRouteUpdate((to) => {
setOperationDetails(to.params.id)
const version = to.query.version ? to.query.version : configVersion
setOperationDetails(to.params.id, version)
responseHeaderTitle.value = 'TYPICAL SUCCESSFUL RESPONSE'
setRoleForm()
})
@ -183,7 +200,7 @@ onBeforeRouteUpdate((to) => {
<input
type="text"
v-model="header"
placeholder="Request Header (Heeader1:Value1::Header2:Value2)"
placeholder="Request Header (Header1:Value1::Header2:Value2)"
/>
</div>
<div class="flex-preview-panel">
@ -229,6 +246,7 @@ onBeforeRouteUpdate((to) => {
</div>
</el-form>
<!--<div v-show="showValidations">-->
<el-divider class="divider" />
<div>
<p>{{ $t('preview.validations') }}:</p>
<!--TODO: implementation; replace hard coded.-->
@ -239,6 +257,7 @@ onBeforeRouteUpdate((to) => {
</ul>
</div>
</div>
<el-divider class="divider" />
<div v-show="showPossibleErrors">
<p>{{ $t('preview.possible_errors') }}:</p>
<ul>
@ -247,6 +266,7 @@ onBeforeRouteUpdate((to) => {
</li>
</ul>
</div>
<el-divider class="divider" />
<div v-show="showConnectorMethods">
<p>{{ $t('preview.connector_methods') }}:</p>
<ul>
@ -255,6 +275,13 @@ onBeforeRouteUpdate((to) => {
</li>
</ul>
</div>
<el-divider class="divider" />
<div>
<p class="footnote">
Version: {{ footNote.version }}, function_name: by {{ footNote.functionName }},
operation_id: {{ footNote.functionName }}, Message Tags: {{ footNote.messageTags }}
</p>
</div>
<br />
</main>
</template>
@ -330,6 +357,15 @@ li {
display: flex;
flex-direction: row;
}
.footnote {
color: var(--el-color-info);
font-size: 12px;
}
.divider {
border-top: 1px #253047 solid;
margin-left: -25px;
padding-right: 50px;
}
#search-input {
-webkit-border-top-right-radius: 0;
-moz-border-top-right-radius: 0;

View File

@ -1,10 +1,13 @@
<script lang="ts">
import { reactive, ref, onBeforeMount, onMounted, inject } from 'vue'
import { Search, Star } from '@element-plus/icons-vue'
import { obpResourceDocsKey } from '@/obp/keys'
import { Search } from '@element-plus/icons-vue'
import { inject, onBeforeMount, onMounted, reactive, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { getMyAPICollections, getMyAPICollectionsEndpoint } from '../obp'
import { OBP_API_VERSION, getMyAPICollections, getMyAPICollectionsEndpoint } from '../obp'
import { getGroupedResourceDocs } from '../obp/resource-docs'
import { SEARCH_LINKS_COLOR as searchLinksColorSetting } from '../obp/style-setting'
const operationIdTitle = {}
const resourceDocs = ref({})
const docs = ref({})
const groups = ref({})
const sortedKeys = ref([])
@ -16,6 +19,7 @@ const form = reactive({
const apiCollections = ref({})
const apiCollectionsEndpointMapping = ref({})
const apiCollectionsEndpoint = ref({})
const searchLinksColor = ref(searchLinksColorSetting)
const clearActiveTab = () => {
const activeTabs = document.querySelectorAll('.active-api-router-tab')
@ -49,16 +53,39 @@ export const initializeAPICollections = async () => {
<script setup lang="ts">
const route = useRoute()
let selectedVersion = route.query.version ? route.query.version : `OBP${OBP_API_VERSION}`
onBeforeMount(async () => {
docs.value = inject('OBP-GroupedResourceDocs')!
resourceDocs.value = inject(obpResourceDocsKey)!
docs.value = getGroupedResourceDocs(selectedVersion, resourceDocs.value)
groups.value = JSON.parse(JSON.stringify(docs.value))
activeKeys.value = Object.keys(groups.value)
sortedKeys.value = activeKeys.value.sort()
await initializeAPICollections()
setTabActive(route.params.id)
let element = document.getElementById("selected-api-version")
if (element !== null) {
element.textContent = selectedVersion;
}
})
onMounted(() => {
routeToFirstAPI()
})
watch(
() => route.query.version,
async (version) => {
selectedVersion = version
docs.value = getGroupedResourceDocs(version, resourceDocs.value)
groups.value = JSON.parse(JSON.stringify(docs.value))
activeKeys.value = Object.keys(groups.value)
sortedKeys.value = activeKeys.value.sort()
await initializeAPICollections()
routeToFirstAPI()
}
)
const routeToFirstAPI = () => {
let element
const elements = document.getElementsByClassName('api-router-link')
const id = route.params.id
@ -71,9 +98,9 @@ onMounted(() => {
if (element) {
element.click()
} else {
elements.item(0).click()
if (elements.item(0)) elements.item(0).click()
}
})
}
const sortLinks = (items: any) => {
const uniqueLinks = {}
@ -103,20 +130,23 @@ const setActive = (event) => {
}
}
const isKeyFound = (keys, item) => keys.every((k) => item.toLowerCase().includes(k))
const filterKeys = (keys, key) => {
const splitKey = key.split(' ').map((k) => k.toLowerCase())
return keys.filter((title) => {
const isGroupFound = title.toLowerCase().includes(key.toLowerCase())
const items = docs.value[title].filter((item) =>
item.summary.toLowerCase().includes(key.toLowerCase())
const isGroupFound = isKeyFound(splitKey, title)
const items = docs.value[title].filter(
(item) => isGroupFound || isKeyFound(splitKey, item.summary)
)
groups.value[title] = items
return isGroupFound || items.length > 0
})
}
const searchEvent = (event) => {
if (event) {
sortedKeys.value = filterKeys(activeKeys.value, event)
const searchEvent = (value) => {
if (value) {
sortedKeys.value = filterKeys(activeKeys.value, value)
} else {
groups.value = JSON.parse(JSON.stringify(docs.value))
sortedKeys.value = Object.keys(groups.value).sort()
@ -127,56 +157,27 @@ const searchEvent = (event) => {
<template>
<el-row>
<el-col :span="24">
<el-input
v-model="form.search"
placeholder="Search"
:prefix-icon="Search"
@input="searchEvent"
/>
<el-input v-model="form.search" placeholder="Search" :prefix-icon="Search" @input="searchEvent" />
</el-col>
</el-row>
<el-collapse v-model="activeKeys">
<el-collapse-item title="My Collections" v-show="showMyCollections" name="my-collections">
<el-collapse-item
v-for="(api, key) of apiCollections"
:key="key"
:title="api.api_collection_name"
:name="api.api_collection_name"
class="child-collapse"
>
<el-collapse-item v-for="(api, key) of apiCollections" :key="key" :title="api.api_collection_name"
:name="api.api_collection_name" class="child-collapse">
<div class="el-tabs--right">
<div
v-for="(value, key) of apiCollectionsEndpoint[api.api_collection_name]"
:key="key"
class="api-router-tab"
@click="setActive"
>
<RouterLink
:to="{ name: 'api', params: { id: value } }"
:id="value"
active-class="active-api-router-link"
class="api-router-link"
>{{ operationIdTitle[value] }}</RouterLink
>
<div v-for="(value, key) of apiCollectionsEndpoint[api.api_collection_name]" :key="key" class="api-router-tab"
@click="setActive">
<RouterLink :to="{ name: 'api', params: { id: value }, query: { version: selectedVersion } }" :id="value"
active-class="active-api-router-link" class="api-router-link">{{ operationIdTitle[value] }}</RouterLink>
</div>
</div>
</el-collapse-item>
</el-collapse-item>
<el-collapse-item v-for="key in sortedKeys" :title="key" :key="key" :name="key">
<div class="el-tabs--right">
<div
v-for="(value, key) of sortLinks(groups[key])"
:key="value"
class="api-router-tab"
@click="setActive"
>
<RouterLink
active-class="active-api-router-link"
class="api-router-link"
:id="value"
:to="{ name: 'api', params: { id: value } }"
>{{ key }}</RouterLink
>
<div v-for="(value, key) of sortLinks(groups[key])" :key="value" class="api-router-tab" @click="setActive">
<RouterLink active-class="active-api-router-link" class="api-router-link" :id="value"
:to="{ name: 'api', params: { id: value }, query: { version: selectedVersion } }">{{ key }}</RouterLink>
</div>
</div>
</el-collapse-item>
@ -206,13 +207,14 @@ const searchEvent = (event) => {
.api-router-tab:hover,
.active-api-router-tab {
border-left: 2px solid #52b165;
border-left: 2px solid v-bind(searchLinksColor);
}
.api-router-tab:hover .api-router-link,
.active-api-router-link {
color: #52b165;
color: v-bind(searchLinksColor);
}
.favorite {
cursor: pointer;
line-height: 2;
@ -222,6 +224,7 @@ const searchEvent = (event) => {
padding: 12px;
color: #39455f;
}
.child-collapse {
margin-left: 15px;
}

View File

@ -1,6 +1,5 @@
{
"header": {
"portal_home": "Portal Home",
"portal_home": "Portal Home",
"api_explorer": "API Explorer",
"api_manager": "API Manager",

View File

@ -3,12 +3,13 @@ import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import App from './App.vue'
import router from './router'
import appRouter from './router'
import { createI18n } from 'vue-i18n'
import { languages, defaultLocale } from './language'
import { getOBPResourceDocs, getGroupedResourceDocs } from './obp/resource-docs'
import { getMyAPICollections, getMyAPICollectionsEndpoint } from './obp'
import { cache as cacheResourceDocs, cacheDoc as cacheResourceDocsDoc } from './obp/resource-docs'
import { cache as cacheMessageDocs, cacheDoc as cacheMessageDocsDoc } from './obp/message-docs'
import { OBP_API_VERSION, getMyAPICollections, getMyAPICollectionsEndpoint } from './obp'
import { getOBPGlossary } from './obp/glossary'
import 'element-plus/dist/index.css'
@ -16,47 +17,108 @@ import './assets/main.css'
import '@fontsource/roboto/300.css'
import '@fontsource/roboto/400.css'
import '@fontsource/roboto/700.css'
;(async () => {
import { obpApiActiveVersionsKey, obpApiHostKey, obpGlossaryKey, obpGroupedMessageDocsKey, obpGroupedResourceDocsKey, obpMyCollectionsEndpointKey, obpResourceDocsKey } from './obp/keys'
import { getCacheStorageInfo } from './obp/common-functions'
(async () => {
const app = createApp(App)
const router = await appRouter()
try {
const worker = new Worker('/js/worker/web-worker.js')
const isDataSetup = await setupData(app, worker)
const cache = await caches.open('obp-resource-docs-cache')
const response = await cache.match('/operationid')
let docs
if (response) {
docs = await response.json()
} else {
docs = await getOBPResourceDocs()
await cache.put('/operationid', new Response(JSON.stringify(docs)))
}
const groupedDocs = await getGroupedResourceDocs(docs)
app.provide('OBP-ResourceDocs', docs)
app.provide('OBP-GroupedResourceDocs', groupedDocs)
app.provide('OBP-API-Host', import.meta.env.VITE_OBP_API_HOST)
const glossary = await getOBPGlossary()
app.provide('OBP-Glossary', glossary)
const messages = Object.assign(languages)
const i18n = createI18n({
locale: defaultLocale,
fallbackLocale: 'ES',
messages
})
app.provide('i18n', i18n)
const apiCollections = (await getMyAPICollections()).api_collections
if (apiCollections) {
for (const { api_collection_name } of apiCollections) {
const apiCollectionsEndpoint = (
await getMyAPICollectionsEndpoint(api_collection_name)
).api_collection_endpoints.map((api) => api.operation_id)
app.provide('OBP-MyCollectionsEndpoint', apiCollectionsEndpoint)
app.use(ElementPlus)
app.use(i18n)
app.use(createPinia())
app.use(router)
app.mount('#app')
if (!isDataSetup) router.replace({ path: 'api-server-error' })
app.config.errorHandler = (error) => {
console.log(error)
router.replace({ path: 'error' })
}
} catch (error) {
console.log(error)
router.replace({ path: 'error' })
}
const messages = Object.assign(languages)
const i18n = createI18n({
locale: defaultLocale,
fallbackLocale: 'ES',
messages
})
app.provide('i18n', i18n)
app.use(ElementPlus)
app.use(i18n)
app.use(createPinia())
app.use(router)
app.mount('#app')
})()
async function setupData(app: App<Element>, worker: Worker) {
try {
// 'open': Returns a Promise that resolves to the Cache object matching the cacheName(obp-resource-docs-cache) (a new cache is created if it doesn't already exist.)
const cacheStorageOfResourceDocs = await caches.open('obp-resource-docs-cache') // Please note: The global 'caches' read-only property returns the 'CacheStorage' object associated with the current context.
// 'match': Checks if a given Request is a key in any of the Cache objects that the CacheStorage object tracks, and returns a Promise that resolves to that match.
const cachedResponseOfResourceDocs = await cacheStorageOfResourceDocs.match('/')
// 'open': Returns a Promise that resolves to the Cache object matching the cacheName(obp-message-docs-cache) (a new cache is created if it doesn't already exist.)
const cacheStorageOfMessageDocs = await caches.open('obp-message-docs-cache') // Please note: The global 'caches' read-only property returns the 'CacheStorage' object associated with the current context.
// 'match': Checks if a given Request is a key in any of the Cache objects that the CacheStorage object tracks, and returns a Promise that resolves to that match.
const cachedResponseOfMessageDocs = await cacheStorageOfMessageDocs.match('/')
// Listen to Web worker
worker.onmessage = async (event) => {
// Update cache docs data in the background
if (event.data === 'update-resource-docs') {
await cacheResourceDocsDoc(cacheStorageOfResourceDocs)
console.log('Resource Docs cache was updated.')
const storageInfo = await getCacheStorageInfo()
console.log(storageInfo)
}
if (event.data === 'update-message-docs') {
await cacheMessageDocsDoc(cacheStorageOfMessageDocs)
console.log('Message Docs cache was updated.')
}
}
const { resourceDocs, groupedDocs } = await cacheResourceDocs(
cacheStorageOfResourceDocs,
cachedResponseOfResourceDocs,
worker
)
const messageDocs = await cacheMessageDocs(
cacheStorageOfMessageDocs,
cachedResponseOfMessageDocs,
worker
)
// Provide data to a component's descendants
// App-level provides are available to all components rendered in the app
// Info: https://vuejs.org/guide/components/provide-inject.html
app.provide(obpResourceDocsKey, resourceDocs)
app.provide(obpApiActiveVersionsKey, Object.keys(resourceDocs).sort())
app.provide(obpGroupedResourceDocsKey, groupedDocs)
app.provide(obpGroupedMessageDocsKey, messageDocs)
app.provide(obpApiHostKey, import.meta.env.VITE_OBP_API_HOST)
const glossary = await getOBPGlossary()
app.provide(obpGlossaryKey, glossary)
const apiCollections = (await getMyAPICollections()).api_collections
if (apiCollections && apiCollections.length > 0) {
//Uncomment this when other collection will be supported.
//for (const { api_collection_name } of apiCollections) {
// const apiCollectionsEndpoint = (
// await getMyAPICollectionsEndpoint(api_collection_name)
// ).api_collection_endpoints.map((api) => api.operation_id)
// app.provide(obpMyCollectionsEndpointKey, apiCollectionsEndpoint)
//}
const apiCollectionsEndpoint = (
await getMyAPICollectionsEndpoint('Favourites')
).api_collection_endpoints.map((api) => api.operation_id)
app.provide(obpMyCollectionsEndpointKey, apiCollectionsEndpoint)
} else {
app.provide(obpMyCollectionsEndpointKey, undefined)
}
return true
} catch (error) {
app.provide(obpApiActiveVersionsKey, [OBP_API_VERSION])
return false
}
}

6
src/obp/api-version.ts Normal file
View File

@ -0,0 +1,6 @@
import { OBP_API_VERSION, get } from '../obp'
// Get API Versions
export async function getOBPAPIVersions(): Promise<any> {
return await get(`obp/${OBP_API_VERSION}/api/versions`)
}

View File

@ -0,0 +1,44 @@
import { isServerUp, serverStatus } from '.'
export function updateLoadingInfoMessage(logMessage: string) {
// 1. Select the div element using the id property
const spinner = document.getElementById('loading-api-spinner')
// 2. Select the div element using the id property
let p = document.getElementById('loading-api-info')
// 3. Add the text content
if (p !== null) {
p.textContent = logMessage
} else {
p = document.createElement('p')
}
// 4. Append the p element to the div element
spinner?.appendChild(p)
}
export function updateServerStatus() {
const oElem = document.getElementById('backend-status')
serverStatus()
.then((body) => {
if (oElem) {
Object.values(body).every((i) => i === true)
? (oElem.className = 'server-is-online')
: (oElem.className = 'server-is-offline')
}
})
.catch((error) => {
console.log(error)
if (oElem) {
oElem.className = 'server-is-offline'
}
})
}
export async function getCacheStorageInfo() {
const message = await navigator.storage.estimate().then((estimate) => {
const percent = ((estimate.usage / estimate.quota) * 100).toFixed(2)
const quota = (estimate.quota / 1024 / 1024).toFixed(2) + 'MB'
const message = `You're currently using about ${percent}% of your estimated storage quota ${quota}`
return message
}).catch((error) => {return `Cannot estimate Cache Storage quota. ${error}`})
return message
}

View File

@ -1,13 +1,10 @@
import { Any, GetAny, Version, API, get } from 'obp-typescript'
import type { APIClientConfig } from 'obp-typescript'
const clientConfig: APIClientConfig = {
baseUri: import.meta.env.VITE_OBP_API_HOST,
version: Version.v510,
withFixedVersion: true
}
import { OBP_API_VERSION, get } from '../obp'
import { updateLoadingInfoMessage } from './common-functions'
// Get Glossary
export async function getOBPGlossary(): Promise<any> {
return await get<API.Any>(clientConfig, Any)(GetAny)('/api/glossary')
const logMessage = `Loading glossary { version: ${OBP_API_VERSION} }`
console.log(logMessage)
updateLoadingInfoMessage(logMessage)
return await get(`obp/${OBP_API_VERSION}/api/glossary`)
}

View File

@ -1,15 +1,23 @@
import superagent from 'superagent'
import { Version } from 'obp-typescript'
const version = import.meta.env.VITE_OBP_API_VERSION
export const OBP_API_VERSION = import.meta.env.VITE_OBP_API_VERSION
const default_collection_name = 'Favourites'
export async function serverStatus(): Promise<any> {
return (await superagent.get(`/api/status`)).body
}
export async function isServerUp(): Promise<boolean> {
//Set the status to offline/down only if all the resource data is not availalbe.
return !Object.values(await serverStatus()).every((isTrue) => !isTrue)
}
export async function get(path: string): Promise<any> {
try {
return (await superagent.get(`/api/get?path=${path}`)).body
} catch (error) {
console.log(error)
return error
return { error }
}
}
@ -18,7 +26,7 @@ export async function create(path: string, body: any): Promise<any> {
return (await superagent.post(`/api/create?path=${path}`).send(JSON.parse(body))).body
} catch (error) {
console.log(error)
return error
return { error }
}
}
@ -27,7 +35,7 @@ export async function update(path: string, body: any): Promise<any> {
return (await superagent.put(`/api/update?path=${path}`).send(JSON.parse(body))).body
} catch (error) {
console.log(error)
return error
return { error }
}
}
@ -36,7 +44,7 @@ export async function discard(path: string): Promise<any> {
return (await superagent.delete(`/api/delete?path=${path}`)).body
} catch (error) {
console.log(error)
return error
return { error }
}
}
@ -45,12 +53,13 @@ export async function getCurrentUser(): Promise<any> {
return (await superagent.get(`/api/user/current`)).body
} catch (error) {
console.log(error)
return { error }
}
}
export async function createEntitlement(bankId: string, roleName: string): Promise<any> {
const userId = (await getCurrentUser()).user_id
const url = `/obp/${version}/users/${userId}/entitlements`
const url = `/obp/${OBP_API_VERSION}/users/${userId}/entitlements`
const body = {
role_name: roleName,
bank_id: bankId
@ -59,7 +68,7 @@ export async function createEntitlement(bankId: string, roleName: string): Promi
}
export async function createMyAPICollection(): Promise<any> {
const url = `/obp/${version}/my/api-collections`
const url = `/obp/${OBP_API_VERSION}/my/api-collections`
const body = {
api_collection_name: default_collection_name,
is_sharable: true
@ -68,7 +77,7 @@ export async function createMyAPICollection(): Promise<any> {
}
export async function createMyAPICollectionEndpoint(operation_id: string): Promise<any> {
const url = `/obp/${version}/my/api-collections/${default_collection_name}/api-collection-endpoints`
const url = `/obp/${OBP_API_VERSION}/my/api-collections/${default_collection_name}/api-collection-endpoints`
const body = {
operation_id
}
@ -76,14 +85,14 @@ export async function createMyAPICollectionEndpoint(operation_id: string): Promi
}
export async function deleteMyAPICollectionEndpoint(operation_id: string): Promise<any> {
const url = `/obp/${version}/my/api-collections/${default_collection_name}/api-collection-endpoints/${operation_id}`
const url = `/obp/${OBP_API_VERSION}/my/api-collections/${default_collection_name}/api-collection-endpoints/${operation_id}`
return await discard(url)
}
export async function getMyAPICollections(): Promise<any> {
return await get(`/obp/${version}/my/api-collections`)
return await get(`/obp/${OBP_API_VERSION}/my/api-collections`)
}
export async function getMyAPICollectionsEndpoint(collectionName: string): Promise<any> {
return await get(`/obp/${version}/my/api-collections/${collectionName}/api-collection-endpoints`)
return await get(`/obp/${OBP_API_VERSION}/my/api-collections/${collectionName}/api-collection-endpoints`)
}

9
src/obp/keys.ts Normal file
View File

@ -0,0 +1,9 @@
import type { InjectionKey } from 'vue'
export const obpResourceDocsKey = Symbol('OBP-ResourceDocs') as InjectionKey<any>
export const obpApiActiveVersionsKey = Symbol('OBP-APIActiveVersions') as InjectionKey<any>
export const obpGroupedResourceDocsKey = Symbol('OBP-GroupedResourceDocs') as InjectionKey<any>
export const obpGroupedMessageDocsKey = Symbol('OBP-GroupedMessageDocs') as InjectionKey<any> // This cause an issue
export const obpApiHostKey = Symbol('OBP-API-Host') as InjectionKey<any>
export const obpGlossaryKey = Symbol('OBP-Glossary') as InjectionKey<any>
export const obpMyCollectionsEndpointKey = Symbol('OBP-MyCollectionsEndpoint') as InjectionKey<any>

62
src/obp/message-docs.ts Normal file
View File

@ -0,0 +1,62 @@
import { OBP_API_VERSION, get, isServerUp } from '../obp'
import { updateLoadingInfoMessage } from './common-functions'
export const connectors = [
'kafka_vSept2018',
'akka_vDec2018',
'rest_vMar2019',
'stored_procedure_vDec2019'
]
// Get Message Docs
export async function getOBPMessageDocs(item: string): Promise<any> {
const logMessage = `Loading message docs { connector: ${item} }`
console.log(logMessage)
updateLoadingInfoMessage(logMessage)
return await get(`obp/${OBP_API_VERSION}/message-docs/${item}`)
}
export function getGroupedMessageDocs(docs: any): Promise<any> {
return docs.message_docs.reduce((values: any, doc: any) => {
const tag = doc.adapter_implementation.group.replace('-', '').trim()
;(values[tag] = values[tag] || []).push(doc)
return values
}, {})
}
export async function cacheDoc(cacheStorageOfMessageDocs: any): Promise<any> {
const messageDocs = await connectors.reduce(async (agroup: any, connector: any) => {
const logMessage = `Caching message docs { connector: ${connector} }`
console.log(logMessage)
updateLoadingInfoMessage(logMessage)
const group = await agroup
const docs = await getOBPMessageDocs(connector)
if (!Object.keys(docs).includes('code')) {
group[connector] = getGroupedMessageDocs(docs)
}
return group
}, Promise.resolve({}))
await cacheStorageOfMessageDocs.put('/', new Response(JSON.stringify(messageDocs)))
return messageDocs
}
async function getCacheDoc(cacheStorageOfMessageDocs: any): Promise<any> {
return await cacheDoc(cacheStorageOfMessageDocs)
}
export async function cache(
cacheStorage: any,
cachedResponse: any,
worker: any
): Promise<any> {
try {
worker.postMessage('update-message-docs')
return await cachedResponse.json()
} catch (error) {
console.warn('No message docs cache or malformed cache.')
console.log('Caching message docs...')
const isServerActive = await isServerUp()
if (!isServerActive) throw new Error('API Server is not responding.')
return await getCacheDoc(cacheStorage)
}
}

View File

@ -1,26 +1,75 @@
import { Any, GetAny, Version, API, get } from 'obp-typescript'
import type { APIClientConfig } from 'obp-typescript'
const clientConfig: APIClientConfig = {
baseUri: import.meta.env.VITE_OBP_API_HOST,
version: Version.v510,
withFixedVersion: true
}
import { OBP_API_VERSION, get, isServerUp } from '../obp'
import { getOBPAPIVersions } from '../obp/api-version'
import { updateLoadingInfoMessage } from './common-functions'
// Get Resource Docs
export async function getOBPResourceDocs(): Promise<any> {
return await get<API.Any>(clientConfig, Any)(GetAny)(`/resource-docs/${Version.v510}/obp`)
export async function getOBPResourceDocs(apiStandardAndVersion: string): Promise<any> {
const logMessage = `Loading API ${apiStandardAndVersion}`
console.log(logMessage)
updateLoadingInfoMessage(logMessage)
return await get(`/obp/${OBP_API_VERSION}/resource-docs/${apiStandardAndVersion}/obp`)
}
export async function getGroupedResourceDocs(docs: any): Promise<any> {
return docs.resource_docs.reduce((values: any, doc: any) => {
doc.tags.forEach((tag: any) => {
;(values[tag] = values[tag] || []).push(doc)
})
export function getGroupedResourceDocs(apiStandardAndVersion: string, docs: any): Promise<any> {
if (apiStandardAndVersion === undefined || docs === undefined) return Promise.resolve<any>({})
return docs[apiStandardAndVersion].resource_docs.reduce((values: any, doc: any) => {
const tag = doc.tags[0] // Group by the first tag at resorce doc
;(values[tag] = values[tag] || []).push(doc)
return values
}, {})
}
export function getOperationDetails(docs: any, operation_id: string): any {
return docs.resource_docs.filter((doc: any) => doc.operation_id === operation_id)[0]
export function getOperationDetails(version: string, operation_id: string, docs: any): any {
return docs[version].resource_docs.filter((doc: any) => doc.operation_id === operation_id)[0]
}
export async function cacheDoc(cacheStorageOfResourceDocs: any): Promise<any> {
const apiVersions = await getOBPAPIVersions()
if (apiVersions) {
const scannedAPIVersions = apiVersions.scanned_api_versions
const resourceDocsMapping: any = {}
for (const { apiStandard, API_VERSION } of scannedAPIVersions) {
const logMessage = `Caching API { standard: ${apiStandard}, version: ${API_VERSION} }`
console.log(logMessage)
if (apiStandard) {
const version = `${apiStandard.toUpperCase()}${API_VERSION}`
const resourceDocs = await getOBPResourceDocs(version)
if (version && Object.keys(resourceDocs).includes('resource_docs'))
resourceDocsMapping[version] = resourceDocs
}
updateLoadingInfoMessage(logMessage)
}
await cacheStorageOfResourceDocs.put('/', new Response(JSON.stringify(resourceDocsMapping)))
return resourceDocsMapping
} else {
const resourceDocs = { ['OBP' + OBP_API_VERSION]: await getOBPResourceDocs(OBP_API_VERSION) }
await cacheStorageOfResourceDocs.put('/', new Response(JSON.stringify(resourceDocs)))
return resourceDocs
}
}
async function getCacheDoc(cacheStorageOfResourceDocs: any): Promise<any> {
return await cacheDoc(cacheStorageOfResourceDocs)
}
export async function cache(
cachedStorage: any,
cachedResponse: any,
worker: any
): Promise<any> {
try {
worker.postMessage('update-resource-docs')
const resourceDocs = await cachedResponse.json()
const groupedResourceDocs = getGroupedResourceDocs('OBP' + OBP_API_VERSION, resourceDocs)
return { resourceDocs, groupedDocs: groupedResourceDocs }
} catch (error) {
console.warn('No resource docs cache or malformed cache.')
console.log('Caching resource docs...')
const isServerActive = await isServerUp()
if (!isServerActive) throw new Error('API Server is not responding.')
const resourceDocs = await getCacheDoc(cachedStorage)
const groupedDocs = getGroupedResourceDocs('OBP' + OBP_API_VERSION, resourceDocs)
return { resourceDocs, groupedDocs }
}
}

14
src/obp/style-setting.ts Normal file
View File

@ -0,0 +1,14 @@
//Logo
export const LOGO_URL = import.meta.env.VITE_OBP_LOGO_URL
//Header styles
export const HEADER_LINKS_COLOR = import.meta.env.VITE_OBP_HEADER_LINKS_COLOR || '#39455f'
export const HEADER_LINKS_HOVER_COLOR = import.meta.env.VITE_OBP_HEADER_LINKS_HOVER_COLOR || '#39455f'
export const HEADER_LINKS_BACKGROUND_COLOR =
import.meta.env.VITE_OBP_HEADER_LINKS_BACKGROUND_COLOR || '#eef0f4'
//Search nav styles
export const SEARCH_LINKS_COLOR = import.meta.env.VITE_OBP_LINKS_COLOR || '#52b165'
//Content summary styles
export const SUMMARY_PAGER_LINKS_COLOR = import.meta.env.VITE_OBP_LINKS_COLOR || '#52b165'

View File

@ -1,50 +1,69 @@
import { createRouter, createWebHistory } from 'vue-router'
import GlossaryView from '../views/GlossaryView.vue'
import MessageDocsView from '../views/MessageDocsView.vue'
import BodyView from '../views/BodyView.vue'
import Content from '../components/Content.vue'
import Preview from '../components/Preview.vue'
import NotFoundView from '../views/NotFoundView.vue'
import InternalServerErrorView from '../views/InternalServerErrorView.vue'
import APIServerErrorView from '../views/APIServerErrorView.vue'
import APIServerStatusView from '../views/APIServerStatusView.vue'
import { isServerUp } from '../obp'
const router = createRouter({
history: createWebHistory(),
mode: 'history',
routes: [
{
path: '/',
redirect: '/operationid'
},
{
path: '/glossary',
name: 'glossary',
component: GlossaryView
},
{
path: '/operationid',
name: 'operationid',
component: BodyView
},
{
path: '/operationid/:id',
name: 'operationid-path',
component: BodyView,
children: [
{
path: '',
name: 'api',
components: {
body: Content,
preview: Preview
export default async function router(): Promise<any> {
const isServerActive = await isServerUp()
const router = createRouter({
history: createWebHistory(),
mode: 'history',
routes: [
{
path: '/',
redirect: isServerActive ? '/operationid' : '/api-server-error'
},
{
path: '/status',
name: 'status',
component: APIServerStatusView
},
{
path: '/glossary',
name: 'glossary',
component: isServerActive ? GlossaryView : InternalServerErrorView
},
{
path: '/message-docs/:id',
name: 'message-docs',
component: isServerActive ? MessageDocsView : InternalServerErrorView
},
{
path: '/operationid',
name: 'operationid',
component: isServerActive ? BodyView : InternalServerErrorView
},
{
path: '/operationid/:id',
name: 'operationid-path',
component: BodyView,
children: [
{
path: '',
name: 'api',
components: {
body: Content,
preview: Preview
}
}
}
]
},
{
path: '/callback',
name: 'callback',
component: BodyView
},
{ path: '/:pathMatch(.*)*', name: 'notFound', component: NotFoundView }
]
})
export default router
]
},
{
path: '/callback',
name: 'callback',
component: isServerActive ? BodyView : InternalServerErrorView
},
{ path: '/error', name: 'error', component: InternalServerErrorView },
{ path: '/api-server-error', name: 'apiServerError', component: APIServerErrorView },
{ path: '/:pathMatch(.*)*', name: 'notFound', component: NotFoundView }
]
})
return router
}

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { ref } from 'vue'
const version = ref(__APP_VERSION__)
</script>
<template>
<main>500 | The API server is not responding.</main>
<span>Version: {{ version }}</span>
</template>
<style scoped>
main {
margin-top: -60px;
display: flex;
justify-content: center;
align-items: center;
color: #39455f;
font-family: 'roboto';
font-size: 30px;
}
span {
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -0,0 +1,81 @@
<script setup lang="ts">
import { ref, onBeforeMount } from 'vue'
import { SuccessFilled, RemoveFilled } from '@element-plus/icons-vue'
import { serverStatus } from './../obp'
const version = ref(__APP_VERSION__)
const status = ref({})
onBeforeMount(async () => {
status.value = await serverStatus()
})
</script>
<template>
<main>
<div class="content">
<div v-for="(value, name, index) in status">
<div v-if="name === 'status'" class="status">
<el-icon v-if="value === true" style="vertical-align: middle; color: green; width: auto"
><SuccessFilled
/></el-icon>
<el-icon v-else style="vertical-align: middle; color: red"><RemoveFilled /></el-icon>
<span class="main-status-label">{{ name }}</span>
</div>
<div v-else class="sub-status">
<span class="status-label">{{ name }}</span>
&nbsp;&nbsp;&nbsp;<el-divider />&nbsp;&nbsp;&nbsp;
<el-icon
v-if="value === true"
style="vertical-align: middle; color: green; font-size: 16px"
><SuccessFilled
/></el-icon>
<el-icon v-else style="vertical-align: middle; color: red"><RemoveFilled /></el-icon>
</div>
</div>
</div>
</main>
<span>Version: {{ version }}</span>
</template>
<style scoped>
main {
margin-top: -60px;
display: flex;
justify-content: center;
align-items: center;
color: #39455f;
font-family: 'roboto';
font-size: 30px;
}
span {
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
}
.content {
width: 30%;
}
.main-status-label {
display: block;
font-size: 28px;
padding: 15px;
}
.main-status-label:first-letter {
text-transform: uppercase;
}
.status-label {
font-size: 16px;
}
.status {
display: inline-grid;
justify-content: center;
align-items: center;
width: 100%;
}
.sub-status {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
}
</style>

View File

@ -1,6 +1,9 @@
<script setup lang="ts">
import SearchNav from '../components/SearchNav.vue'
import Menu from '../components/Menu.vue'
import Collections from '../components/Collections.vue'
import { inject } from 'vue'
</script>
<template>
@ -11,6 +14,9 @@ import Menu from '../components/Menu.vue'
</el-aside>
<el-main>
<el-container class="main">
<!--<el-header class="collections">
<Collections />
</el-header>-->
<el-header class="menu">
<Menu />
</el-header>
@ -49,4 +55,10 @@ import Menu from '../components/Menu.vue'
background-color: #151d30;
max-height: 100vh;
}
.collections {
margin-left: -20px;
margin-right: -20px;
padding-left: 20px;
padding-right: 20px;
}
</style>

View File

@ -1,8 +1,9 @@
<script setup lang="ts">
import { reactive, ref, onBeforeMount, onMounted, inject } from 'vue'
import SearchNav from '../components/GlossarySearchNav.vue'
import { obpGlossaryKey } from '@/obp/keys';
const glossary = ref(inject('OBP-Glossary')!.glossary_items)
const glossary = ref(inject(obpGlossaryKey)!.glossary_items)
</script>
<template>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { ref } from 'vue'
const version = ref(__APP_VERSION__)
</script>
<template>
<main>500 | Internal Server Error</main>
<span>Version: {{ version }}</span>
</template>
<style scoped>
main {
margin-top: -60px;
display: flex;
justify-content: center;
align-items: center;
color: #39455f;
font-family: 'roboto';
font-size: 30px;
}
span {
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@ -0,0 +1,143 @@
<script setup lang="ts">
import { reactive, ref, onBeforeMount, onMounted, inject, watch } from 'vue'
import { useRoute } from 'vue-router'
import SearchNav from '../components/MessageDocsSearchNav.vue'
import { connectors } from '../obp/message-docs'
import { obpGroupedMessageDocsKey } from '@/obp/keys';
let connector = connectors[0]
const route = useRoute()
const groupedMessageDocs = ref(inject(obpGroupedMessageDocsKey)!)
const messageDocs = ref({})
onBeforeMount(() => {
setDoc()
})
watch(
() => route.params.id,
async (id) => {
setDoc()
}
)
const setDoc = () => {
const paramConnector = route.params.id
if (connectors.includes(paramConnector)) {
connector = paramConnector
}
messageDocs.value = groupedMessageDocs.value[connector]
}
function showRequiredFieldInfo(value: any) {
return (JSON.stringify(value, null) === '{}' || JSON.stringify(value, null) === '')
}
function showDependentEndpoints(value: any) {
return value.lengtt > 0
}
</script>
<template>
<el-container>
<el-aside class="search-nav" width="20%">
<SearchNav />
</el-aside>
<el-main>
<el-container class="main">
<el-container>
<main>
<el-backtop :right="100" :bottom="100" target="main" />
<div v-for="(group, key) of messageDocs" :key="key">
<div v-for="(value, key) of group" :key="value">
<el-divider content-position="left">{{ value.process }}</el-divider>
<a v-bind:href="`#${value.process}`" :id="value.process">
<h2>{{ value.description }}</h2>
</a>
<el-descriptions direction="vertical" :column="1" border>
<el-descriptions-item label="Outbound Topic">
{{ value.outbound_topic }}
</el-descriptions-item>
<el-descriptions-item label="Inbound Topic">
{{ value.inbound_topic }}
</el-descriptions-item>
<el-descriptions-item label="Outbound Message">
<pre>{{ JSON.stringify(value.example_outbound_message, null, 4) }}</pre>
</el-descriptions-item>
<el-descriptions-item label="Inbound Message">
<pre>{{ JSON.stringify(value.example_inbound_message, null, 4) }}</pre>
</el-descriptions-item>
<el-descriptions-item v-if="showRequiredFieldInfo(value.requiredFieldInfo)" label="Required Fields">
<pre>{{ JSON.stringify(value.requiredFieldInfo, null, 4) }}</pre>
</el-descriptions-item>
<el-descriptions-item v-if="showDependentEndpoints(value.dependent_endpoints)"
label="Dependent Endpoints">
<ul>
<li v-for="(endpoint, key) of value.dependent_endpoints">
{{ endpoint.version }}: {{ endpoint.name }}
</li>
</ul>
</el-descriptions-item>
</el-descriptions>
<el-divider content-position="right">{{ value.process }}</el-divider>
<br />
<br />
<br />
<br />
</div>
</div>
</main>
</el-container>
</el-container>
</el-main>
</el-container>
</template>
<style scoped>
.main {
max-height: 90vh;
}
template {
overflow: auto;
max-height: 900px;
}
main {
margin: 25px;
color: #39455f;
font-family: 'Roboto';
}
span {
font-size: 28px;
}
div {
font-size: 14px;
}
pre {
font-family: 'Roboto';
}
.content :deep(strong) {
font-family: 'Roboto';
}
a {
text-decoration: none;
color: #39455f;
}
.content :deep(a) {
text-decoration: none;
color: #ffffff;
font-family: 'Roboto';
font-size: 14px;
border-radius: 3px;
background-color: #52b165;
padding: 1px;
}
.content :deep(a):hover {
background-color: #39455f;
}
</style>

View File

@ -34,6 +34,7 @@ export default defineConfig({
__VUE_I18N_FULL_INSTALL__: true,
__VUE_I18N_LEGACY_API__: false,
__INTLIFY_PROD_DEVTOOLS__: false,
__APP_VERSION__: JSON.stringify(process.env.npm_package_version)
},
server:{
proxy: {

2990
yarn.lock

File diff suppressed because it is too large Load Diff