Merge branch 'main' into feature/notifications

This commit is contained in:
2026-01-28 20:47:54 +01:00
8 changed files with 121 additions and 40 deletions

View File

@@ -1,15 +1,15 @@
FROM alpine:3.22 AS svelte_build FROM alpine:3.22 AS svelte_build
RUN apk add npm RUN apk add npm
RUN adduser -Dh /home/svelte svelte RUN adduser -Dh /home/svelte svelte
ADD . /home/svelte/Umbrella ADD frontend /home/svelte/Umbrella/frontend
RUN chown -R svelte /home/svelte/Umbrella RUN chown -R svelte /home/svelte/Umbrella
USER svelte USER svelte
WORKDIR /home/svelte/Umbrella/frontend WORKDIR /home/svelte/Umbrella/frontend
RUN npm install && npm run build RUN npm install && npm run build
FROM alpine AS java_build FROM alpine:3.22 AS java_build
RUN apk add gradle fontconfig font-opensans openjdk21-jre RUN apk add gradle
ADD . /Umbrella ADD . /Umbrella
WORKDIR /Umbrella WORKDIR /Umbrella
COPY --from=svelte_build /home/svelte/Umbrella/frontend/dist web/src/main/resources/web COPY --from=svelte_build /home/svelte/Umbrella/frontend/dist web/src/main/resources/web
@@ -17,14 +17,18 @@ RUN gradle --no-daemon build
FROM alpine FROM alpine
RUN apk add bash fontconfig font-opensans graphviz openjdk21-jre weasyprint RUN apk --no-cache add bash fontconfig font-opensans graphviz openjdk21-jre tzdata weasyprint \
RUN adduser -D umbrella && adduser -D umbrella
COPY --from=java_build /Umbrella/backend/build/libs/backend.jar /home/umbrella/jar/
RUN chown -R umbrella /home/umbrella
ADD https://github.com/plantuml/plantuml/releases/download/v1.2025.10/plantuml-1.2025.10.jar /home/umbrella/plantuml.jar
WORKDIR /home/umbrella WORKDIR /home/umbrella
RUN chmod a+rx plantuml.jar
USER umbrella
RUN mkdir .config && ln -s /host/config.json .config/Umbrella.json
EXPOSE 80 EXPOSE 80
CMD java -jar jar/backend.jar CMD java -jar jar/backend.jar
ADD https://github.com/plantuml/plantuml/releases/download/v1.2025.10/plantuml-1.2025.10.jar /home/umbrella/plantuml.jar
COPY --from=java_build /Umbrella/backend/build/libs/backend.jar /home/umbrella/jar/
RUN mkdir .config \
&& ln -s /host/config.json .config/Umbrella.json \
&& chmod a+rx plantuml.jar \
&& chown -R umbrella . \
&& ln -s /usr/share/zoneinfo/Europe/Berlin /etc/localtime
USER umbrella

View File

@@ -541,6 +541,11 @@ public class DocumentApi extends BaseHandler implements DocumentService {
var userCompanyIds = companyService().listCompaniesOf(user).keySet(); var userCompanyIds = companyService().listCompaniesOf(user).keySet();
var documents = db.find(userCompanyIds,keys,fulltext); var documents = db.find(userCompanyIds,keys,fulltext);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return sendContent(ex,mapValues(documents)); return sendContent(ex,mapValues(documents));
} }

View File

@@ -1,14 +1,15 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router'; import { useTinyRouter } from 'svelte-tiny-router';
import { api, post, target } from '../../urls.svelte.js'; import { api, get, post, target } from '../../urls.svelte.js';
import { error, yikes } from '../../warn.svelte'; import { error, warn, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte.js'; import { t } from '../../translations.svelte.js';
import { display } from '../../time.svelte'; import { display } from '../../time.svelte';
import Bookmark from '../bookmark/Template.svelte'; import Bookmark from '../bookmark/Template.svelte';
const router = useTinyRouter(); const router = useTinyRouter();
let counter = 9;
let bookmarks = $state(null); let bookmarks = $state(null);
let companies = $state(null); let companies = $state(null);
let documents = $state(null); let documents = $state(null);
@@ -34,12 +35,12 @@
}); });
function doSearch(ignored){ function doSearch(ignored){
warn(t('searching…'));
let url = window.location.origin + window.location.pathname; let url = window.location.origin + window.location.pathname;
if (key) url += '?key=' + encodeURI(key); if (key) url += '?key=' + encodeURI(key);
window.history.replaceState(history.state, '', url); window.history.replaceState(history.state, '', url);
const data = { key : key, fulltext : fulltext }; const data = { key : key, fulltext : fulltext };
post(api('bookmark/search'),data).then(handleBookmarks); post(api('bookmark/search'),data).then(handleBookmarks);
post(api('company/search '),data).then(handleCompanies); post(api('company/search '),data).then(handleCompanies);
post(api('document/search'),data).then(handleDocuments); post(api('document/search'),data).then(handleDocuments);
@@ -51,6 +52,22 @@
post(api('wiki/search' ),data).then(handleWikiPages); post(api('wiki/search' ),data).then(handleWikiPages);
} }
async function getTitle(key,module,entity_id){
get(api(module+'/'+entity_id)).then(res => setTitle(res,key,module))
}
async function setTitle(resp,key,module){
if (resp.ok){
const json = await resp.json();
if (json.name) notes[key].title = t(module)+": "+json.name;
if (json.title) notes[key].title = t(module)+": "+json.title;
if (module == 'document'){
notes[key].title = t(json.type)+" "+json.number;
}
}
}
function onclick(e){ function onclick(e){
e.preventDefault(); e.preventDefault();
var target = e.target; var target = e.target;
@@ -61,6 +78,7 @@
} }
async function handleBookmarks(resp){ async function handleBookmarks(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
bookmarks = Object.keys(res).length ? res : null; bookmarks = Object.keys(res).length ? res : null;
@@ -70,6 +88,7 @@
} }
async function handleCompanies(resp){ async function handleCompanies(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const json = await resp.json(); const json = await resp.json();
companies = Object.keys(json).length ? json : null; companies = Object.keys(json).length ? json : null;
@@ -79,6 +98,7 @@
} }
async function handleDocuments(resp){ async function handleDocuments(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const json = await resp.json(); const json = await resp.json();
documents = Object.keys(json).length ? json : null; documents = Object.keys(json).length ? json : null;
@@ -88,15 +108,25 @@
} }
async function handleNotes(resp){ async function handleNotes(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const json = await resp.json(); const json = await resp.json();
notes = Object.keys(json).length ? json : null; if ( Object.keys(json).length ) {
for (let key of Object.keys(json)){
let module = json[key].module;
let entity_id = json[key].entity_id;
json[key].title = t(module)+' '+entity_id;
getTitle(key,module,entity_id);
}
notes = json;
} else notes = null;
} else { } else {
error(resp); error(resp);
} }
} }
async function handleProjects(resp){ async function handleProjects(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
projects = Object.keys(res).length ? res : null; projects = Object.keys(res).length ? res : null;
@@ -106,6 +136,7 @@
} }
async function handleStock(resp){ async function handleStock(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
stock = Object.keys(res).length ? res : null; stock = Object.keys(res).length ? res : null;
@@ -115,6 +146,7 @@
} }
async function handleTasks(resp){ async function handleTasks(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
tasks = Object.keys(res).length ? res : null; tasks = Object.keys(res).length ? res : null;
@@ -124,6 +156,7 @@
} }
async function handleTimes(resp){ async function handleTimes(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
times = Object.keys(res).length ? res : null; times = Object.keys(res).length ? res : null;
@@ -133,6 +166,7 @@
} }
async function handleWikiPages(resp){ async function handleWikiPages(resp){
quitOne();
if (resp.ok){ if (resp.ok){
const res = await resp.json(); const res = await resp.json();
pages = Object.keys(res).length ? res : null; pages = Object.keys(res).length ? res : null;
@@ -141,6 +175,13 @@
} }
} }
function quitOne(){
counter--;
if (counter > 0) {
warn(t('searching…')+" "+counter);
} else yikes();
}
$effect(() => doSearch(key)) $effect(() => doSearch(key))
</script> </script>
@@ -236,23 +277,6 @@
</ul> </ul>
</fieldset> </fieldset>
{/if} {/if}
{#if notes}
<fieldset>
<legend>
{t('notes')}
</legend>
<ul>
{#each Object.values(notes) as note}
<li>
<b>
<a href="/{note.module}/{note.entity_id}/view" {onclick} >{t(note.module)} {note.entity_id}:</a>
</b>
{@html target(note.text.rendered)}
</li>
{/each}
</ul>
</fieldset>
{/if}
{#if times} {#if times}
<fieldset> <fieldset>
<legend> <legend>
@@ -308,3 +332,20 @@
</ul> </ul>
</fieldset> </fieldset>
{/if} {/if}
{#if notes}
<fieldset>
<legend>
{t('notes')}
</legend>
<ul>
{#each Object.values(notes) as note}
<li>
<b>
<a href="/{note.module}/{note.entity_id}/view" {onclick} >{note.title}</a>
</b>
{@html target(note.text.rendered)}
</li>
{/each}
</ul>
</fieldset>
{/if}

View File

@@ -132,7 +132,23 @@ public class StockModule extends BaseHandler implements StockService {
yield super.doGet(path,ex); yield super.doGet(path,ex);
} }
} }
case null, default -> super.doGet(path,ex); case null -> super.doGet(path,ex);
default -> {
try {
var id = Long.parseLong(head);
Item item = stockDb.loadItem(id);
Owner owner = item.owner().resolve();
if (owner instanceof Company company) {
if (!companyService().membership(company.id(),user.get().id())) throw forbidden("You are not allowed to access {0}",OBJECT);
}
if (owner instanceof UmbrellaUser u){
if (u.id() != user.get().id()) throw forbidden("You are not allowed to access {0}",OBJECT);
}
yield sendContent(ex,item);
} catch (NumberFormatException nfe){
yield super.doGet(path,ex);
}
}
}; };
} catch (UmbrellaException e){ } catch (UmbrellaException e){
return send(ex,e); return send(ex,e);

View File

@@ -39,6 +39,7 @@ public class SqliteDb extends BaseDb implements TagDB{
public SqliteDb(Connection tagDb, Connection bmDb) { public SqliteDb(Connection tagDb, Connection bmDb) {
super(tagDb); super(tagDb);
bookmarks = new de.srsoftware.umbrella.bookmarks.SqliteDb(bmDb); bookmarks = new de.srsoftware.umbrella.bookmarks.SqliteDb(bmDb);
createTables();
} }
@Override @Override

View File

@@ -305,6 +305,7 @@
"saved": "gespeichert", "saved": "gespeichert",
"save_object": "{object} speichern", "save_object": "{object} speichern",
"search": "Suche", "search": "Suche",
"searching…": "suche…",
"select_company" : "Wählen Sie eine ihrer Firmen:", "select_company" : "Wählen Sie eine ihrer Firmen:",
"select_customer": "Kunde auswählen", "select_customer": "Kunde auswählen",
"select_property": "Eigenschaft auswählen", "select_property": "Eigenschaft auswählen",

View File

@@ -305,6 +305,7 @@
"saved": "saved", "saved": "saved",
"save_object": "save {object}", "save_object": "save {object}",
"search": "search", "search": "search",
"searching…": "searhcing…",
"select_company" : "select on of you companies:", "select_company" : "select on of you companies:",
"select_customer": "select customer", "select_customer": "select customer",
"select_property": "select property", "select_property": "select property",

View File

@@ -152,15 +152,27 @@ public class SqliteDb extends BaseDb implements WikiDb {
@Override @Override
public Map<Long, WikiPage> find(long userId, List<String> keys, boolean fulltext) { public Map<Long, WikiPage> find(long userId, List<String> keys, boolean fulltext) {
try { try {
var query = select(ALL).from(TABLE_PAGES).leftJoin(ID,TABLE_PAGES_USERS,PAGE_ID).where(USER_ID,equal(userId));
for (var key : keys) query.where(TITLE,like("%"+key+"%"));
var rs = query.exec(db);
var map = new HashMap<Long,WikiPage>(); var map = new HashMap<Long,WikiPage>();
{
var query = select(ALL).from(TABLE_PAGES).leftJoin(ID, TABLE_PAGES_USERS, PAGE_ID).where(USER_ID, equal(userId));
for (var key : keys) query.where(TITLE, like("%" + key.replaceAll("[ÄäÖöÜüß]", "%") + "%"));
var rs = query.exec(db);
while (rs.next()) { while (rs.next()) {
var page = WikiPage.of(rs); var page = WikiPage.of(rs);
map.put(page.id(),page); map.put(page.id(), page);
} }
rs.close(); rs.close();
}
if (fulltext) {
var query = select(ALL).from(TABLE_PAGES).leftJoin(ID, TABLE_PAGES_USERS, PAGE_ID).where(USER_ID, equal(userId));
for (var key : keys) query.where(CONTENT, like("%" + key.replaceAll("[ÄäÖöÜüß]", "%") + "%"));
var rs = query.exec(db);
while (rs.next()) {
var page = WikiPage.of(rs);
map.put(page.id(), page);
}
rs.close();
}
return map; return map;
} catch (SQLException e) { } catch (SQLException e) {
throw failedToSearchDb(t(WIKI_PAGES)).causedBy(e); throw failedToSearchDb(t(WIKI_PAGES)).causedBy(e);