Add backup
This commit is contained in:
parent
67f24adfdf
commit
c6620d3395
|
@ -12,6 +12,7 @@ data class Post(
|
|||
val url: String,
|
||||
val image: String?,
|
||||
val pinned: Boolean,
|
||||
val hideOnProjector: Boolean,
|
||||
override val createdAt: Long = 0,
|
||||
override val updateAt: Long = 0
|
||||
) : Model {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package de.kif.frontend.views
|
||||
|
||||
import de.kif.frontend.iterator
|
||||
import de.kif.frontend.launch
|
||||
import de.kif.frontend.repository.WorkGroupRepository
|
||||
import de.westermann.kobserve.event.EventListener
|
||||
|
@ -14,16 +15,12 @@ fun initWorkGroupConstraints() {
|
|||
var index = 10000
|
||||
|
||||
val constraints =
|
||||
ListView.wrap<View>(document.getElementsByClassName("work-group-constraints")[0] as HTMLElement)
|
||||
document.getElementsByClassName("work-group-constraints")[0] as HTMLElement
|
||||
val addButton =
|
||||
View.wrap(document.getElementsByClassName("work-group-constraints-add")[0] as HTMLElement)
|
||||
val addList =
|
||||
ListView.wrap<View>(document.getElementsByClassName("work-group-constraints-add-list")[0] as HTMLElement)
|
||||
|
||||
console.log(constraints.html)
|
||||
console.log(addButton.html)
|
||||
console.log(addList.html)
|
||||
|
||||
addButton.onClick {
|
||||
addList.classList += "active"
|
||||
|
||||
|
@ -39,9 +36,12 @@ fun initWorkGroupConstraints() {
|
|||
|
||||
addList.textView("Add only on day") {
|
||||
onClick {
|
||||
constraints.html.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
constraints.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
classList += "input-group"
|
||||
html.appendChild(TextView("On day").apply { classList += "form-btn" }.html)
|
||||
html.appendChild(TextView("On day").apply {
|
||||
classList += "form-btn"
|
||||
onClick { this@wrap.html.remove() }
|
||||
}.html)
|
||||
html.appendChild(InputView(InputType.NUMBER).apply {
|
||||
classList += "form-control"
|
||||
html.name = "constraint-only-on-day-${index++}"
|
||||
|
@ -53,9 +53,12 @@ fun initWorkGroupConstraints() {
|
|||
}
|
||||
addList.textView("Add only after time") {
|
||||
onClick {
|
||||
constraints.html.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
constraints.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
classList += "input-group"
|
||||
html.appendChild(TextView("After time").apply { classList += "form-btn" }.html)
|
||||
html.appendChild(TextView("After time").apply {
|
||||
classList += "form-btn"
|
||||
onClick { this@wrap.html.remove() }
|
||||
}.html)
|
||||
html.appendChild(InputView(InputType.NUMBER).apply {
|
||||
classList += "form-control"
|
||||
html.name = "constraint-only-after-time-${index++}"
|
||||
|
@ -67,9 +70,12 @@ fun initWorkGroupConstraints() {
|
|||
}
|
||||
addList.textView("Add not at same time") {
|
||||
onClick {
|
||||
constraints.html.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
constraints.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
classList += "input-group"
|
||||
html.appendChild(TextView("Not with").apply { classList += "form-btn" }.html)
|
||||
html.appendChild(TextView("Not with").apply {
|
||||
classList += "form-btn"
|
||||
onClick { this@wrap.html.remove() }
|
||||
}.html)
|
||||
|
||||
val select = createHtmlView<HTMLSelectElement>()
|
||||
select.classList.add("form-control")
|
||||
|
@ -92,9 +98,12 @@ fun initWorkGroupConstraints() {
|
|||
}
|
||||
addList.textView("Add only after work group") {
|
||||
onClick {
|
||||
constraints.html.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
constraints.appendChild(View.wrap(createHtmlView<HTMLDivElement>()) {
|
||||
classList += "input-group"
|
||||
html.appendChild(TextView("After AK").apply { classList += "form-btn" }.html)
|
||||
html.appendChild(TextView("After AK").apply {
|
||||
classList += "form-btn"
|
||||
onClick { this@wrap.html.remove() }
|
||||
}.html)
|
||||
|
||||
val select = createHtmlView<HTMLSelectElement>()
|
||||
select.classList.add("form-control")
|
||||
|
@ -115,4 +124,18 @@ fun initWorkGroupConstraints() {
|
|||
}.html)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(constraints)
|
||||
for (child in constraints.children.iterator()) {
|
||||
console.log(child)
|
||||
if (child.classList.contains("input-group")) {
|
||||
val span = child.firstElementChild as HTMLElement
|
||||
console.log(span)
|
||||
|
||||
span.addEventListener("click", org.w3c.dom.events.EventListener {
|
||||
println("click")
|
||||
child.remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ open class TableLine(line: HTMLElement) : View(line) {
|
|||
protected fun setupBoolean(view: TextView, onSave: () -> Unit) {
|
||||
view.classList += "no-select"
|
||||
view.tabIndex = 0
|
||||
view.onDblClick {
|
||||
view.onClick {
|
||||
onSave()
|
||||
}
|
||||
view.onKeyDown {
|
||||
|
|
|
@ -23,6 +23,7 @@ $input-border-color: #888;
|
|||
$table-border-color: rgba($text-primary-color, 0.1);
|
||||
$table-header-color: rgba($text-primary-color, 0.06);
|
||||
|
||||
$border-radius: 0.2rem;
|
||||
$transitionTime: 150ms;
|
||||
|
||||
$bg-disabled-color: rgba($text-primary-color, .26);
|
||||
|
@ -45,7 +46,7 @@ body, html {
|
|||
padding: 0;
|
||||
|
||||
& > *:last-child {
|
||||
margin-bottom: 1rem;
|
||||
//margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,7 +324,7 @@ a {
|
|||
height: 2.5rem;
|
||||
width: 100%;
|
||||
background-color: $background-primary-color;
|
||||
border-radius: 0.2rem;
|
||||
border-radius: $border-radius;
|
||||
margin: 1px;
|
||||
transition: border-color $transitionTime;
|
||||
|
||||
|
@ -433,7 +434,7 @@ select:-moz-focusring {
|
|||
display: inline-block;
|
||||
margin-right: 0.6rem;
|
||||
|
||||
border-radius: 0.2rem;
|
||||
border-radius: $border-radius;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9rem;
|
||||
|
@ -484,8 +485,14 @@ form {
|
|||
margin-top: 1px;
|
||||
}
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin-right: 0;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top-left-radius: 0;
|
||||
|
@ -547,7 +554,7 @@ form {
|
|||
.calendar-work-group {
|
||||
position: relative;
|
||||
display: block;
|
||||
border-radius: 0.2rem;
|
||||
border-radius: $border-radius;
|
||||
line-height: 2rem;
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
|
@ -615,7 +622,7 @@ form {
|
|||
right: 0;
|
||||
background-color: #fff;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 0.2rem;
|
||||
border-radius: $border-radius;
|
||||
display: none;
|
||||
z-index: 10;
|
||||
|
||||
|
@ -643,7 +650,7 @@ form {
|
|||
.calendar-entry {
|
||||
position: absolute;
|
||||
display: block;
|
||||
border-radius: 0.2rem;
|
||||
border-radius: $border-radius;
|
||||
z-index: 1;
|
||||
line-height: 2rem;
|
||||
font-size: 0.8rem;
|
||||
|
@ -934,26 +941,62 @@ form {
|
|||
|
||||
.work-group-constraints {
|
||||
position: relative;
|
||||
|
||||
& > label {
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
span {
|
||||
width: 4rem;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover::after {
|
||||
content: 'DELETE';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
font-weight: bold;
|
||||
color: $primary-text-color;
|
||||
background: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 12rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.work-group-constraints-add {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: -0.5rem;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.work-group-constraints-add-list {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: -2rem;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
display: none;
|
||||
padding: 0.5rem 0;
|
||||
|
||||
background: $background-primary-color;
|
||||
border: solid 1px $table-border-color;
|
||||
border: solid 1px $input-border-color;
|
||||
border-radius: $border-radius;
|
||||
|
||||
span {
|
||||
padding: 0 0.5rem;
|
||||
line-height: 2rem;
|
||||
display: block;
|
||||
|
||||
&:hover {
|
||||
background-color: $table-header-color;
|
||||
|
@ -1022,6 +1065,10 @@ form {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.post-column-left, .post-column-right {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.post-image {
|
||||
width: 100%;
|
||||
margin: 0.4rem 0 0;
|
||||
|
|
82
src/jvmMain/kotlin/de/kif/backend/backup/Backup.kt
Normal file
82
src/jvmMain/kotlin/de/kif/backend/backup/Backup.kt
Normal file
|
@ -0,0 +1,82 @@
|
|||
package de.kif.backend.backup
|
||||
|
||||
import de.kif.backend.database.Connection
|
||||
import de.kif.backend.repository.*
|
||||
import de.kif.common.Message
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Backup(
|
||||
val posts: List<Post> = emptyList(),
|
||||
val rooms: List<Room> = emptyList(),
|
||||
val schedules: List<Schedule> = emptyList(),
|
||||
val tracks: List<Track> = emptyList(),
|
||||
val users: List<User> = emptyList(),
|
||||
val workGroups: List<WorkGroup> = emptyList()
|
||||
) {
|
||||
companion object {
|
||||
suspend fun backup(vararg repositories: RepositoryType): String {
|
||||
var backup = Backup()
|
||||
|
||||
val repositorySet = repositories.toMutableSet()
|
||||
|
||||
if (RepositoryType.SCHEDULE in repositorySet) {
|
||||
repositorySet += RepositoryType.ROOM
|
||||
repositorySet += RepositoryType.WORK_GROUP
|
||||
}
|
||||
|
||||
if (RepositoryType.WORK_GROUP in repositorySet) {
|
||||
repositorySet += RepositoryType.TRACK
|
||||
}
|
||||
|
||||
for (repository in repositorySet) {
|
||||
backup = when (repository) {
|
||||
RepositoryType.ROOM -> backup.copy(rooms = RoomRepository.all())
|
||||
RepositoryType.SCHEDULE -> backup.copy(schedules = ScheduleRepository.all())
|
||||
RepositoryType.TRACK -> backup.copy(tracks = TrackRepository.all())
|
||||
RepositoryType.USER -> backup.copy(users = UserRepository.all())
|
||||
RepositoryType.WORK_GROUP -> backup.copy(workGroups = WorkGroupRepository.all())
|
||||
RepositoryType.POST -> backup.copy(posts = PostRepository.all())
|
||||
}
|
||||
}
|
||||
|
||||
return Message.json.stringify(serializer(), backup)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
suspend fun import(data: String) {
|
||||
val backup = Message.json.parse(serializer(), data)
|
||||
|
||||
val userMap = backup.users.associateWith { UserRepository.create(it) }
|
||||
val postMap = backup.posts.associateWith { PostRepository.create(it) }
|
||||
|
||||
val roomMap = backup.rooms.associateWith { RoomRepository.create(it) }
|
||||
val trackMap = backup.tracks.associateWith { TrackRepository.create(it) }
|
||||
val workGroupMap = backup.workGroups.associateWith {
|
||||
var workGroup = it
|
||||
val track = workGroup.track
|
||||
if (track != null) {
|
||||
workGroup = workGroup.copy(track = track.copy(id = trackMap[track] ?: return@associateWith -1L))
|
||||
}
|
||||
|
||||
WorkGroupRepository.create(workGroup)
|
||||
}
|
||||
val scheduleMap = backup.schedules.associateWith {
|
||||
ScheduleRepository.create(
|
||||
it.copy(
|
||||
room = it.room.copy(id = roomMap[it.room] ?: return@associateWith -1L),
|
||||
workGroup = it.workGroup.copy(id = workGroupMap[it.workGroup] ?: return@associateWith -1L)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun restore(data: String) {
|
||||
Connection.reset()
|
||||
|
||||
import(data)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,28 +3,72 @@ package de.kif.backend.database
|
|||
import de.kif.backend.Configuration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import mu.KotlinLogging
|
||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.nio.file.Files
|
||||
import java.sql.Connection.TRANSACTION_SERIALIZABLE
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
object Connection {
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
private val schemaList = arrayOf(
|
||||
DbTrack, DbWorkGroup,
|
||||
DbRoom, DbSchedule,
|
||||
DbUser, DbUserPermission,
|
||||
DbPost
|
||||
)
|
||||
|
||||
fun init() {
|
||||
val dbPath = Configuration.Path.databasePath.toString()
|
||||
Database.connect("jdbc:sqlite:$dbPath", "org.sqlite.JDBC")
|
||||
TransactionManager.manager.defaultIsolationLevel = TRANSACTION_SERIALIZABLE
|
||||
|
||||
transaction {
|
||||
SchemaUtils.create(
|
||||
DbTrack, DbWorkGroup,
|
||||
DbRoom, DbSchedule,
|
||||
DbUser, DbUserPermission,
|
||||
DbPost
|
||||
)
|
||||
try {
|
||||
create()
|
||||
} catch (e: ExposedSQLException) {
|
||||
logger.error { "Cannot initialize the database!" }
|
||||
|
||||
print("Do you want to recreate the database (you will lose all data)? [y/N]: ")
|
||||
val result = readLine()
|
||||
|
||||
if (result == null || result.toLowerCase() !in "yes") {
|
||||
exitProcess(1)
|
||||
} else {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun create() {
|
||||
transaction {
|
||||
SchemaUtils.createMissingTablesAndColumns(*schemaList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun delete() {
|
||||
transaction {
|
||||
SchemaUtils.drop(*schemaList)
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
delete()
|
||||
|
||||
Configuration.Path.uploadsPath.toFile().deleteRecursively()
|
||||
Configuration.Path.sessionsPath.toFile().deleteRecursively()
|
||||
|
||||
Files.createDirectory(Configuration.Path.uploadsPath)
|
||||
Files.createDirectory(Configuration.Path.sessionsPath)
|
||||
|
||||
create()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> dbQuery(block: () -> T): T = withContext(Dispatchers.IO) {
|
||||
|
|
|
@ -85,6 +85,7 @@ object DbPost : Table() {
|
|||
val url = varchar("url", 64).uniqueIndex()
|
||||
val image = varchar("image", 64).nullable()
|
||||
val pinned = bool("pinned")
|
||||
val hideOnProjector = bool("hideOnProjector")
|
||||
|
||||
val createdAt = long("createdAt")
|
||||
val updatedAt = long("updatedAt")
|
||||
|
|
|
@ -25,11 +25,12 @@ object PostRepository : Repository<Post> {
|
|||
val url = row[DbPost.url]
|
||||
val image = row[DbPost.image]
|
||||
val pinned = row[DbPost.pinned]
|
||||
val hideOnProjector = row[DbPost.hideOnProjector]
|
||||
|
||||
val createdAt = row[DbPost.createdAt]
|
||||
val updatedAt = row[DbPost.updatedAt]
|
||||
|
||||
return Post(id, name, content, url, image, pinned, createdAt, updatedAt)
|
||||
return Post(id, name, content, url, image, pinned, hideOnProjector, createdAt, updatedAt)
|
||||
}
|
||||
|
||||
override suspend fun get(id: Long): Post? {
|
||||
|
@ -60,6 +61,7 @@ object PostRepository : Repository<Post> {
|
|||
it[url] = model.url
|
||||
it[image] = model.image
|
||||
it[pinned] = model.pinned
|
||||
it[hideOnProjector] = model.hideOnProjector
|
||||
|
||||
it[createdAt] = now
|
||||
it[updatedAt] = now
|
||||
|
@ -91,6 +93,7 @@ object PostRepository : Repository<Post> {
|
|||
it[url] = model.url
|
||||
it[image] = model.image
|
||||
it[pinned] = model.pinned
|
||||
it[hideOnProjector] = model.hideOnProjector
|
||||
|
||||
it[updatedAt] = now
|
||||
}
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
package de.kif.backend.route
|
||||
|
||||
import de.kif.backend.authenticate
|
||||
import de.kif.backend.authenticateOrRedirect
|
||||
import de.kif.backend.backup.Backup
|
||||
import de.kif.backend.route.api.error
|
||||
import de.kif.backend.view.MainTemplate
|
||||
import de.kif.backend.view.MenuTemplate
|
||||
import de.kif.common.RepositoryType
|
||||
import de.kif.common.model.Permission
|
||||
import io.ktor.application.call
|
||||
import io.ktor.html.respondHtmlTemplate
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.content.PartData
|
||||
import io.ktor.http.content.forEachPart
|
||||
import io.ktor.http.content.streamProvider
|
||||
import io.ktor.request.receiveMultipart
|
||||
import io.ktor.response.respondRedirect
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.post
|
||||
import kotlinx.html.*
|
||||
|
||||
fun Route.account() {
|
||||
|
@ -30,8 +44,175 @@ fun Route.account() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
if (user.checkPermission(Permission.ROOM)) {
|
||||
a("/account/backup/rooms.json", classes = "form-btn") {
|
||||
attributes["download"] = "rooms-backup"
|
||||
+"Create room backup"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.USER)) {
|
||||
a("/account/backup/users.json", classes = "form-btn") {
|
||||
attributes["download"] = "users-backup"
|
||||
+"Create user backup"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.POST)) {
|
||||
a("/account/backup/posts.json", classes = "form-btn") {
|
||||
attributes["download"] = "posts-backup"
|
||||
+"Create post backup"
|
||||
}
|
||||
}
|
||||
if (user.checkPermission(Permission.WORK_GROUP)) {
|
||||
a("/account/backup/work-groups.json", classes = "form-btn") {
|
||||
attributes["download"] = "work-groups-backup"
|
||||
+"Create work group backup"
|
||||
}
|
||||
}
|
||||
if (
|
||||
user.checkPermission(Permission.WORK_GROUP) &&
|
||||
user.checkPermission(Permission.ROOM) &&
|
||||
user.checkPermission(Permission.SCHEDULE)
|
||||
) {
|
||||
a("/account/backup/schedules.json", classes = "form-btn") {
|
||||
attributes["download"] = "schedules-backup"
|
||||
+"Create schedule backup"
|
||||
}
|
||||
}
|
||||
|
||||
if (user.checkPermission(Permission.ADMIN)) {
|
||||
a("/account/backup.json", classes = "form-btn") {
|
||||
attributes["download"] = "backup.json"
|
||||
+"Create backup"
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
form(
|
||||
action = "/account/import",
|
||||
method = FormMethod.post,
|
||||
encType = FormEncType.multipartFormData
|
||||
) {
|
||||
div("form-group") {
|
||||
label {
|
||||
htmlFor = "backup"
|
||||
+"Backup image"
|
||||
}
|
||||
input(
|
||||
name = "backup",
|
||||
classes = "form-btn",
|
||||
type = InputType.file
|
||||
) {
|
||||
id = "backup"
|
||||
value = "Select backup image"
|
||||
accept = ".json"
|
||||
}
|
||||
}
|
||||
|
||||
div("form-switch-group") {
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "reset",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "reset"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "reset"
|
||||
+"Reset"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
button(type = ButtonType.submit, classes = "form-btn btn-primary") {
|
||||
+"Import"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get("/account/backup/rooms.json") {
|
||||
authenticate(Permission.ROOM) {
|
||||
call.respondText(Backup.backup(RepositoryType.ROOM), ContentType.Application.Json, HttpStatusCode.OK)
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
get("/account/backup/users.json") {
|
||||
authenticate(Permission.USER) {
|
||||
call.respondText(Backup.backup(RepositoryType.USER), ContentType.Application.Json, HttpStatusCode.OK)
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
get("/account/backup/posts.json") {
|
||||
authenticate(Permission.POST) {
|
||||
call.respondText(Backup.backup(RepositoryType.POST), ContentType.Application.Json, HttpStatusCode.OK)
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
get("/account/backup/work-groups.json") {
|
||||
authenticate(Permission.WORK_GROUP) {
|
||||
call.respondText(Backup.backup(RepositoryType.WORK_GROUP), ContentType.Application.Json, HttpStatusCode.OK)
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
get("/account/backup/schedules.json") {
|
||||
authenticate(Permission.ROOM, Permission.WORK_GROUP, Permission.SCHEDULE) {
|
||||
call.respondText(Backup.backup(RepositoryType.SCHEDULE), ContentType.Application.Json, HttpStatusCode.OK)
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
get("/account/backup.json") {
|
||||
authenticate(Permission.ADMIN) {
|
||||
call.respondText(Backup.backup(*RepositoryType.values()), ContentType.Application.Json, HttpStatusCode.OK)
|
||||
} onFailure {
|
||||
call.error(HttpStatusCode.Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
post("/account/import") {
|
||||
authenticateOrRedirect(Permission.ADMIN) {
|
||||
var reset = false
|
||||
var import = ""
|
||||
|
||||
call.receiveMultipart().forEachPart { part ->
|
||||
val name = part.name ?: return@forEachPart
|
||||
when (part) {
|
||||
is PartData.FormItem -> {
|
||||
if (name == "reset" && part.value == "on") {
|
||||
reset = true
|
||||
}
|
||||
}
|
||||
is PartData.FileItem -> {
|
||||
import = part.streamProvider().bufferedReader().readText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
Backup.restore(import)
|
||||
} else {
|
||||
Backup.import(import)
|
||||
}
|
||||
|
||||
call.respondRedirect("/account")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -236,6 +236,20 @@ fun Route.overview() {
|
|||
+"Pinned"
|
||||
}
|
||||
}
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "hide-on-projector",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "hide-on-projector"
|
||||
checked = editPost.hideOnProjector
|
||||
}
|
||||
label {
|
||||
htmlFor = "hide-on-projector"
|
||||
+"Hide on projector"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
|
@ -317,6 +331,7 @@ fun Route.overview() {
|
|||
params["url"]?.let { post = post.copy(url = it) }
|
||||
params["content"]?.let { post = post.copy(content = it) }
|
||||
params["pinned"]?.let { post = post.copy(pinned = it == "on") }
|
||||
params["hide-on-projector"]?.let { post = post.copy(hideOnProjector = it == "on") }
|
||||
|
||||
if (params["image-delete"] == "on") {
|
||||
val currentImage = post.image
|
||||
|
@ -430,6 +445,20 @@ fun Route.overview() {
|
|||
+"Pinned"
|
||||
}
|
||||
}
|
||||
div("form-group form-switch") {
|
||||
input(
|
||||
name = "hide-on-projector",
|
||||
classes = "form-control",
|
||||
type = InputType.checkBox
|
||||
) {
|
||||
id = "hide-on-projector"
|
||||
checked = false
|
||||
}
|
||||
label {
|
||||
htmlFor = "hide-on-projector"
|
||||
+"Hide on projector"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div("form-group") {
|
||||
|
@ -502,8 +531,9 @@ fun Route.overview() {
|
|||
val content = params["content"] ?: return@post
|
||||
val url = params["url"] ?: return@post
|
||||
val pinned = params["pinned"] == "on"
|
||||
val hideOnProjector = params["hide-on-projector"] == "on"
|
||||
|
||||
val post = Post(null, name, content, url, imageUploadName, pinned)
|
||||
val post = Post(null, name, content, url, imageUploadName, pinned, hideOnProjector)
|
||||
|
||||
PostRepository.create(post)
|
||||
|
||||
|
|
|
@ -354,8 +354,6 @@ fun Route.workGroup() {
|
|||
+"Accessible"
|
||||
}
|
||||
}
|
||||
|
||||
div("work-group-constraints")
|
||||
}
|
||||
|
||||
div("form-group work-group-constraints") {
|
||||
|
|
18
src/jvmMain/kotlin/de/kif/backend/util/LogbackFilter.kt
Normal file
18
src/jvmMain/kotlin/de/kif/backend/util/LogbackFilter.kt
Normal file
|
@ -0,0 +1,18 @@
|
|||
package de.kif.backend.util
|
||||
|
||||
import ch.qos.logback.classic.Level
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent
|
||||
import ch.qos.logback.core.filter.Filter
|
||||
import ch.qos.logback.core.spi.FilterReply
|
||||
|
||||
class LogbackFilter : Filter<ILoggingEvent>() {
|
||||
override fun decide(event: ILoggingEvent?): FilterReply = if (event == null) {
|
||||
FilterReply.NEUTRAL
|
||||
} else {
|
||||
if (event.loggerName.contains("Exposed".toRegex())) {
|
||||
if (event.level.toInt() > Level.ERROR_INT) FilterReply.ACCEPT else FilterReply.DENY
|
||||
} else {
|
||||
FilterReply.ACCEPT
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,18 +9,22 @@ import kotlinx.html.*
|
|||
|
||||
class MainTemplate : Template<HTML> {
|
||||
val content = Placeholder<HtmlBlockTag>()
|
||||
val menuTemplate =TemplatePlaceholder<MenuTemplate>()
|
||||
val menuTemplate = TemplatePlaceholder<MenuTemplate>()
|
||||
|
||||
override fun HTML.apply() {
|
||||
head {
|
||||
meta(charset = "utf-8")
|
||||
meta(name="viewport",content = "width=device-width, initial-scale=1.0")
|
||||
meta(name = "viewport", content = "width=device-width, initial-scale=1.0")
|
||||
|
||||
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 = "https://fonts.googleapis.com/css?family=Bungee|Oswald", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
link(
|
||||
href = "https://fonts.googleapis.com/css?family=Bungee|Oswald",
|
||||
type = LinkType.textCss,
|
||||
rel = LinkRel.stylesheet
|
||||
)
|
||||
link(href = "/static/style/style.css", type = LinkType.textCss, rel = LinkRel.stylesheet)
|
||||
|
||||
script(src = "/static/require.min.js") {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<configuration debug="false">
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
|
||||
<filter class="de.westermann.robots.server.util.LogFilter" />
|
||||
<filter class="de.kif.backend.util.LogbackFilter" />
|
||||
|
||||
<withJansi>true</withJansi>
|
||||
<encoder>
|
||||
|
|
Loading…
Reference in a new issue