├── .github ├── FUNDING.yml └── workflows │ ├── publish.yaml │ ├── release.yaml │ ├── cli-test.yaml │ └── dashboard-run-test.yaml ├── .dockerignore ├── vhs ├── .gitignore ├── new_project_non-interactive.tape └── new_project.tape ├── pkg ├── dashboard │ └── frontend │ │ ├── test-app │ │ ├── .gitignore │ │ ├── tsconfig.json │ │ ├── migrations │ │ │ ├── my-db │ │ │ │ └── 1_create_table.up.sql │ │ │ └── my-second-db │ │ │ │ └── 1_create_table.up.sql │ │ ├── docs-website │ │ │ ├── src │ │ │ │ ├── counter.js │ │ │ │ ├── main.js │ │ │ │ ├── javascript.svg │ │ │ │ └── style.css │ │ │ ├── package.json │ │ │ ├── .gitignore │ │ │ ├── index.html │ │ │ └── public │ │ │ │ └── vite.svg │ │ ├── vite-website │ │ │ ├── src │ │ │ │ ├── counter.js │ │ │ │ ├── main.js │ │ │ │ ├── javascript.svg │ │ │ │ └── style.css │ │ │ ├── package.json │ │ │ ├── .gitignore │ │ │ ├── index.html │ │ │ └── public │ │ │ │ └── vite.svg │ │ ├── local.nitric.yaml │ │ ├── nitric.yaml │ │ ├── package.json │ │ ├── services │ │ │ ├── my-test-db.ts │ │ │ └── my-test-secret.ts │ │ └── README.md │ │ ├── src │ │ ├── env.d.ts │ │ ├── components │ │ │ ├── apis │ │ │ │ ├── index.ts │ │ │ │ ├── APIMethodBadge.tsx │ │ │ │ └── APIRoutesList.tsx │ │ │ ├── shared │ │ │ │ ├── Loading │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── Loading.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── NotFoundAlert.tsx │ │ │ │ ├── ResourceDropdownMenu.tsx │ │ │ │ ├── Badge.tsx │ │ │ │ ├── TextField.tsx │ │ │ │ └── SectionCard.tsx │ │ │ ├── layout │ │ │ │ ├── AppLayout │ │ │ │ │ ├── index.ts │ │ │ │ │ └── NavigationItem.tsx │ │ │ │ └── BreadCrumbs.tsx │ │ │ ├── architecture │ │ │ │ ├── index.ts │ │ │ │ └── nodes │ │ │ │ │ ├── QueueNode.tsx │ │ │ │ │ ├── BucketNode.tsx │ │ │ │ │ ├── SecretNode.tsx │ │ │ │ │ ├── HttpProxyNode.tsx │ │ │ │ │ ├── JobNode.tsx │ │ │ │ │ ├── KeyValueNode.tsx │ │ │ │ │ ├── TopicNode.tsx │ │ │ │ │ ├── BatchNode.tsx │ │ │ │ │ ├── ServiceNode.tsx │ │ │ │ │ ├── APINode.tsx │ │ │ │ │ ├── WebsocketNode.tsx │ │ │ │ │ ├── WebsitesNode.tsx │ │ │ │ │ └── ScheduleNode.tsx │ │ │ ├── storage │ │ │ │ ├── index.ts │ │ │ │ ├── file-browser-styles.css │ │ │ │ ├── download-files.ts │ │ │ │ └── FileUpload.tsx │ │ │ ├── secrets │ │ │ │ ├── SecretVersionsTable │ │ │ │ │ └── index.ts │ │ │ │ ├── SecretsTreeView.tsx │ │ │ │ └── SecretsContext.tsx │ │ │ ├── ui │ │ │ │ ├── skeleton.tsx │ │ │ │ ├── collapsible.tsx │ │ │ │ ├── label.tsx │ │ │ │ ├── textarea.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── separator.tsx │ │ │ │ ├── checkbox.tsx │ │ │ │ ├── switch.tsx │ │ │ │ ├── tooltip.tsx │ │ │ │ ├── popover.tsx │ │ │ │ ├── badge.tsx │ │ │ │ ├── scroll-area.tsx │ │ │ │ ├── alert.tsx │ │ │ │ ├── tabs.tsx │ │ │ │ └── button.tsx │ │ │ ├── logs │ │ │ │ └── FilterTrigger.tsx │ │ │ ├── databases │ │ │ │ └── DatabasesMenu.tsx │ │ │ ├── events │ │ │ │ ├── EventsMenu.tsx │ │ │ │ └── EventsHistory.tsx │ │ │ └── websites │ │ │ │ ├── SitesList.tsx │ │ │ │ └── SiteTreeView.tsx │ │ ├── lib │ │ │ ├── hooks │ │ │ │ ├── fetcher.ts │ │ │ │ ├── use-sql-meta.ts │ │ │ │ ├── use-secret.ts │ │ │ │ ├── use-history.ts │ │ │ │ ├── use-logs.ts │ │ │ │ └── use-bucket.ts │ │ │ └── utils │ │ │ │ ├── is-valid-url.ts │ │ │ │ ├── cn.ts │ │ │ │ ├── get-host.ts │ │ │ │ ├── get-topic-subscriptions.ts │ │ │ │ ├── get-bucket-notifications.ts │ │ │ │ ├── copy-to-clipboard.ts │ │ │ │ ├── strings.ts │ │ │ │ ├── uniq.ts │ │ │ │ ├── format-file-size.ts │ │ │ │ ├── get-file-extension.ts │ │ │ │ ├── index.ts │ │ │ │ ├── format-response-time.ts │ │ │ │ ├── headers.ts │ │ │ │ ├── generate-path-params.ts │ │ │ │ ├── generate-path.ts │ │ │ │ ├── generate-response.ts │ │ │ │ ├── get-date-string.ts │ │ │ │ └── flatten-paths.ts │ │ ├── pages │ │ │ ├── index.astro │ │ │ ├── logs.astro │ │ │ ├── websites.astro │ │ │ ├── websockets.astro │ │ │ ├── secrets.astro │ │ │ ├── storage.astro │ │ │ ├── architecture.astro │ │ │ ├── databases.astro │ │ │ ├── jobs.astro │ │ │ ├── topics.astro │ │ │ ├── schedules.astro │ │ │ └── not-found.astro │ │ ├── styles │ │ │ └── grid.css │ │ ├── hooks │ │ │ ├── use-mobile.tsx │ │ │ └── use-params.tsx │ │ └── layouts │ │ │ └── Layout.astro │ │ ├── public │ │ ├── favicon.ico │ │ └── nitric-no-text.svg │ │ ├── cypress │ │ ├── fixtures │ │ │ └── photo.jpg │ │ ├── e2e │ │ │ ├── fathom.cy.ts │ │ │ ├── a11y.cy.ts │ │ │ ├── topics.cy.ts │ │ │ ├── schedules.cy.ts │ │ │ ├── websites.cy.ts │ │ │ └── architecture.cy.ts │ │ ├── tsconfig.json │ │ └── support │ │ │ └── e2e.ts │ │ ├── prettier.config.js │ │ ├── tsconfig.json │ │ ├── cypress.config.ts │ │ ├── astro.config.mjs │ │ ├── .gitignore │ │ ├── components.json │ │ ├── .eslintrc.cjs │ │ └── README.md ├── project │ ├── runtime │ │ ├── jvm.dockerfile │ │ ├── csharp.dockerfile │ │ ├── python.dockerfile │ │ ├── dart.dockerfile │ │ ├── javascript.dockerfile │ │ ├── typescript.dockerfile │ │ └── generate_test.go │ ├── dockerhost │ │ └── dockerhost.go │ ├── templates │ │ └── getter.go │ └── stack │ │ ├── azure.config.yaml │ │ ├── azuretf.config.yaml │ │ ├── gcp.config.yaml │ │ └── gcptf.config.yaml ├── collector │ └── default-migrations.dockerfile ├── cloud │ ├── errorsx │ │ └── logger.go │ └── env │ │ └── env.go ├── view │ └── tui │ │ ├── teax │ │ ├── quit.go │ │ ├── teax.go │ │ └── program.go │ │ ├── errors.go │ │ ├── tui.go │ │ ├── components │ │ ├── list │ │ │ └── list.go │ │ ├── validation │ │ │ └── validation.go │ │ └── view │ │ │ └── fragment.go │ │ ├── fragments │ │ ├── hotkey.go │ │ ├── errorlist.go │ │ └── tag.go │ │ ├── keys.go │ │ ├── commands │ │ ├── stack │ │ │ └── validators.go │ │ └── project │ │ │ └── validators.go │ │ └── printer.go ├── preview │ └── feature.go ├── version │ └── version.go ├── iox │ └── channelwriter.go ├── eventbus │ └── eventbus.go ├── validation │ ├── name.go │ └── rule.go ├── ports │ └── ports.go ├── netx │ └── freeport.go ├── system │ └── logs.go ├── pflagx │ └── string_enum.go └── env │ └── env.go ├── .releaserc.json ├── .gitignore ├── main.go ├── tools └── tools.go ├── cmd ├── version.go └── build.go ├── docs └── assets │ └── nitric-logo.svg ├── .golangci.yml ├── hack ├── github_release │ └── main.go └── modversion │ └── main.go ├── mocks └── mock_utils │ └── mock_getter.go └── .goreleaser.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: nitrictech -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .git/ 3 | bin/ -------------------------------------------------------------------------------- /vhs/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore generated demo images 2 | *.gif -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .nitric/ -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrictech/cli/HEAD/pkg/dashboard/frontend/public/favicon.ico -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/apis/index.ts: -------------------------------------------------------------------------------- 1 | import APIExplorer from './APIExplorer' 2 | 3 | export default APIExplorer 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | import Loading from './Loading' 2 | 3 | export default Loading 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/layout/AppLayout/index.ts: -------------------------------------------------------------------------------- 1 | import AppLayout from './AppLayout' 2 | 3 | export default AppLayout 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/index.ts: -------------------------------------------------------------------------------- 1 | import Architecture from './Architecture' 2 | 3 | export default Architecture 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/fixtures/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrictech/cli/HEAD/pkg/dashboard/frontend/cypress/fixtures/photo.jpg -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/storage/index.ts: -------------------------------------------------------------------------------- 1 | import StorageExplorer from './StorageExplorer' 2 | 3 | export default StorageExplorer 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/prettier.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | singleQuote: true, 3 | semi: false, 4 | plugins: ['prettier-plugin-tailwindcss'], 5 | } 6 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/hooks/fetcher.ts: -------------------------------------------------------------------------------- 1 | export const fetcher = (options?: RequestInit) => (url: string) => 2 | fetch(url, options).then((r) => r.json()) 3 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/index.ts: -------------------------------------------------------------------------------- 1 | import { SecretVersionsTable } from './SecretVersionsTable' 2 | 3 | export default SecretVersionsTable 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "target": "ESNext", 5 | "moduleResolution": "node", 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main"], 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | "@semantic-release/github" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/is-valid-url.ts: -------------------------------------------------------------------------------- 1 | export const isValidUrl = (value: string) => { 2 | try { 3 | return !decodeURI(value).includes('%') 4 | } catch (e) { 5 | return false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /pkg/project/runtime/jvm.dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM openjdk:22-slim 3 | 4 | ARG HANDLER 5 | 6 | COPY $HANDLER /usr/app/app.jar 7 | 8 | CMD ["java", "-jar", "/usr/app/app.jar"] 9 | 10 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/cn.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/e2e/fathom.cy.ts: -------------------------------------------------------------------------------- 1 | describe('fathom test suite', () => { 2 | it(`Should include fathom script`, () => { 3 | cy.visit('/') 4 | cy.get('#fathom-script[data-site=FAKE1234]').should('exist') 5 | }) 6 | }) 7 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/get-host.ts: -------------------------------------------------------------------------------- 1 | const isDev = import.meta.env.DEV 2 | 3 | export const getHost = () => { 4 | if (typeof window === 'undefined') { 5 | return null 6 | } 7 | 8 | return isDev ? 'localhost:49152' : window.location.host 9 | } 10 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/get-topic-subscriptions.ts: -------------------------------------------------------------------------------- 1 | import type { Topic, Subscriber } from '@/types' 2 | 3 | export const getTopicSubscriptions = ( 4 | topic: Topic, 5 | subscriptions: Subscriber[], 6 | ) => subscriptions.filter((n) => n.topic === topic.name) 7 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import APIExplorer from "../components/apis/APIExplorer"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/logs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from "../layouts/Layout.astro"; 3 | import LogsExplorer from "@/components/logs/LogsExplorer"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "react", 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/*": ["./src/*"], 9 | }, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/get-bucket-notifications.ts: -------------------------------------------------------------------------------- 1 | import type { Bucket, Notification } from '@/types' 2 | 3 | export const getBucketNotifications = ( 4 | bucket: Bucket, 5 | notifications: Notification[], 6 | ) => notifications.filter((n) => n.bucket === bucket.name) 7 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/websites.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import SiteExplorer from "@/components/websites/SiteExplorer"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/websockets.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import WSExplorer from "../components/websockets/WSExplorer"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["esnext", "dom"], 5 | "types": ["cypress", "cypress-axe"], 6 | "resolveJsonModule": true, 7 | "isolatedModules": false, 8 | }, 9 | "include": ["**/*.ts"], 10 | } 11 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/secrets.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import SecretsExplorer from "@/components/secrets/SecretsExplorer"; 3 | import Layout from "@/layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/storage.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import StorageExplorer from "../components/storage/StorageExplorer"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/storage/file-browser-styles.css: -------------------------------------------------------------------------------- 1 | .file-explorer .chonky-activeButton, 2 | .MuiPopover-root .chonky-activeButton, 3 | .chonky-selectionSizeText { 4 | @apply !text-primary; 5 | } 6 | 7 | [data-test-id='file-entry'] > *:first-child { 8 | @apply !bg-blue-100; 9 | } 10 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/copy-to-clipboard.ts: -------------------------------------------------------------------------------- 1 | export const copyToClipboard = (str: string) => { 2 | const focused = window.document.hasFocus() 3 | if (focused) { 4 | window.navigator?.clipboard?.writeText(str) 5 | } else { 6 | console.warn('Unable to copy to clipboard') 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/architecture.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import ArchitectureExplorer from "../components/architecture"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/databases.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DatabasesExplorer from "@/components/databases/DatabasesExplorer"; 3 | import Layout from "@/layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/jobs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import EventExplorer from "../components/events/EventsExplorer"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/topics.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import EventExplorer from "../components/events/EventsExplorer"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/collector/default-migrations.dockerfile: -------------------------------------------------------------------------------- 1 | # Default migrations dockerfile 2 | FROM migrate/migrate 3 | 4 | ENV DB_URL="" 5 | ENV NITRIC_DB_NAME="" 6 | 7 | ARG MIGRATIONS_PATH 8 | 9 | COPY ${MIGRATIONS_PATH} /migrations 10 | 11 | ENTRYPOINT ["sh", "-c", "migrate -path=/migrations -database $DB_URL up"] 12 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/pages/schedules.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import EventExplorer from "../components/events/EventsExplorer"; 3 | import Layout from "../layouts/Layout.astro"; 4 | --- 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/migrations/my-db/1_create_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE my_migration_table ( 2 | id SERIAL PRIMARY KEY, 3 | name TEXT NOT NULL DEFAULT '' 4 | ); 5 | 6 | -- seed some data 7 | INSERT INTO my_migration_table (name) VALUES ('my-db-foo'); 8 | INSERT INTO my_migration_table (name) VALUES ('my-db-bar'); -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | e2e: { 5 | baseUrl: 'http://localhost:49152', 6 | setupNodeEvents(on, config) { 7 | // implement node event listeners here 8 | }, 9 | chromeWebSecurity: false, 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/index.ts: -------------------------------------------------------------------------------- 1 | import Loading from './Loading' 2 | import Badge from './Badge' 3 | import FieldRows, { type FieldRow } from './FieldRows' 4 | import Spinner from './Spinner' 5 | import Tabs from './Tabs' 6 | 7 | export { Loading, Badge, FieldRows, Spinner, Tabs } 8 | export type { FieldRow } 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/docs-website/src/counter.js: -------------------------------------------------------------------------------- 1 | export function setupCounter(element) { 2 | let counter = 0 3 | const setCounter = (count) => { 4 | counter = count 5 | element.innerHTML = `count is ${counter}` 6 | } 7 | element.addEventListener('click', () => setCounter(counter + 1)) 8 | setCounter(0) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/migrations/my-second-db/1_create_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE my_migration_table ( 2 | id SERIAL PRIMARY KEY, 3 | name TEXT NOT NULL DEFAULT '' 4 | ); 5 | 6 | -- seed some data 7 | INSERT INTO my_migration_table (name) VALUES ('my-second-db-foo'); 8 | INSERT INTO my_migration_table (name) VALUES ('my-second-db-bar'); -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/vite-website/src/counter.js: -------------------------------------------------------------------------------- 1 | export function setupCounter(element) { 2 | let counter = 0 3 | const setCounter = (count) => { 4 | counter = count 5 | element.innerHTML = `count is ${counter}` 6 | } 7 | element.addEventListener('click', () => setCounter(counter + 1)) 8 | setCounter(0) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/docs-website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs-website", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^6.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/vite-website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-website", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^6.1.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/strings.ts: -------------------------------------------------------------------------------- 1 | export const formatJSON = ( 2 | value: any, 3 | space: string | number | undefined = 2, 4 | ) => { 5 | try { 6 | if (typeof value === 'string') { 7 | value = JSON.parse(value) 8 | } 9 | 10 | return JSON.stringify(value, null, space) 11 | } catch (e) { 12 | return value 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils/cn' 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | all.coverprofile 15 | 16 | # Go workspace file 17 | go.work 18 | .vscode/ 19 | 20 | dist/ 21 | 22 | # Ignore nitric temp files 23 | .nitric/ -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible' 2 | 3 | const Collapsible = CollapsiblePrimitive.Root 4 | 5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 6 | 7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 8 | 9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 10 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import tailwind from '@astrojs/tailwind' 3 | 4 | import react from '@astrojs/react' 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | integrations: [tailwind({ applyBaseStyles: false, nesting: true }), react()], 9 | outDir: '../dist', 10 | prefetch: { 11 | prefetchAll: true, 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/docs-website/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/vite-website/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | cypress/downloads 24 | cypress/screenshots 25 | cypress/videos -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/uniq.ts: -------------------------------------------------------------------------------- 1 | export const sortedUniq = (arr: any[]) => [...new Set(arr)].sort() 2 | 3 | export const uniqBy = (arr: any[], iteratee: any) => { 4 | if (typeof iteratee === 'string') { 5 | const prop = iteratee 6 | iteratee = (item: any) => item[prop] 7 | } 8 | 9 | return arr.filter( 10 | (x, i, self) => i === self.findIndex((y) => iteratee(x) === iteratee(y)), 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.cjs", 8 | "css": "./src/styles/globals.css", 9 | "baseColor": "gray", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils/cn" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vhs/new_project_non-interactive.tape: -------------------------------------------------------------------------------- 1 | # Where should we write the GIF? 2 | Output new_project_non-interactive.gif 3 | 4 | Set Framerate 16 5 | 6 | # Set up a 1200x600 terminal with 46px font. 7 | Set FontSize 46 8 | Set Width 2000 9 | Set Height 800 10 | 11 | # Type a command in the terminal. 12 | Type `../bin/nitric new my-project "official/TypeScript - Starter"` 13 | 14 | # Run the command by pressing enter. 15 | Enter 16 | 17 | Sleep 15s -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/format-file-size.ts: -------------------------------------------------------------------------------- 1 | export function formatFileSize(bytes: number): string { 2 | const units = ['bytes', 'KB', 'MB', 'GB', 'TB'] 3 | let size = bytes 4 | let unitIndex = 0 5 | 6 | while (size >= 1024 && unitIndex < units.length - 1) { 7 | size /= 1024 8 | unitIndex++ 9 | } 10 | 11 | const unit = units[unitIndex] 12 | 13 | return `${bytes > 1024 ? size.toFixed(2) : size} ${unit}` 14 | } 15 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/docs-website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/vite-website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/local.nitric.yaml: -------------------------------------------------------------------------------- 1 | # local.nitric.yaml 2 | # Local development configuration for Nitric services. 3 | 4 | apis: 5 | # API Resources 6 | first-api: 7 | port: 6001 8 | 9 | second-api: 10 | port: 6002 11 | 12 | my-db-api: 13 | port: 6003 14 | 15 | my-secret-api: 16 | port: 6004 17 | 18 | websockets: 19 | # WebSocket Resources 20 | socket: 21 | port: 7001 22 | 23 | socket-2: 24 | port: 7002 25 | 26 | socket-3: 27 | port: 7003 28 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/get-file-extension.ts: -------------------------------------------------------------------------------- 1 | export function getFileExtension(contentType: string): string { 2 | switch (contentType) { 3 | case 'application/xml': 4 | case 'text/xml': 5 | return '.xml' 6 | case 'application/json': 7 | return '.json' 8 | case 'application/pdf': 9 | return '.pdf' 10 | case 'application/zip': 11 | return '.zip' 12 | case 'application/octet-stream': 13 | return '.bin' 14 | default: 15 | return '' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './flatten-paths' 2 | export * from './format-file-size' 3 | export * from './format-response-time' 4 | export * from './generate-path-params' 5 | export * from './generate-path' 6 | export * from './get-file-extension' 7 | export * from './get-date-string' 8 | export * from './headers' 9 | export * from './uniq' 10 | export * from './get-host' 11 | export * from './generate-response' 12 | export * from './strings' 13 | export * from './cn' 14 | export * from './is-valid-url' 15 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/styles/grid.css: -------------------------------------------------------------------------------- 1 | .rdg { 2 | @apply box-border h-full overflow-x-auto overflow-y-scroll rounded-lg bg-code; 3 | } 4 | 5 | .rdg *, 6 | .rdg ::after, 7 | .rdg ::before { 8 | box-sizing: inherit; 9 | } 10 | 11 | .rdg-cell { 12 | @apply flex flex border-b border-r border-gray-600 bg-code text-sm text-gray-200; 13 | white-space: nowrap; 14 | overflow: hidden; 15 | text-overflow: ellipsis; 16 | line-height: inherit; 17 | } 18 | 19 | .rdg-header-row .rdg-cell { 20 | @apply bg-gray-700 text-gray-100; 21 | } 22 | -------------------------------------------------------------------------------- /pkg/project/runtime/csharp.dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 2 | 3 | # https://github.com/dotnet/runtime/issues/94909 4 | ENV DOTNET_EnableWriteXorExecute=0 5 | 6 | ARG HANDLER 7 | 8 | WORKDIR /app 9 | 10 | # Copy everything 11 | COPY . ./ 12 | 13 | # Build and publish a release 14 | RUN dotnet publish -c Release -o out --self-contained -p:PublishSingleFile=true 15 | 16 | # Build runtime image 17 | FROM mcr.microsoft.com/dotnet/runtime-deps:8.0 18 | 19 | ARG HANDLER 20 | 21 | COPY --from=build /app/out/${HANDLER} /usr/bin/handler 22 | 23 | ENTRYPOINT ["handler"] -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/storage/download-files.ts: -------------------------------------------------------------------------------- 1 | export const downloadFiles = async ( 2 | files: Array<{ url: string; name: string }>, 3 | ): Promise => { 4 | const promises = files.map(async (file) => { 5 | const response = await fetch(file.url) 6 | const blob = await response.blob() 7 | const link = document.createElement('a') 8 | link.href = window.URL.createObjectURL(blob) 9 | link.setAttribute('download', file.name) 10 | document.body.appendChild(link) 11 | link.click() 12 | document.body.removeChild(link) 13 | }) 14 | 15 | await Promise.all(promises) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/format-response-time.ts: -------------------------------------------------------------------------------- 1 | export function formatResponseTime(milliseconds: number): string { 2 | if (milliseconds < 1000) { 3 | return milliseconds.toFixed(2) + ' ms' 4 | } else if (milliseconds < 60 * 1000) { 5 | return (milliseconds / 1000).toFixed(2) + ' s' 6 | } else if (milliseconds < 60 * 60 * 1000) { 7 | return (milliseconds / (60 * 1000)).toFixed(2) + ' m' 8 | } else if (milliseconds < 24 * 60 * 60 * 1000) { 9 | return (milliseconds / (60 * 60 * 1000)).toFixed(2) + ' h' 10 | } else { 11 | return (milliseconds / (24 * 60 * 60 * 1000)).toFixed(2) + ' d' 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/headers.ts: -------------------------------------------------------------------------------- 1 | import type { FieldRow } from '../../components/shared/FieldRows' 2 | 3 | export const headersToObject = (headers: Headers): Record => { 4 | return Array.from(headers.entries()).reduce( 5 | (acc, [key, value]) => { 6 | acc[key] = value 7 | return acc 8 | }, 9 | {} as Record, 10 | ) 11 | } 12 | 13 | export const fieldRowArrToHeaders = (arr: FieldRow[]) => { 14 | const headers = new Headers() 15 | arr.forEach((obj) => { 16 | if (obj.key) { 17 | headers.append(obj.key, obj.value) 18 | } 19 | }) 20 | return headers 21 | } 22 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/nitric.yaml: -------------------------------------------------------------------------------- 1 | name: test-app 2 | services: 3 | - match: ./services/*.ts 4 | start: yarn dev:functions $SERVICE_PATH 5 | websites: 6 | - basedir: ./vite-website 7 | build: 8 | command: yarn build 9 | output: ./dist 10 | dev: 11 | command: yarn dev --port 7850 12 | url: http://localhost:7850 13 | - basedir: ./docs-website 14 | path: /docs 15 | build: 16 | command: yarn build --base=/docs 17 | output: ./dist 18 | dev: 19 | command: yarn dev --port 7851 --base=/docs 20 | url: http://localhost:7851/docs 21 | preview: 22 | - sql-databases 23 | - websites 24 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/logs/FilterTrigger.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSidebar } from '../ui/sidebar' 3 | import { Button } from '../ui/button' 4 | import { FunnelIcon } from '@heroicons/react/24/outline' 5 | 6 | const FilterTrigger: React.FC = () => { 7 | const { toggleSidebar } = useSidebar() 8 | 9 | return ( 10 | 19 | ) 20 | } 21 | 22 | export default FilterTrigger 23 | -------------------------------------------------------------------------------- /vhs/new_project.tape: -------------------------------------------------------------------------------- 1 | # Where should we write the GIF? 2 | Output new_project.gif 3 | 4 | Set Framerate 16 5 | 6 | # Set up a 1200x600 terminal with 46px font. 7 | Set FontSize 46 8 | Set Width 1900 9 | Set Height 1400 10 | 11 | # Type a command in the terminal. 12 | Type "../bin/nitric new" 13 | 14 | # Run the command by pressing enter. 15 | Enter 16 | 17 | # Admire the output for a bit. 18 | Sleep 5s 19 | 20 | Type "example-project&" 21 | 22 | Sleep 4s 23 | 24 | Backspace 25 | 26 | Sleep 2s 27 | 28 | Enter 29 | 30 | Sleep 4s 31 | 32 | Down 33 | 34 | Sleep 1s 35 | 36 | Down 37 | 38 | Sleep 1s 39 | 40 | Up 41 | 42 | Sleep 1s 43 | 44 | Enter 45 | 46 | Sleep 15s -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener('change', onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener('change', onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/apis/APIMethodBadge.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react' 2 | import { Badge } from '../shared' 3 | import type { Method } from '../../types' 4 | 5 | interface Props { 6 | method: Method 7 | className?: string 8 | } 9 | 10 | export const APIMethodBadge: FC = ({ method, className }) => { 11 | return ( 12 | 26 | {method} 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish 3 | 4 | on: 5 | push: 6 | # run only against tags 7 | tags: 8 | - "*" 9 | 10 | env: 11 | FATHOM_SITE: ${{ vars.FATHOM_SITE }} 12 | 13 | jobs: 14 | publish: 15 | name: GoReleaser 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - uses: actions/setup-go@v3 23 | with: 24 | go-version: 1.22 25 | 26 | - uses: goreleaser/goreleaser-action@v3 27 | with: 28 | distribution: goreleaser 29 | version: 1.18 30 | args: release --rm-dist 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.NITRIC_BOT_TOKEN }} 33 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/generate-path-params.ts: -------------------------------------------------------------------------------- 1 | import type { FieldRow } from '../../components/shared' 2 | import type { APIRequest, Endpoint } from '../../types' 3 | 4 | export const generatePathParams = (endpoint: Endpoint, request: APIRequest) => { 5 | const pathParams: FieldRow[] = [] 6 | 7 | if (endpoint.params?.length) { 8 | endpoint.params.forEach((p) => { 9 | p.value.forEach((v) => { 10 | if (v.in === 'path') { 11 | const existing = request.pathParams.find((pp) => pp.key === v.name) 12 | 13 | pathParams.push({ 14 | key: v.name, 15 | value: existing?.value || '', 16 | }) 17 | } 18 | }) 19 | }) 20 | } 21 | 22 | return pathParams 23 | } 24 | -------------------------------------------------------------------------------- /pkg/project/runtime/python.dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | ARG HANDLER 4 | 5 | ENV HANDLER=${HANDLER} 6 | ENV PYTHONUNBUFFERED=TRUE 7 | 8 | RUN apt-get update -y && \ 9 | apt-get install -y ca-certificates git && \ 10 | update-ca-certificates 11 | 12 | RUN pip install --upgrade pip pipenv 13 | 14 | COPY . . 15 | 16 | # Guarantee lock file if we have a Pipfile and no Pipfile.lock 17 | RUN (stat Pipfile && pipenv lock) || echo "No Pipfile found" 18 | 19 | # Output a requirements.txt file for final module install if there is a Pipfile.lock found 20 | RUN (stat Pipfile.lock && pipenv requirements > requirements.txt) || echo "No Pipfile.lock found" 21 | 22 | RUN pip install --no-cache-dir -r requirements.txt 23 | 24 | ENTRYPOINT python -u $HANDLER 25 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/utils/generate-path.ts: -------------------------------------------------------------------------------- 1 | import type { FieldRow } from '../../components/shared' 2 | 3 | export function generatePath( 4 | path: string, 5 | pathParams: FieldRow[], 6 | queryParams: FieldRow[], 7 | ) { 8 | pathParams.forEach((p) => { 9 | path = path.replace(`{${p.key}}`, p.value) 10 | }) 11 | 12 | if (queryParams.length) { 13 | const searchParams = new URLSearchParams() 14 | 15 | queryParams.forEach((param) => { 16 | if (param.key) { 17 | searchParams.append(param.key, param.value) 18 | } 19 | }) 20 | 21 | const queryPath = searchParams.toString() 22 | 23 | if (queryPath) { 24 | path = `${path}?${queryPath.replace(/^(\?)/, '')}` 25 | } 26 | } 27 | 28 | return path 29 | } 30 | -------------------------------------------------------------------------------- /pkg/project/runtime/dart.dockerfile: -------------------------------------------------------------------------------- 1 | FROM dart:stable AS build 2 | 3 | ARG HANDLER 4 | WORKDIR /app 5 | 6 | # Resolve app dependencies. 7 | COPY pubspec.* ./ 8 | RUN dart pub get 9 | 10 | # Ensure the ./bin folder exists 11 | RUN mkdir -p ./bin 12 | 13 | # Copy app source code and AOT compile it. 14 | COPY . . 15 | # Ensure packages are still up-to-date if anything has changed 16 | RUN dart pub get --offline 17 | RUN dart compile exe ./${HANDLER} -o bin/main 18 | 19 | # Build a minimal serving image from AOT-compiled `/server` and required system 20 | # libraries and configuration files stored in `/runtime/` from the build stage. 21 | FROM alpine 22 | 23 | COPY --from=build /runtime/ / 24 | COPY --from=build /app/bin/main /app/bin/ 25 | 26 | ENTRYPOINT ["/app/bin/main"] 27 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-starter", 3 | "version": "1.0.0", 4 | "description": "nitric typescript starter template", 5 | "private": true, 6 | "dependencies": { 7 | "@nitric/sdk": "^1.3.0", 8 | "express": "^4.18.2", 9 | "pg": "^8.12.0" 10 | }, 11 | "engines": { 12 | "node": ">=20.0.0" 13 | }, 14 | "devDependencies": { 15 | "@types/express": "^4.17.21", 16 | "@types/pg": "^8.11.6", 17 | "dotenv": "^16.0.2", 18 | "nodemon": "^3.1.4", 19 | "ts-node": "^10.9.1", 20 | "typescript": "^4.8.3" 21 | }, 22 | "scripts": { 23 | "dev:functions": "nodemon -r dotenv/config", 24 | "install:websites": "yarn --cwd ./vite-website install && yarn --cwd ./docs-website install" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: 9 | 10 | env: 11 | FATHOM_SITE: ${{ vars.FATHOM_SITE }} 12 | 13 | jobs: 14 | release: 15 | name: semantic-release 16 | runs-on: ubuntu-latest 17 | outputs: 18 | new-release-published: ${{ steps.semantic-release.outputs.new_release_published }} 19 | new-release-version: ${{ steps.semantic-release.outputs.new_release_version }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | persist-credentials: false 25 | - id: semantic-release 26 | uses: cycjimmy/semantic-release-action@v4 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.NITRIC_BOT_TOKEN }} 29 | -------------------------------------------------------------------------------- /pkg/cloud/errorsx/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package errorsx 18 | 19 | type ServiceErrorLogger func(serviceName string, err error) 20 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/services/my-test-db.ts: -------------------------------------------------------------------------------- 1 | import { api, sql } from '@nitric/sdk' 2 | import pg from 'pg' 3 | const { Client } = pg 4 | 5 | const myDb = sql('my-db', { 6 | migrations: 'file://migrations/my-db', 7 | }) 8 | const mySecondDb = sql('my-second-db', { 9 | migrations: 'file://migrations/my-second-db', 10 | }) 11 | 12 | const dbApi = api('my-db-api') 13 | 14 | const getClient = async () => { 15 | const connStr = await myDb.connectionString() 16 | const client = new Client(connStr) 17 | 18 | return client 19 | } 20 | 21 | dbApi.get('/get', async (ctx) => { 22 | const client = await getClient() 23 | 24 | const res = await client.query('SELECT $1::text as message', ['Hello world!']) 25 | await client.end() 26 | 27 | return ctx.res.json(res.rows[0].message) 28 | }) 29 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | import 'cypress-axe' 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import "github.com/nitrictech/cli/cmd" 20 | 21 | func main() { 22 | cmd.Execute() 23 | } 24 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/lib/hooks/use-sql-meta.ts: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr' 2 | import { fetcher } from './fetcher' 3 | import { SQL_API, TABLE_QUERY } from '../constants' 4 | 5 | export interface SqlMetaResult { 6 | columns: { 7 | column_name: string 8 | data_type: string 9 | column_order: number 10 | }[] 11 | is_table: boolean 12 | qualified_name: string 13 | schema_name: string 14 | table_name: string 15 | } 16 | 17 | export const useSqlMeta = (connectionString?: string) => { 18 | const { data, mutate } = useSWR( 19 | connectionString ? SQL_API : null, 20 | fetcher({ 21 | method: 'POST', 22 | body: JSON.stringify({ query: TABLE_QUERY, connectionString }), 23 | }), 24 | ) 25 | 26 | return { 27 | data, 28 | mutate, 29 | loading: !data, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/QueueNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Queue } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type QueueNodeData = NodeBaseData 8 | 9 | export const QueueNode: ComponentType> = (props) => { 10 | const { data } = props 11 | 12 | return ( 13 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | //go:build tools 18 | // +build tools 19 | 20 | package tools 21 | 22 | import ( 23 | _ "github.com/golangci/golangci-lint/cmd/golangci-lint" 24 | ) 25 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/BucketNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Bucket } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type BucketNodeData = NodeBaseData 8 | 9 | export const BucketNode: ComponentType> = (props) => { 10 | const { data } = props 11 | 12 | return ( 13 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/SecretNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Secret } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type SecretNodeData = NodeBaseData 8 | 9 | export const SecretNode: ComponentType> = (props) => { 10 | const { data } = props 11 | 12 | return ( 13 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as LabelPrimitive from '@radix-ui/react-label' 3 | import { cva, type VariantProps } from 'class-variance-authority' 4 | 5 | import { cn } from '@/lib/utils/cn' 6 | 7 | const labelVariants = cva( 8 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )) 22 | Label.displayName = LabelPrimitive.Root.displayName 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/HttpProxyNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { HttpProxy } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type HttpProxyNodeData = NodeBaseData 8 | 9 | export const HttpProxyNode: ComponentType> = ( 10 | props, 11 | ) => { 12 | const { data } = props 13 | 14 | return ( 15 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/NotFoundAlert.tsx: -------------------------------------------------------------------------------- 1 | import React, { type PropsWithChildren } from 'react' 2 | import { Alert } from '../ui/alert' 3 | import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' 4 | 5 | interface Props extends PropsWithChildren { 6 | className?: string 7 | } 8 | 9 | const NotFoundAlert: React.FC = ({ children, className }) => { 10 | return ( 11 | 12 |
13 |
14 |
16 |
17 |

{children}

18 |
19 |
20 |
21 | ) 22 | } 23 | 24 | export default NotFoundAlert 25 | -------------------------------------------------------------------------------- /pkg/view/tui/teax/quit.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package teax 18 | 19 | import tea "github.com/charmbracelet/bubbletea" 20 | 21 | type QuitMsg struct{} 22 | 23 | func Quit() tea.Msg { 24 | return QuitMsg{} 25 | } 26 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/JobNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { BatchJob } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type JobNodeData = NodeBaseData 8 | 9 | export const JobNode: ComponentType> = (props) => { 10 | const { data } = props 11 | 12 | return ( 13 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/view/tui/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package tui 18 | 19 | import ( 20 | "os" 21 | ) 22 | 23 | func CheckErr(err error) { 24 | if err != nil { 25 | Error.Println(err.Error()) 26 | os.Exit(1) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/KeyValueNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { KeyValue } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type KeyValueNodeData = NodeBaseData 8 | 9 | export const KeyValueNode: ComponentType> = ( 10 | props, 11 | ) => { 12 | const { data } = props 13 | 14 | return ( 15 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/services/my-test-secret.ts: -------------------------------------------------------------------------------- 1 | import { api, secret } from '@nitric/sdk' 2 | 3 | const mySecret = secret('my-first-secret').allow('access', 'put') 4 | 5 | const mySecondSecret = secret('my-second-secret').allow('access', 'put') 6 | 7 | const shhApi = api('my-secret-api') 8 | 9 | shhApi.get('/get', async (ctx) => { 10 | const latestValue = await mySecret.latest().access() 11 | 12 | ctx.res.body = latestValue.asString() 13 | 14 | return ctx 15 | }) 16 | 17 | shhApi.post('/set', async (ctx) => { 18 | const data = ctx.req.json() 19 | 20 | await mySecret.put(JSON.stringify(data)) 21 | 22 | return ctx 23 | }) 24 | 25 | shhApi.post('/set-binary', async (ctx) => { 26 | const data = new Uint8Array(1024) 27 | for (let i = 0; i < data.length; i++) { 28 | data[i] = i % 256 29 | } 30 | 31 | await mySecret.put(data) 32 | 33 | return ctx 34 | }) 35 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/test-app/vite-website/src/main.js: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import javascriptLogo from './javascript.svg' 3 | import viteLogo from '/vite.svg' 4 | import { setupCounter } from './counter.js' 5 | 6 | document.querySelector('#app').innerHTML = ` 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |

Hello Nitric!

15 |
16 | 17 |
18 |

19 | Click on the Vite logo to learn more 20 |

21 |
22 | ` 23 | 24 | setupCounter(document.querySelector('#counter')) 25 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils/cn' 4 | 5 | export type TextareaProps = React.TextareaHTMLAttributes 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 |