Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5598fa5156 | ||
|
|
3b94358162 | ||
|
|
e8e1658b3a | ||
|
|
ac9ce68fbe | ||
|
|
1ad16fb35b | ||
|
|
2b6a9f5433 | ||
|
|
8541e3b42e |
@@ -0,0 +1,27 @@
|
|||||||
|
# Git / IDE
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.vs
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.user
|
||||||
|
|
||||||
|
# .NET build artifacts
|
||||||
|
**/bin
|
||||||
|
**/obj
|
||||||
|
|
||||||
|
# Node
|
||||||
|
**/node_modules
|
||||||
|
|
||||||
|
# Build output (budowany wewnątrz obrazu)
|
||||||
|
client/dist
|
||||||
|
backend/wwwroot
|
||||||
|
|
||||||
|
# Local SurrealDB data — dostarczane przez wolumen ./data
|
||||||
|
data
|
||||||
|
backend/data
|
||||||
|
|
||||||
|
# Env / docs
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
backend/local-docs
|
||||||
+10
@@ -13,6 +13,9 @@ Thumbs.db
|
|||||||
bin/
|
bin/
|
||||||
obj/
|
obj/
|
||||||
|
|
||||||
|
# Local SurrealDB data (embedded RocksDB) — persisted next to the running app
|
||||||
|
data/
|
||||||
|
|
||||||
# Node
|
# Node
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
@@ -21,7 +24,14 @@ dist/
|
|||||||
# Client build output copied into backend
|
# Client build output copied into backend
|
||||||
backend/wwwroot/
|
backend/wwwroot/
|
||||||
|
|
||||||
|
# Local documentation (not tracked)
|
||||||
|
backend/local-docs/
|
||||||
|
|
||||||
# Env / secrets
|
# Env / secrets
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
|
# Docker — lokalne nadpisania
|
||||||
|
docker-compose.override.yaml
|
||||||
|
docker-compose.override.yml
|
||||||
|
|||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# --- Stage 1: build klienta (Vue 3 + Vite) ---
|
||||||
|
FROM node:22-alpine AS client
|
||||||
|
WORKDIR /src/client
|
||||||
|
COPY client/package.json client/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY client/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# --- Stage 2: build i publish backendu (ASP.NET Core, .NET 10) ---
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY backend/TarninaHub.csproj ./backend/
|
||||||
|
RUN dotnet restore ./backend/TarninaHub.csproj
|
||||||
|
COPY backend/ ./backend/
|
||||||
|
COPY --from=client /src/client/dist ./backend/wwwroot/
|
||||||
|
RUN dotnet publish ./backend/TarninaHub.csproj \
|
||||||
|
--configuration Release \
|
||||||
|
--output /app/publish \
|
||||||
|
/p:UseAppHost=false
|
||||||
|
|
||||||
|
# --- Stage 3: runtime ---
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ARG APP_VERSION=0.0.1
|
||||||
|
LABEL org.opencontainers.image.title="TarninaHub" \
|
||||||
|
org.opencontainers.image.version="${APP_VERSION}"
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV ASPNETCORE_ENVIRONMENT=Production \
|
||||||
|
ASPNETCORE_URLS=http://+:8080 \
|
||||||
|
DOTNET_EnableDiagnostics=0
|
||||||
|
|
||||||
|
COPY --from=build /app/publish ./
|
||||||
|
|
||||||
|
# Jedyny wolumen aplikacji: trwałe dane SurrealDB (embedded RocksDB).
|
||||||
|
RUN mkdir -p /app/data
|
||||||
|
VOLUME ["/app/data"]
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT ["dotnet", "TarninaHub.dll"]
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
# hubmi
|
# TarninaHub
|
||||||
|
|
||||||
|
**Wersja: 0.0.1**
|
||||||
|
|
||||||
Platforma Małopolskiego Hubu Innowacji Społecznych (MVP hackathonu).
|
Platforma Małopolskiego Hubu Innowacji Społecznych (MVP hackathonu).
|
||||||
|
|
||||||
- **backend/** — aplikacja backendowa w C# (ASP.NET Core, `HubMi.Api`).
|
- **backend/** — aplikacja backendowa w C# (ASP.NET Core, `TarninaHub`).
|
||||||
- **client/** — aplikacja frontendowa Vue 3 + TypeScript (Vite).
|
- **client/** — aplikacja frontendowa Vue 3 + TypeScript (Vite).
|
||||||
- **scripts/** — skrypty budujące całość.
|
- **scripts/** — skrypty budujące całość.
|
||||||
- `HubMi.slnx` — solution .NET.
|
- `TarninaHub.slnx` — solution .NET.
|
||||||
|
- `Dockerfile` + `docker-compose.yaml` — uruchomienie kontenerowe.
|
||||||
|
|
||||||
## Wymagania
|
## Wymagania
|
||||||
|
|
||||||
@@ -34,15 +37,33 @@ npm --prefix client run copy # kopiuje client/dist -> backend/wwwroot
|
|||||||
node scripts/build.mjs # buduje całość (client + copy + backend)
|
node scripts/build.mjs # buduje całość (client + copy + backend)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
cp docker-compose.yaml.example docker-compose.yaml
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
- Aplikacja: http://localhost:8080
|
||||||
|
- Dane: `./data` (bind mount, trwałe — SurrealDB embedded RocksDB)
|
||||||
|
- Obraz: `tarninahub:0.0.1`
|
||||||
|
|
||||||
|
Wolumen hosta można wskazać zmienną `TARNINAHUB_PORT` (domyślnie `8080`).
|
||||||
|
Plik `docker-compose.yaml.example` jest szablonem; `docker-compose.yaml` możesz
|
||||||
|
dostosować lokalnie.
|
||||||
|
|
||||||
## Struktura
|
## Struktura
|
||||||
|
|
||||||
```
|
```
|
||||||
hubmi/
|
TarninaHub/
|
||||||
├─ backend/ # ASP.NET Core Web API + wwwroot (build z client)
|
├─ backend/ # ASP.NET Core Web API + wwwroot (build z client)
|
||||||
├─ client/ # Vue 3 + TS + Vite
|
├─ client/ # Vue 3 + TS + Vite
|
||||||
├─ scripts/
|
├─ scripts/
|
||||||
│ ├─ build.mjs # build całości
|
│ ├─ build.mjs # build całości
|
||||||
│ └─ copy-client.mjs # client/dist -> backend/wwwroot
|
│ └─ copy-client.mjs # client/dist -> backend/wwwroot
|
||||||
├─ HubMi.slnx
|
├─ Dockerfile # multi-stage: client + backend
|
||||||
|
├─ docker-compose.yaml # uruchomienie (bind ./data)
|
||||||
|
├─ docker-compose.yaml.example
|
||||||
|
├─ TarninaHub.slnx
|
||||||
└─ README.md
|
└─ README.md
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using TarninaHub.Models;
|
||||||
|
using TarninaHub.Services;
|
||||||
|
|
||||||
|
namespace TarninaHub.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/system")]
|
||||||
|
public class SystemController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IStorageService _storageService;
|
||||||
|
|
||||||
|
public SystemController(IStorageService storageService)
|
||||||
|
{
|
||||||
|
_storageService = storageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("storage")]
|
||||||
|
public async Task<ActionResult<StorageStatusResponse>> GetStorage(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var info = _storageService.GetInfo();
|
||||||
|
var probeCount = await _storageService.GetProbeCountAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Ok(new StorageStatusResponse(
|
||||||
|
info.Engine,
|
||||||
|
info.DataDirectory,
|
||||||
|
info.Namespace,
|
||||||
|
info.Database,
|
||||||
|
probeCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("storage/probes")]
|
||||||
|
public async Task<ActionResult<StorageProbeResponse>> CreateProbe(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var probe = await _storageService.AddProbeAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Ok(new StorageProbeResponse(probe.Label, probe.CreatedAt));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace TarninaHub.Models;
|
||||||
|
|
||||||
|
public sealed class StorageProbe
|
||||||
|
{
|
||||||
|
public string Label { get; set; } = "probe";
|
||||||
|
|
||||||
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record StorageInfo(
|
||||||
|
string Engine,
|
||||||
|
string DataDirectory,
|
||||||
|
string Namespace,
|
||||||
|
string Database);
|
||||||
|
|
||||||
|
public sealed record StorageStatusResponse(
|
||||||
|
string Engine,
|
||||||
|
string DataDirectory,
|
||||||
|
string Namespace,
|
||||||
|
string Database,
|
||||||
|
int ProbeCount);
|
||||||
|
|
||||||
|
public sealed record StorageProbeResponse(
|
||||||
|
string Label,
|
||||||
|
DateTimeOffset CreatedAt);
|
||||||
@@ -1,12 +1,55 @@
|
|||||||
|
using TarninaHub.Services;
|
||||||
|
using TarninaHub.Settings;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
var surrealSettings = builder.Configuration.GetSection("SurrealDb").Get<SurrealDbSettings>()
|
||||||
|
?? new SurrealDbSettings();
|
||||||
|
|
||||||
|
builder.Services.Configure<SurrealDbSettings>(builder.Configuration.GetSection("SurrealDb"));
|
||||||
|
|
||||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IStorageService, StorageService>();
|
||||||
|
|
||||||
|
var surrealOptions = new SurrealDbOptions
|
||||||
|
{
|
||||||
|
Endpoint = BuildEndpoint(surrealSettings),
|
||||||
|
Namespace = surrealSettings.Namespace,
|
||||||
|
Database = surrealSettings.Database,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(surrealSettings.RemoteUrl))
|
||||||
|
{
|
||||||
|
// Embedded RocksDB, persisted next to the running app (./data), never in memory.
|
||||||
|
var dataDirectory = Path.Combine(AppContext.BaseDirectory, surrealSettings.DataDirectory);
|
||||||
|
Directory.CreateDirectory(dataDirectory);
|
||||||
|
|
||||||
|
builder.Services
|
||||||
|
.AddSurreal(surrealOptions, ServiceLifetime.Singleton)
|
||||||
|
.AddRocksDbProvider();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remote SurrealDB server on its own dedicated port (used for sync with the frontend).
|
||||||
|
surrealOptions.Username = surrealSettings.Username;
|
||||||
|
surrealOptions.Password = surrealSettings.Password;
|
||||||
|
|
||||||
|
builder.Services.AddSurreal(surrealOptions, ServiceLifetime.Singleton);
|
||||||
|
}
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var storageService = scope.ServiceProvider.GetRequiredService<IStorageService>();
|
||||||
|
|
||||||
|
await storageService.InitializeAsync(CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
@@ -25,3 +68,15 @@ app.MapControllers();
|
|||||||
app.MapFallbackToFile("index.html");
|
app.MapFallbackToFile("index.html");
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
static string BuildEndpoint(SurrealDbSettings settings)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(settings.RemoteUrl))
|
||||||
|
{
|
||||||
|
return settings.RemoteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataDirectory = Path.Combine(AppContext.BaseDirectory, settings.DataDirectory);
|
||||||
|
|
||||||
|
return $"rocksdb://{dataDirectory.Replace('\\', '/')}";
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using TarninaHub.Models;
|
||||||
|
|
||||||
|
namespace TarninaHub.Services;
|
||||||
|
|
||||||
|
public interface IStorageService
|
||||||
|
{
|
||||||
|
Task InitializeAsync(CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
StorageInfo GetInfo();
|
||||||
|
|
||||||
|
Task<int> GetProbeCountAsync(CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<StorageProbe> AddProbeAsync(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using SurrealDb.Net;
|
||||||
|
using TarninaHub.Models;
|
||||||
|
using TarninaHub.Settings;
|
||||||
|
|
||||||
|
namespace TarninaHub.Services;
|
||||||
|
|
||||||
|
public sealed class StorageService : IStorageService
|
||||||
|
{
|
||||||
|
private const string ProbeTable = "storage_probe";
|
||||||
|
|
||||||
|
private readonly ISurrealDbClient _client;
|
||||||
|
private readonly SurrealDbSettings _settings;
|
||||||
|
private readonly string _dataDirectory;
|
||||||
|
|
||||||
|
public StorageService(ISurrealDbClient client, IOptions<SurrealDbSettings> settings)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
_settings = settings.Value;
|
||||||
|
_dataDirectory = Path.Combine(AppContext.BaseDirectory, _settings.DataDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _client.RawQuery(
|
||||||
|
$"DEFINE TABLE IF NOT EXISTS {ProbeTable} SCHEMALESS;",
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StorageInfo GetInfo()
|
||||||
|
{
|
||||||
|
var isRemote = !string.IsNullOrWhiteSpace(_settings.RemoteUrl);
|
||||||
|
|
||||||
|
return new StorageInfo(
|
||||||
|
isRemote ? "remote" : "rocksdb",
|
||||||
|
isRemote ? _settings.RemoteUrl! : _dataDirectory,
|
||||||
|
_settings.Namespace,
|
||||||
|
_settings.Database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetProbeCountAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var probes = await _client.Select<StorageProbe>(ProbeTable, cancellationToken);
|
||||||
|
|
||||||
|
return probes.Count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<StorageProbe> AddProbeAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var probe = new StorageProbe
|
||||||
|
{
|
||||||
|
CreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
};
|
||||||
|
|
||||||
|
return _client.Create(ProbeTable, probe, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace TarninaHub.Settings;
|
||||||
|
|
||||||
|
public sealed class SurrealDbSettings
|
||||||
|
{
|
||||||
|
public string Namespace { get; set; } = "hubmi";
|
||||||
|
|
||||||
|
public string Database { get; set; } = "hubmi";
|
||||||
|
|
||||||
|
public string DataDirectory { get; set; } = "data";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When set (e.g. ws://127.0.0.1:8010/rpc), the backend connects to a
|
||||||
|
/// dedicated SurrealDB server instead of the embedded RocksDB engine.
|
||||||
|
/// The server must persist to ./data, not to memory.
|
||||||
|
/// </summary>
|
||||||
|
public string? RemoteUrl { get; set; }
|
||||||
|
|
||||||
|
public string? Username { get; set; }
|
||||||
|
|
||||||
|
public string? Password { get; set; }
|
||||||
|
}
|
||||||
@@ -4,10 +4,14 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Version>0.0.1</Version>
|
||||||
|
<InformationalVersion>0.0.1</InformationalVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.12" />
|
||||||
|
<PackageReference Include="SurrealDb.Embedded.RocksDb" Version="1.0.0" />
|
||||||
|
<PackageReference Include="SurrealDb.Net" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -4,5 +4,13 @@
|
|||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"SurrealDb": {
|
||||||
|
"Namespace": "hubmi",
|
||||||
|
"Database": "hubmi",
|
||||||
|
"DataDirectory": "data",
|
||||||
|
"RemoteUrl": "ws://127.0.0.1:8010/rpc",
|
||||||
|
"Username": "root",
|
||||||
|
"Password": "root"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,10 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"SurrealDb": {
|
||||||
|
"Namespace": "hubmi",
|
||||||
|
"Database": "hubmi",
|
||||||
|
"DataDirectory": "data"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# SurrealDB — frontend (Vite)
|
||||||
|
|
||||||
|
# Wspólna przestrzeń nazw / baza
|
||||||
|
VITE_SURREAL_NAMESPACE=hubmi
|
||||||
|
VITE_SURREAL_DATABASE=hubmi
|
||||||
|
|
||||||
|
# A) Lokalna baza w przeglądarce (WASM + IndexedDB; dane zostają lokalnie)
|
||||||
|
VITE_SURREAL_LOCAL_URL=indxdb://tarninahub
|
||||||
|
|
||||||
|
# B) Backend na dedykowanym porcie (tylko dla SurrealDB) — synchronizacja
|
||||||
|
VITE_SURREAL_REMOTE_URL=ws://127.0.0.1:8010/rpc
|
||||||
|
|
||||||
|
# Poświadczenia do zdalnej bazy. W produkcji NIE używaj root w przeglądarce —
|
||||||
|
# użyj tokenu dostępu rekordowego (record access) lub konta o wąskich uprawnieniach.
|
||||||
|
VITE_SURREAL_USERNAME=
|
||||||
|
VITE_SURREAL_PASSWORD=
|
||||||
Generated
+119
-3
@@ -1,14 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "hubmi-client",
|
"name": "hubmi-client",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "hubmi-client",
|
"name": "hubmi-client",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
|
"@surrealdb/wasm": "^2.6.1",
|
||||||
|
"@vueuse/core": "^15.0.0",
|
||||||
|
"surrealdb": "^2.0.10",
|
||||||
"vue": "^3.5.42",
|
"vue": "^3.5.42",
|
||||||
"vuetify": "^4.2.3"
|
"vuetify": "^4.2.3"
|
||||||
},
|
},
|
||||||
@@ -369,6 +372,24 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@surrealdb/cbor": {
|
||||||
|
"version": "2.0.0-alpha.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@surrealdb/cbor/-/cbor-2.0.0-alpha.4.tgz",
|
||||||
|
"integrity": "sha512-l/hNZ3ZCOJq8rzwx1y5ZV/8b2bYCWdhGqRVY33Pu50NAHyAW5qLsMn3ZBkZow77mAKwNCsbDsRGnyYEuU95mjw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@surrealdb/wasm": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@surrealdb/wasm/-/wasm-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-pBxgOX/wZ+aBvMIJbDv6UtUDCT0RjhpPqa31NMUmAZuTx9WfZb1PQan4vCBH9rcSPDR6BC8zJ4pFQUuliFQUEQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"surrealdb": "^2.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.19.1",
|
"version": "24.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz",
|
||||||
@@ -379,6 +400,12 @@
|
|||||||
"undici-types": ">=7.24.0 <7.24.7"
|
"undici-types": ">=7.24.0 <7.24.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/web-bluetooth": {
|
||||||
|
"version": "0.0.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||||
|
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "6.0.9",
|
"version": "6.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.9.tgz",
|
||||||
@@ -615,6 +642,53 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vueuse/core": {
|
||||||
|
"version": "15.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-15.0.0.tgz",
|
||||||
|
"integrity": "sha512-Zv67h5mrk7YHDJsRObx+DXaCBz6RKBUpxqLesJ+3xZNPG6yuQs7ja7kg3veFrrouYLWAqydQ7CwGEbLVxB43Ng==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/web-bluetooth": "^0.0.21",
|
||||||
|
"@vueuse/metadata": "15.0.0",
|
||||||
|
"@vueuse/shared": "15.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/metadata": {
|
||||||
|
"version": "15.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-15.0.0.tgz",
|
||||||
|
"integrity": "sha512-sfWE5LADAKJ0KnjtGliMdSN8Ij/L7uP6rXIZlgPloAI4zvvefbw6H5Lt7KJA6RqQOYgVqWF5Guj50HHMIzygBg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/shared": {
|
||||||
|
"version": "15.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-15.0.0.tgz",
|
||||||
|
"integrity": "sha512-vbNXhGrbV53xPOMSyOzMHQRvhMgZf7oNDw2RChUetVxwTIEkInQ1joZ9JbT0G3z2n2AJNjvmCWeM9ySYrnvogA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/alien-signals": {
|
"node_modules/alien-signals": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz",
|
||||||
@@ -1093,6 +1167,33 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/surrealdb": {
|
||||||
|
"version": "2.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/surrealdb/-/surrealdb-2.0.10.tgz",
|
||||||
|
"integrity": "sha512-4r3LqhvGIRdEMUDO2u21jSi0C8x0x374mOmqCEEM5TrTsQWi5dUihBAfUFee+EBkrJeVDhBgcnvaMNPn+owc2Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@surrealdb/sqon": "^0.1.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tslib": "^2.6.3",
|
||||||
|
"typescript": "^5.0.0 || ^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/surrealdb/node_modules/@surrealdb/sqon": {
|
||||||
|
"version": "0.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@surrealdb/sqon/-/sqon-0.1.5.tgz",
|
||||||
|
"integrity": "sha512-wTo5eREYb+spcErgyKsyNt/QVnz9MaPhOrhqalNp6BWWQNXqgd0gZ8Pub2jjckLrULxRi9Cujamif0T34LBhBA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@surrealdb/cbor": "2.0.0-alpha.4",
|
||||||
|
"uuidv7": "^1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"tslib": "^2.6.3",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
@@ -1110,11 +1211,17 @@
|
|||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"license": "0BSD",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -1131,6 +1238,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/uuidv7": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuidv7/-/uuidv7-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-4kPkK3/XTQW9Hbm4CaqfICn+kY9LJtDVEOfgsRRra/+n2Ofg4NqzRFceAkxvQ/Ud/6BpHOPzj8cirqM7TzTN5Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"uuidv7": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "8.3.2",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz",
|
||||||
|
|||||||
+4
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hubmi-client",
|
"name": "hubmi-client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -11,6 +11,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
|
"@surrealdb/wasm": "^2.6.1",
|
||||||
|
"@vueuse/core": "^15.0.0",
|
||||||
|
"surrealdb": "^2.0.10",
|
||||||
"vue": "^3.5.42",
|
"vue": "^3.5.42",
|
||||||
"vuetify": "^4.2.3"
|
"vuetify": "^4.2.3"
|
||||||
},
|
},
|
||||||
|
|||||||
+33
-1
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import MainView from './pages/MainView.vue'
|
import MainView from './pages/MainView.vue'
|
||||||
|
import { useThemeMode } from './composables/useThemeMode'
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
title: string
|
title: string
|
||||||
@@ -10,6 +11,11 @@ interface NavItem {
|
|||||||
|
|
||||||
const drawer = ref(false)
|
const drawer = ref(false)
|
||||||
|
|
||||||
|
const { mode, themeOptions } = useThemeMode()
|
||||||
|
const activeTheme = computed(
|
||||||
|
() => themeOptions.find((option) => option.value === mode.value) ?? themeOptions[2]!,
|
||||||
|
)
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
{ title: 'Misja', href: '#misja', icon: 'mdi-compass-outline' },
|
{ title: 'Misja', href: '#misja', icon: 'mdi-compass-outline' },
|
||||||
{ title: 'Jak to działa', href: '#jak-to-dziala', icon: 'mdi-sitemap-outline' },
|
{ title: 'Jak to działa', href: '#jak-to-dziala', icon: 'mdi-sitemap-outline' },
|
||||||
@@ -29,6 +35,32 @@ const navItems: NavItem[] = [
|
|||||||
/>
|
/>
|
||||||
<v-app-bar-title>TarninaHub</v-app-bar-title>
|
<v-app-bar-title>TarninaHub</v-app-bar-title>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
|
|
||||||
|
<v-menu location="bottom end">
|
||||||
|
<template #activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
v-bind="props"
|
||||||
|
:prepend-icon="activeTheme.icon"
|
||||||
|
:aria-label="`Motyw: ${activeTheme.title}. Zmień motyw`"
|
||||||
|
color="on-primary"
|
||||||
|
variant="text"
|
||||||
|
class="me-1"
|
||||||
|
>
|
||||||
|
{{ activeTheme.title }}
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list aria-label="Wybór motywu">
|
||||||
|
<v-list-item
|
||||||
|
v-for="option in themeOptions"
|
||||||
|
:key="option.value"
|
||||||
|
:prepend-icon="option.icon"
|
||||||
|
:title="option.title"
|
||||||
|
:active="mode === option.value"
|
||||||
|
@click="mode = option.value"
|
||||||
|
/>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
|
||||||
<nav aria-label="Menu główne" class="d-none d-md-block">
|
<nav aria-label="Menu główne" class="d-none d-md-block">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-for="item in navItems"
|
v-for="item in navItems"
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { computed, watch } from 'vue'
|
||||||
|
import { usePreferredDark, useStorage } from '@vueuse/core'
|
||||||
|
import { useTheme } from 'vuetify'
|
||||||
|
|
||||||
|
export type ThemeMode = 'light' | 'dark' | 'system'
|
||||||
|
export type ResolvedTheme = 'light' | 'dark'
|
||||||
|
|
||||||
|
export interface ThemeOption {
|
||||||
|
value: ThemeMode
|
||||||
|
title: string
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const themeOptions: ThemeOption[] = [
|
||||||
|
{ value: 'light', title: 'Jasny', icon: 'mdi-white-balance-sunny' },
|
||||||
|
{ value: 'dark', title: 'Ciemny', icon: 'mdi-weather-night' },
|
||||||
|
{ value: 'system', title: 'Systemowy', icon: 'mdi-theme-light-dark' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useThemeMode() {
|
||||||
|
const theme = useTheme()
|
||||||
|
const preferredDark = usePreferredDark()
|
||||||
|
const mode = useStorage<ThemeMode>('tarninahub-theme', 'system')
|
||||||
|
|
||||||
|
const resolved = computed<ResolvedTheme>(() =>
|
||||||
|
mode.value === 'system' ? (preferredDark.value ? 'dark' : 'light') : mode.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(resolved, (value) => theme.change(value), { immediate: true })
|
||||||
|
|
||||||
|
return { mode, resolved, preferredDark, themeOptions }
|
||||||
|
}
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import vuetify from './plugins/vuetify'
|
import vuetify from './plugins/vuetify'
|
||||||
|
import { connectLocalSurreal } from './services/surreal'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import './styles/a11y.css'
|
import './styles/a11y.css'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
|
void connectLocalSurreal().catch((error: unknown) => {
|
||||||
|
console.error('Nie udało się połączyć z lokalną bazą SurrealDB', error)
|
||||||
|
})
|
||||||
|
|
||||||
createApp(App).use(vuetify).mount('#app')
|
createApp(App).use(vuetify).mount('#app')
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const steps: Step[] = [
|
|||||||
<h2 id="dla-kogo-title" class="text-h4 text-center mb-8">Dla kogo?</h2>
|
<h2 id="dla-kogo-title" class="text-h4 text-center mb-8">Dla kogo?</h2>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col v-for="feature in features" :key="feature.title" cols="12" md="4">
|
<v-col v-for="feature in features" :key="feature.title" cols="12" md="4">
|
||||||
<v-card class="h-100 pa-2" variant="elevated" elevation="2">
|
<v-card class="h-100 pa-2" variant="outlined">
|
||||||
<v-card-item>
|
<v-card-item>
|
||||||
<v-icon
|
<v-icon
|
||||||
:icon="feature.icon"
|
:icon="feature.icon"
|
||||||
|
|||||||
@@ -5,66 +5,81 @@ import * as components from 'vuetify/components'
|
|||||||
import * as directives from 'vuetify/directives'
|
import * as directives from 'vuetify/directives'
|
||||||
import { en, pl } from 'vuetify/locale'
|
import { en, pl } from 'vuetify/locale'
|
||||||
|
|
||||||
const light: ThemeDefinition = {
|
export const light: ThemeDefinition = {
|
||||||
dark: false,
|
dark: false,
|
||||||
colors: {
|
colors: {
|
||||||
background: '#FFFFFF',
|
background: '#FBF8FF',
|
||||||
|
'on-background': '#1A1030',
|
||||||
surface: '#FFFFFF',
|
surface: '#FFFFFF',
|
||||||
'surface-variant': '#4D4D4D',
|
'on-surface': '#1A1030',
|
||||||
primary: '#1565C0',
|
'surface-bright': '#FFFFFF',
|
||||||
|
'on-surface-bright': '#1A1030',
|
||||||
|
'surface-light': '#F1EAFB',
|
||||||
|
'on-surface-light': '#1A1030',
|
||||||
|
'surface-variant': '#ECE4FA',
|
||||||
|
'on-surface-variant': '#1A1030',
|
||||||
|
|
||||||
|
primary: '#5B21B6',
|
||||||
'on-primary': '#FFFFFF',
|
'on-primary': '#FFFFFF',
|
||||||
secondary: '#455A64',
|
secondary: '#6A1B9A',
|
||||||
'on-secondary': '#FFFFFF',
|
'on-secondary': '#FFFFFF',
|
||||||
|
info: '#0B4F9E',
|
||||||
|
'on-info': '#FFFFFF',
|
||||||
|
success: '#166534',
|
||||||
|
'on-success': '#FFFFFF',
|
||||||
|
warning: '#7C4A00',
|
||||||
|
'on-warning': '#FFFFFF',
|
||||||
error: '#B3261E',
|
error: '#B3261E',
|
||||||
'on-error': '#FFFFFF',
|
'on-error': '#FFFFFF',
|
||||||
success: '#1B5E20',
|
|
||||||
'on-success': '#FFFFFF',
|
outline: '#6E5D93',
|
||||||
warning: '#8A4B00',
|
focus: '#4C1D95',
|
||||||
'on-warning': '#FFFFFF',
|
},
|
||||||
info: '#01579B',
|
variables: {
|
||||||
'on-info': '#FFFFFF',
|
'border-color': '#6E5D93',
|
||||||
|
'border-opacity': 1,
|
||||||
|
'high-emphasis-opacity': 1,
|
||||||
|
'medium-emphasis-opacity': 0.8,
|
||||||
|
'disabled-opacity': 0.5,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const dark: ThemeDefinition = {
|
export const dark: ThemeDefinition = {
|
||||||
dark: true,
|
dark: true,
|
||||||
colors: {
|
colors: {
|
||||||
background: '#121212',
|
background: '#151020',
|
||||||
surface: '#1E1E1E',
|
'on-background': '#F1EBFF',
|
||||||
'surface-variant': '#CFCFCF',
|
surface: '#1E1730',
|
||||||
primary: '#90CAF9',
|
'on-surface': '#F1EBFF',
|
||||||
'on-primary': '#0D1B2A',
|
'surface-bright': '#2A2142',
|
||||||
secondary: '#B0BEC5',
|
'on-surface-bright': '#F1EBFF',
|
||||||
'on-secondary': '#10181C',
|
'surface-light': '#2A2142',
|
||||||
|
'on-surface-light': '#F1EBFF',
|
||||||
|
'surface-variant': '#2A2142',
|
||||||
|
'on-surface-variant': '#F1EBFF',
|
||||||
|
|
||||||
|
primary: '#B79CFF',
|
||||||
|
'on-primary': '#240850',
|
||||||
|
secondary: '#E3AEF2',
|
||||||
|
'on-secondary': '#3A0F4A',
|
||||||
|
info: '#9EC5FF',
|
||||||
|
'on-info': '#0A2447',
|
||||||
|
success: '#86DDA0',
|
||||||
|
'on-success': '#08311A',
|
||||||
|
warning: '#FFC766',
|
||||||
|
'on-warning': '#3A2600',
|
||||||
error: '#FFB4AB',
|
error: '#FFB4AB',
|
||||||
'on-error': '#410002',
|
'on-error': '#4A0A06',
|
||||||
success: '#A5D6A7',
|
|
||||||
'on-success': '#0B2A0E',
|
|
||||||
warning: '#FFCC80',
|
|
||||||
'on-warning': '#2E1A00',
|
|
||||||
info: '#81D4FA',
|
|
||||||
'on-info': '#00263A',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const highContrast: ThemeDefinition = {
|
outline: '#9A8FB5',
|
||||||
dark: true,
|
focus: '#FFD54F',
|
||||||
colors: {
|
},
|
||||||
background: '#000000',
|
variables: {
|
||||||
surface: '#000000',
|
'border-color': '#9A8FB5',
|
||||||
'surface-variant': '#FFFFFF',
|
'border-opacity': 1,
|
||||||
primary: '#FFFF00',
|
'high-emphasis-opacity': 1,
|
||||||
'on-primary': '#000000',
|
'medium-emphasis-opacity': 0.8,
|
||||||
secondary: '#00FFFF',
|
'disabled-opacity': 0.5,
|
||||||
'on-secondary': '#000000',
|
|
||||||
error: '#FF8A80',
|
|
||||||
'on-error': '#000000',
|
|
||||||
success: '#69F0AE',
|
|
||||||
'on-success': '#000000',
|
|
||||||
warning: '#FFD740',
|
|
||||||
'on-warning': '#000000',
|
|
||||||
info: '#80D8FF',
|
|
||||||
'on-info': '#000000',
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +87,7 @@ export default createVuetify({
|
|||||||
components,
|
components,
|
||||||
directives,
|
directives,
|
||||||
locale: { locale: 'pl', fallback: 'en', messages: { pl, en } },
|
locale: { locale: 'pl', fallback: 'en', messages: { pl, en } },
|
||||||
theme: { defaultTheme: 'system', themes: { light, dark, highContrast } },
|
theme: { defaultTheme: 'system', themes: { light, dark } },
|
||||||
defaults: {
|
defaults: {
|
||||||
global: { ripple: false },
|
global: { ripple: false },
|
||||||
VBtn: { size: 'large', variant: 'flat', color: 'primary' },
|
VBtn: { size: 'large', variant: 'flat', color: 'primary' },
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Surreal, createRemoteEngines } from 'surrealdb'
|
||||||
|
import { createWasmEngines } from '@surrealdb/wasm'
|
||||||
|
|
||||||
|
const namespace = import.meta.env.VITE_SURREAL_NAMESPACE ?? 'hubmi'
|
||||||
|
const database = import.meta.env.VITE_SURREAL_DATABASE ?? 'hubmi'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A) Lokalna baza SurrealDB w przeglądarce.
|
||||||
|
* Persistencja przez IndexedDB (`indxdb://`) — localStorage jest zbyt mały.
|
||||||
|
* Dane prywatne użytkownika zostają tutaj i nie są wysyłane automatycznie.
|
||||||
|
*/
|
||||||
|
const localDatabase = import.meta.env.VITE_SURREAL_LOCAL_DATABASE ?? 'tarninahub'
|
||||||
|
const localUrl = import.meta.env.VITE_SURREAL_LOCAL_URL ?? `indxdb://${localDatabase}`
|
||||||
|
|
||||||
|
export const localSurreal = new Surreal({
|
||||||
|
engines: {
|
||||||
|
...createRemoteEngines(),
|
||||||
|
...createWasmEngines(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let localConnection: Promise<void> | null = null
|
||||||
|
|
||||||
|
export function connectLocalSurreal(): Promise<void> {
|
||||||
|
localConnection ??= localSurreal
|
||||||
|
.connect(localUrl, { namespace, database })
|
||||||
|
.then(() => undefined)
|
||||||
|
|
||||||
|
return localConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* B) Zdalna baza backendu na dedykowanym porcie (tylko dla SurrealDB),
|
||||||
|
* używana do synchronizacji danych uznanych za nadające się do wysłania.
|
||||||
|
*/
|
||||||
|
const remoteUrl =
|
||||||
|
import.meta.env.VITE_SURREAL_REMOTE_URL ?? 'ws://127.0.0.1:8010/rpc'
|
||||||
|
const username = import.meta.env.VITE_SURREAL_USERNAME as string | undefined
|
||||||
|
const password = import.meta.env.VITE_SURREAL_PASSWORD as string | undefined
|
||||||
|
|
||||||
|
export const remoteSurreal = new Surreal()
|
||||||
|
|
||||||
|
let remoteConnection: Promise<void> | null = null
|
||||||
|
|
||||||
|
export function connectRemoteSurreal(): Promise<void> {
|
||||||
|
remoteConnection ??= remoteSurreal
|
||||||
|
.connect(remoteUrl, {
|
||||||
|
namespace,
|
||||||
|
database,
|
||||||
|
...(username && password
|
||||||
|
? { authentication: { username, password } }
|
||||||
|
: {}),
|
||||||
|
})
|
||||||
|
.then(() => undefined)
|
||||||
|
|
||||||
|
return remoteConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
export { namespace, database }
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
.v-theme--light {
|
.v-theme--light {
|
||||||
--a11y-focus: #0b3d91;
|
--a11y-focus: #4c1d95;
|
||||||
}
|
}
|
||||||
.v-theme--dark {
|
.v-theme--dark {
|
||||||
--a11y-focus: #ffd54f;
|
--a11y-focus: #ffd54f;
|
||||||
}
|
}
|
||||||
.v-theme--highContrast {
|
|
||||||
--a11y-focus: #ffff00;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: 112.5%;
|
font-size: 112.5%;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
name: tarninahub
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: tarninahub:0.0.1
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
APP_VERSION: 0.0.1
|
||||||
|
container_name: tarninahub
|
||||||
|
restart: unless-stopped
|
||||||
|
# RocksDB zapisuje do bind-mountu ./data; root upraszcza uprawnienia (MVP).
|
||||||
|
user: "0:0"
|
||||||
|
ports:
|
||||||
|
- "${TARNINAHUB_PORT:-8080}:8080"
|
||||||
|
environment:
|
||||||
|
ASPNETCORE_ENVIRONMENT: Production
|
||||||
|
ASPNETCORE_URLS: http://+:8080
|
||||||
|
SurrealDb__Namespace: hubmi
|
||||||
|
SurrealDb__Database: hubmi
|
||||||
|
SurrealDb__DataDirectory: data
|
||||||
|
volumes:
|
||||||
|
# Jedyny volumen aplikacji — trwałe dane (SurrealDB / RocksDB).
|
||||||
|
- ./data:/app/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-fsS", "http://localhost:8080/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# TarninaHub — szablon docker-compose (wersja projektu 0.0.1)
|
||||||
|
#
|
||||||
|
# Użycie:
|
||||||
|
# cp docker-compose.yaml.example docker-compose.yaml
|
||||||
|
# docker compose up --build
|
||||||
|
#
|
||||||
|
# Aplikacja: http://localhost:8080
|
||||||
|
# Dane: ./data (bind mount, trwałe, SurrealDB embedded RocksDB)
|
||||||
|
|
||||||
|
name: tarninahub
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
# Obraz budowany lokalnie z Dockerfile (multi-stage: klient Vue + backend .NET).
|
||||||
|
image: tarninahub:${APP_VERSION:-0.0.1}
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
APP_VERSION: ${APP_VERSION:-0.0.1}
|
||||||
|
container_name: tarninahub
|
||||||
|
restart: unless-stopped
|
||||||
|
# RocksDB zapisuje do bind-mountu ./data; root upraszcza uprawnienia (MVP).
|
||||||
|
# W produkcji użyj named volume albo dopasuj właściciela katalogu ./data.
|
||||||
|
user: "0:0"
|
||||||
|
ports:
|
||||||
|
- "${TARNINAHUB_PORT:-8080}:8080"
|
||||||
|
environment:
|
||||||
|
ASPNETCORE_ENVIRONMENT: Production
|
||||||
|
ASPNETCORE_URLS: http://+:8080
|
||||||
|
# SurrealDB — wbudowany silnik RocksDB (domyślnie), dane w /app/data.
|
||||||
|
SurrealDb__Namespace: hubmi
|
||||||
|
SurrealDb__Database: hubmi
|
||||||
|
SurrealDb__DataDirectory: data
|
||||||
|
# Tryb zdalny (opcjonalnie) — wskaż zewnętrzny serwer SurrealDB zamiast
|
||||||
|
# wbudowanego RocksDB. Wtedy usuń `user: "0:0"` i przekieruj port 8010.
|
||||||
|
# SurrealDb__RemoteUrl: ws://127.0.0.1:8010/rpc
|
||||||
|
# SurrealDb__Username: root
|
||||||
|
# SurrealDb__Password: root
|
||||||
|
volumes:
|
||||||
|
# Jedyny volumen aplikacji — trwałe dane (SurrealDB / RocksDB).
|
||||||
|
- ./data:/app/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-fsS", "http://localhost:8080/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Uruchamia SurrealDB jako serwer na dedykowanym porcie (tylko dla bazy),
|
||||||
|
z trwałym magazynem RocksDB w katalogu ./data (nie w pamięci).
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Port (domyślnie 8010) służy wyłącznie SurrealDB i jest używany zarówno przez
|
||||||
|
backend (HubMi.Api), jak i przez frontend do synchronizacji (WebSocket /rpc).
|
||||||
|
Dane trwale zapisywane są w backend/data (RocksDB).
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[int]$Port = 8010,
|
||||||
|
[string]$Bind = "127.0.0.1",
|
||||||
|
[string]$Username = "root",
|
||||||
|
[string]$Password = "root",
|
||||||
|
[string]$DataPath = (Join-Path $PSScriptRoot "..\backend\data")
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$surreal = (Get-Command surreal -ErrorAction SilentlyContinue)?.Source
|
||||||
|
if (-not $surreal) {
|
||||||
|
throw "Nie znaleziono 'surreal' w PATH. Zainstaluj SurrealDB: https://surrealdb.com/install"
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedData = [System.IO.Path]::GetFullPath($DataPath)
|
||||||
|
New-Item -ItemType Directory -Force -Path $resolvedData | Out-Null
|
||||||
|
|
||||||
|
Write-Host "SurrealDB: bind $Bind`:$Port, rocksdb:$resolvedData"
|
||||||
|
|
||||||
|
& $surreal start `
|
||||||
|
--user $Username `
|
||||||
|
--pass $Password `
|
||||||
|
--bind "${Bind}:${Port}" `
|
||||||
|
--log info `
|
||||||
|
"rocksdb:$resolvedData"
|
||||||
Reference in New Issue
Block a user