Compare commits
69 Commits
@nhost/vue
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73cb65b9be | ||
|
|
5e7c8395c2 | ||
|
|
c2837209e6 | ||
|
|
638710ea29 | ||
|
|
a79fddbafb | ||
|
|
ab6a8f2add | ||
|
|
69a5661bcf | ||
|
|
0886118f9d | ||
|
|
34fc08ca7c | ||
|
|
153de22713 | ||
|
|
bf4a1f6c2a | ||
|
|
2a67d0f872 | ||
|
|
b156c7b72e | ||
|
|
b484b04ae2 | ||
|
|
2e55c7f46a | ||
|
|
2d983e6ab1 | ||
|
|
df5b4302c3 | ||
|
|
828aed2df9 | ||
|
|
310df10892 | ||
|
|
555fba4400 | ||
|
|
885d10620a | ||
|
|
a8370f5aaa | ||
|
|
bd07905846 | ||
|
|
47a2164549 | ||
|
|
a96c79de00 | ||
|
|
596d0666fc | ||
|
|
9aaa407d29 | ||
|
|
1767b2f105 | ||
|
|
c99c5c4191 | ||
|
|
d845da2503 | ||
|
|
9f1ba1686c | ||
|
|
48b09a58ff | ||
|
|
2169908883 | ||
|
|
ed16c8b5de | ||
|
|
c618503376 | ||
|
|
f306c3940c | ||
|
|
ef125216bb | ||
|
|
fb43fefb5c | ||
|
|
73744c90f0 | ||
|
|
9fbea9787e | ||
|
|
e5f54bc197 | ||
|
|
10a6ae4853 | ||
|
|
d6ca1c7cfd | ||
|
|
bb85a95eda | ||
|
|
e84acf4692 | ||
|
|
2f20a70a28 | ||
|
|
819e1e97dc | ||
|
|
7c1cca0a43 | ||
|
|
0f51f4e868 | ||
|
|
97a6fcead9 | ||
|
|
b7c799d62c | ||
|
|
18b14b27fd | ||
|
|
67a867c93a | ||
|
|
0a1fb12467 | ||
|
|
78467ee348 | ||
|
|
c24eef0db9 | ||
|
|
2159b8171e | ||
|
|
8903e6abd9 | ||
|
|
7290260990 | ||
|
|
06529a1ea4 | ||
|
|
607d89e2aa | ||
|
|
0cca72311c | ||
|
|
a6525b6467 | ||
|
|
387be37b6e | ||
|
|
c8fd8bbcc7 | ||
|
|
bfb34bad00 | ||
|
|
666a75a233 | ||
|
|
3b050217df | ||
|
|
0ed4481615 |
56
.github/workflows/codeql-analysis.yml
vendored
Normal file
56
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push: {}
|
||||||
|
pull_request: {}
|
||||||
|
schedule:
|
||||||
|
- cron: '20 23 * * 3'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'javascript' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
@@ -1,5 +1,51 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 0.20.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- df5b4302c: chore(dashboard): remove run feature flag
|
||||||
|
- bf4a1f6c2: feat(dashboard): fetch auth, postgres, hasura and storage versions from dashboard
|
||||||
|
- 34fc08ca7: fix(dashboard/run): show correct private registry in service details
|
||||||
|
- 885d10620: chore(dashboard): change feedback to contact us
|
||||||
|
|
||||||
|
## 0.20.15
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- ed16c8b5d: feat(run): add a confirmation dialog when deleting a run service
|
||||||
|
- 216990888: fix(run): center loading indicator when selecting a project
|
||||||
|
|
||||||
|
## 0.20.14
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 9fbea9787: feat: add node18 announcement
|
||||||
|
|
||||||
|
## 0.20.13
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- e84acf469: fix(run): handle subdomain undefined error when creating a new service
|
||||||
|
|
||||||
|
## 0.20.12
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b7c799d62: feat(run): add dialog to copy registry and URLs
|
||||||
|
|
||||||
|
## 0.20.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8903e6abd: fix(dashboard): show correct egress limit in usage stats
|
||||||
|
|
||||||
|
## 0.20.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 666a75a23: feat(dashboard): add functions execution time and egress volume to usage stats
|
||||||
|
|
||||||
## 0.20.9
|
## 0.20.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ test('should show a sidebar with menu items', async () => {
|
|||||||
const navLocator = page.getByRole('navigation', { name: /main navigation/i });
|
const navLocator = page.getByRole('navigation', { name: /main navigation/i });
|
||||||
await expect(navLocator).toBeVisible();
|
await expect(navLocator).toBeVisible();
|
||||||
await expect(navLocator.getByRole('list').getByRole('listitem')).toHaveCount(
|
await expect(navLocator.getByRole('list').getByRole('listitem')).toHaveCount(
|
||||||
11,
|
12,
|
||||||
);
|
);
|
||||||
await expect(
|
await expect(
|
||||||
navLocator.getByRole('link', { name: /overview/i }),
|
navLocator.getByRole('link', { name: /overview/i }),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "0.20.9",
|
"version": "0.20.16",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ export interface AnnouncementContextProps {
|
|||||||
|
|
||||||
// Note: You can define the active announcement here.
|
// Note: You can define the active announcement here.
|
||||||
const announcement: AnnouncementType = {
|
const announcement: AnnouncementType = {
|
||||||
id: 'nhost-run',
|
id: 'node-18',
|
||||||
href: 'https://discord.com/invite/9V7Qb2U',
|
href: 'https://github.com/nhost/nhost/discussions/2239',
|
||||||
content:
|
content:
|
||||||
'Now you can bring custom and third-party OSS services to run alongside your Nhost projects',
|
"Starting October 1st, we're upgrading to Node.js 18 for improved performance, security, and stability. Learn more.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AnnouncementContext = createContext<AnnouncementContextProps>({});
|
export const AnnouncementContext = createContext<AnnouncementContextProps>({});
|
||||||
|
|||||||
70
dashboard/src/components/common/ContactUs/ContactUs.tsx
Normal file
70
dashboard/src/components/common/ContactUs/ContactUs.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { Link } from '@/components/ui/v2/Link';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export interface ContactUsProps
|
||||||
|
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
|
||||||
|
|
||||||
|
export default function FeedbackForm({ className, ...props }: ContactUsProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
'grid max-w-md grid-flow-row gap-2 py-4 px-5',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Text variant="h3" component="h2">
|
||||||
|
Contact us
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
To report issues with Nhost, please open a GitHub issue in the{' '}
|
||||||
|
<Link
|
||||||
|
href="https://github.com/nhost/nhost/issues/new"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
underline="hover"
|
||||||
|
>
|
||||||
|
nhost/nhost
|
||||||
|
</Link>{' '}
|
||||||
|
repository.
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
For issues related to the CLI, please visit the{' '}
|
||||||
|
<Link
|
||||||
|
href="https://github.com/nhost/cli/issues/new"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
underline="hover"
|
||||||
|
>
|
||||||
|
nhost/cli
|
||||||
|
</Link>{' '}
|
||||||
|
repository.
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
If you need assistance or have any questions, feel free to join us on{' '}
|
||||||
|
<Link
|
||||||
|
href="https://discord.com/invite/9V7Qb2U"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
underline="hover"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</Link>
|
||||||
|
. Alternatively, if you prefer, you can also open a{' '}
|
||||||
|
<Link
|
||||||
|
href="https://github.com/nhost/nhost/discussions/new/choose"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
underline="hover"
|
||||||
|
>
|
||||||
|
GitHub discussion
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
<Text>We're here to help, so don't hesitate to reach out!</Text>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
2
dashboard/src/components/common/ContactUs/index.ts
Normal file
2
dashboard/src/components/common/ContactUs/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './ContactUs';
|
||||||
|
export { default as ContactUs } from './ContactUs';
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import { Avatar } from '@/components/ui/v2/Avatar';
|
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
|
||||||
import { useInsertFeedbackOneMutation } from '@/utils/__generated__/graphql';
|
|
||||||
import { useUserData } from '@nhost/nextjs';
|
|
||||||
import Image from 'next/image';
|
|
||||||
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { twMerge } from 'tailwind-merge';
|
|
||||||
|
|
||||||
export interface FeedbackFormProps
|
|
||||||
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
|
|
||||||
|
|
||||||
// TODO: Use `react-hook-form` here instead of the custom form implementation
|
|
||||||
export default function FeedbackForm({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: FeedbackFormProps) {
|
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
|
||||||
const [insertFeedback, { loading }] = useInsertFeedbackOneMutation();
|
|
||||||
const user = useUserData();
|
|
||||||
|
|
||||||
const [feedback, setFeedback] = useState('');
|
|
||||||
const [feedbackSent, setFeedbackSent] = useState(false);
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
setTimeout(() => {
|
|
||||||
setFeedbackSent(false);
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit(e: React.SyntheticEvent<HTMLFormElement>) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const feedbackWithProjectInfo = [
|
|
||||||
currentProject && `Project ID: ${currentProject.id}`,
|
|
||||||
typeof window !== 'undefined' && `URL: ${window.location.href}`,
|
|
||||||
feedback,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join('\n\n');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await insertFeedback({
|
|
||||||
variables: {
|
|
||||||
feedback: {
|
|
||||||
feedback: feedbackWithProjectInfo,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
setFeedbackSent(true);
|
|
||||||
setFeedback('');
|
|
||||||
} catch (error) {
|
|
||||||
// TODO: Display error to user and use a logging solution
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (feedbackSent) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
'grid max-w-md grid-flow-row justify-center gap-4 py-4 px-5 text-center',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src="/assets/FeedbackReceived.svg"
|
|
||||||
alt="Light bulb with a checkmark"
|
|
||||||
width={72}
|
|
||||||
height={72}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid grid-flow-row gap-2">
|
|
||||||
<Text variant="h3" component="h2" className="text-center">
|
|
||||||
Feedback Received
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text>
|
|
||||||
Thanks for sending us your thoughts! Feel free to send more feedback
|
|
||||||
as you explore the beta, and stay tuned for updates.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
color="secondary"
|
|
||||||
className="mt-2 text-sm+ font-normal"
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
Go Back
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={twMerge(
|
|
||||||
'grid max-w-md grid-flow-row gap-2 py-4 px-5',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<Text variant="h3" component="h2">
|
|
||||||
Leave Feedback
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text>
|
|
||||||
Nhost is still in beta and not everything is in place yet, but we'd
|
|
||||||
love to know what you think of it so far.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="grid grid-flow-row gap-2">
|
|
||||||
<div className="grid grid-flow-col place-content-between gap-2">
|
|
||||||
<Text className="font-medium">
|
|
||||||
What do you think we should improve?
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Avatar
|
|
||||||
className="h-6 w-6 rounded-full"
|
|
||||||
alt={user?.displayName}
|
|
||||||
src={user?.avatarUrl}
|
|
||||||
>
|
|
||||||
{user?.displayName}
|
|
||||||
</Avatar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
multiline
|
|
||||||
value={feedback}
|
|
||||||
onChange={(event) => setFeedback(event.target.value)}
|
|
||||||
placeholder="Your feedback"
|
|
||||||
rows={6}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
hideEmptyHelperText
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button type="submit" disabled={!feedback} loading={loading}>
|
|
||||||
Send Feedback
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './FeedbackForm';
|
|
||||||
export { default as FeedbackForm } from './FeedbackForm';
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeedbackForm } from '@/components/common/FeedbackForm';
|
import { ContactUs } from '@/components/common/ContactUs';
|
||||||
import { NavLink } from '@/components/common/NavLink';
|
import { NavLink } from '@/components/common/NavLink';
|
||||||
import { AccountMenu } from '@/components/layout/AccountMenu';
|
import { AccountMenu } from '@/components/layout/AccountMenu';
|
||||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
|
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
|
||||||
@@ -75,14 +75,14 @@ export default function Header({ className, ...props }: HeaderProps) {
|
|||||||
hideChevron
|
hideChevron
|
||||||
className="rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
|
className="rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
|
||||||
>
|
>
|
||||||
Feedback
|
Contact us
|
||||||
</Dropdown.Trigger>
|
</Dropdown.Trigger>
|
||||||
|
|
||||||
<Dropdown.Content
|
<Dropdown.Content
|
||||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
>
|
>
|
||||||
<FeedbackForm className="max-w-md" />
|
<ContactUs className="max-w-md" />
|
||||||
</Dropdown.Content>
|
</Dropdown.Content>
|
||||||
</Dropdown.Root>
|
</Dropdown.Root>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeedbackForm } from '@/components/common/FeedbackForm';
|
import { ContactUs } from '@/components/common/ContactUs';
|
||||||
import { NavLink } from '@/components/common/NavLink';
|
import { NavLink } from '@/components/common/NavLink';
|
||||||
import { ThemeSwitcher } from '@/components/common/ThemeSwitcher';
|
import { ThemeSwitcher } from '@/components/common/ThemeSwitcher';
|
||||||
import { Nav } from '@/components/presentational/Nav';
|
import { Nav } from '@/components/presentational/Nav';
|
||||||
@@ -171,7 +171,7 @@ export default function MobileNav({ className, ...props }: MobileNavProps) {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
role={undefined}
|
role={undefined}
|
||||||
>
|
>
|
||||||
<ListItem.Text>Feedback</ListItem.Text>
|
<ListItem.Text>Contact us</ListItem.Text>
|
||||||
</ListItem.Button>
|
</ListItem.Button>
|
||||||
</ListItem.Root>
|
</ListItem.Root>
|
||||||
</Dropdown.Trigger>
|
</Dropdown.Trigger>
|
||||||
@@ -180,7 +180,7 @@ export default function MobileNav({ className, ...props }: MobileNavProps) {
|
|||||||
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
>
|
>
|
||||||
<FeedbackForm className="max-w-md" />
|
<ContactUs className="max-w-md" />
|
||||||
</Dropdown.Content>
|
</Dropdown.Content>
|
||||||
</Dropdown.Root>
|
</Dropdown.Root>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ import MaterialLinearProgress, {
|
|||||||
|
|
||||||
export interface LinearProgressProps extends MaterialLinearProgressProps {}
|
export interface LinearProgressProps extends MaterialLinearProgressProps {}
|
||||||
|
|
||||||
const LinearProgress = styled(MaterialLinearProgress)(({ theme }) => ({
|
const LinearProgress = styled(MaterialLinearProgress)(({ theme, value }) => ({
|
||||||
height: 12,
|
height: 12,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
[`&.${linearProgressClasses.colorPrimary}`]: {
|
[`&.${linearProgressClasses.colorPrimary}`]: {
|
||||||
backgroundColor: theme.palette.grey[300],
|
backgroundColor: theme.palette.grey[300],
|
||||||
},
|
},
|
||||||
[`& .${linearProgressClasses.bar}`]: {
|
[`& .${linearProgressClasses.bar}`]: {
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor:
|
||||||
|
value >= 100 ? theme.palette.error.dark : theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
|
Software_Type_Enum,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
|
useGetSoftwareVersionsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
@@ -28,15 +30,6 @@ export type AuthServiceVersionFormValues = Yup.InferType<
|
|||||||
typeof validationSchema
|
typeof validationSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const AVAILABLE_AUTH_VERSIONS = [
|
|
||||||
'0.21.2',
|
|
||||||
'0.20.1',
|
|
||||||
'0.20.0',
|
|
||||||
'0.19.3',
|
|
||||||
'0.19.2',
|
|
||||||
'0.19.1',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function AuthServiceVersionSettings() {
|
export default function AuthServiceVersionSettings() {
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
@@ -49,9 +42,16 @@ export default function AuthServiceVersionSettings() {
|
|||||||
fetchPolicy: 'cache-only',
|
fetchPolicy: 'cache-only',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: authVersionsData } = useGetSoftwareVersionsQuery({
|
||||||
|
variables: {
|
||||||
|
software: Software_Type_Enum.Auth,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { version } = data?.config?.auth || {};
|
const { version } = data?.config?.auth || {};
|
||||||
|
const versions = authVersionsData?.softwareVersions || [];
|
||||||
const availableVersions = Array.from(
|
const availableVersions = Array.from(
|
||||||
new Set(AVAILABLE_AUTH_VERSIONS).add(version),
|
new Set(versions.map((el) => el.version)).add(version),
|
||||||
)
|
)
|
||||||
.sort()
|
.sort()
|
||||||
.reverse()
|
.reverse()
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetPostgresSettingsDocument,
|
GetPostgresSettingsDocument,
|
||||||
|
Software_Type_Enum,
|
||||||
useGetPostgresSettingsQuery,
|
useGetPostgresSettingsQuery,
|
||||||
|
useGetSoftwareVersionsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
@@ -30,15 +32,6 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
|
|||||||
typeof validationSchema
|
typeof validationSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const AVAILABLE_POSTGRES_VERSIONS = [
|
|
||||||
'14.6-20230705-1',
|
|
||||||
'14.6-20230613-1',
|
|
||||||
'14.6-20230525',
|
|
||||||
'14.6-20230406-2',
|
|
||||||
'14.6-20230406-1',
|
|
||||||
'14.6-20230404',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function DatabaseServiceVersionSettings() {
|
export default function DatabaseServiceVersionSettings() {
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
@@ -51,9 +44,16 @@ export default function DatabaseServiceVersionSettings() {
|
|||||||
fetchPolicy: 'cache-only',
|
fetchPolicy: 'cache-only',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
|
||||||
|
variables: {
|
||||||
|
software: Software_Type_Enum.PostgreSql,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { version } = data?.config?.postgres || {};
|
const { version } = data?.config?.postgres || {};
|
||||||
|
const databaseVersions = databaseVersionsData?.softwareVersions || [];
|
||||||
const availableVersions = Array.from(
|
const availableVersions = Array.from(
|
||||||
new Set(AVAILABLE_POSTGRES_VERSIONS).add(version),
|
new Set(databaseVersions.map((el) => el.version)).add(version),
|
||||||
)
|
)
|
||||||
.sort()
|
.sort()
|
||||||
.reverse()
|
.reverse()
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
|
Software_Type_Enum,
|
||||||
useGetHasuraSettingsQuery,
|
useGetHasuraSettingsQuery,
|
||||||
|
useGetSoftwareVersionsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
@@ -30,16 +32,6 @@ export type HasuraServiceVersionFormValues = Yup.InferType<
|
|||||||
typeof validationSchema
|
typeof validationSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const AVAILABLE_HASURA_VERSIONS = [
|
|
||||||
'v2.29.0-ce',
|
|
||||||
'v2.28.2-ce',
|
|
||||||
'v2.27.0-ce',
|
|
||||||
'v2.25.1-ce',
|
|
||||||
'v2.25.0-ce',
|
|
||||||
'v2.24.1-ce',
|
|
||||||
'v2.15.2',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function HasuraServiceVersionSettings() {
|
export default function HasuraServiceVersionSettings() {
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
@@ -53,9 +45,16 @@ export default function HasuraServiceVersionSettings() {
|
|||||||
fetchPolicy: 'cache-only',
|
fetchPolicy: 'cache-only',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({
|
||||||
|
variables: {
|
||||||
|
software: Software_Type_Enum.Hasura,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { version } = data?.config?.hasura || {};
|
const { version } = data?.config?.hasura || {};
|
||||||
|
const versions = hasuraVersionsData?.softwareVersions || [];
|
||||||
const availableVersions = Array.from(
|
const availableVersions = Array.from(
|
||||||
new Set(AVAILABLE_HASURA_VERSIONS).add(version),
|
new Set(versions.map((el) => el.version)).add(version),
|
||||||
)
|
)
|
||||||
.sort()
|
.sort()
|
||||||
.reverse()
|
.reverse()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeedbackForm } from '@/components/common/FeedbackForm';
|
import { ContactUs } from '@/components/common/ContactUs';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
||||||
@@ -99,7 +99,7 @@ export default function AppLoader({
|
|||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||||
>
|
>
|
||||||
<FeedbackForm />
|
<ContactUs />
|
||||||
</Dropdown.Content>
|
</Dropdown.Content>
|
||||||
</Dropdown.Root>
|
</Dropdown.Root>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeedbackForm } from '@/components/common/FeedbackForm';
|
import { ContactUs } from '@/components/common/ContactUs';
|
||||||
import { Container } from '@/components/layout/Container';
|
import { Container } from '@/components/layout/Container';
|
||||||
import { Modal } from '@/components/ui/v1/Modal';
|
import { Modal } from '@/components/ui/v1/Modal';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
@@ -250,7 +250,7 @@ export default function ApplicationErrored() {
|
|||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||||
>
|
>
|
||||||
<FeedbackForm />
|
<ContactUs />
|
||||||
</Dropdown.Content>
|
</Dropdown.Content>
|
||||||
</Dropdown.Root>
|
</Dropdown.Root>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FeedbackForm } from '@/components/common/FeedbackForm';
|
import { ContactUs } from '@/components/common/ContactUs';
|
||||||
import { Container } from '@/components/layout/Container';
|
import { Container } from '@/components/layout/Container';
|
||||||
import { Modal } from '@/components/ui/v1/Modal';
|
import { Modal } from '@/components/ui/v1/Modal';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
@@ -65,7 +65,7 @@ export default function ApplicationUnknown() {
|
|||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||||
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||||
>
|
>
|
||||||
<FeedbackForm />
|
<ContactUs />
|
||||||
</Dropdown.Content>
|
</Dropdown.Content>
|
||||||
</Dropdown.Root>
|
</Dropdown.Root>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
|
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
|
import {
|
||||||
|
useDeleteRunServiceConfigMutation,
|
||||||
|
useDeleteRunServiceMutation,
|
||||||
|
} from '@/utils/__generated__/graphql';
|
||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export interface DeleteServiceModalProps {
|
||||||
|
service: RunService;
|
||||||
|
onDelete?: () => Promise<any>;
|
||||||
|
close: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeleteServiceModal({
|
||||||
|
service,
|
||||||
|
onDelete,
|
||||||
|
close,
|
||||||
|
}: DeleteServiceModalProps) {
|
||||||
|
const [remove, setRemove] = useState(false);
|
||||||
|
const [loadingRemove, setLoadingRemove] = useState(false);
|
||||||
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
const [deleteRunService] = useDeleteRunServiceMutation();
|
||||||
|
const [deleteRunServiceConfig] = useDeleteRunServiceConfigMutation();
|
||||||
|
|
||||||
|
const deleteServiceAndConfig = async () => {
|
||||||
|
await deleteRunService({ variables: { serviceID: service.id } });
|
||||||
|
await deleteRunServiceConfig({
|
||||||
|
variables: { appID: currentProject.id, serviceID: service.id },
|
||||||
|
});
|
||||||
|
await onDelete?.();
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleClick() {
|
||||||
|
setLoadingRemove(true);
|
||||||
|
|
||||||
|
await toast.promise(
|
||||||
|
deleteServiceAndConfig(),
|
||||||
|
{
|
||||||
|
loading: 'Deleting the service...',
|
||||||
|
success: `The service has been deleted successfully.`,
|
||||||
|
error: (arg: ApolloError) => {
|
||||||
|
// we need to get the internal error message from the GraphQL error
|
||||||
|
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
||||||
|
const { message } = (internal as Record<string, any>)?.error || {};
|
||||||
|
|
||||||
|
// we use the default Apollo error message if we can't find the
|
||||||
|
// internal error message
|
||||||
|
return (
|
||||||
|
message ||
|
||||||
|
arg.message ||
|
||||||
|
'An error occurred while deleting the service. Please try again.'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getToastStyleProps(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={twMerge('w-full rounded-lg p-6 text-left')}>
|
||||||
|
<div className="grid grid-flow-row gap-1">
|
||||||
|
<Text variant="h3" component="h2">
|
||||||
|
Delete Service {service?.config?.name}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text variant="subtitle2">
|
||||||
|
Are you sure you want to delete this service?
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
variant="subtitle2"
|
||||||
|
className="font-bold"
|
||||||
|
sx={{ color: (theme) => `${theme.palette.error.main} !important` }}
|
||||||
|
>
|
||||||
|
This cannot be undone.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Box className="my-4">
|
||||||
|
<Checkbox
|
||||||
|
id="accept-1"
|
||||||
|
label={`I'm sure I want to delete ${service?.config?.name}`}
|
||||||
|
className="py-2"
|
||||||
|
checked={remove}
|
||||||
|
onChange={(_event, checked) => setRemove(checked)}
|
||||||
|
aria-label="Confirm Delete Project #1"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<div className="grid grid-flow-row gap-2">
|
||||||
|
<Button
|
||||||
|
color="error"
|
||||||
|
onClick={handleClick}
|
||||||
|
disabled={!remove}
|
||||||
|
loading={loadingRemove}
|
||||||
|
>
|
||||||
|
Delete Service
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button variant="outlined" color="secondary" onClick={close}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './DeleteServiceModal';
|
||||||
|
export { default as DeleteServiceModal } from './DeleteServiceModal';
|
||||||
@@ -14,7 +14,6 @@ import type { SvgIconProps } from '@/components/ui/v2/icons/SvgIcon';
|
|||||||
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
|
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { useHypertune } from '@/hooks/useHypertune';
|
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
|
|
||||||
export interface ProjectRoute {
|
export interface ProjectRoute {
|
||||||
@@ -58,26 +57,8 @@ export interface ProjectRoute {
|
|||||||
export default function useProjectRoutes() {
|
export default function useProjectRoutes() {
|
||||||
const isPlatform = useIsPlatform();
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const {
|
const { currentProject, loading: currentProjectLoading } =
|
||||||
currentWorkspace,
|
useCurrentWorkspaceAndProject();
|
||||||
currentProject,
|
|
||||||
loading: currentProjectLoading,
|
|
||||||
} = useCurrentWorkspaceAndProject();
|
|
||||||
|
|
||||||
const hypertune = useHypertune();
|
|
||||||
|
|
||||||
const enableServices =
|
|
||||||
currentWorkspace &&
|
|
||||||
hypertune
|
|
||||||
.root({
|
|
||||||
context: {
|
|
||||||
workSpace: {
|
|
||||||
id: currentWorkspace.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.enableServices({})
|
|
||||||
.get(false);
|
|
||||||
|
|
||||||
const nhostRoutes: ProjectRoute[] = [
|
const nhostRoutes: ProjectRoute[] = [
|
||||||
{
|
{
|
||||||
@@ -118,7 +99,7 @@ export default function useProjectRoutes() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let allRoutes: ProjectRoute[] = [
|
const allRoutes: ProjectRoute[] = [
|
||||||
{
|
{
|
||||||
relativePath: '/',
|
relativePath: '/',
|
||||||
exact: true,
|
exact: true,
|
||||||
@@ -156,18 +137,14 @@ export default function useProjectRoutes() {
|
|||||||
label: 'Storage',
|
label: 'Storage',
|
||||||
icon: <StorageIcon />,
|
icon: <StorageIcon />,
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
|
||||||
if (enableServices) {
|
|
||||||
allRoutes.push({
|
|
||||||
relativePath: '/services',
|
relativePath: '/services',
|
||||||
exact: false,
|
exact: false,
|
||||||
label: 'Run',
|
label: 'Run',
|
||||||
icon: <ServicesIcon />,
|
icon: <ServicesIcon />,
|
||||||
});
|
},
|
||||||
}
|
...nhostRoutes,
|
||||||
|
];
|
||||||
allRoutes = [...allRoutes, ...nhostRoutes];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nhostRoutes,
|
nhostRoutes,
|
||||||
|
|||||||
@@ -41,11 +41,6 @@ export default function OverviewMetrics() {
|
|||||||
numberOfDecimals: 0,
|
numberOfDecimals: 0,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Egress Volume',
|
|
||||||
tooltip: 'Amount of data your services have sent to users',
|
|
||||||
value: prettifySize(data?.egressVolume?.value || 0),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Logs',
|
label: 'Logs',
|
||||||
tooltip: 'Amount of logs stored',
|
tooltip: 'Amount of logs stored',
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function OverviewUsageMetrics() {
|
|||||||
remoteAppMetricsData?.filesAggregate?.aggregate?.sum?.size || 0;
|
remoteAppMetricsData?.filesAggregate?.aggregate?.sum?.size || 0;
|
||||||
const totalStorage = currentProject?.plan?.isFree
|
const totalStorage = currentProject?.plan?.isFree
|
||||||
? 1 * 1000 ** 3 // 1 GB
|
? 1 * 1000 ** 3 // 1 GB
|
||||||
: 10 * 1000 ** 3; // 10 GB
|
: 50 * 1000 ** 3; // 10 GB
|
||||||
|
|
||||||
// metrics for users
|
// metrics for users
|
||||||
const usedUsers = remoteAppMetricsData?.usersAggregate?.aggregate?.count || 0;
|
const usedUsers = remoteAppMetricsData?.usersAggregate?.aggregate?.count || 0;
|
||||||
@@ -105,6 +105,16 @@ export function OverviewUsageMetrics() {
|
|||||||
// metrics for functions
|
// metrics for functions
|
||||||
const usedFunctions = functionsInfoData?.app.metadataFunctions.length || 0;
|
const usedFunctions = functionsInfoData?.app.metadataFunctions.length || 0;
|
||||||
const totalFunctions = currentProject?.plan?.isFree ? 10 : 50;
|
const totalFunctions = currentProject?.plan?.isFree ? 10 : 50;
|
||||||
|
const usedFunctionsDuration = projectMetrics?.functionsDuration.value || 0;
|
||||||
|
const totalFunctionsDuration = currentProject?.plan?.isFree
|
||||||
|
? 3600 // 1 hour
|
||||||
|
: 3600 * 10; // 10 hours
|
||||||
|
|
||||||
|
// metrics for egress
|
||||||
|
const usedEgressVolume = projectMetrics?.egressVolume.value || 0;
|
||||||
|
const totalEgressVolume = currentProject?.plan?.isFree
|
||||||
|
? 5 * 1000 ** 3 // 5 GB
|
||||||
|
: 50 * 1000 ** 3; // 50 GB
|
||||||
|
|
||||||
if (metricsLoading) {
|
if (metricsLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -112,7 +122,9 @@ export function OverviewUsageMetrics() {
|
|||||||
<UsageProgress label="Database" percentage={0} />
|
<UsageProgress label="Database" percentage={0} />
|
||||||
<UsageProgress label="Storage" percentage={0} />
|
<UsageProgress label="Storage" percentage={0} />
|
||||||
<UsageProgress label="Users" percentage={0} />
|
<UsageProgress label="Users" percentage={0} />
|
||||||
<UsageProgress label="Functions" percentage={0} />
|
<UsageProgress label="Number of Functions" percentage={0} />
|
||||||
|
<UsageProgress label="Functions Execution Time" percentage={0} />
|
||||||
|
<UsageProgress label="Egress Volume" percentage={0} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -139,6 +151,18 @@ export function OverviewUsageMetrics() {
|
|||||||
used={usedFunctions}
|
used={usedFunctions}
|
||||||
percentage={100}
|
percentage={100}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<UsageProgress
|
||||||
|
label="Functions"
|
||||||
|
used={usedFunctionsDuration}
|
||||||
|
percentage={100}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UsageProgress
|
||||||
|
label="Egress"
|
||||||
|
used={usedEgressVolume}
|
||||||
|
percentage={100}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -167,11 +191,25 @@ export function OverviewUsageMetrics() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<UsageProgress
|
<UsageProgress
|
||||||
label="Functions"
|
label="Number of Functions"
|
||||||
used={usedFunctions}
|
used={usedFunctions}
|
||||||
total={totalFunctions}
|
total={totalFunctions}
|
||||||
percentage={(usedFunctions / totalFunctions) * 100}
|
percentage={(usedFunctions / totalFunctions) * 100}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<UsageProgress
|
||||||
|
label="Functions Execution Time"
|
||||||
|
used={Math.trunc(usedFunctionsDuration)}
|
||||||
|
total={`${totalFunctionsDuration} seconds`}
|
||||||
|
percentage={(usedFunctionsDuration / totalFunctionsDuration) * 100}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UsageProgress
|
||||||
|
label="Egress Volume"
|
||||||
|
used={prettifySize(usedEgressVolume)}
|
||||||
|
total={prettifySize(totalEgressVolume)}
|
||||||
|
percentage={(usedEgressVolume / totalEgressVolume) * 100}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import { toast } from 'react-hot-toast';
|
|||||||
import { parse } from 'shell-quote';
|
import { parse } from 'shell-quote';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { ServiceConfirmationDialog } from './components/ServiceConfirmationDialog';
|
import { ServiceConfirmationDialog } from './components/ServiceConfirmationDialog';
|
||||||
|
import { ServiceDetailsDialog } from './components/ServiceDetailsDialog';
|
||||||
|
|
||||||
export enum PortTypes {
|
export enum PortTypes {
|
||||||
HTTP = 'http',
|
HTTP = 'http',
|
||||||
@@ -94,7 +95,7 @@ export interface ServiceFormProps extends DialogFormProps {
|
|||||||
/**
|
/**
|
||||||
* if there is initialData then it's an update operation
|
* if there is initialData then it's an update operation
|
||||||
*/
|
*/
|
||||||
initialData?: ServiceFormValues;
|
initialData?: ServiceFormValues & { subdomain?: string }; // subdomain is only set on the backend
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the operation is cancelled.
|
* Function to be called when the operation is cancelled.
|
||||||
@@ -119,6 +120,10 @@ export default function ServiceForm({
|
|||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
|
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
|
||||||
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation();
|
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation();
|
||||||
|
const [detailsServiceId, setDetailsServiceId] = useState('');
|
||||||
|
const [detailsServiceSubdomain, setDetailsServiceSubdomain] = useState(
|
||||||
|
initialData?.subdomain,
|
||||||
|
);
|
||||||
|
|
||||||
const [createServiceFormError, setCreateServiceFormError] =
|
const [createServiceFormError, setCreateServiceFormError] =
|
||||||
useState<Error | null>(null);
|
useState<Error | null>(null);
|
||||||
@@ -196,11 +201,13 @@ export default function ServiceForm({
|
|||||||
config,
|
config,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setDetailsServiceId(serviceID);
|
||||||
} else {
|
} else {
|
||||||
// Insert service config
|
// Insert service config
|
||||||
const {
|
const {
|
||||||
data: {
|
data: {
|
||||||
insertRunService: { id: newServiceID },
|
insertRunService: { id: newServiceID, subdomain },
|
||||||
},
|
},
|
||||||
} = await insertRunService({
|
} = await insertRunService({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -227,6 +234,9 @@ export default function ServiceForm({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setDetailsServiceId(newServiceID);
|
||||||
|
setDetailsServiceSubdomain(subdomain);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -254,8 +264,6 @@ export default function ServiceForm({
|
|||||||
getToastStyleProps(),
|
getToastStyleProps(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// await refetchWorkspaceAndProject();
|
|
||||||
// refestch the services
|
|
||||||
onSubmit?.();
|
onSubmit?.();
|
||||||
} catch {
|
} catch {
|
||||||
// Note: The toast will handle the error.
|
// Note: The toast will handle the error.
|
||||||
@@ -277,6 +285,28 @@ export default function ServiceForm({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (detailsServiceId) {
|
||||||
|
openDialog({
|
||||||
|
title: 'Service Details',
|
||||||
|
component: (
|
||||||
|
<ServiceDetailsDialog
|
||||||
|
serviceID={detailsServiceId}
|
||||||
|
subdomain={detailsServiceSubdomain}
|
||||||
|
ports={formValues.ports}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
props: {
|
||||||
|
PaperProps: {
|
||||||
|
className: 'max-w-2xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [detailsServiceId, detailsServiceSubdomain, formValues, openDialog]);
|
||||||
|
|
||||||
const pricingExplanation = () => {
|
const pricingExplanation = () => {
|
||||||
const vCPUs = `${formValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER} vCPUs`;
|
const vCPUs = `${formValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER} vCPUs`;
|
||||||
const mem = `${formValues.compute.memory} MiB Mem`;
|
const mem = `${formValues.compute.memory} MiB Mem`;
|
||||||
|
|||||||
@@ -32,20 +32,20 @@ export default function PortsFormSection() {
|
|||||||
name: 'ports',
|
name: 'ports',
|
||||||
});
|
});
|
||||||
|
|
||||||
const formValues = useWatch<ServiceFormValues>();
|
const formValues = useWatch<ServiceFormValues & { subdomain: string }>();
|
||||||
|
|
||||||
const onChangePortType = (value: string | undefined, index: number) =>
|
const onChangePortType = (value: string | undefined, index: number) =>
|
||||||
setValue(`ports.${index}.type`, value as PortTypes);
|
setValue(`ports.${index}.type`, value as PortTypes);
|
||||||
|
|
||||||
const showURL = (index: number) =>
|
const showURL = (index: number) =>
|
||||||
|
formValues.subdomain &&
|
||||||
formValues.ports[index]?.type === PortTypes.HTTP &&
|
formValues.ports[index]?.type === PortTypes.HTTP &&
|
||||||
formValues.ports[index]?.publish;
|
formValues.ports[index]?.publish;
|
||||||
|
|
||||||
const getPortURL = (_port: string | number, _name: string) => {
|
const getPortURL = (_port: string | number, subdomain: string) => {
|
||||||
const port = Number(_port) > 0 ? Number(_port) : '[port]';
|
const port = Number(_port) > 0 ? Number(_port) : '[port]';
|
||||||
const name = _name && _name.length > 0 ? _name : '[name]';
|
|
||||||
|
|
||||||
return `https://${currentProject?.subdomain}-${name}-${port}.svc.${currentProject?.region.awsName}.${currentProject?.region.domain}`;
|
return `https://${subdomain}-${port}.svc.${currentProject?.region.awsName}.${currentProject?.region.domain}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -144,7 +144,7 @@ export default function PortsFormSection() {
|
|||||||
title="URL"
|
title="URL"
|
||||||
value={getPortURL(
|
value={getPortURL(
|
||||||
formValues.ports[index]?.port,
|
formValues.ports[index]?.port,
|
||||||
formValues.name,
|
formValues.subdomain,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
|
||||||
|
import type { ConfigRunServicePort } from '@/utils/__generated__/graphql';
|
||||||
|
|
||||||
|
export interface ServiceDetailsDialogProps {
|
||||||
|
/**
|
||||||
|
* The id of the service
|
||||||
|
*/
|
||||||
|
serviceID: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The subdomain of the service
|
||||||
|
*/
|
||||||
|
subdomain: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The service ports
|
||||||
|
* We use partial here because `port` is set as required in ConfigRunServicePort
|
||||||
|
*/
|
||||||
|
ports: Partial<ConfigRunServicePort>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ServiceDetailsDialog({
|
||||||
|
serviceID,
|
||||||
|
subdomain,
|
||||||
|
ports,
|
||||||
|
}: ServiceDetailsDialogProps) {
|
||||||
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
|
const { closeDialog } = useDialog();
|
||||||
|
|
||||||
|
const getPortURL = (_port: string | number) => {
|
||||||
|
const port = Number(_port) > 0 ? Number(_port) : '[port]';
|
||||||
|
|
||||||
|
return `https://${subdomain}-${port}.svc.${currentProject?.region.awsName}.${currentProject?.region.domain}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 px-6 pb-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Text color="secondary">Private registry</Text>
|
||||||
|
<InfoCard
|
||||||
|
title=""
|
||||||
|
value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ports?.length > 0 && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Text color="secondary">Ports</Text>
|
||||||
|
{ports
|
||||||
|
.filter((port) => port.publish)
|
||||||
|
.map((port) => (
|
||||||
|
<InfoCard
|
||||||
|
title={`${port.type}:${port.port}`}
|
||||||
|
value={getPortURL(port.port)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => closeDialog()}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './ServiceDetailsDialog';
|
||||||
|
export { default as ServiceDetailsDialog } from './ServiceDetailsDialog';
|
||||||
@@ -10,21 +10,14 @@ import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
|
|||||||
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
|
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal';
|
||||||
import {
|
import {
|
||||||
ServiceForm,
|
ServiceForm,
|
||||||
type PortTypes,
|
type PortTypes,
|
||||||
} from '@/features/services/components/ServiceForm';
|
} from '@/features/services/components/ServiceForm';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import {
|
|
||||||
useDeleteRunServiceConfigMutation,
|
|
||||||
useDeleteRunServiceMutation,
|
|
||||||
} from '@/utils/__generated__/graphql';
|
|
||||||
import type { ApolloError } from '@apollo/client';
|
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
|
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
|
||||||
import { toast } from 'react-hot-toast';
|
|
||||||
|
|
||||||
interface ServicesListProps {
|
interface ServicesListProps {
|
||||||
/**
|
/**
|
||||||
@@ -51,16 +44,7 @@ export default function ServicesList({
|
|||||||
onCreateOrUpdate,
|
onCreateOrUpdate,
|
||||||
onDelete,
|
onDelete,
|
||||||
}: ServicesListProps) {
|
}: ServicesListProps) {
|
||||||
const { openDrawer } = useDialog();
|
const { openDrawer, openDialog, closeDialog } = useDialog();
|
||||||
const [deleteRunService] = useDeleteRunServiceMutation();
|
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
|
||||||
const [deleteRunServiceConfig] = useDeleteRunServiceConfigMutation();
|
|
||||||
|
|
||||||
const deleteServiceAndConfig = async (appID: string, serviceID: string) => {
|
|
||||||
await deleteRunService({ variables: { serviceID } });
|
|
||||||
await deleteRunServiceConfig({ variables: { appID, serviceID } });
|
|
||||||
await onDelete?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
const viewService = async (service: RunService) => {
|
const viewService = async (service: RunService) => {
|
||||||
openDrawer({
|
openDrawer({
|
||||||
@@ -76,6 +60,7 @@ export default function ServicesList({
|
|||||||
initialData={{
|
initialData={{
|
||||||
...service.config,
|
...service.config,
|
||||||
image: service.config?.image?.image,
|
image: service.config?.image?.image,
|
||||||
|
subdomain: service.subdomain,
|
||||||
command: service.config?.command?.join(' '),
|
command: service.config?.command?.join(' '),
|
||||||
ports: service.config?.ports?.map((item) => ({
|
ports: service.config?.ports?.map((item) => ({
|
||||||
port: item.port,
|
port: item.port,
|
||||||
@@ -95,28 +80,16 @@ export default function ServicesList({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteService = async (serviceID: string) => {
|
const deleteService = async (service: RunService) => {
|
||||||
await toast.promise(
|
openDialog({
|
||||||
deleteServiceAndConfig(currentProject.id, serviceID),
|
component: (
|
||||||
{
|
<DeleteServiceModal
|
||||||
loading: 'Deleteing the service...',
|
service={service}
|
||||||
success: `The service has been deleted successfully.`,
|
close={closeDialog}
|
||||||
error: (arg: ApolloError) => {
|
onDelete={onDelete}
|
||||||
// we need to get the internal error message from the GraphQL error
|
/>
|
||||||
const { internal } = arg.graphQLErrors[0]?.extensions || {};
|
),
|
||||||
const { message } = (internal as Record<string, any>)?.error || {};
|
});
|
||||||
|
|
||||||
// we use the default Apollo error message if we can't find the
|
|
||||||
// internal error message
|
|
||||||
return (
|
|
||||||
message ||
|
|
||||||
arg.message ||
|
|
||||||
'An error occurred while deleting the service. Please try again.'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -203,7 +176,7 @@ export default function ServicesList({
|
|||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||||
sx={{ color: 'error.main' }}
|
sx={{ color: 'error.main' }}
|
||||||
onClick={() => deleteService(service.id)}
|
onClick={() => deleteService(service)}
|
||||||
>
|
>
|
||||||
<TrashIcon className="h-4 w-4" />
|
<TrashIcon className="h-4 w-4" />
|
||||||
<Text className="font-medium" color="error">
|
<Text className="font-medium" color="error">
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetStorageSettingsDocument,
|
GetStorageSettingsDocument,
|
||||||
|
Software_Type_Enum,
|
||||||
|
useGetSoftwareVersionsQuery,
|
||||||
useGetStorageSettingsQuery,
|
useGetStorageSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
@@ -30,8 +32,6 @@ export type StorageServiceVersionFormValues = Yup.InferType<
|
|||||||
typeof validationSchema
|
typeof validationSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const AVAILABLE_STORAGE_VERSIONS = ['0.3.5', '0.3.4', '0.3.3', '0.3.2'];
|
|
||||||
|
|
||||||
export default function StorageServiceVersionSettings() {
|
export default function StorageServiceVersionSettings() {
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
@@ -44,9 +44,16 @@ export default function StorageServiceVersionSettings() {
|
|||||||
fetchPolicy: 'cache-only',
|
fetchPolicy: 'cache-only',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: storageVersionsData } = useGetSoftwareVersionsQuery({
|
||||||
|
variables: {
|
||||||
|
software: Software_Type_Enum.Storage,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { version } = data?.config?.storage || {};
|
const { version } = data?.config?.storage || {};
|
||||||
|
const versions = storageVersionsData?.softwareVersions || [];
|
||||||
const availableVersions = Array.from(
|
const availableVersions = Array.from(
|
||||||
new Set(AVAILABLE_STORAGE_VERSIONS).add(version),
|
new Set(versions.map((el) => el.version)).add(version),
|
||||||
)
|
)
|
||||||
.sort()
|
.sort()
|
||||||
.reverse()
|
.reverse()
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ query GetProjectMetrics(
|
|||||||
) {
|
) {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
functionsDuration: getFunctionsDuration(appID: $appId, from: $from, to: $to) {
|
||||||
|
value
|
||||||
|
}
|
||||||
postgresVolumeCapacity: getPostgresVolumeCapacity(appID: $appId) {
|
postgresVolumeCapacity: getPostgresVolumeCapacity(appID: $appId) {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|||||||
9
dashboard/src/gql/platform/getSoftwareVersions.gql
Normal file
9
dashboard/src/gql/platform/getSoftwareVersions.gql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
query getSoftwareVersions($software: software_type_enum!) {
|
||||||
|
softwareVersions(
|
||||||
|
where: { software: { _eq: $software } }
|
||||||
|
order_by: { version: desc }
|
||||||
|
) {
|
||||||
|
version
|
||||||
|
software
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
query getRunService($id: uuid!, $resolve: Boolean!) {
|
query getRunService($id: uuid!, $resolve: Boolean!) {
|
||||||
runService(id: $id) {
|
runService(id: $id) {
|
||||||
id
|
id
|
||||||
|
subdomain
|
||||||
config(resolve: $resolve) {
|
config(resolve: $resolve) {
|
||||||
name
|
name
|
||||||
image {
|
image {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ query getRunServices(
|
|||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
|
subdomain
|
||||||
config(resolve: $resolve) {
|
config(resolve: $resolve) {
|
||||||
name
|
name
|
||||||
image {
|
image {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
mutation insertRunService($object: run_service_insert_input!) {
|
mutation insertRunService($object: run_service_insert_input!) {
|
||||||
insertRunService(object: $object) {
|
insertRunService(object: $object) {
|
||||||
id
|
id
|
||||||
appID
|
subdomain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,10 +133,12 @@ export default function SelectWorkspaceAndProject() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<div className="flex w-full justify-center">
|
||||||
delay={500}
|
<ActivityIndicator
|
||||||
label="Loading workspaces and projects..."
|
delay={500}
|
||||||
/>
|
label="Loading workspaces and projects..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2089
dashboard/src/utils/__generated__/graphql.ts
generated
2089
dashboard/src/utils/__generated__/graphql.ts
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,17 @@
|
|||||||
# @nhost/docs
|
# @nhost/docs
|
||||||
|
|
||||||
|
## 0.6.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 9aaa407d2: Fix messaging around Run's private beta
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 819e1e97d: update fqdn format for nhost run
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ Currently, only services of type `http` can be exposed to the internet.
|
|||||||
|
|
||||||
2. Once the service of type `http` is published, you can connect to it using a URL with the following format:
|
2. Once the service of type `http` is published, you can connect to it using a URL with the following format:
|
||||||
|
|
||||||
`https://<subdomain>-<svc_name>-<port>.svc.<region>.nhost.run`
|
`https://<run_service_subdomain>-<port>.svc.<region>.nhost.run`
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
`https://zlbmqjfczuwqvsquujno-mysvc-3000.svc.eu-central-1.nhost.run`
|
`https://zlbmqjfczuwqvsquujno-3000.svc.eu-central-1.nhost.run`
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Nhost Run enables you to seamlessly incorporate your custom software within your
|
|||||||

|

|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
Currently Nhost Run is in private beta. If you are interested in using this service feel free to reach out to us via [email](mailto:support@nhost.io), [GitHub](https://github.com/nhost/nhost/issues), or [Discord](https://discord.com/invite/9V7Qb2U)
|
Currently Nhost Run is in public beta. If you find any bugs or if you have any feedback and suggestions, please reach out to us via [email](mailto:support@nhost.io), [GitHub](https://github.com/nhost/nhost/issues), or [Discord](https://discord.com/invite/9V7Qb2U)
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Use Cases
|
## Use Cases
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/docs",
|
"name": "@nhost/docs",
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
|
|||||||
@@ -726,13 +726,226 @@
|
|||||||
"title": "Service Restarts",
|
"title": "Service Restarts",
|
||||||
"type": "timeseries"
|
"type": "timeseries"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "bytes"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 37
|
||||||
|
},
|
||||||
|
"id": 39,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum by(ingress) (irate(nginx_ingress_controller_response_size_sum[$__rate_interval]))",
|
||||||
|
"interval": "2m",
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "sum(irate(fastly_prom_exporter_bytes_sent[$__rate_interval]))",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "2m",
|
||||||
|
"legendFormat": "storage-cdn",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Egress Traffic",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 37
|
||||||
|
},
|
||||||
|
"id": 41,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"editorMode": "builder",
|
||||||
|
"expr": "sum by(ingress) (irate(nginx_ingress_controller_requests[$__interval]))",
|
||||||
|
"interval": "2m",
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "${DS_PROMETHEUS}"
|
||||||
|
},
|
||||||
|
"editorMode": "builder",
|
||||||
|
"expr": "sum(irate(fastly_prom_exporter_requests_total[$__interval]))",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "2m",
|
||||||
|
"legendFormat": "storage-cdn",
|
||||||
|
"range": true,
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Requests Rate",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"collapsed": false,
|
"collapsed": false,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 37
|
"y": 45
|
||||||
},
|
},
|
||||||
"id": 10,
|
"id": 10,
|
||||||
"panels": [],
|
"panels": [],
|
||||||
@@ -801,7 +1014,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 38
|
"y": 46
|
||||||
},
|
},
|
||||||
"id": 12,
|
"id": 12,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -908,7 +1121,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 38
|
"y": 46
|
||||||
},
|
},
|
||||||
"id": 14,
|
"id": 14,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -1015,7 +1228,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 46
|
"y": 54
|
||||||
},
|
},
|
||||||
"id": 16,
|
"id": 16,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -1120,7 +1333,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 46
|
"y": 54
|
||||||
},
|
},
|
||||||
"id": 19,
|
"id": 19,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -1171,7 +1384,7 @@
|
|||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 54
|
"y": 62
|
||||||
},
|
},
|
||||||
"id": 21,
|
"id": 21,
|
||||||
"panels": [],
|
"panels": [],
|
||||||
@@ -1240,7 +1453,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 55
|
"y": 63
|
||||||
},
|
},
|
||||||
"id": 22,
|
"id": 22,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -1347,7 +1560,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 55
|
"y": 63
|
||||||
},
|
},
|
||||||
"id": 23,
|
"id": 23,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -1397,7 +1610,7 @@
|
|||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 63
|
"y": 71
|
||||||
},
|
},
|
||||||
"id": 25,
|
"id": 25,
|
||||||
"panels": [],
|
"panels": [],
|
||||||
@@ -1466,7 +1679,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 64
|
"y": 72
|
||||||
},
|
},
|
||||||
"id": 26,
|
"id": 26,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -1573,7 +1786,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 64
|
"y": 72
|
||||||
},
|
},
|
||||||
"id": 27,
|
"id": 27,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -1623,7 +1836,7 @@
|
|||||||
"h": 1,
|
"h": 1,
|
||||||
"w": 24,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 72
|
"y": 80
|
||||||
},
|
},
|
||||||
"id": 29,
|
"id": 29,
|
||||||
"panels": [],
|
"panels": [],
|
||||||
@@ -1692,7 +1905,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 73
|
"y": 81
|
||||||
},
|
},
|
||||||
"id": 30,
|
"id": 30,
|
||||||
"options": {
|
"options": {
|
||||||
@@ -1799,7 +2012,7 @@
|
|||||||
"h": 8,
|
"h": 8,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 73
|
"y": 81
|
||||||
},
|
},
|
||||||
"id": 31,
|
"id": 31,
|
||||||
"options": {
|
"options": {
|
||||||
|
|||||||
Reference in New Issue
Block a user