Add path prefix
This commit is contained in:
parent
19956ebafb
commit
afbced61e3
39 changed files with 656 additions and 305 deletions
|
@ -1,6 +1,8 @@
|
|||
[server]
|
||||
host = "localhost"
|
||||
port = 8080
|
||||
prefix = ""
|
||||
debug = false
|
||||
|
||||
[schedule]
|
||||
reference = "2019-06-12"
|
||||
|
|
|
@ -11,7 +11,8 @@ import org.w3c.dom.events.Event
|
|||
import kotlin.browser.window
|
||||
|
||||
class WebSocketClient() {
|
||||
private val url = "ws://${window.location.host}/"
|
||||
val prefix = js("prefix")
|
||||
private val url = "ws://${window.location.host}$prefix/"
|
||||
|
||||
private lateinit var ws: WebSocket
|
||||
private var reconnect = false
|
||||
|
|
|
@ -11,6 +11,8 @@ import kotlinx.serialization.list
|
|||
|
||||
object PostRepository : Repository<Post> {
|
||||
|
||||
val prefix = js("prefix")
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
@ -18,35 +20,35 @@ object PostRepository : Repository<Post> {
|
|||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): Post? {
|
||||
val json = repositoryGet("/api/post/$id") ?: return null
|
||||
val json = repositoryGet("$prefix/api/post/$id") ?: return null
|
||||
return parser.parse(json, Post.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: Post): Long {
|
||||
return repositoryPost("/api/posts", Message.json.stringify(Post.serializer(), model))
|
||||
return repositoryPost("$prefix/api/posts", Message.json.stringify(Post.serializer(), model))
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: Post) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/post/${model.id}", Message.json.stringify(Post.serializer(), model))
|
||||
repositoryPost("$prefix/api/post/${model.id}", Message.json.stringify(Post.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/post/$id/delete")
|
||||
repositoryPost("$prefix/api/post/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Post> {
|
||||
val json = repositoryGet("/api/posts") ?: return emptyList()
|
||||
val json = repositoryGet("$prefix/api/posts") ?: return emptyList()
|
||||
return parser.parse(json, Post.serializer().list)
|
||||
}
|
||||
|
||||
suspend fun htmlByUrl(url: String): String {
|
||||
return repositoryRawGet("/api/p/$url")
|
||||
return repositoryRawGet("$prefix/api/p/$url")
|
||||
}
|
||||
|
||||
suspend fun render(data: String): String {
|
||||
return repositoryPost("/api/render", data)
|
||||
return repositoryPost("$prefix/api/render", data)
|
||||
}
|
||||
|
||||
val handler = object : MessageHandler(RepositoryType.POST) {
|
||||
|
|
|
@ -11,6 +11,8 @@ import kotlinx.serialization.list
|
|||
|
||||
object RoomRepository : Repository<Room> {
|
||||
|
||||
val prefix = js("prefix")
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
@ -18,26 +20,26 @@ object RoomRepository : Repository<Room> {
|
|||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): Room? {
|
||||
val json = repositoryGet("/api/room/$id") ?: return null
|
||||
val json = repositoryGet("$prefix/api/room/$id") ?: return null
|
||||
return parser.parse(json, Room.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: Room): Long {
|
||||
return repositoryPost("/api/rooms", Message.json.stringify(Room.serializer(), model))
|
||||
return repositoryPost("$prefix/api/rooms", Message.json.stringify(Room.serializer(), model))
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: Room) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/room/${model.id}", Message.json.stringify(Room.serializer(), model))
|
||||
repositoryPost("$prefix/api/room/${model.id}", Message.json.stringify(Room.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/room/$id/delete")
|
||||
repositoryPost("$prefix/api/room/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Room> {
|
||||
val json = repositoryGet("/api/rooms") ?: return emptyList()
|
||||
val json = repositoryGet("$prefix/api/rooms") ?: return emptyList()
|
||||
return parser.parse(json, Room.serializer().list)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ import kotlinx.serialization.list
|
|||
|
||||
object ScheduleRepository : Repository<Schedule> {
|
||||
|
||||
val prefix = js("prefix")
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
@ -19,26 +21,26 @@ object ScheduleRepository : Repository<Schedule> {
|
|||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): Schedule? {
|
||||
val json = repositoryGet("/api/schedule/$id") ?: return null
|
||||
val json = repositoryGet("$prefix/api/schedule/$id") ?: return null
|
||||
return parser.parse(json, Schedule.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: Schedule): Long {
|
||||
return repositoryPost("/api/schedules", Message.json.stringify(Schedule.serializer(), model))
|
||||
return repositoryPost("$prefix/api/schedules", Message.json.stringify(Schedule.serializer(), model))
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: Schedule) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/schedule/${model.id}", Message.json.stringify(Schedule.serializer(), model))
|
||||
repositoryPost("$prefix/api/schedule/${model.id}", Message.json.stringify(Schedule.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/schedule/$id/delete")
|
||||
repositoryPost("$prefix/api/schedule/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Schedule> {
|
||||
val json = repositoryGet("/api/schedules") ?: return emptyList()
|
||||
val json = repositoryGet("$prefix/api/schedules") ?: return emptyList()
|
||||
return parser.parse(json, Schedule.serializer().list)
|
||||
}
|
||||
|
||||
|
@ -52,12 +54,12 @@ object ScheduleRepository : Repository<Schedule> {
|
|||
}
|
||||
|
||||
suspend fun checkConstraints(): ConstraintMap {
|
||||
val json = repositoryGet("/api/constraints")
|
||||
val json = repositoryGet("$prefix/api/constraints")
|
||||
return parser.parse(json, ConstraintMap.serializer())
|
||||
}
|
||||
|
||||
suspend fun checkConstraintsFor(schedule: Schedule): ConstraintMap {
|
||||
val json = repositoryGet("/api/constraint/${schedule.id}")
|
||||
val json = repositoryGet("$prefix/api/constraint/${schedule.id}")
|
||||
return parser.parse(json, ConstraintMap.serializer())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import kotlinx.serialization.list
|
|||
|
||||
object TrackRepository : Repository<Track> {
|
||||
|
||||
val prefix = js("prefix")
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
@ -18,26 +20,26 @@ object TrackRepository : Repository<Track> {
|
|||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): Track? {
|
||||
val json = repositoryGet("/api/track/$id") ?: return null
|
||||
val json = repositoryGet("$prefix/api/track/$id") ?: return null
|
||||
return parser.parse(json, Track.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: Track): Long {
|
||||
return repositoryPost("/api/tracks", Message.json.stringify(Track.serializer(), model))
|
||||
return repositoryPost("$prefix/api/tracks", Message.json.stringify(Track.serializer(), model))
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: Track) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/track/${model.id}", Message.json.stringify(Track.serializer(), model))
|
||||
repositoryPost("$prefix/api/track/${model.id}", Message.json.stringify(Track.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/track/$id/delete")
|
||||
repositoryPost("$prefix/api/track/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<Track> {
|
||||
val json = repositoryGet("/api/tracks") ?: return emptyList()
|
||||
val json = repositoryGet("$prefix/api/tracks") ?: return emptyList()
|
||||
return parser.parse(json, Track.serializer().list)
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import kotlinx.serialization.list
|
|||
|
||||
object UserRepository : Repository<User> {
|
||||
|
||||
val prefix = js("prefix")
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
@ -18,26 +20,26 @@ object UserRepository : Repository<User> {
|
|||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): User? {
|
||||
val json = repositoryGet("/api/user/$id") ?: return null
|
||||
val json = repositoryGet("$prefix/api/user/$id") ?: return null
|
||||
return parser.parse(json, User.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: User): Long {
|
||||
return repositoryPost("/api/users", Message.json.stringify(User.serializer(), model))
|
||||
return repositoryPost("$prefix/api/users", Message.json.stringify(User.serializer(), model))
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: User) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/user/${model.id}", Message.json.stringify(User.serializer(), model))
|
||||
repositoryPost("$prefix/api/user/${model.id}", Message.json.stringify(User.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/user/$id/delete")
|
||||
repositoryPost("$prefix/api/user/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<User> {
|
||||
val json = repositoryGet("/api/users") ?: return emptyList()
|
||||
val json = repositoryGet("$prefix/api/users") ?: return emptyList()
|
||||
return parser.parse(json, User.serializer().list)
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import kotlinx.serialization.list
|
|||
|
||||
object WorkGroupRepository : Repository<WorkGroup> {
|
||||
|
||||
val prefix = js("prefix")
|
||||
|
||||
override val onCreate = EventHandler<Long>()
|
||||
override val onUpdate = EventHandler<Long>()
|
||||
override val onDelete = EventHandler<Long>()
|
||||
|
@ -18,26 +20,26 @@ object WorkGroupRepository : Repository<WorkGroup> {
|
|||
private val parser = DynamicObjectParser()
|
||||
|
||||
override suspend fun get(id: Long): WorkGroup? {
|
||||
val json = repositoryGet("/api/workgroup/$id") ?: return null
|
||||
val json = repositoryGet("$prefix/api/workgroup/$id") ?: return null
|
||||
return parser.parse(json, WorkGroup.serializer())
|
||||
}
|
||||
|
||||
override suspend fun create(model: WorkGroup): Long {
|
||||
return repositoryPost("/api/workgroups", Message.json.stringify(WorkGroup.serializer(), model))
|
||||
return repositoryPost("$prefix/api/workgroups", Message.json.stringify(WorkGroup.serializer(), model))
|
||||
?: throw IllegalStateException("Cannot create model!")
|
||||
}
|
||||
|
||||
override suspend fun update(model: WorkGroup) {
|
||||
if (model.id == null) throw IllegalStateException("Cannot update model which was not created!")
|
||||
repositoryPost("/api/workgroup/${model.id}", Message.json.stringify(WorkGroup.serializer(), model))
|
||||
repositoryPost("$prefix/api/workgroup/${model.id}", Message.json.stringify(WorkGroup.serializer(), model))
|
||||
}
|
||||
|
||||
override suspend fun delete(id: Long) {
|
||||
repositoryPost("/api/workgroup/$id/delete")
|
||||
repositoryPost("$prefix/api/workgroup/$id/delete")
|
||||
}
|
||||
|
||||
override suspend fun all(): List<WorkGroup> {
|
||||
val json = repositoryGet("/api/workgroups") ?: return emptyList()
|
||||
val json = repositoryGet("$prefix/api/workgroups") ?: return emptyList()
|
||||
return parser.parse(json, WorkGroup.serializer().list)
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ fun initWorkGroupConstraints() {
|
|||
html.name = "constraint-only-on-day-${index++}"
|
||||
min = -1337.0
|
||||
max = 1337.0
|
||||
placeholder = "Tag"
|
||||
}.html)
|
||||
}.html)
|
||||
}
|
||||
|
@ -64,6 +65,7 @@ fun initWorkGroupConstraints() {
|
|||
html.name = "constraint-not-on-day-${index++}"
|
||||
min = -1337.0
|
||||
max = 1337.0
|
||||
placeholder = "Tag"
|
||||
}.html)
|
||||
}.html)
|
||||
}
|
||||
|
@ -78,13 +80,15 @@ fun initWorkGroupConstraints() {
|
|||
}.html)
|
||||
html.appendChild(InputView(InputType.TEXT).apply {
|
||||
classList += "form-control"
|
||||
html.name = "constraint-only-before-time-day-${index++}"
|
||||
html.name = "constraint-only-before-time-day-${index}"
|
||||
placeholder = "Tag (optional)"
|
||||
}.html)
|
||||
html.appendChild(InputView(InputType.NUMBER).apply {
|
||||
classList += "form-control"
|
||||
html.name = "constraint-only-before-time-${index++}"
|
||||
min = -1337.0
|
||||
max = 133700.0
|
||||
placeholder = "Minuten"
|
||||
}.html)
|
||||
}.html)
|
||||
}
|
||||
|
@ -99,13 +103,15 @@ fun initWorkGroupConstraints() {
|
|||
}.html)
|
||||
html.appendChild(InputView(InputType.TEXT).apply {
|
||||
classList += "form-control"
|
||||
html.name = "constraint-only-after-time-day-${index++}"
|
||||
html.name = "constraint-only-after-time-day-${index}"
|
||||
placeholder = "Tag (optional)"
|
||||
}.html)
|
||||
html.appendChild(InputView(InputType.NUMBER).apply {
|
||||
classList += "form-control"
|
||||
html.name = "constraint-only-after-time-${index++}"
|
||||
min = -1337.0
|
||||
max = 133700.0
|
||||
placeholder = "Minuten"
|
||||
}.html)
|
||||
}.html)
|
||||
}
|
||||
|
|
|
@ -9,17 +9,20 @@ import de.westermann.kwebview.iterator
|
|||
import org.w3c.dom.*
|
||||
import kotlin.browser.document
|
||||
import kotlin.browser.window
|
||||
|
||||
import kotlin.js.Date
|
||||
|
||||
class Calendar(calendar: HTMLElement) : View(calendar) {
|
||||
|
||||
var autoScroll = true
|
||||
|
||||
val day: Int = calendar.dataset["day"]?.toIntOrNull() ?: -1
|
||||
|
||||
val calendarTable = calendar.getElementsByClassName("calendar-table")[0] as HTMLElement
|
||||
private val calendarTableHeader = calendar.getElementsByClassName("calendar-header")[0] as HTMLElement
|
||||
|
||||
val day = calendarTable.dataset["day"]?.toIntOrNull() ?: -1
|
||||
val referenceDate = calendarTable.dataset["reference"]?.toLongOrNull() ?: -1L
|
||||
val nowDate = calendarTable.dataset["now"]?.toLongOrNull() ?: -1L
|
||||
val timeDifference = Date.now().toLong() - nowDate
|
||||
|
||||
fun scrollVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
|
||||
scrollAllVerticalBy(pixel, scrollBehavior)
|
||||
}
|
||||
|
@ -46,7 +49,6 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
|
|||
Orientation.ROOM_TO_TIME
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
scroll += calendarTable
|
||||
|
||||
|
@ -131,7 +133,6 @@ class Calendar(calendar: HTMLElement) : View(calendar) {
|
|||
private var scroll = listOf<HTMLElement>()
|
||||
|
||||
private fun scrollAllVerticalBy(pixel: Double, scrollBehavior: ScrollBehavior = ScrollBehavior.SMOOTH) {
|
||||
println("scroll ${scroll.size} elemenets")
|
||||
for (calendarTable in scroll) {
|
||||
calendarTable.scrollBy(ScrollToOptions(0.0, pixel, scrollBehavior))
|
||||
}
|
||||
|
|
|
@ -86,9 +86,14 @@ class CalendarBody(val calendar: Calendar, view: HTMLElement) : ViewCollection<C
|
|||
}
|
||||
|
||||
fun update(scroll: ScrollBehavior) {
|
||||
val currentTime = Date().let {
|
||||
it.getHours() * 60 + it.getMinutes()
|
||||
}
|
||||
val now = Date.now().toLong() - calendar.timeDifference
|
||||
val refDay = calendar.referenceDate / (1000 * 60 * 60 * 24)
|
||||
val nowDay = now / (1000 * 60 * 60 * 24)
|
||||
val d = nowDay - refDay
|
||||
val diff = (day - d).toInt()
|
||||
val date = Date(now)
|
||||
val currentTime = date.getHours() * 60 + date.getMinutes() + (diff * 60 * 24)
|
||||
|
||||
val rowTime = (currentTime / 15) * 15
|
||||
|
||||
var activeRow: CalendarRow? = null
|
||||
|
@ -116,7 +121,7 @@ class CalendarBody(val calendar: Calendar, view: HTMLElement) : ViewCollection<C
|
|||
|
||||
if (calendar.autoScroll && activeRow != null) {
|
||||
if (calendar.orientation == Calendar.Orientation.ROOM_TO_TIME) {
|
||||
calendar.scrollVerticalTo((activeRow.offsetTop).toDouble(), scroll)
|
||||
calendar.scrollVerticalTo((activeRow.offsetTop - 150).toDouble(), scroll)
|
||||
} else {
|
||||
calendar.scrollHorizontalTo((activeRow.offsetLeft - 100).toDouble(), scroll)
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@ import de.westermann.kwebview.iterator
|
|||
import de.kif.frontend.launch
|
||||
import de.kif.frontend.repository.RepositoryDelegate
|
||||
import de.kif.frontend.repository.ScheduleRepository
|
||||
import de.kif.frontend.views.overview.getByClassOrCreate
|
||||
import de.westermann.kwebview.*
|
||||
import de.westermann.kwebview.components.Body
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.HTMLSpanElement
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import kotlin.browser.document
|
||||
import kotlin.browser.window
|
||||
import kotlin.dom.appendText
|
||||
import kotlin.dom.isText
|
||||
|
@ -19,6 +22,7 @@ import kotlin.js.Date
|
|||
class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : View(view) {
|
||||
|
||||
private lateinit var mouseDelta: Point
|
||||
private var ignoreEditHover = false
|
||||
private var newCell: CalendarCell? = null
|
||||
|
||||
private var language by dataset.property("language")
|
||||
|
@ -43,9 +47,20 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie
|
|||
private var moveLockRoom: Long? = null
|
||||
private var moveLockTime: Int? = null
|
||||
|
||||
private val nameView = view.getByClassOrCreate<HTMLSpanElement>("calendar-entry-name")
|
||||
|
||||
private fun onMove(event: MouseEvent) {
|
||||
val position = event.toPoint() - mouseDelta
|
||||
|
||||
if (ignoreEditHover) {
|
||||
for (element in document.elementsFromPoint(position.x, position.y)) {
|
||||
if (element.classList.contains("calendar-edit")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
ignoreEditHover = false
|
||||
}
|
||||
|
||||
val cell = calendar.calendarCells.find {
|
||||
position in it.dimension
|
||||
}
|
||||
|
@ -245,13 +260,7 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie
|
|||
}
|
||||
}
|
||||
|
||||
for (element in html.childNodes) {
|
||||
if (element.isText) {
|
||||
html.removeChild(element)
|
||||
}
|
||||
}
|
||||
|
||||
html.appendText(workGroup.name)
|
||||
nameView.textContent = workGroup.name
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -268,6 +277,7 @@ class CalendarEntry(private val calendar: CalendarBody, view: HTMLElement) : Vie
|
|||
|
||||
entry.load(workGroup)
|
||||
entry.mouseDelta = Point.ZERO
|
||||
entry.ignoreEditHover = true
|
||||
entry.startDrag()
|
||||
|
||||
return entry
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
$border-radius: 0.2rem;
|
||||
$transitionTime: 150ms;
|
||||
|
||||
$mainFont: 'Raleway', Roboto, Arial, sans-serif;
|
||||
$menuFont: 'Bungee', 'Raleway', Roboto, Arial, sans-serif;
|
||||
$headFont: 'Montserrat', 'Raleway', Roboto, Arial, sans-serif;
|
||||
|
||||
@mixin no-select() {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
|
|
|
@ -213,6 +213,8 @@
|
|||
span:first-child {
|
||||
color: var(--text-primary-color);
|
||||
width: 6rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
|
@ -297,6 +299,13 @@
|
|||
@include no-select()
|
||||
}
|
||||
|
||||
.calendar-entry-name {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.calendar-table-box {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
|
@ -404,7 +413,7 @@
|
|||
left: 6rem;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
border-bottom: solid 1px $primary-color;
|
||||
border-bottom: solid 1px var(--primary-color);
|
||||
}
|
||||
|
||||
&.calendar-now::after {
|
||||
|
@ -414,8 +423,8 @@
|
|||
left: 6rem;
|
||||
transform: scale(1, 0.5) rotate(-45deg);
|
||||
transform-origin: bottom;
|
||||
border-bottom: solid 0.4rem $primary-color;
|
||||
border-right: solid 0.4rem $primary-color;
|
||||
border-bottom: solid 0.4rem var(--primary-color);
|
||||
border-right: solid 0.4rem var(--primary-color);
|
||||
border-top: solid 0.4rem transparent;
|
||||
border-left: solid 0.4rem transparent;
|
||||
margin-top: -0.55rem;
|
||||
|
@ -498,7 +507,7 @@
|
|||
top: 3rem;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
border-right: solid 1px $primary-color;
|
||||
border-right: solid 1px var(--primary-color);
|
||||
}
|
||||
|
||||
&.calendar-now::after {
|
||||
|
@ -508,8 +517,8 @@
|
|||
top: 3rem;
|
||||
transform: scale(0.5, 1) rotate(45deg);
|
||||
transform-origin: right;
|
||||
border-bottom: solid 0.4rem $primary-color;
|
||||
border-right: solid 0.4rem $primary-color;
|
||||
border-bottom: solid 0.4rem var(--primary-color);
|
||||
border-right: solid 0.4rem var(--primary-color);
|
||||
border-top: solid 0.4rem transparent;
|
||||
border-left: solid 0.4rem transparent;
|
||||
margin-top: -0.3rem;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
color: var(--text-primary-color);
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
font-family: "Bungee", sans-serif;
|
||||
font-family: $menuFont;
|
||||
font-weight: normal;
|
||||
font-size: 1.1rem;
|
||||
position: relative;
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
line-height: 2rem;
|
||||
padding: 0 1rem;
|
||||
font-weight: bold;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-family: $headFont;
|
||||
|
||||
&:empty::before {
|
||||
display: block;
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
margin-top: -0.5rem !important;
|
||||
bottom: 0 !important;
|
||||
}
|
||||
|
||||
.calendar-entry::after {
|
||||
content: none;
|
||||
}
|
||||
|
@ -65,6 +66,12 @@
|
|||
line-height: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
.calendar-cell {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wall-calendar {
|
||||
|
|
|
@ -15,7 +15,7 @@ body, html {
|
|||
color: var(--text-primary-color);
|
||||
background: var(--background-secondary-color);
|
||||
|
||||
font-family: 'Raleway', 'Montserrat', Roboto, Arial, sans-serif;
|
||||
font-family: $mainFont;
|
||||
font-weight: 500;
|
||||
|
||||
width: 100%;
|
||||
|
@ -30,6 +30,10 @@ body, html {
|
|||
}
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: $headFont;
|
||||
}
|
||||
|
||||
.no-select {
|
||||
@include no-select()
|
||||
}
|
||||
|
|
|
@ -13,13 +13,21 @@ import io.ktor.http.content.files
|
|||
import io.ktor.http.content.static
|
||||
import io.ktor.jackson.jackson
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.route
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.websocket.WebSockets
|
||||
import org.slf4j.event.Level
|
||||
import java.nio.file.Paths
|
||||
|
||||
val prefix = Configuration.Server.prefix
|
||||
|
||||
fun Application.main() {
|
||||
install(DefaultHeaders)
|
||||
install(CallLogging)
|
||||
install(CallLogging) {
|
||||
if (Configuration.Server.debug) {
|
||||
level = Level.INFO
|
||||
}
|
||||
}
|
||||
install(ConditionalHeaders)
|
||||
install(Compression)
|
||||
install(DataConversion)
|
||||
|
@ -42,47 +50,50 @@ fun Application.main() {
|
|||
security()
|
||||
|
||||
routing {
|
||||
static("/static") {
|
||||
files(Configuration.Path.webPath.toFile())
|
||||
}
|
||||
|
||||
static("/images") {
|
||||
files(Configuration.Path.uploadsPath.toFile())
|
||||
}
|
||||
|
||||
val srcFile = Paths.get("src")?.toFile()
|
||||
if (srcFile != null && srcFile.exists() && srcFile.isDirectory) {
|
||||
static("/src") {
|
||||
files("src")
|
||||
route(prefix) {
|
||||
static("/static") {
|
||||
files(Configuration.Path.webPath.toFile())
|
||||
}
|
||||
|
||||
static("/images") {
|
||||
files(Configuration.Path.uploadsPath.toFile())
|
||||
}
|
||||
|
||||
val srcFile = Paths.get("src")?.toFile()
|
||||
if (srcFile != null && srcFile.exists() && srcFile.isDirectory) {
|
||||
static("/src") {
|
||||
files("src")
|
||||
}
|
||||
}
|
||||
|
||||
// UI routes
|
||||
overview()
|
||||
calendar()
|
||||
login()
|
||||
account()
|
||||
|
||||
board()
|
||||
wall()
|
||||
|
||||
workGroup()
|
||||
track()
|
||||
room()
|
||||
user()
|
||||
|
||||
// RESTful routes
|
||||
authenticateApi()
|
||||
|
||||
roomApi()
|
||||
scheduleApi()
|
||||
trackApi()
|
||||
userApi()
|
||||
workGroupApi()
|
||||
constraintsApi()
|
||||
postApi()
|
||||
|
||||
// Web socket push notifications
|
||||
pushService()
|
||||
}
|
||||
|
||||
// UI routes
|
||||
overview()
|
||||
calendar()
|
||||
login()
|
||||
account()
|
||||
|
||||
board()
|
||||
wall()
|
||||
|
||||
workGroup()
|
||||
track()
|
||||
room()
|
||||
user()
|
||||
|
||||
// RESTful routes
|
||||
authenticateApi()
|
||||
|
||||
roomApi()
|
||||
scheduleApi()
|
||||
trackApi()
|
||||
userApi()
|
||||
workGroupApi()
|
||||
constraintsApi()
|
||||
postApi()
|
||||
|
||||
// Web socket push notifications
|
||||
pushService()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,15 @@ object Configuration {
|
|||
private object ServerSpec : ConfigSpec("server") {
|
||||
val host by required<String>()
|
||||
val port by required<Int>()
|
||||
val debug by required<Boolean>()
|
||||
val cPrefix by required<String>("prefix")
|
||||
}
|
||||
|
||||
object Server {
|
||||
val host by c(ServerSpec.host)
|
||||
val port by c(ServerSpec.port)
|
||||
val debug by c(ServerSpec.debug)
|
||||
val prefix by c(ServerSpec.cPrefix)
|
||||
}
|
||||
|
||||
private object PathSpec : ConfigSpec("path") {
|
||||
|
|
|
@ -31,7 +31,7 @@ fun main(args: Array<String>) {
|
|||
var password: String? = null
|
||||
while (password == null) {
|
||||
print("Password: ")
|
||||
password = System.console()?.readPassword()?.toString() ?: readLine()
|
||||
password = System.console()?.readPassword()?.joinToString("") ?: readLine()
|
||||
}
|
||||
|
||||
println("Create root user '$username' with pw '${"*".repeat(password.length)}'")
|
||||
|
|
|
@ -14,6 +14,7 @@ import io.ktor.sessions.*
|
|||
import io.ktor.util.hex
|
||||
import io.ktor.util.pipeline.PipelineContext
|
||||
import org.mindrot.jbcrypt.BCrypt
|
||||
import de.kif.backend.prefix
|
||||
|
||||
interface ErrorContext {
|
||||
suspend infix fun onFailure(block: suspend () -> Unit)
|
||||
|
@ -49,7 +50,7 @@ suspend inline fun PipelineContext<Unit, ApplicationCall>.authenticateOrRedirect
|
|||
block: (user: User) -> Unit
|
||||
) {
|
||||
authenticate(*permissions, block = block) onFailure {
|
||||
call.respondRedirect("/login?redirect=${call.request.path()}}")
|
||||
call.respondRedirect("$prefix/login?redirect=${call.request.path()}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +69,7 @@ data class PortalSession(val userId: Long) {
|
|||
|
||||
if (user == null) {
|
||||
call.sessions.clear<PortalSession>()
|
||||
call.respondRedirect("/login?onFailure")
|
||||
call.respondRedirect("$prefix/login?onFailure")
|
||||
throw IllegalAccessException()
|
||||
}
|
||||
|
||||
|
@ -93,7 +94,7 @@ fun Application.security() {
|
|||
userParamName = "username"
|
||||
passwordParamName = "password"
|
||||
challenge = FormAuthChallenge.Redirect { _ ->
|
||||
"/login?onFailure"
|
||||
"$prefix/login?onFailure"
|
||||
}
|
||||
validate { credential: UserPasswordCredential ->
|
||||
val user = UserRepository.find(credential.name) ?: return@validate null
|
||||
|
|
|
@ -26,6 +26,7 @@ import io.ktor.routing.post
|
|||
import io.ktor.util.toMap
|
||||
import kotlinx.html.*
|
||||
import mu.KotlinLogging
|
||||
import de.kif.backend.prefix
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
|
@ -40,7 +41,7 @@ fun Route.account() {
|
|||
br {}
|
||||
+"Du hast folgende Rechte: ${user.permissions.joinToString(", ") { it.germanInfo }}"
|
||||
br {}
|
||||
a("/logout") {
|
||||
a("$prefix/logout") {
|
||||
button(classes = "form-btn") {
|
||||
+"Ausloggen"
|
||||
}
|
||||
|
@ -69,25 +70,25 @@ fun Route.account() {
|
|||
|
||||
div("account-backup") {
|
||||
if (user.checkPermission(Permission.ROOM)) {
|
||||
a("/account/backup/rooms.json", classes = "form-btn") {
|
||||
a("$prefix/account/backup/rooms.json", classes = "form-btn") {
|
||||
attributes["download"] = "rooms-backup.json"
|
||||
+"Räume sichern"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.USER)) {
|
||||
a("/account/backup/users.json", classes = "form-btn") {
|
||||
a("$prefix/account/backup/users.json", classes = "form-btn") {
|
||||
attributes["download"] = "users-backup.json"
|
||||
+"Nutzer sichern"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.POST)) {
|
||||
a("/account/backup/posts.json", classes = "form-btn") {
|
||||
a("$prefix/account/backup/posts.json", classes = "form-btn") {
|
||||
attributes["download"] = "posts-backup.json"
|
||||
+"Beiträge sichern"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.WORK_GROUP)) {
|
||||
a("/account/backup/work-groups.json", classes = "form-btn") {
|
||||
a("$prefix/account/backup/work-groups.json", classes = "form-btn") {
|
||||
attributes["download"] = "work-groups-backup.json"
|
||||
+"Arbeitskreise sichern"
|
||||
}
|
||||
|
@ -97,14 +98,14 @@ fun Route.account() {
|
|||
user.checkPermission(Permission.ROOM) &&
|
||||
user.checkPermission(Permission.SCHEDULE)
|
||||
) {
|
||||
a("/account/backup/schedules.json", classes = "form-btn") {
|
||||
a("$prefix/account/backup/schedules.json", classes = "form-btn") {
|
||||
attributes["download"] = "schedules-backup.json"
|
||||
+"Zeitplan sichern"
|
||||
}
|
||||
}
|
||||
|
||||
if (user.checkPermission(Permission.ADMIN)) {
|
||||
a("/account/backup.json", classes = "form-btn") {
|
||||
a("$prefix/account/backup.json", classes = "form-btn") {
|
||||
attributes["download"] = "backup.json"
|
||||
+"Alles sichern" // TODO: richtiger Text?
|
||||
}
|
||||
|
@ -179,7 +180,7 @@ fun Route.account() {
|
|||
div("account-import-wiki") {
|
||||
span { +"Arbeitskreise aus dem KIF-Wiki importieren" }
|
||||
|
||||
form(action = "/account/import", method = FormMethod.post) {
|
||||
form(action = "$prefix/account/import", method = FormMethod.post) {
|
||||
for ((index, section) in wikiSections.withIndex()) {
|
||||
div("form-group") {
|
||||
label {
|
||||
|
@ -330,7 +331,7 @@ fun Route.account() {
|
|||
Backup.import(import)
|
||||
}
|
||||
|
||||
call.respondRedirect("/account")
|
||||
call.respondRedirect("$prefix/account")
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
@ -387,7 +388,7 @@ fun Route.account() {
|
|||
|
||||
logger.info { "Import $counter from ${importedWorkGroups.size} work groups!" }
|
||||
|
||||
call.respondRedirect("/account")
|
||||
call.respondRedirect("$prefix/account")
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.Configuration
|
||||
import de.kif.backend.repository.PostRepository
|
||||
import de.kif.backend.repository.RoomRepository
|
||||
import de.kif.backend.repository.ScheduleRepository
|
||||
import de.kif.backend.view.respondMain
|
||||
import de.kif.common.model.Schedule
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import kotlinx.css.CSSBuilder
|
||||
import kotlinx.css.Color
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.img
|
||||
import kotlinx.html.span
|
||||
import kotlinx.html.unsafe
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
@ -120,20 +121,19 @@ fun Route.board() {
|
|||
}
|
||||
div("board-content") {
|
||||
div("board-calendar calendar") {
|
||||
div("calendar-table") {
|
||||
renderCalendar(
|
||||
CalendarOrientation.ROOM_TO_TIME,
|
||||
day,
|
||||
min,
|
||||
max,
|
||||
rooms,
|
||||
schedules
|
||||
)
|
||||
}
|
||||
renderCalendar(
|
||||
CalendarOrientation.ROOM_TO_TIME,
|
||||
day,
|
||||
min,
|
||||
max,
|
||||
rooms,
|
||||
schedules
|
||||
)
|
||||
}
|
||||
div("board-twitter") {
|
||||
unsafe {
|
||||
raw("""
|
||||
raw(
|
||||
"""
|
||||
<a
|
||||
class="twitter-timeline"
|
||||
href="${Configuration.Twitter.timeline}"
|
||||
|
@ -145,7 +145,8 @@ fun Route.board() {
|
|||
data-dnt="true"
|
||||
>Twitter wall</a>
|
||||
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import kotlin.collections.component2
|
|||
import kotlin.collections.set
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import de.kif.backend.prefix
|
||||
|
||||
const val MINUTES_OF_DAY = 24 * 60
|
||||
|
||||
|
@ -53,7 +54,9 @@ private fun DIV.calendarCell(schedule: Schedule?) {
|
|||
attributes["data-time"] = schedule.time.toString()
|
||||
attributes["data-length"] = schedule.workGroup.length.toString()
|
||||
|
||||
+schedule.workGroup.name
|
||||
span("calendar-entry-name") {
|
||||
+schedule.workGroup.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,66 +73,75 @@ fun DIV.renderCalendar(
|
|||
val minutesOfDay = to - from
|
||||
|
||||
val now = Calendar.getInstance()
|
||||
val currentTime = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE)
|
||||
val d = (now.timeInMillis / (1000 * 60 * 60 * 24) -
|
||||
Configuration.Schedule.referenceDate.time / (1000 * 60 * 60 * 24)).toInt()
|
||||
val diff = day - d
|
||||
val currentTime = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE) + (diff * 60 * 24)
|
||||
|
||||
div("calendar-table-box ${orientation.name.toLowerCase().replace("_", "-")}") {
|
||||
div("calendar-header") {
|
||||
div("calendar-cell") {
|
||||
span {
|
||||
+"Raum"
|
||||
}
|
||||
}
|
||||
div("calendar-table") {
|
||||
attributes["data-day"] = day.toString()
|
||||
attributes["data-reference"] = Configuration.Schedule.referenceDate.time.toString()
|
||||
attributes["data-now"] = now.timeInMillis.toString()
|
||||
|
||||
for (room in rooms) {
|
||||
div("calendar-table-box ${orientation.name.toLowerCase().replace("_", "-")}") {
|
||||
div("calendar-header") {
|
||||
div("calendar-cell") {
|
||||
attributes["data-room"] = room.id.toString()
|
||||
|
||||
span {
|
||||
+room.name
|
||||
+"Raum"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("calendar-body") {
|
||||
for (i in 0 until minutesOfDay / CALENDAR_GRID_WIDTH) {
|
||||
val time = ((i * CALENDAR_GRID_WIDTH + from) % MINUTES_OF_DAY).let {
|
||||
if (it < 0) it + MINUTES_OF_DAY else it
|
||||
}
|
||||
val minutes = (time % 60).toString().padStart(2, '0')
|
||||
val hours = (time / 60).toString().padStart(2, '0')
|
||||
val timeString = "$hours:$minutes"
|
||||
|
||||
val start = i * CALENDAR_GRID_WIDTH + from
|
||||
val end = (i + 1) * CALENDAR_GRID_WIDTH + from - 1
|
||||
|
||||
var rowClass = "calendar-row"
|
||||
|
||||
if (currentTime in start..end) {
|
||||
rowClass += " calendar-now calendar-now-${currentTime - start}"
|
||||
}
|
||||
|
||||
div(rowClass) {
|
||||
attributes["data-time"] = start.toString()
|
||||
attributes["data-day"] = day.toString()
|
||||
|
||||
for (room in rooms) {
|
||||
div("calendar-cell") {
|
||||
if (time % gridLabelWidth == 0) {
|
||||
span {
|
||||
+timeString
|
||||
}
|
||||
attributes["data-room"] = room.id.toString()
|
||||
|
||||
span {
|
||||
+room.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("calendar-body") {
|
||||
for (i in 0 until minutesOfDay / CALENDAR_GRID_WIDTH) {
|
||||
val time = ((i * CALENDAR_GRID_WIDTH + from) % MINUTES_OF_DAY).let {
|
||||
if (it < 0) it + MINUTES_OF_DAY else it
|
||||
}
|
||||
val minutes = (time % 60).toString().padStart(2, '0')
|
||||
val hours = (time / 60).toString().padStart(2, '0')
|
||||
val timeString = "$hours:$minutes"
|
||||
|
||||
val start = i * CALENDAR_GRID_WIDTH + from
|
||||
val end = (i + 1) * CALENDAR_GRID_WIDTH + from - 1
|
||||
|
||||
var rowClass = "calendar-row"
|
||||
|
||||
if (currentTime in start..end) {
|
||||
rowClass += " calendar-now calendar-now-${currentTime - start}"
|
||||
}
|
||||
|
||||
div(rowClass) {
|
||||
attributes["data-time"] = start.toString()
|
||||
attributes["data-day"] = day.toString()
|
||||
|
||||
for (room in rooms) {
|
||||
div("calendar-cell") {
|
||||
attributes["data-room"] = room.id.toString()
|
||||
if (time % gridLabelWidth == 0) {
|
||||
span {
|
||||
+timeString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
title = room.name + " - " + timeString
|
||||
for (room in rooms) {
|
||||
div("calendar-cell") {
|
||||
attributes["data-room"] = room.id.toString()
|
||||
|
||||
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
|
||||
title = room.name + " - " + timeString
|
||||
|
||||
calendarCell(schedule)
|
||||
val schedule = (start..end).mapNotNull { schedules[room]?.get(it) }.firstOrNull()
|
||||
|
||||
calendarCell(schedule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +160,7 @@ fun Route.calendar() {
|
|||
val todayDay = todayDate.time / (1000 * 60 * 60 * 24)
|
||||
val day = todayDay - refDay
|
||||
|
||||
call.respondRedirect("/calendar/$day", false)
|
||||
call.respondRedirect("$prefix/calendar/$day", false)
|
||||
}
|
||||
|
||||
get("/calendar/{day}/rtt") {
|
||||
|
@ -159,7 +171,7 @@ fun Route.calendar() {
|
|||
path = "/"
|
||||
)
|
||||
val day = call.parameters["day"]?.toIntOrNull() ?: 0
|
||||
call.respondRedirect("/calendar/$day")
|
||||
call.respondRedirect("$prefix/calendar/$day")
|
||||
}
|
||||
|
||||
get("/calendar/{day}/ttr") {
|
||||
|
@ -170,7 +182,7 @@ fun Route.calendar() {
|
|||
path = "/"
|
||||
)
|
||||
val day = call.parameters["day"]?.toIntOrNull() ?: 0
|
||||
call.respondRedirect("/calendar/$day")
|
||||
call.respondRedirect("$prefix/calendar/$day")
|
||||
}
|
||||
|
||||
get("/calendar/{day}") {
|
||||
|
@ -237,20 +249,20 @@ fun Route.calendar() {
|
|||
div("header") {
|
||||
div("header-left") {
|
||||
if (editable || day - 1 > range.start) {
|
||||
a("/calendar/${day - 1}") { i("material-icons") { +"chevron_left" } }
|
||||
a("$prefix/calendar/${day - 1}") { i("material-icons") { +"chevron_left" } }
|
||||
}
|
||||
span {
|
||||
+dateString
|
||||
}
|
||||
if (editable || day + 1 < range.endInclusive) {
|
||||
a("/calendar/${day + 1}") { i("material-icons") { +"chevron_right" } }
|
||||
a("$prefix/calendar/${day + 1}") { i("material-icons") { +"chevron_right" } }
|
||||
}
|
||||
}
|
||||
div("header-right") {
|
||||
a("/calendar/$day/rtt", classes = "form-btn") {
|
||||
a("$prefix/calendar/$day/rtt", classes = "form-btn") {
|
||||
+"Vertikal"
|
||||
}
|
||||
a("/calendar/$day/ttr", classes = "form-btn") {
|
||||
a("$prefix/calendar/$day/ttr", classes = "form-btn") {
|
||||
+"Horizontal"
|
||||
}
|
||||
if (editable) {
|
||||
|
@ -267,19 +279,16 @@ fun Route.calendar() {
|
|||
}
|
||||
|
||||
div("calendar") {
|
||||
attributes["data-day"] = day.toString()
|
||||
attributes["data-editable"] = editable.toString()
|
||||
|
||||
div("calendar-table") {
|
||||
renderCalendar(
|
||||
orientation,
|
||||
day,
|
||||
min,
|
||||
max,
|
||||
rooms,
|
||||
schedules
|
||||
)
|
||||
}
|
||||
renderCalendar(
|
||||
orientation,
|
||||
day,
|
||||
min,
|
||||
max,
|
||||
rooms,
|
||||
schedules
|
||||
)
|
||||
|
||||
if (editable) {
|
||||
div("calendar-edit") {
|
||||
|
|
|
@ -16,6 +16,7 @@ import io.ktor.sessions.get
|
|||
import io.ktor.sessions.sessions
|
||||
import io.ktor.sessions.set
|
||||
import kotlinx.html.*
|
||||
import de.kif.backend.prefix
|
||||
|
||||
fun Route.login() {
|
||||
route("login") {
|
||||
|
@ -24,11 +25,12 @@ fun Route.login() {
|
|||
val principal = call.principal<UserPrinciple>() ?: return@post
|
||||
if (principal.user.id == null) return@post
|
||||
call.sessions.set(PortalSession(principal.user.id))
|
||||
call.respondRedirect(call.parameters["redirect"] ?: "/")
|
||||
call.respondRedirect(call.parameters["redirect"] ?: "$prefix/")
|
||||
}
|
||||
}
|
||||
|
||||
get {
|
||||
val redirect = call.parameters["redirect"] ?: "$prefix/"
|
||||
val needLogin = call.sessions.get<PortalSession>() == null
|
||||
if (needLogin) {
|
||||
respondMain {
|
||||
|
@ -36,7 +38,7 @@ fun Route.login() {
|
|||
div {
|
||||
div {
|
||||
br { }
|
||||
form("/login", method = FormMethod.post) {
|
||||
form("$prefix/login?redirect=$redirect", method = FormMethod.post) {
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "username"
|
||||
|
@ -68,7 +70,7 @@ fun Route.login() {
|
|||
name = "redirect",
|
||||
type = InputType.hidden
|
||||
) {
|
||||
value = call.parameters["redirect"] ?: "/"
|
||||
value = redirect
|
||||
}
|
||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
||||
+"Einloggen"
|
||||
|
@ -86,13 +88,13 @@ fun Route.login() {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
call.respondRedirect(call.parameters["redirect"] ?: "/")
|
||||
call.respondRedirect(redirect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get("logout") {
|
||||
call.sessions.clear<PortalSession>()
|
||||
call.respondRedirect("/")
|
||||
call.respondRedirect("$prefix/")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import io.ktor.routing.get
|
|||
import io.ktor.routing.post
|
||||
import kotlinx.html.*
|
||||
import java.io.File
|
||||
import de.kif.backend.prefix
|
||||
|
||||
fun DIV.createPost(post: Post, editable: Boolean = false, additionalClasses: String = "") {
|
||||
var classes = "post"
|
||||
|
@ -39,11 +40,11 @@ fun DIV.createPost(post: Post, editable: Boolean = false, additionalClasses: Str
|
|||
attributes["data-id"] = post.id.toString()
|
||||
attributes["data-pinned"] = post.pinned.toString()
|
||||
|
||||
a("/p/${post.url}", classes = "post-name") {
|
||||
a("$prefix/p/${post.url}", classes = "post-name") {
|
||||
+post.name
|
||||
}
|
||||
if (editable) {
|
||||
a("/post/${post.id}", classes = "post-edit") {
|
||||
a("$prefix/post/${post.id}", classes = "post-edit") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
|
@ -269,7 +270,7 @@ fun Route.overview() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/") {
|
||||
a("$prefix/") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
|
@ -279,7 +280,7 @@ fun Route.overview() {
|
|||
}
|
||||
}
|
||||
}
|
||||
a("/post/${editPost.id}/delete") {
|
||||
a("$prefix/post/${editPost.id}/delete") {
|
||||
button(classes = "form-btn btn-danger") {
|
||||
+"Löschen"
|
||||
}
|
||||
|
@ -360,7 +361,7 @@ fun Route.overview() {
|
|||
|
||||
PostRepository.update(post)
|
||||
|
||||
call.respondRedirect("/")
|
||||
call.respondRedirect("$prefix/")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -474,7 +475,7 @@ fun Route.overview() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/") {
|
||||
a("$prefix/") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
|
@ -536,7 +537,7 @@ fun Route.overview() {
|
|||
|
||||
PostRepository.create(post)
|
||||
|
||||
call.respondRedirect("/")
|
||||
call.respondRedirect("$prefix/")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -546,7 +547,7 @@ fun Route.overview() {
|
|||
|
||||
PostRepository.delete(postId)
|
||||
|
||||
call.respondRedirect("/")
|
||||
call.respondRedirect("$prefix/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import kotlinx.html.*
|
|||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.set
|
||||
import de.kif.backend.prefix
|
||||
|
||||
fun Route.room() {
|
||||
|
||||
|
@ -38,7 +39,7 @@ fun Route.room() {
|
|||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/room/new") {
|
||||
a("$prefix/room/new") {
|
||||
button(classes = "form-btn btn-primary") {
|
||||
+"Raum hinzufügen"
|
||||
}
|
||||
|
@ -92,7 +93,7 @@ fun Route.room() {
|
|||
}
|
||||
}
|
||||
td(classes = "action") {
|
||||
a("/room/${u.id}") {
|
||||
a("$prefix/room/${u.id}") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
|
@ -223,7 +224,7 @@ fun Route.room() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/room") {
|
||||
a("$prefix/room") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
|
@ -233,7 +234,7 @@ fun Route.room() {
|
|||
}
|
||||
}
|
||||
}
|
||||
a("/room/${editRoom.id}/delete") {
|
||||
a("$prefix/room/${editRoom.id}/delete") {
|
||||
button(classes = "form-btn btn-danger") {
|
||||
+"Löschen"
|
||||
}
|
||||
|
@ -261,7 +262,7 @@ fun Route.room() {
|
|||
|
||||
RoomRepository.update(room)
|
||||
|
||||
call.respondRedirect("/rooms")
|
||||
call.respondRedirect("$prefix/rooms")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -382,7 +383,7 @@ fun Route.room() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/room") {
|
||||
a("$prefix/room") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
|
@ -415,7 +416,7 @@ fun Route.room() {
|
|||
|
||||
RoomRepository.create(room)
|
||||
|
||||
call.respondRedirect("/rooms")
|
||||
call.respondRedirect("$prefix/rooms")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,7 +426,7 @@ fun Route.room() {
|
|||
|
||||
RoomRepository.delete(roomId)
|
||||
|
||||
call.respondRedirect("/rooms")
|
||||
call.respondRedirect("$prefix/rooms")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.repository.TrackRepository
|
||||
import de.kif.backend.view.MainTemplate
|
||||
|
@ -25,6 +24,7 @@ import kotlinx.css.Display
|
|||
import kotlinx.html.*
|
||||
import kotlin.collections.set
|
||||
import kotlin.random.Random
|
||||
import de.kif.backend.prefix
|
||||
|
||||
fun DIV.colorPicker(color: Color?) {
|
||||
val colorString = color?.toString() ?: Color(
|
||||
|
@ -95,7 +95,7 @@ fun Route.track() {
|
|||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/track/new") {
|
||||
a("$prefix/track/new") {
|
||||
button(classes = "form-btn btn-primary") {
|
||||
+"Track hinzufügen"
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ fun Route.track() {
|
|||
+u.color.toString()
|
||||
}
|
||||
td(classes = "action") {
|
||||
a("/track/${u.id}") {
|
||||
a("$prefix/track/${u.id}") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ fun Route.track() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/track") {
|
||||
a("$prefix/track") {
|
||||
button(classes = "form-btn") {
|
||||
+"Cancel"
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ fun Route.track() {
|
|||
}
|
||||
}
|
||||
}
|
||||
a("/track/${editTrack.id}/delete") {
|
||||
a("$prefix/track/${editTrack.id}/delete") {
|
||||
button(classes = "form-btn btn-danger") {
|
||||
+"Löschen"
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ fun Route.track() {
|
|||
|
||||
TrackRepository.update(editTrack)
|
||||
|
||||
call.respondRedirect("/tracks")
|
||||
call.respondRedirect("$prefix/tracks")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,7 +232,7 @@ fun Route.track() {
|
|||
colorPicker(null)
|
||||
}
|
||||
div("form-group") {
|
||||
a("/track") {
|
||||
a("$prefix/track") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ fun Route.track() {
|
|||
|
||||
TrackRepository.create(track)
|
||||
|
||||
call.respondRedirect("/tracks")
|
||||
call.respondRedirect("$prefix/tracks")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ fun Route.track() {
|
|||
|
||||
TrackRepository.delete(trackId)
|
||||
|
||||
call.respondRedirect("/tracks")
|
||||
call.respondRedirect("$prefix/tracks")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.checkPassword
|
||||
import de.kif.backend.hashPassword
|
||||
import de.kif.backend.prefix
|
||||
import de.kif.backend.repository.UserRepository
|
||||
import de.kif.backend.view.MainTemplate
|
||||
import de.kif.backend.view.MenuTemplate
|
||||
import de.kif.backend.view.TableTemplate
|
||||
import de.kif.backend.view.respondMain
|
||||
import de.kif.common.Search
|
||||
|
@ -13,7 +12,6 @@ import de.kif.common.model.Permission
|
|||
import de.kif.common.model.User
|
||||
import io.ktor.application.call
|
||||
import io.ktor.html.insert
|
||||
import io.ktor.html.respondHtmlTemplate
|
||||
import io.ktor.request.receiveParameters
|
||||
import io.ktor.response.respondRedirect
|
||||
import io.ktor.routing.Route
|
||||
|
@ -38,7 +36,7 @@ fun Route.user() {
|
|||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/user/new") {
|
||||
a("$prefix/user/new") {
|
||||
button(classes = "form-btn btn-primary") {
|
||||
+"Nutzer hinzufügen"
|
||||
}
|
||||
|
@ -71,7 +69,7 @@ fun Route.user() {
|
|||
+u.permissions.joinToString(", ") { it.toString().toLowerCase() }
|
||||
}
|
||||
td(classes = "action") {
|
||||
a("/user/${u.id}") {
|
||||
a("$prefix/user/${u.id}") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
|
@ -131,17 +129,40 @@ fun Route.user() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/user") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
label {
|
||||
htmlFor = "current-password"
|
||||
+"Passwort ändern"
|
||||
}
|
||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
||||
+"Speichern"
|
||||
input(
|
||||
name = "current-password",
|
||||
classes = "form-control",
|
||||
type = InputType.password
|
||||
) {
|
||||
id = "current-password"
|
||||
placeholder = "Aktuelles Passwort"
|
||||
value = ""
|
||||
}
|
||||
input(
|
||||
name = "new-password-1",
|
||||
classes = "form-control",
|
||||
type = InputType.password
|
||||
) {
|
||||
id = "new-password-1"
|
||||
placeholder = "Neues Passwort"
|
||||
value = ""
|
||||
}
|
||||
input(
|
||||
name = "new-password-2",
|
||||
classes = "form-control",
|
||||
type = InputType.password
|
||||
) {
|
||||
id = "new-password-2"
|
||||
placeholder = "Neues Passwort wiederholen"
|
||||
value = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
a("/user/${editUser.id}/delete") {
|
||||
a("$prefix/user/${editUser.id}/delete") {
|
||||
button(classes = "form-btn btn-danger") {
|
||||
+"Löschen"
|
||||
}
|
||||
|
@ -162,6 +183,16 @@ fun Route.user() {
|
|||
|
||||
params["username"]?.let { editUser = editUser.copy(username = it) }
|
||||
|
||||
val currentPassword = params["current-password"]
|
||||
val newPassword1 = params["new-password-1"]
|
||||
val newPassword2 = params["new-password-2"]
|
||||
|
||||
if (currentPassword != null && newPassword1 != null && newPassword2 != null && currentPassword.isNotBlank() && newPassword1.isNotBlank() && newPassword2.isNotBlank()) {
|
||||
if (checkPassword(currentPassword, editUser.password) && newPassword1 == newPassword2) {
|
||||
editUser = editUser.copy(password = hashPassword(newPassword1))
|
||||
}
|
||||
}
|
||||
|
||||
val permissions = Permission.values().filter { permission ->
|
||||
val name = permission.toString().toLowerCase()
|
||||
user.checkPermission(permission) && params["permission-$name"] == "on"
|
||||
|
@ -170,7 +201,7 @@ fun Route.user() {
|
|||
|
||||
UserRepository.update(user)
|
||||
|
||||
call.respondRedirect("/users")
|
||||
call.respondRedirect("$prefix/users")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,7 +266,7 @@ fun Route.user() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/user") {
|
||||
a("$prefix/user") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
|
@ -270,7 +301,7 @@ fun Route.user() {
|
|||
|
||||
UserRepository.create(newUser)
|
||||
|
||||
call.respondRedirect("/users")
|
||||
call.respondRedirect("$prefix/users")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,7 +316,7 @@ fun Route.user() {
|
|||
UserRepository.delete(userId)
|
||||
}
|
||||
|
||||
call.respondRedirect("/users")
|
||||
call.respondRedirect("$prefix/users")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,12 @@ data class WallData(
|
|||
|
||||
suspend fun genWallData(day: Int): WallData {
|
||||
val list = ScheduleRepository.getByDay(day)
|
||||
val schedules = RoomRepository.all().associateWith { emptyMap<Int, Schedule>() } + list.groupBy { it.room }.mapValues { (_, it) ->
|
||||
it.associateBy {
|
||||
it.time
|
||||
val schedules =
|
||||
RoomRepository.all().associateWith { emptyMap<Int, Schedule>() } + list.groupBy { it.room }.mapValues { (_, it) ->
|
||||
it.associateBy {
|
||||
it.time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var max = 0
|
||||
var min = 24 * 60
|
||||
|
@ -59,16 +60,14 @@ fun Route.wall() {
|
|||
for (day in days) {
|
||||
div("wall-box") {
|
||||
div("wall-calendar calendar") {
|
||||
div("calendar-table") {
|
||||
renderCalendar(
|
||||
CalendarOrientation.TIME_TO_ROOM,
|
||||
day.number,
|
||||
min,
|
||||
max,
|
||||
day.schedules.keys.toList().sortedBy { it.id },
|
||||
day.schedules
|
||||
)
|
||||
}
|
||||
renderCalendar(
|
||||
CalendarOrientation.TIME_TO_ROOM,
|
||||
day.number,
|
||||
min,
|
||||
max,
|
||||
day.schedules.keys.toList().sortedBy { it.id },
|
||||
day.schedules
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import kotlinx.css.CSSBuilder
|
|||
import kotlinx.css.Display
|
||||
import kotlinx.html.*
|
||||
import kotlin.collections.set
|
||||
import de.kif.backend.prefix
|
||||
|
||||
private const val separator = "###"
|
||||
|
||||
|
@ -33,12 +34,12 @@ fun Route.workGroup() {
|
|||
searchValue = search
|
||||
|
||||
action {
|
||||
a("/tracks") {
|
||||
a("$prefix/tracks") {
|
||||
button(classes = "form-btn") {
|
||||
+"Tracks bearbeiten"
|
||||
}
|
||||
}
|
||||
a("/workgroup/new") {
|
||||
a("$prefix/workgroup/new") {
|
||||
button(classes = "form-btn btn-primary") {
|
||||
+"Arbeitskreis hinzufügen"
|
||||
}
|
||||
|
@ -132,7 +133,7 @@ fun Route.workGroup() {
|
|||
}
|
||||
}
|
||||
td(classes = "action") {
|
||||
a("/workgroup/${u.id}") {
|
||||
a("$prefix/workgroup/${u.id}") {
|
||||
i("material-icons") { +"edit" }
|
||||
}
|
||||
}
|
||||
|
@ -400,6 +401,8 @@ fun Route.workGroup() {
|
|||
|
||||
min = "-1337"
|
||||
max = "1337"
|
||||
|
||||
placeholder = "Tag"
|
||||
}
|
||||
}
|
||||
ConstraintType.NotOnDay -> {
|
||||
|
@ -415,6 +418,8 @@ fun Route.workGroup() {
|
|||
|
||||
min = "-1337"
|
||||
max = "1337"
|
||||
|
||||
placeholder = "Tag"
|
||||
}
|
||||
}
|
||||
ConstraintType.OnlyBeforeTime -> {
|
||||
|
@ -426,7 +431,8 @@ fun Route.workGroup() {
|
|||
classes = "form-control"
|
||||
) {
|
||||
value = constraint.day?.toString() ?: ""
|
||||
placeholder = "day"
|
||||
|
||||
placeholder = "Tag (optional)"
|
||||
}
|
||||
input(
|
||||
name = "constraint-only-before-time-$index",
|
||||
|
@ -437,6 +443,8 @@ fun Route.workGroup() {
|
|||
|
||||
min = "-1337"
|
||||
max = "133700"
|
||||
|
||||
placeholder = "Minuten"
|
||||
}
|
||||
}
|
||||
ConstraintType.OnlyAfterTime -> {
|
||||
|
@ -448,7 +456,8 @@ fun Route.workGroup() {
|
|||
classes = "form-control"
|
||||
) {
|
||||
value = constraint.day?.toString() ?: ""
|
||||
placeholder = "day"
|
||||
|
||||
placeholder = "Tag (optional)"
|
||||
}
|
||||
input(
|
||||
name = "constraint-only-after-time-$index",
|
||||
|
@ -459,6 +468,8 @@ fun Route.workGroup() {
|
|||
|
||||
min = "-1337"
|
||||
max = "133700"
|
||||
|
||||
placeholder = "Minuten"
|
||||
}
|
||||
}
|
||||
ConstraintType.NotAtSameTime -> {
|
||||
|
@ -500,7 +511,7 @@ fun Route.workGroup() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/workgroup") {
|
||||
a("$prefix/workgroup") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
|
@ -510,7 +521,7 @@ fun Route.workGroup() {
|
|||
}
|
||||
}
|
||||
}
|
||||
a("/workgroup/${editWorkGroup.id}/delete") {
|
||||
a("$prefix/workgroup/${editWorkGroup.id}/delete") {
|
||||
button(classes = "form-btn btn-danger") {
|
||||
+"Löschen"
|
||||
}
|
||||
|
@ -558,7 +569,7 @@ fun Route.workGroup() {
|
|||
|
||||
WorkGroupRepository.update(editWorkGroup)
|
||||
|
||||
call.respondRedirect("/workgroups")
|
||||
call.respondRedirect("$prefix/workgroups")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -796,7 +807,7 @@ fun Route.workGroup() {
|
|||
}
|
||||
|
||||
div("form-group") {
|
||||
a("/workgroup") {
|
||||
a("$prefix/workgroup") {
|
||||
button(classes = "form-btn") {
|
||||
+"Abbrechen"
|
||||
}
|
||||
|
@ -856,7 +867,7 @@ fun Route.workGroup() {
|
|||
|
||||
WorkGroupRepository.create(workGroup)
|
||||
|
||||
call.respondRedirect("/workgroups")
|
||||
call.respondRedirect("$prefix/workgroups")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -866,7 +877,7 @@ fun Route.workGroup() {
|
|||
|
||||
WorkGroupRepository.delete(workGroupId)
|
||||
|
||||
call.respondRedirect("/workgroups")
|
||||
call.respondRedirect("$prefix/workgroups")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -882,14 +893,14 @@ private fun parseConstraintParam(params: Map<String, String?>) = params.map { (k
|
|||
}
|
||||
key.startsWith("constraint-only-after-time") -> {
|
||||
if ("day" in key) {
|
||||
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyAfterTime, day = it) }
|
||||
Constraint(ConstraintType.OnlyAfterTime, day = value?.toIntOrNull())
|
||||
} else {
|
||||
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyAfterTime, time = it) }
|
||||
}
|
||||
}
|
||||
key.startsWith("constraint-only-before-time") -> {
|
||||
if ("day" in key) {
|
||||
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyBeforeTime, day = it) }
|
||||
Constraint(ConstraintType.OnlyBeforeTime, day = value?.toIntOrNull())
|
||||
} else {
|
||||
value?.toIntOrNull()?.let { Constraint(ConstraintType.OnlyBeforeTime, time = it) }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package de.kif.backend.util
|
||||
|
||||
import de.kif.backend.prefix
|
||||
import de.kif.backend.repository.*
|
||||
import de.kif.common.*
|
||||
import de.kif.common.RepositoryType
|
||||
|
@ -27,7 +28,7 @@ object PushService {
|
|||
}
|
||||
|
||||
fun Route.pushService() {
|
||||
webSocket {
|
||||
webSocket("/") {
|
||||
PushService.clients += this
|
||||
|
||||
try {
|
||||
|
|
|
@ -2,10 +2,14 @@ package de.kif.backend.view
|
|||
|
||||
import de.kif.backend.PortalSession
|
||||
import de.kif.backend.Resources
|
||||
import de.kif.backend.prefix
|
||||
import de.kif.common.model.User
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.call
|
||||
import io.ktor.html.*
|
||||
import io.ktor.html.Placeholder
|
||||
import io.ktor.html.Template
|
||||
import io.ktor.html.insert
|
||||
import io.ktor.html.respondHtmlTemplate
|
||||
import io.ktor.request.path
|
||||
import io.ktor.request.uri
|
||||
import io.ktor.response.respondRedirect
|
||||
|
@ -30,36 +34,41 @@ class MainTemplate(
|
|||
|
||||
title("KIF Portal")
|
||||
|
||||
link(href = "/static/external/material-icons.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
link(href = "/static/external/font/Montserrat.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
link(href = "$prefix/static/external/material-icons.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
link(
|
||||
href = "$prefix/static/external/font/Montserrat.css",
|
||||
type = LinkType.textCss,
|
||||
rel = LinkRel.stylesheet
|
||||
)
|
||||
link(
|
||||
href = "https://fonts.googleapis.com/css?family=Bungee|Oswald|Raleway",
|
||||
type = LinkType.textCss,
|
||||
rel = LinkRel.stylesheet
|
||||
)
|
||||
link(href = "/static/style/style.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
link(href = "$prefix/static/style/style.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
|
||||
when (theme) {
|
||||
Theme.LIGHT -> {
|
||||
// Ignore
|
||||
}
|
||||
Theme.DARK -> {
|
||||
link(href = "/static/style/dark.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
link(href = "$prefix/static/style/dark.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
}
|
||||
Theme.PRINCESS -> {
|
||||
link(href = "/static/style/princess.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
link(href = "$prefix/static/style/princess.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
}
|
||||
Theme.BRETT -> {
|
||||
link(href = "/static/style/board.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
link(href = "$prefix/static/style/board.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
}
|
||||
}
|
||||
|
||||
script(src = "/static/require.min.js") {}
|
||||
script(src = "$prefix/static/require.min.js") {}
|
||||
|
||||
script {
|
||||
unsafe {
|
||||
+"require.config({baseUrl: '/static'});\n"
|
||||
+("require([${Resources.jsModules}]);\n")
|
||||
+"let prefix = '$prefix';\n"
|
||||
+"require.config({baseUrl: '$prefix/static'});\n"
|
||||
+"require([${Resources.jsModules}]);\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,10 +105,10 @@ class MainTemplate(
|
|||
}
|
||||
|
||||
enum class Theme(val text: String, val display: Boolean, val dark: Boolean, val primaryColor: String) {
|
||||
LIGHT("Hell",true, false, "#B11D33"),
|
||||
DARK("Dunkel",true, true, "#ef5350"),
|
||||
PRINCESS("Barbie",true, false, "#B11D33"),
|
||||
BRETT("Brett",false, false, "#B11D33");
|
||||
LIGHT("Hell", true, false, "#B11D33"),
|
||||
DARK("Dunkel", true, true, "#ef5350"),
|
||||
PRINCESS("Barbie", true, false, "#B11D33"),
|
||||
BRETT("Brett", false, false, "#B11D33");
|
||||
|
||||
companion object {
|
||||
private val lookup = values().toList().associateBy { it.name }
|
||||
|
@ -118,7 +127,7 @@ suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
|
|||
body: MainTemplate.(Theme) -> Unit
|
||||
) {
|
||||
val param = call.request.queryParameters["theme"]
|
||||
val url = call.request.uri.substring(1)
|
||||
val url = call.request.uri.substring(1 + prefix.length)
|
||||
val user = call.sessions.get<PortalSession>()?.getUser(call)
|
||||
|
||||
if (param != null) {
|
||||
|
@ -126,7 +135,7 @@ suspend fun PipelineContext<Unit, ApplicationCall>.respondMain(
|
|||
name = "theme",
|
||||
value = Theme.lookup(param).name,
|
||||
maxAge = Int.MAX_VALUE,
|
||||
path = "/"
|
||||
path = "$prefix/"
|
||||
)
|
||||
call.respondRedirect(call.request.path())
|
||||
} else {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package de.kif.backend.view
|
||||
|
||||
import de.kif.backend.Configuration
|
||||
import de.kif.common.model.Permission
|
||||
import de.kif.common.model.User
|
||||
import io.ktor.html.Template
|
||||
import kotlinx.html.*
|
||||
import de.kif.backend.prefix
|
||||
|
||||
class MenuTemplate(
|
||||
private val url: String,
|
||||
|
@ -16,10 +18,10 @@ class MenuTemplate(
|
|||
nav("menu") {
|
||||
div("container") {
|
||||
div("menu-left") {
|
||||
a("/", classes = if (tab == null) "active" else null) {
|
||||
a("$prefix/", classes = if (tab == null) "active" else null) {
|
||||
+"Neuigkeiten"
|
||||
}
|
||||
a("/calendar", classes = if (tab == Tab.CALENDAR) "active" else null) {
|
||||
a("$prefix/calendar", classes = if (tab == Tab.CALENDAR) "active" else null) {
|
||||
+"Zeitplan"
|
||||
}
|
||||
}
|
||||
|
@ -30,26 +32,26 @@ class MenuTemplate(
|
|||
val user = user
|
||||
div("menu-content") {
|
||||
if (user == null) {
|
||||
a("/account", classes = if (tab == Tab.LOGIN) "active" else null) {
|
||||
a("$prefix/account", classes = if (tab == Tab.LOGIN) "active" else null) {
|
||||
+"Einloggen"
|
||||
}
|
||||
} else {
|
||||
if (user.checkPermission(Permission.WORK_GROUP)) {
|
||||
a("/workgroups", classes = if (tab == Tab.WORK_GROUP) "active" else null) {
|
||||
a("$prefix/workgroups", classes = if (tab == Tab.WORK_GROUP) "active" else null) {
|
||||
+"Arbeitskreise"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.ROOM)) {
|
||||
a("/rooms", classes = if (tab == Tab.ROOM) "active" else null) {
|
||||
a("$prefix/rooms", classes = if (tab == Tab.ROOM) "active" else null) {
|
||||
+"Räume"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.USER)) {
|
||||
a("/users", classes = if (tab == Tab.USER) "active" else null) {
|
||||
a("$prefix/users", classes = if (tab == Tab.USER) "active" else null) {
|
||||
+"Nutzer"
|
||||
}
|
||||
}
|
||||
a("/account", classes = if (tab == Tab.ACCOUNT) "active" else null) {
|
||||
a("$prefix/account", classes = if (tab == Tab.ACCOUNT) "active" else null) {
|
||||
+user.username
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,78 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KIF Portal</title>
|
||||
<link href="/static/external/material-icons.css" rel="Stylesheet" type="text/css">
|
||||
<link href="/static/external/font/Montserrat.css" rel="Stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Bungee|Oswald" rel="Stylesheet" type="text/css">
|
||||
<link href="/static/style/style.css" rel="Stylesheet" type="text/css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--background-secondary-color: #f5f5f5;
|
||||
--text-primary-color: #333;
|
||||
}
|
||||
|
||||
.main-error {
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
text-align: center;
|
||||
margin-top: -2rem;
|
||||
position: absolute;
|
||||
top: 48%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-error span {
|
||||
display: block;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.main-error span:first-child {
|
||||
font-size: 1.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
body, html {
|
||||
color: var(--text-primary-color);
|
||||
background: var(--background-secondary-color);
|
||||
|
||||
font-family: 'Raleway', 'Montserrat', Roboto, Arial, sans-serif;
|
||||
font-weight: 500;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
width: 720px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
width: 960px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
width: 1140px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
@ -4,10 +4,78 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KIF Portal</title>
|
||||
<link href="/static/external/material-icons.css" rel="Stylesheet" type="text/css">
|
||||
<link href="/static/external/font/Montserrat.css" rel="Stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Bungee|Oswald" rel="Stylesheet" type="text/css">
|
||||
<link href="/static/style/style.css" rel="Stylesheet" type="text/css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--background-secondary-color: #f5f5f5;
|
||||
--text-primary-color: #333;
|
||||
}
|
||||
|
||||
.main-error {
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
text-align: center;
|
||||
margin-top: -2rem;
|
||||
position: absolute;
|
||||
top: 48%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-error span {
|
||||
display: block;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.main-error span:first-child {
|
||||
font-size: 1.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
body, html {
|
||||
color: var(--text-primary-color);
|
||||
background: var(--background-secondary-color);
|
||||
|
||||
font-family: 'Raleway', 'Montserrat', Roboto, Arial, sans-serif;
|
||||
font-weight: 500;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
width: 720px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
width: 960px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
width: 1140px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
@ -4,10 +4,78 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>KIF Portal</title>
|
||||
<link href="/static/external/material-icons.css" rel="Stylesheet" type="text/css">
|
||||
<link href="/static/external/font/Montserrat.css" rel="Stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Bungee|Oswald" rel="Stylesheet" type="text/css">
|
||||
<link href="/static/style/style.css" rel="Stylesheet" type="text/css">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--background-secondary-color: #f5f5f5;
|
||||
--text-primary-color: #333;
|
||||
}
|
||||
|
||||
.main-error {
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
text-align: center;
|
||||
margin-top: -2rem;
|
||||
position: absolute;
|
||||
top: 48%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.main-error span {
|
||||
display: block;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.main-error span:first-child {
|
||||
font-size: 1.5rem;
|
||||
padding-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
body, html {
|
||||
color: var(--text-primary-color);
|
||||
background: var(--background-secondary-color);
|
||||
|
||||
font-family: 'Raleway', 'Montserrat', Roboto, Arial, sans-serif;
|
||||
font-weight: 500;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
width: 720px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
width: 960px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
width: 1140px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
[server]
|
||||
host = "localhost"
|
||||
port = 8080
|
||||
debug = false
|
||||
prefix = ""
|
||||
|
||||
[path]
|
||||
web = "web"
|
||||
|
|
Loading…
Reference in a new issue