Merge branch 'development'

This commit is contained in:
Methapon2001 2023-12-03 10:44:35 +07:00
commit 16138ab179
No known key found for this signature in database
GPG key ID: 849924FEF46BD132
66 changed files with 3344 additions and 757 deletions

View file

@ -1,6 +0,0 @@
.DS_Store
node_modules
dist
.env
.env.*
!.env.example

View file

@ -29,7 +29,6 @@
"vue-router": "^4.2.5"
},
"devDependencies": {
"@mayank1513/vue-tag-input": "^1.2.0",
"@quasar/vite-plugin": "^1.6.0",
"@rushstack/eslint-patch": "^1.6.0",
"@types/jsdom": "^21.1.6",

View file

@ -37,6 +37,7 @@ const props = withDefaults(
flat
v-close-popup
@click="() => ($emit('update:open', !open))"
id="dialogDeleteClose"
/>
<q-btn
@ -45,6 +46,7 @@ const props = withDefaults(
label="ลบ"
class="text-red"
@click="() => $emit('confirm')"
id="dialogDeleteConfirm"
/>
</q-card-actions>
</q-card>

View file

@ -1,6 +1,11 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { QSelect } from 'quasar'
const storesCategory: string[] = []
const filterDataCategory = ref<string[]>(storesCategory)
const props = withDefaults(
defineProps<{
open: boolean
@ -11,14 +16,17 @@ const props = withDefaults(
mode: 'create' | 'edit'
title?: string
description?: string
keyword?: string
category?: string
keyword?: string[]
category?: string[]
}>(),
{
open: false,
},
)
const inputKeyword = ref<string[]>(props.keyword || [])
const inputCategory = ref<string[]>(props.category || [])
const emit = defineEmits([
'update:open',
'update:title',
@ -50,12 +58,41 @@ function submit() {
file: file.value,
title: props.title ?? '',
description: props.description ?? '',
keyword: props.keyword ?? '',
category: props.category ?? '',
keyword: props.keyword,
category: props.category,
})
emit('update:open', !open), reset()
}
const createKeyword = ((val, done) => {
if (val.length > 2) {
if (!inputKeyword.value.includes(val)) {
done(val, 'add-unique')
}
}
}) satisfies QSelect['onNewValue']
const createCategory = ((val, done) => {
if (val.length > 2) {
if (!inputCategory.value.includes(val)) {
done(val, 'add-unique')
}
}
}) satisfies QSelect['onNewValue']
const filterCategory = ((val, update) => {
update(() => {
if (val === '') {
filterDataCategory.value = storesCategory
} else {
const needle = val.toLowerCase()
filterDataCategory.value = storesCategory.filter(
(v) => v.toLowerCase().indexOf(needle) > -1,
)
}
})
}) satisfies QSelect['onFilter']
onMounted(() => window.addEventListener('keydown', keydown))
onUnmounted(() => window.addEventListener('keydown', keydown))
@ -92,6 +129,7 @@ const file = ref<File | undefined>()
icon="close"
color="red"
@click="() => ($emit('update:open', !open), reset())"
id="fileFormIconClose"
/>
</q-toolbar>
@ -111,6 +149,7 @@ const file = ref<File | undefined>()
? 'ไม่สามารถเพิ่มไฟล์ที่ชื่อยาวเกิน 85 ตัวอักษรได้'
: ''
"
id="inputFile"
>
<template v-slot:prepend>
<q-icon name="attach_file" />
@ -126,7 +165,9 @@ const file = ref<File | undefined>()
class="q-my-sm"
placeholder="กรอกชื่อเรื่อง"
:model-value="title"
:rules="[(v) => v.length > 3 || 'ชื่อต้องยาวกว่า 3 ตัวอักษร']"
@update:model-value="(v) => $emit('update:title', v)"
id="inputTitle"
/>
</section>
@ -140,31 +181,54 @@ const file = ref<File | undefined>()
placeholder="กรอกรายละเอียด"
:model-value="description"
@update:model-value="(v) => $emit('update:description', v)"
id="inputDescription"
/>
</section>
<section class="q-mb-md">
<span class="text-weight-bold">กล/หมวดหม</span>
<q-input
outlined
dense
class="q-mt-sm"
placeholder="เลือกกลุ่ม/หมวดหมู่"
:model-value="category"
@update:model-value="(v) => $emit('update:category', v)"
/>
<div class="q-mt-md">
<q-select
outlined
:model-value="category"
use-input
use-chips
multiple
input-debounce="0"
@new-value="createCategory"
:options="filterDataCategory"
@filter="filterCategory"
style="width: 250px"
@update:model-value="(v) => $emit('update:category', v)"
id="inputCategory"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey"> No results </q-item-section>
</q-item>
</template>
</q-select>
</div>
</section>
<section class="q-mb-md">
<span class="text-weight-bold">คำสำค</span>
<q-input
outlined
dense
class="q-mt-sm"
placeholder="คำสำคัญ"
:model-value="keyword"
@update:model-value="(v) => $emit('update:keyword', v)"
/>
<div class="q-mt-md">
<q-select
outlined
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
style="width: 250px"
:model-value="props.keyword"
@update:model-value="(v) => $emit('update:keyword', v)"
@new-value="createKeyword"
id="inputKeyword"
>
</q-select>
</div>
</section>
<section :style="{ display: 'flex', gap: '.5rem' }">
@ -173,6 +237,7 @@ const file = ref<File | undefined>()
type="submit"
color="primary"
:disable="error.fileName2Long"
id="submitFile"
/>
<q-btn
label="ยกเลิก"
@ -180,6 +245,7 @@ const file = ref<File | undefined>()
color="primary"
flat
@click="() => ($emit('update:open', false), reset())"
id="fileFormBtnClose"
/>
</section>
</q-form>

View file

@ -15,12 +15,13 @@ const props = withDefaults(
defineProps<{ action: boolean; viewMode: 'view_list' | 'view_module' }>(),
{
action: false,
},
}
)
const DEPT_NAME = ['ตู้เอกสาร', 'ลิ้นชัก', 'แฟ้ม', 'แฟ้มย่อย'] as const
const { getFileInfo, getFileNameFormat } = useFileInfoStore()
const { currentFolder, currentFile, currentDept, currentPath } =
storeToRefs(useTreeDataStore())
const { currentFolder, currentFile, currentDept, currentPath } = storeToRefs(
useTreeDataStore()
)
const {
createFolder,
editFolder,
@ -30,15 +31,15 @@ const {
updateFile,
deleteFile,
checkFile,
checkFileName,
refaceFile,
} = useTreeDataStore()
const currentIcon = computed(() =>
currentDept.value === 0
? 'mdi-file-cabinet'
: currentDept.value === 1
? 'inbox'
: 'o_folder_open',
? 'inbox'
: 'o_folder_open'
)
const dialogDeleteState = ref<boolean>(false)
@ -57,11 +58,11 @@ const fileFormData = ref<{
file?: File
title?: string
description?: string
keyword?: string
category?: string
keyword?: string[]
category?: string[]
}>({})
const fileFormType = ref<'edit' | 'create'>('create')
const fileFormError = ref<{ fileExist?: boolean; fileName2Long?: boolean }>({})
const fileFormError = ref<{ fileExist?: boolean }>({})
const fileExistNotification = ref<boolean>(false)
function triggerFolderDelete(pathname: string) {
@ -110,10 +111,10 @@ function triggerFileEdit(
value: {
title: string
description: string
keyword: string
category: string
keyword: string[]
category: string[]
},
pathname: string,
pathname: string
) {
fileFormState.value = true
fileFormType.value = 'edit'
@ -122,7 +123,7 @@ function triggerFileEdit(
title: value.title,
description: value.description,
keyword: value.keyword,
category: value.keyword,
category: value.category,
}
}
@ -134,10 +135,10 @@ async function submitFileForm(
file?: File
title: string
description: string
keyword: string
category: string
keyword: string[]
category: string[]
},
force = false,
force = false
) {
currentParam.value = value
@ -153,6 +154,13 @@ async function submitFileForm(
keyword: value.keyword,
category: value.category,
})
setTimeout(() => {
refaceFile(currentPath.value)
}, 3000)
setTimeout(() => {
refaceFile(currentPath.value)
}, 10000)
} else {
await updateFile(
fileFormPath.value,
@ -162,7 +170,7 @@ async function submitFileForm(
keyword: value.keyword,
category: value.category,
},
value.file,
value.file
)
}
fileFormData.value = {}
@ -176,7 +184,8 @@ async function submitFileForm(
{{ DEPT_NAME[currentDept] }}
</div>
<div class="grid q-mt-md">
<div v-for="value in currentFolder">
<div v-for="value in currentFolder" key="value.name">
{{ value.name }}
<div
:style="{
position: 'relative',
@ -187,7 +196,11 @@ async function submitFileForm(
padding: currentDept > 2 ? '.5rem 0' : '.5rem',
}"
class="box"
@click="() => getFolder(value.pathname)"
@click="
() => {
;(folderFormState = false), getFolder(value.pathname)
}
"
>
<div class="q-px-md flex items-center justify-center">
<q-icon
@ -307,10 +320,10 @@ async function submitFileForm(
{
title: value.title,
description: value.description,
keyword: value.keyword.join(','),
category: value.category.join(','),
keyword: value.keyword,
category: value.category,
},
value.pathname,
value.pathname
)
"
@delete="() => triggerFileDelete(value.pathname)"
@ -320,7 +333,7 @@ async function submitFileForm(
class="text-overflow-handle block q-px-md text-center"
style="max-width: 100%"
>
{{ getFileNameFormat(value.fileName) }}
{{ value.title }}
</div>
</div>
</div>
@ -369,12 +382,7 @@ async function submitFileForm(
v-model:description="fileFormData.description"
v-model:keyword="fileFormData.keyword"
v-model:category="fileFormData.category"
@filechange="
(name: string) => (
(fileFormError.fileExist = checkFile(name)),
(fileFormError.fileName2Long = checkFileName(name))
)
"
@filechange="(name: string) => (fileFormError.fileExist = checkFile(name))"
@submit="submitFileForm"
/>
@ -426,7 +434,7 @@ async function submitFileForm(
.add-button {
position: absolute;
top: 50%;
right: 40%;
right: 30%;
background-color: white;
}

View file

@ -6,17 +6,17 @@ defineEmits(['edit', 'delete'])
<q-btn @click.stop icon="more_vert" color="grey" flat dense>
<q-menu auto-close>
<q-list dense>
<q-item clickable>
<q-item-section @click.prevent.stop="() => $emit('edit')">
<div class="row items-center">
<q-item clickable @click="() => $emit('edit')">
<q-item-section>
<div class="row items-center white ">
<q-icon name="edit" color="positive" />
<span class="q-ml-sm">แกไข</span>
</div>
</q-item-section>
</q-item>
<q-item clickable>
<q-item-section @click.prevent.stop="() => $emit('delete')">
<div class="row items-center">
<q-item clickable @click="() => $emit('delete')">
<q-item-section>
<div class="row items-center white ">
<q-icon name="delete" color="negative" />
<span class="q-ml-sm">ลบ</span>
</div>
@ -26,3 +26,16 @@ defineEmits(['edit', 'delete'])
</q-menu>
</q-btn>
</template>
<style scoped>
.white {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
</style>

View file

@ -4,13 +4,19 @@ import { useSearchDataStore } from '@/stores/searched-data'
import { useFileInfoStore } from '@/stores/file-info-data'
import FileIcon from '@/components/FileIcon.vue'
import type { QTableProps } from 'quasar'
import { onMounted, ref, watch } from 'vue'
defineProps<{
viewMode: 'view_list' | 'view_module'
}>()
const { foundFile } = storeToRefs(useSearchDataStore())
const { getFileInfo, getSize, getType, getFileNameFormat } = useFileInfoStore()
const { getFileInfo, getSize, getType } = useFileInfoStore()
const keywordList = ref<string[]>([])
const categoryList = ref<string[]>([])
const selectKeyword = ref<string[]>([])
const selectCategory = ref<string[]>([])
const filterFoundFile = ref<any>()
const columns: QTableProps['columns'] = [
{
name: 'name',
@ -46,12 +52,81 @@ const columns: QTableProps['columns'] = [
style: 'width: 20px',
},
]
function filterSearch() {
function updateList() {
keywordList.value = []
categoryList.value = []
foundFile.value.forEach((obj) => {
obj.keyword.forEach((keyword) => {
if (!keywordList.value.includes(keyword)) {
keywordList.value.push(keyword)
}
})
obj.category.forEach((category) => {
if (!categoryList.value.includes(category)) {
categoryList.value.push(category)
}
})
})
}
function filterArray() {
if (!(selectKeyword.value.length || selectCategory.value.length)) {
filterFoundFile.value = foundFile.value
} else {
filterFoundFile.value = foundFile.value.filter(
(entry) =>
entry.keyword.some((kw) => selectKeyword.value.includes(kw)) ||
entry.category.some((kw) => selectCategory.value.includes(kw)),
)
}
}
updateList()
filterArray()
}
watch(
[
() => foundFile.value,
() => selectKeyword.value,
() => selectCategory.value,
],
filterSearch,
)
onMounted(() => {
filterSearch()
})
</script>
<template>
<div class="row grid q-pt-md q-gutter-sm">
<q-select
outlined
dense
multiple
use-chips
v-model="selectKeyword"
:options="keywordList"
style="width: 100%"
label="คำสำคัญ"
/>
<q-select
outlined
dense
multiple
use-chips
v-model="selectCategory"
:options="categoryList"
style="width: 100%"
label="กลุ่ม/หมวดหมู่"
/>
</div>
<div v-if="viewMode === 'view_list' && foundFile.length > 0">
<div class="grid q-mt-md">
<div v-for="(value, index) in foundFile" :key="value.title">
<div v-for="(value, index) in filterFoundFile" :key="value.title">
<div
:style="{
position: 'relative',
@ -63,7 +138,7 @@ const columns: QTableProps['columns'] = [
maxWidth: '100%',
}"
class="box"
@click="() => getFileInfo(foundFile[index])"
@click="() => getFileInfo(filterFoundFile[index])"
>
<div class="q-px-md flex items-center justify-center">
<file-icon
@ -75,7 +150,7 @@ const columns: QTableProps['columns'] = [
class="text-overflow-handle block q-px-md text-center"
style="max-width: 100%"
>
{{ getFileNameFormat(value.fileName) }}
{{ value.title }}
</div>
</div>
</div>
@ -86,7 +161,7 @@ const columns: QTableProps['columns'] = [
<q-table
flat
bordered
:rows="foundFile"
:rows="filterFoundFile"
:columns="columns"
row-key="name"
hide-bottom

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
const props = withDefaults(
defineProps<{
@ -14,6 +14,11 @@ const props = withDefaults(
)
const emit = defineEmits(['update:open', 'update:name', 'submit'])
const offensiveWord = ref<boolean>(false)
function checkOffensiveWord(input: string) {
return /[\\?%:|"<>#]/.test(input)
}
function reset() {
emit('update:name', undefined)
@ -48,6 +53,7 @@ onUnmounted(() => window.addEventListener('keydown', keydown))
:width="300"
:breakpoint="500"
:model-value="open"
@update:model-value="(v) => $emit('update:open', v)"
>
<q-form @submit.prevent="submit">
<q-toolbar class="q-mb-md q-pa-none">
@ -66,19 +72,32 @@ onUnmounted(() => window.addEventListener('keydown', keydown))
dense
icon="close"
color="red"
@click="() => $emit('update:open', !open)"
@click="
() => {
offensiveWord = false
$emit('update:open', !open)
}
"
/>
</q-toolbar>
<section class="q-mb-md">
<span class="text-weight-bold">{{ tree }}</span>
<q-input
ref="nameInput"
outlined
dense
class="q-my-sm"
placeholder="กรอกชื่อ"
:model-value="name"
@update:model-value="(v) => $emit('update:name', v)"
error-message="คำต้องห้านจะเปลี่ยนเป็น - เมื่อกดสร้าง"
:error="offensiveWord"
@update:model-value="
(v) => {
$emit('update:name', v)
offensiveWord = checkOffensiveWord(v as string)
}
"
/>
</section>
@ -89,7 +108,12 @@ onUnmounted(() => window.addEventListener('keydown', keydown))
type="reset"
color="primary"
flat
@click="() => ($emit('update:open', false), reset())"
@click="
() => {
offensiveWord = false
$emit('update:open', false), reset()
}
"
/>
</section>
</q-form>

View file

@ -55,14 +55,16 @@ const folderFormData = ref<{
}>({})
const folderFormType = ref<'edit' | 'create'>('create')
const fileFormError = ref<{ fileExist?: boolean }>({})
const fileExistNotification = ref<boolean>(false)
const fileFormState = ref<boolean>(false)
const fileFormPath = ref<string>('')
const fileFormData = ref<{
file?: File
title?: string
description?: string
keyword?: string
category?: string
keyword?: string[]
category?: string[]
}>({})
const fileFormType = ref<'edit' | 'create'>('create')
@ -116,8 +118,8 @@ function triggerFileEdit(
value: {
title: string
description: string
keyword: string
category: string
keyword: string[]
category: string[]
},
pathname: string
) {
@ -128,19 +130,31 @@ function triggerFileEdit(
title: value.title,
description: value.description,
keyword: value.keyword,
category: value.keyword,
category: value.category,
}
}
async function submitFileForm(value: {
mode: 'create' | 'edit'
file: File
title: string
description: string
keyword: string
category: string
}) {
if (value.mode === 'create') {
const currentParam = ref<Parameters<typeof submitFileForm>[0]>()
async function submitFileForm(
value: {
mode: 'create' | 'edit'
file?: File
title: string
description: string
keyword: string[]
category: string[]
},
force = false
) {
currentParam.value = value
if (value.file && checkFile(value.file.name) && !force) {
fileExistNotification.value = true
return
}
if (value.mode === 'create' && value.file) {
await uploadFile(currentPath.value, value.file, {
title: value.title,
description: value.description,
@ -161,6 +175,7 @@ async function submitFileForm(value: {
}
fileFormData.value = {}
fileFormState.value = false
currentParam.value = undefined
}
const columnsFolder: QTableProps['columns'] = [
@ -243,18 +258,19 @@ const onRowClick = (evt: Event, row: TreeDataFolder, index: number) => {
</script>
<template>
<div class="q-pa-md">
<div class="q-mt-md">
<div class="q-gutter-sm">
<div
class="flex flex-break d justify-between space-between"
v-if="currentDept >= 1"
v-if="currentDept >= 1 && props.mode == 'admin' && currentDept != 4"
>
<div>
<span class="text-h6">{{ currentLevel }}</span>
</div>
<div>
<q-btn
v-if="props.mode == 'admin' && currentDept != 4"
outline
push
class="q-px-md q-ml-md q-py-sm"
@ -278,6 +294,7 @@ const onRowClick = (evt: Event, row: TreeDataFolder, index: number) => {
:rows-per-page-options="[0]"
@row-click="onRowClick"
class="cursor"
v-if=" currentDept != 4 "
>
<template v-slot:body-cell-name="nameRow">
<q-td style="width: 50%">
@ -331,7 +348,7 @@ const onRowClick = (evt: Event, row: TreeDataFolder, index: number) => {
</div>
</div>
<div class="q-pa-md" v-if="currentDept >= 3">
<div class="q-mt-md" v-if="currentDept >= 3">
<div class="q-gutter-sm">
<div class="flex flex-break d justify-between space-between">
<div>

View file

@ -58,18 +58,7 @@ onMounted(getCabinet)
<h5 class="q-my-none" v-if="mode === 'admin'">จัดเก็บเอกสาร</h5>
<h5 class="q-my-none" v-else>สืบค้นเอกสาร</h5>
</div>
<div class="col-3" v-if="mode === 'admin'">
<q-input
rounded
outlined
dense
label="ค้นหา"
bg-color="white"
v-model="inputSearch"
>
<template v-slot:append><q-icon name="search" /></template>
</q-input>
</div>
<search-bar :mode="mode" v-if="mode === 'admin'" />
</div>
</section>
<section
@ -97,7 +86,7 @@ onMounted(getCabinet)
class="bg-white rounded-borders shadow-5 relative"
v-if="isFilePreview === false"
>
<search-bar v-if="mode === 'user'" />
<search-bar :mode="mode" v-if="mode === 'user'" />
<div class="bg-white q-pa-md">
<div class="row items-center justify-between">
<span class="text-body1">
@ -107,7 +96,12 @@ onMounted(getCabinet)
dense
class="q-mr-sm q-px-sm"
v-if="currentDept > 0 && isSearch === false"
@click="() => gotoParent()"
@click="
() => {
folderFormState = false
gotoParent()
}
"
>
<q-icon name="arrow_back" size="1rem" color="primary" />
</q-btn>
@ -137,6 +131,7 @@ onMounted(getCabinet)
dense
icon="add"
@click="() => triggerFolderCreate()"
id="createFolder"
/>
</span>
<div>
@ -147,6 +142,7 @@ onMounted(getCabinet)
icon="refresh"
class="q-mr-sm"
@click="() => getFolder(currentPath)"
id="getFolder"
/>
<q-btn
flat
@ -159,6 +155,7 @@ onMounted(getCabinet)
viewMode === 'view_list' ? 'view_module' : 'view_list'
}
"
id="viewMode"
/>
</div>
</div>

View file

@ -1,40 +0,0 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
import TagInput from "@mayank1513/vue-tag-input";
import "@mayank1513/vue-tag-input/style.css";
const autocompleteItems = [
"No dependencies",
"Autocompletion",
"Keep Focused",
"Fast Settup",
"Mini Sized",
"Customizable",
"Backspace/Delete to remove tag",
"Turns red when backspace/delete is pressed",
"Examples",
"Docs",
"Copy/Paste",
]
const tags = ref<string[]>([]);
</script>
<template>
<q-btn @click="()=> {console.log(tags);
}">test</q-btn>
<tag-input tagBgColor="rgb(189, 184, 179)" :autocomplete-items="autocompleteItems" v-model="tags" />
<!-- <q-btn
class="q-px-md"
label="บันทึก"
type="submit"
color="primary"
dense
@click="
() => {
$emit('update:drawerFile')
}
"
/> -->
</template>

View file

@ -9,6 +9,8 @@ const { isAdvSearchCall } = storeToRefs(useSearchDataStore())
const optionsField = [
{ label: 'ชื่อเรื่อง (title)', value: 'title' },
{ label: 'คำสำคัญ (keyword)', value: 'keyword' },
{ label: 'หมวดหมู่ (category)', value: 'category' },
{ label: 'เนื้อหาในไฟล์ (content)', value: 'attachment.content' },
]
const optionsOp = [
{ label: 'และ', value: 'AND' },
@ -126,6 +128,7 @@ function clearAdvSearchData() {
</div>
<div class="col-4 col-md-2">
<q-select
id="advSearchOp"
dense
outlined
emit-value
@ -136,6 +139,7 @@ function clearAdvSearchData() {
</div>
<div class="col-grow col-md-3">
<q-select
id="advSearchField"
dense
outlined
emit-value
@ -145,7 +149,13 @@ function clearAdvSearchData() {
/>
</div>
<div class="col-grow">
<q-input dense outlined v-model="item.value" placeholder="เอกสาร"
<q-input
id="advSearchValue"
dense
outlined
v-model="item.value"
placeholder="เอกสาร"
@keydown.enter.prevent="searchSubmit()"
><template v-slot:append>
<q-icon
name="close"
@ -181,17 +191,21 @@ function clearAdvSearchData() {
<div class="row q-col-gutter-md q-pb-md">
<div class="col-12 col-md-5">
<q-input
id="advSearchKeyword"
dense
outlined
placeholder="คำสำคัญ:"
placeholder="คำสำคัญ"
@keydown.enter.prevent="searchSubmit()"
v-model="advSearchDataField.keyword"
/>
</div>
<div class="col-12 col-md-grow">
<q-input
id="advSearchDes"
dense
outlined
placeholder="รายละเอียด:"
placeholder="รายละเอียด"
@keydown.enter.prevent="searchSubmit()"
v-model="advSearchDataField.description"
/>
</div>

View file

@ -54,7 +54,7 @@ async function downloadSubmit(path: any) {
color="primary"
/>
</q-btn>
{{ getFileNameFormat(fileInfo?.fileName) }}</span
{{ fileInfo?.title }}</span
>
</div>
@ -85,7 +85,7 @@ async function downloadSubmit(path: any) {
class="text-overflow-handle block q-px-md text-center"
style="max-width: 100%"
>
{{ getFileNameFormat(fileInfo?.fileName) }}
{{ fileInfo?.title }}
</div>
</div>
<div class="column q-py-sm">
@ -133,12 +133,7 @@ async function downloadSubmit(path: any) {
<span>กล/หมวดหม</span>
</div>
<div class="col-grow">
<span
class="text-grey"
v-for="category in fileInfo?.category"
:key="category"
>{{ category }}</span
>
<span class="text-grey">{{ fileInfo?.category.join(', ') }}</span>
</div>
</div>
<q-separator />
@ -147,12 +142,7 @@ async function downloadSubmit(path: any) {
<span>คำสำค</span>
</div>
<div class="col-grow">
<span
class="text-grey"
v-for="keyword in fileInfo?.keyword"
:key="keyword"
>{{ keyword }}</span
>
<span class="text-grey">{{ fileInfo?.keyword.join(', ') }}</span>
</div>
</div>
<q-separator />

View file

@ -16,6 +16,8 @@ const advSearchComp = ref<InstanceType<typeof AdvancedSearch>>()
const optionsField = [
{ label: 'ชื่อเรื่อง (title)', value: 'title' },
{ label: 'คำสำคัญ (keyword)', value: 'keyword' },
{ label: 'หมวดหมู่ (category)', value: 'category' },
{ label: 'เนื้อหาในไฟล์ (content)', value: 'attachment.content' },
]
const searchData = ref<{
field: string
@ -31,41 +33,48 @@ const submitSearchData = ref<{
AND: [],
OR: [],
})
const props = defineProps<{
mode: 'admin' | 'user'
}>()
async function searchSubmit() {
if (searchData.value.value.trim() !== '') {
submitSearchData.value = { AND: [], OR: [] }
submitSearchData.value.AND.push({
field: searchData.value.field,
value: searchData.value.value,
})
submitSearchData.value.AND.push({
field: 'fileName',
value: searchData.value.value,
})
if (isAdvSearchCall.value && advSearchComp.value) {
const advField = advSearchComp.value.advSearchDataField
const advRow = advSearchComp.value.advSearchDataRow
advRow.forEach((d: { field: string; value: string; op: string }) => {
if (d.field && d.value.trim() !== '') {
const op = d.op === 'AND' ? 'AND' : 'OR'
submitSearchData.value[op].push({ field: d.field, value: d.value })
}
if (props.mode === 'admin') {
optionsField.forEach((option) => {
submitSearchData.value.OR.push({
field: option.value,
value: searchData.value.value,
})
})
if (advField.keyword.trim() !== '') {
submitSearchData.value.AND.push({
field: 'keyword',
value: advField.keyword,
})
}
if (advField.description.trim() !== '') {
submitSearchData.value.AND.push({
field: 'description',
value: advField.description,
} else {
submitSearchData.value.OR.push({
field: searchData.value.field,
value: searchData.value.value,
})
if (isAdvSearchCall.value && advSearchComp.value) {
const advField = advSearchComp.value.advSearchDataField
const advRow = advSearchComp.value.advSearchDataRow
advRow.forEach((d: { field: string; value: string; op: string }) => {
if (d.field && d.value.trim() !== '') {
const op = d.op === 'AND' ? 'AND' : 'OR'
submitSearchData.value[op].push({ field: d.field, value: d.value })
}
})
if (advField.keyword.trim() !== '') {
submitSearchData.value.AND.push({
field: 'keyword',
value: advField.keyword,
})
}
if (advField.description.trim() !== '') {
submitSearchData.value.AND.push({
field: 'description',
value: advField.description,
})
}
}
}
@ -73,11 +82,8 @@ async function searchSubmit() {
loaderStore.show()
const res = await axiosClient.post<EhrFile[]>(
`${import.meta.env.VITE_API_ENDPOINT}/search`,
submitSearchData.value
submitSearchData.value,
)
console.log(submitSearchData.value);
console.log(res.data);
getFoundFile(res.data)
isSearch.value = true
} catch (error) {
@ -90,59 +96,78 @@ async function searchSubmit() {
</script>
<template>
<div class="q-pa-md bg-grey-1">
<div class="row items-center q-col-gutter-md">
<div class="col-12 col-md-3">
<q-select
dense
outlined
emit-value
map-options
class="bg-white"
v-model="searchData.field"
:options="optionsField"
/>
</div>
<div class="col-12 col-md-grow">
<q-input
class="bg-white"
dense
outlined
v-model="searchData.value"
placeholder="เอกสาร"
@keydown.enter.prevent="searchSubmit"
>
<template v-slot:append>
<q-icon
name="close"
@click="() => (searchData.value = '', isSearch = false)"
class="cursor-pointer"
/>
</template>
</q-input>
</div>
</div>
<div class="column">
<div class="row items-center justify-between q-gutter-y-md q-pt-sm">
<div class="column col-grow">
<advanced-search
ref="advSearchComp"
:searchSubmit="searchSubmit"
:submit-search-data="submitSearchData"
/>
</div>
<div v-if="isAdvSearchCall === false">
<q-btn
style="width: 150px"
color="primary"
label="ค้นหา"
icon="mdi-magnify"
@click="searchSubmit"
/>
</div>
</div>
</div>
<div class="col-3" v-if="mode === 'admin'">
<q-input
rounded
outlined
dense
label="ค้นหา"
bg-color="white"
v-model="searchData.value"
id="inputSearch"
@keydown.enter.prevent="searchSubmit"
>
<template v-slot:append><q-icon name="search" /></template>
</q-input>
</div>
<div v-if="mode === 'user'">
<div class="q-pa-md bg-grey-1">
<div class="row items-center q-col-gutter-md">
<div class="col-12 col-md-3">
<q-select
id="searchField"
dense
outlined
emit-value
map-options
class="bg-white"
v-model="searchData.field"
:options="optionsField"
/>
</div>
<div class="col-12 col-md-grow">
<q-input
id="searchValue"
class="bg-white"
dense
outlined
v-model="searchData.value"
placeholder="เอกสาร"
@keydown.enter.prevent="searchSubmit"
>
<template v-slot:append>
<q-icon
name="close"
@click="() => ((searchData.value = ''), (isSearch = false))"
class="cursor-pointer"
/>
</template>
</q-input>
</div>
</div>
<div class="column">
<div class="row items-center justify-between q-gutter-y-md q-pt-sm">
<div class="column col-grow">
<advanced-search
ref="advSearchComp"
:searchSubmit="searchSubmit"
:submit-search-data="submitSearchData"
/>
</div>
<div v-if="isAdvSearchCall === false">
<q-btn
style="width: 150px"
color="primary"
label="ค้นหา"
icon="mdi-magnify"
@click="searchSubmit"
/>
</div>
</div>
</div>
</div>
<q-separator />
</div>
<q-separator />
</template>

View file

@ -40,9 +40,6 @@ export interface TreeDataFolder {
export const useTreeDataStore = defineStore('changeCabinet', () => {
const loader = useLoader()
const indexToRemove = ref<number>()
const fileNameRemove = ref<string>('')
const data = ref<TreeDataFolder[]>([])
const currentFolder = ref<TreeDataFolder[]>([])
const currentFile = ref<EhrFile[]>([])
@ -96,7 +93,7 @@ export const useTreeDataStore = defineStore('changeCabinet', () => {
currentPath.value = pathname
const res = await axiosClient.get<EhrFolder[]>(
`${apiEndpoint}${requestPath}`,
`${apiEndpoint}${requestPath}`
)
const list = res.data.map((v) => ({
@ -246,9 +243,9 @@ export const useTreeDataStore = defineStore('changeCabinet', () => {
metadata: {
title: string
description: string
keyword: string
category: string
},
keyword: string[]
category: string[]
}
) {
loader.show()
@ -268,7 +265,7 @@ export const useTreeDataStore = defineStore('changeCabinet', () => {
{
file: file.name,
...metadata,
},
}
)
if (res && res.data.upload) {
@ -294,10 +291,10 @@ export const useTreeDataStore = defineStore('changeCabinet', () => {
metadata: {
title: string
description: string
keyword: string
category: string
keyword: string[]
category: string[]
},
file?: File,
file?: File
) {
loader.show()
@ -312,7 +309,7 @@ export const useTreeDataStore = defineStore('changeCabinet', () => {
const res = await axiosClient.patch<{ upload: string }>(
`${apiEndpoint}${requestPath}`,
{ file: file?.name, ...metadata },
{ file: file?.name, ...metadata }
)
if (res && res.data.upload) {
@ -354,7 +351,7 @@ export const useTreeDataStore = defineStore('changeCabinet', () => {
await getFile(currentPath.value)
currentFile.value = currentFile.value.filter((v) => v.pathname !== pathname)
listDataFile.value = currentFile.value
return loader.hide()
}
@ -366,6 +363,26 @@ export const useTreeDataStore = defineStore('changeCabinet', () => {
return fileName.length >= 85
}
async function refaceFile(pathname: string) {
const pathArray: string[] = pathname.split('/').filter(Boolean)
if (pathArray.length <= 2) {
currentFile.value = []
return loader.hide()
}
let requestPath = `cabinet/${pathArray[0]}/drawer/${pathArray[1]}`
if (pathArray.length >= 3) requestPath += `/folder/${pathArray[2]}`
if (pathArray.length >= 4) requestPath += `/subfolder/${pathArray[3]}`
requestPath += '/file'
const res = await axiosClient.get<EhrFile[]>(`${apiEndpoint}${requestPath}`)
currentFile.value = res.data
listDataFile.value = currentFile.value
}
return {
data,
currentFolder,
@ -385,5 +402,6 @@ export const useTreeDataStore = defineStore('changeCabinet', () => {
editFolder,
checkFile,
checkFileName,
refaceFile,
}
})

View file

@ -1,66 +0,0 @@
version: "3.8"
name: "ehr"
services:
elasticsearch:
build:
context: .
restart: unless-stopped
ports:
- 9200:9200
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
environment:
- xpack.security.enabled=false
- discovery.type=single-node
kibana:
image: kibana:8.10.2
restart: unless-stopped
depends_on:
- elasticsearch
ports:
- 5601:5601
volumes:
- kibana-data:/usr/share/kibana/data
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
minio:
image: minio/minio:latest
restart: unless-stopped
depends_on:
- elasticsearch
command: server --console-address ":9001" /data
ports:
- 9000:9000
- 9001:9001
volumes:
- minio-data:/data
environment:
- MINIO_ROOT_USER=ehr
- MINIO_ROOT_PASSWORD=P@ssw0rd
keycloak:
image: quay.io/keycloak/keycloak:22.0.3
restart: unless-stopped
command:
- start-dev
ports:
- 8080:8080
volumes:
- keycloak-data:/opt/keycloak/data
environment:
- KEYCLOAK_ADMIN=ehr
- KEYCLOAK_ADMIN_PASSWORD=P@ssw0rd
volumes:
elasticsearch-data:
driver: local
kibana-data:
driver: local
minio-data:
driver: local
keycloak-data:
driver: local

View file

@ -2,6 +2,7 @@ import {
Body,
Controller,
Delete,
Example,
Get,
Patch,
Path,
@ -21,7 +22,7 @@ import HttpStatusCode from "../interfaces/http-status";
import { StorageFile } from "../interfaces/storage-fs";
import HttpError from "../interfaces/http-error";
import { copyCond, pathExist } from "../utils/minio";
import { copyCond, pathExist, replaceIllegalChars } from "../utils/minio";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
@ -31,10 +32,33 @@ if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified."
@Route("/cabinet/{cabinetName}/drawer/{drawerName}/folder/{folderName}/file")
export class FileController extends Controller {
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Get("/")
@Tags("ไฟล์")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example([
{
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
fileName: "เอกสาร 1",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
},
])
public async getFile(
@Path() cabinetName: string,
@Path() drawerName: string,
@ -62,6 +86,11 @@ export class FileController extends Controller {
return records;
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
*/
@Post("/")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin"])
@ -70,15 +99,46 @@ export class FileController extends Controller {
"ตำแหน่งที่ระบุไม่พบ กรุณาเตรียมตำแหน่งที่ต้องการก่อนดำเนินการ",
)
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
@Example({
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
fileName: "เอกสาร 1",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
})
public async uploadFile(
@Request() request: { user: { preferred_username: string } },
@Body()
body: {
/**
* @example "เอกสาร 1"
*/
file: string;
/**
* @example "เอกสาร"
*/
title?: string;
/**
* @example "เอกสารการเงิน"
*/
description?: string;
category?: string;
keyword?: string;
/**
* @example ["บัญชี"]
*/
category?: string[];
/**
* @example ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"]
*/
keyword?: string[];
},
@Path() cabinetName: string,
@Path() drawerName: string,
@ -89,7 +149,7 @@ export class FileController extends Controller {
}
const basePath = `${cabinetName}/${drawerName}/${folderName}/`;
const pathname = `${basePath}${body.file}`;
const pathname = `${basePath}${replaceIllegalChars(body.file)}`;
if (!(await pathExist(DEFAULT_BUCKET!, basePath))) {
throw new HttpError(
@ -120,13 +180,13 @@ export class FileController extends Controller {
const metadata: Partial<StorageFile> = {
pathname,
path: basePath,
fileName: body.file,
fileName: replaceIllegalChars( body.file ),
fileSize: 0,
fileType: "",
title: body.title ?? "",
description: body.description ?? "",
category: body.category?.split(",") ?? [],
keyword: body.keyword?.split(",") ?? [],
category: body.category ?? [],
keyword: body.keyword ?? [],
upload: false,
createdAt: new Date().toISOString(),
createdBy: rec ? rec.createdBy : "n/a",
@ -150,6 +210,12 @@ export class FileController extends Controller {
};
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example fileName "เอกสาร 1"
*/
@Patch("/{fileName}")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin"])
@ -164,11 +230,26 @@ export class FileController extends Controller {
@Path() fileName: string,
@Body()
body: {
/**
* @example "เอกสารใหม่"
*/
file?: string;
/**
* @example "เอกสารการเงิน"
*/
title?: string;
/**
* @example "เอกสารการเงินฉบับใหม่"
*/
description?: string;
category?: string;
keyword?: string;
/**
* @example ["บัญชี"]
*/
category?: string[];
/**
* @example ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"]
*/
keyword?: string[];
},
): Promise<void | { upload: string }> {
if (body.file && body.file.length > 85) {
@ -191,7 +272,7 @@ export class FileController extends Controller {
// assume user will probably replace file by re-upload but maybe just rename
if (body.file) {
const destination = `${basePath}${body.file}`;
const destination = `${basePath}${replaceIllegalChars(body.file)}`;
const source = `/${DEFAULT_BUCKET}/${basePath}${fileName}`;
const copy = await minioClient.copyObject(DEFAULT_BUCKET!, destination, source, copyCond);
@ -212,7 +293,7 @@ export class FileController extends Controller {
doc: {
pathname: destination,
path: basePath,
fileName: body.file,
fileName: replaceIllegalChars(body.file),
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
@ -238,8 +319,6 @@ export class FileController extends Controller {
id,
doc: {
...body,
keyword: body.keyword?.split(","),
category: body.category?.split(","),
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
@ -252,12 +331,18 @@ export class FileController extends Controller {
? {
upload: await minioClient.presignedPutObject(
DEFAULT_BUCKET!,
`${basePath}${body.file ?? fileName}`,
`${basePath}${replaceIllegalChars(body.file) ?? fileName}`,
),
}
: this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example fileName "เอกสารใหม่"
*/
@Delete("/{fileName}")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin"])
@ -275,6 +360,12 @@ export class FileController extends Controller {
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example fileName "เอกสารใหม่"
*/
@Get("/{fileName}")
@Tags("ดาวน์โหลด")
@Security("bearerAuth")

View file

@ -1,104 +0,0 @@
import { Body, Controller, Get, Path, Post, Put, Query, Route, SuccessResponse } from "tsoa";
import { replaceIllegalChars } from "../utils/minio";
import minioClient from "../minio";
import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
if (!DEFAULT_BUCKET) throw Error("Default MinIO bucket must be specified.");
if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified.");
@Route("/storage/d")
export class StorageController extends Controller {
@Get()
@SuccessResponse(HttpStatusCode.OK, "Success")
public async getFolder(@Query() path: string, @Query() bucket?: string) {
const list = await new Promise<{ pathname: string; name: string }[]>((resolve, reject) => {
const item: { pathname: string; name: string }[] = [];
const stream = minioClient.listObjectsV2(bucket ?? DEFAULT_BUCKET!, path);
stream.on("data", (v) => {
if (v && v.prefix) {
item.push({
pathname: v.prefix,
name: v.prefix.slice(path?.length).split("/")[0],
});
}
});
stream.on("end", () => resolve(item));
stream.on("error", () => reject(new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์")));
});
return list;
}
@Post()
@SuccessResponse(HttpStatusCode.CREATED, "Success")
public async createFolder(@Query() path: string, @Query() bucket?: string) {
const fragments = path.split("/").filter(Boolean);
await Promise.all(
fragments.map(async (_, i, a) => {
const path = [...a.slice(0, i + 1)].map((x) => replaceIllegalChars(x)).join("/");
const created = await minioClient
.putObject(bucket ?? DEFAULT_BUCKET!, `${path}/.keep`, "", 0)
.catch((e) => console.error(e));
if (!created) throw new Error("เกิดข้อผิดพลาดกับระบบจัดการไฟล์");
}),
);
return this.setStatus(HttpStatusCode.CREATED);
}
@Put()
@SuccessResponse(HttpStatusCode.NO_CONTENT, "Success")
public async updateFolder(
@Body()
body: {
from: {
bucket: string;
path: string;
};
to: {
bucket: string;
path: string;
};
},
) {
const src = body.from.path.split("/").filter(Boolean).join("/");
const dst = body.to.path.split("/").filter(Boolean).join("/");
if (
!Boolean(
await minioClient
.statObject(DEFAULT_BUCKET!, `${dst.replace(/^\/|\/$/g, "")}/.keep`)
.catch((e) => {
if (e.code === "NotFound") return false;
throw new Error(`Minio Error: ${e}`);
}),
)
)
throw new HttpError(HttpStatusCode.NOT_FOUND, "Destination Not Found");
const list = await new Promise<{ pathname: string; name: string }[]>((resolve, reject) => {
const item: { pathname: string; name: string }[] = [];
const stream = minioClient.listObjectsV2(body.from.bucket, src);
stream.on("data", (v) => {
if (v && v.prefix) {
item.push({
pathname: v.prefix,
name: v.prefix.slice(src.length).split("/")[0],
});
}
});
stream.on("error", (e) => reject(new Error(`Minio Error: ${e}`)));
stream.on("end", () => resolve(item));
});
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
}

View file

@ -2,6 +2,7 @@ import {
Body,
Controller,
Delete,
Example,
Get,
Patch,
Path,
@ -21,7 +22,7 @@ import HttpStatusCode from "../interfaces/http-status";
import { StorageFile } from "../interfaces/storage-fs";
import HttpError from "../interfaces/http-error";
import { copyCond, pathExist } from "../utils/minio";
import { copyCond, pathExist, replaceIllegalChars } from "../utils/minio";
const DEFAULT_BUCKET = process.env.MINIO_BUCKET;
const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX;
@ -33,10 +34,34 @@ if (!DEFAULT_INDEX) throw Error("Default ElasticSearch index must be specified."
"/cabinet/{cabinetName}/drawer/{drawerName}/folder/{folderName}/subfolder/{subFolderName}/file",
)
export class SubFolderFileController extends Controller {
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
*/
@Get("/")
@Tags("ไฟล์")
@Security("bearerAuth")
@SuccessResponse(HttpStatusCode.OK, "สำเร็จ")
@Example([
{
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/เอกสาร 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
fileName: "เอกสาร 1",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
},
])
public async getFile(
@Path() cabinetName: string,
@Path() drawerName: string,
@ -65,6 +90,12 @@ export class SubFolderFileController extends Controller {
return records;
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
*/
@Post("/")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin"])
@ -73,15 +104,46 @@ export class SubFolderFileController extends Controller {
"ตำแหน่งที่ระบุไม่พบ กรุณาเตรียมตำแหน่งที่ต้องการก่อนดำเนินการ",
)
@SuccessResponse(HttpStatusCode.CREATED, "สำเร็จ")
@Example({
pathname: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/เอกสาร 1",
path: "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/",
title: "เอกสาร",
description: "เอกสารการเงิน",
category: ["บัญชี"],
keyword: ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"],
upload: false,
fileName: "เอกสาร 1",
fileSize: 10240,
fileType: "application/pdf",
createdAt: "2021-07-20T12:33:13.018Z",
createdBy: "admin",
updatedAt: "2021-07-20T12:33:13.018Z",
updatedBy: "admin",
})
public async uploadFile(
@Request() request: { user: { preferred_username: string } },
@Body()
body: {
/**
* @example "เอกสาร 1"
*/
file: string;
/**
* @example "เอกสาร"
*/
title?: string;
/**
* @example "เอกสารการเงิน"
*/
description?: string;
category?: string;
keyword?: string;
/**
* @example ["บัญชี"]
*/
category?: string[];
/**
* @example ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"]
*/
keyword?: string[];
},
@Path() cabinetName: string,
@Path() drawerName: string,
@ -93,7 +155,7 @@ export class SubFolderFileController extends Controller {
}
const basePath = `${cabinetName}/${drawerName}/${folderName}/${subFolderName}/`;
const pathname = `${basePath}${body.file}`;
const pathname = `${basePath}${replaceIllegalChars(body.file)}`;
if (!(await pathExist(DEFAULT_BUCKET!, basePath))) {
throw new HttpError(
@ -124,13 +186,13 @@ export class SubFolderFileController extends Controller {
const metadata: Partial<StorageFile> = {
pathname,
path: basePath,
fileName: body.file,
fileName: replaceIllegalChars(body.file),
fileSize: 0,
fileType: "",
title: body.title ?? "",
description: body.description ?? "",
category: body.category?.split(",") ?? [],
keyword: body.keyword?.split(",") ?? [],
category: body.category ?? [],
keyword: body.keyword ?? [],
upload: false,
createdAt: new Date().toISOString(),
createdBy: rec ? rec.createdBy : "n/a",
@ -154,6 +216,13 @@ export class SubFolderFileController extends Controller {
};
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
* @example fileName "เอกสาร 1"
*/
@Patch("/{fileName}")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin"])
@ -168,11 +237,26 @@ export class SubFolderFileController extends Controller {
@Path() fileName: string,
@Body()
body: {
/**
* @example "เอกสารใหม่"
*/
file?: string;
/**
* @example "เอกสารการเงิน"
*/
title?: string;
/**
* @example "เอกสารการเงินฉบับใหม่"
*/
description?: string;
category?: string;
keyword?: string;
/**
* @example ["บัญชี"]
*/
category?: string[];
/**
* @example ["เงิน", "บัญชี", "รายจ่าย", "รายรับ"]
*/
keyword?: string[];
},
) {
if (body.file && body.file.length > 85) {
@ -195,7 +279,7 @@ export class SubFolderFileController extends Controller {
// assume user will probably replace file by re-upload but maybe just rename
if (body.file) {
const destination = `${basePath}${body.file}`;
const destination = `${basePath}${replaceIllegalChars(body.file)}`;
const source = `/${DEFAULT_BUCKET}/${basePath}${fileName}`;
const copy = await minioClient.copyObject(DEFAULT_BUCKET!, destination, source, copyCond);
@ -215,7 +299,7 @@ export class SubFolderFileController extends Controller {
id,
doc: {
pathname: destination,
fileName: body.file,
fileName: replaceIllegalChars(body.file),
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
@ -241,8 +325,6 @@ export class SubFolderFileController extends Controller {
id,
doc: {
...body,
keyword: body.keyword?.split(","),
category: body.category?.split(","),
updatedAt: new Date().toISOString(),
updatedBy: request.user.preferred_username ?? "n/a",
},
@ -255,12 +337,19 @@ export class SubFolderFileController extends Controller {
? {
upload: await minioClient.presignedPutObject(
DEFAULT_BUCKET!,
`${basePath}${body.file ?? fileName}`,
`${basePath}${replaceIllegalChars(body.file) ?? fileName}`,
),
}
: this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example subFolderName "แฟ้มย่อย 1"
* @example fileName "เอกสาร 1"
*/
@Delete("/{fileName}")
@Tags("ไฟล์")
@Security("bearerAuth", ["admin"])
@ -279,6 +368,13 @@ export class SubFolderFileController extends Controller {
return this.setStatus(HttpStatusCode.NO_CONTENT);
}
/**
* @example cabinetName "ตู้เอกสาร 1"
* @example drawerName "ลิ้นชัก 1"
* @example folderName "แฟ้ม 1"
* @example folderName "แฟ้มย่อย 1"
* @example fileName "เอกสาร 1"
*/
@Get("/{fileName}")
@Tags("ดาวน์โหลด")
@Security("bearerAuth")

View file

@ -21,9 +21,19 @@ export async function handler(key: string, event: string): Promise<boolean> {
}
if (!cachedBuffer[key]) {
const stream = await minioClient.getObject(bucket, pathname);
const buffer = Buffer.concat(await stream.toArray());
cachedBuffer[key] = buffer;
try {
const stream = await minioClient.getObject(bucket, pathname);
const buffer = Buffer.concat(await stream.toArray());
cachedBuffer[key] = buffer;
} catch (e: any) {
if (e.code === "NoSuchKey") {
console.info(`[AMQ] Key: ${key} received but cannot be found.`)
delete cachedBuffer[key];
delete cachedMetadata[key];
await ensureDelete(pathname);
return true;
}
}
}
if (!cachedMetadata[key]) {
@ -33,6 +43,8 @@ export async function handler(key: string, event: string): Promise<boolean> {
const rec = await popInfo(pathname);
console.info(`[AMQ] Key: ${key} - ${rec ?? 'Not Found.'}`)
const result = rec
? await handleFoundRecord(rec, cachedBuffer[key], cachedMetadata[key])
: await handleNotFoundRecord(pathname, cachedBuffer[key], cachedMetadata[key]);

View file

@ -13,8 +13,6 @@ import { FolderController } from './controllers/folderController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { SearchController } from './controllers/searchController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { StorageController } from './controllers/storageController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { SubFolderController } from './controllers/subFolderController';
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
import { SubFolderFileController } from './controllers/subFolderFileController';
@ -327,7 +325,7 @@ export function RegisterRoutes(app: Router) {
function FileController_uploadFile(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"string"},"category":{"dataType":"string"},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string","required":true}}},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"array","array":{"dataType":"string"}},"category":{"dataType":"array","array":{"dataType":"string"}},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string","required":true}}},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
@ -361,7 +359,7 @@ export function RegisterRoutes(app: Router) {
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
fileName: {"in":"path","name":"fileName","required":true,"dataType":"string"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"string"},"category":{"dataType":"string"},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string"}}},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"array","array":{"dataType":"string"}},"category":{"dataType":"array","array":{"dataType":"string"}},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string"}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
@ -577,83 +575,6 @@ export function RegisterRoutes(app: Router) {
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/storage/d',
...(fetchMiddlewares<RequestHandler>(StorageController)),
...(fetchMiddlewares<RequestHandler>(StorageController.prototype.getFolder)),
function StorageController_getFolder(request: any, response: any, next: any) {
const args = {
path: {"in":"query","name":"path","required":true,"dataType":"string"},
bucket: {"in":"query","name":"bucket","dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new StorageController();
const promise = controller.getFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 200, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.post('/storage/d',
...(fetchMiddlewares<RequestHandler>(StorageController)),
...(fetchMiddlewares<RequestHandler>(StorageController.prototype.createFolder)),
function StorageController_createFolder(request: any, response: any, next: any) {
const args = {
path: {"in":"query","name":"path","required":true,"dataType":"string"},
bucket: {"in":"query","name":"bucket","dataType":"string"},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new StorageController();
const promise = controller.createFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 201, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.put('/storage/d',
...(fetchMiddlewares<RequestHandler>(StorageController)),
...(fetchMiddlewares<RequestHandler>(StorageController.prototype.updateFolder)),
function StorageController_updateFolder(request: any, response: any, next: any) {
const args = {
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"to":{"dataType":"nestedObjectLiteral","nestedProperties":{"path":{"dataType":"string","required":true},"bucket":{"dataType":"string","required":true}},"required":true},"from":{"dataType":"nestedObjectLiteral","nestedProperties":{"path":{"dataType":"string","required":true},"bucket":{"dataType":"string","required":true}},"required":true}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request, response);
const controller = new StorageController();
const promise = controller.updateFolder.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, 204, next);
} catch (err) {
return next(err);
}
});
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
app.get('/cabinet/:cabinetName/drawer/:drawerName/folder/:folderName/subfolder',
authenticateMiddleware([{"bearerAuth":[]}]),
...(fetchMiddlewares<RequestHandler>(SubFolderController)),
@ -808,7 +729,7 @@ export function RegisterRoutes(app: Router) {
function SubFolderFileController_uploadFile(request: any, response: any, next: any) {
const args = {
request: {"in":"request","name":"request","required":true,"dataType":"object"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"string"},"category":{"dataType":"string"},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string","required":true}}},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"array","array":{"dataType":"string"}},"category":{"dataType":"array","array":{"dataType":"string"}},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string","required":true}}},
cabinetName: {"in":"path","name":"cabinetName","required":true,"dataType":"string"},
drawerName: {"in":"path","name":"drawerName","required":true,"dataType":"string"},
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
@ -844,7 +765,7 @@ export function RegisterRoutes(app: Router) {
folderName: {"in":"path","name":"folderName","required":true,"dataType":"string"},
subFolderName: {"in":"path","name":"subFolderName","required":true,"dataType":"string"},
fileName: {"in":"path","name":"fileName","required":true,"dataType":"string"},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"string"},"category":{"dataType":"string"},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string"}}},
body: {"in":"body","name":"body","required":true,"dataType":"nestedObjectLiteral","nestedProperties":{"keyword":{"dataType":"array","array":{"dataType":"string"}},"category":{"dataType":"array","array":{"dataType":"string"}},"description":{"dataType":"string"},"title":{"dataType":"string"},"file":{"dataType":"string"}}},
};
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa

View file

@ -599,6 +599,35 @@
"$ref": "#/components/schemas/StorageFile"
},
"type": "array"
},
"examples": {
"Example 1": {
"value": [
{
"pathname": "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1",
"path": "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
"title": "เอกสาร",
"description": "เอกสารการเงิน",
"category": [
"บัญชี"
],
"keyword": [
"เงิน",
"บัญชี",
"รายจ่าย",
"รายรับ"
],
"upload": false,
"fileName": "เอกสาร 1",
"fileSize": 10240,
"fileType": "application/pdf",
"createdAt": "2021-07-20T12:33:13.018Z",
"createdBy": "admin",
"updatedAt": "2021-07-20T12:33:13.018Z",
"updatedBy": "admin"
}
]
}
}
}
}
@ -619,7 +648,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -627,7 +657,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -635,7 +666,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
}
]
},
@ -649,10 +681,16 @@
"schema": {
"properties": {
"keyword": {
"type": "string"
"items": {
"type": "string"
},
"type": "array"
},
"category": {
"type": "string"
"items": {
"type": "string"
},
"type": "array"
},
"description": {
"type": "string"
@ -704,6 +742,33 @@
"createdAt"
],
"type": "object"
},
"examples": {
"Example 1": {
"value": {
"pathname": "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/เอกสาร 1",
"path": "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/",
"title": "เอกสาร",
"description": "เอกสารการเงิน",
"category": [
"บัญชี"
],
"keyword": [
"เงิน",
"บัญชี",
"รายจ่าย",
"รายรับ"
],
"upload": false,
"fileName": "เอกสาร 1",
"fileSize": 10240,
"fileType": "application/pdf",
"createdAt": "2021-07-20T12:33:13.018Z",
"createdBy": "admin",
"updatedAt": "2021-07-20T12:33:13.018Z",
"updatedBy": "admin"
}
}
}
}
}
@ -729,7 +794,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -737,7 +803,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -745,7 +812,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
}
],
"requestBody": {
@ -755,19 +823,37 @@
"schema": {
"properties": {
"keyword": {
"type": "string"
"items": {
"type": "string"
},
"type": "array",
"example": [
"เงิน",
"บัญชี",
"รายจ่าย",
"รายรับ"
]
},
"category": {
"type": "string"
"items": {
"type": "string"
},
"type": "array",
"example": [
"บัญชี"
]
},
"description": {
"type": "string"
"type": "string",
"example": "เอกสารการเงิน"
},
"title": {
"type": "string"
"type": "string",
"example": "เอกสาร"
},
"file": {
"type": "string"
"type": "string",
"example": "เอกสาร 1"
}
},
"required": [
@ -831,7 +917,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -839,7 +926,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -847,7 +935,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
},
{
"in": "path",
@ -855,7 +944,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "เอกสาร 1"
}
],
"requestBody": {
@ -865,19 +955,37 @@
"schema": {
"properties": {
"keyword": {
"type": "string"
"items": {
"type": "string"
},
"type": "array",
"example": [
"เงิน",
"บัญชี",
"รายจ่าย",
"รายรับ"
]
},
"category": {
"type": "string"
"items": {
"type": "string"
},
"type": "array",
"example": [
"บัญชี"
]
},
"description": {
"type": "string"
"type": "string",
"example": "เอกสารการเงินฉบับใหม่"
},
"title": {
"type": "string"
"type": "string",
"example": "เอกสารการเงิน"
},
"file": {
"type": "string"
"type": "string",
"example": "เอกสารใหม่"
}
},
"type": "object"
@ -910,7 +1018,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -918,7 +1027,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -926,7 +1036,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
},
{
"in": "path",
@ -934,7 +1045,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "เอกสารใหม่"
}
]
},
@ -1054,7 +1166,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -1062,7 +1175,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -1070,7 +1184,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
},
{
"in": "path",
@ -1078,7 +1193,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "เอกสารใหม่"
}
]
}
@ -1372,140 +1488,6 @@
}
}
},
"/storage/d": {
"get": {
"operationId": "GetFolder",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"items": {
"properties": {
"name": {
"type": "string"
},
"pathname": {
"type": "string"
}
},
"required": [
"name",
"pathname"
],
"type": "object"
},
"type": "array"
}
}
}
}
},
"security": [],
"parameters": [
{
"in": "query",
"name": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "bucket",
"required": false,
"schema": {
"type": "string"
}
}
]
},
"post": {
"operationId": "CreateFolder",
"responses": {
"201": {
"description": "Success"
}
},
"security": [],
"parameters": [
{
"in": "query",
"name": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "bucket",
"required": false,
"schema": {
"type": "string"
}
}
]
},
"put": {
"operationId": "UpdateFolder",
"responses": {
"204": {
"description": "Success"
}
},
"security": [],
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"properties": {
"to": {
"properties": {
"path": {
"type": "string"
},
"bucket": {
"type": "string"
}
},
"required": [
"path",
"bucket"
],
"type": "object"
},
"from": {
"properties": {
"path": {
"type": "string"
},
"bucket": {
"type": "string"
}
},
"required": [
"path",
"bucket"
],
"type": "object"
}
},
"required": [
"to",
"from"
],
"type": "object"
}
}
}
}
}
},
"/cabinet/{cabinetName}/drawer/{drawerName}/folder/{folderName}/subfolder": {
"get": {
"operationId": "ListFolder",
@ -1804,6 +1786,35 @@
"$ref": "#/components/schemas/StorageFile"
},
"type": "array"
},
"examples": {
"Example 1": {
"value": [
{
"pathname": "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/เอกสาร 1",
"path": "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/",
"title": "เอกสาร",
"description": "เอกสารการเงิน",
"category": [
"บัญชี"
],
"keyword": [
"เงิน",
"บัญชี",
"รายจ่าย",
"รายรับ"
],
"upload": false,
"fileName": "เอกสาร 1",
"fileSize": 10240,
"fileType": "application/pdf",
"createdAt": "2021-07-20T12:33:13.018Z",
"createdBy": "admin",
"updatedAt": "2021-07-20T12:33:13.018Z",
"updatedBy": "admin"
}
]
}
}
}
}
@ -1824,7 +1835,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -1832,7 +1844,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -1840,7 +1853,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
},
{
"in": "path",
@ -1848,7 +1862,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้มย่อย 1"
}
]
},
@ -1862,10 +1877,16 @@
"schema": {
"properties": {
"keyword": {
"type": "string"
"items": {
"type": "string"
},
"type": "array"
},
"category": {
"type": "string"
"items": {
"type": "string"
},
"type": "array"
},
"description": {
"type": "string"
@ -1917,6 +1938,33 @@
"createdAt"
],
"type": "object"
},
"examples": {
"Example 1": {
"value": {
"pathname": "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/เอกสาร 1",
"path": "ตู้เอกสาร 1/ลิ้นชัก 1/แฟ้ม 1/แฟ้มย่อย 1/",
"title": "เอกสาร",
"description": "เอกสารการเงิน",
"category": [
"บัญชี"
],
"keyword": [
"เงิน",
"บัญชี",
"รายจ่าย",
"รายรับ"
],
"upload": false,
"fileName": "เอกสาร 1",
"fileSize": 10240,
"fileType": "application/pdf",
"createdAt": "2021-07-20T12:33:13.018Z",
"createdBy": "admin",
"updatedAt": "2021-07-20T12:33:13.018Z",
"updatedBy": "admin"
}
}
}
}
}
@ -1942,7 +1990,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -1950,7 +1999,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -1958,7 +2008,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
},
{
"in": "path",
@ -1966,7 +2017,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้มย่อย 1"
}
],
"requestBody": {
@ -1976,19 +2028,37 @@
"schema": {
"properties": {
"keyword": {
"type": "string"
"items": {
"type": "string"
},
"type": "array",
"example": [
"เงิน",
"บัญชี",
"รายจ่าย",
"รายรับ"
]
},
"category": {
"type": "string"
"items": {
"type": "string"
},
"type": "array",
"example": [
"บัญชี"
]
},
"description": {
"type": "string"
"type": "string",
"example": "เอกสารการเงิน"
},
"title": {
"type": "string"
"type": "string",
"example": "เอกสาร"
},
"file": {
"type": "string"
"type": "string",
"example": "เอกสาร 1"
}
},
"required": [
@ -2049,7 +2119,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -2057,7 +2128,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -2065,7 +2137,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
},
{
"in": "path",
@ -2073,7 +2146,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้มย่อย 1"
},
{
"in": "path",
@ -2081,7 +2155,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "เอกสาร 1"
}
],
"requestBody": {
@ -2091,19 +2166,37 @@
"schema": {
"properties": {
"keyword": {
"type": "string"
"items": {
"type": "string"
},
"type": "array",
"example": [
"เงิน",
"บัญชี",
"รายจ่าย",
"รายรับ"
]
},
"category": {
"type": "string"
"items": {
"type": "string"
},
"type": "array",
"example": [
"บัญชี"
]
},
"description": {
"type": "string"
"type": "string",
"example": "เอกสารการเงินฉบับใหม่"
},
"title": {
"type": "string"
"type": "string",
"example": "เอกสารการเงิน"
},
"file": {
"type": "string"
"type": "string",
"example": "เอกสารใหม่"
}
},
"type": "object"
@ -2136,7 +2229,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -2144,7 +2238,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -2152,7 +2247,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้ม 1"
},
{
"in": "path",
@ -2160,7 +2256,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "แฟ้มย่อย 1"
},
{
"in": "path",
@ -2168,7 +2265,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "เอกสาร 1"
}
]
},
@ -2288,7 +2386,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ตู้เอกสาร 1"
},
{
"in": "path",
@ -2296,7 +2395,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "ลิ้นชัก 1"
},
{
"in": "path",
@ -2304,6 +2404,14 @@
"required": true,
"schema": {
"type": "string"
},
"examples": {
"Example 1": {
"value": "แฟ้ม 1"
},
"Example 2": {
"value": "แฟ้มย่อย 1"
}
}
},
{
@ -2320,7 +2428,8 @@
"required": true,
"schema": {
"type": "string"
}
},
"example": "เอกสาร 1"
}
]
}

View file

@ -9,7 +9,7 @@ import minioClient from "../minio";
* @returns illegal character replaced path
*/
export function replaceIllegalChars(path: string, replace = "-") {
return path.replace(/[/\\?%*:|"<>]/g, replace);
return path.replace(/[/\\?%*:|"<>#]/g, replace);
}
/**

View file

@ -1,4 +1,5 @@
{
"version":"Auto gen version",
"builddate":"2020-02-02_22:22:22"
}
{
"version": "dev",
"builddate": "2020-01-01_00:00:00"
}

View file

@ -27,21 +27,49 @@ esClient.ingest.putPipeline({
},
});
esClient.indices.putMapping({
esClient.indices.create({
index: DEFAULT_INDEX,
body: {
settings: {
index: {
analysis: {
analyzer: {
analyzer_shingle: {
type: "custom",
tokenizer: "icu_tokenizer",
filter: ["filter_shingle"],
},
},
filter: {
filter_shingle: {
type: "shingle",
max_shingle_size: 3,
min_shingle_size: 2,
output_unigrams: true,
},
},
},
},
},
mappings: {
properties: {
pathname: {
type: "keyword",
},
path: {
type: "keyword",
},
path: { type: "keyword" },
pathname: { type: "keyword" },
fileName: { type: "text", analyzer: "analyzer_shingle" },
title: { type: "text", analyzer: "analyzer_shingle" },
description: { type: "text", analyzer: "analyzer_shingle" },
"attachment.content": { type: "text", analyzer: "analyzer_shingle" },
},
},
});
minioClient.makeBucket(DEFAULT_BUCKET!, (e) => {
if (!e) console.log("Success. Configuration is needed for Bucket Notification to AMQP.");
console.error(e);
if (!e) {
console.log("Success.");
console.log("Configuration is required for Bucket Notification to AMQP.");
console.log("Configuration is required for Keycloak.");
} else {
console.error(e);
}
});

View file

@ -0,0 +1,516 @@
doLogIn=LOGIN
doRegister=Register
doCancel=Cancel
doSubmit=Submit
doBack=Back
doYes=Yes
doNo=No
doContinue=Continue
doIgnore=Ignore
doAccept=Accept
doDecline=Decline
doForgotPassword=Forgot Password?
doClickHere=Click here
doImpersonate=Impersonate
doTryAgain=Try again
doTryAnotherWay=Try Another Way
doConfirmDelete=Confirm deletion
errorDeletingAccount=Error happened while deleting account
deletingAccountForbidden=You do not have enough permissions to delete your own account, contact admin.
kerberosNotConfigured=Kerberos Not Configured
kerberosNotConfiguredTitle=Kerberos Not Configured
bypassKerberosDetail=Either you are not logged in by Kerberos or your browser is not set up for Kerberos login. Please click continue to login in through other means
kerberosNotSetUp=Kerberos is not set up. You cannot login.
registerTitle=Register
loginAccountTitle=Enterprise Document Management (EDM)
loginTitle=Sign in to {0}
loginTitleHtml={0}
impersonateTitle={0} Impersonate User
impersonateTitleHtml=<strong>{0}</strong> Impersonate User
realmChoice=Realm
unknownUser=Unknown user
loginTotpTitle=Mobile Authenticator Setup
loginProfileTitle=Update Account Information
loginIdpReviewProfileTitle=Update Account Information
loginTimeout=Your login attempt timed out. Login will start from the beginning.
reauthenticate=Please re-authenticate to continue
oauthGrantTitle=Grant Access to {0}
oauthGrantTitleHtml={0}
oauthGrantInformation=Make sure you trust {0} by learning how {0} will handle your data.
oauthGrantReview=You could review the
oauthGrantTos=terms of service.
oauthGrantPolicy=privacy policy.
errorTitle=We are sorry...
errorTitleHtml=We are <strong>sorry</strong> ...
emailVerifyTitle=Email verification
emailForgotTitle=Forgot Your Password?
updateEmailTitle=Update email
emailUpdateConfirmationSentTitle=Confirmation email sent
emailUpdateConfirmationSent=A confirmation email has been sent to {0}. You must follow the instructions of the former to complete the email update.
emailUpdatedTitle=Email updated
emailUpdated=The account email has been successfully updated to {0}.
updatePasswordTitle=Update password
codeSuccessTitle=Success code
codeErrorTitle=Error code\: {0}
displayUnsupported=Requested display type unsupported
browserRequired=Browser required to login
browserContinue=Browser required to complete login
browserContinuePrompt=Open browser and continue login? [y/n]:
browserContinueAnswer=y
# Transports
usb=USB
nfc=NFC
bluetooth=Bluetooth
internal=Internal
unknown=Unknown
termsTitle=Terms and Conditions
termsText=<p>Terms and conditions to be defined</p>
termsPlainText=Terms and conditions to be defined.
termsAcceptanceRequired=You must agree to our terms and conditions.
acceptTerms=I agree to the terms and conditions
recaptchaFailed=Invalid Recaptcha
recaptchaNotConfigured=Recaptcha is required, but not configured
consentDenied=Consent denied.
noAccount=New user?
username=Username
usernameOrEmail=Username
firstName=First name
givenName=Given name
fullName=Full name
lastName=Last name
familyName=Family name
email=Email
password=Password
passwordConfirm=Confirm password
passwordNew=New Password
passwordNewConfirm=New Password confirmation
rememberMe=Remember me
authenticatorCode=One-time code
address=Address
street=Street
locality=City or Locality
region=State, Province, or Region
postal_code=Zip or Postal code
country=Country
emailVerified=Email verified
website=Web page
phoneNumber=Phone number
phoneNumberVerified=Phone number verified
gender=Gender
birthday=Birthdate
zoneinfo=Time zone
gssDelegationCredential=GSS Delegation Credential
logoutOtherSessions=Sign out from other devices
profileScopeConsentText=User profile
emailScopeConsentText=Email address
addressScopeConsentText=Address
phoneScopeConsentText=Phone number
offlineAccessScopeConsentText=Offline Access
samlRoleListScopeConsentText=My Roles
rolesScopeConsentText=User roles
restartLoginTooltip=Restart login
loginTotpIntro=You need to set up a One Time Password generator to access this account
loginTotpStep1=Install one of the following applications on your mobile:
loginTotpStep2=Open the application and scan the barcode:
loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup.
loginTotpStep3DeviceName=Provide a Device Name to help you manage your OTP devices.
loginTotpManualStep2=Open the application and enter the key:
loginTotpManualStep3=Use the following configuration values if the application allows setting them:
loginTotpUnableToScan=Unable to scan?
loginTotpScanBarcode=Scan barcode?
loginCredential=Credential
loginOtpOneTime=One-time code
loginTotpType=Type
loginTotpAlgorithm=Algorithm
loginTotpDigits=Digits
loginTotpInterval=Interval
loginTotpCounter=Counter
loginTotpDeviceName=Device Name
loginTotp.totp=Time-based
loginTotp.hotp=Counter-based
totpAppFreeOTPName=FreeOTP
totpAppGoogleName=Google Authenticator
totpAppMicrosoftAuthenticatorName=Microsoft Authenticator
loginChooseAuthenticator=Select login method
oauthGrantRequest=Do you grant these access privileges?
inResource=in
oauth2DeviceVerificationTitle=Device Login
verifyOAuth2DeviceUserCode=Enter the code provided by your device and click Submit
oauth2DeviceInvalidUserCodeMessage=Invalid code, please try again.
oauth2DeviceExpiredUserCodeMessage=The code has expired. Please go back to your device and try connecting again.
oauth2DeviceVerificationCompleteHeader=Device Login Successful
oauth2DeviceVerificationCompleteMessage=You may close this browser window and go back to your device.
oauth2DeviceVerificationFailedHeader=Device Login Failed
oauth2DeviceVerificationFailedMessage=You may close this browser window and go back to your device and try connecting again.
oauth2DeviceConsentDeniedMessage=Consent denied for connecting the device.
oauth2DeviceAuthorizationGrantDisabledMessage=Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client.
emailVerifyInstruction1=An email with instructions to verify your email address has been sent to your address {0}.
emailVerifyInstruction2=Haven''t received a verification code in your email?
emailVerifyInstruction3=to re-send the email.
emailLinkIdpTitle=Link {0}
emailLinkIdp1=An email with instructions to link {0} account {1} with your {2} account has been sent to you.
emailLinkIdp2=Haven''t received a verification code in your email?
emailLinkIdp3=to re-send the email.
emailLinkIdp4=If you already verified the email in different browser
emailLinkIdp5=to continue.
backToLogin=&laquo; Back to Login
emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
emailInstructionUsername=Enter your username and we will send you instructions on how to create a new password.
copyCodeInstruction=Please copy this code and paste it into your application:
pageExpiredTitle=Page has expired
pageExpiredMsg1=To restart the login process
pageExpiredMsg2=To continue the login process
personalInfo=Personal Info:
role_admin=Admin
role_realm-admin=Realm Admin
role_create-realm=Create realm
role_create-client=Create client
role_view-realm=View realm
role_view-users=View users
role_view-applications=View applications
role_view-clients=View clients
role_view-events=View events
role_view-identity-providers=View identity providers
role_manage-realm=Manage realm
role_manage-users=Manage users
role_manage-applications=Manage applications
role_manage-identity-providers=Manage identity providers
role_manage-clients=Manage clients
role_manage-events=Manage events
role_view-profile=View profile
role_manage-account=Manage account
role_manage-account-links=Manage account links
role_read-token=Read token
role_offline-access=Offline access
client_account=Account
client_account-console=Account Console
client_security-admin-console=Security Admin Console
client_admin-cli=Admin CLI
client_realm-management=Realm Management
client_broker=Broker
requiredFields=Required fields
invalidUserMessage=Invalid username or password.
invalidUsernameMessage=Invalid username.
invalidUsernameOrEmailMessage=Invalid username or email.
invalidPasswordMessage=Invalid password.
invalidEmailMessage=Invalid email address.
accountDisabledMessage=Account is disabled, contact your administrator.
accountTemporarilyDisabledMessage=Account is temporarily disabled; contact your administrator or retry later.
expiredCodeMessage=Login timeout. Please sign in again.
expiredActionMessage=Action expired. Please continue with login now.
expiredActionTokenNoSessionMessage=Action expired.
expiredActionTokenSessionExistsMessage=Action expired. Please start again.
sessionLimitExceeded=There are too many sessions
missingFirstNameMessage=Please specify first name.
missingLastNameMessage=Please specify last name.
missingEmailMessage=Please specify email.
missingUsernameMessage=Please specify username.
missingPasswordMessage=Please specify password.
missingTotpMessage=Please specify authenticator code.
missingTotpDeviceNameMessage=Please specify device name.
notMatchPasswordMessage=Passwords don''t match.
error-invalid-value=Invalid value.
error-invalid-blank=Please specify value.
error-empty=Please specify value.
error-invalid-length=Length must be between {1} and {2}.
error-invalid-length-too-short=Minimal length is {1}.
error-invalid-length-too-long=Maximal length is {2}.
error-invalid-email=Invalid email address.
error-invalid-number=Invalid number.
error-number-out-of-range=Number must be between {1} and {2}.
error-number-out-of-range-too-small=Number must have minimal value of {1}.
error-number-out-of-range-too-big=Number must have maximal value of {2}.
error-pattern-no-match=Invalid value.
error-invalid-uri=Invalid URL.
error-invalid-uri-scheme=Invalid URL scheme.
error-invalid-uri-fragment=Invalid URL fragment.
error-user-attribute-required=Please specify this field.
error-invalid-date=Invalid date.
error-user-attribute-read-only=This field is read only.
error-username-invalid-character=Value contains invalid character.
error-person-name-invalid-character=Value contains invalid character.
error-reset-otp-missing-id=Please choose an OTP configuration.
invalidPasswordExistingMessage=Invalid existing password.
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
invalidPasswordConfirmMessage=Password confirmation doesn''t match.
invalidTotpMessage=Invalid authenticator code.
usernameExistsMessage=Username already exists.
emailExistsMessage=Email already exists.
federatedIdentityExistsMessage=User with {0} {1} already exists. Please login to account management to link the account.
federatedIdentityUnavailableMessage=User {0} authenticated with identity provider {1} does not exist. Please contact your administrator.
federatedIdentityUnmatchedEssentialClaimMessage=The ID token issued by the identity provider does not match the configured essential claim. Please contact your administrator.
confirmLinkIdpTitle=Account already exists
federatedIdentityConfirmLinkMessage=User with {0} {1} already exists. How do you want to continue?
federatedIdentityConfirmReauthenticateMessage=Authenticate to link your account with {0}
nestedFirstBrokerFlowMessage=The {0} user {1} is not linked to any known user.
confirmLinkIdpReviewProfile=Review profile
confirmLinkIdpContinue=Add to existing account
configureTotpMessage=You need to set up Mobile Authenticator to activate your account.
configureBackupCodesMessage=You need to set up Backup Codes to activate your account.
updateProfileMessage=You need to update your user profile to activate your account.
updatePasswordMessage=You need to change your password to activate your account.
updateEmailMessage=You need to update your email address to activate your account.
resetPasswordMessage=You need to change your password.
verifyEmailMessage=You need to verify your email address to activate your account.
linkIdpMessage=You need to verify your email address to link your account with {0}.
emailSentMessage=You should receive an email shortly with further instructions.
emailSendErrorMessage=Failed to send email, please try again later.
accountUpdatedMessage=Your account has been updated.
accountPasswordUpdatedMessage=Your password has been updated.
delegationCompleteHeader=Login Successful
delegationCompleteMessage=You may close this browser window and go back to your console application.
delegationFailedHeader=Login Failed
delegationFailedMessage=You may close this browser window and go back to your console application and try logging in again.
noAccessMessage=No access
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}.
invalidPasswordMaxLengthMessage=Invalid password: maximum length {0}.
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
failedToProcessResponseMessage=Failed to process response
httpsRequiredMessage=HTTPS required
realmNotEnabledMessage=Realm not enabled
invalidRequestMessage=Invalid Request
successLogout=You are logged out
failedLogout=Logout failed
unknownLoginRequesterMessage=Unknown login requester
loginRequesterNotEnabledMessage=Login requester not enabled
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.
implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.
invalidRedirectUriMessage=Invalid redirect uri
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
invalidRequesterMessage=Invalid requester
registrationNotAllowedMessage=Registration not allowed
resetCredentialNotAllowedMessage=Reset Credential not allowed
permissionNotApprovedMessage=Permission not approved.
noRelayStateInResponseMessage=No relay state in response from identity provider.
insufficientPermissionMessage=Insufficient permissions to link identities.
couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
couldNotObtainTokenMessage=Could not obtain token from identity provider.
unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider.
unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider.
identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider.
couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider.
unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider.
invalidAccessCodeMessage=Invalid access code.
sessionNotActiveMessage=Session not active.
invalidCodeMessage=An error occurred, please login again through your application.
cookieNotFoundMessage=Cookie not found. Please make sure cookies are enabled in your browser.
insufficientLevelOfAuthentication=The requested level of authentication has not been satisfied.
identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
identityProviderMissingStateMessage=Missing state parameter in response from identity provider.
identityProviderInvalidResponseMessage=Invalid response from identity provider.
identityProviderInvalidSignatureMessage=Invalid signature in response from identity provider.
identityProviderNotFoundMessage=Could not find an identity provider with the identifier.
identityProviderLinkSuccess=You successfully verified your email. Please go back to your original browser and continue there with the login.
staleCodeMessage=This page is no longer valid, please go back to your application and sign in again
realmSupportsNoCredentialsMessage=Realm does not support any credential type.
credentialSetupRequired=Cannot login, credential setup required.
identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
emailVerifiedMessage=Your email address has been verified.
staleEmailVerificationLink=The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email.
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account.
confirmEmailAddressVerification=Confirm validity of e-mail address {0}.
confirmExecutionOfActions=Perform the following action(s)
locale_ar=\u0639\u0631\u0628\u064A
locale_ca=Catal\u00E0
locale_cs=\u010Ce\u0161tina
locale_da=Dansk
locale_de=Deutsch
locale_en=English
locale_es=Espa\u00F1ol
locale_fr=Fran\u00E7ais
locale_hu=Magyar
locale_it=Italiano
locale_ja=\u65E5\u672C\u8A9E
locale_lt=Lietuvi\u0173
locale_nl=Nederlands
locale_no=Norsk
locale_pl=Polski
locale_pt_BR=Portugu\u00EAs (Brasil)
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439
locale_sk=Sloven\u010Dina
locale_sv=Svenska
locale_tr=T\u00FCrk\u00E7e
locale_zh-CN=\u4E2D\u6587\u7B80\u4F53
locale_fi=Suomi
backToApplication=&laquo; Back to Application
missingParameterMessage=Missing parameters\: {0}
clientNotFoundMessage=Client not found.
clientDisabledMessage=Client disabled.
invalidParameterMessage=Invalid parameter\: {0}
alreadyLoggedIn=You are already logged in.
differentUserAuthenticated=You are already authenticated as different user ''{0}'' in this session. Please sign out first.
brokerLinkingSessionExpired=Requested broker account linking, but current session is no longer valid.
proceedWithAction=&raquo; Click here to proceed
acrNotFulfilled=Authentication requirements not fulfilled
requiredAction.CONFIGURE_TOTP=Configure OTP
requiredAction.TERMS_AND_CONDITIONS=Terms and Conditions
requiredAction.UPDATE_PASSWORD=Update Password
requiredAction.UPDATE_PROFILE=Update Profile
requiredAction.VERIFY_EMAIL=Verify Email
requiredAction.CONFIGURE_RECOVERY_AUTHN_CODES=Generate Recovery Codes
requiredAction.webauthn-register-passwordless=Webauthn Register Passwordless
invalidTokenRequiredActions=Required actions included in the link are not valid
doX509Login=You will be logged in as\:
clientCertificate=X509 client certificate\:
noCertificate=[No Certificate]
pageNotFound=Page not found
internalServerError=An internal server error has occurred
console-username=Username:
console-password=Password:
console-otp=One Time Password:
console-new-password=New Password:
console-confirm-password=Confirm Password:
console-update-password=Update of your password is required.
console-verify-email=You need to verify your email address. We sent an email to {0} that contains a verification code. Please enter this code into the input below.
console-email-code=Email Code:
console-accept-terms=Accept Terms? [y/n]:
console-accept=y
# Openshift messages
openshift.scope.user_info=User information
openshift.scope.user_check-access=User access information
openshift.scope.user_full=Full Access
openshift.scope.list-projects=List projects
# SAML authentication
saml.post-form.title=Authentication Redirect
saml.post-form.message=Redirecting, please wait.
saml.post-form.js-disabled=JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.
saml.artifactResolutionServiceInvalidResponse=Unable to resolve artifact.
#authenticators
otp-display-name=Authenticator Application
otp-help-text=Enter a verification code from authenticator application.
otp-reset-description=Which OTP configuration should be removed?
password-display-name=Password
password-help-text=Sign in by entering your password.
auth-username-form-display-name=Username
auth-username-form-help-text=Start sign in by entering your username
auth-username-password-form-display-name=Username and password
auth-username-password-form-help-text=Sign in by entering your username and password.
# Recovery Codes
auth-recovery-authn-code-form-display-name=Recovery Authentication Code
auth-recovery-authn-code-form-help-text=Enter a recovery authentication code from a previously generated list.
auth-recovery-code-info-message=Enter the specified recovery code.
auth-recovery-code-prompt=Recovery code #{0}
auth-recovery-code-header=Login with a recovery authentication code
recovery-codes-error-invalid=Invalid recovery authentication code
recovery-code-config-header=Recovery Authentication Codes
recovery-code-config-warning-title=These recovery codes won't appear again after leaving this page
recovery-code-config-warning-message=Make sure to print, download, or copy them to a password manager and keep them save. Canceling this setup will remove these recovery codes from your account.
recovery-codes-print=Print
recovery-codes-download=Download
recovery-codes-copy=Copy
recovery-codes-copied=Copied
recovery-codes-confirmation-message=I have saved these codes somewhere safe
recovery-codes-action-complete=Complete setup
recovery-codes-action-cancel=Cancel setup
recovery-codes-download-file-header=Keep these recovery codes somewhere safe.
recovery-codes-download-file-description=Recovery codes are single-use passcodes that allow you to sign in to your account if you do not have access to your authenticator.
recovery-codes-download-file-date= These codes were generated on
recovery-codes-label-default=Recovery codes
# WebAuthn
webauthn-display-name=Security Key
webauthn-help-text=Use your security key to sign in.
webauthn-passwordless-display-name=Security Key
webauthn-passwordless-help-text=Use your security key for passwordless sign in.
webauthn-login-title=Security Key login
webauthn-registration-title=Security Key Registration
webauthn-available-authenticators=Available Security Keys
webauthn-unsupported-browser-text=WebAuthn is not supported by this browser. Try another one or contact your administrator.
webauthn-doAuthenticate=Sign in with Security Key
webauthn-createdAt-label=Created
# WebAuthn Error
webauthn-error-title=Security Key Error
webauthn-error-registration=Failed to register your Security key.<br/> {0}
webauthn-error-api-get=Failed to authenticate by the Security key.<br/> {0}
webauthn-error-different-user=First authenticated user is not the one authenticated by the Security key.
webauthn-error-auth-verification=Security key authentication result is invalid.<br/> {0}
webauthn-error-register-verification=Security key registration result is invalid.<br/> {0}
webauthn-error-user-not-found=Unknown user authenticated by the Security key.
# Identity provider
identity-provider-redirector=Connect with another Identity Provider
identity-provider-login-label=Or sign in with
idp-email-verification-display-name=Email Verification
idp-email-verification-help-text=Link your account by validating your email.
idp-username-password-form-display-name=Username and password
idp-username-password-form-help-text=Link your account by logging in.
finalDeletionConfirmation=If you delete your account, it cannot be restored. To keep your account, click Cancel.
irreversibleAction=This action is irreversible
deleteAccountConfirm=Delete account confirmation
deletingImplies=Deleting your account implies:
errasingData=Erasing all your data
loggingOutImmediately=Logging you out immediately
accountUnusable=Any subsequent use of the application will not be possible with this account
userDeletedSuccessfully=User deleted successfully
access-denied=Access denied
access-denied-when-idp-auth=Access denied when authenticating with {0}
frontchannel-logout.title=Logging out
frontchannel-logout.message=You are logging out from following apps
logoutConfirmTitle=Logging out
logoutConfirmHeader=Do you want to log out?
doLogout=Logout
readOnlyUsernameMessage=You can''t update your username as it is read-only.

View file

@ -0,0 +1,609 @@
/* Patternfly CSS places a "bg-login.jpg" as the background on this ".login-pf" class.
This clashes with the "keycloak-bg.png' background defined on the body below.
Therefore the Patternfly background must be set to none. */
.login-pf {
background: none;
}
.login-pf body {
background: url("../img/keycloak-bg.png") no-repeat center center fixed;
background-size: cover;
height: 100%;
}
textarea.pf-c-form-control {
height: auto;
}
.pf-c-alert__title {
font-size: var(--pf-global--FontSize--xs);
}
p.instruction {
margin: 5px 0;
}
.pf-c-button.pf-m-control {
}
h1#kc-page-title {
margin-top: 10px;
color: rgb(163, 163, 163);
}
#kc-locale ul {
background-color: var(--pf-global--BackgroundColor--100);
display: none;
top: 20px;
min-width: 100px;
padding: 0;
}
#kc-locale-dropdown{
display: inline-block;
}
#kc-locale-dropdown:hover ul {
display:block;
}
#kc-locale-dropdown a {
color: var(--pf-global--Color--200);
text-align: right;
font-size: var(--pf-global--FontSize--sm);
}
a#kc-current-locale-link::after {
content: "\2c5";
margin-left: var(--pf-global--spacer--xs)
}
.login-pf .container {
padding-top: 40px;
}
.login-pf a:hover {
color: #0099d3;
}
#kc-logo {
width: 100%;
}
div.kc-logo-text {
background-image: url(../img/keycloak-logo-text.png);
background-repeat: no-repeat;
height: 63px;
width: 300px;
margin: 0 auto;
}
div.kc-logo-text span {
display: none;
}
#kc-header {
color: #ededed;
overflow: visible;
white-space: nowrap;
}
#kc-header-wrapper {
font-size: 29px;
text-transform: uppercase;
letter-spacing: 3px;
line-height: 1.2em;
padding: 20px 0px 0px 0px;
white-space: normal;
}
#kc-content {
width: 100%;
}
#kc-attempted-username {
font-size: 20px;
font-family: inherit;
font-weight: normal;
padding-right: 10px;
}
#kc-username {
text-align: center;
margin-bottom:-10px;
}
#kc-webauthn-settings-form {
padding-top: 8px;
}
#kc-form-webauthn .select-auth-box-parent {
pointer-events: none;
}
#kc-form-webauthn .select-auth-box-desc {
color: var(--pf-global--palette--black-600);
}
#kc-form-webauthn .select-auth-box-headline {
color: var(--pf-global--Color--300);
}
#kc-form-webauthn .select-auth-box-icon {
flex: 0 0 3em;
}
#kc-form-webauthn .select-auth-box-icon-properties {
margin-top: 10px;
font-size: 1.8em;
}
#kc-form-webauthn .select-auth-box-icon-properties.unknown-transport-class {
margin-top: 3px;
}
#kc-form-webauthn .pf-l-stack__item {
margin: -1px 0;
}
#kc-content-wrapper {
margin-top: 20px;
}
#kc-form-wrapper {
margin-top: 10px;
}
#kc-info {
margin: 20px -40px -30px;
}
#kc-info-wrapper {
font-size: 13px;
padding: 15px 35px;
background-color: #F0F0F0;
}
#kc-form-options span {
display: block;
}
#kc-form-options .checkbox {
margin-top: 0;
color: #72767b;
}
#kc-terms-text {
margin-bottom: 20px;
}
#kc-registration-terms-text {
max-height: 100px;
overflow-y: auto;
overflow-x: hidden;
margin: 5px;
}
#kc-registration {
margin-bottom: 0;
}
/* TOTP */
.subtitle {
text-align: right;
margin-top: 30px;
color: #909090;
}
.required {
color: var(--pf-global--danger-color--200);
}
ol#kc-totp-settings {
margin: 0;
padding-left: 20px;
}
ul#kc-totp-supported-apps {
margin-bottom: 10px;
}
#kc-totp-secret-qr-code {
max-width:150px;
max-height:150px;
}
#kc-totp-secret-key {
background-color: #fff;
color: #333333;
font-size: 16px;
padding: 10px 0;
}
/* OAuth */
#kc-oauth h3 {
margin-top: 0;
}
#kc-oauth ul {
list-style: none;
padding: 0;
margin: 0;
}
#kc-oauth ul li {
border-top: 1px solid rgba(255, 255, 255, 0.1);
font-size: 12px;
padding: 10px 0;
}
#kc-oauth ul li:first-of-type {
border-top: 0;
}
#kc-oauth .kc-role {
display: inline-block;
width: 50%;
}
/* Code */
#kc-code textarea {
width: 100%;
height: 8em;
}
/* Social */
.kc-social-links {
margin-top: 20px;
}
.kc-social-links li {
width: 100%;
}
.kc-social-provider-logo {
font-size: 23px;
width: 30px;
height: 25px;
float: left;
}
.kc-social-gray {
color: var(--pf-global--Color--200);
}
.kc-social-item {
margin-bottom: var(--pf-global--spacer--sm);
font-size: 15px;
text-align: center;
}
.kc-social-provider-name {
position: relative;
}
.kc-social-icon-text {
left: -15px;
}
.kc-social-grid {
display:grid;
grid-column-gap: 10px;
grid-row-gap: 5px;
grid-column-end: span 6;
--pf-l-grid__item--GridColumnEnd: span 6;
}
.kc-social-grid .kc-social-icon-text {
left: -10px;
}
.kc-login-tooltip {
position: relative;
display: inline-block;
}
.kc-social-section {
text-align: center;
}
.kc-social-section hr{
margin-bottom: 10px
}
.kc-login-tooltip .kc-tooltip-text{
top:-3px;
left:160%;
background-color: black;
visibility: hidden;
color: #fff;
min-width:130px;
text-align: center;
border-radius: 2px;
box-shadow:0 1px 8px rgba(0,0,0,0.6);
padding: 5px;
position: absolute;
opacity:0;
transition:opacity 0.5s;
}
/* Show tooltip */
.kc-login-tooltip:hover .kc-tooltip-text {
visibility: visible;
opacity:0.7;
}
/* Arrow for tooltip */
.kc-login-tooltip .kc-tooltip-text::after {
content: " ";
position: absolute;
top: 15px;
right: 100%;
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent black transparent transparent;
}
@media (min-width: 768px) {
#kc-container-wrapper {
position: absolute;
width: 100%;
}
.login-pf .container {
padding-right: 80px;
}
#kc-locale {
position: relative;
text-align: right;
z-index: 9999;
}
}
@media (max-width: 767px) {
.login-pf body {
background: white;
}
#kc-header {
padding-left: 15px;
padding-right: 15px;
float: none;
text-align: left;
}
#kc-header-wrapper {
font-size: 16px;
font-weight: bold;
padding: 20px 0 0 0;
color: #72767b;
letter-spacing: 0;
}
div.kc-logo-text {
margin: 0;
width: 150px;
height: 32px;
background-size: 100%;
}
#kc-form {
float: none;
}
#kc-info-wrapper {
border-top: 1px solid rgba(255, 255, 255, 0.1);
background-color: transparent;
}
.login-pf .container {
padding-top: 15px;
padding-bottom: 15px;
}
#kc-locale {
position: absolute;
width: 200px;
top: 20px;
right: 20px;
text-align: right;
z-index: 9999;
}
}
@media (min-height: 646px) {
#kc-container-wrapper {
bottom: 12%;
}
}
@media (max-height: 645px) {
#kc-container-wrapper {
padding-top: 50px;
top: 20%;
}
}
.card-pf form.form-actions .btn {
float: right;
margin-left: 10px;
}
#kc-form-buttons {
margin-top: 20px;
}
.login-pf-page .login-pf-brand {
margin-top: 20px;
max-width: 360px;
width: 40%;
}
.select-auth-box-arrow{
display: flex;
align-items: center;
margin-right: 2rem;
}
.select-auth-box-icon{
display: flex;
flex: 0 0 2em;
justify-content: center;
margin-right: 1rem;
margin-left: 3rem;
}
.select-auth-box-parent{
border-top: 1px solid var(--pf-global--palette--black-200);
padding-top: 1rem;
padding-bottom: 1rem;
cursor: pointer;
}
.select-auth-box-parent:hover{
background-color: #f7f8f8;
}
.select-auth-container {
padding-bottom: 0px !important;
}
.select-auth-box-headline {
font-size: var(--pf-global--FontSize--md);
color: var(--pf-global--primary-color--100);
font-weight: bold;
}
.select-auth-box-desc {
font-size: var(--pf-global--FontSize--sm);
}
.select-auth-box-paragraph {
text-align: center;
font-size: var(--pf-global--FontSize--md);
margin-bottom: 5px;
}
.card-pf {
margin: 0 auto;
box-shadow: var(--pf-global--BoxShadow--lg);
padding: 0 20px;
max-width: 500px;
border-radius: 15px;
}
/*phone*/
@media (max-width: 100px) {
.login-pf-page .card-pf {
max-width: 500px;
padding: 0 20px;
border-top: 4px solid;
box-shadow: var(--pf-global--BoxShadow--lg);
display: block;
margin-left: auto;
margin-right: auto;
border-radius: 25px;
border-color: var(--pf-global--primary-color--100);
}
.kc-social-grid {
grid-column-end: 12;
--pf-l-grid__item--GridColumnEnd: span 12;
}
.kc-social-grid .kc-social-icon-text {
left: -15px;
}
}
.login-pf-page .login-pf-signup {
font-size: 15px;
color: #72767b;
}
#kc-content-wrapper .row {
margin-left: 0;
margin-right: 0;
}
.login-pf-page.login-pf-page-accounts {
margin-left: auto;
margin-right: auto;
}
.login-pf-page .btn-primary {
margin-top: 0;
}
.login-pf-page .list-view-pf .list-group-item {
border-bottom: 1px solid #ededed;
}
.login-pf-page .list-view-pf-description {
width: 100%;
}
#kc-form-login div.form-group:last-of-type,
#kc-register-form div.form-group:last-of-type,
#kc-update-profile-form div.form-group:last-of-type,
#kc-update-email-form div.form-group:last-of-type{
margin-bottom: 0px;
}
.no-bottom-margin {
margin-bottom: 0;
}
#kc-back {
margin-top: 5px;
}
/* Recovery codes */
.kc-recovery-codes-warning {
margin-bottom: 32px;
}
.kc-recovery-codes-warning .pf-c-alert__description p {
font-size: 0.875rem;
}
.kc-recovery-codes-list {
list-style: none;
columns: 2;
margin: 16px 0;
padding: 16px 16px 8px 16px;
border: 1px solid #D2D2D2;
}
.kc-recovery-codes-list li {
margin-bottom: 8px;
font-size: 11px;
}
.kc-recovery-codes-list li span {
color: #6A6E73;
width: 16px;
text-align: right;
display: inline-block;
margin-right: 1px;
}
.kc-recovery-codes-actions {
margin-bottom: 24px;
}
.kc-recovery-codes-actions button {
padding-left: 0;
}
.kc-recovery-codes-actions button i {
margin-right: 8px;
}
.kc-recovery-codes-confirmation {
align-items: baseline;
margin-bottom: 16px;
}
/* End Recovery codes */

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

View file

@ -0,0 +1,516 @@
doLogIn=LOGIN
doRegister=Register
doCancel=Cancel
doSubmit=Submit
doBack=Back
doYes=Yes
doNo=No
doContinue=Continue
doIgnore=Ignore
doAccept=Accept
doDecline=Decline
doForgotPassword=Forgot Password?
doClickHere=Click here
doImpersonate=Impersonate
doTryAgain=Try again
doTryAnotherWay=Try Another Way
doConfirmDelete=Confirm deletion
errorDeletingAccount=Error happened while deleting account
deletingAccountForbidden=You do not have enough permissions to delete your own account, contact admin.
kerberosNotConfigured=Kerberos Not Configured
kerberosNotConfiguredTitle=Kerberos Not Configured
bypassKerberosDetail=Either you are not logged in by Kerberos or your browser is not set up for Kerberos login. Please click continue to login in through other means
kerberosNotSetUp=Kerberos is not set up. You cannot login.
registerTitle=Register
loginAccountTitle=Enterprise Document Management (EDM)
loginTitle=Sign in to {0}
loginTitleHtml={0}
impersonateTitle={0} Impersonate User
impersonateTitleHtml=<strong>{0}</strong> Impersonate User
realmChoice=Realm
unknownUser=Unknown user
loginTotpTitle=Mobile Authenticator Setup
loginProfileTitle=Update Account Information
loginIdpReviewProfileTitle=Update Account Information
loginTimeout=Your login attempt timed out. Login will start from the beginning.
reauthenticate=Please re-authenticate to continue
oauthGrantTitle=Grant Access to {0}
oauthGrantTitleHtml={0}
oauthGrantInformation=Make sure you trust {0} by learning how {0} will handle your data.
oauthGrantReview=You could review the
oauthGrantTos=terms of service.
oauthGrantPolicy=privacy policy.
errorTitle=We are sorry...
errorTitleHtml=We are <strong>sorry</strong> ...
emailVerifyTitle=Email verification
emailForgotTitle=Forgot Your Password?
updateEmailTitle=Update email
emailUpdateConfirmationSentTitle=Confirmation email sent
emailUpdateConfirmationSent=A confirmation email has been sent to {0}. You must follow the instructions of the former to complete the email update.
emailUpdatedTitle=Email updated
emailUpdated=The account email has been successfully updated to {0}.
updatePasswordTitle=Update password
codeSuccessTitle=Success code
codeErrorTitle=Error code\: {0}
displayUnsupported=Requested display type unsupported
browserRequired=Browser required to login
browserContinue=Browser required to complete login
browserContinuePrompt=Open browser and continue login? [y/n]:
browserContinueAnswer=y
# Transports
usb=USB
nfc=NFC
bluetooth=Bluetooth
internal=Internal
unknown=Unknown
termsTitle=Terms and Conditions
termsText=<p>Terms and conditions to be defined</p>
termsPlainText=Terms and conditions to be defined.
termsAcceptanceRequired=You must agree to our terms and conditions.
acceptTerms=I agree to the terms and conditions
recaptchaFailed=Invalid Recaptcha
recaptchaNotConfigured=Recaptcha is required, but not configured
consentDenied=Consent denied.
noAccount=New user?
username=Username
usernameOrEmail=Username
firstName=First name
givenName=Given name
fullName=Full name
lastName=Last name
familyName=Family name
email=Email
password=Password
passwordConfirm=Confirm password
passwordNew=New Password
passwordNewConfirm=New Password confirmation
rememberMe=Remember me
authenticatorCode=One-time code
address=Address
street=Street
locality=City or Locality
region=State, Province, or Region
postal_code=Zip or Postal code
country=Country
emailVerified=Email verified
website=Web page
phoneNumber=Phone number
phoneNumberVerified=Phone number verified
gender=Gender
birthday=Birthdate
zoneinfo=Time zone
gssDelegationCredential=GSS Delegation Credential
logoutOtherSessions=Sign out from other devices
profileScopeConsentText=User profile
emailScopeConsentText=Email address
addressScopeConsentText=Address
phoneScopeConsentText=Phone number
offlineAccessScopeConsentText=Offline Access
samlRoleListScopeConsentText=My Roles
rolesScopeConsentText=User roles
restartLoginTooltip=Restart login
loginTotpIntro=You need to set up a One Time Password generator to access this account
loginTotpStep1=Install one of the following applications on your mobile:
loginTotpStep2=Open the application and scan the barcode:
loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup.
loginTotpStep3DeviceName=Provide a Device Name to help you manage your OTP devices.
loginTotpManualStep2=Open the application and enter the key:
loginTotpManualStep3=Use the following configuration values if the application allows setting them:
loginTotpUnableToScan=Unable to scan?
loginTotpScanBarcode=Scan barcode?
loginCredential=Credential
loginOtpOneTime=One-time code
loginTotpType=Type
loginTotpAlgorithm=Algorithm
loginTotpDigits=Digits
loginTotpInterval=Interval
loginTotpCounter=Counter
loginTotpDeviceName=Device Name
loginTotp.totp=Time-based
loginTotp.hotp=Counter-based
totpAppFreeOTPName=FreeOTP
totpAppGoogleName=Google Authenticator
totpAppMicrosoftAuthenticatorName=Microsoft Authenticator
loginChooseAuthenticator=Select login method
oauthGrantRequest=Do you grant these access privileges?
inResource=in
oauth2DeviceVerificationTitle=Device Login
verifyOAuth2DeviceUserCode=Enter the code provided by your device and click Submit
oauth2DeviceInvalidUserCodeMessage=Invalid code, please try again.
oauth2DeviceExpiredUserCodeMessage=The code has expired. Please go back to your device and try connecting again.
oauth2DeviceVerificationCompleteHeader=Device Login Successful
oauth2DeviceVerificationCompleteMessage=You may close this browser window and go back to your device.
oauth2DeviceVerificationFailedHeader=Device Login Failed
oauth2DeviceVerificationFailedMessage=You may close this browser window and go back to your device and try connecting again.
oauth2DeviceConsentDeniedMessage=Consent denied for connecting the device.
oauth2DeviceAuthorizationGrantDisabledMessage=Client is not allowed to initiate OAuth 2.0 Device Authorization Grant. The flow is disabled for the client.
emailVerifyInstruction1=An email with instructions to verify your email address has been sent to your address {0}.
emailVerifyInstruction2=Haven''t received a verification code in your email?
emailVerifyInstruction3=to re-send the email.
emailLinkIdpTitle=Link {0}
emailLinkIdp1=An email with instructions to link {0} account {1} with your {2} account has been sent to you.
emailLinkIdp2=Haven''t received a verification code in your email?
emailLinkIdp3=to re-send the email.
emailLinkIdp4=If you already verified the email in different browser
emailLinkIdp5=to continue.
backToLogin=&laquo; Back to Login
emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
emailInstructionUsername=Enter your username and we will send you instructions on how to create a new password.
copyCodeInstruction=Please copy this code and paste it into your application:
pageExpiredTitle=Page has expired
pageExpiredMsg1=To restart the login process
pageExpiredMsg2=To continue the login process
personalInfo=Personal Info:
role_admin=Admin
role_realm-admin=Realm Admin
role_create-realm=Create realm
role_create-client=Create client
role_view-realm=View realm
role_view-users=View users
role_view-applications=View applications
role_view-clients=View clients
role_view-events=View events
role_view-identity-providers=View identity providers
role_manage-realm=Manage realm
role_manage-users=Manage users
role_manage-applications=Manage applications
role_manage-identity-providers=Manage identity providers
role_manage-clients=Manage clients
role_manage-events=Manage events
role_view-profile=View profile
role_manage-account=Manage account
role_manage-account-links=Manage account links
role_read-token=Read token
role_offline-access=Offline access
client_account=Account
client_account-console=Account Console
client_security-admin-console=Security Admin Console
client_admin-cli=Admin CLI
client_realm-management=Realm Management
client_broker=Broker
requiredFields=Required fields
invalidUserMessage=Invalid username or password.
invalidUsernameMessage=Invalid username.
invalidUsernameOrEmailMessage=Invalid username or email.
invalidPasswordMessage=Invalid password.
invalidEmailMessage=Invalid email address.
accountDisabledMessage=Account is disabled, contact your administrator.
accountTemporarilyDisabledMessage=Account is temporarily disabled; contact your administrator or retry later.
expiredCodeMessage=Login timeout. Please sign in again.
expiredActionMessage=Action expired. Please continue with login now.
expiredActionTokenNoSessionMessage=Action expired.
expiredActionTokenSessionExistsMessage=Action expired. Please start again.
sessionLimitExceeded=There are too many sessions
missingFirstNameMessage=Please specify first name.
missingLastNameMessage=Please specify last name.
missingEmailMessage=Please specify email.
missingUsernameMessage=Please specify username.
missingPasswordMessage=Please specify password.
missingTotpMessage=Please specify authenticator code.
missingTotpDeviceNameMessage=Please specify device name.
notMatchPasswordMessage=Passwords don''t match.
error-invalid-value=Invalid value.
error-invalid-blank=Please specify value.
error-empty=Please specify value.
error-invalid-length=Length must be between {1} and {2}.
error-invalid-length-too-short=Minimal length is {1}.
error-invalid-length-too-long=Maximal length is {2}.
error-invalid-email=Invalid email address.
error-invalid-number=Invalid number.
error-number-out-of-range=Number must be between {1} and {2}.
error-number-out-of-range-too-small=Number must have minimal value of {1}.
error-number-out-of-range-too-big=Number must have maximal value of {2}.
error-pattern-no-match=Invalid value.
error-invalid-uri=Invalid URL.
error-invalid-uri-scheme=Invalid URL scheme.
error-invalid-uri-fragment=Invalid URL fragment.
error-user-attribute-required=Please specify this field.
error-invalid-date=Invalid date.
error-user-attribute-read-only=This field is read only.
error-username-invalid-character=Value contains invalid character.
error-person-name-invalid-character=Value contains invalid character.
error-reset-otp-missing-id=Please choose an OTP configuration.
invalidPasswordExistingMessage=Invalid existing password.
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
invalidPasswordConfirmMessage=Password confirmation doesn''t match.
invalidTotpMessage=Invalid authenticator code.
usernameExistsMessage=Username already exists.
emailExistsMessage=Email already exists.
federatedIdentityExistsMessage=User with {0} {1} already exists. Please login to account management to link the account.
federatedIdentityUnavailableMessage=User {0} authenticated with identity provider {1} does not exist. Please contact your administrator.
federatedIdentityUnmatchedEssentialClaimMessage=The ID token issued by the identity provider does not match the configured essential claim. Please contact your administrator.
confirmLinkIdpTitle=Account already exists
federatedIdentityConfirmLinkMessage=User with {0} {1} already exists. How do you want to continue?
federatedIdentityConfirmReauthenticateMessage=Authenticate to link your account with {0}
nestedFirstBrokerFlowMessage=The {0} user {1} is not linked to any known user.
confirmLinkIdpReviewProfile=Review profile
confirmLinkIdpContinue=Add to existing account
configureTotpMessage=You need to set up Mobile Authenticator to activate your account.
configureBackupCodesMessage=You need to set up Backup Codes to activate your account.
updateProfileMessage=You need to update your user profile to activate your account.
updatePasswordMessage=You need to change your password to activate your account.
updateEmailMessage=You need to update your email address to activate your account.
resetPasswordMessage=You need to change your password.
verifyEmailMessage=You need to verify your email address to activate your account.
linkIdpMessage=You need to verify your email address to link your account with {0}.
emailSentMessage=You should receive an email shortly with further instructions.
emailSendErrorMessage=Failed to send email, please try again later.
accountUpdatedMessage=Your account has been updated.
accountPasswordUpdatedMessage=Your password has been updated.
delegationCompleteHeader=Login Successful
delegationCompleteMessage=You may close this browser window and go back to your console application.
delegationFailedHeader=Login Failed
delegationFailedMessage=You may close this browser window and go back to your console application and try logging in again.
noAccessMessage=No access
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}.
invalidPasswordMaxLengthMessage=Invalid password: maximum length {0}.
invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits.
invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters.
invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters.
invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordNotEmailMessage=Invalid password: must not be equal to the email.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
failedToProcessResponseMessage=Failed to process response
httpsRequiredMessage=HTTPS required
realmNotEnabledMessage=Realm not enabled
invalidRequestMessage=Invalid Request
successLogout=You are logged out
failedLogout=Logout failed
unknownLoginRequesterMessage=Unknown login requester
loginRequesterNotEnabledMessage=Login requester not enabled
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.
implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.
invalidRedirectUriMessage=Invalid redirect uri
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
invalidRequesterMessage=Invalid requester
registrationNotAllowedMessage=Registration not allowed
resetCredentialNotAllowedMessage=Reset Credential not allowed
permissionNotApprovedMessage=Permission not approved.
noRelayStateInResponseMessage=No relay state in response from identity provider.
insufficientPermissionMessage=Insufficient permissions to link identities.
couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
couldNotObtainTokenMessage=Could not obtain token from identity provider.
unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider.
unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider.
identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider.
couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider.
unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider.
invalidAccessCodeMessage=Invalid access code.
sessionNotActiveMessage=Session not active.
invalidCodeMessage=An error occurred, please login again through your application.
cookieNotFoundMessage=Cookie not found. Please make sure cookies are enabled in your browser.
insufficientLevelOfAuthentication=The requested level of authentication has not been satisfied.
identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
identityProviderMissingStateMessage=Missing state parameter in response from identity provider.
identityProviderInvalidResponseMessage=Invalid response from identity provider.
identityProviderInvalidSignatureMessage=Invalid signature in response from identity provider.
identityProviderNotFoundMessage=Could not find an identity provider with the identifier.
identityProviderLinkSuccess=You successfully verified your email. Please go back to your original browser and continue there with the login.
staleCodeMessage=This page is no longer valid, please go back to your application and sign in again
realmSupportsNoCredentialsMessage=Realm does not support any credential type.
credentialSetupRequired=Cannot login, credential setup required.
identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
emailVerifiedMessage=Your email address has been verified.
staleEmailVerificationLink=The link you clicked is an old stale link and is no longer valid. Maybe you have already verified your email.
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
confirmAccountLinking=Confirm linking the account {0} of identity provider {1} with your account.
confirmEmailAddressVerification=Confirm validity of e-mail address {0}.
confirmExecutionOfActions=Perform the following action(s)
locale_ar=\u0639\u0631\u0628\u064A
locale_ca=Catal\u00E0
locale_cs=\u010Ce\u0161tina
locale_da=Dansk
locale_de=Deutsch
locale_en=English
locale_es=Espa\u00F1ol
locale_fr=Fran\u00E7ais
locale_hu=Magyar
locale_it=Italiano
locale_ja=\u65E5\u672C\u8A9E
locale_lt=Lietuvi\u0173
locale_nl=Nederlands
locale_no=Norsk
locale_pl=Polski
locale_pt_BR=Portugu\u00EAs (Brasil)
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439
locale_sk=Sloven\u010Dina
locale_sv=Svenska
locale_tr=T\u00FCrk\u00E7e
locale_zh-CN=\u4E2D\u6587\u7B80\u4F53
locale_fi=Suomi
backToApplication=&laquo; Back to Application
missingParameterMessage=Missing parameters\: {0}
clientNotFoundMessage=Client not found.
clientDisabledMessage=Client disabled.
invalidParameterMessage=Invalid parameter\: {0}
alreadyLoggedIn=You are already logged in.
differentUserAuthenticated=You are already authenticated as different user ''{0}'' in this session. Please sign out first.
brokerLinkingSessionExpired=Requested broker account linking, but current session is no longer valid.
proceedWithAction=&raquo; Click here to proceed
acrNotFulfilled=Authentication requirements not fulfilled
requiredAction.CONFIGURE_TOTP=Configure OTP
requiredAction.TERMS_AND_CONDITIONS=Terms and Conditions
requiredAction.UPDATE_PASSWORD=Update Password
requiredAction.UPDATE_PROFILE=Update Profile
requiredAction.VERIFY_EMAIL=Verify Email
requiredAction.CONFIGURE_RECOVERY_AUTHN_CODES=Generate Recovery Codes
requiredAction.webauthn-register-passwordless=Webauthn Register Passwordless
invalidTokenRequiredActions=Required actions included in the link are not valid
doX509Login=You will be logged in as\:
clientCertificate=X509 client certificate\:
noCertificate=[No Certificate]
pageNotFound=Page not found
internalServerError=An internal server error has occurred
console-username=Username:
console-password=Password:
console-otp=One Time Password:
console-new-password=New Password:
console-confirm-password=Confirm Password:
console-update-password=Update of your password is required.
console-verify-email=You need to verify your email address. We sent an email to {0} that contains a verification code. Please enter this code into the input below.
console-email-code=Email Code:
console-accept-terms=Accept Terms? [y/n]:
console-accept=y
# Openshift messages
openshift.scope.user_info=User information
openshift.scope.user_check-access=User access information
openshift.scope.user_full=Full Access
openshift.scope.list-projects=List projects
# SAML authentication
saml.post-form.title=Authentication Redirect
saml.post-form.message=Redirecting, please wait.
saml.post-form.js-disabled=JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.
saml.artifactResolutionServiceInvalidResponse=Unable to resolve artifact.
#authenticators
otp-display-name=Authenticator Application
otp-help-text=Enter a verification code from authenticator application.
otp-reset-description=Which OTP configuration should be removed?
password-display-name=Password
password-help-text=Sign in by entering your password.
auth-username-form-display-name=Username
auth-username-form-help-text=Start sign in by entering your username
auth-username-password-form-display-name=Username and password
auth-username-password-form-help-text=Sign in by entering your username and password.
# Recovery Codes
auth-recovery-authn-code-form-display-name=Recovery Authentication Code
auth-recovery-authn-code-form-help-text=Enter a recovery authentication code from a previously generated list.
auth-recovery-code-info-message=Enter the specified recovery code.
auth-recovery-code-prompt=Recovery code #{0}
auth-recovery-code-header=Login with a recovery authentication code
recovery-codes-error-invalid=Invalid recovery authentication code
recovery-code-config-header=Recovery Authentication Codes
recovery-code-config-warning-title=These recovery codes won't appear again after leaving this page
recovery-code-config-warning-message=Make sure to print, download, or copy them to a password manager and keep them save. Canceling this setup will remove these recovery codes from your account.
recovery-codes-print=Print
recovery-codes-download=Download
recovery-codes-copy=Copy
recovery-codes-copied=Copied
recovery-codes-confirmation-message=I have saved these codes somewhere safe
recovery-codes-action-complete=Complete setup
recovery-codes-action-cancel=Cancel setup
recovery-codes-download-file-header=Keep these recovery codes somewhere safe.
recovery-codes-download-file-description=Recovery codes are single-use passcodes that allow you to sign in to your account if you do not have access to your authenticator.
recovery-codes-download-file-date= These codes were generated on
recovery-codes-label-default=Recovery codes
# WebAuthn
webauthn-display-name=Security Key
webauthn-help-text=Use your security key to sign in.
webauthn-passwordless-display-name=Security Key
webauthn-passwordless-help-text=Use your security key for passwordless sign in.
webauthn-login-title=Security Key login
webauthn-registration-title=Security Key Registration
webauthn-available-authenticators=Available Security Keys
webauthn-unsupported-browser-text=WebAuthn is not supported by this browser. Try another one or contact your administrator.
webauthn-doAuthenticate=Sign in with Security Key
webauthn-createdAt-label=Created
# WebAuthn Error
webauthn-error-title=Security Key Error
webauthn-error-registration=Failed to register your Security key.<br/> {0}
webauthn-error-api-get=Failed to authenticate by the Security key.<br/> {0}
webauthn-error-different-user=First authenticated user is not the one authenticated by the Security key.
webauthn-error-auth-verification=Security key authentication result is invalid.<br/> {0}
webauthn-error-register-verification=Security key registration result is invalid.<br/> {0}
webauthn-error-user-not-found=Unknown user authenticated by the Security key.
# Identity provider
identity-provider-redirector=Connect with another Identity Provider
identity-provider-login-label=Or sign in with
idp-email-verification-display-name=Email Verification
idp-email-verification-help-text=Link your account by validating your email.
idp-username-password-form-display-name=Username and password
idp-username-password-form-help-text=Link your account by logging in.
finalDeletionConfirmation=If you delete your account, it cannot be restored. To keep your account, click Cancel.
irreversibleAction=This action is irreversible
deleteAccountConfirm=Delete account confirmation
deletingImplies=Deleting your account implies:
errasingData=Erasing all your data
loggingOutImmediately=Logging you out immediately
accountUnusable=Any subsequent use of the application will not be possible with this account
userDeletedSuccessfully=User deleted successfully
access-denied=Access denied
access-denied-when-idp-auth=Access denied when authenticating with {0}
frontchannel-logout.title=Logging out
frontchannel-logout.message=You are logging out from following apps
logoutConfirmTitle=Logging out
logoutConfirmHeader=Do you want to log out?
doLogout=Logout
readOnlyUsernameMessage=You can''t update your username as it is read-only.

View file

@ -0,0 +1,609 @@
/* Patternfly CSS places a "bg-login.jpg" as the background on this ".login-pf" class.
This clashes with the "keycloak-bg.png' background defined on the body below.
Therefore the Patternfly background must be set to none. */
.login-pf {
background: none;
}
.login-pf body {
background: url("../img/keycloak-bg.png") no-repeat center center fixed;
background-size: cover;
height: 100%;
}
textarea.pf-c-form-control {
height: auto;
}
.pf-c-alert__title {
font-size: var(--pf-global--FontSize--xs);
}
p.instruction {
margin: 5px 0;
}
.pf-c-button.pf-m-control {
}
h1#kc-page-title {
margin-top: 10px;
color: rgb(163, 163, 163);
}
#kc-locale ul {
background-color: var(--pf-global--BackgroundColor--100);
display: none;
top: 20px;
min-width: 100px;
padding: 0;
}
#kc-locale-dropdown{
display: inline-block;
}
#kc-locale-dropdown:hover ul {
display:block;
}
#kc-locale-dropdown a {
color: var(--pf-global--Color--200);
text-align: right;
font-size: var(--pf-global--FontSize--sm);
}
a#kc-current-locale-link::after {
content: "\2c5";
margin-left: var(--pf-global--spacer--xs)
}
.login-pf .container {
padding-top: 40px;
}
.login-pf a:hover {
color: #0099d3;
}
#kc-logo {
width: 100%;
}
div.kc-logo-text {
background-image: url(../img/keycloak-logo-text.png);
background-repeat: no-repeat;
height: 63px;
width: 300px;
margin: 0 auto;
}
div.kc-logo-text span {
display: none;
}
#kc-header {
color: #ededed;
overflow: visible;
white-space: nowrap;
}
#kc-header-wrapper {
font-size: 29px;
text-transform: uppercase;
letter-spacing: 3px;
line-height: 1.2em;
padding: 20px 0px 0px 0px;
white-space: normal;
}
#kc-content {
width: 100%;
}
#kc-attempted-username {
font-size: 20px;
font-family: inherit;
font-weight: normal;
padding-right: 10px;
}
#kc-username {
text-align: center;
margin-bottom:-10px;
}
#kc-webauthn-settings-form {
padding-top: 8px;
}
#kc-form-webauthn .select-auth-box-parent {
pointer-events: none;
}
#kc-form-webauthn .select-auth-box-desc {
color: var(--pf-global--palette--black-600);
}
#kc-form-webauthn .select-auth-box-headline {
color: var(--pf-global--Color--300);
}
#kc-form-webauthn .select-auth-box-icon {
flex: 0 0 3em;
}
#kc-form-webauthn .select-auth-box-icon-properties {
margin-top: 10px;
font-size: 1.8em;
}
#kc-form-webauthn .select-auth-box-icon-properties.unknown-transport-class {
margin-top: 3px;
}
#kc-form-webauthn .pf-l-stack__item {
margin: -1px 0;
}
#kc-content-wrapper {
margin-top: 20px;
}
#kc-form-wrapper {
margin-top: 10px;
}
#kc-info {
margin: 20px -40px -30px;
}
#kc-info-wrapper {
font-size: 13px;
padding: 15px 35px;
background-color: #F0F0F0;
}
#kc-form-options span {
display: block;
}
#kc-form-options .checkbox {
margin-top: 0;
color: #72767b;
}
#kc-terms-text {
margin-bottom: 20px;
}
#kc-registration-terms-text {
max-height: 100px;
overflow-y: auto;
overflow-x: hidden;
margin: 5px;
}
#kc-registration {
margin-bottom: 0;
}
/* TOTP */
.subtitle {
text-align: right;
margin-top: 30px;
color: #909090;
}
.required {
color: var(--pf-global--danger-color--200);
}
ol#kc-totp-settings {
margin: 0;
padding-left: 20px;
}
ul#kc-totp-supported-apps {
margin-bottom: 10px;
}
#kc-totp-secret-qr-code {
max-width:150px;
max-height:150px;
}
#kc-totp-secret-key {
background-color: #fff;
color: #333333;
font-size: 16px;
padding: 10px 0;
}
/* OAuth */
#kc-oauth h3 {
margin-top: 0;
}
#kc-oauth ul {
list-style: none;
padding: 0;
margin: 0;
}
#kc-oauth ul li {
border-top: 1px solid rgba(255, 255, 255, 0.1);
font-size: 12px;
padding: 10px 0;
}
#kc-oauth ul li:first-of-type {
border-top: 0;
}
#kc-oauth .kc-role {
display: inline-block;
width: 50%;
}
/* Code */
#kc-code textarea {
width: 100%;
height: 8em;
}
/* Social */
.kc-social-links {
margin-top: 20px;
}
.kc-social-links li {
width: 100%;
}
.kc-social-provider-logo {
font-size: 23px;
width: 30px;
height: 25px;
float: left;
}
.kc-social-gray {
color: var(--pf-global--Color--200);
}
.kc-social-item {
margin-bottom: var(--pf-global--spacer--sm);
font-size: 15px;
text-align: center;
}
.kc-social-provider-name {
position: relative;
}
.kc-social-icon-text {
left: -15px;
}
.kc-social-grid {
display:grid;
grid-column-gap: 10px;
grid-row-gap: 5px;
grid-column-end: span 6;
--pf-l-grid__item--GridColumnEnd: span 6;
}
.kc-social-grid .kc-social-icon-text {
left: -10px;
}
.kc-login-tooltip {
position: relative;
display: inline-block;
}
.kc-social-section {
text-align: center;
}
.kc-social-section hr{
margin-bottom: 10px
}
.kc-login-tooltip .kc-tooltip-text{
top:-3px;
left:160%;
background-color: black;
visibility: hidden;
color: #fff;
min-width:130px;
text-align: center;
border-radius: 2px;
box-shadow:0 1px 8px rgba(0,0,0,0.6);
padding: 5px;
position: absolute;
opacity:0;
transition:opacity 0.5s;
}
/* Show tooltip */
.kc-login-tooltip:hover .kc-tooltip-text {
visibility: visible;
opacity:0.7;
}
/* Arrow for tooltip */
.kc-login-tooltip .kc-tooltip-text::after {
content: " ";
position: absolute;
top: 15px;
right: 100%;
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent black transparent transparent;
}
@media (min-width: 768px) {
#kc-container-wrapper {
position: absolute;
width: 100%;
}
.login-pf .container {
padding-right: 80px;
}
#kc-locale {
position: relative;
text-align: right;
z-index: 9999;
}
}
@media (max-width: 767px) {
.login-pf body {
background: white;
}
#kc-header {
padding-left: 15px;
padding-right: 15px;
float: none;
text-align: left;
}
#kc-header-wrapper {
font-size: 16px;
font-weight: bold;
padding: 20px 0 0 0;
color: #72767b;
letter-spacing: 0;
}
div.kc-logo-text {
margin: 0;
width: 150px;
height: 32px;
background-size: 100%;
}
#kc-form {
float: none;
}
#kc-info-wrapper {
border-top: 1px solid rgba(255, 255, 255, 0.1);
background-color: transparent;
}
.login-pf .container {
padding-top: 15px;
padding-bottom: 15px;
}
#kc-locale {
position: absolute;
width: 200px;
top: 20px;
right: 20px;
text-align: right;
z-index: 9999;
}
}
@media (min-height: 646px) {
#kc-container-wrapper {
bottom: 12%;
}
}
@media (max-height: 645px) {
#kc-container-wrapper {
padding-top: 50px;
top: 20%;
}
}
.card-pf form.form-actions .btn {
float: right;
margin-left: 10px;
}
#kc-form-buttons {
margin-top: 20px;
}
.login-pf-page .login-pf-brand {
margin-top: 20px;
max-width: 360px;
width: 40%;
}
.select-auth-box-arrow{
display: flex;
align-items: center;
margin-right: 2rem;
}
.select-auth-box-icon{
display: flex;
flex: 0 0 2em;
justify-content: center;
margin-right: 1rem;
margin-left: 3rem;
}
.select-auth-box-parent{
border-top: 1px solid var(--pf-global--palette--black-200);
padding-top: 1rem;
padding-bottom: 1rem;
cursor: pointer;
}
.select-auth-box-parent:hover{
background-color: #f7f8f8;
}
.select-auth-container {
padding-bottom: 0px !important;
}
.select-auth-box-headline {
font-size: var(--pf-global--FontSize--md);
color: var(--pf-global--primary-color--100);
font-weight: bold;
}
.select-auth-box-desc {
font-size: var(--pf-global--FontSize--sm);
}
.select-auth-box-paragraph {
text-align: center;
font-size: var(--pf-global--FontSize--md);
margin-bottom: 5px;
}
.card-pf {
margin: 0 auto;
box-shadow: var(--pf-global--BoxShadow--lg);
padding: 0 20px;
max-width: 500px;
border-radius: 15px;
}
/*phone*/
@media (max-width: 100px) {
.login-pf-page .card-pf {
max-width: 500px;
padding: 0 20px;
border-top: 4px solid;
box-shadow: var(--pf-global--BoxShadow--lg);
display: block;
margin-left: auto;
margin-right: auto;
border-radius: 25px;
border-color: var(--pf-global--primary-color--100);
}
.kc-social-grid {
grid-column-end: 12;
--pf-l-grid__item--GridColumnEnd: span 12;
}
.kc-social-grid .kc-social-icon-text {
left: -15px;
}
}
.login-pf-page .login-pf-signup {
font-size: 15px;
color: #72767b;
}
#kc-content-wrapper .row {
margin-left: 0;
margin-right: 0;
}
.login-pf-page.login-pf-page-accounts {
margin-left: auto;
margin-right: auto;
}
.login-pf-page .btn-primary {
margin-top: 0;
}
.login-pf-page .list-view-pf .list-group-item {
border-bottom: 1px solid #ededed;
}
.login-pf-page .list-view-pf-description {
width: 100%;
}
#kc-form-login div.form-group:last-of-type,
#kc-register-form div.form-group:last-of-type,
#kc-update-profile-form div.form-group:last-of-type,
#kc-update-email-form div.form-group:last-of-type{
margin-bottom: 0px;
}
.no-bottom-margin {
margin-bottom: 0;
}
#kc-back {
margin-top: 5px;
}
/* Recovery codes */
.kc-recovery-codes-warning {
margin-bottom: 32px;
}
.kc-recovery-codes-warning .pf-c-alert__description p {
font-size: 0.875rem;
}
.kc-recovery-codes-list {
list-style: none;
columns: 2;
margin: 16px 0;
padding: 16px 16px 8px 16px;
border: 1px solid #D2D2D2;
}
.kc-recovery-codes-list li {
margin-bottom: 8px;
font-size: 11px;
}
.kc-recovery-codes-list li span {
color: #6A6E73;
width: 16px;
text-align: right;
display: inline-block;
margin-right: 1px;
}
.kc-recovery-codes-actions {
margin-bottom: 24px;
}
.kc-recovery-codes-actions button {
padding-left: 0;
}
.kc-recovery-codes-actions button i {
margin-right: 8px;
}
.kc-recovery-codes-confirmation {
align-items: baseline;
margin-bottom: 16px;
}
/* End Recovery codes */

View file

@ -0,0 +1,44 @@
.login-pf body {
background: #39a5dc;
background-size: cover;
height: 100%;
}
.login-pf body {
background: url("../img/xxx.png") no-repeat center center fixed;
background-size: cover;
background-color: rgba(245, 245, 245, 0.896);
height: 100%;
}
div.kc-logo-text {
background-image: url("../img/logo-bangkok4.png");
background-repeat: no-repeat;
height: 192px;
max-width: 192px;
display: block;
margin-left: auto;
margin-right: auto;
}
.pf-c-button.pf-m-primary {
border-radius: 5px;
border-color: 2px black;
background-color: rgb(147, 146, 146);
}
.pf-c-form-control {
background-color: whitesmoke;
border-color: rgb(188, 176, 176);
border-radius: 2px;
}
.pf-c-form__label-text {
color: rgb(122, 122, 122);
font-size: 14px;
}
h1#kc-page-title {
margin-top: 10px;
color: rgb(163, 163, 163);
font-weight: 500;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

View file

@ -0,0 +1,3 @@
parent=keycloak
import=common/keycloak
styles=css/login.css css/styles.css