Docker build
All checks were successful
/ Push Docker image to local registry (push) Successful in 3m28s
All checks were successful
/ Push Docker image to local registry (push) Successful in 3m28s
This commit is contained in:
parent
ea438cb8b9
commit
6a24fec70e
9 changed files with 103 additions and 17 deletions
9
.dockerignore
Normal file
9
.dockerignore
Normal file
|
@ -0,0 +1,9 @@
|
|||
.git
|
||||
|
||||
frontend/node_modules
|
||||
frontend/dist
|
||||
|
||||
backend/target
|
||||
backend/run
|
||||
backend/data
|
||||
backend/.env
|
36
.forgejo/workflows/publish-docker.yml
Normal file
36
.forgejo/workflows/publish-docker.yml
Normal file
|
@ -0,0 +1,36 @@
|
|||
nam: Publish Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to local registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: setup buildx
|
||||
uses: https://github.com/docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to local registry
|
||||
run: |
|
||||
BASE64_AUTH=`echo -n "$CI_REGISTRY_USER:$CI_REGISTRY_PASSWORD" | base64`
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"auths\": {\"$CI_REGISTRY\": {\"auth\": \"$BASE64_AUTH\"}}}" > ~/.docker/config.json
|
||||
env:
|
||||
CI_REGISTRY: https://git.kallestruik.nl
|
||||
CI_REGISTRY_USER: ${{ secrets.FORGEJO_USERNAME }}
|
||||
CI_REGISTRY_PASSWORD: ${{ secrets.FORGEJO_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: https://github.com/docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: "git.kallestruik.nl/knotes/v3:latest"
|
24
Dockerfile
Normal file
24
Dockerfile
Normal file
|
@ -0,0 +1,24 @@
|
|||
FROM docker.io/library/node:23-alpine3.21 AS frontend_builder
|
||||
WORKDIR /build/frontend
|
||||
COPY frontend .
|
||||
RUN corepack enable
|
||||
RUN pnpm install
|
||||
RUN pnpm run build
|
||||
|
||||
FROM docker.io/library/rust:1.86-slim-bookworm as backend_builder
|
||||
WORKDIR /build/backend
|
||||
COPY backend .
|
||||
RUN apt update && apt install -y clang libclang-dev
|
||||
RUN cargo build --release
|
||||
|
||||
FROM docker.io/library/debian:bookworm-slim as runner
|
||||
WORKDIR /app
|
||||
COPY --from=frontend_builder /build/frontend/dist ./frontend/
|
||||
COPY --from=backend_builder /build/backend/target/release/knotes-backend ./knotes-backend
|
||||
|
||||
ENV DATA_DIR=/data/docs
|
||||
ENV FRONTEND_DIR=/app/frontend
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
CMD ["./knotes-backend"]
|
|
@ -1,6 +1,6 @@
|
|||
use base64::prelude::*;
|
||||
use chrono::serde::ts_seconds;
|
||||
use chrono::{DateTime, Local, Utc};
|
||||
use chrono::{DateTime, Utc};
|
||||
use cookie::time::{Duration, OffsetDateTime};
|
||||
use dotenvy::dotenv;
|
||||
use futures_util::StreamExt;
|
||||
|
@ -98,6 +98,7 @@ struct Connection {
|
|||
bcast: BroadcastGroup,
|
||||
// This is purely here to keep the connection to the DB alive while there is at least one
|
||||
// connection.
|
||||
#[allow(dyn_drop)]
|
||||
_db_subscription: Arc<dyn Drop + Send + Sync>,
|
||||
}
|
||||
|
||||
|
@ -192,7 +193,8 @@ async fn create_openid_client(
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenv().expect("Failed to load .env file");
|
||||
// Allow loading .env to fail, since it won't be available in docker.
|
||||
let _ = dotenv();
|
||||
|
||||
let data_dir = env::var("DATA_DIR").expect("DATA_DIR not set");
|
||||
let data_dir = PathBuf::from_str(&data_dir).expect("DATA_DIR is not a valid path");
|
||||
|
@ -264,6 +266,7 @@ async fn main() {
|
|||
|
||||
let routes = api_routes.or(ws).or(frontend_files).or(index);
|
||||
|
||||
println!("Starting server on http://0.0.0.0:9000");
|
||||
warp::serve(routes).run(([0, 0, 0, 0], 9000)).await;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name": "knotes-frontend",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.6.3",
|
||||
"scripts": {
|
||||
"start": "vite --port 9000",
|
||||
"build": "vite build && tsc",
|
||||
|
|
|
@ -13,6 +13,7 @@ import { mdiPin, mdiPinOff, mdiPlus, mdiTrashCan } from "@mdi/js";
|
|||
import { createCollectionProperty, deleteCollectionProperty } from "~/lib/property";
|
||||
import { useEffect } from "react";
|
||||
import { PropertyTypeCombobox } from "../form/property_type_combobox";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
type CollectionSettingsDialogProps = {
|
||||
name?: string;
|
||||
|
@ -78,18 +79,18 @@ function SettingsSection(props: { collectionId: CollectionId }) {
|
|||
function PropertiesSection(props: { collectionId: CollectionId }) {
|
||||
const collection = useCollection(props.collectionId)
|
||||
|
||||
if (!collection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ensure that the properties array exists on the collection
|
||||
useEffect(() => {
|
||||
if (!collection.has("properties")) {
|
||||
if (collection && !collection.has("properties")) {
|
||||
collection.set("properties", new Y.Array() as any)
|
||||
}
|
||||
}, [collection])
|
||||
|
||||
const properties = collection.get("properties");
|
||||
const properties = collection?.get("properties");
|
||||
|
||||
if (!collection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -152,6 +153,7 @@ function PropertyItem(props: PropertyItemProps) {
|
|||
|
||||
function DangerSection(props: { collectionId: CollectionId }) {
|
||||
const collections = useCollections();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -182,8 +184,10 @@ function DangerSection(props: { collectionId: CollectionId }) {
|
|||
<AlertDialogAction asChild>
|
||||
<Button
|
||||
variant="destructive"
|
||||
// TODO: Navigate away from the page when clicked
|
||||
onClick={() => deleteCollection(collections, props.collectionId)}
|
||||
onClick={() => {
|
||||
navigate({ to: "/app" });
|
||||
deleteCollection(collections, props.collectionId);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
|
|
|
@ -8,7 +8,7 @@ import type { ColorName } from "~/lib/color";
|
|||
import { IconPicker } from "~/components/form/icon_picker";
|
||||
import type { IconName } from "~/lib/icon";
|
||||
import { CollectionPicker } from "~/components/form/collection_picker";
|
||||
import { type CollectionId, createNote } from "~/lib/metadata";
|
||||
import { type CollectionId, createNote, type NoteType } from "~/lib/metadata";
|
||||
import { useNotesMetadata } from "~/hooks/use-metadata";
|
||||
import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
@ -25,7 +25,7 @@ export function NewNoteDialog(props: NewNoteDialogProps) {
|
|||
const [collection, setCollection] = useState<CollectionId | undefined>();
|
||||
const [primaryColor, setPrimaryColor] = useState<ColorName | undefined>();
|
||||
const [secondaryColor, setSecondaryColor] = useState<ColorName | undefined>();
|
||||
const [type, setType] = useState<"text" | "canvas">("text");
|
||||
const [type, setType] = useState<NoteType>("text");
|
||||
|
||||
const navigate = useNavigate();
|
||||
function submit() {
|
||||
|
@ -102,6 +102,10 @@ export function NewNoteDialog(props: NewNoteDialogProps) {
|
|||
<RadioGroupItem value="canvas" id="type-canvas" />
|
||||
<Label htmlFor="type-canvas">Canvas</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="entity" id="type-entity" />
|
||||
<Label htmlFor="type-entity">Entity</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,8 @@ export type NoteId = string
|
|||
export type CollectionId = string
|
||||
export type PropertyId = string
|
||||
|
||||
export type NoteType = "text" | "canvas" | "entity"
|
||||
|
||||
export type NotesMetadata = YArray<NoteMetadata>
|
||||
|
||||
export type NoteMetadata = YMap<{
|
||||
|
@ -20,7 +22,7 @@ export type NoteMetadata = YMap<{
|
|||
secondaryColor: ColorName | ""
|
||||
pinned: boolean
|
||||
properties: YArray<NoteProperty>
|
||||
type: "text" | "canvas"
|
||||
type: NoteType
|
||||
}>
|
||||
|
||||
export type NoteProperty = YMap<{
|
||||
|
@ -61,13 +63,13 @@ export function createCollection(md: CollectionsMetadata, data: {
|
|||
return collection;
|
||||
}
|
||||
|
||||
export function deleteCollection(md: CollectionsMetadata, id: CollectionId) {
|
||||
const index = md.toArray().findIndex(it => it.get("id") == id);
|
||||
export function deleteCollection(cmd: CollectionsMetadata, id: CollectionId) {
|
||||
const index = cmd.toArray().findIndex(it => it.get("id") == id);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
md.delete(index, 1)
|
||||
cmd.delete(index, 1)
|
||||
}
|
||||
|
||||
export function createNote(md: NotesMetadata, data: {
|
||||
|
@ -76,7 +78,7 @@ export function createNote(md: NotesMetadata, data: {
|
|||
collectionId: CollectionId | undefined,
|
||||
primaryColor: ColorName | undefined,
|
||||
secondaryColor: ColorName | undefined,
|
||||
type: "text" | "canvas"
|
||||
type: NoteType
|
||||
}) {
|
||||
const note = new Y.Map() as any as NoteMetadata;
|
||||
note.set("id", randomUUID());
|
||||
|
|
|
@ -31,6 +31,9 @@ function Content(props: { id: string }) {
|
|||
case "canvas": return <>
|
||||
<NoteCanvas noteId={props.id} />
|
||||
</>;
|
||||
case "entity": return <>
|
||||
Entity type
|
||||
</>;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue