mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
Merge branch 'dev' into bullseye
This commit is contained in:
commit
1731bd0b10
69 changed files with 4482 additions and 2414 deletions
17
.github/workflows/eslint.yml
vendored
Normal file
17
.github/workflows/eslint.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
name: ESlint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install npm dependencies
|
||||
run: cd app && npm ci
|
||||
- name: Run linter
|
||||
run: cd app && npm run lint
|
|
@ -1,9 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
before_install:
|
||||
- cd app
|
||||
script:
|
||||
- npm ci
|
||||
- npm run lint
|
||||
- npm run build
|
41
README.md
41
README.md
|
@ -1,20 +1,33 @@
|
|||
# YunoHost Admin
|
||||
<h1 align="center">YunoHost Admin</h1>
|
||||
|
||||
[YunoHost](https://yunohost.org) administration web interface (JS client for the API).
|
||||
<div align="center">
|
||||
|
||||

|
||||
[](https://github.com/YunoHost/yunohost-admin/actions/workflows/eslint.yml)
|
||||
[](https://github.com/YunoHost/yunohost-admin/blob/dev/LICENSE)
|
||||
|
||||
[YunoHost](https://yunohost.org) administration web interface (VueJS client for the API).
|
||||
|
||||
This client is a part of the YunoHost project, and can not be installed directly. Please visit the YunoHost website for [installation instructions](https://yunohost.org/install).
|
||||
|
||||
</div>
|
||||
|
||||
## Bug tracker
|
||||
Issues
|
||||
------
|
||||
|
||||
Please report issues on the [YunoHost bugtracker](https://github.com/YunoHost/issues).
|
||||
- [Please report issues on YunoHost bugtracker](https://github.com/YunoHost/issues).
|
||||
|
||||
## Translate
|
||||
Translation
|
||||
-----------
|
||||
|
||||
[](https://translate.yunohost.org/engage/yunohost/?utm_source=widget)
|
||||
You can help translate Yunohost-Admin on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget)
|
||||
|
||||
## Contributing
|
||||
<div align="center"><img src="https://translate.yunohost.org/widgets/yunohost/-/admin/horizontal-auto.svg" alt="Translation status" /></div>
|
||||
|
||||
Feel free to improve the plugin and send a pull request.
|
||||
Developpers
|
||||
-------------
|
||||
|
||||
Contributions are welcome!
|
||||
|
||||
In order to contribute you will need to setup a development environment using [ynh-dev](https://github.com/YunoHost/ynh-dev) (see the README).
|
||||
Once you have a environment running and are attached to it (with `./ynh-dev start`) you will be able to run:
|
||||
|
@ -28,15 +41,3 @@ This command will install all dependencies and start a dev server (based on [web
|
|||
You can also install [Vue Devtools](https://addons.mozilla.org/fr/firefox/addon/vue-js-devtools/) (module for Firefox but also exists for Chromium/Chrome) if you want component trees, performance views and so on.
|
||||
|
||||
On a YunoHost instance, the web admin files are located at `/usr/share/yunohost/admin`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
* [Vue.js](https://vuejs.org/v2/guide/)
|
||||
* [BootstrapVue](https://bootstrap-vue.org/docs)
|
||||
* [Vue i18n](https://kazupon.github.io/vue-i18n/started.html)
|
||||
* [Vue Router](https://router.vuejs.org/guide/)
|
||||
* [Vuex](https://vuex.vuejs.org/)
|
||||
* [Vuelidate](https://vuelidate.js.org/#getting-started)
|
||||
* [date-fns](https://date-fns.org/v2.16.1/docs/Getting-Started)
|
||||
* [Fork Awesome](https://forkaweso.me/Fork-Awesome/icons/) for icons
|
||||
* [FiraGO](https://bboxtype.com/typefaces/FiraGO/#!layout=specimen) and [Fira Code](https://github.com/tonsky/FiraCode) for fonts
|
||||
|
|
4204
app/package-lock.json
generated
4204
app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -18,10 +18,12 @@
|
|||
"firacode": "^5.2.0",
|
||||
"fontsource-firago": "^3.1.5",
|
||||
"fork-awesome": "^1.1.7",
|
||||
"simple-evaluate": "^1.4.3",
|
||||
"vue": "^2.6.12",
|
||||
"vue-i18n": "^8.24.1",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuelidate": "^0.7.6",
|
||||
"vue-showdown": "^2.4.1",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -125,17 +125,31 @@ export default {
|
|||
},
|
||||
|
||||
mounted () {
|
||||
// Konamicode ;P
|
||||
const konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']
|
||||
let step = 0
|
||||
// Unlock copypasta on log view
|
||||
const copypastaCode = ['ArrowDown', 'ArrowDown', 'ArrowUp', 'ArrowUp']
|
||||
let copypastastep = 0
|
||||
document.addEventListener('keydown', ({ key }) => {
|
||||
if (key === konamiCode[step++]) {
|
||||
if (step === konamiCode.length) {
|
||||
this.$store.commit('SET_SPINNER', 'nyancat')
|
||||
step = 0
|
||||
if (key === copypastaCode[copypastastep++]) {
|
||||
if (copypastastep === copypastaCode.length) {
|
||||
document.getElementsByClassName('unselectable').forEach((element) => element.classList.remove('unselectable'))
|
||||
copypastastep = 0
|
||||
}
|
||||
} else {
|
||||
step = 0
|
||||
copypastastep = 0
|
||||
}
|
||||
})
|
||||
|
||||
// Konamicode ;P
|
||||
const konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a']
|
||||
let konamistep = 0
|
||||
document.addEventListener('keydown', ({ key }) => {
|
||||
if (key === konamiCode[konamistep++]) {
|
||||
if (konamistep === konamiCode.length) {
|
||||
this.$store.commit('SET_SPINNER', 'nyancat')
|
||||
konamistep = 0
|
||||
}
|
||||
} else {
|
||||
konamistep = 0
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -38,8 +38,8 @@ import { openWebSocket, getResponseData, handleError } from './handlers'
|
|||
* @param {Boolean} [options.addLocale=false] - Option to append the locale to the query string.
|
||||
* @return {URLSearchParams}
|
||||
*/
|
||||
export function objectToParams (obj, { addLocale = false } = {}) {
|
||||
const urlParams = new URLSearchParams()
|
||||
export function objectToParams (obj, { addLocale = false } = {}, formData = false) {
|
||||
const urlParams = (formData) ? new FormData() : new URLSearchParams()
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => urlParams.append(key, v))
|
||||
|
@ -96,7 +96,7 @@ export default {
|
|||
if (method === 'GET') {
|
||||
uri += `${uri.includes('?') ? '&' : '?'}locale=${store.getters.locale}`
|
||||
} else {
|
||||
options = { ...options, method, body: objectToParams(data, { addLocale: true }) }
|
||||
options = { ...options, method, body: objectToParams(data, { addLocale: true }, true) }
|
||||
}
|
||||
|
||||
const response = await fetch('/yunohost/api/' + uri, options)
|
||||
|
|
|
@ -49,6 +49,7 @@ class APIBadRequestError extends APIError {
|
|||
super(method, response, errorData)
|
||||
this.name = 'APIBadRequestError'
|
||||
this.key = errorData.error_key
|
||||
this.data = errorData
|
||||
}
|
||||
}
|
||||
|
||||
|
|
59
app/src/components/LazyRenderer.vue
Normal file
59
app/src/components/LazyRenderer.vue
Normal file
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class="lazy-renderer" :style="`min-height: ${fixedMinHeight}px`">
|
||||
<slot v-if="render" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LazyRenderer',
|
||||
|
||||
props: {
|
||||
unrender: { type: Boolean, default: true },
|
||||
minHeight: { type: Number, default: 0 },
|
||||
renderDelay: { type: Number, default: 100 },
|
||||
unrenderDelay: { type: Number, default: 2000 },
|
||||
rootMargin: { type: String, default: '300px' }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
observer: null,
|
||||
render: false,
|
||||
fixedMinHeight: this.minHeight
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
let unrenderTimer
|
||||
let renderTimer
|
||||
this.observer = new IntersectionObserver(entries => {
|
||||
if (entries[0].isIntersecting) {
|
||||
clearTimeout(unrenderTimer)
|
||||
// Show the component after a delay (to avoid rendering while scrolling fast)
|
||||
renderTimer = setTimeout(() => {
|
||||
this.render = true
|
||||
}, this.unrender ? this.renderDelay : 0)
|
||||
|
||||
if (!this.unrender) {
|
||||
// Stop listening to intersections after first appearance if unrendering is not activated
|
||||
this.observer.disconnect()
|
||||
}
|
||||
} else if (this.unrender) {
|
||||
clearTimeout(renderTimer)
|
||||
// Hide the component after a delay if it's no longer in the viewport
|
||||
unrenderTimer = setTimeout(() => {
|
||||
this.fixedMinHeight = this.$el.clientHeight
|
||||
this.render = false
|
||||
}, this.unrenderDelay)
|
||||
}
|
||||
}, { rootMargin: this.rootMargin })
|
||||
|
||||
this.observer.observe(this.$el)
|
||||
},
|
||||
|
||||
beforeDestroy () {
|
||||
this.observer.disconnect()
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,9 +1,15 @@
|
|||
<template>
|
||||
<b-list-group
|
||||
v-bind="$attrs" ref="self"
|
||||
flush :class="{ 'fixed-height': fixedHeight, 'bordered': bordered }"
|
||||
v-bind="$attrs" flush
|
||||
:class="{ 'fixed-height': fixedHeight, 'bordered': bordered }"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<b-list-group-item v-for="({ color, text }, i) in messages" :key="i">
|
||||
<b-list-group-item
|
||||
v-if="limit && messages.length > limit"
|
||||
variant="info" v-t="'api.partial_logs'"
|
||||
/>
|
||||
|
||||
<b-list-group-item v-for="({ color, text }, i) in reducedMessages" :key="i">
|
||||
<span class="status" :class="'bg-' + color" />
|
||||
<span v-html="text" />
|
||||
</b-list-group-item>
|
||||
|
@ -18,15 +24,36 @@ export default {
|
|||
messages: { type: Array, required: true },
|
||||
fixedHeight: { type: Boolean, default: false },
|
||||
bordered: { type: Boolean, default: false },
|
||||
autoScroll: { type: Boolean, default: false }
|
||||
autoScroll: { type: Boolean, default: false },
|
||||
limit: { type: Number, default: null }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
auto: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
reducedMessages () {
|
||||
const len = this.messages.length
|
||||
if (!this.limit || len <= this.limit) {
|
||||
return this.messages
|
||||
}
|
||||
return this.messages.slice(len - this.limit)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
scrollToEnd () {
|
||||
if (!this.auto) return
|
||||
this.$nextTick(() => {
|
||||
const container = this.$refs.self
|
||||
container.scrollTo(0, container.lastElementChild.offsetTop)
|
||||
this.$el.scrollTo(0, this.$el.lastElementChild.offsetTop)
|
||||
})
|
||||
},
|
||||
|
||||
onScroll ({ target }) {
|
||||
this.auto = target.scrollHeight === target.scrollTop + target.clientHeight
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -84,6 +84,7 @@ export default {
|
|||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
& > *:not(:first-child) {
|
||||
margin-left: .5rem;
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
<slot name="server-error">
|
||||
<b-alert
|
||||
variant="danger" class="my-3"
|
||||
:show="serverError !== ''" v-html="serverError"
|
||||
variant="danger" class="my-3" icon="ban"
|
||||
:show="errorFeedback !== ''" v-html="errorFeedback"
|
||||
/>
|
||||
</slot>
|
||||
</b-form>
|
||||
|
@ -46,7 +46,13 @@ export default {
|
|||
|
||||
computed: {
|
||||
disabled () {
|
||||
return this.validation ? this.validation.$invalid : false
|
||||
return false // this.validation ? this.validation.$invalid : false
|
||||
},
|
||||
errorFeedback () {
|
||||
if (this.serverError) return this.serverError
|
||||
else if (this.validation && this.validation.$anyError) {
|
||||
return this.$i18n.t('form_errors.invalid_form')
|
||||
} else return ''
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -26,18 +26,17 @@
|
|||
|
||||
<template #description>
|
||||
<!-- Render description -->
|
||||
<template v-if="description || example || link">
|
||||
<template v-if="description || link">
|
||||
<div class="d-flex">
|
||||
<span v-if="example">{{ $t('form_input_example', { example }) }}</span>
|
||||
|
||||
<b-link v-if="link" :to="link" class="ml-auto">
|
||||
<b-link v-if="link" :to="link" :href="link.href"
|
||||
class="ml-auto"
|
||||
>
|
||||
{{ link.text }}
|
||||
</b-link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="description" v-html="description"
|
||||
:class="{ ['alert p-1 px-2 alert-' + descriptionVariant]: descriptionVariant }"
|
||||
<vue-showdown :markdown="description" flavor="github" v-if="description"
|
||||
:class="{ ['alert p-1 px-2 alert-' + descriptionVariant]: descriptionVariant }"
|
||||
/>
|
||||
</template>
|
||||
<!-- Slot available to overwrite the one above -->
|
||||
|
@ -57,7 +56,6 @@ export default {
|
|||
id: { type: String, default: null },
|
||||
description: { type: String, default: null },
|
||||
descriptionVariant: { type: String, default: null },
|
||||
example: { type: String, default: null },
|
||||
link: { type: Object, default: null },
|
||||
// Rendered field component props
|
||||
component: { type: String, default: 'InputItem' },
|
||||
|
|
|
@ -89,6 +89,9 @@ export default {
|
|||
|
||||
::v-deep .btn {
|
||||
margin-left: .5rem;
|
||||
&.dropdown-toggle-split {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<b-alert v-if="items === null || filteredItems === null" variant="warning">
|
||||
<slot name="alert-message">
|
||||
<icon iname="exclamation-triangle" />
|
||||
{{ $t(items === null ? 'items_verbose_count' : 'search.not_found', { items: $tc('items.' + itemsName, 0) }) }}
|
||||
{{ $tc(items === null ? 'items_verbose_count': 'search.not_found', 0, { items: $tc('items.' + itemsName, 0) }) }}
|
||||
</slot>
|
||||
</b-alert>
|
||||
|
||||
|
|
65
app/src/components/globals/formItems/FileItem.vue
Normal file
65
app/src/components/globals/formItems/FileItem.vue
Normal file
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<b-button-group class="w-100">
|
||||
<b-button @click="clearFiles" variant="danger" v-if="!this.required && this.value !== null && !this.value._removed">
|
||||
<icon iname="trash" />
|
||||
</b-button>
|
||||
<b-form-file
|
||||
v-model="file"
|
||||
ref="input-file"
|
||||
:id="id"
|
||||
v-on="$listeners"
|
||||
:required="required"
|
||||
:placeholder="_placeholder"
|
||||
:accept="accept"
|
||||
:drop-placeholder="dropPlaceholder"
|
||||
:state="state"
|
||||
:browse-text="$t('words.browse')"
|
||||
@blur="$parent.$emit('touch', name)"
|
||||
@focusout.native="$parent.$emit('touch', name)"
|
||||
/>
|
||||
</b-button-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FileItem',
|
||||
|
||||
data () {
|
||||
return {
|
||||
file: this.value
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
id: { type: String, default: null },
|
||||
value: { type: [File, null], default: null },
|
||||
placeholder: { type: String, default: 'Choose a file or drop it here...' },
|
||||
dropPlaceholder: { type: String, default: null },
|
||||
accept: { type: String, default: null },
|
||||
state: { type: Boolean, default: null },
|
||||
required: { type: Boolean, default: false },
|
||||
name: { type: String, default: null }
|
||||
},
|
||||
|
||||
computed: {
|
||||
_placeholder: function () {
|
||||
return (this.value === null) ? this.placeholder : this.value.name
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
clearFiles () {
|
||||
const f = new File([''], this.placeholder)
|
||||
f._removed = true
|
||||
if (this.value && this.value.currentfile) {
|
||||
this.$refs['input-file'].reset()
|
||||
this.$emit('input', f)
|
||||
} else {
|
||||
this.$refs['input-file'].setFiles([f])
|
||||
this.file = f
|
||||
this.$emit('input', f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -7,14 +7,25 @@
|
|||
:type="type"
|
||||
:state="state"
|
||||
:required="required"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
:trim="trim"
|
||||
:autocomplete="autocomplete_"
|
||||
@blur="$parent.$emit('touch', name)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'InputItem',
|
||||
|
||||
data () {
|
||||
return {
|
||||
autocomplete_: (this.autocomplete) ? this.autocomplete : (this.type === 'password') ? 'new-password' : null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: { type: [String, Number], default: null },
|
||||
id: { type: String, default: null },
|
||||
|
@ -22,6 +33,12 @@ export default {
|
|||
type: { type: String, default: 'text' },
|
||||
required: { type: Boolean, default: false },
|
||||
state: { type: Boolean, default: null },
|
||||
min: { type: Number, default: null },
|
||||
max: { type: Number, default: null },
|
||||
step: { type: Number, default: null },
|
||||
trim: { type: Boolean, default: true },
|
||||
autocomplete: { type: String, default: null },
|
||||
pattern: { type: Object, default: null },
|
||||
name: { type: String, default: null }
|
||||
}
|
||||
}
|
||||
|
|
15
app/src/components/globals/formItems/MarkdownItem.vue
Normal file
15
app/src/components/globals/formItems/MarkdownItem.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<template>
|
||||
<vue-showdown :markdown="label" flavor="github" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MarkdownItem',
|
||||
|
||||
props: {
|
||||
id: { type: String, default: null },
|
||||
label: { type: String, default: null }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
42
app/src/components/globals/formItems/ReadOnlyAlertItem.vue
Normal file
42
app/src/components/globals/formItems/ReadOnlyAlertItem.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<b-alert :variant="type" show>
|
||||
<icon :iname="icon_" />
|
||||
<vue-showdown :markdown="label" flavor="github"
|
||||
tag="span" class="markdown"
|
||||
/>
|
||||
</b-alert>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ReadOnlyAlertItem',
|
||||
|
||||
data () {
|
||||
const icons = {
|
||||
success: 'thumbs-up',
|
||||
info: 'info',
|
||||
warning: 'warning',
|
||||
danger: 'ban'
|
||||
}
|
||||
return {
|
||||
icon_: (this.icon) ? this.icon : icons[this.type]
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
id: { type: String, default: null },
|
||||
label: { type: String, default: null },
|
||||
type: { type: String, default: null },
|
||||
icon: { type: String, default: null }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.icon + span.markdown > *:first-child {
|
||||
display: inline-block;
|
||||
}
|
||||
.alert p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
36
app/src/components/globals/formItems/TagsItem.vue
Normal file
36
app/src/components/globals/formItems/TagsItem.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<b-form-tags
|
||||
v-model="tags"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
separator=" ,;"
|
||||
:limit="limit"
|
||||
remove-on-delete
|
||||
:state="state"
|
||||
v-on="$listeners"
|
||||
@blur="$parent.$emit('touch', name)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TagsItem',
|
||||
|
||||
data () {
|
||||
return {
|
||||
tags: this.value
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: { type: Array, default: null },
|
||||
id: { type: String, default: null },
|
||||
placeholder: { type: String, default: null },
|
||||
limit: { type: Number, default: null },
|
||||
required: { type: Boolean, default: false },
|
||||
state: { type: Boolean, default: null },
|
||||
name: { type: String, default: null }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<b-form-group
|
||||
:label="$t('search.for', { items: itemsName })"
|
||||
label-cols-md="auto" label-size="sm" :label-for="id + '-search-input'"
|
||||
:invalid-feedback="$t('search.not_found', { items: $tc('items.' + itemsName, 0) })"
|
||||
:invalid-feedback="$tc('search.not_found', 0, { items: $tc('items.' + itemsName, 0) })"
|
||||
:state="searchState" :disabled="disabled"
|
||||
class="mb-0"
|
||||
>
|
||||
|
@ -57,7 +57,7 @@
|
|||
</b-dropdown-item-button>
|
||||
<b-dropdown-text v-if="!criteria && availableOptions.length === 0">
|
||||
<icon iname="exclamation-triangle" />
|
||||
{{ $t('items_verbose_items_left', { items: $tc('items.' + itemsName, 0) }) }}
|
||||
{{ $tc('items_verbose_items_left', 0, { items: $tc('items.' + itemsName, 0) }) }}
|
||||
</b-dropdown-text>
|
||||
</b-dropdown>
|
||||
</template>
|
||||
|
@ -67,12 +67,15 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TagsSelectize',
|
||||
name: 'TagsSelectizeItem',
|
||||
|
||||
props: {
|
||||
value: { type: Array, required: true },
|
||||
options: { type: Array, required: true },
|
||||
id: { type: String, required: true },
|
||||
placeholder: { type: String, default: null },
|
||||
limit: { type: Number, default: null },
|
||||
name: { type: String, default: null },
|
||||
itemsName: { type: String, required: true },
|
||||
disabledItems: { type: Array, default: () => ([]) },
|
||||
// By default `addTag` and `removeTag` have to be executed manually by listening to 'tag-update'.
|
28
app/src/components/globals/formItems/TextAreaItem.vue
Normal file
28
app/src/components/globals/formItems/TextAreaItem.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<b-form-textarea
|
||||
v-model="value"
|
||||
:id="id"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
:state="state"
|
||||
rows="4"
|
||||
v-on="$listeners"
|
||||
@blur="$parent.$emit('touch', name)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TextAreaItem',
|
||||
|
||||
props: {
|
||||
value: { type: String, default: null },
|
||||
id: { type: String, default: null },
|
||||
placeholder: { type: String, default: null },
|
||||
type: { type: String, default: 'text' },
|
||||
required: { type: Boolean, default: false },
|
||||
state: { type: Boolean, default: null },
|
||||
name: { type: String, default: null }
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -34,7 +34,7 @@ const emailForward = value => helpers.withParams(
|
|||
}
|
||||
)(value)
|
||||
|
||||
const githubLink = helpers.regex('githubLink', /^https:\/\/github.com\/[a-zA-Z0-9-_.]+\/[a-zA-Z0-9-_.]+[/]?$/)
|
||||
const appRepoUrl = helpers.regex('appRepoUrl', /^https:\/\/[a-zA-Z0-9-_.]+\/[a-zA-Z0-9-_./]+\/[a-zA-Z0-9-_.]+_ynh(\/?(-\/)?tree\/[a-zA-Z0-9-_.]+)?(\.git)?\/?$/)
|
||||
|
||||
const includes = items => item => helpers.withParams(
|
||||
{ type: 'includes', value: item },
|
||||
|
@ -48,7 +48,6 @@ const unique = items => item => helpers.withParams(
|
|||
item => items ? !helpers.req(item) || !items.includes(item) : true
|
||||
)(item)
|
||||
|
||||
|
||||
export {
|
||||
alphalownum_,
|
||||
domain,
|
||||
|
@ -57,7 +56,7 @@ export {
|
|||
emailForward,
|
||||
emailForwardLocalPart,
|
||||
emailLocalPart,
|
||||
githubLink,
|
||||
appRepoUrl,
|
||||
includes,
|
||||
name,
|
||||
unique
|
||||
|
|
|
@ -8,13 +8,13 @@ import { isObjectLiteral, isEmptyValue, flattenObjectLiteral } from '@/helpers/c
|
|||
* Tries to find a translation corresponding to the user's locale/fallback locale in a
|
||||
* Yunohost argument or simply return the string if it's not an object literal.
|
||||
*
|
||||
* @param {(Object|String)} field - A field value containing a translation object or string
|
||||
* @param {(Object|String|undefined)} field - A field value containing a translation object or string
|
||||
* @return {String}
|
||||
*/
|
||||
export function formatI18nField (field) {
|
||||
if (typeof field === 'string') return field
|
||||
const { locale, fallbackLocale } = store.state
|
||||
return field[locale] || field[fallbackLocale] || field.en
|
||||
return field ? field[locale] || field[fallbackLocale] || field.en : ''
|
||||
}
|
||||
|
||||
|
||||
|
@ -56,85 +56,209 @@ export function adressToFormValue (address) {
|
|||
* @return {Object} an formated argument containing formItem props, validation and base value.
|
||||
*/
|
||||
export function formatYunoHostArgument (arg) {
|
||||
let value = null
|
||||
let value = (arg.value !== undefined) ? arg.value : (arg.current_value !== undefined) ? arg.current_value : null
|
||||
const validation = {}
|
||||
const error = { message: null }
|
||||
arg.ask = formatI18nField(arg.ask)
|
||||
const field = {
|
||||
component: undefined,
|
||||
label: formatI18nField(arg.ask),
|
||||
label: arg.ask,
|
||||
props: {}
|
||||
}
|
||||
|
||||
if (arg.type === 'boolean') {
|
||||
field.id = arg.name
|
||||
} else {
|
||||
field.props.id = arg.name
|
||||
}
|
||||
|
||||
// Some apps has an argument type `string` as type but expect a select since it has `choices`
|
||||
if (arg.choices !== undefined) {
|
||||
field.component = 'SelectItem'
|
||||
field.props.choices = arg.choices
|
||||
// Input
|
||||
} else if ([undefined, 'string', 'number', 'password', 'email'].includes(arg.type)) {
|
||||
field.component = 'InputItem'
|
||||
if (![undefined, 'string'].includes(arg.type)) {
|
||||
field.props.type = arg.type
|
||||
if (arg.type === 'password') {
|
||||
field.description = i18n.t('good_practices_about_admin_password')
|
||||
field.placeholder = '••••••••'
|
||||
const defaultProps = ['id:name', 'placeholder:example']
|
||||
const components = [
|
||||
{
|
||||
types: [undefined, 'string', 'path'],
|
||||
name: 'InputItem',
|
||||
props: defaultProps.concat(['autocomplete', 'trim', 'choices']),
|
||||
callback: function () {
|
||||
if (arg.choices) {
|
||||
arg.type = 'select'
|
||||
this.name = 'SelectItem'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['email', 'url', 'date', 'time', 'color'],
|
||||
name: 'InputItem',
|
||||
props: defaultProps.concat(['type', 'trim'])
|
||||
},
|
||||
{
|
||||
types: ['password'],
|
||||
name: 'InputItem',
|
||||
props: defaultProps.concat(['type', 'autocomplete', 'trim']),
|
||||
callback: function () {
|
||||
if (!arg.help) {
|
||||
arg.help = 'good_practices_about_admin_password'
|
||||
}
|
||||
arg.example = '••••••••••••'
|
||||
validation.passwordLenght = validators.minLength(8)
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['number', 'range'],
|
||||
name: 'InputItem',
|
||||
props: defaultProps.concat(['type', 'min', 'max', 'step']),
|
||||
callback: function () {
|
||||
if (!isNaN(parseInt(arg.min))) {
|
||||
validation.minValue = validators.minValue(parseInt(arg.min))
|
||||
}
|
||||
if (!isNaN(parseInt(arg.max))) {
|
||||
validation.maxValue = validators.maxValue(parseInt(arg.max))
|
||||
}
|
||||
validation.numValue = validators.helpers.regex('Please provide an integer', new RegExp('^-?[0-9]+$'))
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['select'],
|
||||
name: 'SelectItem',
|
||||
props: ['id:name', 'choices']
|
||||
},
|
||||
{
|
||||
types: ['user', 'domain'],
|
||||
name: 'SelectItem',
|
||||
props: ['id:name', 'choices'],
|
||||
callback: function () {
|
||||
field.link = { name: arg.type + '-list', text: i18n.t(`manage_${arg.type}s`) }
|
||||
field.props.choices = store.getters[arg.type + 'sAsChoices']
|
||||
if (value) {
|
||||
return
|
||||
}
|
||||
if (arg.type === 'domain') {
|
||||
value = store.getters.mainDomain
|
||||
} else {
|
||||
value = field.props.choices.length ? field.props.choices[0].value : null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['file'],
|
||||
name: 'FileItem',
|
||||
props: defaultProps.concat(['accept']),
|
||||
callback: function () {
|
||||
if (value) {
|
||||
value = new File([''], value)
|
||||
value.currentfile = true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['text'],
|
||||
name: 'TextAreaItem',
|
||||
props: defaultProps
|
||||
},
|
||||
{
|
||||
types: ['tags'],
|
||||
name: 'TagsItem',
|
||||
props: defaultProps.concat(['limit', 'placeholder', 'options:choices', 'tagIcon:icon']),
|
||||
callback: function () {
|
||||
if (arg.choices) {
|
||||
this.name = 'TagsSelectizeItem'
|
||||
field.props.auto = true
|
||||
field.props.itemsName = ''
|
||||
field.props.label = arg.placeholder
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
value = value.split(',')
|
||||
} else if (!value) {
|
||||
value = []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['boolean'],
|
||||
name: 'CheckboxItem',
|
||||
props: ['id:name', 'choices'],
|
||||
callback: function () {
|
||||
if (value !== null && value !== undefined) {
|
||||
value = ['1', 'yes', 'y', 'true'].includes(String(value).toLowerCase())
|
||||
} else if (arg.default !== null && arg.default !== undefined) {
|
||||
value = ['1', 'yes', 'y', 'true'].includes(String(arg.default).toLowerCase())
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
types: ['alert'],
|
||||
name: 'ReadOnlyAlertItem',
|
||||
props: ['type:style', 'label:ask', 'icon'],
|
||||
readonly: true
|
||||
},
|
||||
{
|
||||
types: ['markdown', 'display_text'],
|
||||
name: 'MarkdownItem',
|
||||
props: ['label:ask'],
|
||||
readonly: true
|
||||
}
|
||||
// Checkbox
|
||||
} else if (arg.type === 'boolean') {
|
||||
field.component = 'CheckboxItem'
|
||||
if (typeof arg.default === 'number') {
|
||||
value = arg.default === 1
|
||||
} else {
|
||||
value = arg.default || false
|
||||
}
|
||||
// Special (store related)
|
||||
} else if (['user', 'domain'].includes(arg.type)) {
|
||||
field.component = 'SelectItem'
|
||||
field.link = { name: arg.type + '-list', text: i18n.t(`manage_${arg.type}s`) }
|
||||
field.props.choices = store.getters[arg.type + 'sAsChoices']
|
||||
if (arg.type === 'domain') {
|
||||
value = store.getters.mainDomain
|
||||
} else {
|
||||
value = field.props.choices.length ? field.props.choices[0].value : null
|
||||
}
|
||||
]
|
||||
|
||||
// Unknown from the specs, try to display it as an input[text]
|
||||
// FIXME throw an error instead ?
|
||||
} else {
|
||||
field.component = 'InputItem'
|
||||
// Default type management if no one is filled
|
||||
if (arg.type === undefined) {
|
||||
arg.type = (arg.choices === undefined) ? 'string' : 'select'
|
||||
}
|
||||
|
||||
// Search the component bind to the type
|
||||
const component = components.find(element => element.types.includes(arg.type))
|
||||
if (component === undefined) throw new TypeError('Unknown type: ' + arg.type)
|
||||
// Callback use for specific behaviour
|
||||
if (component.callback) component.callback()
|
||||
field.component = component.name
|
||||
// Affect properties to the field Item
|
||||
for (let prop of component.props) {
|
||||
prop = prop.split(':')
|
||||
const propName = prop[0]
|
||||
const argName = prop.slice(-1)[0]
|
||||
if (argName in arg) {
|
||||
field.props[propName] = arg[argName]
|
||||
}
|
||||
}
|
||||
// We don't want to display a label html item as this kind or field contains
|
||||
// already the text to display
|
||||
if (component.readonly) delete field.label
|
||||
// Required (no need for checkbox its value can't be null)
|
||||
if (field.component !== 'CheckboxItem' && arg.optional !== true) {
|
||||
else if (field.component !== 'CheckboxItem' && arg.optional !== true) {
|
||||
validation.required = validators.required
|
||||
}
|
||||
if (arg.pattern && arg.type !== 'tags') {
|
||||
// validation.pattern = validators.helpers.withMessage(arg.pattern.error,
|
||||
validation.pattern = validators.helpers.regex(arg.pattern.error, new RegExp(arg.pattern.regexp))
|
||||
}
|
||||
validation.remote = validators.helpers.withParams(error, (v) => {
|
||||
const result = !error.message
|
||||
error.message = null
|
||||
return result
|
||||
})
|
||||
|
||||
|
||||
// field.props['title'] = field.pattern.error
|
||||
// Default value if still `null`
|
||||
if (value === null && arg.current_value) {
|
||||
value = arg.current_value
|
||||
}
|
||||
if (value === null && arg.default) {
|
||||
value = arg.default
|
||||
}
|
||||
|
||||
// Help message
|
||||
if (arg.help) {
|
||||
field.description = formatI18nField(arg.help)
|
||||
}
|
||||
// Example
|
||||
if (arg.example) {
|
||||
field.example = arg.example
|
||||
if (field.component === 'InputItem') {
|
||||
field.props.placeholder = field.example
|
||||
}
|
||||
|
||||
// Help message
|
||||
if (arg.helpLink) {
|
||||
field.link = { href: arg.helpLink.href, text: i18n.t(arg.helpLink.text) }
|
||||
}
|
||||
|
||||
if (arg.visible) {
|
||||
field.visible = arg.visible
|
||||
// Temporary value to wait visible expression to be evaluated
|
||||
field.isVisible = true
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
field,
|
||||
// Return null instead of empty object if there's no validation
|
||||
validation: Object.keys(validation).length === 0 ? null : validation
|
||||
validation: Object.keys(validation).length === 0 ? null : validation,
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,10 +272,10 @@ export function formatYunoHostArgument (arg) {
|
|||
* @return {Object} an object containing all parsed values to be used in vue views.
|
||||
*/
|
||||
export function formatYunoHostArguments (args, name = null) {
|
||||
let disclaimer = null
|
||||
const form = {}
|
||||
const fields = {}
|
||||
const validations = {}
|
||||
const errors = {}
|
||||
|
||||
// FIXME yunohost should add the label field by default
|
||||
if (name) {
|
||||
|
@ -163,20 +287,35 @@ export function formatYunoHostArguments (args, name = null) {
|
|||
}
|
||||
|
||||
for (const arg of args) {
|
||||
if (arg.type === 'display_text') {
|
||||
disclaimer = formatI18nField(arg.ask)
|
||||
} else {
|
||||
const { value, field, validation } = formatYunoHostArgument(arg)
|
||||
fields[arg.name] = field
|
||||
form[arg.name] = value
|
||||
if (validation) validations[arg.name] = validation
|
||||
}
|
||||
const { value, field, validation, error } = formatYunoHostArgument(arg)
|
||||
fields[arg.name] = field
|
||||
form[arg.name] = value
|
||||
if (validation) validations[arg.name] = validation
|
||||
errors[arg.name] = error
|
||||
}
|
||||
|
||||
return { form, fields, validations, disclaimer }
|
||||
return { form, fields, validations, errors }
|
||||
}
|
||||
|
||||
|
||||
export function pFileReader (file, output, key, base64 = true) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fr = new FileReader()
|
||||
fr.onerror = reject
|
||||
fr.onload = () => {
|
||||
output[key] = fr.result
|
||||
if (base64) {
|
||||
output[key] = fr.result.replace(/data:[^;]*;base64,/, '')
|
||||
}
|
||||
output[key + '[name]'] = file.name
|
||||
resolve()
|
||||
}
|
||||
if (base64) {
|
||||
fr.readAsDataURL(file)
|
||||
} else {
|
||||
fr.readAsText(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Format helper for a form value.
|
||||
* Convert Boolean to (1|0) and concatenate adresses.
|
||||
|
@ -204,14 +343,15 @@ export function formatFormDataValue (value) {
|
|||
* @param {Boolean} [extraParams.removeEmpty=true] - Removes "empty" values from the object.
|
||||
* @return {Object} the parsed data to be sent to the server, with extracted values if specified.
|
||||
*/
|
||||
export function formatFormData (
|
||||
export async function formatFormData (
|
||||
formData,
|
||||
{ extract = null, flatten = false, removeEmpty = true } = {}
|
||||
{ extract = null, flatten = false, removeEmpty = true, removeNull = false, multipart = true } = {}
|
||||
) {
|
||||
const output = {
|
||||
data: {},
|
||||
extracted: {}
|
||||
}
|
||||
const promises = []
|
||||
for (const key in formData) {
|
||||
const type = extract && extract.includes(key) ? 'extracted' : 'data'
|
||||
const value = Array.isArray(formData[key])
|
||||
|
@ -220,12 +360,23 @@ export function formatFormData (
|
|||
|
||||
if (removeEmpty && isEmptyValue(value)) {
|
||||
continue
|
||||
} else if (removeNull && (value === null || value === undefined)) {
|
||||
continue
|
||||
} else if (value instanceof File && !multipart) {
|
||||
if (value.currentfile) {
|
||||
continue
|
||||
} else if (value._removed) {
|
||||
output[type][key] = ''
|
||||
continue
|
||||
}
|
||||
promises.push(pFileReader(value, output[type], key))
|
||||
} else if (flatten && isObjectLiteral(value)) {
|
||||
flattenObjectLiteral(value, output[type])
|
||||
} else {
|
||||
output[type][key] = value
|
||||
}
|
||||
}
|
||||
if (promises.length) await Promise.all(promises)
|
||||
const { data, extracted } = output
|
||||
return extract ? { data, ...extracted } : data
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@
|
|||
"postinstall_domain": "سوف يصبح هذا إسم النطاق الرئيسي المرتبط بخادوم واي يونوهوست YunoHost و الذي مِن خلاله يُمكن للمستخدمين النفاذ إلى بوابة المصادقة قصد تسجيل دخولهم. إسم النطاق هذا سيكون ظاهرا لكافة المستخدمين، لذا يتوجّب اختياره بعناية.",
|
||||
"postinstall_intro_1": "هنيئًا ! تمت عملية تنصيب YunoHost بنجاح.",
|
||||
"postinstall_intro_2": "لم يتبقى إلّا إكمال خطوتين لازمتين لتفعيل خدمات السيرفر.",
|
||||
"postinstall_intro_3": "يمكنكم الحصول على مزيد مِن التفاصيل بزيارة <a href='//yunohost.org/postinstall' target='_blank'>صفحة التعليمات المخصصة لذلك</a>",
|
||||
"postinstall_intro_3": "يمكنكم الحصول على مزيد مِن التفاصيل بزيارة <a href='//yunohost.org/en/install/hardware:vps_debian#fa-cog-proceed-with-the-initial-configuration' target='_blank'>صفحة التعليمات المخصصة لذلك</a>",
|
||||
"postinstall_password": "سوف يتم استخدام كلمة السر هذه في إدارة كافة خدمات السيرفر. يُرجى التأني و الحِكمة عند اختيارها.",
|
||||
"previous": "السابق",
|
||||
"protocol": "Protocol",
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
"home": "Inici",
|
||||
"hook_adminjs_group_configuration": "Configuració del sistema",
|
||||
"hook_conf_cron": "Tasques automàtiques",
|
||||
"hook_conf_ldap": "Base de dades LDAP",
|
||||
"hook_conf_ldap": "Base de dades d’usuaris",
|
||||
"hook_conf_nginx": "NGINX",
|
||||
"hook_conf_ssh": "SSH",
|
||||
"hook_conf_ssowat": "SSOwat",
|
||||
|
@ -129,7 +129,7 @@
|
|||
"postinstall_domain": "Aquest és el primer nom de domini vinculat al vostre servidor YunoHost, però també el que utilitzaran els usuaris del servidor per accedir al portal d'autenticació. Així doncs serà visible per tothom, escolliu-lo amb cura.",
|
||||
"postinstall_intro_1": "Felicitats! YunoHost s'ha instal·lat correctament.",
|
||||
"postinstall_intro_2": "Falten dos passos més per activar els serveis del seu servidor.",
|
||||
"postinstall_intro_3": "Pot obtenir més informació visitant <a href='//yunohost.org/postinstall' target='_blank'>la pàgina de documentació</a>",
|
||||
"postinstall_intro_3": "Pot obtenir més informació visitant <a href='//yunohost.org/en/install/hardware:vps_debian#fa-cog-proceed-with-the-initial-configuration' target='_blank'>la pàgina de documentació</a>",
|
||||
"postinstall_password": "Aquesta contrasenya serà utilitzada per administrar tot en el servidor. Prengui el temps d'escollir una bona contrasenya.",
|
||||
"previous": "Precedent",
|
||||
"protocol": "Protocol",
|
||||
|
@ -250,7 +250,7 @@
|
|||
"app_state_inprogress_explanation": "El desenvolupador d'aquesta aplicació ha dit que encara no està preparada per fer-ne un ús en producció. SIGUEU PRUDENTS!",
|
||||
"app_state_notworking_explanation": "El desenvolupador d'aquesta aplicació l'ha marcat com que «no funciona». TRENCARÀ EL SISTEMA!",
|
||||
"app_state_highquality": "alta qualitat",
|
||||
"app_state_highquality_explanation": "Aquesta aplicació està ben integrada amb YunoHost. Ha estat (i és!) revisada per l'equip d'aplicacions de YunoHost. És segura i serà mantinguda a llarg termini.",
|
||||
"app_state_highquality_explanation": "Aquesta aplicació està ben integrada amb YunoHost des de fa almenys un any.",
|
||||
"app_state_working_explanation": "El desenvolupador d'aquesta aplicació l'ha marcat com «funcionant». Això vol dir que hauria de ser funcional (c.f. nivells d'aplicació) però no té perquè haver estat comprovada, pot encara tenir problemes o no estar completament integrada amb YunoHost.",
|
||||
"license": "Llicència",
|
||||
"only_highquality_apps": "Només aplicacions d'alta qualitat",
|
||||
|
@ -329,13 +329,15 @@
|
|||
"notInUsers": "L'usuari «{value}» ja existeix.",
|
||||
"minValue": "El valor ha de ser un nombre superior o igual a {min}.",
|
||||
"name": "Els noms no poden incloure caràcters especials ha excepció de <code>,.'-</code>",
|
||||
"githubLink": "L'URL ha de ser un enllaç vàlid a un repositori Github",
|
||||
"githubLink": "L'URL ha de ser un enllaç vàlid a un repositori GitHub",
|
||||
"email": "Correu electrònic no vàlid: ha de ser caràcters alfanumèrics i <code>_.</code> exclusivament (per exemple someone@example.com, s0me-1@example.com)",
|
||||
"dynDomain": "Nom de domini no vàlid: Ha de contenir caràcters alfanumèrics en minúscules i guionets exclusivament",
|
||||
"domain": "Nom de domini no vàlid: Ha de contenir caràcters alfanumèrics en minúscules, punts i guionets exclusivament",
|
||||
"between": "El valor ha d'estar entre {min} i {max}.",
|
||||
"alphalownum_": "Només pot contenir caràcters alfanumèrics en minúscules i la barra baixa.",
|
||||
"alpha": "Només pot contenir caràcters alfanumèrics."
|
||||
"alpha": "Només pot contenir caràcters alfanumèrics.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}"
|
||||
},
|
||||
"footer": {
|
||||
"donate": "Fer una donació",
|
||||
|
|
|
@ -109,14 +109,16 @@
|
|||
"notInUsers": "Uživatel '{value}' již existuje.",
|
||||
"minValue": "Hodnota musí být číslo rovno nebo vyšší než {min}.",
|
||||
"name": "Jména nemohou obsahovat speciální znaky mimo <code> ,.'-</code>",
|
||||
"githubLink": "Url musí být validní odkaz na Github repositář",
|
||||
"githubLink": "URL musí být validní odkaz na GitHub repositář",
|
||||
"emailForward": "Nesprávný tvar email pro přesměrování: může obsahovat pouze alfanumerické znaky a <code>_.-+</code> (např. someone+tag@example.com, s0me-1+tag@example.com)",
|
||||
"email": "Nesprávný tvar emailu: může obsahovat alfanumerické znaky a <code>_.-</code> (např. someone@example.com, s0me-1@example.com)",
|
||||
"dynDomain": "Nesprávný tvar doménového jména: může obsahovat pouze malé alfanumerické znaky a pomlčku",
|
||||
"domain": "Nesprávný tvar doménového jména: musí obsahovat pouze malé alfanumerické znaky, tečku a pomlčku",
|
||||
"between": "Hodnota musí být mezi {min} a {max}.",
|
||||
"alphalownum_": "Hodnota musí obsahovat pouze alfanumerické znaky a podtržítko.",
|
||||
"alpha": "Hodnota musí obsahovat pouze čísla."
|
||||
"alpha": "Hodnota musí obsahovat pouze čísla.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}"
|
||||
},
|
||||
"footer": {
|
||||
"donate": "Darovat",
|
||||
|
@ -332,7 +334,7 @@
|
|||
"perform_action": "Vykonat akci '{action}' aplikace '{name}'",
|
||||
"set_default": "Přesměrovat '{domain}' doménový kořen na '{name}'",
|
||||
"install": "Instalovat aplikaci '{name}'",
|
||||
"change_url": "Změnit přístupové url '{name}'",
|
||||
"change_url": "Změnit přístupové URL '{name}'",
|
||||
"change_label": "Změnit popis z '{prevName}' na '{nextName}'"
|
||||
},
|
||||
"adminpw": "Změnit administrační heslo"
|
||||
|
|
|
@ -304,7 +304,7 @@
|
|||
"operation_failed_explanation": "Die Operation ist fehlgeschlagen! Das tut uns leid :( Sie können Sich <a href='https://yunohost.org/help'>hier Hilfe holen</a>. Bitte stellen Sie *die ganzen Logs* der Operation bereit, damit Ihnen besser geholfen werden kann. Dies tun Sie, indem Sie auf den grünen 'mit Yunopaste teilen' Knopf drücken. Wenn Sie die Logs teilen, wird Yunohost automatisch versuchen Ihre privaten Daten wie Domainnamen und IP's zu anonymisieren.",
|
||||
"diagnosis_explanation": "Die Diagnose Funktion wird versuchen, gängige Probleme in verschiedenen Teilen deines Servers zu finden, damit alles reibungslos läuft. Die Diagnose wird auch automatisch zweimal täglich ausgeführt, falls Fehler gefunden werden, bekommt der Administrator ein E-Mail. Beachte, dass einige tests nicht relevant sind, wenn du einzelne Features (zum Beispiel XMPP) nicht benutzt oder du ein komplexes Setup hast. In diesem Fall und wenn du weisst was du tust ist es in Ordnung die dazugehoerigen Warnungen und Hinweise zu ignorieren.",
|
||||
"pending_migrations": "Es gibt einige ausstehende Migrationen, die darauf warten, ausgeführt zu werden. Bitte gehen Sie auf <a href='#/tools/migrations'>Werkzeuge > Migrationen</a> um diese auszuführen.",
|
||||
"tip_about_user_email": "Benutzer:innen werden mit einer verknüpften E-Mail-Adresse (und XMPP Account) erstellt im Format username@domain.tld. Zusätzliche E-Mail-Aliasse and E-Mail-Weiterleitungen können später durch den/die Admin und Benutzer*in hinzugefügt werden.",
|
||||
"tip_about_user_email": "Benutzer:innen werden mit einer verknüpften E-Mail-Adresse (und XMPP Account) erstellt im Format username@domain.tld. Zusätzliche E-Mail-Aliasse and E-Mail-Weiterleitungen können später durch den/die Admin und Benutzer:in hinzugefügt werden.",
|
||||
"logs_suboperations": "Unter-Operationen",
|
||||
"api_errors_titles": {
|
||||
"APIBadRequestError": "Es ist ein Fehler in Yunohost aufgetreten",
|
||||
|
@ -409,14 +409,16 @@
|
|||
"notInUsers": "Der/die Benutzer:in '{value}' existiert bereits.",
|
||||
"minValue": "Der Wert muss eine Zahl sein, die gleich oder größer als {min} ist.",
|
||||
"name": "Namen dürfen keine Sonderzeichen außer <code>, .'- </code> enthalten",
|
||||
"githubLink": "Die URL muss ein gültiger Github-Link zu einem Repository sein",
|
||||
"githubLink": "Die URL muss ein gültiger GitHub-Link zu einem Repository sein",
|
||||
"emailForward": "Ungültige E-Mail-Weiterleitung: Sie darf nur aus alphanumerischen Zeichen und <code> _.-+</code> bestehen (z. B. jemand+tag@example.com, s0me-1+tag@example.com)",
|
||||
"email": "Ungültige E-Mail: Sie darf nur aus alphanumerischen Zeichen und <code> _.-</code> bestehen (z. B. jemand@example.com, s0me-1@example.com)",
|
||||
"dynDomain": "Ungültiger Domainname: Er darf nur aus alphanumerischen Kleinbuchstaben und Bindestrichen bestehen",
|
||||
"domain": "Ungültiger Domainname: Er darf nur aus alphanumerischen Kleinbuchstaben, Punkt- und Strichzeichen bestehen",
|
||||
"between": "Der Wert muss zwischen {min} und {max} liegen.",
|
||||
"alphalownum_": "Der Wert darf nur aus alphanumerischen Kleinbuchstaben und Unterstrichen bestehen.",
|
||||
"alpha": "Der Wert darf nur aus alphabetischen Zeichen bestehen."
|
||||
"alpha": "Der Wert darf nur aus alphabetischen Zeichen bestehen.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}"
|
||||
},
|
||||
"footer": {
|
||||
"donate": "Spenden",
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"administration_password": "Administration password",
|
||||
"all": "All",
|
||||
"api": {
|
||||
"partial_logs": "[...] (check in history for full logs)",
|
||||
"processing": "The server is processing the action...",
|
||||
"query_status": {
|
||||
"error": "Unsuccessful",
|
||||
|
@ -131,6 +132,25 @@
|
|||
"disable": "Disable",
|
||||
"disabled": "Disabled",
|
||||
"dns": "DNS",
|
||||
"domain": {
|
||||
"config": {
|
||||
"edit": "Edit domain configuration",
|
||||
"title": "Domain configuration"
|
||||
},
|
||||
"dns": {
|
||||
"auto_config": "Automatic DNS records configuration",
|
||||
"auto_config_ignored": "ignored, won't be changed by YunoHost unless you check the overwrite option",
|
||||
"auto_config_ok": "Automatic configuration seems to be OK!",
|
||||
"auto_config_zone": "Current DNS zone",
|
||||
"edit": "Edit DNS configuration",
|
||||
"info": "The automatic DNS records configuration is an experimental feature. <br>Consider saving your current DNS zone from your DNS registrar's interface before pushing records from here.",
|
||||
"manual_config": "Suggested DNS records for manual configuration",
|
||||
"push": "Push DNS records to registrar",
|
||||
"push_force": "Overwrite existing records",
|
||||
"push_force_confirm": "Are you sure you want to force push all suggested dns records? Be aware that it may overwrite manually or important default records set by you or your registrar.",
|
||||
"push_force_warning": "It looks like some DNS records that YunoHost would have set are already in the registrar configuration. You can use the overwrite option if you know what you are doing."
|
||||
}
|
||||
},
|
||||
"domain_add": "Add domain",
|
||||
"domain_add_dns_doc": "… and I have <a href='//yunohost.org/dns_config' target='_blank'>set my DNS correctly</a>.",
|
||||
"domain_add_dyndns_doc": "… and I want a dynamic DNS service.",
|
||||
|
@ -143,6 +163,9 @@
|
|||
"domain_delete_forbidden_desc": "You cannot remove '{domain}' since it's the default domain, you need to choose another domain (or <a href='#/domains/add'>add a new one</a>) and set it as the default domain to be able to remove this one.",
|
||||
"domain_dns_config": "DNS configuration",
|
||||
"domain_dns_longdesc": "View DNS configuration",
|
||||
"domain_dns_push_failed_to_authenticate": "Failed to authenticate on registrar's API. Most probably the <a href='#/domains/{domain}/config'>credentials</a> are incorrect? (Error: {error})",
|
||||
"domain_dns_push_managed_in_parent_domain": "The automatic DNS records feature is managed in the parent domain <a href='#/domains/{parent_domain}/dns'>{parent_domain}</a>.",
|
||||
"domain_dns_push_not_applicable": "The automatic DNS records feature is not applicable to domain {domain},<br> You should manually configure your DNS records following the <a href='https://yunohost.org/dns'>documentation</a> and the suggested configuration below.",
|
||||
"domain_name": "Domain name",
|
||||
"domain_visit": "Visit",
|
||||
"domain_visit_url": "Visit {url}",
|
||||
|
@ -172,14 +195,18 @@
|
|||
"dynDomain": "Invalid domain name: Must be lower-case alphanumeric and dash characters only",
|
||||
"email": "Invalid email: must be alphanumeric and <code>_.-</code> characters only (e.g. someone@example.com, s0me-1@example.com)",
|
||||
"emailForward": "Invalid email forward: must be alphanumeric and <code>_.-+</code> characters only (e.g. someone+tag@example.com, s0me-1+tag@example.com)",
|
||||
"githubLink": "Url must be a valid Github link to a repository",
|
||||
"name": "Names may not include special characters except <code> ,.'-</code>",
|
||||
"appRepoUrl": "YunoHost app repository URLs are expected to look like https://domain.tld/path/to/repo_ynh",
|
||||
"name": "Names may not includes special characters except <code> ,.'-</code>",
|
||||
"minValue": "Value must be a number equal or greater than {min}.",
|
||||
"maxValue": "Value must be a number equal or lesser than {max}.",
|
||||
"notInUsers": "The user '{value}' already exists.",
|
||||
"number": "Value must be a number.",
|
||||
"passwordLenght": "Password must be at least 8 characters long.",
|
||||
"passwordMatch": "Passwords don't match.",
|
||||
"required": "Field is required."
|
||||
"required": "Field is required.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}",
|
||||
"invalid_form": "The form contains some errors."
|
||||
},
|
||||
"form_input_example": "Example: {example}",
|
||||
"from_to": "from {0} to {1}",
|
||||
|
@ -247,10 +274,10 @@
|
|||
"services": "no services | service | {c} services",
|
||||
"users": "no users | user | {c} users"
|
||||
},
|
||||
"items_verbose_count": "There are {items}.",
|
||||
"items_verbose_items_left": "There are {items} left.",
|
||||
"items_verbose_count": "There are {items}. | There is 1 {items}. | There are {items}.",
|
||||
"items_verbose_items_left": "There are {items} left. | There is 1 {items} left. | There are {items} left.",
|
||||
"label": "Label",
|
||||
"label_for_manifestname": "Label for {name}",
|
||||
"label_for_manifestname": "Label for {name} (name displayed in the user portal)",
|
||||
"last_ran": "Last time ran:",
|
||||
"license": "License",
|
||||
"local_archives": "Local archives",
|
||||
|
@ -293,7 +320,8 @@
|
|||
"firstname": "John",
|
||||
"lastname": "Doe",
|
||||
"groupname": "My group name",
|
||||
"domain": "my-domain.com"
|
||||
"domain": "my-domain.com",
|
||||
"file": "Browse a file or drag and drop it"
|
||||
},
|
||||
"logs": "Logs",
|
||||
"logs_suboperations": "Sub-operations",
|
||||
|
@ -319,7 +347,8 @@
|
|||
"port": "Port",
|
||||
"ports": "Ports",
|
||||
"postinstall": {
|
||||
"force": "Force the post-install"
|
||||
"force": "Force the post-install",
|
||||
"title": "Postinstall"
|
||||
},
|
||||
"postinstall_domain": "This is the first domain name linked to your YunoHost server, but also the one which will be used by your server's users to access the authentication portal. Accordingly, it will be visible by everyone, so choose it carefully.",
|
||||
"postinstall_intro_1": "Congratulations! YunoHost has been successfully installed.",
|
||||
|
@ -367,9 +396,11 @@
|
|||
"delete": "Delete domain '{name}'",
|
||||
"install_LE": "Install certificate for '{name}'",
|
||||
"manual_renew_LE": "Renew certificate for '{name}'",
|
||||
"push_dns_changes": "Push DNS records to registrar for '{name}'",
|
||||
"regen_selfsigned": "Renew self-signed certificate for '{name}'",
|
||||
"revert_to_selfsigned": "Revert to self-signed certificate for '{name}'",
|
||||
"set_default": "Set '{name}' as default domain"
|
||||
"set_default": "Set '{name}' as default domain",
|
||||
"update_config": "Update '{name}' configuration"
|
||||
},
|
||||
"firewall": {
|
||||
"ports": "{action} port {port} ({protocol}, {connection})",
|
||||
|
@ -415,7 +446,7 @@
|
|||
"save": "Save",
|
||||
"search": {
|
||||
"for": "Search for {items}...",
|
||||
"not_found": "There are {items} matching your criteria."
|
||||
"not_found": "There are {items} matching your criteria. | There is 1 {items} matching your criteria. | There are {items} matching your criteria."
|
||||
},
|
||||
"select_all": "Select all",
|
||||
"select_none": "Select none",
|
||||
|
@ -436,6 +467,7 @@
|
|||
"system_upgrade_all_applications_btn": "Upgrade all applications",
|
||||
"system_upgrade_all_packages_btn": "Upgrade all packages",
|
||||
"tcp": "TCP",
|
||||
"text_selection_is_disabled": "Text selection is disabled. If you want to share this log, please share the *full* log with the 'Share with Yunopaste' button.<br/><small>Or if you really really want to select text, press these keys: ↓↓↑↑.</small>",
|
||||
"tip_about_user_email": "Users are created with an associated email address (and XMPP account) with the format username@domain.tld. Additional email aliases and email forwards can later be added by the admin and the user.",
|
||||
"tools": "Tools",
|
||||
"tools_adminpw": "Change administration password",
|
||||
|
@ -489,14 +521,26 @@
|
|||
"users": "Users",
|
||||
"users_new": "New user",
|
||||
"users_no": "No users.",
|
||||
"users_import": "Import users",
|
||||
"users_export": "Export users",
|
||||
"users_import_csv_file": "CSV File",
|
||||
"users_import_update": "Update existing users",
|
||||
"users_import_delete": "Delete non listed users",
|
||||
"users_import_csv_file_desc": "The CSV file should be in UTF-8 and with columns username, password, groups, email and quota. For an example import CSV file, you can <a href='/yunohost/api/users/export' target='_BLANK'>export your users in CSV file</a> and change the file.",
|
||||
"users_import_update_desc": "If checked, all existing users contained in the CSV file will be updated with the new value",
|
||||
"users_import_delete_desc": "If checked, all existing users that are not in the CSV file will be deleted (and purged).",
|
||||
"users_import_confirm_destructive": "Are you sure you want to delete users that are not present in this file?",
|
||||
"users_import_delete_others": "Delete unlisted users",
|
||||
"version": "Version",
|
||||
"warnings": "{count} warnings",
|
||||
"words": {
|
||||
"browse": "Browse",
|
||||
"collapse": "Collapse",
|
||||
"default": "Default"
|
||||
},
|
||||
"wrong_password": "Wrong password",
|
||||
"yes": "Yes",
|
||||
"yunohost_admin": "YunoHost Admin",
|
||||
"certificate_alert_not_valid": "CRITICAL: Current certificate is not valid! HTTPS won't work at all!",
|
||||
"certificate_alert_selfsigned": "WARNING: Current certificate is self-signed. Browsers will display a spooky warning to new visitors!",
|
||||
"certificate_alert_letsencrypt_about_to_expire": "Current certificate is about to expire. It should soon be renewed automatically.",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"app_state_inprogress_explanation": "Ĉi tiu subtenanto de ĉi tiu app deklaris, ke ĉi tiu aplikaĵo ankoraŭ ne estas preta por produktado. ESPERANTU!",
|
||||
"app_state_notworking_explanation": "Ĉi tiu prizorganto de ĉi tiu app deklaris ĝin \"ne funkcianta\". Ĝi BRAKOS vian sistemon!",
|
||||
"app_state_highquality": "alta kvalito",
|
||||
"app_state_highquality_explanation": "Ĉi tiu app estas bone integrita kun YunoHost. Ĝi estis (kaj estas!) Reviziita de la teamo de YunoHost. Ĝi povas atendi esti sekura kaj konservi longtempe.",
|
||||
"app_state_highquality_explanation": "Ĉi tiu programo estas bone integrita kun YunoHost ekde almenaŭ unu jaro.",
|
||||
"app_state_working_explanation": "La prizorganto de ĉi tiu app deklaris ĝin \"laboranta\". Ĝi signifas, ke ĝi devas esti funkcia (c.f. aplika nivelo) sed ne nepre estas reviziita de pereloj, tamen ĝi eble enhavas problemojn aŭ ne plene integriĝas kun YunoHost.",
|
||||
"good_practices_about_user_password": "Vi nun estas por difini novan uzantan pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ uzi diversajn specojn de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).",
|
||||
"from_to": "de {0} al {1}",
|
||||
|
@ -100,7 +100,7 @@
|
|||
"experimental_warning": "Averto: ĉi tiu funkcio estas eksperimenta kaj ne konsideras stabila, vi ne devas uzi ĝin krom se vi scias kion vi faras.",
|
||||
"hook_conf_cron": "Aŭtomataj taskoj",
|
||||
"domain_visit": "Vizitu",
|
||||
"hook_conf_ldap": "Datumbazo LDAP",
|
||||
"hook_conf_ldap": "Uzanta datumbazo",
|
||||
"install": "Instalu",
|
||||
"confirm_install_domain_root": "Vi ne povos instali iun alian app en {domain}. Daŭrigi ?",
|
||||
"confirm_app_default": "Ĉu vi certas, ke vi volas igi ĉi tiun programon defaŭlta?",
|
||||
|
@ -193,7 +193,7 @@
|
|||
"logs_service": "Servoj ŝtipoj",
|
||||
"tools_rebooting": "Via servilo rekomencas. Por reveni al la retadministra interfaco, vi devas atendi, ke via servilo funkcios. Vi povas kontroli tion per refreŝigado de ĉi tiu paĝo (F5).",
|
||||
"tools_adminpw": "Ŝanĝu administran pasvorton",
|
||||
"multi_instance": "Multaj ekzemploj",
|
||||
"multi_instance": "Povas esti instalita plurajn fojojn",
|
||||
"revert_to_selfsigned_cert_message": "Se vi vere volas, vi povas reinstali mem-subskribitan atestilon. (Ne rekomendita)",
|
||||
"tools_adminpw_current_placeholder": "Enigu vian nunan pasvorton",
|
||||
"user_mailbox_quota": "Poŝta kesto",
|
||||
|
@ -229,7 +229,7 @@
|
|||
"size": "Grandeco",
|
||||
"confirm_cert_manual_renew_LE": "Ĉu vi certas, ke vi volas permane renovigi la atestilon Lasu Ĉifri por ĉi tiu regado nun?",
|
||||
"logs": "Registroj",
|
||||
"postinstall_intro_3": "Vi povas akiri pliajn informojn vizitante la <a href='//yunohost.org/postinstall' target='_blank'> taŭgan dokumentan paĝon </a>",
|
||||
"postinstall_intro_3": "Vi povas akiri pliajn informojn vizitante la <a href='//yunohost.org/en/install/hardware:vps_debian#fa-cog-proceed-with-the-initial-configuration' target='_blank'>taŭgan dokumentan paĝon</a>",
|
||||
"tools_shuttingdown": "Via servilo elŝaltas. Tiel longe kiel via servilo malŝaltas, vi ne povos uzi la retan administradon.",
|
||||
"logs_app": "Aplikoj ŝtipoj",
|
||||
"running": "Kurado",
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
"manage_apps": "Gestión aplicaciones",
|
||||
"manage_domains": "Gestión de dominios",
|
||||
"manage_users": "Gestión de usuarios",
|
||||
"multi_instance": "Instancias múltiples",
|
||||
"multi_instance": "Puede instalarse varias veces",
|
||||
"myserver": "miservidor",
|
||||
"next": "Próximo",
|
||||
"no": "No",
|
||||
|
@ -106,7 +106,7 @@
|
|||
"postinstall_domain": "Este es el primer nombre de dominio vinculado a su servidor YunoHost, pero también el que será utilizado por los usuarios para acceder al portal de identificación. Será visible por todos, elegir con cuidado.",
|
||||
"postinstall_intro_1": "¡Felicitaciones! YunoHost se ha instalado correctamente.",
|
||||
"postinstall_intro_2": "Todavía faltan dos pasos más para activar los servicios de su servidor.",
|
||||
"postinstall_intro_3": "Más información disponible en <a href='//yunohost.org/postinstall' target='_blank'></a>",
|
||||
"postinstall_intro_3": "Más información disponible en <a href='//yunohost.org/en/install/hardware:vps_debian#fa-cog-proceed-with-the-initial-configuration' target='_blank'>página de documentación apropiada</a>",
|
||||
"postinstall_password": "Esta contraseña se utiliza para gestionar todo su servidor. Tómese su tiempo para elegirla sabiamente.",
|
||||
"previous": "Anterior",
|
||||
"protocol": "Protocolo",
|
||||
|
@ -348,7 +348,9 @@
|
|||
"domain": "Nombre de dominio inválido: debe ser alfanumérico en minúsculas, solo caracteres de puntos y guiones",
|
||||
"between": "El valor debe estar entre {min} y {max}.",
|
||||
"alphalownum_": "El valor debe ser alfanumérico en minúsculas y solo caracteres subrayados.",
|
||||
"alpha": "El valor debe ser solo caracteres alfabéticos."
|
||||
"alpha": "El valor debe ser solo caracteres alfabéticos.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}"
|
||||
},
|
||||
"footer": {
|
||||
"donate": "Donar",
|
||||
|
@ -376,5 +378,14 @@
|
|||
"app_actions_label": "Realizar acciones",
|
||||
"app_actions": "Acciones",
|
||||
"api_waiting": "Esperando la respuesta del servidor...",
|
||||
"api_not_found": "Parece que el administrador de la web ha intentado consultar algo que no existe."
|
||||
"api_not_found": "Parece que el administrador de la web ha intentado consultar algo que no existe.",
|
||||
"hook_conf_manually_modified_files": "Configuraciones modificadas manualmente",
|
||||
"hook_data_xmpp": "Datos XMPP",
|
||||
"items_verbose_count": "Hay {items}.",
|
||||
"placeholder": {
|
||||
"lastname": "Doe",
|
||||
"username": "johndoe",
|
||||
"firstname": "John"
|
||||
},
|
||||
"hook_conf_ynh_settings": "Configuraciones de YunoHost"
|
||||
}
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
"api_error": {
|
||||
"error_message": "پیغام خطا :",
|
||||
"view_error": "مشاهده خطا",
|
||||
"sorry": "از این بابت واقعاً متاسفم.",
|
||||
"server_said": "هنگام پردازش و اجرای دستورات ، سرور مذکور:",
|
||||
"sorry": "واقعاً از این بابت متاسفم.",
|
||||
"server_said": "هنگام پردازش و اجرای دستورات ، سمت سرور:",
|
||||
"info": "اطلاعات زیر ممکن است برای شخصی که به شما کمک می کند مفید باشد :",
|
||||
"help": "برای دریافت کمک شما بایدببینید<a href=\"https://forum.yunohost.org/\">انجمن تخصصی</a> یا <a href=\"https://chat.yunohost.org/\">گفتگوی آنلاین</a>و برای برطرف کردن مشکلات و یا گزارش اشکال و خطا مراجعه کنید به آدرس<a href=\"https://github.com/YunoHost/issues\"> ردگیری باگ و خطا</a>."
|
||||
},
|
||||
"api": {
|
||||
"query_status": {
|
||||
"warning": "با خطا یا هشدار با موفقیت انجام شد",
|
||||
"success": "با موفقیت انجام شد",
|
||||
"success": "با موفقیّت انجام شد",
|
||||
"pending": "در حال پیش رفت",
|
||||
"error": "ناموفّق"
|
||||
},
|
||||
|
@ -33,16 +33,16 @@
|
|||
"api_not_found": "به نظر می رسد مدیر وب سعی کرده است از چیزی که وجود ندارد پرس و جو کند.",
|
||||
"all_apps": "همه اپلیکیشن ها",
|
||||
"api_errors_titles": {
|
||||
"APIConnexionError": "سیستم با خطای اتصال مواجه شده",
|
||||
"APINotRespondingError": "رابط کاربری سیستم پاسخ نمی دهد",
|
||||
"APINotFoundError": "رابط کاربری سیستم نتوانست مسیری را پیدا کند",
|
||||
"APIInternalError": "سیستم با خطای داخلی مواجه شده",
|
||||
"APIBadRequestError": "سیستم با خطا مواجه شده",
|
||||
"APIError": "سیستم با خطای غیر منتظره ای روبرو شده"
|
||||
"APIConnexionError": "سیستم YunoHost با خطای اتصال مواجه شده",
|
||||
"APINotRespondingError": "رابط کاربری سیستم YunoHost پاسخ نمی دهد",
|
||||
"APINotFoundError": "رابط کاربری سیستم YunoHost نتوانست مسیری را پیدا کند",
|
||||
"APIInternalError": "سیستم YunoHost با خطای داخلی مواجه شده",
|
||||
"APIBadRequestError": "سیستم YunoHost با خطا مواجه شده",
|
||||
"APIError": "سیستم YunoHost با خطای غیر منتظره ای روبرو شده"
|
||||
},
|
||||
"form_errors": {
|
||||
"name": "اسامی نبایدشامل کاراکتر های خاص باشند! بغیر از:<code> ,.'-</code>",
|
||||
"githubLink": "آدرس اینترنتی باید یک پیوند معتبر Github به مخزن باشد",
|
||||
"githubLink": "آدرس اینترنتی باید یک پیوند معتبر GitHub به مخزن باشد",
|
||||
"emailForward": "ایمیل فوروارد نامعتبر: باید فقط حروف الفبا و عدد و کاراکترهای <code>_.-</code> باشد (بطور مثال : someone@example.com, s0me-1@example.com)",
|
||||
"email": "ایمیل نامعتبر: باید فقط حروف الفبا و عدد و کاراکترهای <code>_.-</code> باشد (بطور مثال : someone@example.com, s0me-1@example.com)",
|
||||
"dynDomain": "نام دامنه نامعتبر است: فقط باید حروف کوچک وکاراکتر خط تیره باشد",
|
||||
|
@ -98,7 +98,7 @@
|
|||
"diagnosis_first_run": "ویژگی عیب یابی سعی می کند مسائل رایج در جنبه های مختلف سرور شما را شناسایی کند تا مطمئن شوید همه چیز بدون مشکل اجرا می شود. لطفاً اگر بلافاصله پس از راه اندازی سرور خطاهای زیادی را مشاهده کردید نگران نشوید : این دقیقاً به منظور کمک به شناسایی و تشخیص مشکلات و راهنمایی شما برای رفع آنها است. همچنین عیب یابی سیستم ، دو بار در روز بطور خودکار انجام می شود و در صورت وجود هرگونه مشکلی، ایمیل و ابلاغ به مدیر سیستم ارسال می شود.",
|
||||
"diagnosis_experimental_disclaimer": "توجه داشته باشید که ویژگی عیب یابی سیستم هنوز آزمایشی است و در حال اصلاح است و ممکن است کاملاً قابل اعتماد نباشد.",
|
||||
"diagnosis": "عیب یابی",
|
||||
"domain_dns_conf_is_just_a_recommendation": "این صفحه پیکربندی * توصیه شده * را به شما نشان می دهد. برای شما سیستم نام دامنه DNS را پیکربندی *نمی کند*. پیکربندی منطقه سیستم نام دامنه مطابق این توصیه در ثبت کننده سیستم نام دامنه خود، بعهده شماست.",
|
||||
"domain_dns_conf_is_just_a_recommendation": "این صفحه پیکربندی * توصیه شده * را به شما نشان می دهد. برای شما سرور نام دامنه را پیکربندی *نمی کند*. این بعهده شماست که مطابق این پیکره بندی توصیه شده ، منطقه سرور نام دامنه خود را در ثبت کننده نام دامنه خود پیکربندی کنید.",
|
||||
"details": "جزئیات",
|
||||
"description": "شرح",
|
||||
"delete": "حذف",
|
||||
|
@ -166,8 +166,8 @@
|
|||
"app_choose_category": "یک دسته را انتخاب کنید",
|
||||
"app_actions_label": "اجراءکردن اقدامات",
|
||||
"app_actions": "اقدامات",
|
||||
"api_waiting": "منتظر پاسخ سِرورها باشید...",
|
||||
"api_not_responding": "رابط کاربری سیستم پاسخ نمی دهد، شاید 'yunohost-api' خاموش و یا راه اندازی مجدد شده؟",
|
||||
"api_waiting": "در انتظار پاسخ سِرورها...",
|
||||
"api_not_responding": "رابط کاربری سیستم YunoHost پاسخ نمی دهد، شاید 'yunohost-api' خاموش و یا راه اندازی مجدد شده؟",
|
||||
"purge_user_data_warning": "پاکسازی داده های کاربر برگشت پذیر نیست. مطمئن باشید که می دانید چه می کنید!",
|
||||
"purge_user_data_checkbox": "داده های {name} پاک شود؟ (با این کار محتوای فهرست های خانه و ایمیل آن حذف می شود.)",
|
||||
"revert_to_selfsigned_cert": "بازگشت به گواهی خود امضا شده",
|
||||
|
@ -177,11 +177,11 @@
|
|||
"manually_renew_letsencrypt": "اکنون به صورت دستی تمدید کنید",
|
||||
"manually_renew_letsencrypt_message": "گواهینامه بطور خودکار تمدید می شود پس از طی 15 روز اعتبار. در صورت تمایل می توانید آن را به صورت دستی تمدید کنید. (توصیه نمیشود).",
|
||||
"install_letsencrypt_cert": "نصب گواهینامه اجازه رمزنگاری",
|
||||
"domain_not_eligible_for_ACME": "به نظر می رسد این دامنه برای گواهی اجازه رمزنگاری آماده نیست. لطفا پیکربندی DNS و قابلیت دسترسی سرور HTTP خود را بررسی کنید. بخش 'DNS records' و بخش 'Web' در <a href='#/diagnosis'>صفحه تشخیص عیب یابی</a> می تواند به شما کمک کند بفهمید چه چیزی اشتباه تنظیم شده است.",
|
||||
"domain_not_eligible_for_ACME": "به نظر می رسد این دامنه برای گواهی اجازه رمزنگاری آماده نیست. لطفا پیکربندی DNS و قابلیت دسترسی سرور HTTP خود را بررسی کنید. بخش 'DNS records' و بخش 'Web' در <a href='#/diagnosis'>صفحه معاینه و عیب یابی</a> می تواند به شما کمک کند بفهمید چه چیزی اشتباه تنظیم شده است.",
|
||||
"domain_is_eligible_for_ACME": "به نظر می رسد این دامنه برای نصب گواهی اجازه رمزگذاری ، به درستی پیکربندی شده است!",
|
||||
"validity": "اعتبار",
|
||||
"certificate_authority": "مرجع صدور گواهینامه",
|
||||
"certificate_status": "وضعیت گواهینامه",
|
||||
"certificate_status": "وضعیّت گواهینامه",
|
||||
"certificate": "گواهی نامه",
|
||||
"confirm_cert_revert_to_selfsigned": "آیا مطمئن هستید که می خواهید این دامنه را به گواهی خود امضا برگردانید؟",
|
||||
"confirm_cert_manual_renew_LE": "آیا مطمئن هستید که می خواهید گواهی اجازه رمزگذاری را برای این دامنه به صورت دستی تمدید کنید؟",
|
||||
|
@ -440,7 +440,7 @@
|
|||
"mailbox_quota_example": "گنجایش یک سی-دی 700 مگابایت، و یک دی-وی-دی 4700 مگابایت میباشد",
|
||||
"mailbox_quota_description": "محدودیت اندازه ذخیره سازی برای محتوای ایمیل تعیین کنید. <br> برای غیرفعال کردن روی 0 تنظیم کنید.",
|
||||
"logout": "خروج",
|
||||
"login": "ورود",
|
||||
"login": "ورود به سیستم",
|
||||
"local_archives": "بایگانی های محلی",
|
||||
"license": "لایسنس، مجوز",
|
||||
"last_ran": "زمان آخرین اجرا:",
|
||||
|
@ -461,7 +461,7 @@
|
|||
},
|
||||
"issues": "{count} مسائل",
|
||||
"installed": "نصب شد",
|
||||
"installation_complete": "نصب کامل",
|
||||
"installation_complete": "عملیّات نصب کامل شد",
|
||||
"install_time": "زمان نصب",
|
||||
"install_name": "نصب {id}",
|
||||
"install": "نصب",
|
||||
|
@ -507,8 +507,8 @@
|
|||
"group_all_users": "تمام کاربران",
|
||||
"group_name": "نام گروه",
|
||||
"group": "گروه",
|
||||
"good_practices_about_user_password": "اکنون در حال تعریف رمز عبور کاربر جدید هستید. گذرواژه باید حداقل 8 کاراکتر باشد - استفاده از رمز عبور طولانی تمرین خوبی است (یعنی عبارت عبور) و/یا استفاده وترکیب انواع مختلف کاراکترها (حروف بزرگ ، کوچک ، عدد و کاراکترهای خاص).",
|
||||
"good_practices_about_admin_password": "اکنون در حال تعریف رمز عبور جدید مدیر سیستم هستید. گذرواژه باید حداقل 8 کاراکتر باشد - هرچند استفاده از رمز عبور طولانی تمرین خوبی است (یعنی عبارت عبور) و/یا استفاده وترکیب انواع مختلف کاراکترها (حروف بزرگ ، کوچک ، عدد و کاراکترهای خاص).",
|
||||
"good_practices_about_user_password": "اکنون در حال تعریف رمز عبور کاربر جدید هستید. گذرواژه باید حداقل 8 کاراکتر طول داشته باشد - هرچند استفاده از رمز عبور طولانی تمرین خوبی است (یعنی عبارت عبور) و/یا با استفاده و ترکیب انواع مختلف کاراکترها (حروف بزرگ ، کوچک ، عدد و کاراکترهای خاص).",
|
||||
"good_practices_about_admin_password": "اکنون در حال تعریف رمز عبور جدید مدیر سیستم هستید. گذرواژه باید حداقل 8 کاراکتر طول داشته باشد - هرچند استفاده از رمز عبور طولانی تمرین خوبی است (یعنی عبارت عبور) و/یا با استفاده و ترکیب انواع مختلف کاراکترها (حروف بزرگ ، کوچک ، عدد و کاراکترهای خاص).",
|
||||
"go_back": "برگشتن به عقب",
|
||||
"from_to": "از {0} تا {1}",
|
||||
"form_input_example": "مثال : {example}",
|
||||
|
|
|
@ -282,7 +282,8 @@
|
|||
"warnings": "{count} avertissements",
|
||||
"words": {
|
||||
"default": "Défaut",
|
||||
"collapse": "Replier"
|
||||
"collapse": "Replier",
|
||||
"browse": "Parcourir"
|
||||
},
|
||||
"configuration": "Configuration",
|
||||
"since": "depuis",
|
||||
|
@ -324,7 +325,7 @@
|
|||
},
|
||||
"tools_power_up": "Votre serveur semble être accessible, vous pouvez maintenant essayer de vous connecter.",
|
||||
"search": {
|
||||
"not_found": "Il y a des {items} qui correspondent à vos critères.",
|
||||
"not_found": "Il y a des {items} qui correspondent à vos critères. | Il y a un {items} qui correspond à vos critères. | Il y a des {items} qui correspondent à vos critères.",
|
||||
"for": "Rechercher {items}..."
|
||||
},
|
||||
"readme": "Lisez-moi",
|
||||
|
@ -335,13 +336,14 @@
|
|||
"groupname": "Le nom de mon groupe",
|
||||
"lastname": "Dupont",
|
||||
"firstname": "Jean",
|
||||
"username": "jeandupont"
|
||||
"username": "jeandupont",
|
||||
"file": "Parcourir un fichier ou le faire glisser et déposer"
|
||||
},
|
||||
"perform": "Exécuter",
|
||||
"migrations_disclaimer_not_checked": "Cette migration nécessite que vous preniez connaissance de sa décharge de responsabilité avant de l'exécuter.",
|
||||
"migrations_disclaimer_check_message": "J'ai lu et compris cette décharge de responsabilité",
|
||||
"mailbox_quota_example": "700 M correspond à un CD, 4 700 M correspond à un DVD",
|
||||
"items_verbose_count": "Il y a des {items}.",
|
||||
"items_verbose_count": "Il y a des {items}. | Il y a un {items}. | Il y a des {items}.",
|
||||
"items": {
|
||||
"users": "aucun utilisateur | utilisateur | {c} utilisateurs",
|
||||
"services": "aucun service | service | {c} services",
|
||||
|
@ -372,14 +374,19 @@
|
|||
"notInUsers": "L'utilisateur '{value}' existe déjà.",
|
||||
"minValue": "La valeur doit être un nombre égal ou supérieur à {min}.",
|
||||
"name": "Les noms ne peuvent pas comporter de caractères spéciaux, sauf <code> ,.'-</code>",
|
||||
"githubLink": "L'URL doit être un lien Github valide vers un dépôt",
|
||||
"appRepoUrl": "Les URLs de dêpôt d'app YunoHost doivent ressembler à https://domain.tld/path/to/repo_ynh",
|
||||
"emailForward": "Adresse de transfert de courrier électronique invalide : elle doit être composée de caractères alphanumérique et de <code>_.-+</code> seulement (par exemple, someone+tag@example.com, s0me-1+tag@example.com)",
|
||||
"email": "Adresse de courriel invalide : elle doit être composée de caractères alphanumérique et des caractères <code>_.-</code> seulement (par exemple someone@example.com, s0me-1@example.com)",
|
||||
"dynDomain": "Nom de domaine invalide : Il doit être composé de minuscules alphanumériques et de tirets uniquement",
|
||||
"domain": "Nom de domaine invalide : Il doit être composé de minuscules alphanumériques, de points et de tirets uniquement",
|
||||
"between": "La valeur doit être comprise entre {min} et {max}.",
|
||||
"alpha": "La chaîne de caractères ne doit contenir que des lettres.",
|
||||
"alphalownum_": "La chaîne de caractères doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore)."
|
||||
"alphalownum_": "La chaîne de caractères doit être composé uniquement de caractères alphanumériques minuscules et de tirets bas (aussi appelé tiret du 8 ou underscore).",
|
||||
"fileMediaTypeMatch": "Type de fichier non valide : doit être <code>{arg}</code>.",
|
||||
"maxValue": "La valeur doit être un nombre égal ou inférieur à {max}.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}",
|
||||
"invalid_form": "Le formulaire contient des erreurs."
|
||||
},
|
||||
"footer": {
|
||||
"donate": "Faire un don",
|
||||
|
@ -443,11 +450,13 @@
|
|||
"warning": "Terminé avec succès avec des erreurs ou des alertes",
|
||||
"pending": "En cours"
|
||||
},
|
||||
"processing": "Le serveur traite l'action..."
|
||||
"processing": "Le serveur traite l'action...",
|
||||
"partial_logs": "[...] ( voir l'historique pour les logs complets)"
|
||||
},
|
||||
"go_back": "Revenir",
|
||||
"postinstall": {
|
||||
"force": "Forcer la post-installation"
|
||||
"force": "Forcer la post-installation",
|
||||
"title": "Post-installation"
|
||||
},
|
||||
"human_routes": {
|
||||
"users": {
|
||||
|
@ -495,7 +504,9 @@
|
|||
"manual_renew_LE": "Renouveler le certificat pour '{name}'",
|
||||
"install_LE": "Installer le certificat pour '{name}'",
|
||||
"delete": "Supprimer le domaine '{name}'",
|
||||
"add": "Ajouter le domaine '{name}'"
|
||||
"add": "Ajouter le domaine '{name}'",
|
||||
"update_config": "Mise à jour de la configuration '{name}'",
|
||||
"push_dns_changes": "Envoyer les enregistrements DNS au registraire pour '{name}'"
|
||||
},
|
||||
"diagnosis": {
|
||||
"unignore": {
|
||||
|
@ -525,6 +536,40 @@
|
|||
},
|
||||
"adminpw": "Changer le mot de passe administrateur"
|
||||
},
|
||||
"items_verbose_items_left": "Il reste des {items}.",
|
||||
"confirm_group_add_access_permission": "Voulez-vous vraiment accorder l'accès à {perm} à {name} ? Un tel accès augmente considérablement la surface d'attaque si {name} se trouve être une personne malveillante. Vous ne devriez le faire que si vous FAITES CONFIANCE à cette personne/ ce groupe."
|
||||
"items_verbose_items_left": "Il reste des {items}. | Il reste un {items}. | Il reste des {items}.",
|
||||
"confirm_group_add_access_permission": "Voulez-vous vraiment accorder l'accès à {perm} à {name} ? Un tel accès augmente considérablement la surface d'attaque si {name} se trouve être une personne malveillante. Vous ne devriez le faire que si vous FAITES CONFIANCE à cette personne/ ce groupe.",
|
||||
"users_import": "Importer des utilisateurs",
|
||||
"users_export": "Exporter les utilisateurs",
|
||||
"users_import_csv_file": "Fichier CSV",
|
||||
"users_import_update": "Mettre à jour les utilisateurs existants",
|
||||
"users_import_delete": "Supprimer les utilisateurs non répertoriés",
|
||||
"users_import_update_desc": "Si coché, tous les utilisateurs existants contenus dans le fichier CSV seront mis à jour avec les nouvelles valeurs",
|
||||
"users_import_delete_desc": "Si coché, tous les utilisateurs existants qui ne sont pas dans le fichier CSV seront supprimés (et purgés).",
|
||||
"users_import_confirm_destructive": "Êtes-vous sûr de vouloir supprimer les utilisateurs qui ne sont pas présents dans ce fichier ?",
|
||||
"users_import_delete_others": "Supprimer les utilisateurs non répertoriés",
|
||||
"users_import_csv_file_desc": "Le fichier CSV doit être au format UTF-8 et avec les colonnes nom d'utilisateur, mot de passe, groupes, email et quota. Pour un exemple d'importation de fichier CSV, vous pouvez <a href='/yunohost/api/users/export' target='_BLANK'>exporter vos utilisateurs dans un fichier CSV</a> et modifier le fichier.",
|
||||
"yunohost_admin": "YunoHost Admin",
|
||||
"domain": {
|
||||
"config": {
|
||||
"edit": "Modifier la configuration du domaine",
|
||||
"title": "Configuration du domaine"
|
||||
},
|
||||
"dns": {
|
||||
"auto_config": "Configuration automatique des enregistrements DNS",
|
||||
"auto_config_ok": "La configuration automatique semble être OK !",
|
||||
"auto_config_zone": "Zone DNS actuelle",
|
||||
"edit": "Modifier la configuration DNS",
|
||||
"info": "La configuration automatique des enregistrements DNS est une fonctionnalité expérimentale. <br>Envisagez de sauvegarder votre zone DNS actuelle à partir de l'interface de votre registraire DNS avant de transférer les enregistrements à partir d'ici.",
|
||||
"manual_config": "Enregistrements DNS suggérés pour la configuration manuelle",
|
||||
"push_force": "Écraser les enregistrements existants",
|
||||
"push_force_warning": "Il semble que certains enregistrements DNS que YunoHost aurait définis soient déjà dans la configuration du registrar. Vous pouvez utiliser l'option d'écrasement si vous savez ce que vous faites.",
|
||||
"push_force_confirm": "Êtes-vous sûr de vouloir forcer tous les enregistrements DNS suggérés ? Sachez qu'il peut écraser manuellement ou des enregistrements par défaut importants définis par vous ou votre registrar.",
|
||||
"auto_config_ignored": "ignoré, ne sera pas modifié par YunoHost sauf si vous cochez l'option d'écrasement",
|
||||
"push": "Envoyer les enregistrements DNS vers le registrar"
|
||||
}
|
||||
},
|
||||
"domain_dns_push_failed_to_authenticate": "Échec de l'authentification sur l'API du registraire. Très probablement les <a href='#/domains/{domain}/config'>informations d'identification</a> sont incorrectes ? (Erreur : {error})",
|
||||
"domain_dns_push_managed_in_parent_domain": "La fonctionnalité d'enregistrement DNS automatique est gérée dans le domaine parent <a href='#/domains/{parent_domain}/dns'>{parent_domain}</a>.",
|
||||
"domain_dns_push_not_applicable": "La fonctionnalité d'enregistrements DNS automatiques n'est pas applicable au domaine {domain},<br> Vous devez configurer manuellement vos enregistrements DNS en suivant la <a href='https://yunohost.org/dns'>documentation</a> et les suggestions configuration ci-dessous.",
|
||||
"text_selection_is_disabled": "La sélection du texte est désactivée. Si vous voulez partager ce log *complet*, vous pouvez le faire en cliquant sur le bouton 'Partager avec Yunopaste'.<br/><small>Ou, si vous voulez vraiment vraiment sélectionner du texte, appuyez sur ces touches : ↓↓↑↑.</small>"
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"pending": "En progreso",
|
||||
"error": "Sen éxito"
|
||||
},
|
||||
"processing": "O servidor está procesando a acción..."
|
||||
"processing": "O servidor está procesando a acción...",
|
||||
"partial_logs": "[...] (mira o historial para ver o rexistro completo)"
|
||||
},
|
||||
"all": "Todo",
|
||||
"administration_password": "Contrasinal de administración",
|
||||
|
@ -155,14 +156,20 @@
|
|||
"notInUsers": "A usuaria '{value}' xa existe.",
|
||||
"minValue": "O valor debe ser un número igual ou superior a {min}.",
|
||||
"name": "Os nomes non inclúen caracteres especiais excepto <code> ,.'-</code>",
|
||||
"githubLink": "A URL debe ser unha ligazón de Github a un repositorio",
|
||||
"githubLink": "URL debe ser unha ligazón a un repositorio de GitHub",
|
||||
"emailForward": "Email de reenvío non válido: ten que ser alfanumérico e caracteres <code>_.-+</code> (ex. menganito+etiqueta@exemplo.com, m3ng4ni7-o+etiqueta@exemplo.com)",
|
||||
"email": "Email non válido: debe ser alfanumércio e caracteres <code>_.-</code> (ex. menganito@exemplo.com, p4c-0@exemplo.com)",
|
||||
"dynDomain": "Nome de dominio non válido: Só pode ter caracteres alfanuméricos en minúscula e barra",
|
||||
"domain": "Nome de dominio non válido: Só pode ser alfanumérico en minúsculas, punto e barra",
|
||||
"between": "O valor ten que estar entre {min} e {max}.",
|
||||
"alphalownum_": "O valor só pode ter caracteres alfanuméricos en minúscula e trazos baixos.",
|
||||
"alpha": "O valor só pode ter caracteres alfanuméricos."
|
||||
"alpha": "O valor só pode ter caracteres alfanuméricos.",
|
||||
"fileMediaTypeMatch": "Tipo de ficheiro non válido: ten que ser <code>{arg}</code>.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}",
|
||||
"maxValue": "O valor ten que ser un número igual ou menor que {max}.",
|
||||
"invalid_form": "O formulario contén erros.",
|
||||
"appRepoUrl": "É de esperar que os URL dos repositorios YunoHost teñan este formato https://domain.tld/path/to/repo_ynh"
|
||||
},
|
||||
"footer": {
|
||||
"donate": "Doa",
|
||||
|
@ -281,8 +288,8 @@
|
|||
"last_ran": "Última execución:",
|
||||
"label_for_manifestname": "Etiqueta para {name}",
|
||||
"label": "Etiqueta",
|
||||
"items_verbose_items_left": "Restan {items}.",
|
||||
"items_verbose_count": "Hai {items}.",
|
||||
"items_verbose_items_left": "Faltan {items}. | Falta 1 {items}. | Faltan {items}.",
|
||||
"items_verbose_count": "Hai {items}. | Hai 1 {items}. | Hai {items}.",
|
||||
"logs_share_with_yunopaste": "Compartir rexistros con YunoPaste",
|
||||
"logs_context": "Contexto",
|
||||
"logs_path": "Ruta",
|
||||
|
@ -304,7 +311,8 @@
|
|||
"groupname": "Nome do grupo",
|
||||
"lastname": "Pérez",
|
||||
"firstname": "Josefa",
|
||||
"username": "pepitaperez"
|
||||
"username": "pepitaperez",
|
||||
"file": "Navega ata o ficheiro ou arrastra e sóltao"
|
||||
},
|
||||
"perform": "Realizar",
|
||||
"path": "Ruta",
|
||||
|
@ -327,7 +335,7 @@
|
|||
"select_none": "Elexir ningún",
|
||||
"select_all": "Elexir todo",
|
||||
"search": {
|
||||
"not_found": "Hai {items} que cumpren o teu criterio.",
|
||||
"not_found": "Hai {items} que cumpren o teu criterio. | Hai 1 {items} que cumpre o teu criterio. | Hai {items} que cumpren o teu criterio.",
|
||||
"for": "Buscar {items}..."
|
||||
},
|
||||
"save": "Gardar",
|
||||
|
@ -379,7 +387,9 @@
|
|||
"manual_renew_LE": "Renovar certificado para '{name}'",
|
||||
"install_LE": "Instalar certificado para '{name}'",
|
||||
"delete": "Eliminar dominio '{name}'",
|
||||
"add": "Engadir dominio '{name}'"
|
||||
"add": "Engadir dominio '{name}'",
|
||||
"push_dns_changes": "Subir rexistros DNS á rexistradora para '{name}'",
|
||||
"update_config": "Actualizar configuración de '{name}'"
|
||||
},
|
||||
"diagnosis": {
|
||||
"unignore": {
|
||||
|
@ -423,7 +433,8 @@
|
|||
"postinstall_intro_1": "Parabéns! YunoHost instalouse correctamente.",
|
||||
"postinstall_domain": "Este é o primeiro nome de dominio ligado ao teu servidor YunoHost, pero tamén o que utilizarán as usuarias para acceder ao portal de autenticación. Por isto, será visible para todo o mundo, así que elíxeo con coidado.",
|
||||
"postinstall": {
|
||||
"force": "Forzar a post-instalación"
|
||||
"force": "Forzar a post-instalación",
|
||||
"title": "Postinstall"
|
||||
},
|
||||
"ports": "Portos",
|
||||
"port": "Porto",
|
||||
|
@ -512,7 +523,8 @@
|
|||
"wrong_password": "Contrasinal incorrecto",
|
||||
"words": {
|
||||
"default": "Por defecto",
|
||||
"collapse": "Pechar"
|
||||
"collapse": "Pechar",
|
||||
"browse": "Navegar"
|
||||
},
|
||||
"warnings": "{count} avisos",
|
||||
"version": "Versión",
|
||||
|
@ -524,5 +536,39 @@
|
|||
"user_new_forward": "novoreenvio@omeudominoexterno.org",
|
||||
"user_mailbox_use": "Espazo de correo utilizado",
|
||||
"user_mailbox_quota": "Cota de correo",
|
||||
"user_interface_link": "Interface de usuaria"
|
||||
"user_interface_link": "Interface de usuaria",
|
||||
"users_import": "Importar usuarias",
|
||||
"users_export": "Exportar usuarias",
|
||||
"users_import_csv_file": "Ficheiro CSV",
|
||||
"users_import_update": "Actualizar usuarias existentes",
|
||||
"users_import_delete": "Eliminar usuarias fóra da lista",
|
||||
"users_import_csv_file_desc": "O ficheiro CSV ten que usar UTF-8 coas columnas nome de usuaria, contrasinal, grupos, email e cota. A xeito de exemplo de importación CSV, podes <a href='/yunohost/api/users/export' target='_BLANK'>exportar as túas usuarias a un ficheiro CSV</a> e edita o ficheiro.",
|
||||
"users_import_update_desc": "Se está marcado, tódalas usuarias existentes contidas no ficheiro CSV vanse actualizar co novo valor",
|
||||
"users_import_delete_desc": "Se está marcado, tódalas usuarias existentes que non están no ficheiro CSV van ser borradas (e eliminadas).",
|
||||
"users_import_confirm_destructive": "Tes a certeza de querer eliminar as usuarias que non están presentes neste ficheiro?",
|
||||
"users_import_delete_others": "Eliminar usuarias de fóra da lista",
|
||||
"yunohost_admin": "Admin YunoHost",
|
||||
"domain": {
|
||||
"config": {
|
||||
"edit": "Editar configuración do dominio",
|
||||
"title": "Configuración do dominio"
|
||||
},
|
||||
"dns": {
|
||||
"auto_config": "Configuración automática dos rexistros DNS",
|
||||
"auto_config_ignored": "ignorado, YunoHost non o cambiará a menos que marques a opción de sobrescritura",
|
||||
"auto_config_ok": "A configuración automática semella correcta!",
|
||||
"auto_config_zone": "Zona DNS actual",
|
||||
"edit": "Editar configuración DNS",
|
||||
"info": "A configuración automática dos rexistros DNS é unha ferramenta experimental. <br>Considera gardar a configuración actual na interface da empresa rexistradora antes de realizar cambios desde aquí.",
|
||||
"manual_config": "Rexistros DNS suxeridos para configuración manual",
|
||||
"push": "Enviar rexistros DNS á rexistradora",
|
||||
"push_force": "Sobrescribir rexistros existentes",
|
||||
"push_force_warning": "Semella que algúns rexistros DNS que YunoHost querería configurar xa están establecidos na configuración. Podes usar a sobrescritura se sabes o que estás a facer.",
|
||||
"push_force_confirm": "Tes a certeza de que queres forzar a sobrescritura cos rexistros suxeridos? Ten en conta que poderías sobrescribir rexistros por defecto ou manuais establecidos na rexistradora."
|
||||
}
|
||||
},
|
||||
"domain_dns_push_managed_in_parent_domain": "A ferramenta para os rexistros DNS automáticos están xestionados polo dominio nai <a href='#/domains/{parent_domain}/dns'>{parent_domain}</a>.",
|
||||
"domain_dns_push_failed_to_authenticate": "Fallou a autenticación na API da empresa rexistradora. Serán incorrectas as <a href='#/domains/{domain}/config'>credenciais</a>? (Erro: {error})",
|
||||
"domain_dns_push_not_applicable": "A ferramenta para rexistro automático DNS non é de aplicación ao dominio {domain}, <br> Deberías configurar manualmente os teus rexistros DNS seguindo a <a href='https://yunohost.org/dns'>documentación</a> e a configuración inferior suxerida.",
|
||||
"text_selection_is_disabled": "A selección de texto está desactivada. Se queres compartir este rexistro, comparte o rexistro *completo* co botón 'Compartir con YunoHost'. <br/><small>Ou se realmente queres seleccionar un texto, preme estas teclas: ↓↓↑↑.</small>"
|
||||
}
|
||||
|
|
294
app/src/i18n/locales/id.json
Normal file
294
app/src/i18n/locales/id.json
Normal file
|
@ -0,0 +1,294 @@
|
|||
{
|
||||
"add": "Tambahkan",
|
||||
"address": {
|
||||
"domain_description": {
|
||||
"email": "Pilih sebuah domain untuk surel Anda.",
|
||||
"domain": "Pilih sebuah domain."
|
||||
},
|
||||
"local_part_description": {
|
||||
"domain": "Pilih sebuah subdomain."
|
||||
}
|
||||
},
|
||||
"administration_password": "Kata sandi administrasi",
|
||||
"all": "Semua",
|
||||
"api": {
|
||||
"processing": "Server sedang memproses tindakan...",
|
||||
"query_status": {
|
||||
"error": "Gagal",
|
||||
"pending": "Sedang berjalan",
|
||||
"success": "Berhasil"
|
||||
}
|
||||
},
|
||||
"api_error": {
|
||||
"server_said": "Ketika memproses tindakan server mengatakan:",
|
||||
"sorry": "Maaf tentang itu.",
|
||||
"info": "Informasi berikut mungkin berguna untuk orang yang membantu Anda:",
|
||||
"error_message": "Pesan kesalahan:",
|
||||
"help": "Anda dapat mencari bantuan di <a href=\"https://forum.yunohost.org/\">forum</a> atau <a href=\"https://chat.yunohost.org/\">di obrolan</a> untuk menangani situasinya, atau laporkan bug di <a href=\"https://github.com/YunoHost/issues\">bugtracker</a>.",
|
||||
"view_error": "Lihat kesalahan"
|
||||
},
|
||||
"api_errors_titles": {
|
||||
"APINotRespondingError": "API YunoHost tidak menjawab",
|
||||
"APIError": "YunoHost mengalami kesalahan yang tak terduga",
|
||||
"APIBadRequestError": "YunoHost mengalami sebuah kesalahan",
|
||||
"APIInternalError": "YunoHost mengalami sebuah kesalahan internal"
|
||||
},
|
||||
"all_apps": "Semua aplikasi",
|
||||
"api_waiting": "Menunggu jawaban server...",
|
||||
"app_choose_category": "Pilih sebuah kategori",
|
||||
"app_config_panel": "Panel konfigurasi",
|
||||
"app_config_panel_label": "Konfigurasi aplikasi ini",
|
||||
"app_config_panel_no_panel": "Aplikasi ini tidak dapat dikonfigurasi",
|
||||
"app_info_access_desc": "Kelompok / pengguna yang diizinkan mengakses aplikasi ini:",
|
||||
"app_info_change_url_disabled_tooltip": "Fitur ini belum diimplementasikan pada aplikasi ini",
|
||||
"app_info_uninstall_desc": "Hapus aplikasi ini.",
|
||||
"app_install_custom_no_manifest": "Tidak ada berkas manifest.json",
|
||||
"app_install_parameters": "Pengaturan pemasangan",
|
||||
"app_show_categories": "Tampilkan kategori",
|
||||
"app_state_notworking": "tidak bekerja",
|
||||
"app_state_notworking_explanation": "Pemelihara aplikasi ini menyatakan ini 'tidak bekerja'. INI AKAN MERUSAK SISTEM ANDA!",
|
||||
"app_state_lowquality": "kualitas rendah",
|
||||
"app_state_highquality": "kualitas tinggi",
|
||||
"app_state_highquality_explanation": "Aplikasi ini berintegrasi dengan YunoHost dengan baik selama sekurang-kurangnya satu tahun.",
|
||||
"app_state_working": "bekerja",
|
||||
"applications": "Aplikasi",
|
||||
"backup": "Pencadangan",
|
||||
"backup_action": "Cadangkan",
|
||||
"backup_new": "Cadangan baru",
|
||||
"begin": "Mulai",
|
||||
"both": "Keduanya",
|
||||
"cancel": "Batal",
|
||||
"catalog": "Katalog",
|
||||
"close": "Tutup",
|
||||
"code": "Kode",
|
||||
"common": {
|
||||
"firstname": "Nama depan",
|
||||
"lastname": "Nama belakang"
|
||||
},
|
||||
"configuration": "Konfigurasi",
|
||||
"confirm_change_maindomain": "Apakah Anda yakin ingin mengubah domain utama?",
|
||||
"confirm_delete": "Apakah Anda yakin ingin menghapus {name}?",
|
||||
"confirm_install_custom_app": "PERINGATAN! Memasang aplikasi pihak ketiga dapat membahayakan integritas serta keamanan sistem Anda. Anda sebaiknya TIDAK memasangnya kecuali Anda yakin dengan apa yang Anda lakukan. Apakah Anda ingin menerima resiko tersebut?",
|
||||
"confirm_app_install": "Apakah Anda yakin ingin memasang aplikasi ini?",
|
||||
"confirm_service_restart": "Apakah Anda yakin ingin memulai-ulang {name}?",
|
||||
"confirm_service_start": "Apakah Anda yakin ingin memulai {name}?",
|
||||
"confirm_service_stop": "Apakah Anda yakin ingin menghentikan {name}?",
|
||||
"confirm_update_apps": "Apakah Anda yakin ingin memperbarui semua aplikasi?",
|
||||
"confirm_update_specific_app": "Apakah Anda yakin ingin memperbarui {app}?",
|
||||
"confirm_upnp_enable": "Apakah Anda yakin ingin mengaktifkan UPnP?",
|
||||
"confirm_upnp_disable": "Apakah Anda yakin ingin menonaktifkan UPnP?",
|
||||
"confirm_reboot_action_reboot": "Apakah Anda yakin ingin memulai-ulang server Anda?",
|
||||
"confirm_reboot_action_shutdown": "Apakah Anda yakin ingin mematikan server Anda?",
|
||||
"created_at": "Dibuat pada",
|
||||
"day_validity": " Kedaluwarsa | 1 hari | {count} hari",
|
||||
"dead": "Nonaktif",
|
||||
"delete": "Hapus",
|
||||
"description": "Keterangan",
|
||||
"details": "Keterangan",
|
||||
"diagnosis": "Diagnosis",
|
||||
"disable": "Nonaktifkan",
|
||||
"disabled": "Dinonaktifkan",
|
||||
"dns": "DNS",
|
||||
"domain_add": "Tambahkan domain",
|
||||
"domain_add_panel_with_domain": "Saya sudah memiliki domain…",
|
||||
"domain_add_panel_without_domain": "Saya belum punya sebuah domain…",
|
||||
"domain_delete_longdesc": "Hapus domain ini",
|
||||
"domain_visit": "Kunjungi",
|
||||
"domain_visit_url": "Kunjungi {url}",
|
||||
"download": "Unduh",
|
||||
"enable": "Aktifkan",
|
||||
"enabled": "Diaktifkan",
|
||||
"error": "Kesalahan",
|
||||
"since": "sejak",
|
||||
"skip": "Lewati",
|
||||
"start": "Mulai",
|
||||
"status": "Status",
|
||||
"tools_adminpw_current_placeholder": "Masukkan kata sandi Anda saat ini",
|
||||
"password": "Kata sandi",
|
||||
"human_routes": {
|
||||
"upgrade": {
|
||||
"system": "Perbarui sistem",
|
||||
"apps": "Perbarui semua aplikasi",
|
||||
"app": "Perbarui aplikasi '{app}'"
|
||||
},
|
||||
"reboot": "Mulai ulang server",
|
||||
"services": {
|
||||
"restart": "Mulai ulang layanan '{name}'",
|
||||
"start": "Jalankan layanan '{name}'",
|
||||
"stop": "Hentikan layanan '{name}'"
|
||||
},
|
||||
"users": {
|
||||
"create": "Buat pengguna '{name}'",
|
||||
"delete": "Hapus pengguna '{name}'",
|
||||
"update": "Perbarui pengguna '{name}'"
|
||||
},
|
||||
"shutdown": "Matikan server",
|
||||
"update": "Periksa pembaruan",
|
||||
"diagnosis": {
|
||||
"run_specific": "Jalankan diagnosis '{description}'",
|
||||
"run": "Jalankan diagnosis",
|
||||
"ignore": {
|
||||
"warning": "Abaikan peringatan"
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"install": "Pasang aplikasi '{name}'"
|
||||
},
|
||||
"domains": {
|
||||
"add": "Tambahkan domain '{name}'",
|
||||
"delete": "Hapus domain '{name}'"
|
||||
}
|
||||
},
|
||||
"save": "Simpan",
|
||||
"search": {
|
||||
"not_found": "Ada {item} yang cocok dengan kriteria Anda."
|
||||
},
|
||||
"select_all": "Pilih semua",
|
||||
"stop": "Berhenti",
|
||||
"system": "Sistem",
|
||||
"system_apps_nothing": "Semua aplikasi sudah mutakhir!",
|
||||
"system_upgrade_all_applications_btn": "Perbarui semua aplikasi",
|
||||
"system_upgrade_all_packages_btn": "Perbarui semua paket (package)",
|
||||
"tcp": "TCP",
|
||||
"tools_adminpw": "Ubah kata sandi administrasi",
|
||||
"tools_adminpw_current": "Kata sandi saat ini",
|
||||
"tools_reboot_done": "Memulai ulang...",
|
||||
"tools_shutdown": "Matikan server Anda",
|
||||
"tools_shutdown_btn": "Matikan",
|
||||
"tools_shutdown_done": "Mematikan...",
|
||||
"tools_shuttingdown": "Server Anda sedang dimatikan. Selama server Anda mati, Anda tidak akan dapat menggunakan administrasi web.",
|
||||
"tools_shutdown_reboot": "Matikan/Mulai Ulang",
|
||||
"tools_webadmin": {
|
||||
"language": "Bahasa",
|
||||
"fallback_language_description": "Bahasa yang akan digunakan jika terjemahan tidak tersedia di bahasa utama.",
|
||||
"cache": "Cache",
|
||||
"cache_description": "Pertimbangkan menonaktifkan cache jika Anda ingin menggunakan CLI sambil menggunakan administrasi web.",
|
||||
"experimental": "Mode eksperimental"
|
||||
},
|
||||
"user_fullname": "Nama lengkap",
|
||||
"users_import_csv_file": "Berkas CSV",
|
||||
"warnings": "{count} peringatan",
|
||||
"certificate_alert_good": "Baik, sertifikatnya terlihat bagus!",
|
||||
"certificate_alert_great": "Bagus! Anda menggunakan sertifikat Let's Encrypt yang valid!",
|
||||
"certificate_alert_unknown": "Status tak diketahui",
|
||||
"certificate_manage": "Kelola sertifikat SSL",
|
||||
"ssl_certificate": "Sertifikat SSL",
|
||||
"domain_is_eligible_for_ACME": "Domain ini kelihatannya sudah dikonfigurasi dengan sehingga Anda dapat memasang sertifikat Let's Encrypt!",
|
||||
"ok": "Oke",
|
||||
"run": "Jalankan",
|
||||
"system_update": "Pembaruan sistem",
|
||||
"system_upgrade_btn": "Perbarui",
|
||||
"tools": "Alat",
|
||||
"tools_reboot": "Mulai ulang server Anda",
|
||||
"tools_reboot_btn": "Mulai ulang",
|
||||
"upnp": "UPnP",
|
||||
"upnp_disabled": "UPnP tidak aktif.",
|
||||
"upnp_enabled": "UPnP aktif.",
|
||||
"url": "URL",
|
||||
"user_email": "Surel",
|
||||
"user_interface_link": "Antarmuka pengguna",
|
||||
"users_import_confirm_destructive": "Apakah Anda yakin ingin menghapus pengguna yang tidak ada di dalam berkas ini?",
|
||||
"version": "Versi",
|
||||
"wrong_password": "Kata sandi salah",
|
||||
"yes": "Ya",
|
||||
"yunohost_admin": "Admin YunoHost",
|
||||
"certificate_alert_not_valid": "KRITIS: Sertifikat saat ini tidak valid! HTTPS sama sekali tidak akan berkerja!",
|
||||
"confirm_cert_install_LE": "Apakah Anda yakin ingin memasang sertifikat Let's Encrypt pada domain ini?",
|
||||
"running": "Berjalan",
|
||||
"service_start_on_boot": "Mulai saat boot",
|
||||
"services": "Layanan",
|
||||
"size": "Ukuran",
|
||||
"tools_webadmin_settings": "Pengaturan administrasi web",
|
||||
"udp": "UDP",
|
||||
"unignore": "Jangan diabaikan",
|
||||
"unknown": "Tidak diketahui",
|
||||
"user_username": "Nama pengguna",
|
||||
"user_username_edit": "Sunting akun milik {name}",
|
||||
"users": "Pengguna",
|
||||
"users_new": "Pengguna baru",
|
||||
"users_no": "Tak ada pengguna.",
|
||||
"certificate": "Sertifikat",
|
||||
"certificate_status": "Status sertifikat",
|
||||
"install_letsencrypt_cert": "Pasang sertifikat Let's Encrypt",
|
||||
"purge_user_data_checkbox": "Bersihkan data milik {name}? (Ini akan menghapus isi direktori home dan mail mereka)",
|
||||
"purge_user_data_warning": "Membersihkan data pengguna tidak dapat dibatalkan. Pastikan Anda yakin dengan perbuatan Anda!",
|
||||
"domain_add_dns_doc": "… dan saya sudah <a href='//yunohost.org/dns_config' target='_blank'>mengatur DNS dengan benar</a>.",
|
||||
"domain_dns_config": "Konfigurasi DNS",
|
||||
"domain_dns_longdesc": "Lihat konfigurasi DNS",
|
||||
"footer": {
|
||||
"help": "Butuh bantuan?",
|
||||
"donate": "Berdonasi",
|
||||
"documentation": "Dokumentasi"
|
||||
},
|
||||
"form_errors": {
|
||||
"notInUsers": "Pengguna '{value}' sudah ada.",
|
||||
"passwordLenght": "Kata sandi harus memiliki sekurang-kurangnya 8 karakter."
|
||||
},
|
||||
"form_input_example": "Contoh: {example}",
|
||||
"group": "Kelompok",
|
||||
"group_name": "Nama kelompok",
|
||||
"group_all_users": "Semua pengguna",
|
||||
"group_visitors": "Pengunjung",
|
||||
"group_add_member": "Tambahkan pengguna",
|
||||
"history": {
|
||||
"is_empty": "Tak ada apapun di riwayat saat ini.",
|
||||
"last_action": "Tindakan terakhir:",
|
||||
"methods": {
|
||||
"DELETE": "hapus",
|
||||
"GET": "baca",
|
||||
"POST": "buat/eksekusi",
|
||||
"PUT": "modifikasi"
|
||||
},
|
||||
"title": "Riwayat"
|
||||
},
|
||||
"domain": {
|
||||
"config": {
|
||||
"title": "Konfigurasi domain"
|
||||
}
|
||||
},
|
||||
"error_server_unexpected": "Kesalahan server yang tak terduga",
|
||||
"everything_good": "Semuanya bagus!",
|
||||
"experimental": "Eksperimental",
|
||||
"footer_version": "Didukung oleh <a href='https://yunohost.org'>YunoHost</a> {version} ({repo}).",
|
||||
"go_back": "Kembali",
|
||||
"group_explain_all_users": "Ini adalah kelompok istimewa yang mengandung semua akun pengguna di server ini",
|
||||
"home": "Beranda",
|
||||
"hook_adminjs_group_configuration": "Konfigurasi sistem",
|
||||
"hook_conf_ldap": "Database pengguna",
|
||||
"hook_conf_ynh_settings": "Konfigurasi YunoHost",
|
||||
"restart": "Mulai ulang",
|
||||
"protocol": "Protokol",
|
||||
"readme": "Readme",
|
||||
"rerun_diagnosis": "Jalankan diagnosis lagi",
|
||||
"open": "Buka",
|
||||
"hook_data_xmpp": "Data XMPP",
|
||||
"ignored": "{count} diabaikan",
|
||||
"infos": "Informasi",
|
||||
"logout": "Keluar",
|
||||
"hook_data_home": "Data pengguna",
|
||||
"hook_data_home_desc": "Data pengguna terletak di /home/PENGGUNA",
|
||||
"install": "Pasang",
|
||||
"install_name": "Pasang {id}",
|
||||
"install_time": "Waktu pemasangan",
|
||||
"installation_complete": "Pemasangan selesai",
|
||||
"installed": "Terpasang",
|
||||
"ipv4": "IPv4",
|
||||
"ipv6": "IPv6",
|
||||
"issues": "{count} masalah",
|
||||
"label": "Label",
|
||||
"last_ran": "Terakhir dijalankan:",
|
||||
"license": "Lisensi",
|
||||
"login": "Masuk",
|
||||
"no": "Tidak",
|
||||
"only_highquality_apps": "Hanya aplikasi berkualitas tinggi",
|
||||
"only_working_apps": "Hanya aplikasi yang dapat bekerja",
|
||||
"only_decent_quality_apps": "Hanya aplikasi yang lumayan berkualitas",
|
||||
"placeholder": {
|
||||
"username": "johndoe",
|
||||
"firstname": "John",
|
||||
"lastname": "Doe",
|
||||
"domain": "domainku.com"
|
||||
},
|
||||
"postinstall_intro_1": "Selamat! YunoHost berhasil dipasang.",
|
||||
"postinstall_intro_3": "Anda dapat mendapatkan lebih banyak informasi dengan mengunjungi <a href='//yunohost.org/en/install/hardware:vps_debian#fa-cog-proceed-with-the-initial-configuration' target='_blank'>halaman dokumentasinya</a>"
|
||||
}
|
|
@ -376,14 +376,16 @@
|
|||
"notInUsers": "L'utente '{value}' esiste già.",
|
||||
"minValue": "Il valore dev'essere maggiore o uguale a {min}.",
|
||||
"name": "I nomi non devono includere caratteri speciali, tolti <code> ,.'-</code>",
|
||||
"githubLink": "L'url dev'essere un repository Github valido",
|
||||
"githubLink": "L'URL dev'essere un repository GitHub valido",
|
||||
"emailForward": "Inoltro email non valido: dev'esser composta di soli caratteri alfanumerici e <code>_.-+</code> (es: prova+ricevute@esempio.com, m4r1-0@esempio.com)",
|
||||
"email": "Email non valida: dev'esser composta di soli caratteri alfanumerici e <code>_.-</code> (es: prova@esempio.com, m4r1-0@esempio.com)",
|
||||
"dynDomain": "Nome dominio non valido: Dev'esser composto di soli caratteri alfanumerici minuscoli e trattini",
|
||||
"domain": "Nome dominio non valido: Dev'esser composto di soli caratteri alfanumerici minuscoli, punti e trattini",
|
||||
"between": "Il valore dev'essere compreso tra {min} e {max}.",
|
||||
"alphalownum_": "Il valore deve contenere solo caratteri alfanumerici minuscoli e underscore.",
|
||||
"alpha": "Il valore deve comprendere solo caratteri alfanumerici."
|
||||
"alpha": "Il valore deve comprendere solo caratteri alfanumerici.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}"
|
||||
},
|
||||
"footer": {
|
||||
"donate": "Dona",
|
||||
|
@ -520,7 +522,7 @@
|
|||
"perform_action": "Esegui l'azione '{action}' dell'app '{name}'",
|
||||
"set_default": "Reindirizza la domain root di '{domain}' a '{name}'",
|
||||
"install": "Installa l'app '{name}'",
|
||||
"change_url": "Cambia l'url d'accesso di '{name}'",
|
||||
"change_url": "Cambia l'URL d'accesso di '{name}'",
|
||||
"change_label": "Cambia label da '{prevName}' a '{nextName}'"
|
||||
},
|
||||
"adminpw": "Cambia password d'amministratore"
|
||||
|
|
13
app/src/i18n/locales/mk.json
Normal file
13
app/src/i18n/locales/mk.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"action": "акција",
|
||||
"add": "додади",
|
||||
"address": {
|
||||
"domain_description": {
|
||||
"domain": "одбери домен",
|
||||
"email": "одбери домен за вашиот емаил"
|
||||
},
|
||||
"local_part_description": {
|
||||
"domain": "одбери под домен"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,7 +49,7 @@
|
|||
"home": "Thuis",
|
||||
"hook_adminjs_group_configuration": "Configuratie",
|
||||
"hook_conf_cron": "Reguliere taken",
|
||||
"hook_conf_ldap": "LDAP",
|
||||
"hook_conf_ldap": "Gebruikersdatabase",
|
||||
"hook_conf_nginx": "Nginx",
|
||||
"hook_conf_ssh": "SSH",
|
||||
"hook_conf_ssowat": "SSOwat",
|
||||
|
@ -93,7 +93,7 @@
|
|||
"postinstall_domain": "Dit is de eerst domeinnaam die aan je YunoHost server gelinkt is, maar ook hetgene gebruikt zal worden als portaal voor je gebruikers om je server te bezoeken. Het is dus zichtbaar door iedereen, kies het zorgvuldig.",
|
||||
"postinstall_intro_1": "Gefeliciteerd! YunoHost is met succes geïnstalleerd.",
|
||||
"postinstall_intro_2": "Er zijn nog twee configuratiestappen nodig om de diensten van je server te activeren.",
|
||||
"postinstall_intro_3": "Je kan meer informatie verkrijgen door <a href='//yunohost.org/postinstall' target='_blank'>de gepaste documentatie</a> te bekijken",
|
||||
"postinstall_intro_3": "Je kan meer informatie verkrijgen door <a href='//yunohost.org/en/install/hardware:vps_debian#fa-cog-proceed-with-the-initial-configuration' target='_blank'>de gepaste documentatie</a> te bekijken",
|
||||
"postinstall_password": "Dit wachtwoord zal gebruikt worden om alles binnen je server te beheren. Neem de tijd om het zorgvuldig te kiezen.",
|
||||
"previous": "Vorige",
|
||||
"protocol": "Protocol",
|
||||
|
@ -160,8 +160,8 @@
|
|||
"app_choose_category": "Kies een categorie",
|
||||
"api_waiting": "Aan het wachten op de reactie van de server...",
|
||||
"api_errors_titles": {
|
||||
"APINotRespondingError": "Yunohost API reageert niet",
|
||||
"APIError": "Yunohost kwam een onverwachte fout tegen"
|
||||
"APINotRespondingError": "YunoHost API reageert niet",
|
||||
"APIError": "YunoHost kwam een onverwachte fout tegen"
|
||||
},
|
||||
"api_error": {
|
||||
"info": "De volgende informatie kan handig zijn voor de persoon die jou helpt:",
|
||||
|
|
|
@ -243,14 +243,19 @@
|
|||
"notInUsers": "O usuário '{value}' já existe.",
|
||||
"minValue": "Valor deve ser um número maior ou igual a {min}.",
|
||||
"name": "Nomes não podem incluir caracteres especiais exceto <code>,.'</code>",
|
||||
"githubLink": "A url deve ser um repositório Github válido",
|
||||
"githubLink": "A URL deve ser um repositório GitHub válido",
|
||||
"emailForward": "Encaminhamento de email inválido: deve ser composto somente de caracteres alfanuméricos e <code>_.-+</code> (e.g. alguem@exemplo.com, 4algu-3m@exemplo.com)",
|
||||
"email": "Email inválido: deve ser composto somente por caracteres alfanuméricos e <code>_.</code> (e.g. alguem@exemplo.com, 4algu-3m@exemplo.com)",
|
||||
"dynDomain": "Nome de domínio inválido: Deve ser composto somente por caracteres alfanuméricos e traço",
|
||||
"domain": "Nome de domínio inválido: Deve ser composto somente por caracteres alfanuméricos minúsculos, ponto e traço",
|
||||
"between": "O valor deve ser entre {min} e {max}.",
|
||||
"alphalownum_": "O valor só deve conter caracteres alfanuméricos minúsculos e o underscore.",
|
||||
"alpha": "O valor só pode ter caracteres alfanuméricos."
|
||||
"alpha": "O valor só pode ter caracteres alfanuméricos.",
|
||||
"maxValue": "O valor deve ser um número igual ou menor que {max}.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}",
|
||||
"invalid_form": "O formulário contém alguns erros.",
|
||||
"fileMediaTypeMatch": "Tipo de arquivo inválido: deve ser <code>{arg}</code>."
|
||||
},
|
||||
"domain_delete_forbidden_desc": "Você não pode remover '{domínio}' por ele ser o domínio padrão, você precisa escolher outro domínio (ou <a href='#/domains/add'> criar um novo</a>) e setá-lo como domínio padrão.",
|
||||
"domain_add_dyndns_forbidden": "Você já esta inscrito a um domínio DynDNS, pode pedir para remover seu domínio DynDNS atual no forum <a href='//forum.yunohost.org/t/nohost-domain-recovery-suppression-de-domaine-en-nohost-me-noho-st-et-ynh-fr/442'> no thread dedicado</a>.",
|
||||
|
@ -469,7 +474,7 @@
|
|||
"perform_action": "Executar ação '{action}' do app '{name}'",
|
||||
"set_default": "Redirecionar domínio root de '{domain}' para '{name}'",
|
||||
"install": "Instalar o app '{name}'",
|
||||
"change_url": "Mudar url de acesso de '{name}'",
|
||||
"change_url": "Mudar URL de acesso de '{name}'",
|
||||
"change_label": "Mudar label de '{prevName}' para '{nextName}'"
|
||||
},
|
||||
"adminpw": "Mudar senha do administrador"
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"backup": "Резервне копіювання",
|
||||
"archive_empty": "Порожній архів",
|
||||
"applications": "Застосунки",
|
||||
"app_state_working_explanation": "Творець застосунку позначив його як \"працює\". Це означає, що він повинен бути робочим (на рівні застосунку), але не перевірений спільнотою. Він може містити помилки або не повністю інтегрований з YunoHost.",
|
||||
"app_state_working_explanation": "Творець застосунку позначив його як «працює». Це означає, що він повинен бути робочим (на рівні застосунку), але не перевірений спільнотою. Він може містити помилки або не повністю інтегрований з YunoHost.",
|
||||
"app_state_working": "працює",
|
||||
"app_state_highquality_explanation": "Цей застосунок добре інтегрований з YunoHost принаймні з останнього року.",
|
||||
"app_state_highquality": "висока якість",
|
||||
|
@ -76,7 +76,7 @@
|
|||
"app_actions": "Дії",
|
||||
"api_waiting": "Очікування відповіді сервера...",
|
||||
"api_not_responding": "API YunoHost не відповідає. Може 'yunohost-api' не працює або щойно перезапускався?",
|
||||
"api_not_found": "Схоже, що вебадмін намагався запросити щось відсутнє.",
|
||||
"api_not_found": "Схоже, що вебадміністрація намагалася запросити щось відсутнє.",
|
||||
"all_apps": "Усі застосунки",
|
||||
"api_errors_titles": {
|
||||
"APIConnexionError": "YunoHost зіткнувся з помилкою з'єднання",
|
||||
|
@ -101,10 +101,11 @@
|
|||
"pending": "Обробляється",
|
||||
"error": "Невдало"
|
||||
},
|
||||
"processing": "Сервер обробляє дію..."
|
||||
"processing": "Сервер обробляє цю дію...",
|
||||
"partial_logs": "[...] (перевірте в історії повні журнали)"
|
||||
},
|
||||
"all": "Усе",
|
||||
"administration_password": "Пароль адміністратора",
|
||||
"administration_password": "Пароль адміністрації",
|
||||
"address": {
|
||||
"local_part_description": {
|
||||
"email": "Оберіть локальну частину вашої пошти.",
|
||||
|
@ -150,7 +151,7 @@
|
|||
"everything_good": "Усе справно!",
|
||||
"experimental": "Експериментальне",
|
||||
"firewall": "Фаєрвол",
|
||||
"footer_version": "Створено <a href='https://yunohost.org'></a> {version} ({repo}).",
|
||||
"footer_version": "Створено <a href='https://yunohost.org'>YunoHost</a> {version} ({repo}).",
|
||||
"footer": {
|
||||
"documentation": "Документація",
|
||||
"help": "Потрібна допомога?",
|
||||
|
@ -162,7 +163,7 @@
|
|||
"domain": "Неприпустиме доменне ім'я: воно має складатися тільки з букв і чисел у нижньому регістрі, крапок і тире",
|
||||
"dynDomain": "Неприпустиме доменне ім'я: воно має складатися тільки з букв і чисел в нижньому регістрі та тире",
|
||||
"email": "Неприпустима е-пошта: в ній мають бути лише букви та числа <code>_.-</code> (на зразок cholovyaha@example.com, ch0lov-1@example.com)",
|
||||
"githubLink": "URL-адреса має бути дійсним посиланням на репозиторій Github",
|
||||
"githubLink": "URL-адреса має бути дійсним посиланням на репозиторій GitHub",
|
||||
"name": "Імена не можуть містити спеціальні символи, за винятком <code> ,.'-</code>",
|
||||
"minValue": "Значення має бути числом, рівним або більшим, ніж {min}.",
|
||||
"notInUsers": "Користувач '{value}' вже існує.",
|
||||
|
@ -171,7 +172,13 @@
|
|||
"passwordMatch": "Паролі не збігаються.",
|
||||
"required": "Обов'язкове поле.",
|
||||
"alphalownum_": "У значенні можуть бути тільки букви в нижньому регістрі, числа та знак нижнього підкреслення.",
|
||||
"emailForward": "Неприпустима переадресація е-пошти: в ній мають бути лише букви та числа <code>_.-</code> (на зразок cholovyaha@example.com, ch0lov-1@example.com)"
|
||||
"emailForward": "Неприпустима переадресація е-пошти: в ній мають бути лише букви та числа <code>_.-</code> (на зразок cholovyaha@example.com, ch0lov-1@example.com)",
|
||||
"fileMediaTypeMatch": "Неприпустимий тип файлу: має бути <code>{arg}</code>.",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}",
|
||||
"invalid_form": "Форма містить деякі помилки.",
|
||||
"maxValue": "Значення має бути числом, рівним або меншим щодо {max}.",
|
||||
"appRepoUrl": "Очікується, що URL-адреси репозиторію застосунків YunoHost будуть виглядати як https: //domain.tld/path/to/repo_ynh"
|
||||
},
|
||||
"form_input_example": "Приклад: {example}",
|
||||
"from_to": "від {0} до {1}",
|
||||
|
@ -234,8 +241,8 @@
|
|||
"services": "немає служб | служба | {c} служб",
|
||||
"users": "немає користувачів | користувач | {c} користувачів"
|
||||
},
|
||||
"items_verbose_count": "Наявно {items} елементів.",
|
||||
"items_verbose_items_left": "Лишилося {items} елементів.",
|
||||
"items_verbose_count": "Наявно {items}. | Наявний 1 {items}. | Наявно {items}.",
|
||||
"items_verbose_items_left": "Лишилося {items}. | Лишився 1 {items}. | Лишилося {items}.",
|
||||
"label": "Заголовок",
|
||||
"label_for_manifestname": "Заголовок для {name}",
|
||||
"last_ran": "Востаннє запущено:",
|
||||
|
@ -266,7 +273,8 @@
|
|||
"firstname": "Ivan",
|
||||
"lastname": "Melnyk",
|
||||
"groupname": "Назва моєї групи",
|
||||
"domain": "miy-domen.ua"
|
||||
"domain": "miy-domen.ua",
|
||||
"file": "Оберіть файл у огляді чи перетягніть його"
|
||||
},
|
||||
"logs": "Журнали",
|
||||
"logs_suboperations": "Субоперації",
|
||||
|
@ -291,12 +299,13 @@
|
|||
"port": "Порт",
|
||||
"ports": "Порти",
|
||||
"postinstall": {
|
||||
"force": "Примусове післявстановлення"
|
||||
"force": "Примусове післявстановлення",
|
||||
"title": "Післявстановлення"
|
||||
},
|
||||
"postinstall_intro_1": "Вітаємо! YunoHost успішно встановлено.",
|
||||
"postinstall_intro_2": "Лишилося зробити два кроки з конфігурації, щоб задіяти служби вашого сервера.",
|
||||
"postinstall_set_domain": "Установити основний домен",
|
||||
"postinstall_set_password": "Установити пароль адміністратора",
|
||||
"postinstall_set_password": "Установити пароль адміністрації",
|
||||
"previous": "Назад",
|
||||
"protocol": "Протокол",
|
||||
"readme": "Прочитай-мене",
|
||||
|
@ -337,7 +346,9 @@
|
|||
"regen_selfsigned": "Поновлення самопідписаного сертифікату для '{name}'",
|
||||
"set_default": "Установлення '{name}' як типового домена",
|
||||
"install_LE": "Установлення сертифікату для '{name}'",
|
||||
"revert_to_selfsigned": "Повернення до самопідписаного сертифікату для '{name}'"
|
||||
"revert_to_selfsigned": "Повернення до самопідписаного сертифікату для '{name}'",
|
||||
"push_dns_changes": "Передати записи DNS до реєстратора для '{name}'",
|
||||
"update_config": "Оновити конфігурацію '{name}'"
|
||||
},
|
||||
"firewall": {
|
||||
"ports": "{action} порт {port} ({protocol}, {connection})",
|
||||
|
@ -384,7 +395,7 @@
|
|||
"save": "Зберегти",
|
||||
"search": {
|
||||
"for": "Пошук за {items}...",
|
||||
"not_found": "Є {items}, що відповідають вашим критеріям."
|
||||
"not_found": "Наявно {items}, що відповідають критеріям. | Наявний {items}, що відповідає критеріям. | Наявно {items}, що відповідають критеріям."
|
||||
},
|
||||
"select_all": "Вибрати все",
|
||||
"select_none": "Не вибирати нічого",
|
||||
|
@ -406,7 +417,7 @@
|
|||
"system_upgrade_all_packages_btn": "Оновлення всіх пакетів",
|
||||
"tcp": "TCP",
|
||||
"tools": "Засоби",
|
||||
"tools_adminpw": "Змінити пароль адміністратора",
|
||||
"tools_adminpw": "Змінити пароль адміністрації",
|
||||
"tools_adminpw_current": "Поточний пароль",
|
||||
"tools_adminpw_current_placeholder": "Уведіть поточний пароль",
|
||||
"tools_power_up": "Ваш сервер, схоже, доступний, тепер ви можете спробувати ввійти в систему.",
|
||||
|
@ -425,7 +436,7 @@
|
|||
"cache_description": "Подумайте про вимкнення кешу, якщо ви плануєте працювати з CLI та одночасно здійснювати навігацію у вебадмініструванні.",
|
||||
"experimental_description": "Дає вам доступ до експериментальних функцій. Вони вважаються нестабільними та можуть зламати вашу систему.<br>Включайте цей режим, тільки якщо ви знаєте, що робите."
|
||||
},
|
||||
"tools_webadmin_settings": "Налаштування вебадміністратора",
|
||||
"tools_webadmin_settings": "Налаштування вебадміністрації",
|
||||
"traceback": "Відслідковування (Traceback)",
|
||||
"udp": "UDP",
|
||||
"unauthorized": "Неавторизований",
|
||||
|
@ -454,7 +465,8 @@
|
|||
"warnings": "{count} попереджень",
|
||||
"words": {
|
||||
"collapse": "Колапс",
|
||||
"default": "Типово"
|
||||
"default": "Типово",
|
||||
"browse": "Огляд"
|
||||
},
|
||||
"wrong_password": "Неправильний пароль",
|
||||
"yes": "Так",
|
||||
|
@ -509,11 +521,11 @@
|
|||
"postinstall_domain": "Це перше доменне ім'я, пов'язане з сервером YonoHost, тобто ім'я, яким будуть користуватися користувачі вашого сервера, щоб потрапити на портал автентифікації. Воно буде видне кожному, вибирайте його ретельно.",
|
||||
"postinstall_intro_3": "Ви можете отримати більше відомостей, <a href='//yunohost.org/en/install/hardware:vps_debian#fa-cog-proceed-with-the-initial-configuration' target='_blank'>відвідавши сторінку документації</a>",
|
||||
"postinstall_password": "Цей пароль потрібен для того, щоб управляти вашим сервером. Не поспішайте, будьте вигадливими, створюючи його.",
|
||||
"tip_about_user_email": "Користувачі створюються із закріпленою адресою е-пошти (і обліковим записом XMPP) в форматі username@domain.tld. Додаткові псевдоніми та переадресації електронної пошти можуть бути пізніше додані адміністратором і користувачем.",
|
||||
"tools_rebooting": "Ваш сервер перезавантажується. Щоб повернутися до інтерфейсу вебадміністрування, необхідно дочекатися, поки ваш сервер перезавантажиться. Ви можете дочекатися появи форми входу або перевірити, оновивши цю сторінку (F5).",
|
||||
"tip_about_user_email": "Користувачі створюються із закріпленою адресою е-пошти (і обліковим записом XMPP) в форматі username@domain.tld. Додаткові аліаси та переадресації е-пошти можуть бути пізніше додані адміністратором і користувачем.",
|
||||
"tools_rebooting": "Ваш сервер перезавантажується. Щоб повернутися до інтерфейсу вебадміністрації, необхідно дочекатися, поки ваш сервер перезавантажиться. Ви можете дочекатися появи форми входу або перевірити, оновивши цю сторінку (F5).",
|
||||
"tools_shutdown": "Вимкнути цей сервер",
|
||||
"tools_shutdown_done": "Вимикання...",
|
||||
"tools_shuttingdown": "Ваш сервер вимикається. Поки ваш сервер вимкнений, ви не зможете скористатися вебадмініструванням.",
|
||||
"tools_shuttingdown": "Ваш сервер вимикається. Поки ваш сервер вимкнений, ви не зможете користуватися вебадміністрацією.",
|
||||
"unmaintained_details": "Цей застосунок не оновлювався досить довгий час, а попередній супроводжувач пішов або у нього немає часу підтримувати цей застосунок. Не соромтеся перевірити репозиторій застосунків, щоб допомогти з цим",
|
||||
"user_mailbox_quota": "Квота поштової скриньки",
|
||||
"users": "Користувачі",
|
||||
|
@ -524,5 +536,39 @@
|
|||
"confirm_cert_revert_to_selfsigned": "Ви справді бажаєте повернути цей домен до самопідписаного сертифікату безпеки?",
|
||||
"domain_not_eligible_for_ACME": "Цей домен, схоже, не готовий для сертифіката безпеки Let's Encrypt. Будь ласка, перевірте конфігурацію DNS і досяжність HTTP-сервера. Розділи 'DNS-записи' і 'Мережа' на <a href='#/diagnosis'>сторінці діагностики</a> можуть допомогти вам зрозуміти, що саме неправильно сконфігуровано.",
|
||||
"purge_user_data_checkbox": "Очистити дані {name}? (це видалить вміст його домівки та поштових каталогів).",
|
||||
"purge_user_data_warning": "Очищення даних користувача не є відновлюваним. Будьте впевнені, що ви знаєте, що робите!"
|
||||
"purge_user_data_warning": "Очищення даних користувача не є відновлюваним. Будьте впевнені, що ви знаєте, що робите!",
|
||||
"users_import": "Імпорт користувачів",
|
||||
"users_export": "Експорт користувачів",
|
||||
"users_import_csv_file": "Файл CSV",
|
||||
"users_import_update": "Оновити наявних користувачів",
|
||||
"users_import_update_desc": "Якщо встановити прапорець, усі наявні користувачі, що містяться у файлі CSV, будуть оновлені новими значеннями",
|
||||
"users_import_confirm_destructive": "Ви впевнені, що хочете видалити користувачів, яких немає у цьому файлі?",
|
||||
"users_import_delete_others": "Видалити не перелічених користувачів",
|
||||
"users_import_delete": "Видалити не перелічених користувачів",
|
||||
"users_import_csv_file_desc": "Файл CSV має бути у кодуванні UTF-8 і містити стовпці з іменем користувача, паролем, групами, е-поштою та квотою. Для прикладу імпорту файла CSV, ви можете <a href='/yunohost/api/users/export' target='_BLANK'>експортувати своїх користувачів у файл CSV</a> та змінити дані файлу.",
|
||||
"users_import_delete_desc": "Якщо встановити прапорець, усі наявні користувачі, яких немає у файлі CSV, будуть видалені (і очищені).",
|
||||
"yunohost_admin": "Вебадміністрація YunoHost",
|
||||
"domain": {
|
||||
"config": {
|
||||
"edit": "Редагувати конфігурацію домена",
|
||||
"title": "Конфігурація домена"
|
||||
},
|
||||
"dns": {
|
||||
"auto_config": "Автоматична конфігурація записів DNS",
|
||||
"auto_config_ignored": "нехтується, не буде змінено YunoHost, якщо ви не встановите опцію перезапису",
|
||||
"auto_config_ok": "Здається, автоматична конфігурація в порядку!",
|
||||
"auto_config_zone": "Поточна зона DNS",
|
||||
"edit": "Редагувати конфігурацію DNS",
|
||||
"manual_config": "Пропоновані записи DNS для налаштування вручну",
|
||||
"push": "Передати записи DNS реєстратору",
|
||||
"push_force": "Перезаписати наявні записи",
|
||||
"push_force_warning": "Схоже, деякі записи DNS, які б встановив YunoHost, вже є у конфігурації реєстратора. Ви можете використовувати опцію перезапису, якщо знаєте, що робите.",
|
||||
"info": "Автоматичне налаштування записів DNS є експериментальною можливістю. <br> Подумайте про збереження вашої поточної зони DNS з інтерфейсу реєстратора DNS, перш ніж надсилати записи звідси.",
|
||||
"push_force_confirm": "Ви впевнені, що хочете примусово передати всі запропоновані записи DNS? Майте на увазі, що це може перезаписати вручну записані або типові важливі записи, встановлені вами або вашим реєстратором."
|
||||
}
|
||||
},
|
||||
"domain_dns_push_managed_in_parent_domain": "Функцією автоматичних записів DNS керує батьківський домен <a href='#/domains/{parent_domain}/dns'>{parent_domain}</a>.",
|
||||
"domain_dns_push_failed_to_authenticate": "Не вдалося автентифікуватися API реєстратора. Швидше за все, <a href='#/domains/{domain}/config'> облікові дані</a> неправильні? (Помилка: {error})",
|
||||
"domain_dns_push_not_applicable": "Автоматична функція DNS-записів не застосовується до домену {domain}, ви повинні вручну налаштувати записи DNS, що слідують за <a href='https://yunohost.org/dns'> документацією</a> та запропонованою конфігурацією",
|
||||
"text_selection_is_disabled": "Виділення тексту вимкнено. Якщо ви хочете поділитися цим журналом, надішліть журнал *повний* за допомогою кнопки \"Поділитися через Yunopaste\".<br/><small>Або якщо ви дійсно хочете виділити текст, натисніть ці клавіші: ↓↓↑↑.</small>"
|
||||
}
|
||||
|
|
|
@ -101,14 +101,16 @@
|
|||
"notInUsers": "用户'{value}'已经存在。",
|
||||
"minValue": "值必须是等于或大于{min}的数字。",
|
||||
"name": "名称中不能包含特殊字符,除了<code>,.'-</code>",
|
||||
"githubLink": "网址必须是指向存储库的有效Github链接",
|
||||
"githubLink": "网址必须是指向存储库的有效GitHub链接",
|
||||
"emailForward": "无效的电子邮件转发:只能为字母数字和<code> _.- +</code>(例如,someone + tag @ example.com,s0me-1 + tag @ example.com)",
|
||||
"email": "无效的电子邮件:只能为字母数字和<code>_.-</code>字符(例如,someone @ example.com,s0me-1 @ example.com)",
|
||||
"domain": "无效的域名:必须为小写字母数字,点和破折号",
|
||||
"dynDomain": "无效的域名:只能为小写字母数字和破折号",
|
||||
"between": "值必须介于{min}和{max}之间。",
|
||||
"alphalownum_": "值必须为小写字母数字和下划线字符。",
|
||||
"alpha": "值必须是按字母顺序排列的字符。"
|
||||
"alpha": "值必须是按字母顺序排列的字符。",
|
||||
"remote": "{message}",
|
||||
"pattern": "{type}"
|
||||
},
|
||||
"footer": {
|
||||
"donate": "赞助",
|
||||
|
|
|
@ -37,9 +37,19 @@ export default {
|
|||
eu: {
|
||||
name: 'Euskara'
|
||||
},
|
||||
fa: {
|
||||
name: 'فارسی',
|
||||
dateFnsLocale: 'fa-IR'
|
||||
},
|
||||
fi: {
|
||||
name: 'Suomi'
|
||||
},
|
||||
fr: {
|
||||
name: 'Français'
|
||||
},
|
||||
gl: {
|
||||
name: 'Galego'
|
||||
},
|
||||
hi: {
|
||||
name: 'हिन्दी'
|
||||
},
|
||||
|
@ -79,6 +89,9 @@ export default {
|
|||
tr: {
|
||||
name: 'Türkçe'
|
||||
},
|
||||
uk: {
|
||||
name: 'Українська'
|
||||
},
|
||||
zh_Hans: {
|
||||
name: '简化字',
|
||||
dateFnsLocale: 'zh-CN'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import BootstrapVue from 'bootstrap-vue'
|
||||
import VueShowdown from 'vue-showdown'
|
||||
|
||||
import i18n from './i18n'
|
||||
import router from './router'
|
||||
|
@ -11,7 +12,6 @@ import { registerGlobalErrorHandlers } from './api'
|
|||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
|
||||
// Styles are imported in `src/App.vue` <style>
|
||||
Vue.use(BootstrapVue, {
|
||||
BSkeleton: { animation: 'none' },
|
||||
|
@ -24,6 +24,11 @@ Vue.use(BootstrapVue, {
|
|||
}
|
||||
})
|
||||
|
||||
Vue.use(VueShowdown, {
|
||||
options: {
|
||||
emoji: true
|
||||
}
|
||||
})
|
||||
|
||||
// Ugly wrapper for `$bvModal.msgBoxConfirm` to set default i18n button titles
|
||||
// FIXME find or wait for a better way
|
||||
|
@ -46,13 +51,14 @@ requireComponent.keys().forEach((fileName) => {
|
|||
Vue.component(component.name, component)
|
||||
})
|
||||
|
||||
|
||||
registerGlobalErrorHandlers()
|
||||
|
||||
|
||||
new Vue({
|
||||
const app = new Vue({
|
||||
i18n,
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
})
|
||||
|
||||
app.$mount('#app')
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import i18n from '@/i18n'
|
||||
import routes from './routes'
|
||||
import store from '@/store'
|
||||
|
||||
|
@ -39,4 +40,30 @@ router.beforeEach((to, from, next) => {
|
|||
}
|
||||
})
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
// Display a simplified breadcrumb as the document title.
|
||||
const routeParams = to.params
|
||||
let breadcrumb = to.meta.breadcrumb
|
||||
if (breadcrumb.length === 0) {
|
||||
breadcrumb = [to.name]
|
||||
} else if (breadcrumb.length > 2) {
|
||||
breadcrumb = breadcrumb.slice(breadcrumb.length - 2)
|
||||
}
|
||||
|
||||
const title = breadcrumb.map(name => {
|
||||
const route = routes.find(route => route.name === name)
|
||||
const { trad, param } = route ? route.meta.args : {}
|
||||
// if a traduction key string has been given and we also need to pass
|
||||
// the route param as a variable.
|
||||
if (trad && param) {
|
||||
return i18n.t(trad, { [param]: routeParams[param] })
|
||||
} else if (trad) {
|
||||
return i18n.t(trad)
|
||||
}
|
||||
return routeParams[param]
|
||||
}).reverse().join(' / ')
|
||||
|
||||
document.title = `${title} | ${i18n.t('yunohost_admin')}`
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
@ -18,14 +18,21 @@ const routes = [
|
|||
path: '/',
|
||||
component: Home,
|
||||
// Leave the empty breadcrumb as it is used by the animated transition to know which way to go
|
||||
meta: { breadcrumb: [] }
|
||||
meta: {
|
||||
args: { trad: 'home' },
|
||||
breadcrumb: []
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'login',
|
||||
path: '/login',
|
||||
component: Login,
|
||||
meta: { noAuth: true, breadcrumb: [] }
|
||||
meta: {
|
||||
noAuth: true,
|
||||
args: { trad: 'login' },
|
||||
breadcrumb: []
|
||||
}
|
||||
},
|
||||
|
||||
/* ───────────────╮
|
||||
|
@ -36,7 +43,11 @@ const routes = [
|
|||
path: '/postinstall',
|
||||
component: () => import(/* webpackChunkName: "views/post-install" */ '@/views/PostInstall'),
|
||||
// Leave the breadcrumb
|
||||
meta: { noAuth: true, breadcrumb: [] }
|
||||
meta: {
|
||||
noAuth: true,
|
||||
args: { trad: 'postinstall.title' },
|
||||
breadcrumb: []
|
||||
}
|
||||
},
|
||||
|
||||
/* ───────╮
|
||||
|
@ -60,6 +71,16 @@ const routes = [
|
|||
breadcrumb: ['user-list', 'user-create']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'user-import',
|
||||
path: '/users/import',
|
||||
component: () => import(/* webpackChunkName: "views/user/import" */ '@/views/user/UserImport'),
|
||||
props: true,
|
||||
meta: {
|
||||
args: { param: 'name' },
|
||||
breadcrumb: ['user-list', 'user-import']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'user-info',
|
||||
path: '/users/:name',
|
||||
|
@ -134,6 +155,16 @@ const routes = [
|
|||
breadcrumb: ['domain-list', 'domain-info']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'domain-config',
|
||||
path: '/domains/:name/config',
|
||||
component: () => import(/* webpackChunkName: "views/domain/dns" */ '@/views/domain/DomainConfig'),
|
||||
props: true,
|
||||
meta: {
|
||||
args: { trad: 'config' },
|
||||
breadcrumb: ['domain-list', 'domain-info', 'domain-config']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'domain-dns',
|
||||
path: '/domains/:name/dns',
|
||||
|
|
|
@ -112,11 +112,52 @@ body {
|
|||
top: 2px;
|
||||
}
|
||||
|
||||
// limit the size of toggle dropdown buttons to a square
|
||||
.dropdown-toggle-split {
|
||||
max-width: 2.5rem;
|
||||
}
|
||||
|
||||
// Fork-awesome overrides
|
||||
.fa-fw {
|
||||
width: 1.25em !important;
|
||||
}
|
||||
|
||||
// custom css
|
||||
.actions {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
.buttons {
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
flex-direction: column-reverse;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.btn {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.btn ~ .btn {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
.btn ~ .dropdown-toggle-split {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do not display ligatures in invalid-feedback to avoid confusion.
|
||||
.invalid-feedback {
|
||||
code {
|
||||
|
@ -132,4 +173,13 @@ code {
|
|||
margin-bottom: 0;
|
||||
padding: 1rem;
|
||||
background-color: $light;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.unselectable {
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
|
@ -146,6 +146,16 @@ export default {
|
|||
return api.fetch('DELETE', param ? `${uri}/${param}` : uri, data, humanKey, options).then(() => {
|
||||
commit('DEL_' + storeKey.toUpperCase(), [param, extraParams].filter(item => !isEmptyValue(item)))
|
||||
})
|
||||
},
|
||||
|
||||
'RESET_CACHE_DATA' ({ state }, keys = Object.keys(state)) {
|
||||
for (const key of keys) {
|
||||
if (key === 'users_details') {
|
||||
state[key] = {}
|
||||
} else {
|
||||
state[key] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -12,7 +12,9 @@ export default {
|
|||
waiting: false, // Boolean
|
||||
history: [], // Array of `request`
|
||||
requests: [], // Array of `request`
|
||||
error: null // null || request
|
||||
error: null, // null || request
|
||||
historyTimer: null, // null || setTimeout id
|
||||
tempMessages: [] // array of messages
|
||||
},
|
||||
|
||||
mutations: {
|
||||
|
@ -52,12 +54,26 @@ export default {
|
|||
state.history.push(request)
|
||||
},
|
||||
|
||||
'ADD_MESSAGE' (state, { message, type }) {
|
||||
const request = state.history[state.history.length - 1]
|
||||
request.messages.push(message)
|
||||
if (['error', 'warning'].includes(type)) {
|
||||
request[type + 's']++
|
||||
'ADD_TEMP_MESSAGE' (state, { request, message, type }) {
|
||||
state.tempMessages.push([message, type])
|
||||
},
|
||||
|
||||
'UPDATE_DISPLAYED_MESSAGES' (state, { request }) {
|
||||
if (!state.tempMessages.length) {
|
||||
state.historyTimer = null
|
||||
return
|
||||
}
|
||||
|
||||
const { messages, warnings, errors } = state.tempMessages.reduce((acc, [message, type]) => {
|
||||
acc.messages.push(message)
|
||||
if (['error', 'warning'].includes(type)) acc[type + 's']++
|
||||
return acc
|
||||
}, { messages: [], warnings: 0, errors: 0 })
|
||||
state.tempMessages = []
|
||||
state.historyTimer = null
|
||||
request.messages = request.messages.concat(messages)
|
||||
request.warnings += warnings
|
||||
request.errors += errors
|
||||
},
|
||||
|
||||
'SET_ERROR' (state, request) {
|
||||
|
@ -147,7 +163,11 @@ export default {
|
|||
return request
|
||||
},
|
||||
|
||||
'END_REQUEST' ({ commit }, { request, success, wait }) {
|
||||
'END_REQUEST' ({ state, commit }, { request, success, wait }) {
|
||||
// Update last messages before finishing this request
|
||||
clearTimeout(state.historyTimer)
|
||||
commit('UPDATE_DISPLAYED_MESSAGES', { request })
|
||||
|
||||
let status = success ? 'success' : 'error'
|
||||
if (success && (request.warnings || request.errors)) {
|
||||
const messages = request.messages
|
||||
|
@ -166,7 +186,7 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
'DISPATCH_MESSAGE' ({ commit }, { request, messages }) {
|
||||
'DISPATCH_MESSAGE' ({ state, commit, dispatch }, { request, messages }) {
|
||||
for (const type in messages) {
|
||||
const message = {
|
||||
text: messages[type].replace('\n', '<br>'),
|
||||
|
@ -183,7 +203,13 @@ export default {
|
|||
commit('UPDATE_REQUEST', { request, key: 'progress', value: Object.values(progress) })
|
||||
}
|
||||
if (message.text) {
|
||||
commit('ADD_MESSAGE', { request, message, type })
|
||||
// To avoid rendering lag issues, limit the flow of websocket messages to batches of 50ms.
|
||||
if (state.historyTimer === null) {
|
||||
state.historyTimer = setTimeout(() => {
|
||||
commit('UPDATE_DISPLAYED_MESSAGES', { request })
|
||||
}, 50)
|
||||
}
|
||||
commit('ADD_TEMP_MESSAGE', { request, message, type })
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
@shown="scrollToAction(i)"
|
||||
@hide="scrollToAction(i)"
|
||||
>
|
||||
<message-list-group :messages="action.messages" flush />
|
||||
<message-list-group :messages="action.messages" />
|
||||
</b-collapse>
|
||||
</b-card>
|
||||
</div>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<message-list-group
|
||||
v-if="hasMessages" :messages="request.messages"
|
||||
bordered fixed-height auto-scroll
|
||||
:limit="100"
|
||||
/>
|
||||
</b-card-body>
|
||||
</template>
|
||||
|
|
|
@ -15,14 +15,6 @@
|
|||
:validation="$v.actions[i]" :id="action.id + '-form'" :server-error="action.serverError"
|
||||
@submit.prevent="performAction(action)" :submit-text="$t('perform')"
|
||||
>
|
||||
<template #disclaimer>
|
||||
<div
|
||||
v-if="action.formDisclaimer"
|
||||
class="alert alert-info" v-html="action.formDisclaimer"
|
||||
/>
|
||||
<b-card-text v-if="action.description" v-html="action.description" />
|
||||
</template>
|
||||
|
||||
<form-field
|
||||
v-for="(field, fname) in action.fields" :key="fname" label-cols="0"
|
||||
v-bind="field" v-model="action.form[fname]" :validation="$v.actions[i][fname]"
|
||||
|
@ -85,11 +77,10 @@ export default {
|
|||
const action = { name, id, serverError: '' }
|
||||
if (description) action.description = formatI18nField(description)
|
||||
if (arguments_ && arguments_.length) {
|
||||
const { form, fields, validations, disclaimer } = formatYunoHostArguments(arguments_)
|
||||
const { form, fields, validations } = formatYunoHostArguments(arguments_)
|
||||
action.form = form
|
||||
action.fields = fields
|
||||
if (validations) action.validations = validations
|
||||
if (disclaimer) action.formDisclaimer = disclaimer
|
||||
}
|
||||
return action
|
||||
})
|
||||
|
|
|
@ -64,52 +64,56 @@
|
|||
|
||||
<!-- APPS CARDS -->
|
||||
<b-card-group v-else deck>
|
||||
<b-card no-body v-for="app in filteredApps" :key="app.id">
|
||||
<b-card-body class="d-flex flex-column">
|
||||
<b-card-title class="d-flex mb-2">
|
||||
{{ app.manifest.name }}
|
||||
<small v-if="app.state !== 'working'" class="d-flex align-items-center ml-2">
|
||||
<b-badge
|
||||
v-if="app.state !== 'highquality'"
|
||||
:variant="(app.color === 'danger' && app.state === 'lowquality') ? 'warning' : app.color"
|
||||
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
||||
>
|
||||
{{ $t('app_state_' + app.state) }}
|
||||
</b-badge>
|
||||
<icon
|
||||
v-else iname="star" class="star"
|
||||
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
||||
/>
|
||||
</small>
|
||||
</b-card-title>
|
||||
<lazy-renderer v-for="app in filteredApps" :key="app.id" :min-height="120">
|
||||
<b-card no-body>
|
||||
<b-card-body class="d-flex flex-column">
|
||||
<b-card-title class="d-flex mb-2">
|
||||
{{ app.manifest.name }}
|
||||
|
||||
<b-card-text>{{ app.manifest.description }}</b-card-text>
|
||||
<small v-if="app.state !== 'working'" class="d-flex align-items-center ml-2">
|
||||
<b-badge
|
||||
v-if="app.state !== 'highquality'"
|
||||
:variant="(app.color === 'danger' && app.state === 'lowquality') ? 'warning' : app.color"
|
||||
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
||||
>
|
||||
{{ $t('app_state_' + app.state) }}
|
||||
</b-badge>
|
||||
|
||||
<b-card-text v-if="app.maintained === 'orphaned'" class="align-self-end mt-auto">
|
||||
<span class="alert-warning p-1" v-b-popover.hover.top="$t('orphaned_details')">
|
||||
<icon iname="warning" /> {{ $t(app.maintained) }}
|
||||
</span>
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
<icon
|
||||
v-else iname="star" class="star"
|
||||
v-b-popover.hover.bottom="$t(`app_state_${app.state}_explanation`)"
|
||||
/>
|
||||
</small>
|
||||
</b-card-title>
|
||||
|
||||
<!-- APP BUTTONS -->
|
||||
<b-button-group>
|
||||
<b-button :href="app.git.url" variant="outline-dark" target="_blank">
|
||||
<icon iname="code" /> {{ $t('code') }}
|
||||
</b-button>
|
||||
<b-card-text>{{ app.manifest.description }}</b-card-text>
|
||||
|
||||
<b-button :href="app.git.url + '/blob/master/README.md'" variant="outline-dark" target="_blank">
|
||||
<icon iname="book" /> {{ $t('readme') }}
|
||||
</b-button>
|
||||
<b-card-text v-if="app.maintained === 'orphaned'" class="align-self-end mt-auto">
|
||||
<span class="alert-warning p-1" v-b-popover.hover.top="$t('orphaned_details')">
|
||||
<icon iname="warning" /> {{ $t(app.maintained) }}
|
||||
</span>
|
||||
</b-card-text>
|
||||
</b-card-body>
|
||||
|
||||
<b-button v-if="app.isInstallable" :variant="app.color" @click="onInstallClick(app)">
|
||||
<icon iname="plus" /> {{ $t('install') }} <icon v-if="app.color === 'danger'" class="ml-1" iname="warning" />
|
||||
</b-button>
|
||||
<b-button v-else :variant="app.color" disabled>
|
||||
{{ $t('installed') }}
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
</b-card>
|
||||
<!-- APP BUTTONS -->
|
||||
<b-button-group>
|
||||
<b-button :href="app.git.url" variant="outline-dark" target="_blank">
|
||||
<icon iname="code" /> {{ $t('code') }}
|
||||
</b-button>
|
||||
|
||||
<b-button :href="app.git.url + '/blob/master/README.md'" variant="outline-dark" target="_blank">
|
||||
<icon iname="book" /> {{ $t('readme') }}
|
||||
</b-button>
|
||||
|
||||
<b-button v-if="app.isInstallable" :variant="app.color" @click="onInstallClick(app)">
|
||||
<icon iname="plus" /> {{ $t('install') }} <icon v-if="app.color === 'danger'" class="ml-1" iname="warning" />
|
||||
</b-button>
|
||||
<b-button v-else :variant="app.color" disabled>
|
||||
{{ $t('installed') }}
|
||||
</b-button>
|
||||
</b-button-group>
|
||||
</b-card>
|
||||
</lazy-renderer>
|
||||
</b-card-group>
|
||||
|
||||
<template #bot>
|
||||
|
@ -155,12 +159,18 @@
|
|||
<script>
|
||||
import { validationMixin } from 'vuelidate'
|
||||
|
||||
import { required, githubLink } from '@/helpers/validators'
|
||||
import LazyRenderer from '@/components/LazyRenderer'
|
||||
import { required, appRepoUrl } from '@/helpers/validators'
|
||||
|
||||
import { randint } from '@/helpers/commons'
|
||||
|
||||
export default {
|
||||
name: 'AppCatalog',
|
||||
|
||||
components: {
|
||||
LazyRenderer
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
queries: [
|
||||
|
@ -249,7 +259,7 @@ export default {
|
|||
|
||||
validations: {
|
||||
customInstall: {
|
||||
url: { required, githubLink }
|
||||
url: { required, appRepoUrl }
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -375,10 +385,11 @@ export default {
|
|||
}
|
||||
|
||||
.card-deck {
|
||||
.card {
|
||||
border-color: $gray-400;
|
||||
> * {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 2rem;
|
||||
flex-basis: 90%;
|
||||
flex-basis: 100%;
|
||||
@include media-breakpoint-up(md) {
|
||||
flex-basis: 50%;
|
||||
max-width: calc(50% - 30px);
|
||||
|
@ -388,6 +399,15 @@ export default {
|
|||
max-width: calc(33.3% - 30px);
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-color: $gray-400;
|
||||
|
||||
// not maintained info
|
||||
.alert-warning {
|
||||
font-size: .75em;
|
||||
|
@ -402,6 +422,8 @@ export default {
|
|||
@include media-breakpoint-up(sm) {
|
||||
min-height: 10rem;
|
||||
}
|
||||
|
||||
flex-basis: 90%;
|
||||
border: 0;
|
||||
|
||||
.btn {
|
||||
|
|
|
@ -1,30 +1,40 @@
|
|||
<template>
|
||||
<view-base :queries="queries" @queries-response="onQueriesResponse" skeleton="card-form-skeleton">
|
||||
<template v-if="panels" #default>
|
||||
<b-alert variant="warning" class="mb-4">
|
||||
<icon iname="exclamation-triangle" /> {{ $t('experimental_warning') }}
|
||||
</b-alert>
|
||||
<b-tabs pills card vertical>
|
||||
<b-tab v-for="{ name, id: id_, sections, help, serverError } in panels"
|
||||
:key="id_"
|
||||
:title="name"
|
||||
>
|
||||
<template #title>
|
||||
<icon iname="wrench" /> {{ name }}
|
||||
</template>
|
||||
<card-form
|
||||
:key="id_"
|
||||
:title="name" icon="wrench" title-tag="h2"
|
||||
:validation="$v.forms[id_]" :id="id_ + '-form'" :server-error="serverError"
|
||||
@submit.prevent="applyConfig(id_)"
|
||||
>
|
||||
<template v-if="help" #disclaimer>
|
||||
<div class="alert alert-info" v-html="help" />
|
||||
</template>
|
||||
|
||||
<card-form
|
||||
v-for="{ name, id: id_, sections, help, serverError } in panels" :key="id_"
|
||||
:title="name" icon="wrench" title-tag="h4"
|
||||
:validation="$v.forms[id_]" :id="id_ + '-form'" :server-error="serverError"
|
||||
collapsable
|
||||
@submit.prevent="applyConfig(id_)"
|
||||
>
|
||||
<template v-if="help" #disclaimer>
|
||||
<div class="alert alert-info" v-html="help" />
|
||||
</template>
|
||||
|
||||
<div v-for="section in sections" :key="section.id" class="mb-5">
|
||||
<b-card-title>{{ section.name }} <small v-if="section.help">{{ section.help }}</small></b-card-title>
|
||||
|
||||
<form-field
|
||||
v-for="(field, fname) in section.fields" :key="fname" label-cols="0"
|
||||
v-bind="field" v-model="forms[id_][fname]" :validation="$v.forms[id_][fname]"
|
||||
/>
|
||||
</div>
|
||||
</card-form>
|
||||
<template v-for="section in sections">
|
||||
<div :key="section.id" class="mb-5" v-if="isVisible(section.visible, section)">
|
||||
<b-card-title v-if="section.name" title-tag="h3">
|
||||
{{ section.name }} <small v-if="section.help">{{ section.help }}</small>
|
||||
</b-card-title>
|
||||
<template v-for="(field, fname) in section.fields">
|
||||
<form-field :key="fname" v-model="forms[id_][fname]"
|
||||
:validation="$v.forms[id_][fname]"
|
||||
v-if="isVisible(field.visible, field)" v-bind="field"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</card-form>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</template>
|
||||
|
||||
<!-- if no config panel -->
|
||||
|
@ -36,10 +46,11 @@
|
|||
|
||||
<script>
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import evaluate from 'simple-evaluate'
|
||||
|
||||
// FIXME needs test and rework
|
||||
import api, { objectToParams } from '@/api'
|
||||
import { formatI18nField, formatYunoHostArguments, formatFormData } from '@/helpers/yunohostArguments'
|
||||
import { formatI18nField, formatYunoHostArguments, formatFormData, pFileReader } from '@/helpers/yunohostArguments'
|
||||
|
||||
export default {
|
||||
name: 'AppConfigPanel',
|
||||
|
@ -53,13 +64,14 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
queries: [
|
||||
['GET', `apps/${this.id}/config-panel`],
|
||||
['GET', `apps/${this.id}/config-panel?full`],
|
||||
['GET', { uri: 'domains' }],
|
||||
['GET', { uri: 'domains/main', storeKey: 'main_domain' }],
|
||||
['GET', { uri: 'users' }]
|
||||
],
|
||||
panels: undefined,
|
||||
forms: undefined,
|
||||
errors: undefined,
|
||||
validations: null
|
||||
}
|
||||
},
|
||||
|
@ -69,27 +81,69 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
isVisible (expression, field) {
|
||||
if (!expression || !field) return true
|
||||
const context = {}
|
||||
|
||||
const promises = []
|
||||
for (const args of Object.values(this.forms)) {
|
||||
for (const shortname in args) {
|
||||
if (args[shortname] instanceof File) {
|
||||
if (expression.includes(shortname)) {
|
||||
promises.push(pFileReader(args[shortname], context, shortname, false))
|
||||
}
|
||||
} else {
|
||||
context[shortname] = args[shortname]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Allow to use match(var,regexp) function
|
||||
const matchRe = new RegExp('match\\(\\s*(\\w+)\\s*,\\s*"([^"]+)"\\s*\\)', 'g')
|
||||
let i = 0
|
||||
Promise.all(promises).then((value) => {
|
||||
for (const matched of expression.matchAll(matchRe)) {
|
||||
i++
|
||||
const varName = matched[1] + '__re' + i.toString()
|
||||
context[varName] = new RegExp(matched[2], 'm').test(context[matched[1]])
|
||||
expression = expression.replace(matched[0], varName)
|
||||
}
|
||||
|
||||
try {
|
||||
field.isVisible = evaluate(context, expression)
|
||||
} catch (error) {
|
||||
field.isVisible = false
|
||||
}
|
||||
})
|
||||
// This value should be updated magically when vuejs will detect isVisible changed
|
||||
return field.isVisible
|
||||
},
|
||||
onQueriesResponse (data) {
|
||||
if (!data.config_panel || data.config_panel.length === 0) {
|
||||
if (!data.panels || data.panels.length === 0) {
|
||||
this.panels = null
|
||||
return
|
||||
}
|
||||
|
||||
const forms = {}
|
||||
const validations_ = {}
|
||||
const errors_ = {}
|
||||
const panels_ = []
|
||||
for (const { id, name, help, sections } of data.config_panel.panel) {
|
||||
const panel_ = { id, name, sections: [] }
|
||||
for (const { id, name, help, sections } of data.panels) {
|
||||
const panel_ = { id, sections: [] }
|
||||
if (name) panel_.name = formatI18nField(name)
|
||||
if (help) panel_.help = formatI18nField(help)
|
||||
forms[id] = {}
|
||||
validations_[id] = {}
|
||||
for (const { name, help, options } of sections) {
|
||||
const section_ = { name }
|
||||
errors_[id] = {}
|
||||
for (const { id_, name, help, visible, options } of sections) {
|
||||
const section_ = { id: id_, isVisible: true, visible }
|
||||
if (help) section_.help = formatI18nField(help)
|
||||
const { form, fields, validations } = formatYunoHostArguments(options)
|
||||
if (name) section_.name = formatI18nField(name)
|
||||
const { form, fields, validations, errors } = formatYunoHostArguments(options)
|
||||
Object.assign(forms[id], form)
|
||||
Object.assign(validations_[id], validations)
|
||||
panel_.sections.push({ name, fields })
|
||||
Object.assign(errors_[id], errors)
|
||||
section_.fields = fields
|
||||
panel_.sections.push(section_)
|
||||
}
|
||||
panels_.push(panel_)
|
||||
}
|
||||
|
@ -97,23 +151,43 @@ export default {
|
|||
this.forms = forms
|
||||
this.validations = { forms: validations_ }
|
||||
this.panels = panels_
|
||||
this.errors = errors_
|
||||
},
|
||||
|
||||
applyConfig (id_) {
|
||||
const args = objectToParams(formatFormData(this.forms[id_]))
|
||||
formatFormData(this.forms[id_], { removeEmpty: false, removeNull: true, multipart: false }).then((formatedData) => {
|
||||
const args = objectToParams(formatedData)
|
||||
|
||||
api.put(
|
||||
`apps/${this.id}/config`, { args }, { key: 'apps.update_config', name: this.id }
|
||||
).then(response => {
|
||||
// FIXME what should be done ?
|
||||
/* eslint-disable-next-line */
|
||||
console.log('SUCCESS', response)
|
||||
}).catch(err => {
|
||||
if (err.name !== 'APIBadRequestError') throw err
|
||||
const panel = this.panels.find(({ id }) => id_ === id)
|
||||
this.$set(panel, 'serverError', err.message)
|
||||
api.put(
|
||||
`apps/${this.id}/config`, { key: id_, args }, { key: 'apps.update_config', name: this.id }
|
||||
).then(response => {
|
||||
api.get(
|
||||
`apps/${this.id}/config-panel?full`, {}, { key: 'apps.get_config', name: this.id }
|
||||
).then(response => {
|
||||
this.onQueriesResponse(response)
|
||||
}).catch(err => {
|
||||
if (err.name !== 'APIBadRequestError') throw err
|
||||
const panel = this.panels.find(({ id }) => id_ === id)
|
||||
this.$set(panel, 'serverError', err.message)
|
||||
})
|
||||
}).catch(err => {
|
||||
if (err.name !== 'APIBadRequestError') throw err
|
||||
const panel = this.panels.find(({ id }) => id_ === id)
|
||||
if (err.data.name) {
|
||||
this.errors[id_][err.data.name].message = err.message
|
||||
} else this.$set(panel, 'serverError', err.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
h3.card-title {
|
||||
margin-bottom: 1em;
|
||||
border-bottom: solid 1px #aaa;
|
||||
}
|
||||
.form-control::placeholder, .form-file-text {
|
||||
color: #6d7780;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
<b-form-group
|
||||
:label="$t('app_info_changeurl_desc')" label-for="input-url"
|
||||
:label-cols-lg="app.supports_change_url ? 0 : 0" label-class="font-weight-bold"
|
||||
v-if="app.is_webapp"
|
||||
>
|
||||
<b-input-group v-if="app.supports_change_url">
|
||||
<b-input-group-prepend is-text>
|
||||
|
@ -98,18 +99,32 @@
|
|||
<icon iname="exclamation" /> {{ $t('app_info_change_url_disabled_tooltip') }}
|
||||
</div>
|
||||
</b-form-group>
|
||||
<hr>
|
||||
<hr v-if="app.is_webapp">
|
||||
|
||||
<!-- CHANGE DOMAIN -->
|
||||
<b-form-group
|
||||
:label="$t('app_info_default_desc', { domain: app.domain })" label-for="main-domain"
|
||||
label-class="font-weight-bold" label-cols-md="4"
|
||||
v-if="app.is_webapp"
|
||||
>
|
||||
<b-button @click="setAsDefaultDomain" id="main-domain" variant="success">
|
||||
<icon iname="star" /> {{ $t('app_make_default') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
<hr>
|
||||
<hr v-if="app.is_webapp">
|
||||
|
||||
<!-- APP CONFIG PANEL -->
|
||||
<template v-if="app.supports_config_panel">
|
||||
<b-form-group
|
||||
:label="$t('app_config_panel_label')" label-for="config"
|
||||
label-cols-md="4" label-class="font-weight-bold"
|
||||
>
|
||||
<b-button id="config" variant="warning" :to="{ name: 'app-config-panel', params: { id } }">
|
||||
<icon iname="cog" /> {{ $t('app_config_panel') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
<hr>
|
||||
</template>
|
||||
|
||||
<!-- UNINSTALL -->
|
||||
<b-form-group
|
||||
|
@ -133,17 +148,6 @@
|
|||
<icon iname="flask" /> {{ $t('app_actions') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
<hr>
|
||||
|
||||
<!-- APP CONFIG PANEL -->
|
||||
<b-form-group
|
||||
:label="$t('app_config_panel_label')" label-for="config"
|
||||
label-cols-md="4" label-class="font-weight-bold"
|
||||
>
|
||||
<b-button id="config" variant="warning" :to="{ name: 'app-config-panel', params: { id } }">
|
||||
<icon iname="flask" /> {{ $t('app_config_panel') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
</card>
|
||||
|
||||
<template #skeleton>
|
||||
|
@ -245,7 +249,9 @@ export default {
|
|||
this.form = form
|
||||
this.app = {
|
||||
domain: app.settings.domain,
|
||||
is_webapp: app.is_webapp,
|
||||
supports_change_url: app.supports_change_url,
|
||||
supports_config_panel: app.supports_config_panel,
|
||||
permissions
|
||||
}
|
||||
},
|
||||
|
|
|
@ -23,14 +23,13 @@
|
|||
:validation="$v" :server-error="serverError"
|
||||
@submit.prevent="performInstall"
|
||||
>
|
||||
<template v-if="formDisclaimer" #disclaimer>
|
||||
<div class="alert alert-info" v-html="formDisclaimer" />
|
||||
<template v-for="(field, fname) in fields">
|
||||
<form-field
|
||||
v-if="isVisible(field.visible, field)"
|
||||
:key="fname" label-cols="0"
|
||||
v-bind="field" v-model="form[fname]" :validation="$v.form[fname]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<form-field
|
||||
v-for="(field, fname) in fields" :key="fname" label-cols="0"
|
||||
v-bind="field" v-model="form[fname]" :validation="$v.form[fname]"
|
||||
/>
|
||||
</card-form>
|
||||
</template>
|
||||
|
||||
|
@ -48,9 +47,10 @@
|
|||
|
||||
<script>
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import evaluate from 'simple-evaluate'
|
||||
|
||||
import api, { objectToParams } from '@/api'
|
||||
import { formatYunoHostArguments, formatI18nField, formatFormData } from '@/helpers/yunohostArguments'
|
||||
import { formatYunoHostArguments, formatI18nField, formatFormData, pFileReader } from '@/helpers/yunohostArguments'
|
||||
|
||||
export default {
|
||||
name: 'AppInstall',
|
||||
|
@ -75,6 +75,7 @@ export default {
|
|||
form: undefined,
|
||||
fields: undefined,
|
||||
validations: null,
|
||||
errors: undefined,
|
||||
serverError: ''
|
||||
}
|
||||
},
|
||||
|
@ -94,15 +95,50 @@ export default {
|
|||
manifest.multi_instance = this.$i18n.t(manifest.multi_instance ? 'yes' : 'no')
|
||||
this.infos = Object.fromEntries(infosKeys.map(key => [key, manifest[key]]))
|
||||
|
||||
const { form, fields, validations, disclaimer } = formatYunoHostArguments(
|
||||
const { form, fields, validations, errors } = formatYunoHostArguments(
|
||||
manifest.arguments.install,
|
||||
manifest.name
|
||||
)
|
||||
|
||||
this.formDisclaimer = disclaimer
|
||||
this.fields = fields
|
||||
this.form = form
|
||||
this.validations = { form: validations }
|
||||
this.errors = errors
|
||||
},
|
||||
|
||||
isVisible (expression, field) {
|
||||
if (!expression || !field) return true
|
||||
const context = {}
|
||||
|
||||
const promises = []
|
||||
for (const shortname in this.form) {
|
||||
if (this.form[shortname] instanceof File) {
|
||||
if (expression.includes(shortname)) {
|
||||
promises.push(pFileReader(this.form[shortname], context, shortname, false))
|
||||
}
|
||||
} else {
|
||||
context[shortname] = this.form[shortname]
|
||||
}
|
||||
}
|
||||
// Allow to use match(var,regexp) function
|
||||
const matchRe = new RegExp('match\\(\\s*(\\w+)\\s*,\\s*"([^"]+)"\\s*\\)', 'g')
|
||||
let i = 0
|
||||
Promise.all(promises).then(() => {
|
||||
for (const matched of expression.matchAll(matchRe)) {
|
||||
i++
|
||||
const varName = matched[1] + '__re' + i.toString()
|
||||
context[varName] = new RegExp(matched[2], 'm').test(context[matched[1]])
|
||||
expression = expression.replace(matched[0], varName)
|
||||
}
|
||||
|
||||
try {
|
||||
field.isVisible = evaluate(context, expression)
|
||||
} catch (error) {
|
||||
field.isVisible = false
|
||||
}
|
||||
})
|
||||
// This value should be updated magically when vuejs will detect isVisible changed
|
||||
return field.isVisible
|
||||
},
|
||||
|
||||
async performInstall () {
|
||||
|
@ -113,14 +149,19 @@ export default {
|
|||
if (!confirmed) return
|
||||
}
|
||||
|
||||
const { data: args, label } = formatFormData(this.form, { extract: ['label'] })
|
||||
const { data: args, label } = await formatFormData(
|
||||
this.form,
|
||||
{ extract: ['label'], removeEmpty: false, removeNull: true, multipart: false }
|
||||
)
|
||||
const data = { app: this.id, label, args: Object.entries(args).length ? objectToParams(args) : undefined }
|
||||
|
||||
api.post('apps', data, { key: 'apps.install', name: this.name }).then(() => {
|
||||
this.$router.push({ name: 'app-list' })
|
||||
}).catch(err => {
|
||||
if (err.name !== 'APIBadRequestError') throw err
|
||||
this.serverError = err.message
|
||||
if (err.data.name) {
|
||||
this.errors[err.data.name].message = err.message
|
||||
} else this.serverError = err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<b-alert v-if="!archives" variant="warning">
|
||||
<icon iname="exclamation-triangle" />
|
||||
{{ $t('items_verbose_count', { items: $tc('items.backups', 0) }) }}
|
||||
{{ $tc('items_verbose_count', 0, { items: $tc('items.backups', 0) }) }}
|
||||
</b-alert>
|
||||
|
||||
<b-list-group v-else>
|
||||
|
|
|
@ -201,7 +201,7 @@ export default {
|
|||
},
|
||||
|
||||
shareLogs () {
|
||||
api.get('diagnosis/show?share').then(({ url }) => {
|
||||
api.get('diagnosis?share').then(({ url }) => {
|
||||
window.open(url, '_blank')
|
||||
})
|
||||
}
|
||||
|
|
162
app/src/views/domain/DomainConfig.vue
Normal file
162
app/src/views/domain/DomainConfig.vue
Normal file
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<view-base :queries="queries" @queries-response="onQueriesResponse" skeleton="card-info-skeleton">
|
||||
<b-tabs pills card vertical>
|
||||
<b-tab v-for="{ name, id: id_, sections, help, serverError } in panels"
|
||||
:key="id_"
|
||||
:title="name"
|
||||
>
|
||||
<template #title>
|
||||
<icon iname="wrench" /> {{ name }}
|
||||
</template>
|
||||
<card-form
|
||||
:key="id_"
|
||||
:title="name" icon="wrench" title-tag="h2"
|
||||
:validation="$v.forms[id_]" :id="id_ + '-form'" :server-error="serverError"
|
||||
@submit.prevent="applyConfig(id_)"
|
||||
>
|
||||
<template v-if="help" #disclaimer>
|
||||
<div class="alert alert-info" v-html="help" />
|
||||
</template>
|
||||
|
||||
<template v-for="section in sections">
|
||||
<div :key="section.id" class="mb-5" v-if="isVisible(section.visible, section)">
|
||||
<b-card-title v-if="section.name" title-tag="h3">
|
||||
{{ section.name }} <small v-if="section.help">{{ section.help }}</small>
|
||||
</b-card-title>
|
||||
<template v-for="(field, fname) in section.fields">
|
||||
<form-field :key="fname" v-model="forms[id_][fname]"
|
||||
:validation="$v.forms[id_][fname]"
|
||||
v-if="isVisible(field.visible, field)" v-bind="field"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</card-form>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</view-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import evaluate from 'simple-evaluate'
|
||||
|
||||
import api, { objectToParams } from '@/api'
|
||||
|
||||
import { formatI18nField, formatYunoHostArguments, formatFormData, pFileReader } from '@/helpers/yunohostArguments'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'DomainConfig',
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
props: {
|
||||
name: { type: String, required: true }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
queries: [
|
||||
['GET', `domains/${this.name}/config?full`]
|
||||
],
|
||||
panels: undefined,
|
||||
forms: undefined,
|
||||
errors: undefined,
|
||||
validations: null
|
||||
}
|
||||
},
|
||||
|
||||
validations () {
|
||||
return this.validations
|
||||
},
|
||||
|
||||
methods: {
|
||||
onQueriesResponse (config) {
|
||||
const forms = {}
|
||||
const validations_ = {}
|
||||
const errors_ = {}
|
||||
const panels_ = []
|
||||
for (const { id, name, help, sections } of config.panels) {
|
||||
const panel_ = { id, sections: [] }
|
||||
if (name) panel_.name = formatI18nField(name)
|
||||
if (help) panel_.help = formatI18nField(help)
|
||||
forms[id] = {}
|
||||
validations_[id] = {}
|
||||
errors_[id] = {}
|
||||
for (const { id_, name, help, visible, options } of sections) {
|
||||
const section_ = { id: id_, isVisible: true, visible }
|
||||
if (help) section_.help = formatI18nField(help)
|
||||
if (name) section_.name = formatI18nField(name)
|
||||
const { form, fields, validations, errors } = formatYunoHostArguments(options)
|
||||
Object.assign(forms[id], form)
|
||||
Object.assign(validations_[id], validations)
|
||||
Object.assign(errors_[id], errors)
|
||||
section_.fields = fields
|
||||
panel_.sections.push(section_)
|
||||
}
|
||||
panels_.push(panel_)
|
||||
}
|
||||
|
||||
this.forms = forms
|
||||
this.validations = { forms: validations_ }
|
||||
this.panels = panels_
|
||||
this.errors = errors_
|
||||
},
|
||||
|
||||
isVisible (expression, field) {
|
||||
if (!expression || !field) return true
|
||||
const context = {}
|
||||
|
||||
const promises = []
|
||||
for (const args of Object.values(this.forms)) {
|
||||
for (const shortname in args) {
|
||||
if (args[shortname] instanceof File) {
|
||||
if (expression.includes(shortname)) {
|
||||
promises.push(pFileReader(args[shortname], context, shortname, false))
|
||||
}
|
||||
} else {
|
||||
context[shortname] = args[shortname]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Allow to use match(var,regexp) function
|
||||
const matchRe = new RegExp('match\\(\\s*(\\w+)\\s*,\\s*"([^"]+)"\\s*\\)', 'g')
|
||||
let i = 0
|
||||
Promise.all(promises).then((value) => {
|
||||
for (const matched of expression.matchAll(matchRe)) {
|
||||
i++
|
||||
const varName = matched[1] + '__re' + i.toString()
|
||||
context[varName] = new RegExp(matched[2], 'm').test(context[matched[1]])
|
||||
expression = expression.replace(matched[0], varName)
|
||||
}
|
||||
|
||||
try {
|
||||
field.isVisible = evaluate(context, expression)
|
||||
} catch (error) {
|
||||
field.isVisible = false
|
||||
}
|
||||
})
|
||||
// This value should be updated magically when vuejs will detect isVisible changed
|
||||
return field.isVisible
|
||||
},
|
||||
|
||||
applyConfig (id_) {
|
||||
formatFormData(this.forms[id_], { removeEmpty: false, removeNull: true, multipart: false }).then((formatedData) => {
|
||||
const args = objectToParams(formatedData)
|
||||
|
||||
api.put(
|
||||
`domains/${this.name}/config`, { key: id_, args }, { key: 'domains.update_config', name: this.name }
|
||||
).then(response => {
|
||||
}).catch(err => {
|
||||
if (err.name !== 'APIBadRequestError') throw err
|
||||
const panel = this.panels.find(({ id }) => id_ === id)
|
||||
if (err.data.name) {
|
||||
this.errors[id_][err.data.name].message = err.message
|
||||
} else this.$set(panel, 'serverError', err.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,18 +1,100 @@
|
|||
<template>
|
||||
<view-base :queries="queries" @queries-response="dnsConfig = $event" skeleton="card-info-skeleton">
|
||||
<template #top>
|
||||
<p class="alert alert-warning">
|
||||
<icon iname="warning" /> {{ $t('domain_dns_conf_is_just_a_recommendation') }}
|
||||
</p>
|
||||
</template>
|
||||
<view-base
|
||||
:queries="queries" @queries-response="onQueriesResponse" :loading="loading"
|
||||
skeleton="card-info-skeleton"
|
||||
>
|
||||
<card v-if="showAutoConfigCard" :title="$t('domain.dns.auto_config')" icon="wrench">
|
||||
<b-alert variant="warning">
|
||||
<icon iname="flask" /> <icon iname="warning" /> <span v-html="$t('domain.dns.info')" />
|
||||
</b-alert>
|
||||
|
||||
<card :title="$t('domain_dns_config')" icon="globe" no-body>
|
||||
<pre class="log"><code>{{ dnsConfig }}</code></pre>
|
||||
<!-- AUTO CONFIG CHANGES -->
|
||||
<template v-if="dnsChanges">
|
||||
<div class="mb-3" v-for="{ action, records, icon, variant} in dnsChanges" :key="icon">
|
||||
<h4 class="mt-4 mb-2">
|
||||
{{ action }}
|
||||
</h4>
|
||||
|
||||
<div class="log">
|
||||
<div
|
||||
v-for="({ name: record, spaces, old_content, content, type, managed_by_yunohost }, i) in records" :key="i"
|
||||
class="records px-2" :class="{ 'ignored': managed_by_yunohost === false && force !== true }"
|
||||
:title="managed_by_yunohost === false && force !== true ? $t('domain.dns.auto_config_ignored') : null"
|
||||
>
|
||||
<icon :iname="icon" :class="'text-' + variant" />
|
||||
{{ record }}
|
||||
<span class="bg-dark text-light px-1 rounded">{{ type }}</span>{{ spaces }}
|
||||
<span v-if="old_content"><span class="text-danger">{{ old_content }}</span> --> </span>
|
||||
<span :class="{ 'text-success': old_content }">{{ content }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- CONFIG OK ALERT -->
|
||||
<b-alert v-else-if="dnsChanges === null" variant="success" class="m-0">
|
||||
<icon iname="thumbs-up" /> {{ $t('domain.dns.auto_config_ok') }}
|
||||
</b-alert>
|
||||
|
||||
<!-- CONFIG ERROR ALERT -->
|
||||
<template v-if="dnsErrors && dnsErrors.length">
|
||||
<b-alert
|
||||
v-for="({ variant, icon, message }, i) in dnsErrors" :key="i"
|
||||
:variant="variant" :class="dnsErrors.length === 1 ? 'm-0' : ''"
|
||||
>
|
||||
<icon :iname="icon" /> <span v-html="message" />
|
||||
</b-alert>
|
||||
</template>
|
||||
|
||||
<!-- CONFIG OVERWRITE DISCLAIMER -->
|
||||
<b-alert v-if="force !== null" variant="warning">
|
||||
<icon iname="warning" /> <span v-html="$t('domain.dns.push_force_warning')" />
|
||||
</b-alert>
|
||||
|
||||
<!-- CONFIG PUSH SUBMIT -->
|
||||
<template v-if="dnsChanges" #buttons>
|
||||
<b-form-checkbox v-if="force !== null" v-model="force">
|
||||
{{ $t('domain.dns.push_force') }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-button variant="success" @click="pushDnsChanges">
|
||||
{{ $t('domain.dns.push') }}
|
||||
</b-button>
|
||||
</template>
|
||||
</card>
|
||||
|
||||
<!-- CURRENT DNS ZONE -->
|
||||
<card
|
||||
v-if="showAutoConfigCard && dnsZone && dnsZone.length"
|
||||
:title="$t('domain.dns.auto_config_zone')" icon="globe" no-body
|
||||
>
|
||||
<div class="log">
|
||||
<div v-for="({ name: record, spaces, content, type }, i) in dnsZone" :key="'zone-' + i" class="records">
|
||||
{{ record }}
|
||||
<span class="bg-dark text-light px-1 rounded">{{ type }}</span>{{ spaces }}
|
||||
<span>{{ content }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</card>
|
||||
|
||||
<!-- MANUAL CONFIG CARD -->
|
||||
<card
|
||||
v-if="showManualConfigCard"
|
||||
:title="$t('domain.dns.manual_config')" icon="globe" no-body
|
||||
>
|
||||
<b-alert variant="warning" class="m-0">
|
||||
<icon iname="warning" /> {{ $t('domain_dns_conf_is_just_a_recommendation') }}
|
||||
</b-alert>
|
||||
|
||||
<pre class="log">{{ dnsConfig }}</pre>
|
||||
</card>
|
||||
</view-base>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { isEmptyValue } from '@/helpers/commons'
|
||||
|
||||
export default {
|
||||
name: 'DomainDns',
|
||||
|
||||
|
@ -23,10 +105,128 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
queries: [
|
||||
['GET', `domains/${this.name}/dns`]
|
||||
['GET', `domains/${this.name}/dns/suggest`]
|
||||
],
|
||||
dnsConfig: ''
|
||||
loading: true,
|
||||
showAutoConfigCard: true,
|
||||
showManualConfigCard: false,
|
||||
dnsConfig: '',
|
||||
dnsChanges: undefined,
|
||||
dnsErrors: undefined,
|
||||
dnsZone: undefined,
|
||||
force: null
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onQueriesResponse (suggestedConfig) {
|
||||
this.dnsConfig = suggestedConfig
|
||||
},
|
||||
|
||||
getDnsChanges () {
|
||||
this.loading = true
|
||||
|
||||
return api.post(
|
||||
`domains/${this.name}/dns/push?dry_run`, {}, null, { wait: false, websocket: false }
|
||||
).then(dnsChanges => {
|
||||
function getLongest (arr, key) {
|
||||
return arr.reduce((acc, obj) => {
|
||||
if (obj[key].length > acc) return obj[key].length
|
||||
return acc
|
||||
}, 0)
|
||||
}
|
||||
|
||||
const changes = []
|
||||
let canForce = false
|
||||
const categories = [
|
||||
{ action: 'create', icon: 'plus', variant: 'success' },
|
||||
{ action: 'update', icon: 'exchange', variant: 'warning' },
|
||||
{ action: 'delete', icon: 'minus', variant: 'danger' }
|
||||
]
|
||||
categories.forEach(category => {
|
||||
const records = dnsChanges[category.action]
|
||||
if (records && records.length > 0) {
|
||||
const longestName = getLongest(records, 'name')
|
||||
const longestType = getLongest(records, 'type')
|
||||
records.forEach(record => {
|
||||
record.name = record.name + ' '.repeat(longestName - record.name.length + 1)
|
||||
record.spaces = ' '.repeat(longestType - record.type.length + 1)
|
||||
if (record.managed_by_yunohost === false) canForce = true
|
||||
})
|
||||
changes.push({ ...category, records })
|
||||
}
|
||||
})
|
||||
|
||||
const unchanged = dnsChanges.unchanged
|
||||
if (unchanged) {
|
||||
const longestName = getLongest(unchanged, 'name')
|
||||
const longestType = getLongest(unchanged, 'type')
|
||||
unchanged.forEach(record => {
|
||||
record.name = record.name + ' '.repeat(longestName - record.name.length + 1)
|
||||
record.spaces = ' '.repeat(longestType - record.type.length + 1)
|
||||
})
|
||||
this.dnsZone = unchanged
|
||||
}
|
||||
|
||||
this.dnsChanges = changes.length > 0 ? changes : null
|
||||
this.force = canForce ? false : null
|
||||
this.loading = false
|
||||
}).catch(err => {
|
||||
if (err.name !== 'APIBadRequestError') throw err
|
||||
const key = err.data.error_key
|
||||
if (key === 'domain_dns_push_managed_in_parent_domain') {
|
||||
const message = this.$t(key, err.data)
|
||||
this.dnsErrors = [{ icon: 'info', variant: 'info', message }]
|
||||
} else if (key === 'domain_dns_push_failed_to_authenticate') {
|
||||
const message = this.$t(key, err.data)
|
||||
this.dnsErrors = [{ icon: 'ban', variant: 'danger', message }]
|
||||
} else {
|
||||
this.showManualConfigCard = true
|
||||
this.showAutoConfigCard = false
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
async pushDnsChanges () {
|
||||
if (this.force) {
|
||||
const confirmed = await this.$askConfirmation(this.$i18n.t('domain.dns.push_force_confirm'))
|
||||
if (!confirmed) return
|
||||
}
|
||||
|
||||
api.post(
|
||||
`domains/${this.name}/dns/push${this.force ? '?force' : ''}`,
|
||||
{},
|
||||
{ key: 'domains.push_dns_changes', name: this.name }
|
||||
).then(async responseData => {
|
||||
await this.getDnsChanges()
|
||||
if (!isEmptyValue(responseData)) {
|
||||
this.dnsErrors = Object.keys(responseData).reduce((acc, key) => {
|
||||
const args = key === 'warnings'
|
||||
? { icon: 'warning', variant: 'warning' }
|
||||
: { icon: 'ban', variant: 'danger' }
|
||||
responseData[key].forEach(message => acc.push({ ...args, message }))
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.getDnsChanges()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.records {
|
||||
white-space: pre;
|
||||
font-family: $font-family-monospace;
|
||||
|
||||
&.ignored {
|
||||
opacity: 0.3;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -18,9 +18,16 @@
|
|||
</b-button>
|
||||
<hr>
|
||||
|
||||
<!-- DOMAIN CONFIG -->
|
||||
<p>{{ $t('domain.config.edit') }}</p>
|
||||
<b-button variant="warning" :to="{ name: 'domain-config', param: { name } }">
|
||||
<icon iname="cog" /> {{ $t('domain.config.title') }}
|
||||
</b-button>
|
||||
<hr>
|
||||
|
||||
<!-- DNS CONFIG -->
|
||||
<p>{{ $t('domain_dns_longdesc') }}</p>
|
||||
<b-button variant="outline-dark" :to="{ name: 'domain-dns', param: { name } }">
|
||||
<p>{{ $t('domain.dns.edit') }}</p>
|
||||
<b-button variant="warning" :to="{ name: 'domain-dns', param: { name } }">
|
||||
<icon iname="globe" /> {{ $t('domain_dns_config') }}
|
||||
</b-button>
|
||||
<hr>
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<tags-selectize
|
||||
<tags-selectize-item
|
||||
v-model="group.members" :options="usersOptions"
|
||||
:id="groupName + '-users'" :label="$t('group_add_member')"
|
||||
tag-icon="user" items-name="users"
|
||||
|
@ -60,7 +60,7 @@
|
|||
<strong>{{ $t('permissions') }}</strong>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<tags-selectize
|
||||
<tags-selectize-item
|
||||
v-model="group.permissions" :options="permissionsOptions"
|
||||
:id="groupName + '-perms'" :label="$t('group_add_permission')"
|
||||
tag-icon="key-modern" items-name="permissions"
|
||||
|
@ -83,7 +83,7 @@
|
|||
</b-col>
|
||||
|
||||
<b-col>
|
||||
<tags-selectize
|
||||
<tags-selectize-item
|
||||
v-model="userGroups[userName].permissions" :options="permissionsOptions"
|
||||
:id="userName + '-perms'" :label="$t('group_add_permission')"
|
||||
tag-icon="key-modern" items-name="permissions"
|
||||
|
@ -94,7 +94,7 @@
|
|||
<hr :key="index">
|
||||
</template>
|
||||
|
||||
<tags-selectize
|
||||
<tags-selectize-item
|
||||
v-model="activeUserGroups" :options="usersOptions"
|
||||
id="user-groups" :label="$t('group_add_member')"
|
||||
no-tags items-name="users"
|
||||
|
@ -109,7 +109,7 @@ import Vue from 'vue'
|
|||
|
||||
import api from '@/api'
|
||||
import { isEmptyValue } from '@/helpers/commons'
|
||||
import TagsSelectize from '@/components/TagsSelectize'
|
||||
import TagsSelectizeItem from '@/components/globals/formItems/TagsSelectizeItem'
|
||||
|
||||
// TODO add global search with type (search by: group, user, permission)
|
||||
// TODO add vuex store update on inputs ?
|
||||
|
@ -117,7 +117,7 @@ export default {
|
|||
name: 'GroupList',
|
||||
|
||||
components: {
|
||||
TagsSelectize
|
||||
TagsSelectizeItem
|
||||
},
|
||||
|
||||
data () {
|
||||
|
|
|
@ -50,12 +50,14 @@
|
|||
<icon iname="plus" /> {{ $t('logs_more') }}
|
||||
</b-button>
|
||||
|
||||
<pre class="log"><code v-html="logs" /></pre>
|
||||
|
||||
<pre class="log unselectable"><code v-html="logs" /></pre>
|
||||
<b-button @click="shareLogs" variant="success" class="w-100 rounded-0">
|
||||
<icon iname="cloud-upload" /> {{ $t('logs_share_with_yunopaste') }}
|
||||
</b-button>
|
||||
</card>
|
||||
|
||||
|
||||
<p class="w-100 px-5 py-2 mb-0" v-html="$t('text_selection_is_disabled')" />
|
||||
</view-base>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -173,8 +173,8 @@ export default {
|
|||
this.form.domain = this.mainDomain
|
||||
},
|
||||
|
||||
onSubmit () {
|
||||
const data = formatFormData(this.form, { flatten: true })
|
||||
async onSubmit () {
|
||||
const data = await formatFormData(this.form, { flatten: true })
|
||||
api.post({ uri: 'users' }, data, { key: 'users.create', name: this.form.username }).then(() => {
|
||||
this.$router.push({ name: 'user-list' })
|
||||
}).catch(err => {
|
||||
|
|
|
@ -205,12 +205,12 @@ export default {
|
|||
label: this.$i18n.t('password'),
|
||||
description: this.$i18n.t('good_practices_about_user_password'),
|
||||
descriptionVariant: 'warning',
|
||||
props: { id: 'change_password', type: 'password', placeholder: '••••••••' }
|
||||
props: { id: 'change_password', type: 'password', placeholder: '••••••••', autocomplete: 'new-password' }
|
||||
},
|
||||
|
||||
confirmation: {
|
||||
label: this.$i18n.t('password_confirmation'),
|
||||
props: { id: 'confirmation', type: 'password', placeholder: '••••••••' }
|
||||
props: { id: 'confirmation', type: 'password', placeholder: '••••••••', autocomplete: 'new-password' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -266,8 +266,8 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
onSubmit () {
|
||||
const formData = formatFormData(this.form, { flatten: true })
|
||||
async onSubmit () {
|
||||
const formData = await formatFormData(this.form, { flatten: true })
|
||||
const user = this.user(this.name)
|
||||
const data = {}
|
||||
if (!Object.prototype.hasOwnProperty.call(formData, 'mailbox_quota')) {
|
||||
|
|
104
app/src/views/user/UserImport.vue
Normal file
104
app/src/views/user/UserImport.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<card-form
|
||||
:title="$t('users_import')" icon="user-plus"
|
||||
:validation="$v" :server-error="serverError"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<!-- CSV FILE -->
|
||||
<form-field v-bind="fields.csvfile" v-model="form.csvfile" :validation="$v.form.csvfile" />
|
||||
|
||||
<!-- UPDATE -->
|
||||
<form-field v-bind="fields.update" v-model="form.update" />
|
||||
|
||||
<!-- DELETE -->
|
||||
<form-field v-bind="fields.delete" v-model="form.delete" />
|
||||
</card-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from '@/api'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
|
||||
import { formatFormData } from '@/helpers/yunohostArguments'
|
||||
import { required } from '@/helpers/validators'
|
||||
|
||||
export default {
|
||||
name: 'UserImport',
|
||||
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
csvfile: null,
|
||||
update: false,
|
||||
delete: false
|
||||
},
|
||||
|
||||
serverError: '',
|
||||
|
||||
fields: {
|
||||
csvfile: {
|
||||
label: this.$i18n.t('users_import_csv_file'),
|
||||
description: this.$i18n.t('users_import_csv_file_desc'),
|
||||
component: 'FileItem',
|
||||
props: {
|
||||
id: 'csvfile',
|
||||
accept: 'text/csv',
|
||||
placeholder: this.$i18n.t('placeholder.file')
|
||||
}
|
||||
},
|
||||
|
||||
update: {
|
||||
label: this.$i18n.t('users_import_update'),
|
||||
description: this.$i18n.t('users_import_update_desc'),
|
||||
component: 'CheckboxItem',
|
||||
props: {
|
||||
id: 'update'
|
||||
}
|
||||
},
|
||||
|
||||
delete: {
|
||||
label: this.$i18n.t('users_import_delete'),
|
||||
description: this.$i18n.t('users_import_delete_desc'),
|
||||
component: 'CheckboxItem',
|
||||
props: {
|
||||
id: 'delete'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
validations: {
|
||||
form: {
|
||||
csvfile: { required }
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async onSubmit () {
|
||||
if (this.form.delete) {
|
||||
const confirmed = await this.$askConfirmation(
|
||||
this.$i18n.t('users_import_confirm_destructive'),
|
||||
{ okTitle: this.$i18n.t('users_import_delete_others') }
|
||||
)
|
||||
if (!confirmed) return
|
||||
}
|
||||
|
||||
var requestArgs = {}
|
||||
Object.assign(requestArgs, this.form)
|
||||
if (!requestArgs.delete) delete requestArgs.delete
|
||||
if (!requestArgs.update) delete requestArgs.update
|
||||
const data = await formatFormData(requestArgs)
|
||||
api.post('users/import', data, { asFormData: true }).then(() => {
|
||||
// Reset all cached data related to users.
|
||||
this.$store.dispatch('RESET_CACHE_DATA', ['users', 'users_details', 'groups', 'permissions'])
|
||||
this.$router.push({ name: 'user-list' })
|
||||
}).catch(error => {
|
||||
this.serverError = error.message
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
mixins: [validationMixin]
|
||||
}
|
||||
</script>
|
|
@ -8,14 +8,24 @@
|
|||
>
|
||||
<template #top-bar-buttons>
|
||||
<b-button variant="info" :to="{ name: 'group-list' }">
|
||||
<icon iname="key-modern" />
|
||||
{{ $t('groups_and_permissions_manage') }}
|
||||
<icon iname="key-modern" /> {{ $t('groups_and_permissions_manage') }}
|
||||
</b-button>
|
||||
|
||||
<b-button variant="success" :to="{ name: 'user-create' }">
|
||||
<icon iname="plus" />
|
||||
{{ $t('users_new') }}
|
||||
</b-button>
|
||||
<b-dropdown
|
||||
:split-to="{ name: 'user-create' }"
|
||||
split variant="outline-success" right
|
||||
split-variant="success"
|
||||
>
|
||||
<template #button-content>
|
||||
<icon iname="plus" /> {{ $t('users_new') }}
|
||||
</template>
|
||||
<b-dropdown-item :to="{ name: 'user-import' }">
|
||||
<icon iname="plus" /> {{ $t('users_import') }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item @click="downloadExport">
|
||||
<icon iname="download" /> {{ $t('users_export') }}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</template>
|
||||
|
||||
<b-list-group>
|
||||
|
@ -48,12 +58,17 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
queries: [
|
||||
['GET', { uri: 'users' }]
|
||||
['GET', { uri: 'users?fields=username&fields=fullname&fields=mail&fields=mailbox-quota&fields=groups', storeKey: 'users' }]
|
||||
],
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
downloadExport () {
|
||||
const host = this.$store.getters.host
|
||||
window.open(`https://${host}/yunohost/api/users/export`, '_blank')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['users']),
|
||||
|
||||
|
@ -61,7 +76,7 @@ export default {
|
|||
if (!this.users) return
|
||||
const search = this.search.toLowerCase()
|
||||
const filtered = this.users.filter(user => {
|
||||
return user.username.toLowerCase().includes(search)
|
||||
return user.username.toLowerCase().includes(search) || user.groups.includes(search)
|
||||
})
|
||||
return filtered.length === 0 ? null : filtered
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const webpack = require('webpack')
|
||||
const fs = require('fs')
|
||||
|
||||
const dateFnsLocales = [
|
||||
'ar',
|
||||
|
@ -11,7 +12,7 @@ const dateFnsLocales = [
|
|||
'eo',
|
||||
'es',
|
||||
'eu',
|
||||
'fa',
|
||||
'fa-IR',
|
||||
'fi',
|
||||
'fr', // for 'fr' & 'br'
|
||||
'gl',
|
||||
|
@ -57,18 +58,21 @@ module.exports = {
|
|||
}
|
||||
},
|
||||
publicPath: '/yunohost/admin',
|
||||
devServer: {
|
||||
devServer: process.env.NODE_ENV === 'development' ? {
|
||||
public: fs.readFileSync('/etc/yunohost/current_host', 'utf8'),
|
||||
https: false,
|
||||
disableHostCheck: true,
|
||||
proxy: {
|
||||
'^/yunohost': {
|
||||
target: `http://${process.env.VUE_APP_IP}`,
|
||||
ws: true,
|
||||
logLevel: 'debug'
|
||||
logLevel: 'info'
|
||||
}
|
||||
},
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
ignored: /node_modules/,
|
||||
aggregateTimeout: 300,
|
||||
poll: 1000
|
||||
}
|
||||
}
|
||||
} : {}
|
||||
}
|
||||
|
|
72
debian/changelog
vendored
72
debian/changelog
vendored
|
@ -4,6 +4,78 @@ yunohost-admin (11.0.0~alpha) unstable; urgency=low
|
|||
|
||||
-- Kay0u <pierre@kayou.io> Mon, 08 Mar 2021 18:04:43 +0100
|
||||
|
||||
yunohost-admin (4.3.1.4) testing; urgency=low
|
||||
|
||||
- [fix] Route for diagnosis share was incorrect (804488ab)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 19 Oct 2021 14:56:23 +0200
|
||||
|
||||
yunohost-admin (4.3.1.3) testing; urgency=low
|
||||
|
||||
- [fix] app url regex: branch names may contain dots (5c883cc0)
|
||||
- [fix] app install: improve label question (0c0df14a)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 18 Oct 2021 19:04:52 +0200
|
||||
|
||||
yunohost-admin (4.3.1.2) testing; urgency=low
|
||||
|
||||
- [enh] appinfo: Don't diplay webapp-related stuff for non-webapps (f3775554)
|
||||
- [enh] app/domain infos: Tweak icons for config panel buttons (791172ab)
|
||||
- [i18n] Translations updated for Galician
|
||||
|
||||
Thanks to all contributors <3 ! (José M)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 07 Oct 2021 10:47:36 +0200
|
||||
|
||||
yunohost-admin (4.3.1.1) testing; urgency=low
|
||||
|
||||
- [enh] Handle 'visible' question property in app install forms ([#404](https://github.com/YunoHost/yunohost-admin/pull/404))
|
||||
- [enh] Optimizations for waiting modal messages and AppCatalog cards rendering ([#406](https://github.com/YunoHost/yunohost-admin/pull/406))
|
||||
- [enh] Support github-independent repo urls and/or pointing to specific branches ([#407](https://github.com/YunoHost/yunohost-admin/pull/407))
|
||||
- [i18n] Translations updated for French, Galician, Ukrainian
|
||||
|
||||
Thanks to all contributors <3 ! (axolotle, José M, ppr, Tymofii-Lytvynenko)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 04 Oct 2021 01:31:10 +0200
|
||||
|
||||
yunohost-admin (4.3.1) testing; urgency=low
|
||||
|
||||
- [fix] app install: choice questions broken (64ae24fa)
|
||||
- [fix] dns push: simplify callback / fix ux message issue ([#403](https://github.com/YunoHost/yunohost-admin/pull/403))
|
||||
- [fix] app install: broken when missing 'ask' property ([#402](https://github.com/YunoHost/yunohost-admin/pull/402))
|
||||
- [fix] Unable to import user from csv anymore ([#401](https://github.com/YunoHost/yunohost-admin/pull/401))
|
||||
- [mod] ui/ux: Make logs unselectable to force people clicking the damn share button >_> ([#400](https://github.com/YunoHost/yunohost-admin/pull/400))
|
||||
- [enh] i18n: add 3 plural forms for now for some translation keys ([#397](https://github.com/YunoHost/yunohost-admin/pull/397))
|
||||
- [i18n] Translations updated for French, Galician, Indonesian, Ukrainian
|
||||
|
||||
Thanks to all contributors <3 ! (axolotle, Eauchat, Éric Gaspar, José M, liimee, ljf, Tymofii-Lytvynenko)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 29 Sep 2021 22:31:28 +0200
|
||||
|
||||
yunohost-admin (4.3.0.1) testing; urgency=low
|
||||
|
||||
- Fix build (thanks axolotle <3) (0c4b7d1)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 20 Sep 2021 00:51:45 +0200
|
||||
|
||||
yunohost-admin (4.3.0) testing; urgency=low
|
||||
|
||||
- [fix] New auth mecanism require to pass credentials as arg instead of password (63524be)
|
||||
- [fix] Prevent browser from suggesting saved password on user password change form ([#329](https://github.com/YunoHost/yunohost-admin/pull/329))
|
||||
- [enh] Filter by groups ([#378](https://github.com/YunoHost/yunohost-admin/pull/378))
|
||||
- [enh] Import users by CSV file + Upload file management ([#357](https://github.com/YunoHost/yunohost-admin/pull/357))
|
||||
- [enh] Add simplified breadcrumb as document title ([#393](https://github.com/YunoHost/yunohost-admin/pull/393))
|
||||
- [enh] New app config panel ([#366](https://github.com/YunoHost/yunohost-admin/pull/366))
|
||||
- [enh] Add domain config panel + registrar and auto DNS configuration mecanism ([#396](https://github.com/YunoHost/yunohost-admin/pull/396))
|
||||
- [i18n] pluralized strings ([#395](https://github.com/YunoHost/yunohost-admin/pull/395))
|
||||
- [i18n] Update supported locales ([#389](https://github.com/YunoHost/yunohost-admin/pull/389))
|
||||
- [i18n] Translations updated for Arabic, Catalan, Chinese (Simplified), Czech, Dutch, Esperanto, French, Galician, German, Hindi, Indonesian, Italian, Kurdish (Central), Macedonian, Persian, Portuguese, Spanish, Ukrainian
|
||||
|
||||
Thanks to all contributors <3 ! (axolotle, Christian Wehrli, Daniel, Éric Gaspar, JocelynDelalande, José M, liimee, ljf, Parviz Homayun, possy eater, ppr, saptrishi das biswas, Tymofii-Lytvynenko)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 19 Sep 2021 23:49:02 +0200
|
||||
>>>>>>> dev
|
||||
|
||||
yunohost-admin (4.2.5) stable; urgency=low
|
||||
|
||||
- [fix] user password change not working properly (c26c4c9)
|
||||
|
|
2
debian/control
vendored
2
debian/control
vendored
|
@ -10,7 +10,7 @@ Homepage: https://yunohost.org/
|
|||
Package: yunohost-admin
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}
|
||||
, yunohost (>= 4.0.0~alpha)
|
||||
, yunohost (>= 4.3)
|
||||
Description: web administration interface for yunohost
|
||||
YunoHost aims to make self-hosting accessible to everyone. It configures
|
||||
an email, Web and IM server alongside a LDAP base. It also provides
|
||||
|
|
Loading…
Add table
Reference in a new issue