Skip to content

Commit

Permalink
Implement OpenApi TypeScript Client for Project Overview (#3703)
Browse files Browse the repository at this point in the history
  • Loading branch information
lorriborri authored Dec 12, 2024
1 parent 20749ba commit 72972ab
Show file tree
Hide file tree
Showing 20 changed files with 208 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ protected Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.Authorization
.requestMatchers(APIConstants.API_USER + "**").hasAnyRole(RoleConstants.ROLE_USER, RoleConstants.ROLE_SUPERADMIN)
.requestMatchers(APIConstants.API_PROJECT + "**").hasAnyRole(RoleConstants.ROLE_USER, RoleConstants.ROLE_SUPERADMIN)
.requestMatchers(APIConstants.API_OWNER + "**").hasAnyRole(RoleConstants.ROLE_OWNER, RoleConstants.ROLE_SUPERADMIN)
.requestMatchers(APIConstants.API_PROJECTS).hasAnyRole(RoleConstants.ROLE_USER, RoleConstants.ROLE_SUPERADMIN, RoleConstants.ROLE_OWNER)
.requestMatchers(APIConstants.API_ANONYMOUS + "**").permitAll()
.requestMatchers(APIConstants.ERROR_PAGE).permitAll()
.requestMatchers(APIConstants.ACTUATOR + "**").permitAll()
Expand Down
1 change: 1 addition & 0 deletions sechub-web-ui-solution/docker/Web-UI-Debian.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ COPY nginx/ /etc/nginx/
# Copy content to web server's document root
COPY --from=builder "${WEB_UI_ARTIFACTS}/dist" "${HTDOCS_FOLDER}"
COPY htdocs/ "${HTDOCS_FOLDER}/"
COPY nginx/config.json "${HTDOCS_FOLDER}/"

# Create self-signed certificate
RUN cd /tmp && \
Expand Down
5 changes: 5 additions & 0 deletions sechub-web-ui-solution/docker/nginx/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
// HOST represents the URL of the API client. In this case, it's the URL of the SPA because we proxy API requests to the backend to sidestep any CORS issues.
"HOST": "https://localhost",
"LOCAL_DEV": false
}
5 changes: 5 additions & 0 deletions sechub-web-ui-solution/docker/nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ http {
add_header Content-Type text/plain;
}

# loading environment variables from config.json during runtime
location /config.json {
root /var/www/html;
}

ssl_certificate /etc/nginx/certificates/sechub-web-ui.cert;
ssl_certificate_key /etc/nginx/certificates/sechub-web-ui.key;
ssl_protocols TLSv1.2 TLSv1.3;
Expand Down
2 changes: 2 additions & 0 deletions sechub-web-ui-solution/env
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ WEB_UI_VERSION="0.2.0"
GIT_TAG=""
GIT_BRANCH=""
WEB_UI_SSL_KEYSTORE_ALIAS="undefined"

# Please see docker/nignx/config.json for ENV variables for the SPA itself
10 changes: 10 additions & 0 deletions sechub-web-ui/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This .env file contains the default environment variables
# For local development, create an .env.local file with the desired variable values
# Note: .env.local will be ignored by git
# Local development variables must start with VITE_API
VITE_API_HOST=http://localhost:3000

# Optional variables needed for testing with SecHub Server Basic Auth
VITE_API_LOCAL_DEV=false
VITE_API_USER='example-test-user'
VITE_API_PASSWORD='example-api-token'
40 changes: 30 additions & 10 deletions sechub-web-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ This project is a web application that provides a user interface for the SecHub

### Installation

Install the node version manager (nvm) and use it to install the correct version of Node.js:
Install the node version manager (nvm) and use it to install and use the correct version of Node.js:
You can skip the installation processes if you already have nvm installed and the node version in nvm.

```bash
nvm install
```

```bash
nvm use
Expand All @@ -19,6 +24,14 @@ Install the project dependencies:
npm install
```

### Building openAPI SecHub Client

To generate the SecHub openAPI Client use:

```bash
npm run generate-api-client
```

### Starting the Development Server

To start the development server with hot-reload, run the following command. The server will be accessible at [http://localhost:3000](http://localhost:3000):
Expand All @@ -29,19 +42,26 @@ npm run dev

> Add NODE_OPTIONS='--no-warnings' to suppress the JSON import warnings that happen as part of the Vuetify import mapping. If you are on Node [v21.3.0](https://nodejs.org/en/blog/release/v21.3.0) or higher, you can change this to NODE_OPTIONS='--disable-warning=5401'. If you don't mind the warning, you can remove this from your package.json dev script.
#### Running in Development mode with sechub for testing

1. Start SecHub Integration Test Server (or Docker Server)
2. Configure your `.env.local` file by copying `.env` to `.env.local` and adjusting the variables as needed.
Set `VITE_API_LOCAL_DEV=true`
Set `VITE_API_USER` to your SecHub user
Set `VITE_API_PASSWORD` to your SecHub Api Token
3. Set `VITE_API_HOST` to the URL of your application http://localhost:3000 - this is because of the proxy defined in the Vite dev server to avoid CORS Issues
4. Start the SPA in Development mode (npm run dev)

Happy Testing!

### Building for Production

Set Environment Variables:
Be aware that `npm run build` sets the environment variables at build time.
For deploying runtime ENV please se sechub-web-ui-solution/docker/nginx/conf.json it will override the VITE variables. Be aware that the config.json will be served by nginx.

To build your project for production, use:

```bash
npm run build
```

### Building openAPI SecHub Client

To generate the SecHub openAPI Client use:

```bash
npm run codegen
```

76 changes: 46 additions & 30 deletions sechub-web-ui/src/components/Projects.vue
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
<!-- SPDX-License-Identifier: MIT -->
<template>
<v-card
class="mr-auto"
color="background_paper"
width="70%"
>
<v-toolbar
color="background_paper"
width="70%"
>
<v-card class="mr-auto" color="background_paper" width="70%">
<v-toolbar color="background_paper" width="70%">
<v-toolbar-title>{{ $t('PROJECTS') }}</v-toolbar-title>
</v-toolbar>

<v-list
bg-color="background_paper"
lines="two"
>
<!-- Case when the user is not assigned to any project -->
<div v-if="projects.length === 0 && !loading">
<v-list bg-color="background_paper" lines="two">
<v-list-item v-if="error" class="ma-5" rounded="lg">{{ $t('ERROR_FETCHING_DATA') }}</v-list-item>
<v-list-item v-else class="ma-5" rounded="lg">{{ $t('NO_PROJECTS_ASSIGNED') }}</v-list-item>
</v-list>
</div>

<!-- Iterate over the projects array -->
<v-list v-else bg-color="background_paper" lines="two">
<v-list-item
v-for="(item, i) in items"
v-for="(project, i) in projects"
:key="i"
background="red"
class="ma-5"
rounded="lg"
:value="item"
:value="project"
>
<template #prepend>
<v-icon :class="getIconClass(item)" :icon="item.icon" />
<v-icon v-if="project.isOwned" :class="ownedClass" icon="mdi-cube" />
<v-icon v-else icon="mdi-cube" />
</template>
<template #title>
<span>{{ item.text }}</span>
<span>{{ project.projectId }}</span>
</template>
<template #subtitle>
<span v-if="item.owned">({{ $t('OWNED') }})</span>
<span v-if="project.isOwned">({{ $t('OWNED') }})</span>
<span v-else>({{ $t('MEMBER') }})</span>
</template>
</v-list-item>
Expand All @@ -40,18 +39,35 @@
</template>

<script>
import defaultClient from '@/services/defaultClient'

export default {
data: () => ({
items: [
{ text: 'Project 1', icon: 'mdi-cube', owned: true },
{ text: 'Project 2', icon: 'mdi-cube', owned: false },
{ text: 'Project 3', icon: 'mdi-cube', owned: true },
],
}),
methods: {
getIconClass (item) {
return item.owned ? 'project-owned' : ''
},
name: 'ProjectsComponent',

setup () {
const projects = ref([])
const loading = ref(true)
const error = ref(null)

onMounted(async () => {
try {
projects.value = await defaultClient.withProjectApi.getAssignedProjectDataList()
} catch (err) {
error.value = 'ProjectAPI error fetching assigned projects.'
console.error('ProjectAPI error fetching assigned projects:', err)
} finally {
loading.value = false
}
})

return {
ownedClass: {
'project-owned': true,
},
projects,
loading,
error,
}
},
}
</script>
Expand Down
27 changes: 25 additions & 2 deletions sechub-web-ui/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
// SPDX-License-Identifier: MIT
export const CONFIG = {
HOST: String(import.meta.env.SECHUB_HOST) || '',

import { ref } from 'vue'

const config = ref({
// New ENV must be defined in global.d.ts
HOST: String(import.meta.env.VITE_API_HOST) || '',
USERNAME: String(import.meta.env.VITE_API_USER) || '',
PASSWORD: String(import.meta.env.VITE_API_PASSWORD) || '',
LOCAL_DEV: String(import.meta.env.VITE_API_LOCAL_DEV) || '',
})

// Overrides local environment variables after project compilation
// Utilizes variables from config.json if available
export async function loadConfig () {
try {
const response = await fetch('/config.json')
const runtimeConfig = await response.json()
config.value = { ...config.value, ...runtimeConfig }
} catch (error) {
console.error('Failed to load configuration, using fallback in .env:', error)
}
}

export function useConfig () {
return config
}
4 changes: 3 additions & 1 deletion sechub-web-ui/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
"GREETING": "Welcome",
"PROJECTS": "Projects",
"OWNED": "owned",
"MEMBER": "member"
"MEMBER": "member",
"NO_PROJECTS_ASSIGNED": "You are not assigned to any projects.",
"ERROR_FETCHING_DATA": "Could not load data from Server."
}
9 changes: 6 additions & 3 deletions sechub-web-ui/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@

// Plugins
import { registerPlugins } from '@/plugins'
import { loadConfig } from './config'

// Components
import App from './App.vue'

// Composables
import { createApp } from 'vue'

const app = createApp(App)
loadConfig().then(() => {
const app = createApp(App)

registerPlugins(app)
registerPlugins(app)

app.mount('#app')
app.mount('#app')
})
2 changes: 1 addition & 1 deletion sechub-web-ui/src/plugins/vuetify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { createVuetify } from 'vuetify'
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
export default createVuetify({
theme: {
defaultTheme: 'lightTheme',
defaultTheme: 'darkTheme',
themes: {
lightTheme,
darkTheme,
Expand Down
28 changes: 23 additions & 5 deletions sechub-web-ui/src/services/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
// SPDX-License-Identifier: MIT
import { CONFIG } from 'src/config'
import { useConfig } from '@/config'
import { Configuration } from '@/generated-sources/openapi'

const apiConfig = new Configuration({
basePath: CONFIG.HOST,
// todo: check if cookie set by server is sent back (for auth reasons)
})
let apiConfig: Configuration
const config = useConfig()

if (config.value.LOCAL_DEV) {
// api configuration for local development with basic auth
apiConfig = new Configuration({
basePath: config.value.HOST,
username: config.value.USERNAME,
password: config.value.PASSWORD,
headers: {
'Content-Type': 'application/json',
},
})
} else {
apiConfig = new Configuration({
basePath: config.value.HOST,
headers: {
// todo: check if cookie set by server is sent back (for auth reasons)
'Content-Type': 'application/json',
},
})
}

export default apiConfig
7 changes: 7 additions & 0 deletions sechub-web-ui/src/services/configurationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
import { ConfigurationApi } from '@/generated-sources/openapi'
import apiConfig from './configuration'

const configurationApi = new ConfigurationApi(apiConfig)

export default configurationApi
13 changes: 13 additions & 0 deletions sechub-web-ui/src/services/defaultClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
import configurationApi from './configurationService'
import projectApi from './productAdministrationService'
import systemApi from './systemApiService'

const defaultClient = {
withProjectApi: projectApi,
withSystemApi: systemApi,
withConfigurationApi: configurationApi,

}

export default defaultClient
7 changes: 7 additions & 0 deletions sechub-web-ui/src/services/productAdministrationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
import { ProjectAdministrationApi } from '@/generated-sources/openapi'
import apiConfig from './configuration'

const projectApi = new ProjectAdministrationApi(apiConfig)

export default projectApi
7 changes: 7 additions & 0 deletions sechub-web-ui/src/services/systemApiService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
import { SystemApi } from '@/generated-sources/openapi'
import apiConfig from './configuration'

const systemApi = new SystemApi(apiConfig)

export default systemApi
5 changes: 4 additions & 1 deletion sechub-web-ui/src/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ declare module 'insane';

interface ImportMeta {
env: {
SECHUB_HOST: string
VITE_API_HOST: string,
VITE_API_USER: string,
VITE_API_PASSWORD: string,
VITE_API_LOCAL_DEV: boolean,
}
}
5 changes: 3 additions & 2 deletions sechub-web-ui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
"types": [
"vite/client",
"vite-plugin-vue-layouts/client",
"unplugin-vue-router/client"
"unplugin-vue-router/client",
],
"allowJs": true,
"strict": true,
"strictNullChecks": true,
"noUnusedLocals": true,
// Set to false to enable build with unused imports from the generated OpenAPI client
"noUnusedLocals": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
Expand Down
Loading

0 comments on commit 72972ab

Please sign in to comment.