Skip to content
Snippets Groups Projects
Verified Commit 5fc730b5 authored by Jonas Zohren's avatar Jonas Zohren :speech_balloon:
Browse files

Move to SvelteKit

parent ea60c14d
Branches migrate-to-oauth
No related tags found
No related merge requests found
.DS_Store
node_modules node_modules
npm-debug.log /build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
Dockerfile Dockerfile
.dockerignore .dockerignore
launch.sh .docker-compose.yml
README.md
.prettier*
.gitignore
.gitlab-ci.yml
.exlint*
*.env
LICENSE
\ No newline at end of file
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
module.exports = {
root: true,
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
}
};
node_modules/ .DS_Store
launch.sh node_modules
/build
/.svelte-kit
/package
.env .env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
build: stages:
stage: build - 'test'
- 'build'
- 'deploy'
.pnpm:
image: node:20-alpine
before_script:
- corepack enable
- corepack prepare pnpm@latest-8 --activate
- pnpm config set store-dir .pnpm-store
cache:
key:
files:
- pnpm-lock.yaml
paths:
- .pnpm-store
unit tests:
extends: '.pnpm'
stage: 'test'
script:
- pnpm install --ignore-scripts
- pnpm run test:unit run --reporter junit --outputFile vitest.junit.xml --passWithNoTests
artifacts:
when: 'always'
expire_in: '30 days'
paths:
- 'vitest.junit.xml'
reports:
junit: 'vitest.junit.xml'
build test:
extends: '.pnpm'
stage: 'test'
variables:
PUBLIC_URL_SOURCE_CODE: '${CI_PROJECT_URL}'
PUBLIC_BUILD_INFO: '${CI_JOB_STARTED_AT}'
script:
- pnpm install --ignore-scripts
- time pnpm run build
lint:
extends: '.pnpm'
stage: 'test'
script:
- pnpm install --ignore-scripts
- pnpm lint
docker:
stage: 'build'
variables:
GIT_SUBMODULE_STRATEGY: 'recursive'
DOCKER_TLS_CERTDIR: '/certs'
DOCKER_BUILDKIT: 1
IMAGE_TAG_SLUG: '${CI_REGISTRY_IMAGE}:latest'
IMAGE_TAG_ID_SHA: '${CI_REGISTRY_IMAGE}:commit-${CI_COMMIT_SHORT_SHA}'
BUILD_CACHE_TAG: '${CI_REGISTRY_IMAGE}/buildcache:ci'
image: image:
name: gcr.io/kaniko-project/executor:debug name: docker.io/docker
entrypoint: [""] services:
script: - name: docker.io/docker:dind
- mkdir -p /kaniko/.docker alias: docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json before_script:
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:latest --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA - docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}
only: script:
- main - docker context create ci-context
- docker buildx create --driver=docker-container --use ci-context
- docker buildx build
--pull
--cache-from=type=registry,ref=${BUILD_CACHE_TAG}
--cache-to=type=registry,mode=max,compression=zstd,ignore-error=true,ref=${BUILD_CACHE_TAG}
--build-arg "PUBLIC_URL_SOURCE_CODE=${CI_PROJECT_URL}"
--provenance=false
--tag ${IMAGE_TAG_SLUG}
--tag ${IMAGE_TAG_ID_SHA}
--push
--ulimit nofile=2048
${CI_PROJECT_DIR}
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
include:
# Do not run duplicate pipelines for branches + merge requests
# See https://docs.gitlab.com/ee/ci/yaml/workflow.html#workflowrules-templates
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
# Scan for security issues in dependencies:
# See https://docs.gitlab.com/ee/user/application_security/dependency_scanning/
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
.npmrc 0 → 100644
engine-strict=true
resolution-mode=highest
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
FROM node:18-alpine # syntax=docker/dockerfile:1.5
FROM docker.io/node:20-alpine AS build
RUN apk add --no-cache ca-certificates wget \
&& wget -O /tmp/pandoc.tar.gz https://github.com/jgm/pandoc/releases/download/2.12/pandoc-2.12-linux-amd64.tar.gz \ # Enable the use of pnpm:
&& tar xvzf /tmp/pandoc.tar.gz --strip-components 1 -C /usr/local/ \ RUN corepack enable
&& update-ca-certificates \
&& apk del wget \ WORKDIR /app
&& rm /tmp/pandoc.tar.gz
COPY --link package.json pnpm-lock.yaml ./
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app RUN pnpm install --ignore-scripts
WORKDIR /home/node/app
COPY package*.json ./ COPY --link . .
RUN npm install
COPY --chown=node:node . . ARG PUBLIC_URL_SOURCE_CODE=""
USER node RUN PUBLIC_BUILD_INFO="built at $(date)" pnpm run build
# Add SIGINT handler, so you can ctrl+c kill the docker container:
RUN echo "process.on('SIGINT', function() {process.exit();});" >> /app/build/index.js
# =======================================================================
FROM gcr.io/distroless/nodejs20-debian11 AS runtime
EXPOSE 3000 EXPOSE 3000
CMD [ "node", "server.js" ] CMD [ "/app/build" ]
WORKDIR /app
# package.json needed for node to use the module format:
COPY --link package.json /app/
COPY --link --from=build /app/build /app/build
LICENSE 0 → 100644
ISC License
Copyright (c) Jonas Zohren
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
\ No newline at end of file
# Template Generation Website # FSR-Protokoll-Pad-Generator
Zur einfachen Erstellung von Protokollen gibt es Tooling, das automatisch TOPs, Web-App, die für anstehende Sitzungen des Fachschaftrats Markdown-Pads vorgeneriert, in denen:
To-Dos, Mails und FSRler zusammensucht und das ganze bereits in ein
vorausgefülltes Protokoll füllt.
## Nutzung mit Docker (empfohlen) - Die aktuellen Mitglieder des FSRs,
- Die anstehenden Tagesordnungspunkte (TOPs) aus GitLab-Issues,
- Die getrackten To-Dos und
- Als sitzungsrelevant markierte E-Mails aus zammad aufgelistet sind
### Vor der ersten Benutzung ![Screenshot](./screenshot.png)
1. Installiere und starte docker auf deinem System. ## Wie kann ich das Pad-Template verändern?
2. Lege eine Datei namens `.env` an und befülle sie mit folgendem Inhalt:
```env - Bearbeite die Datei [`src/template.mustache.md`](./src/template.mustache.md)
HEDGEDOC_URL=https://md.fachschaften.org/ - Eine Dokumentation der für Variablen genutzten Templating-Sprache `mustache` gibt es [hier](https://mustache.github.io/mustache.5.html)
HEDGEDOC_USER=<mein-fachschaften.org-Benutzername> - Die im Template verfügbaren Variablen findet sich in [`/src/lib/templating/templating.js`](./src/lib/templating/templating.js)
HEDGEDOC_PASS=<mein-fachschaften.org-Passwort>
GITLAB_TOKEN=<mein-gitlab-token>
ZAMMAD_TOKEN=<mein-zammad-token>
```
3. Ersetze in der Datei `<mein-fachschaften.org-Benutzername>` und `<mein-fachschaften.org-Passwort>` durch deinen Fachschaften.org-Login
4. Ersetze in der Datei `<mein-gitlab-token>` durch einen echten Token. Gehe dazu in die [Gitlab-Einstellungen](https://gitlab.fachschaften.org/-/profile/personal_access_tokens) und generiere dir einen Access-Token (Haken bei `api` setzen).
5. Ersetze in der Datei `<mein-zammad-token>` durch einen echten Token. Hole dir dazu im [Zammad](https://zammad.oh14.de/#profile/token_access) einen Access-Token. Haken bei "Agent" setzen.
6. Falls noch nicht geschehen, logge dich mit Docker bei Gitlab ein. Führe dazu den folgenden Befehl aus, wobei du deinen Benutzernamen und den gerade generierten GitLab-Access-Token einsetzen musst:
```bash
docker login https://registry.gitlab.fachschaften.org/v2/tudo-fsinfo/fsr/protokoll-generator/ -u <mein-fachschaften.org-Benutzername> -p <mein-gitlab-access-token>
```
### Bei jeder Nutzung: ## Deployment
Sorge dafür, dass Docker gestartet ist und führe dann den folgenden Befehl aus: Für den Produktiveinsatz empfiehlt sich das (von der CI gebaute) Docker-Image:
```bash ```
docker run -p 3000:3000 --env-file .env registry.gitlab.fachschaften.org/tudo-fsinfo/fsr/protokoll-generator:latest registry.gitlab.fachschaften.org/oh14-dev/fsr-protokoll-pad-generator:latest
``` ```
Erreichbar ist die App dann auf Port 3000 per HTTP, also unter http://localhost:3000. Unter [`docker-compose.yml`](./docker-compose.yml) ist eine Beispiel-Deployment-Config verfügbar, die alle benötigten Environment-Variablen enthält.
## "Manuelle" Nutzung
### Dependencies:
- NodeJS > 10.x
- NPM
- Pandoc
- Permission to bind to Port 3000
- rw on /tmp/
### Launching Insbesondere muss in GitLab (Scope: `read_api`) und im Zammad jeweils eine OAuth2-App angelegt und deren `application_id` und `application_secret` in den Environment Variables gesetzt werden.
Einstiegspunkt ist `./server.js` ## Entwicklung
Die Konfiguration erfolgt per Umgebungsvariablen, die man am Besten Auf Linux mit einer aktuellen NodeJS-Version (18+) und [pnpm](https://pnpm.io/installation):
in einer `.env` Datei ablegt:
```envs ```bash
HEDGEDOC_URL=https://md.fachschaften.org/ pnpm install
HEDGEDOC_USER=<mein-fachschaften.org-Benutzername> pnpm run dev
HEDGEDOC_PASS=XXXXXXXXXX
GITLAB_TOKEN=XXXXXXXXXX
ZAMMAD_TOKEN=XXXXXXXXXX
``` ```
Der Node-Prozess lädt beim Start automatisch die `.env` Datei im aktuellen ### Release-Variante bauen:
Verzeichnis.
Mit Bash kann man das Programm dann so starten: Auf Linux mit einem aktuellen Docker:
```bash ```bash
node ./server.js docker buildx build --load --tag "fsr-protkoll-pad-generator:dev" .
``` ```
- `HEDGEDOC_PASS` ist euer LDAP-Passwort für Fachschaften.org ## Lizenz
- `GITLAB_TOKEN` bekommt man [hier](https://gitlab.fachschaften.org/-/profile/personal_access_tokens). Scope: read_api
- `ZAMMAD_TOKEN` gibt es [hier](https://zammad.oh14.de/#profile/token_access). Haken bei "Agent" setzen.
Erreichbar ist die App dann auf Port 3000 per HTTP, also unter http://localhost:3000. [ISC License](https://choosealicense.com/licenses/isc/), the simpler MIT license
## Entwicklung
### Aufbau des Codes
```
├── generate_transcript.js - Holt Infos und generiert ein Protokoll
├── node_modules
├── package.json
├── package-lock.json
├── public
│ └── index.html - HTML-Formular zum Generieren
├── README.md
├── router.js - Auslieferung Formular & API-Endpunkt zum Generieren
├── server.js - Glue-Script zum Server starten.
└── transcript_template.md - Das Protokoll-Template in Pandoc-Template-Syntax
```
function isNonEmptyString(value) {
return typeof value === "string" && value.length > 0;
}
/**
* If targetEnv is empty/not set, set it to the first alias that has contains a value
* @param {string} targetEnv the name of the env that aliases should map to
* @param {string[]} aliases aliases for targetEnv
* @returns targetEnv's value or the first non empty alias value
*/
function aliasEnv(targetEnv, aliases) {
targetEnv = targetEnv.toUpperCase();
aliases = aliases.map((alias) => alias.toUpperCase());
if (
typeof process.env[targetEnv] === "string" &&
process.env[targetEnv].length > 0
) {
return process.env[targetEnv];
} else {
for (const alias of aliases) {
const value = process.env[alias];
if (typeof value === "string" && value.length > 0) {
process.env[targetEnv] = value;
return value;
}
}
}
// Nothing found, all empty
return undefined;
}
/**
* Check if all envs exist and print errors otherwise.
* @param {{name: string, aliases: string[], description: string, whereToGetIt: string?}[]} envsWithDescriptions
* @returns array of error messages
*/
function checkAllEnvsExist(envsWithDescriptions) {
let errorMessages = [];
for (const {
name,
aliases,
description,
whereToGetIt,
} of envsWithDescriptions) {
const value = aliasEnv(name, aliases);
if (!isNonEmptyString(value)) {
const aliasMessage =
aliases.length > 0 ? " (Aliases: " + aliases.join(", ") + ")" : "";
const whereToGetItText = whereToGetIt
? "\n└ You can request it here: " + whereToGetIt
: "";
const errrorMessage = `
Environment variable ${name}${aliasMessage} is not set.
└ Description: ${description}${whereToGetItText}
`;
errorMessages.push(errrorMessage);
}
}
return errorMessages;
}
module.exports.checkAllEnvsExist = checkAllEnvsExist;
---
version: '3'
services:
fsr-protokoll-pad-generator:
build:
context: .
dockerfile: Dockerfile
image: 'registry.gitlab.fachschaften.org/oh14-dev/fsr-protokoll-pad-generator:latest'
restart: unless-stopped
environment:
PUBLIC_URL_PRIVACY: 'https://example.com/datenschutz.html'
PUBLIC_URL_IMPRINT: 'https://example.com/imprint.html'
PUBLIC_BASE_URL: 'https://generator.example.com'
GITLAB_BASE_URL: 'https://gitlab.example.com'
GITLAB_APP_ID: 'xxxxxxxxxxxxxxxxxxxxxxxx'
GITLAB_APP_SECRET: 'xxxxxxxxxxxxxxxxxxxxxxxx'
GITLAB_TODO_PROJECT_ID: '79'
GITLAB_TOP_PROJECT_ID: '77'
GITLAB_FSR_GROUP_ID: '29'
ZAMMAD_BASE_URL: 'https://zammad.example.com'
ZAMMAD_APP_ID: 'xxxxxxxxxxxxxxxxxxxxxxxx'
ZAMMAD_APP_SECRET: 'xxxxxxxxxxxxxxxxxxxxxxxx'
ZAMMAD_MARKER_TAG: 'sitzungsrelevant'
ports:
- '80:3000'
#!/usr/bin/env bash
# pandoc --metadata-file=meta.yaml --template transcript_template.md /dev/null -o out.md
const phin = require("phin");
const fs = require("fs");
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const gitLabToken = process.env.GITLAB_TOKEN;
const zammadToken = process.env.ZAMMAD_TOKEN;
const gitLabToDoUrl =
"https://gitlab.fachschaften.org/tudo-fsinfo/fsr/meta/issues/";
async function gitLabApiCall(url) {
return phin({
url: url,
headers: {
"Private-Token": gitLabToken,
},
parse: "json",
});
}
async function hasUserGitlabFSRAccess() {
try {
const res = await gitLabApiCall(
"https://gitlab.fachschaften.org/api/v4/projects/77/issues?per_page=1"
);
return Array.isArray(res.body);
} catch (e) {
return false;
}
}
module.exports.hasUserGitlabFSRAccess = hasUserGitlabFSRAccess;
async function zammadApiCall(url) {
return phin({
url: url,
headers: {
Authorization: "Token token=" + zammadToken,
},
parse: "json",
});
}
async function fetchFSRMembers() {
console.error("> Fetching FSR members from GitLab project FSR");
const memberRes = await gitLabApiCall(
"https://gitlab.fachschaften.org/api/v4/groups/29/members"
);
const members = memberRes.body
.map((m) => m.name)
.sort();
return members;
}
async function fetchTOPs() {
console.log("> Fetching TOPs from GitLab");
const issueRes = await gitLabApiCall(
"https://gitlab.fachschaften.org/api/v4/projects/77/issues?state=opened&per_page=100&sort=asc"
);
const tops = issueRes.body
// Filter for TOPs
.filter((i) => i.labels.some((l) => l === "TOP" || l === "Fin-TOP"))
.map((i) => ({
isFin: i.labels.some((l) => l === "Fin-TOP"),
title: i.title,
origin: i.assignees.length > 0 ? i.assignees[0].name : i.author.name,
hasComments: i.user_notes_count > 0,
description: i.description,
issue: i.iid,
}));
return tops;
}
async function fetchReports() {
console.log("> Fetching Reports from GitLab");
const issueRes = await gitLabApiCall(
"https://gitlab.fachschaften.org/api/v4/projects/77/issues?state=opened&per_page=100&sort=asc"
);
const reports = issueRes.body
// Filter for Reports
.filter((i) => i.labels.some((l) => l === "Bericht"))
.map((i) => ({
title: i.title,
origin: i.assignees.length > 0 ? i.assignees[0].name : i.author.name,
hasComments: i.user_notes_count > 0,
description: i.description,
issue: i.iid,
}));
return reports;
}
async function fetchTOPNotes(issue_id) {
console.log("> Fetching comments for issue", issue_id);
const noteRes = await gitLabApiCall(
"https://gitlab.fachschaften.org/api/v4/projects/77/issues/" +
issue_id +
"/notes?per_page=100&sort=asc"
);
const notes = noteRes.body
.filter((n) => !n.system) // Filter out non-comment notes
.map((n) => ({
author: n.author.name,
text: n.body,
}));
console.debug(notes);
return notes;
}
async function fetchToDos() {
console.log("> Fetching Todos from GitLab Meta Project");
const todosRes = await gitLabApiCall(
"https://gitlab.fachschaften.org/api/v4/projects/79/issues?state=opened&labels=To-Do&per_page=100"
);
const todos = todosRes.body.map((t) => {
return {
title: t.title,
id: t.iid,
assignees: t.assignees.map((a) => a.name).join(", "),
};
});
return todos;
}
async function fetchMails() {
console.log("> Fetching Mails from Zammad");
const ticketSearchRes = await zammadApiCall(
"https://zammad.oh14.de/api/v1/search?query=%23sitzungsrelevant&sort_by=id&order_by=asc"
);
const tickets = [];
for (let r of ticketSearchRes.body.result.filter(
(e) => e.type === "Ticket"
)) {
const tr = await zammadApiCall(
"https://zammad.oh14.de/api/v1/tickets/" + r.id
);
const ur = await zammadApiCall(
"https://zammad.oh14.de/api/v1/users/" + tr.body.customer_id
);
const user = ur.body;
const ticket = tr.body;
if (ticket.state_id === 4 || ticket.state_id === 7) {
continue; // Closed (4) or deleted (7) ticket
}
const states = await zammadApiCall(
"https://zammad.oh14.de/api/v1/ticket_states"
);
tickets.push({
from: user.firstname + (user.lastname ? " " + user.lastname : ""),
subject: ticket.title,
ticketId: ticket.id,
});
}
return tickets;
}
function getTodayAsString() {
const today = new Date();
const dateStrgs = {
y: today.getFullYear(),
m: ("" + (today.getMonth() + 1)).padStart(2, "0"),
d: ("" + today.getDate()).padStart(2, "0"),
};
const dateStr = `${dateStrgs.y}-${dateStrgs.m}-${dateStrgs.d}`;
return dateStr;
}
function getTodayAsFancyString() {
const today = new Date();
const dateStrgs = {
y: today.getFullYear(),
m: ("" + (today.getMonth() + 1)).padStart(2, "0"),
d: ("" + today.getDate()).padStart(2, "0"),
};
const dateStr = `${dateStrgs.d}.${dateStrgs.m}.${dateStrgs.y}`;
return dateStr;
}
async function main(number) {
console.debug("> Combining TOPs with their GitLab comments");
// Combine TOPs and reports with their comments:
let tops = await fetchTOPs();
let reports = await fetchReports();
for (let top of tops) {
top.comments = await fetchTOPNotes(top.issue);
}
for (let report of reports) {
report.comments = await fetchTOPNotes(report.issue);
}
const transcriptOptions = await gatherTranscriptInfo(tops, reports, number);
console.debug("> Generating template with pandoc");
fs.writeFileSync("/tmp/meta.json", JSON.stringify(transcriptOptions));
const template = await exec(
'echo "" | pandoc --metadata-file=/tmp/meta.json --wrap=preserve --template transcript_template.md --from gfm --to markdown'
);
return template.stdout.trim() + "\n";
}
module.exports.generateTranscript = main;
async function gatherTranscriptInfo(tops, reports, number) {
const mailPromise = fetchMails();
const transcriptOptions = {
number: number,
present: await fetchFSRMembers(), // Assume all FSR members are present.
date: process.env.DATE || getTodayAsString(),
fancyDate: getTodayAsFancyString(),
tops: tops,
reports: reports,
todos: await fetchToDos(),
mails: await mailPromise,
head: process.env.HEAD || undefined,
transcript: process.env.TRANSCRIPT || undefined,
resolutions: process.env.RESOLUTIONS || undefined,
};
return transcriptOptions;
}
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
This diff is collapsed.
{ {
"name": "fsinfo-issues-to-transcript", "name": "my-app",
"version": "1.0.0", "version": "2.0.0",
"description": "", "private": true,
"main": "index.js",
"dependencies": {
"@sentry/node": "^6.13.3",
"dotenv": "^8.2.0",
"koa": "^2.11.0",
"koa-router": "^7.4.0",
"phin": "^3.4.0",
"request": "^2.88.0",
"request-promise-native": "^1.0.8",
"tough-cookie": "latest"
},
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "dev": "vite dev",
"build": "svelte-kit sync && vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"test": "vitest",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@gitbeaker/rest": "^39.5.1",
"@sveltejs/adapter-node": "^1.3.1",
"@sveltejs/kit": "^1.20.4",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"mustache": "^4.2.0",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.0",
"svelte-check": "^3.4.3",
"typescript": "^5.0.0",
"vite": "^4.3.6",
"vitest": "^0.32.2"
}, },
"author": "", "type": "module"
"license": "ISC"
} }
This diff is collapsed.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hello Bulma!</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bulma@0.8.2/css/bulma.min.css"
/>
<script
defer
src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"
></script>
</head>
<body>
<section class="section">
<div class="container">
<h1 class="title">Protokoll vorbereiten</h1>
</div>
</section>
<section>
<div class="container">
<p>
Du kannst hier schon einige Daten des Protokolls eintragen. Diese
werden später bereits ins Protokoll an den richtigen Stellen
eingefügt, damit du das nicht tun musst.
</p>
<p>
<button class="button is-success is-outlined is-fullwidth">
Daten importieren
</button>
</p>
<hr />
<div class="columns">
<div class="field column">
<label class="label">Sitzungsnummer</label>
<div class="control has-icons-left">
<input class="input" id="input-number" type="text" value="512" />
<span class="icon is-small is-left">
<i>#</i>
</span>
<p class="help">
Wenn du keine Nummer benutzen möchtest, lass dieses Feld einfach
leer.
</p>
</div>
</div>
<div class="field column">
<label class="label">Datum</label>
<div class="control">
<input id="input-date" class="input" type="date" />
</div>
</div>
</div>
<div class="field">
<label class="label">Titel</label>
<div class="control">
<input
id="input-title"
class="input"
type="text"
value="FSR-Sitzung 512 (11.11.2011)"
/>
<p class="help">
Wenn du dieses Feld leer lässt, wird versucht aus den anderen
Daten (Datum, Nummer, ...) ein Titel zu generieren.
</p>
</div>
</div>
<div class="columns">
<div class="field column">
<label class="label">Erwartete Anwesende</label>
<div class="control">
<textarea class="textarea"></textarea>
<p class="help">Eine Zeile pro Person</p>
</div>
</div>
<div class="field column">
<label class="label">Erwartete Abwesende</label>
<div class="control">
<textarea class="textarea"></textarea>
<p class="help">Eine Zeile pro Person</p>
</div>
</div>
<div class="field column">
<label class="label">Erwartete Gäste</label>
<div class="control">
<textarea class="textarea"></textarea>
<p class="help">Eine Zeile pro Person</p>
</div>
</div>
</div>
<div class="field">
<label class="label">Tagesordnungspunkte</label>
</div>
<div class="field has-addons">
<p class="control">
<span class="select">
<select>
<option>TOP</option>
<option>Fin-TOP</option>
</select>
</span>
</p>
<p class="control is-expanded">
<input class="input" type="text" value="E-Mails" />
</p>
</div>
<div class="field has-addons">
<p class="control">
<span class="select">
<select>
<option>TOP</option>
<option>Fin-TOP</option>
</select>
</span>
</p>
<p class="control is-expanded">
<input
class="input"
type="text"
placeholder="Neuen TOP hier eintragen"
/>
</p>
</div>
<div class="field">
<label class="label">Integrationsdienste</label>
</div>
<div class="field">
<div class="control">
<label class="checkbox">
<input type="checkbox" />
Pad für dieses Protokoll erstellen
<p class="help">
Erstellt neues Pad auf https://md.fachschaften.org/
</p>
</label>
</div>
</div>
<div class="field">
<div class="control">
<label class="checkbox">
<input type="checkbox" />
Padlink kopieren
</label>
</div>
</div>
<div class="field">
<div class="control">
<label class="checkbox">
<input type="checkbox" />
Padlink verschicken
<p class="help">Link geht an fsinfo@lists.cs.tu-dortmund.de</p>
</label>
</div>
</div>
<div class="field">
<div class="control">
<label class="checkbox">
<input type="checkbox" />
Pad automatisch in Merge-Request umwandeln
<p class="help">
Sobald die End-Zeit eingetragen wird, wird der Padinhalt als
neuer Merge-Request auf
gitlab.fachschaften.org/tudo-fsinfo/sitzungen hinzugefügt.
</p>
</label>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
<div class="control">
<button class="button is-link is-light">Cancel</button>
</div>
</div>
</div>
</section>
</body>
<script>
const number = document.getElementById("input-number").value;
document.getElementById("input-date").valueAsDate = new Date();
document.getElementById(
"input-title"
).value = `FSR-Sitzung ${number} (${new Date().toLocaleDateString("de")})`;
</script>
</html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment