support add user
This commit is contained in:
parent
2ca3aa73b2
commit
70e87dce04
@ -2,7 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2 class="pb-4 pt-3">Channel Configuration</h2>
|
<h2 class="pb-4 pt-3">Channel Configuration</h2>
|
||||||
<div style="width: 100%; height: 43px">
|
<div class="w-100" style="height: 43px">
|
||||||
<div class="float-end">
|
<div class="float-end">
|
||||||
<button class="btn btn-primary" @click="addChannel()">Add new Channel</button>
|
<button class="btn btn-primary" @click="addChannel()">Add new Channel</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,7 +78,7 @@ const router = useRouter()
|
|||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
authStore.removeToken()
|
authStore.removeToken()
|
||||||
authStore.updateIsLogin(false)
|
authStore.isLogin = false
|
||||||
router.push({ path: '/' })
|
router.push({ path: '/' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,19 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2 class="pb-4 pt-3">User Configuration</h2>
|
<h2 class="pb-4 pt-3">User Configuration</h2>
|
||||||
|
<div class="w-100" style="height: 43px">
|
||||||
|
<div class="float-end">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
title="Add new User"
|
||||||
|
data-tooltip="tooltip"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#userModal"
|
||||||
|
>
|
||||||
|
Add User
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<form v-if="configStore.configUser" @submit.prevent="onSubmitUser">
|
<form v-if="configStore.configUser" @submit.prevent="onSubmitUser">
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<label for="userName" class="col-sm-2 col-form-label">Username</label>
|
<label for="userName" class="col-sm-2 col-form-label">Username</label>
|
||||||
@ -18,7 +31,7 @@
|
|||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
<label for="userMail" class="col-sm-2 col-form-label">mail</label>
|
<label for="userMail" class="col-sm-2 col-form-label">mail</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" id="userMail" v-model="configStore.configUser.mail" />
|
<input type="email" class="form-control" id="userMail" v-model="configStore.configUser.mail" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 row">
|
<div class="mb-3 row">
|
||||||
@ -40,6 +53,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="userModal" ref="userModal" class="modal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="userModalLabel">Add User</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cancel"></button>
|
||||||
|
</div>
|
||||||
|
<form @reset="clearUser" @submit.prevent="addUser">
|
||||||
|
<div class="modal-body">
|
||||||
|
<label for="name-input" class="form-label">Username</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="name-input"
|
||||||
|
aria-describedby="Username"
|
||||||
|
v-model.number="user.username"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label for="mail-input" class="form-label mt-2">Mail</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="mail-input"
|
||||||
|
aria-describedby="Mail Address"
|
||||||
|
v-model.number="user.mail"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label for="pass-input" class="form-label mt-2">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="pass-input"
|
||||||
|
aria-describedby="Password"
|
||||||
|
v-model.number="user.password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<label for="confirm-input" class="form-label mt-2">Confirm Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="confirm-input"
|
||||||
|
aria-describedby="Confirm Password"
|
||||||
|
v-model.number="user.confirm"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="reset" class="btn btn-primary" data-bs-dismiss="modal" aria-label="Cancel">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Ok</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -52,9 +122,59 @@ const authStore = useAuth()
|
|||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
const indexStore = useIndex()
|
const indexStore = useIndex()
|
||||||
|
|
||||||
|
const { $bootstrap } = useNuxtApp()
|
||||||
|
|
||||||
|
const userModal = ref()
|
||||||
const newPass = ref('')
|
const newPass = ref('')
|
||||||
const confirmPass = ref('')
|
const confirmPass = ref('')
|
||||||
|
|
||||||
|
const user = ref({
|
||||||
|
username: '',
|
||||||
|
mail: '',
|
||||||
|
password: '',
|
||||||
|
confirm: '',
|
||||||
|
role_id: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
async function clearUser() {
|
||||||
|
user.value.username = ''
|
||||||
|
user.value.mail = ''
|
||||||
|
user.value.password = ''
|
||||||
|
user.value.confirm = ''
|
||||||
|
user.value.role_id = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addUser() {
|
||||||
|
if (user.value.password === user.value.confirm) {
|
||||||
|
// @ts-ignore
|
||||||
|
const modal = $bootstrap.Modal.getOrCreateInstance(userModal.value)
|
||||||
|
modal.hide()
|
||||||
|
|
||||||
|
authStore.inspectToken()
|
||||||
|
const update = await configStore.addNewUser(user.value)
|
||||||
|
|
||||||
|
if (update.status === 200) {
|
||||||
|
indexStore.alertVariant = 'alert-success'
|
||||||
|
indexStore.alertMsg = 'Add user success!'
|
||||||
|
} else {
|
||||||
|
indexStore.alertVariant = 'alert-danger'
|
||||||
|
indexStore.alertMsg = 'Add user failed!'
|
||||||
|
}
|
||||||
|
|
||||||
|
clearUser()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
indexStore.alertVariant = 'alert-danger'
|
||||||
|
indexStore.alertMsg = 'Password mismatch!'
|
||||||
|
}
|
||||||
|
|
||||||
|
indexStore.showAlert = true
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
indexStore.showAlert = false
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
async function onSubmitUser() {
|
async function onSubmitUser() {
|
||||||
if (newPass && newPass.value === confirmPass.value) {
|
if (newPass && newPass.value === confirmPass.value) {
|
||||||
configStore.configUser.password = newPass.value
|
configStore.configUser.password = newPass.value
|
||||||
|
1101
package-lock.json
generated
1101
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ffplayout-frontend",
|
"name": "ffplayout-frontend",
|
||||||
"version": "0.4.0",
|
"version": "0.4.1",
|
||||||
"description": "Web GUI for ffplayout",
|
"description": "Web GUI for ffplayout",
|
||||||
"author": "Jonathan Baecker",
|
"author": "Jonathan Baecker",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -17,10 +17,10 @@
|
|||||||
"@pinia/nuxt": "^0.4.11",
|
"@pinia/nuxt": "^0.4.11",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@vueuse/core": "^10.4.1",
|
"@vueuse/core": "^10.4.1",
|
||||||
"bootstrap": "^5.3.1",
|
"bootstrap": "^5.3.2",
|
||||||
"bootstrap-icons": "^1.10.5",
|
"bootstrap-icons": "^1.11.1",
|
||||||
"cookie-universal-nuxt": "^2.2.2",
|
"cookie-universal-nuxt": "^2.2.2",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.10",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mpegts.js": "^1.7.3",
|
"mpegts.js": "^1.7.3",
|
||||||
@ -33,17 +33,17 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/eslint-config": "^12.0.0",
|
"@nuxtjs/eslint-config": "^12.0.0",
|
||||||
"@types/bootstrap": "^5.2.6",
|
"@types/bootstrap": "^5.2.7",
|
||||||
"@types/lodash": "^4.14.198",
|
"@types/lodash": "^4.14.199",
|
||||||
"@types/splitpanes": "^2.2.1",
|
"@types/splitpanes": "^2.2.2",
|
||||||
"@types/video.js": "^7.3.52",
|
"@types/video.js": "^7.3.52",
|
||||||
"eslint": "^8.48.0",
|
"eslint": "^8.50.0",
|
||||||
"eslint-plugin-nuxt": "^4.0.0",
|
"eslint-plugin-nuxt": "^4.0.0",
|
||||||
"fibers": "^5.0.3",
|
"fibers": "^5.0.3",
|
||||||
"nuxt": "3.7.1",
|
"nuxt": "3.7.4",
|
||||||
"postcss": "^8.4.29",
|
"postcss": "^8.4.30",
|
||||||
"postcss-loader": "^7.3.3",
|
"postcss-loader": "^7.3.3",
|
||||||
"sass": "^1.66.1",
|
"sass": "^1.68.0",
|
||||||
"sass-loader": "^13.3.2"
|
"sass-loader": "^13.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ async function login() {
|
|||||||
async function logout() {
|
async function logout() {
|
||||||
try {
|
try {
|
||||||
authStore.removeToken()
|
authStore.removeToken()
|
||||||
authStore.updateIsLogin(false)
|
authStore.isLogin = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
formError.value = e as string
|
formError.value = e as string
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import jwtDecode, { JwtPayload } from 'jwt-decode'
|
import jwtDecode, { JwtPayload } from 'jwt-decode'
|
||||||
|
|
||||||
|
interface JwtPayloadExt extends JwtPayload {
|
||||||
|
role: string
|
||||||
|
}
|
||||||
|
|
||||||
export const useAuth = defineStore('auth', {
|
export const useAuth = defineStore('auth', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
isLogin: false,
|
isLogin: false,
|
||||||
jwtToken: '',
|
jwtToken: '',
|
||||||
authHeader: {},
|
authHeader: {},
|
||||||
|
role: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {},
|
getters: {},
|
||||||
@ -22,10 +27,6 @@ export const useAuth = defineStore('auth', {
|
|||||||
this.authHeader = { Authorization: `Bearer ${token}` }
|
this.authHeader = { Authorization: `Bearer ${token}` }
|
||||||
},
|
},
|
||||||
|
|
||||||
updateIsLogin(bool: boolean) {
|
|
||||||
this.isLogin = bool
|
|
||||||
},
|
|
||||||
|
|
||||||
removeToken() {
|
removeToken() {
|
||||||
const cookie = useCookie('token')
|
const cookie = useCookie('token')
|
||||||
cookie.value = null
|
cookie.value = null
|
||||||
@ -52,7 +53,9 @@ export const useAuth = defineStore('auth', {
|
|||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.updateToken(response.user.token)
|
this.updateToken(response.user.token)
|
||||||
this.updateIsLogin(true)
|
const decodedToken = jwtDecode<JwtPayloadExt>(response.user.token)
|
||||||
|
this.isLogin = true
|
||||||
|
this.role = decodedToken.role
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.status) {
|
if (error.status) {
|
||||||
@ -72,18 +75,19 @@ export const useAuth = defineStore('auth', {
|
|||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
this.updateToken(token)
|
this.updateToken(token)
|
||||||
const decodedToken = jwtDecode<JwtPayload>(token)
|
const decodedToken = jwtDecode<JwtPayloadExt>(token)
|
||||||
const timestamp = Date.now() / 1000
|
const timestamp = Date.now() / 1000
|
||||||
const expireToken = decodedToken.exp
|
const expireToken = decodedToken.exp
|
||||||
|
this.role = decodedToken.role
|
||||||
|
|
||||||
if (expireToken && this.jwtToken && expireToken - timestamp > 15) {
|
if (expireToken && this.jwtToken && expireToken - timestamp > 15) {
|
||||||
this.updateIsLogin(true)
|
this.isLogin = true
|
||||||
} else {
|
} else {
|
||||||
// Prompt user to re login.
|
// Prompt user to re login.
|
||||||
this.updateIsLogin(false)
|
this.isLogin = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.updateIsLogin(false)
|
this.isLogin = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -6,26 +6,11 @@ const { timeToSeconds } = stringFormatter()
|
|||||||
import { useAuth } from '~/stores/auth'
|
import { useAuth } from '~/stores/auth'
|
||||||
import { useIndex } from '~/stores/index'
|
import { useIndex } from '~/stores/index'
|
||||||
|
|
||||||
interface GuiConfig {
|
|
||||||
id: number
|
|
||||||
config_path: string
|
|
||||||
extra_extensions: string | string[]
|
|
||||||
name: string
|
|
||||||
preview_url: string
|
|
||||||
service: string
|
|
||||||
uts_offset?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface User {
|
|
||||||
username: string
|
|
||||||
mail: string
|
|
||||||
password?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useConfig = defineStore('config', {
|
export const useConfig = defineStore('config', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
configID: 0,
|
configID: 0,
|
||||||
configCount: 0,
|
configCount: 0,
|
||||||
|
contentType: { 'content-type': 'application/json;charset=UTF-8' },
|
||||||
configGui: [] as GuiConfig[],
|
configGui: [] as GuiConfig[],
|
||||||
configGuiRaw: [] as GuiConfig[],
|
configGuiRaw: [] as GuiConfig[],
|
||||||
startInSec: 0,
|
startInSec: 0,
|
||||||
@ -101,19 +86,18 @@ export const useConfig = defineStore('config', {
|
|||||||
async setGuiConfig(obj: GuiConfig): Promise<any> {
|
async setGuiConfig(obj: GuiConfig): Promise<any> {
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const stringObj = _.cloneDeep(obj)
|
const stringObj = _.cloneDeep(obj)
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
let response
|
let response
|
||||||
|
|
||||||
if (this.configGuiRaw.some((e) => e.id === stringObj.id)) {
|
if (this.configGuiRaw.some((e) => e.id === stringObj.id)) {
|
||||||
response = await fetch(`/api/channel/${obj.id}`, {
|
response = await fetch(`/api/channel/${obj.id}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...this.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify(stringObj),
|
body: JSON.stringify(stringObj),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
response = await fetch('/api/channel/', {
|
response = await fetch('/api/channel/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...this.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify(stringObj),
|
body: JSON.stringify(stringObj),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -173,7 +157,6 @@ export const useConfig = defineStore('config', {
|
|||||||
async setPlayoutConfig(obj: any) {
|
async setPlayoutConfig(obj: any) {
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const channel = this.configGui[this.configID].id
|
const channel = this.configGui[this.configID].id
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
|
|
||||||
this.startInSec = timeToSeconds(obj.playlist.day_start)
|
this.startInSec = timeToSeconds(obj.playlist.day_start)
|
||||||
this.playlistLength = timeToSeconds(obj.playlist.length)
|
this.playlistLength = timeToSeconds(obj.playlist.length)
|
||||||
@ -184,7 +167,7 @@ export const useConfig = defineStore('config', {
|
|||||||
|
|
||||||
const update = await fetch(`/api/playout/config/${channel}`, {
|
const update = await fetch(`/api/playout/config/${channel}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...this.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify(obj),
|
body: JSON.stringify(obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -207,15 +190,27 @@ export const useConfig = defineStore('config', {
|
|||||||
|
|
||||||
async setUserConfig(obj: any) {
|
async setUserConfig(obj: any) {
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
|
|
||||||
|
|
||||||
const update = await fetch(`/api/user/${obj.id}`, {
|
const update = await fetch(`/api/user/${obj.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { ...contentType, ...authStore.authHeader },
|
headers: { ...this.contentType, ...authStore.authHeader },
|
||||||
body: JSON.stringify(obj),
|
body: JSON.stringify(obj),
|
||||||
})
|
})
|
||||||
|
|
||||||
return update
|
return update
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async addNewUser(user: User) {
|
||||||
|
const authStore = useAuth()
|
||||||
|
delete user.confirm
|
||||||
|
|
||||||
|
const update = await fetch('/api/user/', {
|
||||||
|
method: 'Post',
|
||||||
|
headers: { ...this.contentType, ...authStore.authHeader },
|
||||||
|
body: JSON.stringify(user),
|
||||||
|
})
|
||||||
|
|
||||||
|
return update
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -30,10 +30,6 @@ export const usePlaylist = defineStore('playlist', {
|
|||||||
|
|
||||||
getters: {},
|
getters: {},
|
||||||
actions: {
|
actions: {
|
||||||
updatePlaylist(list: any) {
|
|
||||||
this.playlist = list
|
|
||||||
},
|
|
||||||
|
|
||||||
async getPlaylist(date: string) {
|
async getPlaylist(date: string) {
|
||||||
const authStore = useAuth()
|
const authStore = useAuth()
|
||||||
const configStore = useConfig()
|
const configStore = useConfig()
|
||||||
@ -52,13 +48,11 @@ export const usePlaylist = defineStore('playlist', {
|
|||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.program) {
|
if (data.program) {
|
||||||
this.updatePlaylist(
|
this.playlist = processPlaylist(configStore.startInSec, configStore.playlistLength, data.program, false)
|
||||||
processPlaylist(configStore.startInSec, configStore.playlistLength, data.program, false)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.updatePlaylist([])
|
this.playlist = []
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
18
types/index.ts → types/index.d.ts
vendored
18
types/index.ts → types/index.d.ts
vendored
@ -1,6 +1,24 @@
|
|||||||
export { }
|
export { }
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
interface GuiConfig {
|
||||||
|
id: number
|
||||||
|
config_path: string
|
||||||
|
extra_extensions: string | string[]
|
||||||
|
name: string
|
||||||
|
preview_url: string
|
||||||
|
service: string
|
||||||
|
uts_offset?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
username: String
|
||||||
|
mail: String
|
||||||
|
password?: String
|
||||||
|
confirm?: String
|
||||||
|
role_id?: Number
|
||||||
|
}
|
||||||
|
|
||||||
interface Crumb {
|
interface Crumb {
|
||||||
text: string
|
text: string
|
||||||
path: string
|
path: string
|
Loading…
x
Reference in New Issue
Block a user