feat(statuspage): refactor shared styles and modernize components for consistent theming, spacing and APIs
This commit is contained in:
497
readme.md
497
readme.md
@@ -1,205 +1,382 @@
|
||||
# @uptime.link/statuspage
|
||||
a catalog with webcomponents for uptimelink dashboard
|
||||
|
||||
## Install
|
||||
🚀 **A powerful collection of web components for building stunning status pages** — because your users deserve to know what's happening, and you deserve to look good while telling them.
|
||||
|
||||
To install the `@uptime.link/statuspage` module, you can use npm. Make sure you have Node.js and npm installed, then run:
|
||||
Built with [Lit](https://lit.dev/) and TypeScript, these components are designed to be composable, customizable, and production-ready out of the box.
|
||||
|
||||
```sh
|
||||
## Issue Reporting and Security
|
||||
|
||||
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🎨 **Themeable** — Automatic light/dark mode support with CSS custom properties
|
||||
- 📱 **Responsive** — Looks great on everything from mobile to ultrawide
|
||||
- 🔌 **Composable** — Use components individually or build complete status pages
|
||||
- 🎯 **Type-Safe** — Full TypeScript support with exported interfaces
|
||||
- ⚡ **Performant** — Lit-based components with minimal overhead
|
||||
- 🎭 **Pre-built Pages** — Demo, all-green, outage, and maintenance page templates included
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
```bash
|
||||
npm install @uptime.link/statuspage
|
||||
```
|
||||
|
||||
## Usage
|
||||
Or with pnpm:
|
||||
|
||||
The `@uptime.link/statuspage` module provides a collection of web components to build a comprehensive status page for Uptime.link dashboards. This includes headers, status bars, asset selectors, status details, incident lists, and more.
|
||||
```bash
|
||||
pnpm add @uptime.link/statuspage
|
||||
```
|
||||
|
||||
Here’s a detailed guide on how to use each component in your TypeScript project with ESM syntax. We will walk through creating a complete status page using the provided components.
|
||||
---
|
||||
|
||||
### Step-by-Step Example
|
||||
## 🚀 Quick Start
|
||||
|
||||
1. **Setup Your Project:**
|
||||
Import the components and start building:
|
||||
|
||||
Ensure you have a TypeScript project setup. Here's a minimal `tsconfig.json` to get started:
|
||||
```typescript
|
||||
import '@uptime.link/statuspage';
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
Then use them in your HTML:
|
||||
|
||||
```html
|
||||
<upl-statuspage-header></upl-statuspage-header>
|
||||
<upl-statuspage-statusbar></upl-statuspage-statusbar>
|
||||
<upl-statuspage-incidents></upl-statuspage-incidents>
|
||||
<upl-statuspage-footer></upl-statuspage-footer>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Components
|
||||
|
||||
### `<upl-statuspage-header>`
|
||||
|
||||
The main navigation header with branding and action buttons.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `pageTitle` | `string` | `'Status'` | Title displayed in the header |
|
||||
| `logoUrl` | `string` | `''` | URL to your logo image |
|
||||
| `showReportButton` | `boolean` | `false` | Show "Report Incident" button |
|
||||
| `showSubscribeButton` | `boolean` | `false` | Show "Subscribe" button |
|
||||
|
||||
**Events:**
|
||||
- `reportNewIncident` — Fired when report button is clicked
|
||||
- `statusSubscribe` — Fired when subscribe button is clicked
|
||||
|
||||
---
|
||||
|
||||
### `<upl-statuspage-pagetitle>`
|
||||
|
||||
A hero section with title and subtitle.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `pageTitle` | `string` | `'System Status'` | Main heading |
|
||||
| `pageSubtitle` | `string` | `''` | Optional description text |
|
||||
| `centered` | `boolean` | `false` | Center-align the content |
|
||||
|
||||
---
|
||||
|
||||
### `<upl-statuspage-statusbar>`
|
||||
|
||||
The overall status indicator — the heart of any status page.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `overallStatus` | `IOverallStatus` | — | Current system status object |
|
||||
| `loading` | `boolean` | `false` | Show loading skeleton |
|
||||
|
||||
The status object supports: `operational`, `degraded`, `partial_outage`, `major_outage`, `maintenance`
|
||||
|
||||
---
|
||||
|
||||
### `<upl-statuspage-statsgrid>`
|
||||
|
||||
Key metrics at a glance — uptime, response time, incidents.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `currentStatus` | `string` | `'operational'` | Current status indicator |
|
||||
| `uptime` | `number` | `100` | Uptime percentage |
|
||||
| `avgResponseTime` | `number` | `0` | Average response time (ms) |
|
||||
| `totalIncidents` | `number` | `0` | Incident count |
|
||||
| `affectedServices` | `number` | `0` | Currently affected services |
|
||||
| `totalServices` | `number` | `0` | Total monitored services |
|
||||
| `timePeriod` | `string` | `'30 days'` | Time range label |
|
||||
| `loading` | `boolean` | `false` | Show loading skeleton |
|
||||
|
||||
---
|
||||
|
||||
### `<upl-statuspage-assetsselector>`
|
||||
|
||||
Interactive service selector with filtering and search.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `services` | `IServiceStatus[]` | `[]` | Array of service objects |
|
||||
| `filterText` | `string` | `''` | Search filter text |
|
||||
| `filterCategory` | `string` | `'all'` | Category filter |
|
||||
| `showOnlySelected` | `boolean` | `false` | Show only selected services |
|
||||
| `loading` | `boolean` | `false` | Show loading state |
|
||||
| `expanded` | `boolean` | `false` | Expand the selector panel |
|
||||
|
||||
**Events:**
|
||||
- `selectionChanged` — Fired with `{ selectedServices: string[] }` when selection changes
|
||||
|
||||
---
|
||||
|
||||
### `<upl-statuspage-statusdetails>`
|
||||
|
||||
Hourly status timeline visualization.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `dataPoints` | `IStatusHistoryPoint[]` | `[]` | Hourly status data |
|
||||
| `historyData` | `IStatusHistoryPoint[]` | `[]` | Alternative data property |
|
||||
| `serviceId` | `string` | `''` | Service identifier |
|
||||
| `serviceName` | `string` | `'Service'` | Display name |
|
||||
| `hoursToShow` | `number` | `48` | Number of hours to display |
|
||||
| `loading` | `boolean` | `false` | Show loading skeleton |
|
||||
|
||||
**Events:**
|
||||
- `barClick` — Fired with `{ timestamp, status, responseTime, serviceId }` on bar click
|
||||
|
||||
---
|
||||
|
||||
### `<upl-statuspage-statusmonth>`
|
||||
|
||||
Calendar-style monthly uptime visualization.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `monthlyData` | `IMonthlyUptime[]` | `[]` | Monthly uptime data |
|
||||
| `serviceId` | `string` | `'all'` | Service identifier |
|
||||
| `serviceName` | `string` | `'All Services'` | Display name |
|
||||
| `loading` | `boolean` | `false` | Show loading skeleton |
|
||||
|
||||
**Events:**
|
||||
- `dayClick` — Fired with day details when a day cell is clicked
|
||||
|
||||
---
|
||||
|
||||
### `<upl-statuspage-incidents>`
|
||||
|
||||
Incident feed with expandable details and status updates.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `currentIncidents` | `IIncidentDetails[]` | `[]` | Active incidents |
|
||||
| `pastIncidents` | `IIncidentDetails[]` | `[]` | Resolved incidents |
|
||||
| `maxPastIncidents` | `number` | `10` | Max past incidents to show |
|
||||
| `loading` | `boolean` | `false` | Show loading skeleton |
|
||||
| `enableSubscription` | `boolean` | `false` | Allow incident subscriptions |
|
||||
| `subscribedIncidentIds` | `string[]` | `[]` | Pre-subscribed incident IDs |
|
||||
|
||||
**Events:**
|
||||
- `subscribeToIncident` — Fired with incident ID on subscription toggle
|
||||
|
||||
---
|
||||
|
||||
### `<upl-statuspage-footer>`
|
||||
|
||||
Full-featured footer with links, social icons, and subscription.
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `companyName` | `string` | `''` | Company name |
|
||||
| `legalUrl` | `string` | `''` | Terms/legal page URL |
|
||||
| `supportEmail` | `string` | `''` | Support email address |
|
||||
| `statusPageUrl` | `string` | `''` | Status page URL |
|
||||
| `lastUpdated` | `number` | — | Last update timestamp |
|
||||
| `currentYear` | `number` | `2024` | Copyright year |
|
||||
| `socialLinks` | `ISocialLink[]` | `[]` | Social media links |
|
||||
| `rssFeedUrl` | `string` | `''` | RSS feed URL |
|
||||
| `apiStatusUrl` | `string` | `''` | Status API URL |
|
||||
| `enableSubscribe` | `boolean` | `false` | Show subscribe button |
|
||||
| `subscriberCount` | `number` | `0` | Display subscriber count |
|
||||
| `additionalLinks` | `IFooterLink[]` | `[]` | Extra footer links |
|
||||
|
||||
**Events:**
|
||||
- `subscribeClick` — Fired when subscribe button is clicked
|
||||
|
||||
---
|
||||
|
||||
## 📐 Interfaces
|
||||
|
||||
All TypeScript interfaces are exported for type safety:
|
||||
|
||||
```typescript
|
||||
import type {
|
||||
IServiceStatus,
|
||||
IStatusHistoryPoint,
|
||||
IIncidentDetails,
|
||||
IIncidentUpdate,
|
||||
IMonthlyUptime,
|
||||
IUptimeDay,
|
||||
IOverallStatus,
|
||||
IStatusPageConfig,
|
||||
ISubscription
|
||||
} from '@uptime.link/statuspage';
|
||||
```
|
||||
|
||||
### Key Interfaces
|
||||
|
||||
```typescript
|
||||
interface IServiceStatus {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
description?: string;
|
||||
currentStatus: 'operational' | 'degraded' | 'partial_outage' | 'major_outage' | 'maintenance';
|
||||
lastChecked: number;
|
||||
uptime30d: number;
|
||||
uptime90d: number;
|
||||
responseTime: number;
|
||||
category?: string;
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
interface IIncidentDetails {
|
||||
id: string;
|
||||
title: string;
|
||||
status: 'investigating' | 'identified' | 'monitoring' | 'resolved' | 'postmortem';
|
||||
severity: 'critical' | 'major' | 'minor' | 'maintenance';
|
||||
affectedServices: string[];
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
updates: IIncidentUpdate[];
|
||||
impact: string;
|
||||
rootCause?: string;
|
||||
resolution?: string;
|
||||
}
|
||||
|
||||
interface IOverallStatus {
|
||||
status: 'operational' | 'degraded' | 'partial_outage' | 'major_outage' | 'maintenance';
|
||||
message: string;
|
||||
lastUpdated: number;
|
||||
affectedServices: number;
|
||||
totalServices: number;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Import Components:**
|
||||
---
|
||||
|
||||
Create an `index.ts` file, and import the components you need:
|
||||
## 🎬 Complete Example
|
||||
|
||||
Build a full status page in minutes:
|
||||
|
||||
```typescript
|
||||
import './elements/index.js';
|
||||
import { page1 } from './pages/index.js';
|
||||
```
|
||||
import '@uptime.link/statuspage';
|
||||
import type { IServiceStatus, IOverallStatus, IIncidentDetails } from '@uptime.link/statuspage';
|
||||
|
||||
3. **Creating a Status Page:**
|
||||
// Get your components
|
||||
const header = document.querySelector('upl-statuspage-header');
|
||||
const statusBar = document.querySelector('upl-statuspage-statusbar');
|
||||
const statsGrid = document.querySelector('upl-statuspage-statsgrid');
|
||||
const incidents = document.querySelector('upl-statuspage-incidents');
|
||||
const footer = document.querySelector('upl-statuspage-footer');
|
||||
|
||||
Here, we'll create a simple status page using the imported components. We'll create individual components like headers, status bars, footers, etc., and combine them into a single page.
|
||||
// Configure the header
|
||||
header.pageTitle = 'Acme Cloud';
|
||||
header.logoUrl = '/logo.svg';
|
||||
header.showSubscribeButton = true;
|
||||
|
||||
### UplStatuspageHeader
|
||||
// Set overall status
|
||||
statusBar.overallStatus = {
|
||||
status: 'operational',
|
||||
message: 'All systems operational',
|
||||
lastUpdated: Date.now(),
|
||||
affectedServices: 0,
|
||||
totalServices: 12
|
||||
};
|
||||
|
||||
```typescript
|
||||
import { UplStatuspageHeader } from './elements/upl-statuspage-header.js';
|
||||
// Configure stats
|
||||
statsGrid.uptime = 99.98;
|
||||
statsGrid.avgResponseTime = 45;
|
||||
statsGrid.totalServices = 12;
|
||||
|
||||
const header: UplStatuspageHeader = document.createElement('upl-statuspage-header');
|
||||
header.pageTitle = "Uptime Link Status Page";
|
||||
document.body.appendChild(header);
|
||||
```
|
||||
|
||||
### UplStatuspageStatusbar
|
||||
|
||||
```typescript
|
||||
import { UplStatuspageStatusbar } from './elements/upl-statuspage-statusbar.js';
|
||||
|
||||
const statusBar: UplStatuspageStatusbar = document.createElement('upl-statuspage-statusbar');
|
||||
document.body.appendChild(statusBar);
|
||||
```
|
||||
|
||||
### UplStatuspageAssetsselector
|
||||
|
||||
```typescript
|
||||
import { UplStatuspageAssetsselector } from './elements/upl-statuspage-assetsselector.js';
|
||||
|
||||
const assetsSelector: UplStatuspageAssetsselector = document.createElement('upl-statuspage-assetsselector');
|
||||
document.body.appendChild(assetsSelector);
|
||||
```
|
||||
|
||||
### UplStatuspageStatusdetails
|
||||
|
||||
```typescript
|
||||
import { UplStatuspageStatusdetails } from './elements/upl-statuspage-statusdetails.js';
|
||||
|
||||
const statusDetails: UplStatuspageStatusdetails = document.createElement('upl-statuspage-statusdetails');
|
||||
document.body.appendChild(statusDetails);
|
||||
```
|
||||
|
||||
### UplStatuspageStatusmonth
|
||||
|
||||
```typescript
|
||||
import { UplStatuspageStatusmonth } from './elements/upl-statuspage-statusmonth.js';
|
||||
|
||||
const statusMonth: UplStatuspageStatusmonth = document.createElement('upl-statuspage-statusmonth');
|
||||
document.body.appendChild(statusMonth);
|
||||
```
|
||||
|
||||
### UplStatuspageIncidents
|
||||
|
||||
```typescript
|
||||
import { UplStatuspageIncidents } from './elements/upl-statuspage-incidents.js';
|
||||
import { uplInterfaces } from './plugins.js';
|
||||
|
||||
const incidents: UplStatuspageIncidents = document.createElement('upl-statuspage-incidents');
|
||||
incidents.currentIncidences = [
|
||||
// Example incident data
|
||||
// Add incidents
|
||||
incidents.currentIncidents = []; // No current incidents
|
||||
incidents.pastIncidents = [
|
||||
{
|
||||
id: "incident1",
|
||||
title: "Server Outage",
|
||||
description: "There was an outage on the main server.",
|
||||
status: "resolved",
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
id: 'inc-001',
|
||||
title: 'Scheduled Maintenance Complete',
|
||||
status: 'resolved',
|
||||
severity: 'maintenance',
|
||||
impact: 'Database maintenance window',
|
||||
affectedServices: ['Database'],
|
||||
startTime: Date.now() - 86400000,
|
||||
endTime: Date.now() - 82800000,
|
||||
updates: [
|
||||
{
|
||||
id: 'upd-1',
|
||||
timestamp: Date.now() - 82800000,
|
||||
status: 'resolved',
|
||||
message: 'Maintenance completed successfully'
|
||||
}
|
||||
]
|
||||
}
|
||||
] as uplInterfaces.IIncident[];
|
||||
];
|
||||
|
||||
incidents.pastIncidences = [
|
||||
// Example past incident data
|
||||
{
|
||||
id: "incident2",
|
||||
title: "Database Maintenance",
|
||||
description: "Scheduled maintenance on the database.",
|
||||
status: "completed",
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
] as uplInterfaces.IIncident[];
|
||||
|
||||
document.body.appendChild(incidents);
|
||||
// Configure footer
|
||||
footer.companyName = 'Acme Cloud';
|
||||
footer.supportEmail = 'support@acme.cloud';
|
||||
footer.enableSubscribe = true;
|
||||
```
|
||||
|
||||
### UplStatuspageFooter
|
||||
---
|
||||
|
||||
## 📄 Pre-built Page Templates
|
||||
|
||||
The package includes ready-to-use page templates:
|
||||
|
||||
```typescript
|
||||
import { UplStatuspageFooter } from './elements/upl-statuspage-footer.js';
|
||||
|
||||
const footer: UplStatuspageFooter = document.createElement('upl-statuspage-footer');
|
||||
footer.legalInfo = "https://example.com/legal";
|
||||
document.body.appendChild(footer);
|
||||
import {
|
||||
statuspageDemo, // Full demo with sample data
|
||||
statuspageAllgreen, // All systems operational
|
||||
statuspageOutage, // Major outage scenario
|
||||
statuspageMaintenance // Scheduled maintenance
|
||||
} from '@uptime.link/statuspage/pages';
|
||||
```
|
||||
|
||||
### Full Example
|
||||
---
|
||||
|
||||
Here’s how you can put it all together to create a full status page:
|
||||
## 🎨 Theming
|
||||
|
||||
```typescript
|
||||
import './elements/index.js';
|
||||
import { page1 } from './pages/index.js';
|
||||
import { UplStatuspageHeader } from './elements/upl-statuspage-header.js';
|
||||
import { UplStatuspageStatusbar } from './elements/upl-statuspage-statusbar.js';
|
||||
import { UplStatuspageAssetsselector } from './elements/upl-statuspage-assetsselector.js';
|
||||
import { UplStatuspageStatusdetails } from './elements/upl-statuspage-statusdetails.js';
|
||||
import { UplStatuspageStatusmonth } from './elements/upl-statuspage-statusmonth.js';
|
||||
import { UplStatuspageIncidents } from './elements/upl-statuspage-incidents.js';
|
||||
import { UplStatuspageFooter } from './elements/upl-statuspage-footer.js';
|
||||
import { uplInterfaces } from './plugins.js';
|
||||
Components automatically adapt to light/dark mode using `cssManager.bdTheme()`. The design follows a modern, minimal aesthetic with:
|
||||
|
||||
const header: UplStatuspageHeader = document.createElement('upl-statuspage-header');
|
||||
header.pageTitle = "Uptime Link Status Page";
|
||||
document.body.appendChild(header);
|
||||
- Clean typography with `-apple-system, BlinkMacSystemFont, 'Segoe UI'` font stack
|
||||
- Subtle shadows and borders
|
||||
- Semantic status colors (green, yellow, orange, red, blue)
|
||||
- Smooth transitions and hover states
|
||||
|
||||
const statusBar: UplStatuspageStatusbar = document.createElement('upl-statuspage-statusbar');
|
||||
document.body.appendChild(statusBar);
|
||||
---
|
||||
|
||||
const assetsSelector: UplStatuspageAssetsselector = document.createElement('upl-statuspage-assetsselector');
|
||||
document.body.appendChild(assetsSelector);
|
||||
## License and Legal Information
|
||||
|
||||
const statusDetails: UplStatuspageStatusdetails = document.createElement('upl-statuspage-statusdetails');
|
||||
document.body.appendChild(statusDetails);
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||
|
||||
const statusMonth: UplStatuspageStatusmonth = document.createElement('upl-statuspage-statusmonth');
|
||||
document.body.appendChild(statusMonth);
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
const incidents: UplStatuspageIncidents = document.createElement('upl-statuspage-incidents');
|
||||
incidents.currentIncidences = [
|
||||
{
|
||||
id: "incident1",
|
||||
title: "Server Outage",
|
||||
description: "There was an outage on the main server.",
|
||||
status: "resolved",
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
] as uplInterfaces.IIncident[];
|
||||
### Trademarks
|
||||
|
||||
incidents.pastIncidences = [
|
||||
{
|
||||
id: "incident2",
|
||||
title: "Database Maintenance",
|
||||
description: "Scheduled maintenance on the database.",
|
||||
status: "completed",
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
] as uplInterfaces.IIncident[];
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||
|
||||
document.body.appendChild(incidents);
|
||||
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||
|
||||
const footer: UplStatuspageFooter = document.createElement('upl-statuspage-footer');
|
||||
footer.legalInfo = "https://example.com/legal";
|
||||
document.body.appendChild(footer);
|
||||
```
|
||||
### Company Information
|
||||
|
||||
This example uses all the components provided by `@uptime.link/statuspage` to create a functional status page. Modify the data, styles, and structure to suit your requirements.
|
||||
undefined
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
|
||||
Reference in New Issue
Block a user