Compare commits

..

1 Commits

Author SHA1 Message Date
8c351440c8 bugfix: renamed cosntant to make system use the right string
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-01-19 22:48:28 +01:00
36 changed files with 63 additions and 236 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 frontend /home/svelte/Umbrella/frontend ADD . /home/svelte/Umbrella
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:3.22 AS java_build FROM alpine AS java_build
RUN apk add gradle RUN apk add gradle fontconfig font-opensans openjdk21-jre
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,18 +17,13 @@ RUN gradle --no-daemon build
FROM alpine FROM alpine
RUN apk --no-cache add bash fontconfig font-opensans graphviz openjdk21-jre tzdata weasyprint \ RUN apk add bash fontconfig font-opensans graphviz openjdk21-jre weasyprint
&& adduser -D umbrella RUN 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
USER umbrella
WORKDIR /home/umbrella WORKDIR /home/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

@@ -1,5 +1,5 @@
FROM alpine:3.22 FROM alpine:3.22
LABEL Maintainer "Stephan Richter" LABEL Maintainer "Stephan Richter <s.richter@srsoftware.de>"
ARG UID=1000 ARG UID=1000
ARG GID=1000 ARG GID=1000
RUN apk add bash npm RUN apk add bash npm

View File

@@ -35,12 +35,11 @@ import org.json.JSONObject;
public class Util { public class Util {
public static final System.Logger LOG = System.getLogger("Util"); public static final System.Logger LOG = System.getLogger("Util");
private static final Pattern UML_PATTERN = Pattern.compile("@start(\\w+)(.*?)@end(\\1)",Pattern.DOTALL); private static final Pattern UML_PATTERN = Pattern.compile("@start(\\w+)(.*)@end(\\1)",Pattern.DOTALL);
private static File plantumlJar = null; private static File plantumlJar = null;
private static final JParsedown MARKDOWN = new JParsedown(); private static final JParsedown MARKDOWN = new JParsedown();
public static final String SHA1 = "SHA-1"; public static final String SHA1 = "SHA-1";
private static final MessageDigest SHA1_DIGEST; private static final MessageDigest SHA1_DIGEST;
private static final Map<Integer,String> umlCache = new HashMap<>();
static { static {
try { try {
@@ -80,22 +79,11 @@ public class Util {
try { try {
if (plantumlJar != null && plantumlJar.exists()) { if (plantumlJar != null && plantumlJar.exists()) {
var matcher = UML_PATTERN.matcher(source); var matcher = UML_PATTERN.matcher(source);
while (matcher.find()) { if (matcher.find()) {
var uml = matcher.group(0).trim(); var uml = matcher.group(0).trim();
var start = matcher.start(0); var start = matcher.start(0);
var end = matcher.end(0); var end = matcher.end(0);
var umlHash = uml.hashCode();
LOG.log(DEBUG,"Hash of Plantuml code: {0}",umlHash);
var svg = umlCache.get(umlHash);
if (svg != null){
LOG.log(DEBUG,"Serving Plantuml generated SVG from cache…");
source = source.substring(0, start) + svg + source.substring(end);
matcher = UML_PATTERN.matcher(source);
continue;
}
LOG.log(DEBUG,"Cache miss. Generating SVG from plantuml code…");
ProcessBuilder processBuilder = new ProcessBuilder("java", "-jar", plantumlJar.getAbsolutePath(), "-tsvg", "-pipe"); ProcessBuilder processBuilder = new ProcessBuilder("java", "-jar", plantumlJar.getAbsolutePath(), "-tsvg", "-pipe");
var ignored = processBuilder.redirectErrorStream(); var ignored = processBuilder.redirectErrorStream();
var process = processBuilder.start(); var process = processBuilder.start();
@@ -106,11 +94,8 @@ public class Util {
try (InputStream is = process.getInputStream()) { try (InputStream is = process.getInputStream()) {
byte[] out = is.readAllBytes(); byte[] out = is.readAllBytes();
LOG.log(DEBUG,"Generated SVG. Pushing to cache…"); var svg = new String(out, UTF_8);
svg = new String(out, UTF_8);
umlCache.put(umlHash,svg);
source = source.substring(0, start) + svg + source.substring(end); source = source.substring(0, start) + svg + source.substring(end);
matcher = UML_PATTERN.matcher(source);
} }
} }
} }

1
demodata/.gitignore vendored
View File

@@ -1 +0,0 @@
*.db-journal

Binary file not shown.

Binary file not shown.

View File

@@ -1,68 +0,0 @@
{
"umbrella": {
"base_url": "http://127.0.0.1:5173",
"logging": {
"rootLevel": "INFO"
},
"http": {
"port": 8080
},
"threads": 16,
"modules": {
"notes": {
"database": "demodata/notes.db"
},
"document": {
"database": "demodata/documents.db",
"templates": "demodata/templates"
},
"wiki": {
"database": "demodata/wiki.db"
},
"project": {
"database": "demodata/projects.db"
},
"message": {
"database": "demodata/message.db",
"smtp": {
"pass": "none",
"port": 587,
"host": "none",
"user": "none"
}
},
"tags": {
"database": "demodata/tags.db"
},
"bookmark": {
"database": "demodata/bookmark.db"
},
"task": {
"database": "demodata/tasks.db"
},
"journal": {
"database": "demodata/journal.db"
},
"contact": {
"database": "demodata/contacts.db"
},
"files": {
"database": "demodata/files.db",
"base_dir": "demodata/filestore"
},
"company": {
"database": "demodata/company.db"
},
"time": {
"database": "demodata/times.db"
},
"stock": {
"database": "demodata/stock.db"
},
"items": {},
"user": {
"database": "demodata/users.db"
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -541,11 +541,6 @@ 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,15 +1,14 @@
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router'; import { useTinyRouter } from 'svelte-tiny-router';
import { api, get, post, target } from '../../urls.svelte.js'; import { api, post, target } from '../../urls.svelte.js';
import { error, warn, yikes } from '../../warn.svelte'; import { error, 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);
@@ -35,12 +34,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);
@@ -52,22 +51,6 @@
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;
@@ -78,7 +61,6 @@
} }
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;
@@ -88,7 +70,6 @@
} }
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;
@@ -98,7 +79,6 @@
} }
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;
@@ -108,25 +88,15 @@
} }
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();
if ( Object.keys(json).length ) { notes = Object.keys(json).length ? json : null;
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;
@@ -136,7 +106,6 @@
} }
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;
@@ -146,7 +115,6 @@
} }
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;
@@ -156,7 +124,6 @@
} }
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;
@@ -166,7 +133,6 @@
} }
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;
@@ -174,13 +140,6 @@
error(resp); error(resp);
} }
} }
function quitOne(){
counter--;
if (counter > 0) {
warn(t('searching…')+" "+counter);
} else yikes();
}
$effect(() => doSearch(key)) $effect(() => doSearch(key))
</script> </script>
@@ -277,6 +236,23 @@
</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>
@@ -332,20 +308,3 @@
</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

@@ -2,7 +2,6 @@
package de.srsoftware.umbrella.message; package de.srsoftware.umbrella.message;
public class Constants { public class Constants {
public static final String AUTH = "mail.smtp.auth";
public static final String CONFIG_DB = "umbrella.modules.message.database"; public static final String CONFIG_DB = "umbrella.modules.message.database";
public static final String CONFIG_SMTP_HOST = "umbrella.modules.message.smtp.host"; public static final String CONFIG_SMTP_HOST = "umbrella.modules.message.smtp.host";
@@ -11,13 +10,11 @@ public class Constants {
public static final String CONFIG_SMTP_USER = "umbrella.modules.message.smtp.user"; public static final String CONFIG_SMTP_USER = "umbrella.modules.message.smtp.user";
public static final String DEBUG_ADDREESS = "umbrella.modules.message.debug_address"; public static final String DEBUG_ADDREESS = "umbrella.modules.message.debug_address";
public static final String ENVELOPE_FROM = "mail.smtp.from"; public static final String SMTP_AUTH = "mail.smtp.auth";
public static final String FIELD_MESSAGES = "messages"; public static final String SMTP_HOST = "mail.smtp.host";
public static final String FIELD_HOST = "host"; public static final String SMTP_FROM = "mail.smtp.from";
public static final String FIELD_PORT = "port"; public static final String SMTP_PORT = "mail.smtp.port";
public static final String HOST = "mail.smtp.host"; public static final String SMTP_SSL = "mail.smtp.ssl.enable";
public static final String PORT = "mail.smtp.port";
public static final String SSL = "mail.smtp.ssl.enable";
public static final String SUBMISSION = "submission"; public static final String SUBMISSION = "submission";
} }

View File

@@ -142,11 +142,11 @@ public class MessageSystem implements PostBox {
private Session session() { private Session session() {
if (session == null){ if (session == null){
Properties props = new Properties(); Properties props = new Properties();
props.put(HOST, host); props.put(SMTP_HOST, host);
props.put(PORT, port); props.put(SMTP_PORT, port);
props.put(AUTH, true); props.put(SMTP_AUTH, true);
props.put(SSL, true); props.put(SMTP_SSL, true);
props.put(ENVELOPE_FROM,from); props.put(SMTP_FROM,from);
session = Session.getInstance(props); session = Session.getInstance(props);
} }
return session; return session;

View File

@@ -132,23 +132,7 @@ public class StockModule extends BaseHandler implements StockService {
yield super.doGet(path,ex); yield super.doGet(path,ex);
} }
} }
case null -> super.doGet(path,ex); case null, default -> 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,7 +39,6 @@ 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

@@ -278,7 +278,6 @@
"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

@@ -278,7 +278,6 @@
"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

@@ -9,5 +9,4 @@ tasks.processResources {
from("../frontend/dist") { from("../frontend/dist") {
into("web") into("web")
} }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE }
}

View File

@@ -57,9 +57,8 @@ footer {
margin: 5px; margin: 5px;
} }
img, svg { img {
max-width: 100%; max-width: 100%;
height: auto !important;
} }
nav { nav {

View File

@@ -57,9 +57,8 @@ footer {
margin: 5px; margin: 5px;
} }
img, svg { img {
max-width: 100%; max-width: 100%;
height: auto !important;
} }
nav { nav {

View File

@@ -57,18 +57,17 @@ footer {
margin: 5px; margin: 5px;
} }
img, svg { img {
max-width: 100%; max-width: 100%;
height: auto !important;
} }
nav { nav {
position: sticky; position: sticky;
z-index: 100; z-index: 100;
top: 0; top: 0;
padding: 5px; padding: 5px;
margin: 0 0 10px 0; margin: 0 0 10px 0;
border-bottom: 1px solid; border-bottom: 1px solid;
} }
td, tr{ td, tr{

View File

@@ -152,27 +152,15 @@ 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>();
{ while (rs.next()) {
var query = select(ALL).from(TABLE_PAGES).leftJoin(ID, TABLE_PAGES_USERS, PAGE_ID).where(USER_ID, equal(userId)); var page = WikiPage.of(rs);
for (var key : keys) query.where(TITLE, like("%" + key.replaceAll("[ÄäÖöÜüß]", "%") + "%")); map.put(page.id(),page);
var rs = query.exec(db);
while (rs.next()) {
var page = WikiPage.of(rs);
map.put(page.id(), page);
}
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();
} }
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);