mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-24 14:50:39 -05:00
Compare commits
119 Commits
deleteComm
...
rolling-3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24210c7b3a | ||
|
|
8565ed9277 | ||
|
|
75b0cd9909 | ||
|
|
b940b0c457 | ||
|
|
6e5e5a7e8a | ||
|
|
62a7f79f51 | ||
|
|
b1968591b1 | ||
|
|
05b80b7f63 | ||
|
|
9659e97056 | ||
|
|
8238a4091a | ||
|
|
0f09cdd8ec | ||
|
|
b9f48edd87 | ||
|
|
926a2c2080 | ||
|
|
7b3e8444d1 | ||
|
|
13c3f42396 | ||
|
|
7c59e57d43 | ||
|
|
7a7d148dcf | ||
|
|
3c8e2dacfd | ||
|
|
77ddcc2d6b | ||
|
|
e255f81279 | ||
|
|
1afc1331af | ||
|
|
6d86b35651 | ||
|
|
977d706a43 | ||
|
|
4bafe2e611 | ||
|
|
99cf64bf32 | ||
|
|
13c3368660 | ||
|
|
e15443d951 | ||
|
|
ad9f8b3b4a | ||
|
|
b4cc781832 | ||
|
|
2670f24605 | ||
|
|
b865d7c2f2 | ||
|
|
d3f73b13ff | ||
|
|
55f1d9e4d2 | ||
|
|
484f8bc66b | ||
|
|
4c2b723ab0 | ||
|
|
683b979bdd | ||
|
|
a6cdbc710d | ||
|
|
49ab88e980 | ||
|
|
9051a0c17e | ||
|
|
84cf82c79d | ||
|
|
6e909bf3d7 | ||
|
|
9e2e28c138 | ||
|
|
078d6f88bb | ||
|
|
e6b59783e9 | ||
|
|
ebb7b1f2a0 | ||
|
|
3bd7a61e46 | ||
|
|
386e322050 | ||
|
|
3ce2317130 | ||
|
|
dbd6a4db71 | ||
|
|
8adf425b2d | ||
|
|
4704cedd3b | ||
|
|
791b4df173 | ||
|
|
273c0ed270 | ||
|
|
400b9a5d30 | ||
|
|
4bdb3bf70f | ||
|
|
601bb4c955 | ||
|
|
ef30a996df | ||
|
|
5225e89e80 | ||
|
|
f3c75468e9 | ||
|
|
acdaee9b92 | ||
|
|
de7b659e3d | ||
|
|
ad8ac8ee25 | ||
|
|
096c86fd83 | ||
|
|
febcb791a8 | ||
|
|
c77ef3033e | ||
|
|
c22c63d91a | ||
|
|
3917fd1393 | ||
|
|
6cd3688010 | ||
|
|
aa9f74bcb4 | ||
|
|
e92f7c9086 | ||
|
|
4998e1e09f | ||
|
|
63ab8f789e | ||
|
|
e21fe7a4fe | ||
|
|
608af81913 | ||
|
|
1213908eb2 | ||
|
|
0c39cb45d4 | ||
|
|
e07b64fef9 | ||
|
|
2d24c27329 | ||
|
|
f794a553cf | ||
|
|
f222ecc344 | ||
|
|
233304159c | ||
|
|
cf916b8a2c | ||
|
|
88cf2d8955 | ||
|
|
c4903ed9a6 | ||
|
|
d209c29239 | ||
|
|
c2ba5ca124 | ||
|
|
b8ee26892c | ||
|
|
08159e679c | ||
|
|
d52329708d | ||
|
|
a5cb9afd7b | ||
|
|
2007411b6c | ||
|
|
cbaf545953 | ||
|
|
db5ac0a4e2 | ||
|
|
8186a26af5 | ||
|
|
f04aebdd2d | ||
|
|
bb0390d37f | ||
|
|
85bda8061d | ||
|
|
a86bf79c7f | ||
|
|
979b93806e | ||
|
|
356f086bfa | ||
|
|
74ba68ffd1 | ||
|
|
a00a511267 | ||
|
|
df6fb3384f | ||
|
|
fb8ddb9e01 | ||
|
|
b85f620800 | ||
|
|
b1ca895a20 | ||
|
|
f37b979474 | ||
|
|
73d49854c6 | ||
|
|
726af50e16 | ||
|
|
818490592f | ||
|
|
8721f23bc5 | ||
|
|
2c43cf7b44 | ||
|
|
e96a0b8c5e | ||
|
|
02d793169d | ||
|
|
9b849f5013 | ||
|
|
cbedf66d19 | ||
|
|
196fe41380 | ||
|
|
88e458c34a | ||
|
|
e226efa4fb |
@@ -3,3 +3,5 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
|
||||
go 1.23.4
|
||||
|
||||
require github.com/gosexy/gettext v0.0.0-20160830220431-74466a0a0c4a // go-xgettext
|
||||
|
||||
require github.com/jessevdk/go-flags v1.6.1 // indirect
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# The test runner source for UI tests
|
||||
WEB_COMMITID=0dca6b808d191ddf15503f20a6594a77e50a68e2
|
||||
WEB_COMMITID=636c9d41f100901c4f75509b3269dffaa94d8004
|
||||
WEB_BRANCH=main
|
||||
|
||||
328
.woodpecker.star
328
.woodpecker.star
@@ -27,11 +27,13 @@ OC_UBUNTU = "owncloud/ubuntu:20.04"
|
||||
ONLYOFFICE_DOCUMENT_SERVER = "onlyoffice/documentserver:7.5.1"
|
||||
PLUGINS_DOCKER_BUILDX = "woodpeckerci/plugin-docker-buildx:latest"
|
||||
PLUGINS_GITHUB_RELEASE = "woodpeckerci/plugin-release"
|
||||
PLUGINS_GIT_ACTION = "quay.io/thegeeklab/wp-git-action"
|
||||
PLUGINS_S3 = "plugins/s3:1"
|
||||
PLUGINS_S3_CACHE = "plugins/s3-cache:1"
|
||||
PLUGINS_SLACK = "plugins/slack:1"
|
||||
REDIS = "redis:6-alpine"
|
||||
READY_RELEASE_GO = "woodpeckerci/plugin-ready-release-go:latest"
|
||||
OPENLDAP = "bitnami/openldap:2.6"
|
||||
|
||||
DEFAULT_PHP_VERSION = "8.2"
|
||||
DEFAULT_NODEJS_VERSION = "20"
|
||||
@@ -44,6 +46,7 @@ dirs = {
|
||||
"zip": "/woodpecker/src/github.com/opencloud-eu/opencloud/zip",
|
||||
"webZip": "/woodpecker/src/github.com/opencloud-eu/opencloud/zip/web.tar.gz",
|
||||
"webPnpmZip": "/woodpecker/src/github.com/opencloud-eu/opencloud/zip/web-pnpm.tar.gz",
|
||||
"playwrightBrowsersArchive": "/woodpecker/src/github.com/opencloud-eu/opencloud/zip/playwright-browsers.tar.gz",
|
||||
"baseGo": "/go/src/github.com/opencloud-eu/opencloud",
|
||||
"gobinTar": "go-bin.tar.gz",
|
||||
"gobinTarPath": "/go/src/github.com/opencloud-eu/opencloud/go-bin.tar.gz",
|
||||
@@ -111,7 +114,7 @@ config = {
|
||||
"skip": False,
|
||||
"withRemotePhp": [True],
|
||||
"emailNeeded": True,
|
||||
"extraEnvironment": {
|
||||
"extraTestEnvironment": {
|
||||
"EMAIL_HOST": "email",
|
||||
"EMAIL_PORT": "9000",
|
||||
},
|
||||
@@ -205,7 +208,7 @@ config = {
|
||||
"skip": False,
|
||||
"withRemotePhp": [True],
|
||||
"emailNeeded": True,
|
||||
"extraEnvironment": {
|
||||
"extraTestEnvironment": {
|
||||
"EMAIL_HOST": "email",
|
||||
"EMAIL_PORT": "9000",
|
||||
},
|
||||
@@ -248,7 +251,7 @@ config = {
|
||||
"withRemotePhp": [True],
|
||||
"federationServer": True,
|
||||
"emailNeeded": True,
|
||||
"extraEnvironment": {
|
||||
"extraTestEnvironment": {
|
||||
"EMAIL_HOST": "email",
|
||||
"EMAIL_PORT": "9000",
|
||||
},
|
||||
@@ -298,6 +301,36 @@ config = {
|
||||
"STORAGE_USERS_DRIVER": "decomposed",
|
||||
},
|
||||
},
|
||||
"multiTenancy": {
|
||||
"suites": [
|
||||
"apiTenancy",
|
||||
],
|
||||
"skip": False,
|
||||
"withRemotePhp": [True],
|
||||
"ldapNeeded": True,
|
||||
"extraTestEnvironment": {
|
||||
"USE_PREPARED_LDAP_USERS": True,
|
||||
},
|
||||
"extraServerEnvironment": {
|
||||
"OC_LDAP_USER_SCHEMA_TENANT_ID": "departmentNumber",
|
||||
"OC_LDAP_URI": "ldaps://ldap-server:1636",
|
||||
"OC_LDAP_INSECURE": True,
|
||||
"OC_LDAP_BIND_DN": "cn=admin,dc=opencloud,dc=eu",
|
||||
"OC_LDAP_BIND_PASSWORD": "admin",
|
||||
"OC_LDAP_GROUP_BASE_DN": "ou=groups,dc=opencloud,dc=eu",
|
||||
"OC_LDAP_GROUP_SCHEMA_ID": "entryUUID",
|
||||
"OC_LDAP_USER_BASE_DN": "ou=users,dc=opencloud,dc=eu",
|
||||
"OC_LDAP_USER_FILTER": "(objectclass=inetOrgPerson)",
|
||||
"OC_LDAP_USER_SCHEMA_ID": "entryUUID",
|
||||
"OC_LDAP_DISABLE_USER_MECHANISM": "none",
|
||||
"GRAPH_LDAP_SERVER_UUID": True,
|
||||
"GRAPH_LDAP_GROUP_CREATE_BASE_DN": "ou=custom,ou=groups,dc=opencloud,dc=eu",
|
||||
"GRAPH_LDAP_REFINT_ENABLED": True,
|
||||
"FRONTEND_READONLY_USER_ATTRIBUTES": "user.onPremisesSamAccountName,user.displayName,user.mail,user.passwordProfile,user.accountEnabled,user.appRoleAssignments",
|
||||
"OC_LDAP_SERVER_WRITE_ENABLED": False,
|
||||
"OC_EXCLUDE_RUN_SERVICES": "idm",
|
||||
},
|
||||
},
|
||||
},
|
||||
"apiTests": {
|
||||
"numberOfParts": 7,
|
||||
@@ -308,7 +341,7 @@ config = {
|
||||
"part": {
|
||||
"skip": False,
|
||||
"totalParts": 4, # divide and run all suites in parts (divide pipelines)
|
||||
"xsuites": ["search", "app-provider", "app-provider-onlyOffice", "app-store", "keycloak", "oidc", "ocm", "a11y"], # suites to skip
|
||||
"xsuites": ["search", "app-provider", "app-provider-onlyOffice", "app-store", "keycloak", "oidc", "ocm", "a11y", "mobile-view"], # suites to skip
|
||||
},
|
||||
"search": {
|
||||
"skip": False,
|
||||
@@ -381,6 +414,7 @@ MINIO_MC_ENV = {
|
||||
"AWS_SECRET_ACCESS_KEY": {
|
||||
"from_secret": "cache_s3_secret_key",
|
||||
},
|
||||
"PUBLIC_BUCKET": "public",
|
||||
}
|
||||
|
||||
CI_HTTP_PROXY_ENV = {
|
||||
@@ -430,6 +464,9 @@ def main(ctx):
|
||||
none
|
||||
"""
|
||||
|
||||
if ctx.build.event == "cron" and ctx.build.sender == "translation-sync":
|
||||
return translation_sync(ctx)
|
||||
|
||||
build_release_helpers = \
|
||||
readyReleaseGo()
|
||||
|
||||
@@ -445,6 +482,7 @@ def main(ctx):
|
||||
checkGherkinLint(ctx) + \
|
||||
checkTestSuitesInExpectedFailures(ctx) + \
|
||||
buildWebCache(ctx) + \
|
||||
cacheBrowsers(ctx) + \
|
||||
getGoBinForTesting(ctx) + \
|
||||
buildOpencloudBinaryForTesting(ctx) + \
|
||||
checkStarlark(ctx) + \
|
||||
@@ -906,7 +944,7 @@ def localApiTestPipeline(ctx):
|
||||
defaults = {
|
||||
"suites": {},
|
||||
"skip": False,
|
||||
"extraEnvironment": {},
|
||||
"extraTestEnvironment": {},
|
||||
"extraServerEnvironment": {},
|
||||
"storages": storages,
|
||||
"accounts_hash_difficulty": 4,
|
||||
@@ -917,6 +955,7 @@ def localApiTestPipeline(ctx):
|
||||
"collaborationServiceNeeded": False,
|
||||
"extraCollaborationEnvironment": {},
|
||||
"withRemotePhp": with_remote_php,
|
||||
"ldapNeeded": False,
|
||||
}
|
||||
|
||||
if "localApiTests" in config:
|
||||
@@ -934,11 +973,13 @@ def localApiTestPipeline(ctx):
|
||||
(waitForServices("online-offices", ["collabora:9980", "onlyoffice:443", "fakeoffice:8080"]) if params["collaborationServiceNeeded"] else []) +
|
||||
(waitForClamavService() if params["antivirusNeeded"] else []) +
|
||||
(waitForEmailService() if params["emailNeeded"] else []) +
|
||||
(ldapService() if params["ldapNeeded"] else []) +
|
||||
(waitForLdapService() if params["ldapNeeded"] else []) +
|
||||
opencloudServer(storage, params["accounts_hash_difficulty"], extra_server_environment = params["extraServerEnvironment"], with_wrapper = True, tika_enabled = params["tikaNeeded"]) +
|
||||
(opencloudServer(storage, params["accounts_hash_difficulty"], deploy_type = "federation", extra_server_environment = params["extraServerEnvironment"]) if params["federationServer"] else []) +
|
||||
((wopiCollaborationService("fakeoffice") + wopiCollaborationService("collabora") + wopiCollaborationService("onlyoffice")) if params["collaborationServiceNeeded"] else []) +
|
||||
(openCloudHealthCheck("wopi", ["wopi-collabora:9304", "wopi-onlyoffice:9304", "wopi-fakeoffice:9304"]) if params["collaborationServiceNeeded"] else []) +
|
||||
localApiTests(name, params["suites"], storage, params["extraEnvironment"], run_with_remote_php) +
|
||||
localApiTests(name, params["suites"], storage, params["extraTestEnvironment"], run_with_remote_php) +
|
||||
logRequests(),
|
||||
"services": (emailService() if params["emailNeeded"] else []) +
|
||||
(clamavService() if params["antivirusNeeded"] else []) +
|
||||
@@ -1210,6 +1251,7 @@ def e2eTestPipeline(ctx):
|
||||
"xsuites": [],
|
||||
"totalParts": 0,
|
||||
"tikaNeeded": False,
|
||||
"reportTracing": False,
|
||||
}
|
||||
|
||||
extra_server_environment = {
|
||||
@@ -1263,10 +1305,14 @@ def e2eTestPipeline(ctx):
|
||||
if params["xsuites"]:
|
||||
e2e_args += " --xsuites %s" % ",".join(params["xsuites"])
|
||||
|
||||
if "with-tracing" in ctx.build.title.lower():
|
||||
params["reportTracing"] = True
|
||||
|
||||
steps_before = \
|
||||
restoreBuildArtifactCache(ctx, dirs["opencloudBinArtifact"], dirs["opencloudBin"]) + \
|
||||
restoreWebCache() + \
|
||||
restoreWebPnpmCache() + \
|
||||
restoreBrowsersCache() + \
|
||||
(tikaService() if params["tikaNeeded"] else []) + \
|
||||
opencloudServer(storage, extra_server_environment = extra_server_environment, tika_enabled = params["tikaNeeded"])
|
||||
|
||||
@@ -1279,15 +1325,16 @@ def e2eTestPipeline(ctx):
|
||||
"RETRY": "1",
|
||||
"WEB_UI_CONFIG_FILE": "%s/%s" % (dirs["base"], dirs["opencloudConfig"]),
|
||||
"LOCAL_UPLOAD_DIR": "/uploads",
|
||||
"PLAYWRIGHT_BROWSERS_PATH": "%s/%s" % (dirs["base"], ".playwright"),
|
||||
"BROWSER": "chromium",
|
||||
"REPORT_TRACING": params["reportTracing"],
|
||||
},
|
||||
"commands": [
|
||||
"cd %s/tests/e2e" % dirs["web"],
|
||||
],
|
||||
}
|
||||
|
||||
# steps_after = uploadTracingResult(ctx) + \
|
||||
# steps_after = logTracingResults()
|
||||
steps_after = []
|
||||
steps_after = uploadTracingResult(ctx)
|
||||
|
||||
if params["totalParts"]:
|
||||
for index in range(params["totalParts"]):
|
||||
@@ -1323,6 +1370,7 @@ def multiServiceE2ePipeline(ctx):
|
||||
"suites": [],
|
||||
"xsuites": [],
|
||||
"tikaNeeded": False,
|
||||
"reportTracing": False,
|
||||
}
|
||||
|
||||
e2e_trigger = [
|
||||
@@ -1412,6 +1460,9 @@ def multiServiceE2ePipeline(ctx):
|
||||
if params["xsuites"]:
|
||||
e2e_args += " --xsuites %s" % ",".join(params["xsuites"])
|
||||
|
||||
if "with-tracing" in ctx.build.title.lower():
|
||||
params["reportTracing"] = True
|
||||
|
||||
steps = \
|
||||
restoreBuildArtifactCache(ctx, dirs["opencloudBinArtifact"], dirs["opencloudBin"]) + \
|
||||
restoreWebCache() + \
|
||||
@@ -1426,6 +1477,7 @@ def multiServiceE2ePipeline(ctx):
|
||||
"OC_BASE_URL": OC_DOMAIN,
|
||||
"HEADLESS": True,
|
||||
"RETRY": "1",
|
||||
"REPORT_TRACING": params["reportTracing"],
|
||||
},
|
||||
"commands": [
|
||||
"cd %s/tests/e2e" % dirs["web"],
|
||||
@@ -1433,8 +1485,7 @@ def multiServiceE2ePipeline(ctx):
|
||||
],
|
||||
}]
|
||||
|
||||
# + logTracingResults()
|
||||
# uploadTracingResult(ctx) + \
|
||||
uploadTracingResult(ctx) + \
|
||||
pipelines.append({
|
||||
"name": "e2e-tests-multi-service",
|
||||
"steps": steps,
|
||||
@@ -1443,56 +1494,24 @@ def multiServiceE2ePipeline(ctx):
|
||||
})
|
||||
return pipelines
|
||||
|
||||
def uploadTracingResult():
|
||||
def uploadTracingResult(ctx):
|
||||
status = ["failure"]
|
||||
if "with-tracing" in ctx.build.title.lower():
|
||||
status = ["failure", "success"]
|
||||
|
||||
return [{
|
||||
"name": "upload-tracing-result",
|
||||
"image": PLUGINS_S3,
|
||||
"settings": {
|
||||
"bucket": {
|
||||
"from_secret": "cache_public_s3_bucket",
|
||||
},
|
||||
"endpoint": CACHE_S3_SERVER,
|
||||
"path_style": True,
|
||||
"source": "webTestRunner/reports/e2e/playwright/tracing/**/*",
|
||||
"strip_prefix": "webTestRunner/reports/e2e/playwright/tracing",
|
||||
"target": "/${DRONE_REPO}/${CI_PIPELINE_NUMBER}/tracing",
|
||||
},
|
||||
"environment": {
|
||||
"AWS_ACCESS_KEY_ID": {
|
||||
"from_secret": "cache_public_s3_access_key",
|
||||
},
|
||||
"AWS_SECRET_ACCESS_KEY": {
|
||||
"from_secret": "cache_public_s3_secret_key",
|
||||
},
|
||||
},
|
||||
"when": {
|
||||
"status": [
|
||||
"failure",
|
||||
],
|
||||
"event": [
|
||||
"pull_request",
|
||||
"cron",
|
||||
],
|
||||
},
|
||||
}]
|
||||
|
||||
def logTracingResults():
|
||||
return [{
|
||||
"name": "log-tracing-result",
|
||||
"image": OC_UBUNTU,
|
||||
"image": MINIO_MC,
|
||||
"environment": MINIO_MC_ENV,
|
||||
"commands": [
|
||||
"mc alias set s3 $MC_HOST $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY",
|
||||
"mc cp -a %s/reports/e2e/playwright/tracing/* s3/$PUBLIC_BUCKET/web/tracing/$CI_REPO_NAME/$CI_PIPELINE_NUMBER/" % dirs["web"],
|
||||
"cd %s/reports/e2e/playwright/tracing/" % dirs["web"],
|
||||
'echo "To see the trace, please open the following link in the console"',
|
||||
'for f in *.zip; do echo "npx playwright show-trace https://cache.owncloud.com/public/${DRONE_REPO}/${CI_PIPELINE_NUMBER}/tracing/$f \n"; done',
|
||||
'for f in *.zip; do echo "npx playwright show-trace $MC_HOST/$PUBLIC_BUCKET/web/tracing/$CI_REPO_NAME/$CI_PIPELINE_NUMBER/$f \n"; done',
|
||||
],
|
||||
"when": {
|
||||
"status": [
|
||||
"failure",
|
||||
],
|
||||
"event": [
|
||||
"pull_request",
|
||||
"cron",
|
||||
],
|
||||
"status": status,
|
||||
},
|
||||
}]
|
||||
|
||||
@@ -1539,10 +1558,10 @@ def dockerReleases(ctx):
|
||||
return pipelines
|
||||
|
||||
def dockerRelease(ctx, repo, build_type):
|
||||
build_args = [
|
||||
"REVISION=%s" % ctx.build.commit,
|
||||
"VERSION=%s" % (ctx.build.ref.replace("refs/tags/", "") if ctx.build.event == "tag" else "daily"),
|
||||
]
|
||||
build_args = {
|
||||
"REVISION": "%s" % ctx.build.commit,
|
||||
"VERSION": "%s" % (ctx.build.ref.replace("refs/tags/", "") if ctx.build.event == "tag" else "daily"),
|
||||
}
|
||||
|
||||
depends_on = getPipelineNames(getGoBinForTesting(ctx))
|
||||
|
||||
@@ -2143,6 +2162,55 @@ def skipIfUnchanged(ctx, type):
|
||||
|
||||
return skip
|
||||
|
||||
def translation_sync(ctx):
|
||||
return [{
|
||||
"name": "translation-sync",
|
||||
"steps": [
|
||||
{
|
||||
"name": "translation-update",
|
||||
"image": OC_CI_GOLANG,
|
||||
"commands": [
|
||||
"make l10n-read",
|
||||
"curl -o- https://raw.githubusercontent.com/transifex/cli/master/install.sh | bash",
|
||||
". ~/.profile",
|
||||
"make l10n-push",
|
||||
"make l10n-pull",
|
||||
"rm tx",
|
||||
"make l10n-clean",
|
||||
],
|
||||
"environment": {
|
||||
"TX_TOKEN": {
|
||||
"from_secret": "tx_token",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "translation-push",
|
||||
"image": PLUGINS_GIT_ACTION,
|
||||
"settings": {
|
||||
"action": ["commit", "push"],
|
||||
"branch": ctx.build.branch,
|
||||
"message": "[tx] updated from transifex",
|
||||
"author_name": "opencloudeu",
|
||||
"author_email": "devops@opencloud.eu",
|
||||
"netrc_username": {
|
||||
"from_secret": "github_username",
|
||||
},
|
||||
"netrc_password": {
|
||||
"from_secret": "github_token",
|
||||
},
|
||||
"empty_commit": False,
|
||||
},
|
||||
},
|
||||
],
|
||||
"when": [
|
||||
{
|
||||
"event": "cron",
|
||||
"cron": "translation-sync",
|
||||
},
|
||||
],
|
||||
}]
|
||||
|
||||
def example_deploys(ctx):
|
||||
on_merge_deploy = [
|
||||
"opencloud_full/master.yml",
|
||||
@@ -2593,6 +2661,77 @@ def generateWebPnpmCache(ctx):
|
||||
},
|
||||
]
|
||||
|
||||
def cacheBrowsers(ctx):
|
||||
e2e_trigger = [
|
||||
event["base"],
|
||||
{
|
||||
"event": "pull_request",
|
||||
"path": {
|
||||
"exclude": skipIfUnchanged(ctx, "e2e-tests"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"event": "tag",
|
||||
"ref": "refs/tags/**",
|
||||
},
|
||||
]
|
||||
|
||||
check_browser_step = [{
|
||||
"name": "check-browsers-cache",
|
||||
"image": MINIO_MC,
|
||||
"environment": MINIO_MC_ENV,
|
||||
"commands": [
|
||||
"mc alias set s3 $MC_HOST $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY",
|
||||
"mc ls --recursive s3/$CACHE_BUCKET/web",
|
||||
"cd %s" % dirs["web"],
|
||||
"bash tests/woodpecker/script.sh check_browsers_cache",
|
||||
],
|
||||
}]
|
||||
|
||||
webPnpmCacheSteps = restoreWebPnpmCache(extra_commands = [
|
||||
". ./.woodpecker.env",
|
||||
"if $BROWSER_CACHE_FOUND; then exit 0; fi",
|
||||
])
|
||||
|
||||
browser_cache_steps = [
|
||||
{
|
||||
"name": "install-browsers",
|
||||
"image": OC_CI_NODEJS % DEFAULT_NODEJS_VERSION,
|
||||
"environment": {
|
||||
"PLAYWRIGHT_BROWSERS_PATH": ".playwright",
|
||||
},
|
||||
"commands": [
|
||||
". ./.woodpecker.env",
|
||||
"if $BROWSER_CACHE_FOUND; then exit 0; fi",
|
||||
"cd %s" % dirs["web"],
|
||||
"pnpm exec playwright install --with-deps",
|
||||
"pnpm exec playwright install --list",
|
||||
"tar -czf %s .playwright" % dirs["playwrightBrowsersArchive"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "upload-browsers-cache",
|
||||
"image": MINIO_MC,
|
||||
"environment": MINIO_MC_ENV,
|
||||
"commands": [
|
||||
". ./.woodpecker.env",
|
||||
"if $BROWSER_CACHE_FOUND; then exit 0; fi",
|
||||
"cd %s" % dirs["web"],
|
||||
"playwright_version=$(bash tests/woodpecker/script.sh get_playwright_version)",
|
||||
"mc alias set s3 $MC_HOST $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY",
|
||||
"mc cp -r -a %s s3/$CACHE_BUCKET/web/browsers-cache/$playwright_version/" % dirs["playwrightBrowsersArchive"],
|
||||
"mc ls --recursive s3/$CACHE_BUCKET/web",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return [{
|
||||
"name": "cache-browsers",
|
||||
"depends_on": getPipelineNames(buildWebCache(ctx)),
|
||||
"steps": restoreWebCache() + check_browser_step + webPnpmCacheSteps + browser_cache_steps,
|
||||
"when": e2e_trigger,
|
||||
}]
|
||||
|
||||
def generateWebCache(ctx):
|
||||
return [
|
||||
getWoodpeckerEnvAndCheckScript(ctx),
|
||||
@@ -2642,12 +2781,12 @@ def restoreWebCache():
|
||||
],
|
||||
}]
|
||||
|
||||
def restoreWebPnpmCache():
|
||||
def restoreWebPnpmCache(extra_commands = []):
|
||||
return [{
|
||||
"name": "restore-web-pnpm-cache",
|
||||
"image": MINIO_MC,
|
||||
"environment": MINIO_MC_ENV,
|
||||
"commands": [
|
||||
"commands": extra_commands + [
|
||||
"source ./.woodpecker.env",
|
||||
"mc alias set s3 $MC_HOST $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY",
|
||||
"mc cp -r -a s3/$CACHE_BUCKET/opencloud/web-test-runner/$WEB_COMMITID/web-pnpm.tar.gz %s" % dirs["zip"],
|
||||
@@ -2656,16 +2795,38 @@ def restoreWebPnpmCache():
|
||||
# we need to install again because the node_modules are not cached
|
||||
"name": "unzip-and-install-pnpm",
|
||||
"image": OC_CI_NODEJS % DEFAULT_NODEJS_VERSION,
|
||||
"commands": [
|
||||
"commands": extra_commands + [
|
||||
"cd %s" % dirs["web"],
|
||||
"rm -rf .pnpm-store",
|
||||
"tar -xvf %s" % dirs["webPnpmZip"],
|
||||
'npm install --silent --global --force "$(jq -r ".packageManager" < package.json)"',
|
||||
"pnpm config set store-dir ./.pnpm-store",
|
||||
"for i in $(seq 3); do pnpm install && break || sleep 1; done",
|
||||
"for i in $(seq 3); do pnpm install --no-frozen-lockfile && break || sleep 1; done",
|
||||
],
|
||||
}]
|
||||
|
||||
def restoreBrowsersCache():
|
||||
return [
|
||||
{
|
||||
"name": "restore-browsers-cache",
|
||||
"image": MINIO_MC,
|
||||
"environment": MINIO_MC_ENV,
|
||||
"commands": [
|
||||
"cd %s" % dirs["web"],
|
||||
"playwright_version=$(bash tests/woodpecker/script.sh get_playwright_version)",
|
||||
"mc alias set s3 $MC_HOST $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY",
|
||||
"mc cp -r -a s3/$CACHE_BUCKET/web/browsers-cache/$playwright_version/playwright-browsers.tar.gz %s" % dirs["web"],
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "unzip-browsers-cache",
|
||||
"image": OC_UBUNTU,
|
||||
"commands": [
|
||||
"tar -xvf /woodpecker/src/github.com/opencloud-eu/opencloud/webTestRunner/playwright-browsers.tar.gz -C .",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
def emailService():
|
||||
return [{
|
||||
"name": "email",
|
||||
@@ -2696,6 +2857,49 @@ def waitForClamavService():
|
||||
],
|
||||
}]
|
||||
|
||||
def ldapService():
|
||||
return [
|
||||
{
|
||||
"name": "ldap-server",
|
||||
"image": OPENLDAP,
|
||||
"detach": True,
|
||||
"environment": {
|
||||
"BITNAMI_DEBUG": "true",
|
||||
"LDAP_TLS_VERIFY_CLIENT": "never",
|
||||
"LDAP_ENABLE_TLS": "yes",
|
||||
"LDAP_TLS_CA_FILE": "/opt/bitnami/openldap/share/openldap.crt",
|
||||
"LDAP_TLS_CERT_FILE": "/opt/bitnami/openldap/share/openldap.crt",
|
||||
"LDAP_TLS_KEY_FILE": "/opt/bitnami/openldap/share/openldap.key",
|
||||
"LDAP_ROOT": "dc=opencloud,dc=eu",
|
||||
"LDAP_ADMIN_PASSWORD": "admin",
|
||||
},
|
||||
"commands": [
|
||||
"mkdir -p /opt/bitnami/openldap/share",
|
||||
"mkdir -p /tmp/custom-scripts",
|
||||
"mkdir -p /tmp/ldif-files",
|
||||
"cp tests/config/woodpecker/ldap/*.ldif /tmp/ldif-files/",
|
||||
"cp tests/config/woodpecker/ldap/docker-entrypoint-override.sh /tmp/custom-scripts/",
|
||||
"chmod +x /tmp/custom-scripts/docker-entrypoint-override.sh",
|
||||
"ls -la /tmp/ldif-files/",
|
||||
"/tmp/custom-scripts/docker-entrypoint-override.sh /opt/bitnami/scripts/openldap/run.sh",
|
||||
],
|
||||
"backend_options": {
|
||||
"docker": {
|
||||
"user": "0:0",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
def waitForLdapService():
|
||||
return [{
|
||||
"name": "wait-for-ldap",
|
||||
"image": OC_CI_WAIT_FOR,
|
||||
"commands": [
|
||||
"wait-for -it ldap-server:1636 -t 600",
|
||||
],
|
||||
}]
|
||||
|
||||
def fakeOffice():
|
||||
return [
|
||||
{
|
||||
|
||||
92
CHANGELOG.md
92
CHANGELOG.md
@@ -1,5 +1,97 @@
|
||||
# Changelog
|
||||
|
||||
## [3.3.0](https://github.com/opencloud-eu/opencloud/releases/tag/v3.3.0) - 2025-08-12
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@ScharfViktor, @aduffeck, @michaelstingl
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- Tenant [[#1274](https://github.com/opencloud-eu/opencloud/pull/1274)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- chore: bump web to v3.3.0 [[#1329](https://github.com/opencloud-eu/opencloud/pull/1329)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- multiTenancyTests [[#1313](https://github.com/opencloud-eu/opencloud/pull/1313)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Fix posix driver documentation in STORAGE_USERS_DRIVER description [[#1305](https://github.com/opencloud-eu/opencloud/pull/1305)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Improve indexing performance using batches [[#1306](https://github.com/opencloud-eu/opencloud/pull/1306)]
|
||||
- Do not run the timout func if the work func has run [[#1302](https://github.com/opencloud-eu/opencloud/pull/1302)]
|
||||
- Make sure to register prometheus collectors only once [[#1295](https://github.com/opencloud-eu/opencloud/pull/1295)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [decomposed] bump-version-v3.3.0 [[#1332](https://github.com/opencloud-eu/opencloud/pull/1332)]
|
||||
- [full-ci] Reva bump 2.36.0 [[#1328](https://github.com/opencloud-eu/opencloud/pull/1328)]
|
||||
- Bump reva [[#1315](https://github.com/opencloud-eu/opencloud/pull/1315)]
|
||||
|
||||
## [3.2.1](https://github.com/opencloud-eu/opencloud/releases/tag/v3.2.1) - 2025-07-30
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@aduffeck, @dragonchaser, @individual-it
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Do not try to log metrics when we failed to get the consumer info [[#1289](https://github.com/opencloud-eu/opencloud/pull/1289)]
|
||||
- Add thumbnails to sharedWithMe and sharedByMe requests [[#1257](https://github.com/opencloud-eu/opencloud/pull/1257)]
|
||||
|
||||
## [3.2.0](https://github.com/opencloud-eu/opencloud/releases/tag/v3.2.0) - 2025-07-21
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@AlexAndBear, @JammingBen, @ScharfViktor, @Svanvith, @aduffeck, @butonic, @dragonchaser, @fschade, @individual-it, @jnweiger, @micbar, @rhafer
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- Metrics [[#1242](https://github.com/opencloud-eu/opencloud/pull/1242)]
|
||||
- Add `HasTrashedItems` property to /me/drives endpoint [[#1163](https://github.com/opencloud-eu/opencloud/pull/1163)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- [full-ci] chore: bump web to v3.2.0 [[#1253](https://github.com/opencloud-eu/opencloud/pull/1253)]
|
||||
- proxy(sign_url_auth): Allow to verify server signed URLs [[#1191](https://github.com/opencloud-eu/opencloud/pull/1191)]
|
||||
- Switch to the raw nats consumer instead of the go-micro events [[#1171](https://github.com/opencloud-eu/opencloud/pull/1171)]
|
||||
- change: adjust default values for the S3 Uploads [[#1224](https://github.com/opencloud-eu/opencloud/pull/1224)]
|
||||
- feat(web): add dark mode and adjust light theme colors [[#1188](https://github.com/opencloud-eu/opencloud/pull/1188)]
|
||||
- change: set better decomposedS3 defaults for multipart upload [[#1200](https://github.com/opencloud-eu/opencloud/pull/1200)]
|
||||
- add missing full username mapper to the full example [[#1181](https://github.com/opencloud-eu/opencloud/pull/1181)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix ready checks [[#1222](https://github.com/opencloud-eu/opencloud/pull/1222)]
|
||||
- Update config.go [[#1183](https://github.com/opencloud-eu/opencloud/pull/1183)]
|
||||
- Fix wrong build version [[#1210](https://github.com/opencloud-eu/opencloud/pull/1210)]
|
||||
- Update Makefile [[#1187](https://github.com/opencloud-eu/opencloud/pull/1187)]
|
||||
- fix(collaboration): re register app providers in a configurable interval [[#1035](https://github.com/opencloud-eu/opencloud/pull/1035)]
|
||||
- Fix lico idp doesn't load opencloud font anymore [[#1153](https://github.com/opencloud-eu/opencloud/pull/1153)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [decomposed] bump-version-v3.2.0 [[#1258](https://github.com/opencloud-eu/opencloud/pull/1258)]
|
||||
- [full-ci] Reva bump 2.35.0 [[#1255](https://github.com/opencloud-eu/opencloud/pull/1255)]
|
||||
- build(deps): bump golang.org/x/net from 0.41.0 to 0.42.0 [[#1232](https://github.com/opencloud-eu/opencloud/pull/1232)]
|
||||
- build(deps): bump github.com/KimMachineGun/automemlimit from 0.7.3 to 0.7.4 [[#1226](https://github.com/opencloud-eu/opencloud/pull/1226)]
|
||||
- build(deps): bump golang.org/x/text from 0.26.0 to 0.27.0 [[#1227](https://github.com/opencloud-eu/opencloud/pull/1227)]
|
||||
- build(deps): bump golang.org/x/sync from 0.15.0 to 0.16.0 [[#1209](https://github.com/opencloud-eu/opencloud/pull/1209)]
|
||||
- build(deps): bump golang.org/x/term from 0.32.0 to 0.33.0 [[#1208](https://github.com/opencloud-eu/opencloud/pull/1208)]
|
||||
- build(deps): bump github.com/olekukonko/tablewriter from 1.0.7 to 1.0.8 [[#1174](https://github.com/opencloud-eu/opencloud/pull/1174)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.11.5 to 2.11.6 [[#1164](https://github.com/opencloud-eu/opencloud/pull/1164)]
|
||||
- build(deps): bump github.com/go-playground/validator/v10 from 10.26.0 to 10.27.0 [[#1165](https://github.com/opencloud-eu/opencloud/pull/1165)]
|
||||
- build(deps): bump github.com/pkg/xattr from 0.4.11 to 0.4.12 [[#1156](https://github.com/opencloud-eu/opencloud/pull/1156)]
|
||||
- build(deps): bump go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp from 0.61.0 to 0.62.0 [[#1155](https://github.com/opencloud-eu/opencloud/pull/1155)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.5.1 to 1.6.0 [[#1148](https://github.com/opencloud-eu/opencloud/pull/1148)]
|
||||
- build(deps): bump github.com/oklog/run from 1.1.0 to 1.2.0 [[#1150](https://github.com/opencloud-eu/opencloud/pull/1150)]
|
||||
|
||||
## [3.1.0](https://github.com/opencloud-eu/opencloud/releases/tag/v3.1.0) - 2025-06-30
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@@ -58,14 +58,15 @@ mkdir ${sandbox} && cd ${sandbox}
|
||||
# The operating system
|
||||
os="linux"
|
||||
|
||||
if [[ $OSTYPE == 'darwin'* ]]; then
|
||||
if [[ "$OSTYPE" == 'darwin'* ]]; then
|
||||
os="darwin"
|
||||
fi
|
||||
|
||||
# The platform
|
||||
dlarch="amd64"
|
||||
|
||||
if [[ $(uname -s) == "Darwin" && $(uname -m) == "arm64" ]]; then
|
||||
if [[ ( "$(uname -s)" == "Darwin" && "$(uname -m)" == "arm64" ) ||
|
||||
( "$(uname -s)" == "Linux" && "$(uname -m)" == "aarch64" ) ]]; then
|
||||
dlarch="arm64"
|
||||
fi
|
||||
|
||||
|
||||
@@ -2166,6 +2166,23 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "96bc2621-a714-4f15-ac1d-bc32df94382d",
|
||||
"name": "display name",
|
||||
"providerId": "full-name-ldap-mapper",
|
||||
"subComponents": {},
|
||||
"config": {
|
||||
"read.only": [
|
||||
"false"
|
||||
],
|
||||
"write.only": [
|
||||
"true"
|
||||
],
|
||||
"ldap.full.name.attribute": [
|
||||
"displayName"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cab8b569-0f50-4e13-b2a5-d24ee513cd8b",
|
||||
"name": "first name",
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
title: "1. Simple Multi-Tenancy using a single OpenCloud Instance"
|
||||
---
|
||||
|
||||
* Status: proposed
|
||||
* Deciders: [@micbar @butonic @dragotin @rhafer]
|
||||
* Date: 2025-05-20
|
||||
|
||||
Technical Story: https://github.com/opencloud-eu/opencloud/issues/877
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
To reduce resource usage and cost service providers want a single OpenCloud
|
||||
instance to host multiple tenants. Members of the same tenant should be able to
|
||||
only see each other when trying to share resources. A user can only be a member
|
||||
of a single tenant. Moving a user from one tenant to another is not supported.
|
||||
|
||||
OpenCloud does currently not have any concept of multi-tenancy. All users able to
|
||||
login to an OpenCloud instance are able to see each other and share resources with
|
||||
everybody. This ADR is supposed to layout a concept for a minimal multi-tenancy
|
||||
solution that implements the characteristics mentioned above.
|
||||
|
||||
To further limit the scope there are a couple of constraints:
|
||||
|
||||
- Tenants are rather small (sometimes just a single user, often less than 10)
|
||||
- There is just a single IDP with a single "realm".
|
||||
- The user-management is external to OpenCloud
|
||||
- The membership of a user to a tenant is represented by a tenant id
|
||||
that is provided via a claim in the users' Access Token/UserInfo or by a per User
|
||||
Attribute in the LDAP directory.
|
||||
- There is no need to support per tenant groups
|
||||
- There is no need to isolate the storage of the tenants from each other
|
||||
- Role Assignment happens at first login via OIDC claims
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
* Low Resource Overhead: The solution should not require much additional
|
||||
resources (CPU, Memory, Storage) per tenant on the OpenCloud instance.
|
||||
* Implementation effort: The solution should be easy to implement and maintain.
|
||||
* Security: The solution should prevent users from seeing or accessing anything
|
||||
from other tenants.
|
||||
|
||||
## Considered Options
|
||||
|
||||
### Option 1: Tenant ID as a new property of the CS3 UserId
|
||||
|
||||
The CS3 UserId (https://buf.build/cs3org-buf/cs3apis/docs/main:cs3.identity.user.v1beta1#cs3.identity.user.v1beta1.UserId)
|
||||
is extended by a new property "tenantId".
|
||||
|
||||
#### Pros:
|
||||
|
||||
* Everywhere the UserID is used the tenant id is also available.
|
||||
* This might allow implementing more sophisticated checks e.g. on permission
|
||||
grants and to a certain extend during share creation.
|
||||
* the tenant id is immediately available e.g. in Events/Auditlog without an additional
|
||||
user lookup
|
||||
|
||||
#### Cons:
|
||||
|
||||
* Requires changes to the CS3 API
|
||||
* Adds even more semantics to the UserId. Ideally the UserID would just be an
|
||||
opaque identifier without carrying and specific semantics other than being
|
||||
globally unique.
|
||||
* on the GraphAPI the ID of a User is just a opaque string without any additional
|
||||
meaning. (Currently it is just using the `OpaqueId` property of the CS3 UserId,
|
||||
without considering the `idp` property.)
|
||||
|
||||
### Option 2: Tenant ID is stored as the `idp` value of the CS3 UserId
|
||||
|
||||
Instead of introducing a new property the on the CS3 UserId we'll just override
|
||||
the `idp` value with the tenant id.
|
||||
|
||||
#### Pros
|
||||
|
||||
* No changes to the CS3 API required
|
||||
* The pros of Option 1 apply here as well
|
||||
|
||||
#### Cons:
|
||||
|
||||
* It's a crutch, we're already "abusing" the `idp` property of the CS3 UserId
|
||||
to have a different meaning in the context of federated sharing. Adding an
|
||||
additiona meaning could make the code even more complicated.
|
||||
* Apart from the API change the Cons of Option 1 apply here as well.
|
||||
|
||||
### Option 3: Tenant ID is a property of the CS3 User Object
|
||||
|
||||
A new (optional) property "tenantId" is added to the CS3 User Object.
|
||||
|
||||
#### Pros:
|
||||
|
||||
* Avoid overloading CS3 UserId with additional semantics.
|
||||
* The tenant id is available everywhere the User Object is used.
|
||||
|
||||
#### Cons:
|
||||
|
||||
* Requires changes to the CS3 API
|
||||
* Might require more user lookups in places where we need to find out the
|
||||
tenant id of a specific user
|
||||
|
||||
### Option 4: Tenant ID is invisible to the CS3 API
|
||||
|
||||
Reva Tokens would get a new property `tenantId`. To have the tenant id available
|
||||
of the signed in user available with every request.
|
||||
While not being part of the CS3 API objects the `users` service will be made "tenant aware"
|
||||
so that is only returns users of the same tenant as the requesting user e.g. by using
|
||||
proper LDAP filters or subtree searches.
|
||||
|
||||
#### Pros:
|
||||
|
||||
* No changes to the CS3 API required
|
||||
* Code changes could be limited to the `users` and`share-provider` services and the reva token manager
|
||||
* The tenant id of the current user is available everywhere the reva token is used.
|
||||
|
||||
#### Cons:
|
||||
|
||||
* Can this work with the App Token feature if the Tenant Id is not part of the User Object in
|
||||
any way, how can the the token manager know with Id to add to the token?
|
||||
* What about places where the system user is doing stuff on behalf of a user
|
||||
|
||||
### Option 5: Tenant membership via LDAP group membership
|
||||
|
||||
All members of a tenant are assigned to the same group. The `users` service gets a config
|
||||
switch to only allow users to search for users that are part of the same group.
|
||||
|
||||
#### Pros:
|
||||
|
||||
* ?
|
||||
|
||||
#### Cons:
|
||||
|
||||
* The group needs to be hidden somehow from the `groups` service as this is not supposed to be a
|
||||
"sharing group".
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
### Chosen option: Option 1 - Tenant ID as a new property of the CS3 UserId
|
||||
|
||||
As part of the OIDC Connect Authentication OpenCloud will receive a tenant id via the
|
||||
Access Token or Userinfo endpoint. For the initial implementation it is assume that OpenCloud
|
||||
has read access to a shared LDAP server, that contains all users of all tenants. Users in
|
||||
LDAP will have a the tenant id as an dedicated attribute.
|
||||
|
||||
### Implementation Steps
|
||||
|
||||
* Extend the CS3 UserId with a new property "tenantId"
|
||||
* Adapt the `users` service` to only return users that are part of the same tenant
|
||||
* To cleanup technical debt and reduce code duplication the `cs3` users backend in the
|
||||
graph service is being improved so that is can fully replace the `LDAP` users backend for
|
||||
read operations.
|
||||
* The reva `share-provider` service only allow shares between users of the same tenant
|
||||
|
||||
34
go.mod
34
go.mod
@@ -5,7 +5,7 @@ go 1.24.1
|
||||
require (
|
||||
dario.cat/mergo v1.0.2
|
||||
github.com/CiscoM31/godata v1.0.11
|
||||
github.com/KimMachineGun/automemlimit v0.7.3
|
||||
github.com/KimMachineGun/automemlimit v0.7.4
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/MicahParks/keyfunc/v2 v2.1.0
|
||||
github.com/Nerzal/gocloak/v13 v13.9.0
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/blevesearch/bleve/v2 v2.5.2
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/coreos/go-oidc/v3 v3.14.1
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20250218144737-544dd3919658
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20250725064958-2d9caef4db2a
|
||||
github.com/davidbyttow/govips/v2 v2.16.0
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
|
||||
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
|
||||
@@ -35,7 +35,7 @@ require (
|
||||
github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry v1.2.0
|
||||
github.com/go-playground/validator/v10 v10.27.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/go-tika v0.3.1
|
||||
@@ -58,13 +58,13 @@ require (
|
||||
github.com/nats-io/nats-server/v2 v2.11.6
|
||||
github.com/nats-io/nats.go v1.43.0
|
||||
github.com/oklog/run v1.2.0
|
||||
github.com/olekukonko/tablewriter v1.0.7
|
||||
github.com/olekukonko/tablewriter v1.0.8
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/open-policy-agent/opa v1.6.0
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450
|
||||
github.com/opencloud-eu/reva/v2 v2.34.0
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
|
||||
github.com/opencloud-eu/reva/v2 v2.36.0
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/xattr v0.4.12
|
||||
@@ -97,16 +97,16 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0
|
||||
go.opentelemetry.io/otel/sdk v1.37.0
|
||||
go.opentelemetry.io/otel/trace v1.37.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
|
||||
golang.org/x/image v0.28.0
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/net v0.42.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/term v0.32.0
|
||||
golang.org/x/text v0.26.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/term v0.33.0
|
||||
golang.org/x/text v0.27.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822
|
||||
google.golang.org/grpc v1.73.0
|
||||
google.golang.org/grpc v1.74.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
@@ -317,9 +317,9 @@ require (
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/yashtewari/glob-intersection v0.2.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.1 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.1 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.1 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.2 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.2 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
|
||||
@@ -329,9 +329,9 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
|
||||
|
||||
68
go.sum
68
go.sum
@@ -68,8 +68,8 @@ github.com/CiscoM31/godata v1.0.11 h1:w7y8twuW02LdH6mak3/GJ5i0GrCv2IoZUJVqa/g5Ye
|
||||
github.com/CiscoM31/godata v1.0.11/go.mod h1:ZMiT6JuD3Rm83HEtiTx4JEChsd25YCrxchKGag/sdTc=
|
||||
github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c h1:ocsNvQ2tNHme4v/lTs17HROamc7mFzZfzWcg4m+UXN0=
|
||||
github.com/DeepDiver1975/secure v0.0.0-20240611112133-abc838fb797c/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/KimMachineGun/automemlimit v0.7.3 h1:oPgMp0bsWez+4fvgSa11Rd9nUDrd8RLtDjBoT3ro+/A=
|
||||
github.com/KimMachineGun/automemlimit v0.7.3/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
|
||||
github.com/KimMachineGun/automemlimit v0.7.4 h1:UY7QYOIfrr3wjjOAqahFmC3IaQCLWvur9nmfIn6LnWk=
|
||||
github.com/KimMachineGun/automemlimit v0.7.4/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
@@ -244,8 +244,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo
|
||||
github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4=
|
||||
github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
|
||||
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20250218144737-544dd3919658 h1:CmH7twDuNUrHQXChZMafWjsEp1V47KutJlOAt6FjzGA=
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20250218144737-544dd3919658/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ=
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20250725064958-2d9caef4db2a h1:4IvTz3MUno/nlgngdyZhkyxzJR/w7+H+2ZXoZQKidgg=
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20250725064958-2d9caef4db2a/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ=
|
||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
@@ -459,8 +459,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -848,8 +848,8 @@ github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5
|
||||
github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc=
|
||||
github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
|
||||
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ=
|
||||
github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
@@ -866,10 +866,10 @@ github.com/open-policy-agent/opa v1.6.0 h1:/S/cnNQJ2MUMNzizHPbisTWBHowmLkPrugY5j
|
||||
github.com/open-policy-agent/opa v1.6.0/go.mod h1:zFmw4P+W62+CWGYRDDswfVYSCnPo6oYaktQnfIaRFC4=
|
||||
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M=
|
||||
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450 h1:QWn9G2f1R/EbyZSbkjtd9jqNq9X0NIphmmD4KYLNZtA=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
|
||||
github.com/opencloud-eu/reva/v2 v2.34.0 h1:9oiMWj3wAaooddgq2hskD6e+JBbdqzQNVublPMkhKDs=
|
||||
github.com/opencloud-eu/reva/v2 v2.34.0/go.mod h1:hfXmgujx6teOLoh5dbDINaAgHYwXZZr9qYczxU/h+do=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 h1:vD/EdfDUrv4omSFjrinT8Mvf+8D7f9g4vgQ2oiDrVUI=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
|
||||
github.com/opencloud-eu/reva/v2 v2.36.0 h1:5FBjhXqW8F4v7F76vGYpH7IGuRtcbKHoyOyj3syG7W8=
|
||||
github.com/opencloud-eu/reva/v2 v2.36.0/go.mod h1:/FyYaUWxtllu8TOcIIx53BjChc+hSpcQicBI/OTICjw=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
@@ -1157,12 +1157,12 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
||||
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
||||
go.etcd.io/etcd/api/v3 v3.6.1 h1:yJ9WlDih9HT457QPuHt/TH/XtsdN2tubyxyQHSHPsEo=
|
||||
go.etcd.io/etcd/api/v3 v3.6.1/go.mod h1:lnfuqoGsXMlZdTJlact3IB56o3bWp1DIlXPIGKRArto=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.1 h1:CxDVv8ggphmamrXM4Of8aCC8QHzDM4tGcVr9p2BSoGk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.1/go.mod h1:aTkCp+6ixcVTZmrJGa7/Mc5nMNs59PEgBbq+HCmWyMc=
|
||||
go.etcd.io/etcd/client/v3 v3.6.1 h1:KelkcizJGsskUXlsxjVrSmINvMMga0VWwFF0tSPGEP0=
|
||||
go.etcd.io/etcd/client/v3 v3.6.1/go.mod h1:fCbPUdjWNLfx1A6ATo9syUmFVxqHH9bCnPLBZmnLmMY=
|
||||
go.etcd.io/etcd/api/v3 v3.6.2 h1:25aCkIMjUmiiOtnBIp6PhNj4KdcURuBak0hU2P1fgRc=
|
||||
go.etcd.io/etcd/api/v3 v3.6.2/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.2 h1:zw+HRghi/G8fKpgKdOcEKpnBTE4OO39T6MegA0RopVU=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.2/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI=
|
||||
go.etcd.io/etcd/client/v3 v3.6.2 h1:RgmcLJxkpHqpFvgKNwAQHX3K+wsSARMXKgjmUSpoSKQ=
|
||||
go.etcd.io/etcd/client/v3 v3.6.2/go.mod h1:PL7e5QMKzjybn0FosgiWvCUDzvdChpo5UgGR4Sk4Gzc=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -1239,8 +1239,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1338,8 +1338,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1366,8 +1366,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1448,8 +1448,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -1461,8 +1461,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1478,8 +1478,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1542,8 +1542,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1624,8 +1624,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/grpc v1.74.0 h1:sxRSkyLxlceWQiqDofxDot3d4u7DyoHPc7SBXMj8gGY=
|
||||
google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s=
|
||||
google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e h1:m7aQHHqd0q89mRwhwS9Bx2rjyl/hsFAeta+uGrHsQaU=
|
||||
google.golang.org/grpc/examples v0.0.0-20211102180624-670c133e568e/go.mod h1:gID3PKrg7pWKntu9Ss6zTLJ0ttC0X9IHgREOCZwbCVU=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
|
||||
@@ -521,7 +521,15 @@ func trap(s *Service, ctx context.Context) {
|
||||
func pingNats(cfg *occfg.Config) error {
|
||||
// We need to get a natsconfig from somewhere. We can use any one.
|
||||
evcfg := cfg.Postprocessing.Postprocessing.Events
|
||||
_, err := stream.NatsFromConfig("initial", true, stream.NatsConfig(evcfg))
|
||||
_, err := stream.NatsFromConfig("initial", true, stream.NatsConfig{
|
||||
Endpoint: evcfg.Endpoint,
|
||||
Cluster: evcfg.Cluster,
|
||||
EnableTLS: evcfg.EnableTLS,
|
||||
TLSInsecure: evcfg.TLSInsecure,
|
||||
TLSRootCACertificate: evcfg.TLSRootCACertificate,
|
||||
AuthUsername: evcfg.AuthUsername,
|
||||
AuthPassword: evcfg.AuthPassword,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ var (
|
||||
// LatestTag is the latest released version plus the dev meta version.
|
||||
// Will be overwritten by the release pipeline
|
||||
// Needs a manual change for every tagged release
|
||||
LatestTag = "3.1.0+dev"
|
||||
LatestTag = "3.3.0+dev"
|
||||
|
||||
// Date indicates the build date.
|
||||
// This has been removed, it looks like you can only replace static strings with recent go versions
|
||||
|
||||
@@ -9,3 +9,7 @@ include ../.make/default.mk
|
||||
.PHONY: go-generate
|
||||
go-generate: $(MOCKERY)
|
||||
$(MOCKERY)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(NOOP)
|
||||
|
||||
@@ -79,9 +79,9 @@ type EventHistoryService_GetEvents_Call struct {
|
||||
}
|
||||
|
||||
// GetEvents is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.GetEventsRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *EventHistoryService_Expecter) GetEvents(ctx interface{}, in interface{}, opts ...interface{}) *EventHistoryService_GetEvents_Call {
|
||||
return &EventHistoryService_GetEvents_Call{Call: _e.mock.On("GetEvents",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -89,8 +89,25 @@ func (_e *EventHistoryService_Expecter) GetEvents(ctx interface{}, in interface{
|
||||
|
||||
func (_c *EventHistoryService_GetEvents_Call) Run(run func(ctx context.Context, in *v0.GetEventsRequest, opts ...client.CallOption)) *EventHistoryService_GetEvents_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.GetEventsRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.GetEventsRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.GetEventsRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -145,9 +162,9 @@ type EventHistoryService_GetEventsForUser_Call struct {
|
||||
}
|
||||
|
||||
// GetEventsForUser is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.GetEventsForUserRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *EventHistoryService_Expecter) GetEventsForUser(ctx interface{}, in interface{}, opts ...interface{}) *EventHistoryService_GetEventsForUser_Call {
|
||||
return &EventHistoryService_GetEventsForUser_Call{Call: _e.mock.On("GetEventsForUser",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -155,8 +172,25 @@ func (_e *EventHistoryService_Expecter) GetEventsForUser(ctx interface{}, in int
|
||||
|
||||
func (_c *EventHistoryService_GetEventsForUser_Call) Run(run func(ctx context.Context, in *v0.GetEventsForUserRequest, opts ...client.CallOption)) *EventHistoryService_GetEventsForUser_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.GetEventsForUserRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.GetEventsForUserRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.GetEventsForUserRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ type PoliciesProviderService_Evaluate_Call struct {
|
||||
}
|
||||
|
||||
// Evaluate is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.EvaluateRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *PoliciesProviderService_Expecter) Evaluate(ctx interface{}, in interface{}, opts ...interface{}) *PoliciesProviderService_Evaluate_Call {
|
||||
return &PoliciesProviderService_Evaluate_Call{Call: _e.mock.On("Evaluate",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -89,8 +89,25 @@ func (_e *PoliciesProviderService_Expecter) Evaluate(ctx interface{}, in interfa
|
||||
|
||||
func (_c *PoliciesProviderService_Evaluate_Call) Run(run func(ctx context.Context, in *v0.EvaluateRequest, opts ...client.CallOption)) *PoliciesProviderService_Evaluate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.EvaluateRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.EvaluateRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.EvaluateRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ type SearchProviderService_IndexSpace_Call struct {
|
||||
}
|
||||
|
||||
// IndexSpace is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.IndexSpaceRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *SearchProviderService_Expecter) IndexSpace(ctx interface{}, in interface{}, opts ...interface{}) *SearchProviderService_IndexSpace_Call {
|
||||
return &SearchProviderService_IndexSpace_Call{Call: _e.mock.On("IndexSpace",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -89,8 +89,25 @@ func (_e *SearchProviderService_Expecter) IndexSpace(ctx interface{}, in interfa
|
||||
|
||||
func (_c *SearchProviderService_IndexSpace_Call) Run(run func(ctx context.Context, in *v0.IndexSpaceRequest, opts ...client.CallOption)) *SearchProviderService_IndexSpace_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.IndexSpaceRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.IndexSpaceRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.IndexSpaceRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -145,9 +162,9 @@ type SearchProviderService_Search_Call struct {
|
||||
}
|
||||
|
||||
// Search is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.SearchRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *SearchProviderService_Expecter) Search(ctx interface{}, in interface{}, opts ...interface{}) *SearchProviderService_Search_Call {
|
||||
return &SearchProviderService_Search_Call{Call: _e.mock.On("Search",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -155,8 +172,25 @@ func (_e *SearchProviderService_Expecter) Search(ctx interface{}, in interface{}
|
||||
|
||||
func (_c *SearchProviderService_Search_Call) Run(run func(ctx context.Context, in *v0.SearchRequest, opts ...client.CallOption)) *SearchProviderService_Search_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.SearchRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.SearchRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.SearchRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ type ValueService_GetValue_Call struct {
|
||||
}
|
||||
|
||||
// GetValue is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.GetValueRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *ValueService_Expecter) GetValue(ctx interface{}, in interface{}, opts ...interface{}) *ValueService_GetValue_Call {
|
||||
return &ValueService_GetValue_Call{Call: _e.mock.On("GetValue",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -89,8 +89,25 @@ func (_e *ValueService_Expecter) GetValue(ctx interface{}, in interface{}, opts
|
||||
|
||||
func (_c *ValueService_GetValue_Call) Run(run func(ctx context.Context, in *v0.GetValueRequest, opts ...client.CallOption)) *ValueService_GetValue_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.GetValueRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.GetValueRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.GetValueRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -145,9 +162,9 @@ type ValueService_GetValueByUniqueIdentifiers_Call struct {
|
||||
}
|
||||
|
||||
// GetValueByUniqueIdentifiers is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.GetValueByUniqueIdentifiersRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *ValueService_Expecter) GetValueByUniqueIdentifiers(ctx interface{}, in interface{}, opts ...interface{}) *ValueService_GetValueByUniqueIdentifiers_Call {
|
||||
return &ValueService_GetValueByUniqueIdentifiers_Call{Call: _e.mock.On("GetValueByUniqueIdentifiers",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -155,8 +172,25 @@ func (_e *ValueService_Expecter) GetValueByUniqueIdentifiers(ctx interface{}, in
|
||||
|
||||
func (_c *ValueService_GetValueByUniqueIdentifiers_Call) Run(run func(ctx context.Context, in *v0.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption)) *ValueService_GetValueByUniqueIdentifiers_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.GetValueByUniqueIdentifiersRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.GetValueByUniqueIdentifiersRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.GetValueByUniqueIdentifiersRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -211,9 +245,9 @@ type ValueService_ListValues_Call struct {
|
||||
}
|
||||
|
||||
// ListValues is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.ListValuesRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *ValueService_Expecter) ListValues(ctx interface{}, in interface{}, opts ...interface{}) *ValueService_ListValues_Call {
|
||||
return &ValueService_ListValues_Call{Call: _e.mock.On("ListValues",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -221,8 +255,25 @@ func (_e *ValueService_Expecter) ListValues(ctx interface{}, in interface{}, opt
|
||||
|
||||
func (_c *ValueService_ListValues_Call) Run(run func(ctx context.Context, in *v0.ListValuesRequest, opts ...client.CallOption)) *ValueService_ListValues_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.ListValuesRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.ListValuesRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.ListValuesRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
@@ -277,9 +328,9 @@ type ValueService_SaveValue_Call struct {
|
||||
}
|
||||
|
||||
// SaveValue is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.SaveValueRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *ValueService_Expecter) SaveValue(ctx interface{}, in interface{}, opts ...interface{}) *ValueService_SaveValue_Call {
|
||||
return &ValueService_SaveValue_Call{Call: _e.mock.On("SaveValue",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -287,8 +338,25 @@ func (_e *ValueService_Expecter) SaveValue(ctx interface{}, in interface{}, opts
|
||||
|
||||
func (_c *ValueService_SaveValue_Call) Run(run func(ctx context.Context, in *v0.SaveValueRequest, opts ...client.CallOption)) *ValueService_SaveValue_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.SaveValueRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.SaveValueRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.SaveValueRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ type ThumbnailService_GetThumbnail_Call struct {
|
||||
}
|
||||
|
||||
// GetThumbnail is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - in
|
||||
// - opts
|
||||
// - ctx context.Context
|
||||
// - in *v0.GetThumbnailRequest
|
||||
// - opts ...client.CallOption
|
||||
func (_e *ThumbnailService_Expecter) GetThumbnail(ctx interface{}, in interface{}, opts ...interface{}) *ThumbnailService_GetThumbnail_Call {
|
||||
return &ThumbnailService_GetThumbnail_Call{Call: _e.mock.On("GetThumbnail",
|
||||
append([]interface{}{ctx, in}, opts...)...)}
|
||||
@@ -89,8 +89,25 @@ func (_e *ThumbnailService_Expecter) GetThumbnail(ctx interface{}, in interface{
|
||||
|
||||
func (_c *ThumbnailService_GetThumbnail_Call) Run(run func(ctx context.Context, in *v0.GetThumbnailRequest, opts ...client.CallOption)) *ThumbnailService_GetThumbnail_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
variadicArgs := args[2].([]client.CallOption)
|
||||
run(args[0].(context.Context), args[1].(*v0.GetThumbnailRequest), variadicArgs...)
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *v0.GetThumbnailRequest
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*v0.GetThumbnailRequest)
|
||||
}
|
||||
var arg2 []client.CallOption
|
||||
var variadicArgs []client.CallOption
|
||||
if len(args) > 2 {
|
||||
variadicArgs = args[2].([]client.CallOption)
|
||||
}
|
||||
arg2 = variadicArgs
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2...,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Ivan Fustero, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Ivan Fustero, 2025\n"
|
||||
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ca\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/response.go:44
|
||||
msgid "description"
|
||||
msgstr "descripció"
|
||||
|
||||
#: pkg/service/response.go:43
|
||||
msgid "display name"
|
||||
msgstr "nom a mostrar"
|
||||
|
||||
#: pkg/service/response.go:42
|
||||
msgid "expiration date"
|
||||
msgstr "data de venciment"
|
||||
|
||||
#: pkg/service/response.go:41
|
||||
msgid "password"
|
||||
msgstr "contrasenya"
|
||||
|
||||
#: pkg/service/response.go:40
|
||||
msgid "permission"
|
||||
msgstr "permís"
|
||||
|
||||
#: pkg/service/response.go:39
|
||||
msgid "some field"
|
||||
msgstr "algun camp"
|
||||
|
||||
#: pkg/service/response.go:26
|
||||
msgid "{resource} was downloaded via public link {token}"
|
||||
msgstr "{resource} s'ha baixat a través de l'enllaç públic {token}"
|
||||
|
||||
#: pkg/service/response.go:24
|
||||
msgid "{user} added {resource} to {folder}"
|
||||
msgstr "{user} ha afegit {resource} a {folder}"
|
||||
|
||||
#: pkg/service/response.go:36
|
||||
msgid "{user} added {sharee} as member of {space}"
|
||||
msgstr "{user} ha afegit {resource} a {folder}"
|
||||
|
||||
#: pkg/service/response.go:27
|
||||
msgid "{user} deleted {resource} from {folder}"
|
||||
msgstr "{user} ha suprimit {resource} de {folder}"
|
||||
|
||||
#: pkg/service/response.go:28
|
||||
msgid "{user} moved {resource} to {folder}"
|
||||
msgstr "{user} ha mogut {resource} cap a {folder}"
|
||||
|
||||
#: pkg/service/response.go:35
|
||||
msgid "{user} removed link to {resource}"
|
||||
msgstr " {user} ha suprimit l'enllaç de {resource}"
|
||||
|
||||
#: pkg/service/response.go:32
|
||||
msgid "{user} removed {sharee} from {resource}"
|
||||
msgstr "{user} ha suprimit {sharee} de {resource}"
|
||||
|
||||
#: pkg/service/response.go:37
|
||||
msgid "{user} removed {sharee} from {space}"
|
||||
msgstr "{user} ha suprimit {sharee} de {space}"
|
||||
|
||||
#: pkg/service/response.go:29
|
||||
msgid "{user} renamed {oldResource} to {resource}"
|
||||
msgstr "{user} ha renombrat {oldResource} a {resource}"
|
||||
|
||||
#: pkg/service/response.go:33
|
||||
msgid "{user} shared {resource} via link"
|
||||
msgstr "{user} ha compartit {resource} via enllaç"
|
||||
|
||||
#: pkg/service/response.go:30
|
||||
msgid "{user} shared {resource} with {sharee}"
|
||||
msgstr "{user} ha compartit {resource} amb {sharee}"
|
||||
|
||||
#: pkg/service/response.go:34
|
||||
msgid "{user} updated {field} for a link {token} on {resource}"
|
||||
msgstr "{user} ha actualitzat {field} per l'enllaç {token} a {resource}"
|
||||
|
||||
#: pkg/service/response.go:31
|
||||
msgid "{user} updated {field} for the {resource}"
|
||||
msgstr "{user} ha actualitzat {field} per al {resource}"
|
||||
|
||||
#: pkg/service/response.go:25
|
||||
msgid "{user} updated {resource} in {folder}"
|
||||
msgstr "{user} ha actualitzat {resource} a {folder}"
|
||||
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-01-27 11:01+0100\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Jörn Friedrich Dreyer <jfd@butonic.de>, 2025\n"
|
||||
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Elías Martín, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Elías Martín, 2025\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: pkg/service/response.go:44
|
||||
msgid "description"
|
||||
msgstr "descripción"
|
||||
|
||||
#: pkg/service/response.go:43
|
||||
msgid "display name"
|
||||
msgstr "nombre mostrado"
|
||||
|
||||
#: pkg/service/response.go:42
|
||||
msgid "expiration date"
|
||||
msgstr "fecha de expiración"
|
||||
|
||||
#: pkg/service/response.go:41
|
||||
msgid "password"
|
||||
msgstr "contraseña"
|
||||
|
||||
#: pkg/service/response.go:40
|
||||
msgid "permission"
|
||||
msgstr "permiso"
|
||||
|
||||
#: pkg/service/response.go:39
|
||||
msgid "some field"
|
||||
msgstr "algún campo"
|
||||
|
||||
#: pkg/service/response.go:26
|
||||
msgid "{resource} was downloaded via public link {token}"
|
||||
msgstr "{resource} fué descargado vía link público {token}"
|
||||
|
||||
#: pkg/service/response.go:24
|
||||
msgid "{user} added {resource} to {folder}"
|
||||
msgstr "{user} añadió {resource} a {folder}"
|
||||
|
||||
#: pkg/service/response.go:36
|
||||
msgid "{user} added {sharee} as member of {space}"
|
||||
msgstr "{user} añadió {sharee} como miembro de {space}"
|
||||
|
||||
#: pkg/service/response.go:27
|
||||
msgid "{user} deleted {resource} from {folder}"
|
||||
msgstr "{user} eliminó {resource} de {folder}"
|
||||
|
||||
#: pkg/service/response.go:28
|
||||
msgid "{user} moved {resource} to {folder}"
|
||||
msgstr "{user} movió {resource} a {folder}"
|
||||
|
||||
#: pkg/service/response.go:35
|
||||
msgid "{user} removed link to {resource}"
|
||||
msgstr "{user} eliminó el link a {resource}"
|
||||
|
||||
#: pkg/service/response.go:32
|
||||
msgid "{user} removed {sharee} from {resource}"
|
||||
msgstr "{user} eliminó {sharee} de {resource}"
|
||||
|
||||
#: pkg/service/response.go:37
|
||||
msgid "{user} removed {sharee} from {space}"
|
||||
msgstr "{user} eliminó {sharee} de {space}"
|
||||
|
||||
#: pkg/service/response.go:29
|
||||
msgid "{user} renamed {oldResource} to {resource}"
|
||||
msgstr "{user} renombró {oldResource} a {resource}"
|
||||
|
||||
#: pkg/service/response.go:33
|
||||
msgid "{user} shared {resource} via link"
|
||||
msgstr "{user} compartió {resource} vía enlace"
|
||||
|
||||
#: pkg/service/response.go:30
|
||||
msgid "{user} shared {resource} with {sharee}"
|
||||
msgstr "{user} compartió {resource} con {sharee}"
|
||||
|
||||
#: pkg/service/response.go:34
|
||||
msgid "{user} updated {field} for a link {token} on {resource}"
|
||||
msgstr "{user} actualizó {field} para el link {token} en {resource}"
|
||||
|
||||
#: pkg/service/response.go:31
|
||||
msgid "{user} updated {field} for the {resource}"
|
||||
msgstr "{user} actualizó {field} para el {resource}"
|
||||
|
||||
#: pkg/service/response.go:25
|
||||
msgid "{user} updated {resource} in {folder}"
|
||||
msgstr "{user} actualizó {resource} en {folder}"
|
||||
@@ -0,0 +1,102 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# eric_G <junk.eg@free.fr>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
|
||||
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: pkg/service/response.go:44
|
||||
msgid "description"
|
||||
msgstr "description"
|
||||
|
||||
#: pkg/service/response.go:43
|
||||
msgid "display name"
|
||||
msgstr "nom d'affichage"
|
||||
|
||||
#: pkg/service/response.go:42
|
||||
msgid "expiration date"
|
||||
msgstr "date d'expiration"
|
||||
|
||||
#: pkg/service/response.go:41
|
||||
msgid "password"
|
||||
msgstr "mot de passe"
|
||||
|
||||
#: pkg/service/response.go:40
|
||||
msgid "permission"
|
||||
msgstr "permission"
|
||||
|
||||
#: pkg/service/response.go:39
|
||||
msgid "some field"
|
||||
msgstr "un certain champ"
|
||||
|
||||
#: pkg/service/response.go:26
|
||||
msgid "{resource} was downloaded via public link {token}"
|
||||
msgstr "{resource} a été téléchargé via le lien public {token}"
|
||||
|
||||
#: pkg/service/response.go:24
|
||||
msgid "{user} added {resource} to {folder}"
|
||||
msgstr "{user} a ajouté {resource} à {folder}"
|
||||
|
||||
#: pkg/service/response.go:36
|
||||
msgid "{user} added {sharee} as member of {space}"
|
||||
msgstr "{user} a ajouté {sharee} en tant que membre de {space}"
|
||||
|
||||
#: pkg/service/response.go:27
|
||||
msgid "{user} deleted {resource} from {folder}"
|
||||
msgstr "{user} a supprimé {resource} de {folder}"
|
||||
|
||||
#: pkg/service/response.go:28
|
||||
msgid "{user} moved {resource} to {folder}"
|
||||
msgstr "{user} a déplacé {resource} vers {folder}"
|
||||
|
||||
#: pkg/service/response.go:35
|
||||
msgid "{user} removed link to {resource}"
|
||||
msgstr "{user} a supprimé le lien vers {resource}"
|
||||
|
||||
#: pkg/service/response.go:32
|
||||
msgid "{user} removed {sharee} from {resource}"
|
||||
msgstr "{user} a supprimé {sharee} de {resource}"
|
||||
|
||||
#: pkg/service/response.go:37
|
||||
msgid "{user} removed {sharee} from {space}"
|
||||
msgstr "{user} a retiré {sharee} de {space}"
|
||||
|
||||
#: pkg/service/response.go:29
|
||||
msgid "{user} renamed {oldResource} to {resource}"
|
||||
msgstr "{user} a renommé {oldResource} en {resource}"
|
||||
|
||||
#: pkg/service/response.go:33
|
||||
msgid "{user} shared {resource} via link"
|
||||
msgstr "{user} a partagé {resource} via un lien"
|
||||
|
||||
#: pkg/service/response.go:30
|
||||
msgid "{user} shared {resource} with {sharee}"
|
||||
msgstr "{user} a partagé {resource} avec {sharee}"
|
||||
|
||||
#: pkg/service/response.go:34
|
||||
msgid "{user} updated {field} for a link {token} on {resource}"
|
||||
msgstr "{user} a mis à jour {field} pour un lien {token} sur {resource}"
|
||||
|
||||
#: pkg/service/response.go:31
|
||||
msgid "{user} updated {field} for the {resource}"
|
||||
msgstr "{user} a mis à jour {field} pour la {resource}"
|
||||
|
||||
#: pkg/service/response.go:25
|
||||
msgid "{user} updated {resource} in {folder}"
|
||||
msgstr "{user} a mis à jour {resource} dans {folder}"
|
||||
@@ -0,0 +1,103 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Simone Broglia, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Simone Broglia, 2025\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: it\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: pkg/service/response.go:44
|
||||
msgid "description"
|
||||
msgstr "descrizione"
|
||||
|
||||
#: pkg/service/response.go:43
|
||||
msgid "display name"
|
||||
msgstr "nome visualizzato"
|
||||
|
||||
#: pkg/service/response.go:42
|
||||
msgid "expiration date"
|
||||
msgstr "data di scadenza"
|
||||
|
||||
#: pkg/service/response.go:41
|
||||
msgid "password"
|
||||
msgstr "password"
|
||||
|
||||
#: pkg/service/response.go:40
|
||||
msgid "permission"
|
||||
msgstr "permessi"
|
||||
|
||||
#: pkg/service/response.go:39
|
||||
msgid "some field"
|
||||
msgstr "un campo"
|
||||
|
||||
#: pkg/service/response.go:26
|
||||
msgid "{resource} was downloaded via public link {token}"
|
||||
msgstr "{resource} è stato scaricato tramite link pubblico {token}"
|
||||
|
||||
#: pkg/service/response.go:24
|
||||
msgid "{user} added {resource} to {folder}"
|
||||
msgstr "{user} ha aggiunto {resource} alla cartella {folder} "
|
||||
|
||||
#: pkg/service/response.go:36
|
||||
msgid "{user} added {sharee} as member of {space}"
|
||||
msgstr "{user} ha aggiunto {sharee} come membro dello spazio {space}"
|
||||
|
||||
#: pkg/service/response.go:27
|
||||
msgid "{user} deleted {resource} from {folder}"
|
||||
msgstr "{user} ha eliminato {resource} dalla cartella {folder}"
|
||||
|
||||
#: pkg/service/response.go:28
|
||||
msgid "{user} moved {resource} to {folder}"
|
||||
msgstr "{user} ha spostato {resource} nella cartella {folder}"
|
||||
|
||||
#: pkg/service/response.go:35
|
||||
msgid "{user} removed link to {resource}"
|
||||
msgstr "{user} ha rimosso il link a {resource} "
|
||||
|
||||
#: pkg/service/response.go:32
|
||||
msgid "{user} removed {sharee} from {resource}"
|
||||
msgstr "{user} ha rimosso {sharee} da {resource}"
|
||||
|
||||
#: pkg/service/response.go:37
|
||||
msgid "{user} removed {sharee} from {space}"
|
||||
msgstr "{user} ha rimosso {sharee} dallo spazio {space}"
|
||||
|
||||
#: pkg/service/response.go:29
|
||||
msgid "{user} renamed {oldResource} to {resource}"
|
||||
msgstr "{user} ha rinominato {oldResource} in {resource}"
|
||||
|
||||
#: pkg/service/response.go:33
|
||||
msgid "{user} shared {resource} via link"
|
||||
msgstr "{user} ha condiviso {resource} tramite link"
|
||||
|
||||
#: pkg/service/response.go:30
|
||||
msgid "{user} shared {resource} with {sharee}"
|
||||
msgstr "{user} ha condiviso {resource} con {sharee}"
|
||||
|
||||
#: pkg/service/response.go:34
|
||||
msgid "{user} updated {field} for a link {token} on {resource}"
|
||||
msgstr ""
|
||||
"{user} ha aggiornato il campo {field} per il link {token} su {resource}"
|
||||
|
||||
#: pkg/service/response.go:31
|
||||
msgid "{user} updated {field} for the {resource}"
|
||||
msgstr "{user} ha aggiornato il campo {field} per {resource}"
|
||||
|
||||
#: pkg/service/response.go:25
|
||||
msgid "{user} updated {resource} in {folder}"
|
||||
msgstr "{user} ha aggiornato {resource} nella cartella {folder}"
|
||||
@@ -0,0 +1,103 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# gapho shin, 2025
|
||||
# Junghyuk Kwon <kwon@junghy.uk>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Junghyuk Kwon <kwon@junghy.uk>, 2025\n"
|
||||
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ko\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: pkg/service/response.go:44
|
||||
msgid "description"
|
||||
msgstr "설명"
|
||||
|
||||
#: pkg/service/response.go:43
|
||||
msgid "display name"
|
||||
msgstr "표시 이름"
|
||||
|
||||
#: pkg/service/response.go:42
|
||||
msgid "expiration date"
|
||||
msgstr "만료일"
|
||||
|
||||
#: pkg/service/response.go:41
|
||||
msgid "password"
|
||||
msgstr "비밀번호"
|
||||
|
||||
#: pkg/service/response.go:40
|
||||
msgid "permission"
|
||||
msgstr "권한"
|
||||
|
||||
#: pkg/service/response.go:39
|
||||
msgid "some field"
|
||||
msgstr "일부 필드"
|
||||
|
||||
#: pkg/service/response.go:26
|
||||
msgid "{resource} was downloaded via public link {token}"
|
||||
msgstr "{resource}가 {token} 공개 링크를 통해 다운로드되었습니다"
|
||||
|
||||
#: pkg/service/response.go:24
|
||||
msgid "{user} added {resource} to {folder}"
|
||||
msgstr "{user}가 {folder}에 {resource}을/를 추가했습니다"
|
||||
|
||||
#: pkg/service/response.go:36
|
||||
msgid "{user} added {sharee} as member of {space}"
|
||||
msgstr "{user}가 {space}의 멤버로 {sharee}를 추가했습니다"
|
||||
|
||||
#: pkg/service/response.go:27
|
||||
msgid "{user} deleted {resource} from {folder}"
|
||||
msgstr "{user}가 {folder}에서 {resource}을/를 삭제했습니다"
|
||||
|
||||
#: pkg/service/response.go:28
|
||||
msgid "{user} moved {resource} to {folder}"
|
||||
msgstr "{user}가 {resource}를 {folder}로 이동했습니다"
|
||||
|
||||
#: pkg/service/response.go:35
|
||||
msgid "{user} removed link to {resource}"
|
||||
msgstr "{user}가 {resource}에 대한 링크를 제거했습니다"
|
||||
|
||||
#: pkg/service/response.go:32
|
||||
msgid "{user} removed {sharee} from {resource}"
|
||||
msgstr "{user}가 {resource}에서 {sharee}를 제거했습니다"
|
||||
|
||||
#: pkg/service/response.go:37
|
||||
msgid "{user} removed {sharee} from {space}"
|
||||
msgstr "{user}가 {space}에서 {sharee}를 제거했습니다"
|
||||
|
||||
#: pkg/service/response.go:29
|
||||
msgid "{user} renamed {oldResource} to {resource}"
|
||||
msgstr "{user}가 {oldResource}에서 {Resource}로 이름을 변경하였습니다."
|
||||
|
||||
#: pkg/service/response.go:33
|
||||
msgid "{user} shared {resource} via link"
|
||||
msgstr "{user}가 링크를 통해 {resource}를 공유했습니다"
|
||||
|
||||
#: pkg/service/response.go:30
|
||||
msgid "{user} shared {resource} with {sharee}"
|
||||
msgstr "{user}가 {sharee}에서 {resource}를 공유했습니다"
|
||||
|
||||
#: pkg/service/response.go:34
|
||||
msgid "{user} updated {field} for a link {token} on {resource}"
|
||||
msgstr "{사용자}가 {resource}의 {token} 링크에 대해 {field}를 업데이트했습니다"
|
||||
|
||||
#: pkg/service/response.go:31
|
||||
msgid "{user} updated {field} for the {resource}"
|
||||
msgstr "{user}가 {resource}에 대해 {field}를 업데이트했습니다"
|
||||
|
||||
#: pkg/service/response.go:25
|
||||
msgid "{user} updated {resource} in {folder}"
|
||||
msgstr " {user}가 {folder}의 {resource} 을/를 업데이트했습니다"
|
||||
@@ -0,0 +1,102 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Stephan Paternotte <stephan@paternottes.net>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-08-02 00:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: nl\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/response.go:44
|
||||
msgid "description"
|
||||
msgstr "beschrijving"
|
||||
|
||||
#: pkg/service/response.go:43
|
||||
msgid "display name"
|
||||
msgstr "weergavenaam"
|
||||
|
||||
#: pkg/service/response.go:42
|
||||
msgid "expiration date"
|
||||
msgstr "verloopdatum"
|
||||
|
||||
#: pkg/service/response.go:41
|
||||
msgid "password"
|
||||
msgstr "wachtwoord"
|
||||
|
||||
#: pkg/service/response.go:40
|
||||
msgid "permission"
|
||||
msgstr "machtiging"
|
||||
|
||||
#: pkg/service/response.go:39
|
||||
msgid "some field"
|
||||
msgstr "een veld"
|
||||
|
||||
#: pkg/service/response.go:26
|
||||
msgid "{resource} was downloaded via public link {token}"
|
||||
msgstr "{resource} is gedownload via openbare link {token}"
|
||||
|
||||
#: pkg/service/response.go:24
|
||||
msgid "{user} added {resource} to {folder}"
|
||||
msgstr "{user} heeft {resource} toegevoegd aan {folder}"
|
||||
|
||||
#: pkg/service/response.go:36
|
||||
msgid "{user} added {sharee} as member of {space}"
|
||||
msgstr "{user} heeft {sharee} als lid toegevoegd van {space}"
|
||||
|
||||
#: pkg/service/response.go:27
|
||||
msgid "{user} deleted {resource} from {folder}"
|
||||
msgstr "{user} heeft {resource} verwijderd van {folder}"
|
||||
|
||||
#: pkg/service/response.go:28
|
||||
msgid "{user} moved {resource} to {folder}"
|
||||
msgstr "{user} heeft {resource} verplaatst naar {folder}"
|
||||
|
||||
#: pkg/service/response.go:35
|
||||
msgid "{user} removed link to {resource}"
|
||||
msgstr "{user} heeft link naar {resource} verwijderd"
|
||||
|
||||
#: pkg/service/response.go:32
|
||||
msgid "{user} removed {sharee} from {resource}"
|
||||
msgstr "{user} heeft {sharee} verwijderd van {resource}"
|
||||
|
||||
#: pkg/service/response.go:37
|
||||
msgid "{user} removed {sharee} from {space}"
|
||||
msgstr "{user} heeft {sharee} verwijderd van {space}"
|
||||
|
||||
#: pkg/service/response.go:29
|
||||
msgid "{user} renamed {oldResource} to {resource}"
|
||||
msgstr "{user} heeft {oldResource} hernoemd tot {resource}"
|
||||
|
||||
#: pkg/service/response.go:33
|
||||
msgid "{user} shared {resource} via link"
|
||||
msgstr "{user} heeft {resource} gedeeld via link"
|
||||
|
||||
#: pkg/service/response.go:30
|
||||
msgid "{user} shared {resource} with {sharee}"
|
||||
msgstr "{user} heeft {resource} gedeeld met {sharee}"
|
||||
|
||||
#: pkg/service/response.go:34
|
||||
msgid "{user} updated {field} for a link {token} on {resource}"
|
||||
msgstr "{user} heeft {field} bijgewerkt voor een link {token} op {resource}"
|
||||
|
||||
#: pkg/service/response.go:31
|
||||
msgid "{user} updated {field} for the {resource}"
|
||||
msgstr "{user} heeft {field} bijgewerkt voor {resource}"
|
||||
|
||||
#: pkg/service/response.go:25
|
||||
msgid "{user} updated {resource} in {folder}"
|
||||
msgstr "{user} heeft {resource} bijgewerkt in {folder}"
|
||||
@@ -0,0 +1,103 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Savely Krasovsky, 2025
|
||||
# Анастасия Ванина, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-08-08 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Анастасия Ванина, 2025\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ru\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#: pkg/service/response.go:44
|
||||
msgid "description"
|
||||
msgstr "Описание"
|
||||
|
||||
#: pkg/service/response.go:43
|
||||
msgid "display name"
|
||||
msgstr "Отображаемое имя"
|
||||
|
||||
#: pkg/service/response.go:42
|
||||
msgid "expiration date"
|
||||
msgstr "Дата истечения срока"
|
||||
|
||||
#: pkg/service/response.go:41
|
||||
msgid "password"
|
||||
msgstr "Пароль"
|
||||
|
||||
#: pkg/service/response.go:40
|
||||
msgid "permission"
|
||||
msgstr "Разрешение"
|
||||
|
||||
#: pkg/service/response.go:39
|
||||
msgid "some field"
|
||||
msgstr "Какое-то поле"
|
||||
|
||||
#: pkg/service/response.go:26
|
||||
msgid "{resource} was downloaded via public link {token}"
|
||||
msgstr "{resource} был загружен через публичную ссылку {token}"
|
||||
|
||||
#: pkg/service/response.go:24
|
||||
msgid "{user} added {resource} to {folder}"
|
||||
msgstr "{user} добавил(-ла) {resource} в{folder}"
|
||||
|
||||
#: pkg/service/response.go:36
|
||||
msgid "{user} added {sharee} as member of {space}"
|
||||
msgstr "{user} добавил(-а) {sharee} как члена {space}"
|
||||
|
||||
#: pkg/service/response.go:27
|
||||
msgid "{user} deleted {resource} from {folder}"
|
||||
msgstr "{user} удалил(-а) {resource} из {folder}"
|
||||
|
||||
#: pkg/service/response.go:28
|
||||
msgid "{user} moved {resource} to {folder}"
|
||||
msgstr "{user} перенес(-ла) {resource} в {folder}"
|
||||
|
||||
#: pkg/service/response.go:35
|
||||
msgid "{user} removed link to {resource}"
|
||||
msgstr "{user} удалил(-а) ссылку на {resource}"
|
||||
|
||||
#: pkg/service/response.go:32
|
||||
msgid "{user} removed {sharee} from {resource}"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/response.go:37
|
||||
msgid "{user} removed {sharee} from {space}"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/response.go:29
|
||||
msgid "{user} renamed {oldResource} to {resource}"
|
||||
msgstr "{user} переименовал {oldResource} в {resource}"
|
||||
|
||||
#: pkg/service/response.go:33
|
||||
msgid "{user} shared {resource} via link"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/response.go:30
|
||||
msgid "{user} shared {resource} with {sharee}"
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/response.go:34
|
||||
msgid "{user} updated {field} for a link {token} on {resource}"
|
||||
msgstr "{user} обновил(-а) {field} для ссылки {token} на {resource}"
|
||||
|
||||
#: pkg/service/response.go:31
|
||||
msgid "{user} updated {field} for the {resource}"
|
||||
msgstr "{user} обновил(-а) {field} у {resource}"
|
||||
|
||||
#: pkg/service/response.go:25
|
||||
msgid "{user} updated {resource} in {folder}"
|
||||
msgstr "{user} обновил(-а) {resource} в {folder}"
|
||||
@@ -0,0 +1,102 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# YQS Yang, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: YQS Yang, 2025\n"
|
||||
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: zh\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: pkg/service/response.go:44
|
||||
msgid "description"
|
||||
msgstr "描述"
|
||||
|
||||
#: pkg/service/response.go:43
|
||||
msgid "display name"
|
||||
msgstr "显示名称"
|
||||
|
||||
#: pkg/service/response.go:42
|
||||
msgid "expiration date"
|
||||
msgstr "过期日期"
|
||||
|
||||
#: pkg/service/response.go:41
|
||||
msgid "password"
|
||||
msgstr "密码"
|
||||
|
||||
#: pkg/service/response.go:40
|
||||
msgid "permission"
|
||||
msgstr "权限"
|
||||
|
||||
#: pkg/service/response.go:39
|
||||
msgid "some field"
|
||||
msgstr "特定字段"
|
||||
|
||||
#: pkg/service/response.go:26
|
||||
msgid "{resource} was downloaded via public link {token}"
|
||||
msgstr "{resource} 已通过公开链接 {token} 下载"
|
||||
|
||||
#: pkg/service/response.go:24
|
||||
msgid "{user} added {resource} to {folder}"
|
||||
msgstr "{user} 将 {resource} 添加到 {folder}"
|
||||
|
||||
#: pkg/service/response.go:36
|
||||
msgid "{user} added {sharee} as member of {space}"
|
||||
msgstr "{user} 添加 {sharee} 成为 {space} 的成员"
|
||||
|
||||
#: pkg/service/response.go:27
|
||||
msgid "{user} deleted {resource} from {folder}"
|
||||
msgstr "{user} 从 {folder} 中删除 {resource}"
|
||||
|
||||
#: pkg/service/response.go:28
|
||||
msgid "{user} moved {resource} to {folder}"
|
||||
msgstr "{user} 将 {resource} 移动到 {folder}"
|
||||
|
||||
#: pkg/service/response.go:35
|
||||
msgid "{user} removed link to {resource}"
|
||||
msgstr "{user} 移除了 {resource} 的链接"
|
||||
|
||||
#: pkg/service/response.go:32
|
||||
msgid "{user} removed {sharee} from {resource}"
|
||||
msgstr "{user} 将 {sharee} 从 {resource} 移除"
|
||||
|
||||
#: pkg/service/response.go:37
|
||||
msgid "{user} removed {sharee} from {space}"
|
||||
msgstr "{user} 将 {sharee} 从 {space} 移除"
|
||||
|
||||
#: pkg/service/response.go:29
|
||||
msgid "{user} renamed {oldResource} to {resource}"
|
||||
msgstr "{user} 将 {oldResource} 重命名为 {resource}"
|
||||
|
||||
#: pkg/service/response.go:33
|
||||
msgid "{user} shared {resource} via link"
|
||||
msgstr "{user} 通过链接分享了 {resource}"
|
||||
|
||||
#: pkg/service/response.go:30
|
||||
msgid "{user} shared {resource} with {sharee}"
|
||||
msgstr "{user} 与 {sharee} 分享了 {resource}"
|
||||
|
||||
#: pkg/service/response.go:34
|
||||
msgid "{user} updated {field} for a link {token} on {resource}"
|
||||
msgstr "{user} 更新了 {resource} 上链接 {token} 的 {field}"
|
||||
|
||||
#: pkg/service/response.go:31
|
||||
msgid "{user} updated {field} for the {resource}"
|
||||
msgstr "{user} 更新了 {resource} 的 {field}"
|
||||
|
||||
#: pkg/service/response.go:25
|
||||
msgid "{user} updated {resource} in {folder}"
|
||||
msgstr "{user} 在 {folder} 中更新了 {resource}"
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/dutchcoders/go-clamd"
|
||||
|
||||
@@ -28,7 +29,11 @@ func Server(opts ...Option) (*http.Server, error) {
|
||||
case "clamav":
|
||||
return clamd.NewClamd(cfg.Scanner.ClamAV.Socket).Ping()
|
||||
case "icap":
|
||||
return checks.NewTCPCheck(cfg.Scanner.ICAP.URL)(ctx)
|
||||
u, err := url.Parse(cfg.Scanner.ICAP.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checks.NewTCPCheck(u.Host)(ctx)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ type LDAPProvider struct {
|
||||
|
||||
type LDAPUserSchema struct {
|
||||
ID string `yaml:"id" env:"OC_LDAP_USER_SCHEMA_ID;AUTH_BASIC_LDAP_USER_SCHEMA_ID" desc:"LDAP Attribute to use as the unique ID for users. This should be a stable globally unique ID like a UUID." introductionVersion:"1.0.0"`
|
||||
TenantID string `yaml:"tenant_id" env:"OC_LDAP_USER_SCHEMA_TENANT_ID;AUTH_BASIC_LDAP_USER_SCHEMA_TENANT_ID" desc:"LDAP Attribute to use for the tenant ID of users. This is used to identify the tenant of a user in a multi-tenant environment." introductionVersion:"%%NEXT%%"`
|
||||
IDIsOctetString bool `yaml:"id_is_octet_string" env:"OC_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;AUTH_BASIC_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is e.g. required when using the 'objectGUID' attribute of Active Directory for the user IDs." introductionVersion:"1.0.0"`
|
||||
Mail string `yaml:"mail" env:"OC_LDAP_USER_SCHEMA_MAIL;AUTH_BASIC_LDAP_USER_SCHEMA_MAIL" desc:"LDAP Attribute to use for the email address of users." introductionVersion:"1.0.0"`
|
||||
DisplayName string `yaml:"display_name" env:"OC_LDAP_USER_SCHEMA_DISPLAYNAME;AUTH_BASIC_LDAP_USER_SCHEMA_DISPLAYNAME" desc:"LDAP Attribute to use for the displayname of users." introductionVersion:"1.0.0"`
|
||||
|
||||
@@ -77,6 +77,7 @@ func ldapConfigFromString(cfg config.LDAPProvider) map[string]interface{} {
|
||||
"idp": cfg.IDP,
|
||||
"user_schema": map[string]interface{}{
|
||||
"id": cfg.UserSchema.ID,
|
||||
"tenantId": cfg.UserSchema.TenantID,
|
||||
"idIsOctetString": cfg.UserSchema.IDIsOctetString,
|
||||
"mail": cfg.UserSchema.Mail,
|
||||
"displayName": cfg.UserSchema.DisplayName,
|
||||
|
||||
@@ -4,10 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/run"
|
||||
"github.com/urfave/cli/v2"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
|
||||
registry "github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
"github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
"github.com/opencloud-eu/opencloud/pkg/tracing"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/config/parser"
|
||||
@@ -19,8 +23,6 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/server/http"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
"github.com/urfave/cli/v2"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
)
|
||||
|
||||
// Server is the entrypoint for the server command.
|
||||
@@ -68,9 +70,15 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := helpers.RegisterAppProvider(ctx, cfg, logger, gatewaySelector, appUrls); err != nil {
|
||||
return err
|
||||
}
|
||||
ticker := time.NewTicker(cfg.CS3Api.APPRegistrationInterval)
|
||||
defer ticker.Stop()
|
||||
go func() {
|
||||
for ; true; <-ticker.C {
|
||||
if err := helpers.RegisterAppProvider(ctx, cfg, logger, gatewaySelector, appUrls); err != nil {
|
||||
logger.Warn().Err(err).Msg("Failed to register app provider")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
st := store.Create(
|
||||
store.Store(cfg.Store.Store),
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package config
|
||||
|
||||
import "github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
)
|
||||
|
||||
// CS3Api defines the available configuration in order to access to the CS3 gateway.
|
||||
type CS3Api struct {
|
||||
Gateway Gateway `yaml:"gateway"`
|
||||
DataGateway DataGateway `yaml:"datagateway"`
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
Gateway Gateway `yaml:"gateway"`
|
||||
DataGateway DataGateway `yaml:"datagateway"`
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
APPRegistrationInterval time.Duration `yaml:"app_registration_interval" env:"COLLABORATION_CS3API_APP_REGISTRATION_INTERVAL" desc:"The interval at which the app provider registers itself." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// Gateway defines the available configuration for the CS3 API gateway
|
||||
|
||||
@@ -65,6 +65,7 @@ func DefaultConfig() *config.Config {
|
||||
DataGateway: config.DataGateway{
|
||||
Insecure: false,
|
||||
},
|
||||
APPRegistrationInterval: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ type LDAP struct {
|
||||
UserNameAttribute string `yaml:"user_name_attribute" env:"OC_LDAP_USER_SCHEMA_USERNAME;GRAPH_LDAP_USER_NAME_ATTRIBUTE" desc:"LDAP Attribute to use for username of users." introductionVersion:"1.0.0"`
|
||||
UserIDAttribute string `yaml:"user_id_attribute" env:"OC_LDAP_USER_SCHEMA_ID;GRAPH_LDAP_USER_UID_ATTRIBUTE" desc:"LDAP Attribute to use as the unique ID for users. This should be a stable globally unique ID like a UUID." introductionVersion:"1.0.0"`
|
||||
UserIDIsOctetString bool `yaml:"user_id_is_octet_string" env:"OC_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING;GRAPH_LDAP_USER_SCHEMA_ID_IS_OCTETSTRING" desc:"Set this to true if the defined 'ID' attribute for users is of the 'OCTETSTRING' syntax. This is required when using the 'objectGUID' attribute of Active Directory for the user ID's." introductionVersion:"1.0.0"`
|
||||
UserTenantIDAttribute string `yaml:"user_tenant_id_attribute" env:"OC_LDAP_USER_SCHEMA_TENANT_ID;GRAPH_LDAP_USER_SCHEMA_TENANT_ID" desc:"LDAP Attribute to use for the tenant ID of users. This is used to identify the tenant of a user in a multi-tenant environment." introductionVersion:"%%NEXT%%"`
|
||||
UserTypeAttribute string `yaml:"user_type_attribute" env:"OC_LDAP_USER_SCHEMA_USER_TYPE;GRAPH_LDAP_USER_TYPE_ATTRIBUTE" desc:"LDAP Attribute to distinguish between 'Member' and 'Guest' users. Default is 'openCloudUserType'." introductionVersion:"1.0.0"`
|
||||
UserEnabledAttribute string `yaml:"user_enabled_attribute" env:"OC_LDAP_USER_ENABLED_ATTRIBUTE;GRAPH_USER_ENABLED_ATTRIBUTE" desc:"LDAP Attribute to use as a flag telling if the user is enabled or disabled." introductionVersion:"1.0.0"`
|
||||
DisableUserMechanism string `yaml:"disable_user_mechanism" env:"OC_LDAP_DISABLE_USER_MECHANISM;GRAPH_DISABLE_USER_MECHANISM" desc:"An option to control the behavior for disabling users. Supported options are 'none', 'attribute' and 'group'. If set to 'group', disabling a user via API will add the user to the configured group for disabled users, if set to 'attribute' this will be done in the ldap user entry, if set to 'none' the disable request is not processed. Default is 'attribute'." introductionVersion:"1.0.0"`
|
||||
|
||||
@@ -96,6 +96,7 @@ func DefaultConfig() *config.Config {
|
||||
// FIXME: switch this to some more widely available attribute by default
|
||||
// ideally this needs to be constant for the lifetime of a users
|
||||
UserIDAttribute: "openCloudUUID",
|
||||
UserTenantIDAttribute: "",
|
||||
UserTypeAttribute: "openCloudUserType",
|
||||
UserEnabledAttribute: "openCloudUserEnabled",
|
||||
DisableUserMechanism: "attribute",
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
cs3group "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
|
||||
cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/odata"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -44,7 +44,7 @@ func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.U
|
||||
}
|
||||
|
||||
// GetUser implements the Backend Interface.
|
||||
func (i *CS3) GetUser(ctx context.Context, userID string, _ *godata.GoDataRequest) (*libregraph.User, error) {
|
||||
func (i *CS3) GetUser(ctx context.Context, nameOrId string, _ *godata.GoDataRequest) (*libregraph.User, error) {
|
||||
logger := i.Logger.SubloggerWithRequestID(ctx)
|
||||
logger.Debug().Str("backend", "cs3").Msg("GetUser")
|
||||
gatewayClient, err := i.GatewaySelector.Next()
|
||||
@@ -53,22 +53,43 @@ func (i *CS3) GetUser(ctx context.Context, userID string, _ *godata.GoDataReques
|
||||
return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
|
||||
}
|
||||
|
||||
// Try to get the user by username first
|
||||
res, err := gatewayClient.GetUserByClaim(ctx, &cs3user.GetUserByClaimRequest{
|
||||
Claim: "userid", // FIXME add consts to reva
|
||||
Value: userID,
|
||||
Claim: "username", // FIXME add consts to reva
|
||||
Value: nameOrId,
|
||||
})
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
logger.Error().Str("backend", "cs3").Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request: transport error")
|
||||
logger.Error().Str("backend", "cs3").Err(err).Str("nameOrId", nameOrId).Msg("error sending get user by claim id grpc request: transport error")
|
||||
return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
|
||||
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_OK:
|
||||
return CreateUserModelFromCS3(res.GetUser()), nil
|
||||
case res.GetStatus().GetCode() == cs3rpc.Code_CODE_NOT_FOUND:
|
||||
// If the user was not found by username, try to get it by user ID
|
||||
default:
|
||||
logger.Debug().Str("backend", "cs3").Err(err).Str("nameOrId", nameOrId).Msg("error sending get user by claim id grpc request")
|
||||
return nil, errorcode.New(errorcode.GeneralException, res.GetStatus().GetMessage())
|
||||
|
||||
}
|
||||
|
||||
// If the user was not found by username, try to get it by user ID
|
||||
res, err = gatewayClient.GetUserByClaim(ctx, &cs3user.GetUserByClaimRequest{
|
||||
Claim: "userid", // FIXME add consts to reva
|
||||
Value: nameOrId,
|
||||
})
|
||||
switch {
|
||||
case err != nil:
|
||||
logger.Error().Str("backend", "cs3").Err(err).Str("nameOrId", nameOrId).Msg("error sending get user by claim id grpc request: transport error")
|
||||
return nil, errorcode.New(errorcode.ServiceNotAvailable, err.Error())
|
||||
case res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK:
|
||||
if res.GetStatus().GetCode() == cs3rpc.Code_CODE_NOT_FOUND {
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, res.GetStatus().GetMessage())
|
||||
}
|
||||
logger.Debug().Str("backend", "cs3").Err(err).Str("userid", userID).Msg("error sending get user by claim id grpc request")
|
||||
logger.Debug().Str("backend", "cs3").Err(err).Str("nameOrId", nameOrId).Msg("error sending get user by claim id grpc request")
|
||||
return nil, errorcode.New(errorcode.GeneralException, res.GetStatus().GetMessage())
|
||||
}
|
||||
|
||||
return CreateUserModelFromCS3(res.GetUser()), nil
|
||||
}
|
||||
|
||||
@@ -167,7 +188,7 @@ func (i *CS3) GetGroups(ctx context.Context, oreq *godata.GoDataRequest) ([]*lib
|
||||
|
||||
// CreateGroup implements the Backend Interface. It's currently not supported for the CS3 backend
|
||||
func (i *CS3) CreateGroup(ctx context.Context, group libregraph.Group) (*libregraph.Group, error) {
|
||||
return nil, errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
// GetGroup implements the Backend Interface.
|
||||
@@ -202,25 +223,25 @@ func (i *CS3) GetGroup(ctx context.Context, groupID string, queryParam url.Value
|
||||
|
||||
// DeleteGroup implements the Backend Interface. It's currently not supported for the CS3 backend
|
||||
func (i *CS3) DeleteGroup(ctx context.Context, id string) error {
|
||||
return errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
// UpdateGroupName implements the Backend Interface. It's currently not supported for the CS3 backend
|
||||
func (i *CS3) UpdateGroupName(ctx context.Context, groupID string, groupName string) error {
|
||||
return errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
// GetGroupMembers implements the Backend Interface. It's currently not supported for the CS3 backend
|
||||
func (i *CS3) GetGroupMembers(ctx context.Context, groupID string, _ *godata.GoDataRequest) ([]*libregraph.User, error) {
|
||||
return nil, errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
// AddMembersToGroup implements the Backend Interface. It's currently not supported for the CS3 backend
|
||||
func (i *CS3) AddMembersToGroup(ctx context.Context, groupID string, memberID []string) error {
|
||||
return errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
// RemoveMemberFromGroup implements the Backend Interface. It's currently not supported for the CS3 backend
|
||||
func (i *CS3) RemoveMemberFromGroup(ctx context.Context, groupID string, memberID string) error {
|
||||
return errorcode.New(errorcode.NotSupported, "not implemented")
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/libregraph/idm/pkg/ldapdn"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
|
||||
@@ -80,6 +81,7 @@ type LDAP struct {
|
||||
type userAttributeMap struct {
|
||||
displayName string
|
||||
id string
|
||||
tenantId string
|
||||
mail string
|
||||
userName string
|
||||
givenName string
|
||||
@@ -115,6 +117,7 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
|
||||
uam := userAttributeMap{
|
||||
displayName: config.UserDisplayNameAttribute,
|
||||
id: config.UserIDAttribute,
|
||||
tenantId: config.UserTenantIDAttribute,
|
||||
mail: config.UserEmailAttribute,
|
||||
userName: config.UserNameAttribute,
|
||||
accountEnabled: config.UserEnabledAttribute,
|
||||
@@ -614,7 +617,17 @@ func (i *LDAP) FilterUsers(ctx context.Context, oreq *godata.GoDataRequest, filt
|
||||
i.userAttributeMap.displayName, search,
|
||||
)
|
||||
}
|
||||
userFilter = fmt.Sprintf("(&%s(objectClass=%s)%s%s)", i.userFilter, i.userObjectClass, queryFilter, userFilter)
|
||||
|
||||
// apply tenant filter if applicable
|
||||
var tenantFilter string
|
||||
if i.userAttributeMap.tenantId != "" {
|
||||
currentUser, ok := ctxpkg.ContextGetUser(ctx)
|
||||
if ok && currentUser.Id.GetTenantId() != "" {
|
||||
tenantFilter = fmt.Sprintf("(%s=%s)", i.userAttributeMap.tenantId, ldap.EscapeFilter(currentUser.Id.GetTenantId()))
|
||||
}
|
||||
}
|
||||
|
||||
userFilter = fmt.Sprintf("(&%s(objectClass=%s)%s%s%s)", i.userFilter, i.userObjectClass, queryFilter, userFilter, tenantFilter)
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
i.userBaseDN, i.userScope, ldap.NeverDerefAliases, 0, 0, false,
|
||||
userFilter,
|
||||
|
||||
135
services/graph/pkg/l10n/locale/ca/LC_MESSAGES/graph.po
Normal file
135
services/graph/pkg/l10n/locale/ca/LC_MESSAGES/graph.po
Normal file
@@ -0,0 +1,135 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Ivan Fustero, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Ivan Fustero, 2025\n"
|
||||
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ca\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. UnifiedRole Editor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole EditorListGrants, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditorListGrants, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:116 pkg/unifiedrole/roles.go:122
|
||||
#: pkg/unifiedrole/roles.go:128 pkg/unifiedrole/roles.go:140
|
||||
#: pkg/unifiedrole/roles.go:146
|
||||
msgid "Can edit"
|
||||
msgstr "Pot editar"
|
||||
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:134
|
||||
msgid "Can edit without versions"
|
||||
msgstr "Pot editar sense versions"
|
||||
|
||||
#. UnifiedRole Manager, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:158
|
||||
msgid "Can manage"
|
||||
msgstr "Pot gestionar"
|
||||
|
||||
#. UnifiedRole EditorLite, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:152
|
||||
msgid "Can upload"
|
||||
msgstr "Pot pujar"
|
||||
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:98 pkg/unifiedrole/roles.go:104
|
||||
#: pkg/unifiedrole/roles.go:110
|
||||
msgid "Can view"
|
||||
msgstr "Pot veure"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:164
|
||||
msgid "Can view (secure)"
|
||||
msgstr "Pot veure (de forma segura)"
|
||||
|
||||
#. UnifiedRole FullDenial, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:170
|
||||
msgid "Cannot access"
|
||||
msgstr "No pot accedir"
|
||||
|
||||
#. UnifiedRole FullDenial, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:167
|
||||
msgid "Deny all access."
|
||||
msgstr "Denega tot accés."
|
||||
|
||||
#. default description for new spaces
|
||||
#: pkg/service/v0/spacetemplates.go:32
|
||||
msgid "Here you can add a description for this Space."
|
||||
msgstr "Aquí podeu afegir una descripció per a aquest espai."
|
||||
|
||||
#. UnifiedRole Viewer, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:95 pkg/unifiedrole/roles.go:107
|
||||
msgid "View and download."
|
||||
msgstr "Visualitza i descarrega."
|
||||
|
||||
#. UnifiedRole SecureViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:161
|
||||
msgid "View only documents, images and PDFs. Watermarks will be applied."
|
||||
msgstr "View only documents, images and PDFs. Watermarks will be applied."
|
||||
|
||||
#. UnifiedRole FileEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:137
|
||||
msgid "View, download and edit."
|
||||
msgstr "Visualitza, descarrega i edita."
|
||||
|
||||
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:101
|
||||
msgid "View, download and show all invited people."
|
||||
msgstr "Visualitza, descarrega i mostra totes les persones convidades."
|
||||
|
||||
#. UnifiedRole EditorLite, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:149
|
||||
msgid "View, download and upload."
|
||||
msgstr "Visualitza, descarrega i puja."
|
||||
|
||||
#. UnifiedRole FileEditorListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:143
|
||||
msgid "View, download, edit and show all invited people."
|
||||
msgstr "Visualitza, descarrega, edita i mostra totes les persones convidades."
|
||||
|
||||
#. UnifiedRole Editor, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:113 pkg/unifiedrole/roles.go:131
|
||||
msgid "View, download, upload, edit, add and delete."
|
||||
msgstr "Visualitza, descarrega, puja, edita, afegeix i suprimeix."
|
||||
|
||||
#. UnifiedRole Manager, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:155
|
||||
msgid "View, download, upload, edit, add, delete and manage members."
|
||||
msgstr ""
|
||||
"Visualitza, descarrega, puja, edita, afegeix, suprimeix i gestiona els "
|
||||
"membres."
|
||||
|
||||
#. UnifiedRoleListGrants Editor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:119
|
||||
msgid "View, download, upload, edit, add, delete and show all invited people."
|
||||
msgstr ""
|
||||
"Visualitza, descarregar, pujar, editar, afegir, esborrar i mostrar totes les"
|
||||
" persones convidades."
|
||||
|
||||
#. UnifiedRole SpaseEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:125
|
||||
msgid "View, download, upload, edit, add, delete including the history."
|
||||
msgstr ""
|
||||
"Visualitza, baixa, puja, edita, afegeix, suprimeix incloent l'historial."
|
||||
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-01-27 11:01+0100\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Jörn Friedrich Dreyer <jfd@butonic.de>, 2025\n"
|
||||
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"
|
||||
|
||||
135
services/graph/pkg/l10n/locale/es/LC_MESSAGES/graph.po
Normal file
135
services/graph/pkg/l10n/locale/es/LC_MESSAGES/graph.po
Normal file
@@ -0,0 +1,135 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Elías Martín, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Elías Martín, 2025\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#. UnifiedRole Editor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole EditorListGrants, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditorListGrants, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:116 pkg/unifiedrole/roles.go:122
|
||||
#: pkg/unifiedrole/roles.go:128 pkg/unifiedrole/roles.go:140
|
||||
#: pkg/unifiedrole/roles.go:146
|
||||
msgid "Can edit"
|
||||
msgstr "Puede editar"
|
||||
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:134
|
||||
msgid "Can edit without versions"
|
||||
msgstr "Se puede editar sin versiones"
|
||||
|
||||
#. UnifiedRole Manager, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:158
|
||||
msgid "Can manage"
|
||||
msgstr "Se puede gestionar"
|
||||
|
||||
#. UnifiedRole EditorLite, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:152
|
||||
msgid "Can upload"
|
||||
msgstr "Se puede subir"
|
||||
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:98 pkg/unifiedrole/roles.go:104
|
||||
#: pkg/unifiedrole/roles.go:110
|
||||
msgid "Can view"
|
||||
msgstr "Se puede visualizar"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:164
|
||||
msgid "Can view (secure)"
|
||||
msgstr "Se puede visualizar (de forma segura)"
|
||||
|
||||
#. UnifiedRole FullDenial, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:170
|
||||
msgid "Cannot access"
|
||||
msgstr "No se puede acceder"
|
||||
|
||||
#. UnifiedRole FullDenial, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:167
|
||||
msgid "Deny all access."
|
||||
msgstr "Prohibir todo el acceso"
|
||||
|
||||
#. default description for new spaces
|
||||
#: pkg/service/v0/spacetemplates.go:32
|
||||
msgid "Here you can add a description for this Space."
|
||||
msgstr "Aquí puedes agregar una descripción a este Espacio."
|
||||
|
||||
#. UnifiedRole Viewer, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:95 pkg/unifiedrole/roles.go:107
|
||||
msgid "View and download."
|
||||
msgstr "Ver y descargar"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:161
|
||||
msgid "View only documents, images and PDFs. Watermarks will be applied."
|
||||
msgstr ""
|
||||
"Ver solamente documentos, imágenes y archivos PDF. Será aplicada una marca "
|
||||
"de agua."
|
||||
|
||||
#. UnifiedRole FileEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:137
|
||||
msgid "View, download and edit."
|
||||
msgstr "Ver. descargar y editar."
|
||||
|
||||
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:101
|
||||
msgid "View, download and show all invited people."
|
||||
msgstr "Ver, descargar y mostrar a todas las personas invitadas."
|
||||
|
||||
#. UnifiedRole EditorLite, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:149
|
||||
msgid "View, download and upload."
|
||||
msgstr "Ver, descargar y subir."
|
||||
|
||||
#. UnifiedRole FileEditorListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:143
|
||||
msgid "View, download, edit and show all invited people."
|
||||
msgstr "Ver, descargar, editar y mostrar a todas las perosnas invitadas."
|
||||
|
||||
#. UnifiedRole Editor, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:113 pkg/unifiedrole/roles.go:131
|
||||
msgid "View, download, upload, edit, add and delete."
|
||||
msgstr "Ver, descargar, subuir, editar, añadir y eliminar."
|
||||
|
||||
#. UnifiedRole Manager, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:155
|
||||
msgid "View, download, upload, edit, add, delete and manage members."
|
||||
msgstr "Ver, descargar, subir, editar, añadir, eliminar y gestionar miembros."
|
||||
|
||||
#. UnifiedRoleListGrants Editor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:119
|
||||
msgid "View, download, upload, edit, add, delete and show all invited people."
|
||||
msgstr ""
|
||||
"Ver, descargar, subir, editar, añadir, eliminar y mostrar a todas las "
|
||||
"personas invitadas."
|
||||
|
||||
#. UnifiedRole SpaseEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:125
|
||||
msgid "View, download, upload, edit, add, delete including the history."
|
||||
msgstr ""
|
||||
"Ver, descargar, subir, editar, añadir y borrar incluyendo el historial."
|
||||
139
services/graph/pkg/l10n/locale/fr/LC_MESSAGES/graph.po
Normal file
139
services/graph/pkg/l10n/locale/fr/LC_MESSAGES/graph.po
Normal file
@@ -0,0 +1,139 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# eric_G <junk.eg@free.fr>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
|
||||
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#. UnifiedRole Editor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole EditorListGrants, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditorListGrants, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:116 pkg/unifiedrole/roles.go:122
|
||||
#: pkg/unifiedrole/roles.go:128 pkg/unifiedrole/roles.go:140
|
||||
#: pkg/unifiedrole/roles.go:146
|
||||
msgid "Can edit"
|
||||
msgstr "Peut éditer"
|
||||
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:134
|
||||
msgid "Can edit without versions"
|
||||
msgstr "Peut éditer sans versions"
|
||||
|
||||
#. UnifiedRole Manager, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:158
|
||||
msgid "Can manage"
|
||||
msgstr "Peut gérer"
|
||||
|
||||
#. UnifiedRole EditorLite, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:152
|
||||
msgid "Can upload"
|
||||
msgstr "Peut téléchargé"
|
||||
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:98 pkg/unifiedrole/roles.go:104
|
||||
#: pkg/unifiedrole/roles.go:110
|
||||
msgid "Can view"
|
||||
msgstr "Peut consulté"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:164
|
||||
msgid "Can view (secure)"
|
||||
msgstr "Peut consulté (sécurisé)"
|
||||
|
||||
#. UnifiedRole FullDenial, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:170
|
||||
msgid "Cannot access"
|
||||
msgstr "Accès impossible"
|
||||
|
||||
#. UnifiedRole FullDenial, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:167
|
||||
msgid "Deny all access."
|
||||
msgstr "Refuser tout accès."
|
||||
|
||||
#. default description for new spaces
|
||||
#: pkg/service/v0/spacetemplates.go:32
|
||||
msgid "Here you can add a description for this Space."
|
||||
msgstr "Vous pouvez ajouter une description pour cet espace."
|
||||
|
||||
#. UnifiedRole Viewer, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:95 pkg/unifiedrole/roles.go:107
|
||||
msgid "View and download."
|
||||
msgstr "Afficher et télécharger."
|
||||
|
||||
#. UnifiedRole SecureViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:161
|
||||
msgid "View only documents, images and PDFs. Watermarks will be applied."
|
||||
msgstr ""
|
||||
"Afficher uniquement les documents, les images et les PDF. Des filigranes "
|
||||
"seront appliqués."
|
||||
|
||||
#. UnifiedRole FileEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:137
|
||||
msgid "View, download and edit."
|
||||
msgstr "Afficher, télécharger et modifier."
|
||||
|
||||
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:101
|
||||
msgid "View, download and show all invited people."
|
||||
msgstr "Afficher, télécharger et montrer toutes les personnes invitées."
|
||||
|
||||
#. UnifiedRole EditorLite, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:149
|
||||
msgid "View, download and upload."
|
||||
msgstr "Afficher, télécharger et téléverser."
|
||||
|
||||
#. UnifiedRole FileEditorListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:143
|
||||
msgid "View, download, edit and show all invited people."
|
||||
msgstr ""
|
||||
"Afficher, télécharger, modifier et montrer toutes les personnes invitées."
|
||||
|
||||
#. UnifiedRole Editor, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:113 pkg/unifiedrole/roles.go:131
|
||||
msgid "View, download, upload, edit, add and delete."
|
||||
msgstr "Afficher, télécharger, téléverser, modifier, ajouter et supprimer."
|
||||
|
||||
#. UnifiedRole Manager, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:155
|
||||
msgid "View, download, upload, edit, add, delete and manage members."
|
||||
msgstr ""
|
||||
"Afficher, télécharger, téléverser, modifier, ajouter, supprimer et gérer les"
|
||||
" membres."
|
||||
|
||||
#. UnifiedRoleListGrants Editor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:119
|
||||
msgid "View, download, upload, edit, add, delete and show all invited people."
|
||||
msgstr ""
|
||||
"Afficher, télécharger, téléverser, modifier, ajouter, supprimer et montrer "
|
||||
"toutes les personnes invitées."
|
||||
|
||||
#. UnifiedRole SpaseEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:125
|
||||
msgid "View, download, upload, edit, add, delete including the history."
|
||||
msgstr ""
|
||||
"Afficher, télécharger, téléverser, modifier, ajouter, supprimer, y compris "
|
||||
"l'historique."
|
||||
135
services/graph/pkg/l10n/locale/it/LC_MESSAGES/graph.po
Normal file
135
services/graph/pkg/l10n/locale/it/LC_MESSAGES/graph.po
Normal file
@@ -0,0 +1,135 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Simone Broglia, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Simone Broglia, 2025\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: it\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#. UnifiedRole Editor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole EditorListGrants, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditorListGrants, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:116 pkg/unifiedrole/roles.go:122
|
||||
#: pkg/unifiedrole/roles.go:128 pkg/unifiedrole/roles.go:140
|
||||
#: pkg/unifiedrole/roles.go:146
|
||||
msgid "Can edit"
|
||||
msgstr "Può modificare"
|
||||
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:134
|
||||
msgid "Can edit without versions"
|
||||
msgstr "Può modificare senza versioni"
|
||||
|
||||
#. UnifiedRole Manager, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:158
|
||||
msgid "Can manage"
|
||||
msgstr "Può gestire"
|
||||
|
||||
#. UnifiedRole EditorLite, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:152
|
||||
msgid "Can upload"
|
||||
msgstr "Può caricare"
|
||||
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:98 pkg/unifiedrole/roles.go:104
|
||||
#: pkg/unifiedrole/roles.go:110
|
||||
msgid "Can view"
|
||||
msgstr "Può visualizzare"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:164
|
||||
msgid "Can view (secure)"
|
||||
msgstr "Può visualizzare (protetto)"
|
||||
|
||||
#. UnifiedRole FullDenial, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:170
|
||||
msgid "Cannot access"
|
||||
msgstr "Accesso non consentito"
|
||||
|
||||
#. UnifiedRole FullDenial, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:167
|
||||
msgid "Deny all access."
|
||||
msgstr "Blocca tutti gli accessi"
|
||||
|
||||
#. default description for new spaces
|
||||
#: pkg/service/v0/spacetemplates.go:32
|
||||
msgid "Here you can add a description for this Space."
|
||||
msgstr "Qui puoi aggiungere una descrizione per questo Spazio."
|
||||
|
||||
#. UnifiedRole Viewer, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:95 pkg/unifiedrole/roles.go:107
|
||||
msgid "View and download."
|
||||
msgstr "Visualizza e scarica."
|
||||
|
||||
#. UnifiedRole SecureViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:161
|
||||
msgid "View only documents, images and PDFs. Watermarks will be applied."
|
||||
msgstr ""
|
||||
"Visualizza solo documenti, immagini e PDF. Verranno applicate filigrane."
|
||||
|
||||
#. UnifiedRole FileEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:137
|
||||
msgid "View, download and edit."
|
||||
msgstr "Visualizza, scarica e modifica."
|
||||
|
||||
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:101
|
||||
msgid "View, download and show all invited people."
|
||||
msgstr "Visualizza, scarica e mostra tutte le persone invitate."
|
||||
|
||||
#. UnifiedRole EditorLite, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:149
|
||||
msgid "View, download and upload."
|
||||
msgstr "Visualizza, scarica e carica."
|
||||
|
||||
#. UnifiedRole FileEditorListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:143
|
||||
msgid "View, download, edit and show all invited people."
|
||||
msgstr "Visualizza, scarica, modifica e mostra tutte le persone invitate."
|
||||
|
||||
#. UnifiedRole Editor, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:113 pkg/unifiedrole/roles.go:131
|
||||
msgid "View, download, upload, edit, add and delete."
|
||||
msgstr "Visualizza, scarica, carica, modifica, aggiungi ed elimina."
|
||||
|
||||
#. UnifiedRole Manager, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:155
|
||||
msgid "View, download, upload, edit, add, delete and manage members."
|
||||
msgstr ""
|
||||
"Visualizza, scarica, carica, modifica, aggiungi, elimina e gestisci i "
|
||||
"membri."
|
||||
|
||||
#. UnifiedRoleListGrants Editor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:119
|
||||
msgid "View, download, upload, edit, add, delete and show all invited people."
|
||||
msgstr ""
|
||||
"Visualizza, scarica, carica, modifica, aggiungi, elimina e mostra tutte le "
|
||||
"persone invitate."
|
||||
|
||||
#. UnifiedRole SpaseEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:125
|
||||
msgid "View, download, upload, edit, add, delete including the history."
|
||||
msgstr "View, download, upload, edit, add, delete including the history."
|
||||
130
services/graph/pkg/l10n/locale/ko/LC_MESSAGES/graph.po
Normal file
130
services/graph/pkg/l10n/locale/ko/LC_MESSAGES/graph.po
Normal file
@@ -0,0 +1,130 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# gapho shin, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: gapho shin, 2025\n"
|
||||
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ko\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. UnifiedRole Editor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole EditorListGrants, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditorListGrants, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:116 pkg/unifiedrole/roles.go:122
|
||||
#: pkg/unifiedrole/roles.go:128 pkg/unifiedrole/roles.go:140
|
||||
#: pkg/unifiedrole/roles.go:146
|
||||
msgid "Can edit"
|
||||
msgstr "편집 가능"
|
||||
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:134
|
||||
msgid "Can edit without versions"
|
||||
msgstr "버전 없이 편집 가능"
|
||||
|
||||
#. UnifiedRole Manager, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:158
|
||||
msgid "Can manage"
|
||||
msgstr "관리 가능"
|
||||
|
||||
#. UnifiedRole EditorLite, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:152
|
||||
msgid "Can upload"
|
||||
msgstr "업로드 가능"
|
||||
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:98 pkg/unifiedrole/roles.go:104
|
||||
#: pkg/unifiedrole/roles.go:110
|
||||
msgid "Can view"
|
||||
msgstr "보기 가능"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:164
|
||||
msgid "Can view (secure)"
|
||||
msgstr "보기 가능 (보안)"
|
||||
|
||||
#. UnifiedRole FullDenial, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:170
|
||||
msgid "Cannot access"
|
||||
msgstr "접근 불가"
|
||||
|
||||
#. UnifiedRole FullDenial, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:167
|
||||
msgid "Deny all access."
|
||||
msgstr "모든 접근 거부"
|
||||
|
||||
#. default description for new spaces
|
||||
#: pkg/service/v0/spacetemplates.go:32
|
||||
msgid "Here you can add a description for this Space."
|
||||
msgstr "여기에 이 공간에 대한 설명을 추가할 수 있습니다."
|
||||
|
||||
#. UnifiedRole Viewer, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:95 pkg/unifiedrole/roles.go:107
|
||||
msgid "View and download."
|
||||
msgstr "보기 및 다운로드."
|
||||
|
||||
#. UnifiedRole SecureViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:161
|
||||
msgid "View only documents, images and PDFs. Watermarks will be applied."
|
||||
msgstr "문서, 이미지 및 PDF만 보기. 워터마크가 적용됩니다."
|
||||
|
||||
#. UnifiedRole FileEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:137
|
||||
msgid "View, download and edit."
|
||||
msgstr "보기, 다운로드 및 편집."
|
||||
|
||||
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:101
|
||||
msgid "View, download and show all invited people."
|
||||
msgstr "모든 초대받은 사람들을 보고, 다운로드하고, 보여줍니다."
|
||||
|
||||
#. UnifiedRole EditorLite, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:149
|
||||
msgid "View, download and upload."
|
||||
msgstr "보기, 다운로드 및 업로드."
|
||||
|
||||
#. UnifiedRole FileEditorListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:143
|
||||
msgid "View, download, edit and show all invited people."
|
||||
msgstr "모든 초대받은 사람들을 보고, 다운로드하고, 편집하고, 보여줍니다."
|
||||
|
||||
#. UnifiedRole Editor, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:113 pkg/unifiedrole/roles.go:131
|
||||
msgid "View, download, upload, edit, add and delete."
|
||||
msgstr "보기, 다운로드, 업로드, 편집, 추가 및 삭제."
|
||||
|
||||
#. UnifiedRole Manager, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:155
|
||||
msgid "View, download, upload, edit, add, delete and manage members."
|
||||
msgstr "회원 보기, 다운로드, 업로드, 편집, 추가, 삭제 및 관리."
|
||||
|
||||
#. UnifiedRoleListGrants Editor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:119
|
||||
msgid "View, download, upload, edit, add, delete and show all invited people."
|
||||
msgstr "모든 초대받은 사람들을 보고, 다운로드하고, 업로드하고, 편집하고, 추가하고, 삭제하고, 보여줍니다."
|
||||
|
||||
#. UnifiedRole SpaseEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:125
|
||||
msgid "View, download, upload, edit, add, delete including the history."
|
||||
msgstr "기록을 포함하여 보기, 다운로드, 업로드, 편집, 추가, 삭제합니다."
|
||||
138
services/graph/pkg/l10n/locale/nl/LC_MESSAGES/graph.po
Normal file
138
services/graph/pkg/l10n/locale/nl/LC_MESSAGES/graph.po
Normal file
@@ -0,0 +1,138 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Stephan Paternotte <stephan@paternottes.net>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-08-05 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: nl\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. UnifiedRole Editor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole EditorListGrants, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditorListGrants, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:116 pkg/unifiedrole/roles.go:122
|
||||
#: pkg/unifiedrole/roles.go:128 pkg/unifiedrole/roles.go:140
|
||||
#: pkg/unifiedrole/roles.go:146
|
||||
msgid "Can edit"
|
||||
msgstr "Kan bewerken"
|
||||
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:134
|
||||
msgid "Can edit without versions"
|
||||
msgstr "Kan bewerken zonder versies"
|
||||
|
||||
#. UnifiedRole Manager, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:158
|
||||
msgid "Can manage"
|
||||
msgstr "Kan beheren"
|
||||
|
||||
#. UnifiedRole EditorLite, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:152
|
||||
msgid "Can upload"
|
||||
msgstr "Kan uploaden"
|
||||
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:98 pkg/unifiedrole/roles.go:104
|
||||
#: pkg/unifiedrole/roles.go:110
|
||||
msgid "Can view"
|
||||
msgstr "Kan weergeven"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:164
|
||||
msgid "Can view (secure)"
|
||||
msgstr "Kan weergeven (beveiligd)"
|
||||
|
||||
#. UnifiedRole FullDenial, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:170
|
||||
msgid "Cannot access"
|
||||
msgstr "Heeft geen toegang"
|
||||
|
||||
#. UnifiedRole FullDenial, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:167
|
||||
msgid "Deny all access."
|
||||
msgstr "Alle toegang weigeren."
|
||||
|
||||
#. default description for new spaces
|
||||
#: pkg/service/v0/spacetemplates.go:32
|
||||
msgid "Here you can add a description for this Space."
|
||||
msgstr "Hier kun je een beschrijving voor deze ruimte toevoegen."
|
||||
|
||||
#. UnifiedRole Viewer, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:95 pkg/unifiedrole/roles.go:107
|
||||
msgid "View and download."
|
||||
msgstr "Weergeven en downloaden."
|
||||
|
||||
#. UnifiedRole SecureViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:161
|
||||
msgid "View only documents, images and PDFs. Watermarks will be applied."
|
||||
msgstr ""
|
||||
"Alleen documenten, afbeeldingen en PDF's weergeven. Met toepassing van "
|
||||
"watermerken."
|
||||
|
||||
#. UnifiedRole FileEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:137
|
||||
msgid "View, download and edit."
|
||||
msgstr "Weergeven, downloaden en bewerken."
|
||||
|
||||
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:101
|
||||
msgid "View, download and show all invited people."
|
||||
msgstr "Weergeven, downloaden en alle genodigde personen zien."
|
||||
|
||||
#. UnifiedRole EditorLite, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:149
|
||||
msgid "View, download and upload."
|
||||
msgstr "Weergeven, downloaden en uploaden."
|
||||
|
||||
#. UnifiedRole FileEditorListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:143
|
||||
msgid "View, download, edit and show all invited people."
|
||||
msgstr "Weergeven, downloaden, bewerken en alle genodigde personen zien."
|
||||
|
||||
#. UnifiedRole Editor, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:113 pkg/unifiedrole/roles.go:131
|
||||
msgid "View, download, upload, edit, add and delete."
|
||||
msgstr "Weergeven, downloaden, uploaden, bewerken, toevoegen en verwijderen."
|
||||
|
||||
#. UnifiedRole Manager, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:155
|
||||
msgid "View, download, upload, edit, add, delete and manage members."
|
||||
msgstr ""
|
||||
"Weergeven, downloaden, uploaden, bewerken, toevoegen, verwijderen en leden "
|
||||
"beheren."
|
||||
|
||||
#. UnifiedRoleListGrants Editor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:119
|
||||
msgid "View, download, upload, edit, add, delete and show all invited people."
|
||||
msgstr ""
|
||||
"Weergeven, downloaden, uploaden, bewerken, toevoegen, verwijderen en alle "
|
||||
"genodigde personen zien."
|
||||
|
||||
#. UnifiedRole SpaseEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:125
|
||||
msgid "View, download, upload, edit, add, delete including the history."
|
||||
msgstr ""
|
||||
"Weergeven, downloaden, uploaden, bewerken, toevoegen, verwijderen, incl. "
|
||||
"geschiedenis."
|
||||
140
services/graph/pkg/l10n/locale/ru/LC_MESSAGES/graph.po
Normal file
140
services/graph/pkg/l10n/locale/ru/LC_MESSAGES/graph.po
Normal file
@@ -0,0 +1,140 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Savely Krasovsky, 2025
|
||||
# Lulufox, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-08-12 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Lulufox, 2025\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ru\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#. UnifiedRole Editor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole EditorListGrants, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditorListGrants, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:116 pkg/unifiedrole/roles.go:122
|
||||
#: pkg/unifiedrole/roles.go:128 pkg/unifiedrole/roles.go:140
|
||||
#: pkg/unifiedrole/roles.go:146
|
||||
msgid "Can edit"
|
||||
msgstr "Может редактировать"
|
||||
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:134
|
||||
msgid "Can edit without versions"
|
||||
msgstr "Может редактировать без версионирования"
|
||||
|
||||
#. UnifiedRole Manager, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:158
|
||||
msgid "Can manage"
|
||||
msgstr "Может управлять"
|
||||
|
||||
#. UnifiedRole EditorLite, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:152
|
||||
msgid "Can upload"
|
||||
msgstr "Может загружать"
|
||||
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:98 pkg/unifiedrole/roles.go:104
|
||||
#: pkg/unifiedrole/roles.go:110
|
||||
msgid "Can view"
|
||||
msgstr "Можно смотреть"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:164
|
||||
msgid "Can view (secure)"
|
||||
msgstr "Можно смотреть (защищено)"
|
||||
|
||||
#. UnifiedRole FullDenial, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:170
|
||||
msgid "Cannot access"
|
||||
msgstr "Не имеет доступа"
|
||||
|
||||
#. UnifiedRole FullDenial, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:167
|
||||
msgid "Deny all access."
|
||||
msgstr "Отказать во всех доступах"
|
||||
|
||||
#. default description for new spaces
|
||||
#: pkg/service/v0/spacetemplates.go:32
|
||||
msgid "Here you can add a description for this Space."
|
||||
msgstr "Здесь вы можете добавить описание для Пространства"
|
||||
|
||||
#. UnifiedRole Viewer, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:95 pkg/unifiedrole/roles.go:107
|
||||
msgid "View and download."
|
||||
msgstr "Просмотреть и скачать"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:161
|
||||
msgid "View only documents, images and PDFs. Watermarks will be applied."
|
||||
msgstr ""
|
||||
"Просмотреть только документы, изображения и файлы PDF. Будут присутствовать "
|
||||
"вотермарки."
|
||||
|
||||
#. UnifiedRole FileEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:137
|
||||
msgid "View, download and edit."
|
||||
msgstr "Просмотреть, скачать и редактировать."
|
||||
|
||||
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:101
|
||||
msgid "View, download and show all invited people."
|
||||
msgstr "Просмотреть, скачать и показать всех приглашенных людей."
|
||||
|
||||
#. UnifiedRole EditorLite, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:149
|
||||
msgid "View, download and upload."
|
||||
msgstr "Просмотреть, скачать и загрузить."
|
||||
|
||||
#. UnifiedRole FileEditorListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:143
|
||||
msgid "View, download, edit and show all invited people."
|
||||
msgstr ""
|
||||
"Просмотреть, скачать, отредактировать и показать всех приглашенных людей."
|
||||
|
||||
#. UnifiedRole Editor, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:113 pkg/unifiedrole/roles.go:131
|
||||
msgid "View, download, upload, edit, add and delete."
|
||||
msgstr "Просмотреть, скачать, загрузить, редактировать, добавить и удалить."
|
||||
|
||||
#. UnifiedRole Manager, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:155
|
||||
msgid "View, download, upload, edit, add, delete and manage members."
|
||||
msgstr ""
|
||||
"Просмотреть, скачать, загрузить, редактировать, добавить, удалить и "
|
||||
"управлять командой."
|
||||
|
||||
#. UnifiedRoleListGrants Editor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:119
|
||||
msgid "View, download, upload, edit, add, delete and show all invited people."
|
||||
msgstr ""
|
||||
"Просмотреть, скачать, загрузить, редактировать, добавить, удалить и показать"
|
||||
" всех приглашенных людей."
|
||||
|
||||
#. UnifiedRole SpaseEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:125
|
||||
msgid "View, download, upload, edit, add, delete including the history."
|
||||
msgstr ""
|
||||
"Просмотреть, скачать, загрузить, редактировать, добавить, удалить включая "
|
||||
"историю."
|
||||
130
services/graph/pkg/l10n/locale/zh/LC_MESSAGES/graph.po
Normal file
130
services/graph/pkg/l10n/locale/zh/LC_MESSAGES/graph.po
Normal file
@@ -0,0 +1,130 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# YQS Yang, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: YQS Yang, 2025\n"
|
||||
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: zh\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. UnifiedRole Editor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole EditorListGrants, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditor, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole FileEditorListGrants, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:116 pkg/unifiedrole/roles.go:122
|
||||
#: pkg/unifiedrole/roles.go:128 pkg/unifiedrole/roles.go:140
|
||||
#: pkg/unifiedrole/roles.go:146
|
||||
msgid "Can edit"
|
||||
msgstr "可编辑"
|
||||
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role DisplayName (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:134
|
||||
msgid "Can edit without versions"
|
||||
msgstr "可编辑(无版本控制)"
|
||||
|
||||
#. UnifiedRole Manager, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:158
|
||||
msgid "Can manage"
|
||||
msgstr "可管理"
|
||||
|
||||
#. UnifiedRole EditorLite, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:152
|
||||
msgid "Can upload"
|
||||
msgstr "可上传"
|
||||
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole Viewer, Role DisplayName (resolves directly)
|
||||
#. UnifiedRole SpaseViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:98 pkg/unifiedrole/roles.go:104
|
||||
#: pkg/unifiedrole/roles.go:110
|
||||
msgid "Can view"
|
||||
msgstr "可查看"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:164
|
||||
msgid "Can view (secure)"
|
||||
msgstr "可查看(安全)"
|
||||
|
||||
#. UnifiedRole FullDenial, Role DisplayName (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:170
|
||||
msgid "Cannot access"
|
||||
msgstr "无法访问"
|
||||
|
||||
#. UnifiedRole FullDenial, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:167
|
||||
msgid "Deny all access."
|
||||
msgstr "拒绝所有访问。"
|
||||
|
||||
#. default description for new spaces
|
||||
#: pkg/service/v0/spacetemplates.go:32
|
||||
msgid "Here you can add a description for this Space."
|
||||
msgstr "此处可为该空间添加描述。"
|
||||
|
||||
#. UnifiedRole Viewer, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaceViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:95 pkg/unifiedrole/roles.go:107
|
||||
msgid "View and download."
|
||||
msgstr "查看和下载。"
|
||||
|
||||
#. UnifiedRole SecureViewer, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:161
|
||||
msgid "View only documents, images and PDFs. Watermarks will be applied."
|
||||
msgstr "仅可查看文档、图片和PDF,且会添加水印。"
|
||||
|
||||
#. UnifiedRole FileEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:137
|
||||
msgid "View, download and edit."
|
||||
msgstr "查看、下载和编辑。"
|
||||
|
||||
#. UnifiedRole ViewerListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:101
|
||||
msgid "View, download and show all invited people."
|
||||
msgstr "查看、下载并显示所有受邀人员。"
|
||||
|
||||
#. UnifiedRole EditorLite, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:149
|
||||
msgid "View, download and upload."
|
||||
msgstr "查看、下载和上传。"
|
||||
|
||||
#. UnifiedRole FileEditorListGrants, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:143
|
||||
msgid "View, download, edit and show all invited people."
|
||||
msgstr "查看、下载、编辑并显示所有受邀人员。"
|
||||
|
||||
#. UnifiedRole Editor, Role Description (resolves directly)
|
||||
#. UnifiedRole SpaseEditorWithoutVersions, Role Description (resolves
|
||||
#. directly)
|
||||
#: pkg/unifiedrole/roles.go:113 pkg/unifiedrole/roles.go:131
|
||||
msgid "View, download, upload, edit, add and delete."
|
||||
msgstr "查看、下载、上传、编辑、添加和删除。"
|
||||
|
||||
#. UnifiedRole Manager, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:155
|
||||
msgid "View, download, upload, edit, add, delete and manage members."
|
||||
msgstr "查看、下载、上传、编辑、添加、删除和管理成员。"
|
||||
|
||||
#. UnifiedRoleListGrants Editor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:119
|
||||
msgid "View, download, upload, edit, add, delete and show all invited people."
|
||||
msgstr "查看、下载、上传、编辑、添加、删除并显示所有受邀人员。"
|
||||
|
||||
#. UnifiedRole SpaseEditor, Role Description (resolves directly)
|
||||
#: pkg/unifiedrole/roles.go:125
|
||||
msgid "View, download, upload, edit, add, delete including the history."
|
||||
msgstr "查看、下载、上传、编辑、添加和删除(含历史版本)。"
|
||||
@@ -2,6 +2,7 @@ package debug
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/checks"
|
||||
"github.com/opencloud-eu/opencloud/pkg/handlers"
|
||||
@@ -17,9 +18,13 @@ func Server(opts ...Option) (*http.Server, error) {
|
||||
WithLogger(options.Logger).
|
||||
WithCheck("web reachability", checks.NewHTTPCheck(options.Config.HTTP.Addr))
|
||||
|
||||
u, err := url.Parse(options.Config.Identity.LDAP.URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readyHandlerConfiguration := healthHandlerConfiguration.
|
||||
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint)).
|
||||
WithCheck("ldap reachability", checks.NewTCPCheck(options.Config.Identity.LDAP.URI))
|
||||
WithCheck("ldap reachability", checks.NewTCPCheck(u.Host))
|
||||
|
||||
return debug.NewService(
|
||||
debug.Logger(options.Logger),
|
||||
|
||||
@@ -251,7 +251,7 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersi
|
||||
return nil, errorcode.New(errorcode.GeneralException, res.Status.Message)
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion, expandPermissions)
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion, expandPermissions, getFieldMask(odataReq))
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drives: error parsing grpc response")
|
||||
return nil, errorcode.New(errorcode.GeneralException, err.Error())
|
||||
@@ -289,7 +289,7 @@ func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
log = log.With().Str("url", webDavBaseURL.String()).Logger()
|
||||
|
||||
_, expandPermissions, err := parseDriveRequest(r)
|
||||
odataReq, expandPermissions, err := parseDriveRequest(r)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drives: error parsing odata request")
|
||||
errorcode.RenderError(w, r, err)
|
||||
@@ -322,7 +322,7 @@ func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions)
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions, getFieldMask(odataReq))
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
@@ -480,7 +480,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1, false)
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1, false, nil)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
@@ -648,7 +648,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1, false)
|
||||
spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1, false, nil)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not update drive: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
@@ -659,7 +659,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, spaces[0])
|
||||
}
|
||||
|
||||
func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool) ([]*libregraph.Drive, error) {
|
||||
func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool, fieldMask map[string]struct{}) ([]*libregraph.Drive, error) {
|
||||
errg, ctx := errgroup.WithContext(ctx)
|
||||
work := make(chan *storageprovider.StorageSpace, len(storageSpaces))
|
||||
results := make(chan *libregraph.Drive, len(storageSpaces))
|
||||
@@ -689,7 +689,7 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces
|
||||
// skip OCM shares they are no supposed to show up in the drives list
|
||||
continue
|
||||
}
|
||||
res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion, expandPermissions)
|
||||
res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion, expandPermissions, fieldMask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -773,7 +773,7 @@ func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*stor
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool) (*libregraph.Drive, error) {
|
||||
func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool, fieldMask map[string]struct{}) (*libregraph.Drive, error) {
|
||||
logger := g.logger.SubloggerWithRequestID(ctx)
|
||||
if space.Root == nil {
|
||||
logger.Error().Msg("unable to parse space: space has no root")
|
||||
@@ -790,7 +790,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
|
||||
Name: space.Name,
|
||||
//"createdDateTime": "string (timestamp)", // TODO read from StorageSpace ... needs Opaque for now
|
||||
DriveType: &space.SpaceType,
|
||||
// we currently always expandt the root because it carries the deleted property that indiccates if a space is trashed
|
||||
// we currently always expand the root because it carries the deleted property that indicates if a space is trashed
|
||||
Root: &libregraph.DriveItem{
|
||||
Id: libregraph.PtrString(storagespace.FormatResourceID(spaceRid)),
|
||||
},
|
||||
@@ -799,6 +799,10 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
|
||||
drive.Root.Permissions, _ = g.cs3SpacePermissionsToLibreGraph(ctx, space, false, apiVersion)
|
||||
}
|
||||
|
||||
if _, ok := fieldMask["@libre.graph.hasTrashedItems"]; ok {
|
||||
drive.LibreGraphHasTrashedItems = &space.HasTrashedItems
|
||||
}
|
||||
|
||||
if space.SpaceType == _spaceTypeMountpoint {
|
||||
var remoteItem *libregraph.RemoteItem
|
||||
grantID := storageprovider.ResourceId{
|
||||
@@ -1175,3 +1179,15 @@ func validateSpaceName(name string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFieldMask(odataReq *godata.GoDataRequest) map[string]struct{} {
|
||||
fieldMask := map[string]struct{}{}
|
||||
if odataReq != nil && odataReq.Query.Select != nil && len(odataReq.Query.Select.SelectItems) > 0 {
|
||||
for _, item := range odataReq.Query.Select.SelectItems {
|
||||
for _, token := range item.Segments {
|
||||
fieldMask[token.Value] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldMask
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
"github.com/go-chi/render"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
)
|
||||
@@ -17,9 +20,7 @@ func (g Graph) GetSharedByMe(w http.ResponseWriter, r *http.Request) {
|
||||
g.logger.Debug().Msg("Calling GetRootDriveChildren")
|
||||
ctx := r.Context()
|
||||
|
||||
driveItems := make(driveItemsByResourceID)
|
||||
var err error
|
||||
driveItems, err = g.listUserShares(ctx, nil, driveItems)
|
||||
driveItems, err := g.listUserShares(ctx, nil, make(driveItemsByResourceID))
|
||||
if err != nil {
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
@@ -39,6 +40,37 @@ func (g Graph) GetSharedByMe(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
expand := r.URL.Query().Get("$expand")
|
||||
expandThumbnails := strings.Contains(expand, "thumbnails")
|
||||
if expandThumbnails {
|
||||
for k, item := range driveItems {
|
||||
mt := item.GetFile().MimeType
|
||||
if mt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, match := thumbnail.SupportedMimeTypes[*mt]
|
||||
if match {
|
||||
baseUrl := fmt.Sprintf("%s/dav/spaces/%s?scalingup=0&preview=1&processor=thumbnail",
|
||||
g.config.Commons.OpenCloudURL,
|
||||
item.GetId())
|
||||
smallUrl := baseUrl + "&x=36&y=36"
|
||||
mediumUrl := baseUrl + "&x=48&y=48"
|
||||
largeUrl := baseUrl + "&x=96&y=96"
|
||||
|
||||
item.SetThumbnails([]libregraph.ThumbnailSet{
|
||||
{
|
||||
Small: &libregraph.Thumbnail{Url: &smallUrl},
|
||||
Medium: &libregraph.Thumbnail{Url: &mediumUrl},
|
||||
Large: &libregraph.Thumbnail{Url: &largeUrl},
|
||||
},
|
||||
})
|
||||
|
||||
driveItems[k] = item // assign modified item back to the map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res := make([]libregraph.DriveItem, 0, len(driveItems))
|
||||
for _, v := range driveItems {
|
||||
res = append(res, v)
|
||||
|
||||
@@ -2,7 +2,9 @@ package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
|
||||
@@ -10,12 +12,17 @@ import (
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
"github.com/opencloud-eu/opencloud/services/thumbnails/pkg/thumbnail"
|
||||
)
|
||||
|
||||
// ListSharedWithMe lists the files shared with the current user.
|
||||
func (g Graph) ListSharedWithMe(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
driveItems, err := g.listSharedWithMe(ctx)
|
||||
|
||||
expand := r.URL.Query().Get("$expand")
|
||||
expandThumbnails := strings.Contains(expand, "thumbnails")
|
||||
|
||||
driveItems, err := g.listSharedWithMe(ctx, expandThumbnails)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("listSharedWithMe failed")
|
||||
errorcode.RenderError(w, r, err)
|
||||
@@ -27,7 +34,7 @@ func (g Graph) ListSharedWithMe(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// listSharedWithMe is a helper function that lists the drive items shared with the current user.
|
||||
func (g Graph) listSharedWithMe(ctx context.Context) ([]libregraph.DriveItem, error) {
|
||||
func (g Graph) listSharedWithMe(ctx context.Context, expandThumbnails bool) ([]libregraph.DriveItem, error) {
|
||||
gatewayClient, err := g.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Msg("could not select next gateway client")
|
||||
@@ -59,5 +66,34 @@ func (g Graph) listSharedWithMe(ctx context.Context) ([]libregraph.DriveItem, er
|
||||
driveItems = append(driveItems, ocmDriveItems...)
|
||||
}
|
||||
|
||||
if expandThumbnails {
|
||||
for k, item := range driveItems {
|
||||
mt := item.GetFile().MimeType
|
||||
if mt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, match := thumbnail.SupportedMimeTypes[*mt]
|
||||
if match {
|
||||
baseUrl := fmt.Sprintf("%s/dav/spaces/%s?scalingup=0&preview=1&processor=thumbnail",
|
||||
g.config.Commons.OpenCloudURL,
|
||||
item.RemoteItem.GetId())
|
||||
smallUrl := baseUrl + "&x=36&y=36"
|
||||
mediumUrl := baseUrl + "&x=48&y=48"
|
||||
largeUrl := baseUrl + "&x=96&y=96"
|
||||
|
||||
item.SetThumbnails([]libregraph.ThumbnailSet{
|
||||
{
|
||||
Small: &libregraph.Thumbnail{Url: &smallUrl},
|
||||
Medium: &libregraph.Thumbnail{Url: &mediumUrl},
|
||||
Large: &libregraph.Thumbnail{Url: &largeUrl},
|
||||
},
|
||||
})
|
||||
|
||||
driveItems[k] = item // assign modified item back to the map
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return driveItems, err
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/google/uuid"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
settingsmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/settings/v0"
|
||||
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
@@ -31,7 +32,6 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
)
|
||||
|
||||
// GetMe implements the Service interface.
|
||||
@@ -183,7 +183,7 @@ func (g Graph) GetUserDrive(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions)
|
||||
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions, nil)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("could not get personal drive: error parsing grpc response")
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
|
||||
@@ -546,7 +546,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
expandPermissions = expandDrivesPermissions
|
||||
}
|
||||
d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1, expandPermissions)
|
||||
d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1, expandPermissions, nil)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Interface("id", sp.Id).Msg("error converting space to drive")
|
||||
continue
|
||||
@@ -577,7 +577,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ctxHasFullPerms := g.contextUserHasFullAccountPerms(r.Context())
|
||||
if !ctxHasFullPerms && !g.config.API.ShowUserEmailInResults {
|
||||
user.Mail = nil
|
||||
|
||||
@@ -31,6 +31,7 @@ displayName: Admin
|
||||
description: An admin for this OpenCloud instance.
|
||||
mail: admin@example.org
|
||||
openCloudUUID: {{ .ID }}
|
||||
openCloudTenantId: {{ .TenantID }}
|
||||
openCloudExternalIdentity: $ {{ .Issuer }} $ {{ .ID }}
|
||||
{{ else -}}
|
||||
dn: uid={{ .Name }},ou=sysusers,o=libregraph-idm
|
||||
|
||||
@@ -15,6 +15,7 @@ mail: alan@example.org
|
||||
openCloudUserEnabled: TRUE
|
||||
openCloudUUID: b1f74ec4-dd7e-11ef-a543-03775734d0f7
|
||||
openCloudExternalIdentity: $ {{.}} $ b1f74ec4-dd7e-11ef-a543-03775734d0f7
|
||||
openCloudTenantId: cd22ea13-f6b4-4f5f-a2c2-69b5a0f07a8b
|
||||
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
|
||||
|
||||
dn: uid=lynn,ou=users,o=libregraph-idm
|
||||
@@ -34,6 +35,7 @@ mail: lynn@example.org
|
||||
openCloudUserEnabled: TRUE
|
||||
openCloudUUID: 60708dda-e897-11ef-919f-bbb7437d6ec2
|
||||
openCloudExternalIdentity: $ {{.}} $ 60708dda-e897-11ef-919f-bbb7437d6ec2
|
||||
openCloudTenantId: cd22ea13-f6b4-4f5f-a2c2-69b5a0f07a8b
|
||||
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
|
||||
|
||||
dn: uid=mary,ou=users,o=libregraph-idm
|
||||
@@ -53,6 +55,7 @@ mail: mary@example.org
|
||||
openCloudUserEnabled: TRUE
|
||||
openCloudUUID: 056fc874-dd7f-11ef-ba84-af6fca4b7289
|
||||
openCloudExternalIdentity: $ {{.}} $ 056fc874-dd7f-11ef-ba84-af6fca4b7289
|
||||
openCloudTenantId: cd22ea13-f6b4-4f5f-a2c2-69b5a0f07a8b
|
||||
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
|
||||
|
||||
dn: uid=margaret,ou=users,o=libregraph-idm
|
||||
@@ -72,6 +75,7 @@ mail: margaret@example.org
|
||||
openCloudUserEnabled: TRUE
|
||||
openCloudUUID: 801abee4-dd7f-11ef-a324-83f55a754b62
|
||||
openCloudExternalIdentity: $ {{.}} $ 801abee4-dd7f-11ef-a324-83f55a754b62
|
||||
openCloudTenantId: cd22ea13-f6b4-4f5f-a2c2-69b5a0f07a8b
|
||||
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
|
||||
|
||||
dn: uid=dennis,ou=users,o=libregraph-idm
|
||||
@@ -91,6 +95,7 @@ mail: dennis@example.org
|
||||
openCloudUserEnabled: TRUE
|
||||
openCloudUUID: cd88bf9a-dd7f-11ef-a609-7f78deb2345f
|
||||
openCloudExternalIdentity: $ {{.}} $ cd88bf9a-dd7f-11ef-a609-7f78deb2345f
|
||||
openCloudTenantId: cd22ea13-f6b4-4f5f-a2c2-69b5a0f07a8b
|
||||
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTY1NTM2LHQ9MSxwPTE2JGg1NUxqckhWVjdEdXVzTkxjbXRoa0EkMzZ3aGZSMjdyTDFOYXQxa0xTajdrVGFubTBnb3VKRGZ0ck9DTStuRHo5cw==
|
||||
|
||||
dn: cn=users,ou=groups,o=libregraph-idm
|
||||
|
||||
@@ -132,6 +132,7 @@ func bootstrap(logger log.Logger, cfg *config.Config, srvcfg server.Config) erro
|
||||
Name string
|
||||
Password string
|
||||
ID string
|
||||
TenantID string
|
||||
Issuer string
|
||||
}
|
||||
|
||||
@@ -151,12 +152,16 @@ func bootstrap(logger log.Logger, cfg *config.Config, srvcfg server.Config) erro
|
||||
}
|
||||
|
||||
if cfg.AdminUserID != "" {
|
||||
serviceUsers = append(serviceUsers, svcUser{
|
||||
adminUser := svcUser{
|
||||
Name: "admin",
|
||||
Password: cfg.ServiceUserPasswords.OCAdmin,
|
||||
ID: cfg.AdminUserID,
|
||||
Issuer: cfg.DemoUsersIssuerUrl,
|
||||
})
|
||||
}
|
||||
if cfg.CreateDemoUsers {
|
||||
adminUser.TenantID = "cd22ea13-f6b4-4f5f-a2c2-69b5a0f07a8b"
|
||||
}
|
||||
serviceUsers = append(serviceUsers, adminUser)
|
||||
}
|
||||
|
||||
bdb := &ldbbolt.LdbBolt{}
|
||||
|
||||
@@ -2,6 +2,7 @@ package debug
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/checks"
|
||||
"github.com/opencloud-eu/opencloud/pkg/handlers"
|
||||
@@ -17,8 +18,12 @@ func Server(opts ...Option) (*http.Server, error) {
|
||||
WithLogger(options.Logger).
|
||||
WithCheck("http reachability", checks.NewHTTPCheck(options.Config.HTTP.Addr))
|
||||
|
||||
u, err := url.Parse(options.Config.Ldap.URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readyHandlerConfiguration := healthHandlerConfiguration.
|
||||
WithCheck("ldap reachability", checks.NewTCPCheck(options.Config.Ldap.URI))
|
||||
WithCheck("ldap reachability", checks.NewTCPCheck(u.Host))
|
||||
|
||||
return debug.NewService(
|
||||
debug.Logger(options.Logger),
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Ivan Fustero, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Ivan Fustero, 2025\n"
|
||||
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ca\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:65
|
||||
msgid "Click here to check it: {ShareLink}"
|
||||
msgstr "Clica aquí per verificar: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .CallToAction }}
|
||||
#. SharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:23 pkg/email/templates.go:50
|
||||
msgid "Click here to view it: {ShareLink}"
|
||||
msgstr "Clica aquí per visualitzar: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:19
|
||||
msgid "Hello {ShareGrantee}"
|
||||
msgstr "Hola, {ShareGrantee}"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:32
|
||||
msgid "Hello {ShareGrantee},"
|
||||
msgstr "Hola, {ShareGrantee},"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. UnsharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. MembershipExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:46 pkg/email/templates.go:59
|
||||
#: pkg/email/templates.go:74
|
||||
msgid "Hello {SpaceGrantee},"
|
||||
msgstr "Hola, {SpaceGrantee},"
|
||||
|
||||
#. Grouped email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:118
|
||||
msgid "Hi {DisplayName},"
|
||||
msgstr "Hola, {DisplayName},"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting
|
||||
#. }}
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:87 pkg/email/templates.go:104
|
||||
msgid "Hi,"
|
||||
msgstr "Hola,"
|
||||
|
||||
#. MembershipExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:72
|
||||
msgid "Membership of '{SpaceName}' expired at {ExpiredAt}"
|
||||
msgstr "Incripció a '{SpaceName}' ha caducat el {ExpiredAt}"
|
||||
|
||||
#. Grouped email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:116
|
||||
msgid "Report"
|
||||
msgstr "Informe"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, Subject field (resolves
|
||||
#. directly)
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, Subject
|
||||
#. field (resolves directly)
|
||||
#: pkg/email/templates.go:85 pkg/email/templates.go:102
|
||||
msgid "ScienceMesh: {InitiatorName} wants to collaborate with you"
|
||||
msgstr "ScienceMesh: {InitiatorName} vol col·laborar amb vós"
|
||||
|
||||
#. ShareExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:30
|
||||
msgid "Share to '{ShareFolder}' expired at {ExpiredAt}"
|
||||
msgstr "Compartir a '{ShareFolder}' caducat el {ExpiredAt}"
|
||||
|
||||
#. MembershipExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:76
|
||||
msgid ""
|
||||
"Your membership of space {SpaceName} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this membership has expired you still might have access through other shares and/or space memberships"
|
||||
msgstr ""
|
||||
"La seva inscripció al espai {SpaceName} ha caducat el {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Encara que aquesta inscripció hagi caducat, podria seguir tenint accés a través d'altres comparticions i/o espais inscrit."
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:34
|
||||
msgid ""
|
||||
"Your share to {ShareFolder} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this share has been revoked you still might have access through other shares and/or space memberships."
|
||||
msgstr ""
|
||||
"La teva compartició a {ShareFolder} ha caducat el {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Encara que aquesta compartició ha estat cancel·lada, podria seguir tenint accés a través d'altres comparticions i/o espais inscrits."
|
||||
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:106
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"Please visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) vol començar a compartir recursos de col·laboració amb vós.\n"
|
||||
"Visiteu la configuració de la federació i utilitzeu els detalls següents:\n"
|
||||
"Testimoni (token): {Token}\n"
|
||||
"Domini del proveïdor: {ProviderDomain}"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{
|
||||
#. .MessageBody }}
|
||||
#: pkg/email/templates.go:89
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"To accept the invite, please visit the following URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternatively, you can visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) vol començar a compartir recursos de col·laboració amb vós.\n"
|
||||
"Per acceptar la invitació, visiteu la següent URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternativament, podeu visitar la configuració de la vostra federació i utilitzar els següents detalls:\n"
|
||||
"Testimoni (token): {Token}\n"
|
||||
"Domini del proveïdor: {ProviderDomain}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:21
|
||||
msgid "{ShareSharer} has shared \"{ShareFolder}\" with you."
|
||||
msgstr "{ShareSharer} ha compartit \"{ShareFolder}\" amb tu."
|
||||
|
||||
#. ShareCreated email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:17
|
||||
msgid "{ShareSharer} shared '{ShareFolder}' with you"
|
||||
msgstr "{ShareSharer} comparteix \"{ShareFolder}\" amb tu"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:48
|
||||
msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"."
|
||||
msgstr "{SpaceSharer} l'ha invitat a unir-se a \"{SpaceName}\"."
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:61
|
||||
msgid ""
|
||||
"{SpaceSharer} has removed you from \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"You might still have access through your other groups or direct membership."
|
||||
msgstr ""
|
||||
"{SpaceSharer} l'ha eliminat de \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"Podria seguir tenint accés a través d'altres grups o d'inscripció directa."
|
||||
|
||||
#. SharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:44
|
||||
msgid "{SpaceSharer} invited you to join {SpaceName}"
|
||||
msgstr "{SpaceSharer} l'invita a unir-se {SpaceName}"
|
||||
|
||||
#. UnsharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:57
|
||||
msgid "{SpaceSharer} removed you from {SpaceName}"
|
||||
msgstr "{SpaceSharer} l'ha eliminat de {SpaceName}"
|
||||
@@ -5,15 +5,16 @@
|
||||
#
|
||||
# Translators:
|
||||
# Jörn Friedrich Dreyer <jfd@butonic.de>, 2025
|
||||
# Jonas, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-01-27 11:01+0100\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Jörn Friedrich Dreyer <jfd@butonic.de>, 2025\n"
|
||||
"Last-Translator: Jonas, 2025\n"
|
||||
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -53,7 +54,7 @@ msgstr "Hallo {SpaceGrantee},"
|
||||
#. Grouped email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:118
|
||||
msgid "Hi {DisplayName},"
|
||||
msgstr ""
|
||||
msgstr "Hallo {DisplayName},"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting
|
||||
#. }}
|
||||
@@ -71,7 +72,7 @@ msgstr "Die Mitgliedschaft für '{SpaceName}' ist am {ExpiredAt} abgelaufen."
|
||||
#. Grouped email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:116
|
||||
msgid "Report"
|
||||
msgstr ""
|
||||
msgstr "Bericht"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, Subject field (resolves
|
||||
#. directly)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# eric_G <junk.eg@free.fr>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
|
||||
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:65
|
||||
msgid "Click here to check it: {ShareLink}"
|
||||
msgstr "Cliquez ici pour le consulter : {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .CallToAction }}
|
||||
#. SharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:23 pkg/email/templates.go:50
|
||||
msgid "Click here to view it: {ShareLink}"
|
||||
msgstr "Cliquez ici pour le voir : {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:19
|
||||
msgid "Hello {ShareGrantee}"
|
||||
msgstr "Bonjour {ShareGrantee}"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:32
|
||||
msgid "Hello {ShareGrantee},"
|
||||
msgstr "Bonjour {ShareGrantee},"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. UnsharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. MembershipExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:46 pkg/email/templates.go:59
|
||||
#: pkg/email/templates.go:74
|
||||
msgid "Hello {SpaceGrantee},"
|
||||
msgstr "Bonjour {SpaceGrantee}"
|
||||
|
||||
#. Grouped email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:118
|
||||
msgid "Hi {DisplayName},"
|
||||
msgstr "Bonjour {DisplayName},"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting
|
||||
#. }}
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:87 pkg/email/templates.go:104
|
||||
msgid "Hi,"
|
||||
msgstr "Bonjour,"
|
||||
|
||||
#. MembershipExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:72
|
||||
msgid "Membership of '{SpaceName}' expired at {ExpiredAt}"
|
||||
msgstr "L'affiliation à '{SpaceName}' a expiré à {ExpiredAt}"
|
||||
|
||||
#. Grouped email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:116
|
||||
msgid "Report"
|
||||
msgstr "Rapport"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, Subject field (resolves
|
||||
#. directly)
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, Subject
|
||||
#. field (resolves directly)
|
||||
#: pkg/email/templates.go:85 pkg/email/templates.go:102
|
||||
msgid "ScienceMesh: {InitiatorName} wants to collaborate with you"
|
||||
msgstr "ScienceMesh : {InitiatorName} souhaite collaborer avec vous"
|
||||
|
||||
#. ShareExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:30
|
||||
msgid "Share to '{ShareFolder}' expired at {ExpiredAt}"
|
||||
msgstr "Le partage avec '{ShareFolder}' a expiré le {ExpiredAt}"
|
||||
|
||||
#. MembershipExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:76
|
||||
msgid ""
|
||||
"Your membership of space {SpaceName} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this membership has expired you still might have access through other shares and/or space memberships"
|
||||
msgstr ""
|
||||
"Votre adhésion à l'espace {SpaceName} a expiré à {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Même si cette adhésion a expiré, vous pouvez encore avoir accès à d'autres parts et/ou adhésions à l'espace."
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:34
|
||||
msgid ""
|
||||
"Your share to {ShareFolder} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this share has been revoked you still might have access through other shares and/or space memberships."
|
||||
msgstr ""
|
||||
"Votre partage sur {ShareFolder} a expiré à {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Même si ce partage a été révoqué, vous pouvez toujours y avoir accès par le biais d'autres partages et/ou adhésions à l'espace."
|
||||
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:106
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"Please visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) souhaite commencer à partager des ressources de collaboration avec vous.\n"
|
||||
"Veuillez consulter les paramètres de votre fédération et utiliser les informations suivantes :\n"
|
||||
"Jeton : {Token}\n"
|
||||
"Domaine du fournisseur : {ProviderDomain}"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{
|
||||
#. .MessageBody }}
|
||||
#: pkg/email/templates.go:89
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"To accept the invite, please visit the following URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternatively, you can visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) souhaite commencer à partager des ressources de collaboration avec vous.\n"
|
||||
"Pour accepter l'invitation, veuillez visiter l'URL suivante :\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Vous pouvez également consulter les paramètres de votre fédération et utiliser les informations suivantes :\n"
|
||||
"Jeton : {Token}\n"
|
||||
"Domaine du fournisseur : {ProviderDomain}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:21
|
||||
msgid "{ShareSharer} has shared \"{ShareFolder}\" with you."
|
||||
msgstr "{ShareSharer} a partagé « {ShareFolder} » avec vous."
|
||||
|
||||
#. ShareCreated email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:17
|
||||
msgid "{ShareSharer} shared '{ShareFolder}' with you"
|
||||
msgstr "{ShareSharer} a partagé « {ShareFolder} » avec vous"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:48
|
||||
msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"."
|
||||
msgstr "{SpaceSharer} vous a invité à rejoindre « {SpaceName} »."
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:61
|
||||
msgid ""
|
||||
"{SpaceSharer} has removed you from \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"You might still have access through your other groups or direct membership."
|
||||
msgstr ""
|
||||
"{SpaceSharer} vous a retiré de « {SpaceName} ».\n"
|
||||
"\n"
|
||||
"Il se peut que vous y ayez encore accès par l'intermédiaire de vos autres groupes ou de votre adhésion directe."
|
||||
|
||||
#. SharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:44
|
||||
msgid "{SpaceSharer} invited you to join {SpaceName}"
|
||||
msgstr "{SpaceSharer} vous a invité à rejoindre {SpaceName}"
|
||||
|
||||
#. UnsharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:57
|
||||
msgid "{SpaceSharer} removed you from {SpaceName}"
|
||||
msgstr "{SpaceSharer} vous a retiré de {SpaceName}"
|
||||
@@ -0,0 +1,181 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Simone Broglia, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Simone Broglia, 2025\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/opencloud-eu/teams/204053/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: it\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:65
|
||||
msgid "Click here to check it: {ShareLink}"
|
||||
msgstr "Clicca qui per verificarlo: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .CallToAction }}
|
||||
#. SharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:23 pkg/email/templates.go:50
|
||||
msgid "Click here to view it: {ShareLink}"
|
||||
msgstr "Clicca qui per visualizzarlo: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:19
|
||||
msgid "Hello {ShareGrantee}"
|
||||
msgstr "Ciao {ShareGrantee}"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:32
|
||||
msgid "Hello {ShareGrantee},"
|
||||
msgstr "Ciao {ShareGrantee},"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. UnsharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. MembershipExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:46 pkg/email/templates.go:59
|
||||
#: pkg/email/templates.go:74
|
||||
msgid "Hello {SpaceGrantee},"
|
||||
msgstr "Ciao {SpaceGrantee},"
|
||||
|
||||
#. Grouped email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:118
|
||||
msgid "Hi {DisplayName},"
|
||||
msgstr "Ciao {DisplayName},"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting
|
||||
#. }}
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:87 pkg/email/templates.go:104
|
||||
msgid "Hi,"
|
||||
msgstr "Ciao,"
|
||||
|
||||
#. MembershipExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:72
|
||||
msgid "Membership of '{SpaceName}' expired at {ExpiredAt}"
|
||||
msgstr "L'iscrizione a '{SpaceName}' è scaduta il {ExpiredAt}"
|
||||
|
||||
#. Grouped email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:116
|
||||
msgid "Report"
|
||||
msgstr "Rapporto"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, Subject field (resolves
|
||||
#. directly)
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, Subject
|
||||
#. field (resolves directly)
|
||||
#: pkg/email/templates.go:85 pkg/email/templates.go:102
|
||||
msgid "ScienceMesh: {InitiatorName} wants to collaborate with you"
|
||||
msgstr "ScienceMesh: {InitiatorName} vuole collaborare con te"
|
||||
|
||||
#. ShareExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:30
|
||||
msgid "Share to '{ShareFolder}' expired at {ExpiredAt}"
|
||||
msgstr "La condivisione con '{ShareFolder}' è scaduta il {ExpiredAt}"
|
||||
|
||||
#. MembershipExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:76
|
||||
msgid ""
|
||||
"Your membership of space {SpaceName} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this membership has expired you still might have access through other shares and/or space memberships"
|
||||
msgstr ""
|
||||
"La tua iscrizione allo spazio {SpaceName} è scaduta il {ExpiredAt}.\n"
|
||||
"\n"
|
||||
"Tuttavia, potresti ancora avere accesso tramite altre condivisioni e/o iscrizioni ad altri spazi"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:34
|
||||
msgid ""
|
||||
"Your share to {ShareFolder} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this share has been revoked you still might have access through other shares and/or space memberships."
|
||||
msgstr ""
|
||||
"La tua condivisione con {ShareFolder} è scaduta il {ExpiredAt}.\n"
|
||||
"\n"
|
||||
"Tuttavia, potresti ancora avere accesso tramite altre condivisioni e/o iscrizioni ad altri spazi"
|
||||
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:106
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"Please visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) desidera iniziare a condividere risorse di collaborazione con te.\n"
|
||||
"Visita le impostazioni di federazione e utilizza i seguenti dettagli:\n"
|
||||
"\n"
|
||||
"Token: {Token}\n"
|
||||
"Dominio del provider: {ProviderDomain}"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{
|
||||
#. .MessageBody }}
|
||||
#: pkg/email/templates.go:89
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"To accept the invite, please visit the following URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternatively, you can visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) desidera iniziare a condividere risorse di collaborazione con te.\n"
|
||||
"Per accettare l'invito, visita il seguente URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"In alternativa, puoi accedere alle impostazioni di federazione e utilizzare i seguenti dettagli:\n"
|
||||
"\n"
|
||||
"Token: {Token}\n"
|
||||
"Dominio del provider: {ProviderDomain}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:21
|
||||
msgid "{ShareSharer} has shared \"{ShareFolder}\" with you."
|
||||
msgstr "{ShareSharer} ha condiviso \"{ShareFolder}\" con te."
|
||||
|
||||
#. ShareCreated email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:17
|
||||
msgid "{ShareSharer} shared '{ShareFolder}' with you"
|
||||
msgstr "{ShareSharer} ha condiviso '{ShareFolder}' con te"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:48
|
||||
msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"."
|
||||
msgstr "{SpaceSharer} ti ha invitato a unirti a \"{SpaceName}\"."
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:61
|
||||
msgid ""
|
||||
"{SpaceSharer} has removed you from \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"You might still have access through your other groups or direct membership."
|
||||
msgstr ""
|
||||
"{SpaceSharer} ti ha rimosso da \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"Potresti comunque avere accesso tramite altri gruppi o iscrizioni dirette."
|
||||
|
||||
#. SharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:44
|
||||
msgid "{SpaceSharer} invited you to join {SpaceName}"
|
||||
msgstr "{SpaceSharer} ti ha invitato a unirti a {SpaceName}"
|
||||
|
||||
#. UnsharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:57
|
||||
msgid "{SpaceSharer} removed you from {SpaceName}"
|
||||
msgstr "{SpaceSharer} ti ha rimosso da {SpaceName}"
|
||||
@@ -0,0 +1,179 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# gapho shin, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: gapho shin, 2025\n"
|
||||
"Language-Team: Korean (https://app.transifex.com/opencloud-eu/teams/204053/ko/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ko\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:65
|
||||
msgid "Click here to check it: {ShareLink}"
|
||||
msgstr "여기를 클릭하여 확인하세요: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .CallToAction }}
|
||||
#. SharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:23 pkg/email/templates.go:50
|
||||
msgid "Click here to view it: {ShareLink}"
|
||||
msgstr "여기를 클릭하여 확인하세요: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:19
|
||||
msgid "Hello {ShareGrantee}"
|
||||
msgstr "안녕하세요 {ShareGrantee}"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:32
|
||||
msgid "Hello {ShareGrantee},"
|
||||
msgstr "안녕하세요 {ShareGrantee},"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. UnsharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. MembershipExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:46 pkg/email/templates.go:59
|
||||
#: pkg/email/templates.go:74
|
||||
msgid "Hello {SpaceGrantee},"
|
||||
msgstr "안녕하세요 {SpaceGrantee},"
|
||||
|
||||
#. Grouped email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:118
|
||||
msgid "Hi {DisplayName},"
|
||||
msgstr "안녕하세요 {DisplayName},"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting
|
||||
#. }}
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:87 pkg/email/templates.go:104
|
||||
msgid "Hi,"
|
||||
msgstr "안녕하세요"
|
||||
|
||||
#. MembershipExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:72
|
||||
msgid "Membership of '{SpaceName}' expired at {ExpiredAt}"
|
||||
msgstr "'{SpaceName}'의 멤버십이 {ExpireDat}에서 만료되었습니다"
|
||||
|
||||
#. Grouped email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:116
|
||||
msgid "Report"
|
||||
msgstr "리포트"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, Subject field (resolves
|
||||
#. directly)
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, Subject
|
||||
#. field (resolves directly)
|
||||
#: pkg/email/templates.go:85 pkg/email/templates.go:102
|
||||
msgid "ScienceMesh: {InitiatorName} wants to collaborate with you"
|
||||
msgstr "ScienceMesh: {InitiatorName}이/가 여러분과 협력하고 싶습니다"
|
||||
|
||||
#. ShareExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:30
|
||||
msgid "Share to '{ShareFolder}' expired at {ExpiredAt}"
|
||||
msgstr "'{ShareFolder}'의 공유가 {ExpireDat}에서 만료됨"
|
||||
|
||||
#. MembershipExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:76
|
||||
msgid ""
|
||||
"Your membership of space {SpaceName} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this membership has expired you still might have access through other shares and/or space memberships"
|
||||
msgstr ""
|
||||
"{SpaceName} 의 멤버십이 {ExpireDat}에서 만료되었습니다\n"
|
||||
"\n"
|
||||
"이 멤버십이 만료되었더라도 다른 공유 또는 스페이스 멤버십을 통해 액세스할 수 있습니다"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:34
|
||||
msgid ""
|
||||
"Your share to {ShareFolder} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this share has been revoked you still might have access through other shares and/or space memberships."
|
||||
msgstr ""
|
||||
"{ShareFolder}에 대한 공유가 {ExpireDat}에서 만료되었습니다\n"
|
||||
"\n"
|
||||
"이 공유가 취소되더라도 다른 공유 또는 스페이스 멤버십을 통해 액세스할 수 있습니다."
|
||||
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:106
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"Please visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) 가 여러분과 협업 리소스를 공유하고자 합니다.\n"
|
||||
"공통 설정을 방문하시고 다음 세부 정보를 확인 바랍니다:\n"
|
||||
" 토큰: {Token}\n"
|
||||
" 공급자 도메인: {ProviderDomain}"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{
|
||||
#. .MessageBody }}
|
||||
#: pkg/email/templates.go:89
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"To accept the invite, please visit the following URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternatively, you can visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer}({ShareSharerMail})가 여러분과 협업 리소스를 공유하고자 합니다.\n"
|
||||
"초대를 수락하려면 다음 URL을 방문하세요:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"또는 공통 설정을 방문하시고 다음 세부 정보를 확인 바랍니다:\n"
|
||||
"토큰: {Token}\n"
|
||||
"공급자 도메인: {ProviderDomain}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:21
|
||||
msgid "{ShareSharer} has shared \"{ShareFolder}\" with you."
|
||||
msgstr "{ShareSharer}에서 \"{ShareFolder}\"를 공유했습니다."
|
||||
|
||||
#. ShareCreated email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:17
|
||||
msgid "{ShareSharer} shared '{ShareFolder}' with you"
|
||||
msgstr "{ShareSharer}가 '{ShareFolder}'를 공유했습니다"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:48
|
||||
msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"."
|
||||
msgstr "{SpaceSharer}에서 \"{SpaceName}\"에 참여하도록 초대했습니다."
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:61
|
||||
msgid ""
|
||||
"{SpaceSharer} has removed you from \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"You might still have access through your other groups or direct membership."
|
||||
msgstr ""
|
||||
"{SpaceSharer}가 사용자를 \"{SpaceName}\"에서 삭제했습니다.\n"
|
||||
"\n"
|
||||
"다른 그룹이나 직접 멤버십을 통해서도 액세스할 수 있습니다."
|
||||
|
||||
#. SharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:44
|
||||
msgid "{SpaceSharer} invited you to join {SpaceName}"
|
||||
msgstr "{SpaceSharer}가 여러분을 {SpaceName}에 초대했습니다"
|
||||
|
||||
#. UnsharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:57
|
||||
msgid "{SpaceSharer} removed you from {SpaceName}"
|
||||
msgstr "{SpaceSharer}가 {SpaceName}에서 사용자를 삭제했습니다"
|
||||
@@ -0,0 +1,179 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Stephan Paternotte <stephan@paternottes.net>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-08-05 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: nl\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:65
|
||||
msgid "Click here to check it: {ShareLink}"
|
||||
msgstr "Klik hier om het te controleren: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .CallToAction }}
|
||||
#. SharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:23 pkg/email/templates.go:50
|
||||
msgid "Click here to view it: {ShareLink}"
|
||||
msgstr "Klik hier om het te bekijken: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:19
|
||||
msgid "Hello {ShareGrantee}"
|
||||
msgstr "Beste {ShareGrantee}"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:32
|
||||
msgid "Hello {ShareGrantee},"
|
||||
msgstr "Beste {ShareGrantee},"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. UnsharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. MembershipExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:46 pkg/email/templates.go:59
|
||||
#: pkg/email/templates.go:74
|
||||
msgid "Hello {SpaceGrantee},"
|
||||
msgstr "Beste {SpaceGrantee},"
|
||||
|
||||
#. Grouped email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:118
|
||||
msgid "Hi {DisplayName},"
|
||||
msgstr "Hallo {DisplayName},"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting
|
||||
#. }}
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:87 pkg/email/templates.go:104
|
||||
msgid "Hi,"
|
||||
msgstr "Hallo,"
|
||||
|
||||
#. MembershipExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:72
|
||||
msgid "Membership of '{SpaceName}' expired at {ExpiredAt}"
|
||||
msgstr "Lidmaatschap van '{SpaceName}' is verlopen op {ExpiredAt}"
|
||||
|
||||
#. Grouped email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:116
|
||||
msgid "Report"
|
||||
msgstr "Rapportage"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, Subject field (resolves
|
||||
#. directly)
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, Subject
|
||||
#. field (resolves directly)
|
||||
#: pkg/email/templates.go:85 pkg/email/templates.go:102
|
||||
msgid "ScienceMesh: {InitiatorName} wants to collaborate with you"
|
||||
msgstr "ScienceMesh: {InitiatorName} wil met je samenwerken"
|
||||
|
||||
#. ShareExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:30
|
||||
msgid "Share to '{ShareFolder}' expired at {ExpiredAt}"
|
||||
msgstr "Share '{ShareFolder}' is verlopen op {ExpiredAt}"
|
||||
|
||||
#. MembershipExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:76
|
||||
msgid ""
|
||||
"Your membership of space {SpaceName} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this membership has expired you still might have access through other shares and/or space memberships"
|
||||
msgstr ""
|
||||
"Jouw lidmaatschap van ruimte {SpaceName} is verlopen op {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Hoewel dit lidmaatschap is verlopen, kun je mogelijk nog steeds toegang hebben via andere shares en/of ruimtelidmaatschappen."
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:34
|
||||
msgid ""
|
||||
"Your share to {ShareFolder} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this share has been revoked you still might have access through other shares and/or space memberships."
|
||||
msgstr ""
|
||||
"Jouw toegang tot {ShareFolder} is verlopen op {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Hoewel de toegang is ingetrokken, kun je mogelijk nog steeds toegang hebben via andere shares en/of ruimtelidmaatschappen."
|
||||
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:106
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"Please visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wil samenwerkingsbronnen met je delen.\n"
|
||||
"Open je federatie-instellingen en gebruik de volgende gegevens.:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{
|
||||
#. .MessageBody }}
|
||||
#: pkg/email/templates.go:89
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"To accept the invite, please visit the following URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternatively, you can visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wil samenwerkingsbronnen met je delen.\n"
|
||||
"Open de volgende URL om de uitnodiging te accepteren:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternatief kun je je federatie-instellingen openen en de volgende gegevens gebruiken.:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:21
|
||||
msgid "{ShareSharer} has shared \"{ShareFolder}\" with you."
|
||||
msgstr "{ShareSharer} heeft \"{ShareFolder}\" met jou gedeeld."
|
||||
|
||||
#. ShareCreated email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:17
|
||||
msgid "{ShareSharer} shared '{ShareFolder}' with you"
|
||||
msgstr "{ShareSharer} deelt '{ShareFolder}' met jou"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:48
|
||||
msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"."
|
||||
msgstr "{SpaceSharer} heeft je uitgenodigd om deel te nemen in \"{SpaceName}\"."
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:61
|
||||
msgid ""
|
||||
"{SpaceSharer} has removed you from \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"You might still have access through your other groups or direct membership."
|
||||
msgstr ""
|
||||
"{SpaceSharer} heeft jou verwijderd uit \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"Je hebt mogelijk nog steeds toegang via andere groepen of directe lidmaatschap."
|
||||
|
||||
#. SharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:44
|
||||
msgid "{SpaceSharer} invited you to join {SpaceName}"
|
||||
msgstr "{SpaceSharer} heeft jou uitgenodigd om deel te nemen in {SpaceName}"
|
||||
|
||||
#. UnsharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:57
|
||||
msgid "{SpaceSharer} removed you from {SpaceName}"
|
||||
msgstr "{SpaceSharer} heeft jou verwijderd uit {SpaceName}"
|
||||
@@ -0,0 +1,179 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Davis Kaza, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Davis Kaza, 2025\n"
|
||||
"Language-Team: Swedish (https://app.transifex.com/opencloud-eu/teams/204053/sv/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: sv\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:65
|
||||
msgid "Click here to check it: {ShareLink}"
|
||||
msgstr "Klicka här för att kontrollera det: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .CallToAction }}
|
||||
#. SharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:23 pkg/email/templates.go:50
|
||||
msgid "Click here to view it: {ShareLink}"
|
||||
msgstr "Klicka här för att visa det: {ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:19
|
||||
msgid "Hello {ShareGrantee}"
|
||||
msgstr "Hej {ShareGrantee}"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:32
|
||||
msgid "Hello {ShareGrantee},"
|
||||
msgstr "Hej {ShareGrantee},"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. UnsharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. MembershipExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:46 pkg/email/templates.go:59
|
||||
#: pkg/email/templates.go:74
|
||||
msgid "Hello {SpaceGrantee},"
|
||||
msgstr "Hej {SpaceGrantee},"
|
||||
|
||||
#. Grouped email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:118
|
||||
msgid "Hi {DisplayName},"
|
||||
msgstr "Hej {DisplayName},"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting
|
||||
#. }}
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:87 pkg/email/templates.go:104
|
||||
msgid "Hi,"
|
||||
msgstr "Hej,"
|
||||
|
||||
#. MembershipExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:72
|
||||
msgid "Membership of '{SpaceName}' expired at {ExpiredAt}"
|
||||
msgstr "Medlemskapet i '{SpaceName}' löpte ut {ExpiredAt}"
|
||||
|
||||
#. Grouped email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:116
|
||||
msgid "Report"
|
||||
msgstr "Rapport"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, Subject field (resolves
|
||||
#. directly)
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, Subject
|
||||
#. field (resolves directly)
|
||||
#: pkg/email/templates.go:85 pkg/email/templates.go:102
|
||||
msgid "ScienceMesh: {InitiatorName} wants to collaborate with you"
|
||||
msgstr "ScienceMesh: {InitiatorName} vill samarbeta med dig"
|
||||
|
||||
#. ShareExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:30
|
||||
msgid "Share to '{ShareFolder}' expired at {ExpiredAt}"
|
||||
msgstr "Delning till '{ShareFolder}' löpte ut {ExpiredAt}"
|
||||
|
||||
#. MembershipExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:76
|
||||
msgid ""
|
||||
"Your membership of space {SpaceName} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this membership has expired you still might have access through other shares and/or space memberships"
|
||||
msgstr ""
|
||||
"Ditt medlemskap i arbetsytan {SpaceName} har löpt ut {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Även om detta medlemskap har löpt ut kan du fortfarande ha tillgång via andra delningar och/eller medlemskap i arbetsytor"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:34
|
||||
msgid ""
|
||||
"Your share to {ShareFolder} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this share has been revoked you still might have access through other shares and/or space memberships."
|
||||
msgstr ""
|
||||
"Din delning till {ShareFolder} har löpt ut {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Även om denna delning har löpt ut kan du fortfarande ha tillgång via andra delningar och/eller medlemskap i arbetsytor"
|
||||
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:106
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"Please visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) vill börja dela samarbetsresurser med dig.\n"
|
||||
"Var vänlig och kolla dina federationsinställningar och använd följande uppgifter:\n"
|
||||
"Token: {Token}\n"
|
||||
"ProviderDomain: {ProviderDomain}"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{
|
||||
#. .MessageBody }}
|
||||
#: pkg/email/templates.go:89
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"To accept the invite, please visit the following URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternatively, you can visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer} ({ShareSharerMail}) vill börja dela samarbetsresurser med dig.\n"
|
||||
"Var vänlig och besök följande URL för att acceptera inbjudan:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternativt kan du kolla dina federationsinställningar och använda följande uppgifter:\n"
|
||||
"Token: {Token}\n"
|
||||
"ProviderDomain: {ProviderDomain}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:21
|
||||
msgid "{ShareSharer} has shared \"{ShareFolder}\" with you."
|
||||
msgstr "{ShareSharer} har delat \"{ShareFolder}\" med dig."
|
||||
|
||||
#. ShareCreated email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:17
|
||||
msgid "{ShareSharer} shared '{ShareFolder}' with you"
|
||||
msgstr "{ShareSharer} delade '{ShareFolder}' med dig"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:48
|
||||
msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"."
|
||||
msgstr "{SpaceSharer} har bjudit in dig till \"{SpaceName}\"."
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:61
|
||||
msgid ""
|
||||
"{SpaceSharer} has removed you from \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"You might still have access through your other groups or direct membership."
|
||||
msgstr ""
|
||||
"{SpaceSharer} har tagit bort dig från \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"Du kan fortfarande ha tillgång via dina andra grupper eller via direkt medlemskap."
|
||||
|
||||
#. SharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:44
|
||||
msgid "{SpaceSharer} invited you to join {SpaceName}"
|
||||
msgstr "{SpaceSharer} bjöd in dig att gå med i {SpaceName}"
|
||||
|
||||
#. UnsharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:57
|
||||
msgid "{SpaceSharer} removed you from {SpaceName}"
|
||||
msgstr "{SpaceSharer} tog bort dig från {SpaceName}"
|
||||
@@ -0,0 +1,179 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# YQS Yang, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: YQS Yang, 2025\n"
|
||||
"Language-Team: Chinese (https://app.transifex.com/opencloud-eu/teams/204053/zh/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: zh\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:65
|
||||
msgid "Click here to check it: {ShareLink}"
|
||||
msgstr "点击此处查看:{ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .CallToAction }}
|
||||
#. SharedSpace email template, resolves via {{ .CallToAction }}
|
||||
#: pkg/email/templates.go:23 pkg/email/templates.go:50
|
||||
msgid "Click here to view it: {ShareLink}"
|
||||
msgstr "点击此处查看:{ShareLink}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:19
|
||||
msgid "Hello {ShareGrantee}"
|
||||
msgstr "您好,{ShareGrantee}"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:32
|
||||
msgid "Hello {ShareGrantee},"
|
||||
msgstr "您好,{ShareGrantee}:"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. UnsharedSpace email template, resolves via {{ .Greeting }}
|
||||
#. MembershipExpired email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:46 pkg/email/templates.go:59
|
||||
#: pkg/email/templates.go:74
|
||||
msgid "Hello {SpaceGrantee},"
|
||||
msgstr "您好,{SpaceGrantee}:"
|
||||
|
||||
#. Grouped email template, resolves via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:118
|
||||
msgid "Hi {DisplayName},"
|
||||
msgstr "您好,{DisplayName}:"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{ .Greeting
|
||||
#. }}
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .Greeting }}
|
||||
#: pkg/email/templates.go:87 pkg/email/templates.go:104
|
||||
msgid "Hi,"
|
||||
msgstr "您好:"
|
||||
|
||||
#. MembershipExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:72
|
||||
msgid "Membership of '{SpaceName}' expired at {ExpiredAt}"
|
||||
msgstr "您在空间“{SpaceName}”的成员资格已于{ExpiredAt}过期"
|
||||
|
||||
#. Grouped email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:116
|
||||
msgid "Report"
|
||||
msgstr "报告"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, Subject field (resolves
|
||||
#. directly)
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, Subject
|
||||
#. field (resolves directly)
|
||||
#: pkg/email/templates.go:85 pkg/email/templates.go:102
|
||||
msgid "ScienceMesh: {InitiatorName} wants to collaborate with you"
|
||||
msgstr "ScienceMesh:{InitiatorName}希望与您协作"
|
||||
|
||||
#. ShareExpired email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:30
|
||||
msgid "Share to '{ShareFolder}' expired at {ExpiredAt}"
|
||||
msgstr "共享文件夹“{ShareFolder}”的访问权限已于{ExpiredAt}过期"
|
||||
|
||||
#. MembershipExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:76
|
||||
msgid ""
|
||||
"Your membership of space {SpaceName} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this membership has expired you still might have access through other shares and/or space memberships"
|
||||
msgstr ""
|
||||
"您在空间{SpaceName}的成员资格已于{ExpiredAt}过期\n"
|
||||
"\n"
|
||||
"即使此成员资格已过期,您仍可能通过其他共享和/或空间成员资格获得访问权限"
|
||||
|
||||
#. ShareExpired email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:34
|
||||
msgid ""
|
||||
"Your share to {ShareFolder} has expired at {ExpiredAt}\n"
|
||||
"\n"
|
||||
"Even though this share has been revoked you still might have access through other shares and/or space memberships."
|
||||
msgstr ""
|
||||
"您对{ShareFolder}的共享访问权限已于{ExpiredAt}过期\n"
|
||||
"\n"
|
||||
"即使此共享已被撤销,您仍可能通过其他共享和/或空间成员资格获得访问权限"
|
||||
|
||||
#. ScienceMeshInviteTokenGeneratedWithoutShareLink email template, resolves
|
||||
#. via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:106
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"Please visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer}({ShareSharerMail})希望与您共享协作资源\n"
|
||||
"请访问您的联邦设置并使用以下详细信息:\n"
|
||||
" 令牌:{Token}\n"
|
||||
" 提供商域:{ProviderDomain}"
|
||||
|
||||
#. ScienceMeshInviteTokenGenerated email template, resolves via {{
|
||||
#. .MessageBody }}
|
||||
#: pkg/email/templates.go:89
|
||||
msgid ""
|
||||
"{ShareSharer} ({ShareSharerMail}) wants to start sharing collaboration resources with you.\n"
|
||||
"To accept the invite, please visit the following URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"Alternatively, you can visit your federation settings and use the following details:\n"
|
||||
" Token: {Token}\n"
|
||||
" ProviderDomain: {ProviderDomain}"
|
||||
msgstr ""
|
||||
"{ShareSharer}({ShareSharerMail})希望与您共享协作资源\n"
|
||||
"要接受邀请,请访问以下URL:\n"
|
||||
"{ShareLink}\n"
|
||||
"\n"
|
||||
"或者,您可以访问您的联邦设置并使用以下详细信息:\n"
|
||||
" 令牌:{Token}\n"
|
||||
" 提供商域:{ProviderDomain}"
|
||||
|
||||
#. ShareCreated email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:21
|
||||
msgid "{ShareSharer} has shared \"{ShareFolder}\" with you."
|
||||
msgstr "{ShareSharer}已与您共享了“{ShareFolder}”"
|
||||
|
||||
#. ShareCreated email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:17
|
||||
msgid "{ShareSharer} shared '{ShareFolder}' with you"
|
||||
msgstr "{ShareSharer}与您共享了“{ShareFolder}”"
|
||||
|
||||
#. SharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:48
|
||||
msgid "{SpaceSharer} has invited you to join \"{SpaceName}\"."
|
||||
msgstr "{SpaceSharer}已邀请您加入“{SpaceName}”"
|
||||
|
||||
#. UnsharedSpace email template, resolves via {{ .MessageBody }}
|
||||
#: pkg/email/templates.go:61
|
||||
msgid ""
|
||||
"{SpaceSharer} has removed you from \"{SpaceName}\".\n"
|
||||
"\n"
|
||||
"You might still have access through your other groups or direct membership."
|
||||
msgstr ""
|
||||
"{SpaceSharer}已将您从“{SpaceName}”中移除\n"
|
||||
"\n"
|
||||
"您可能仍可通过其他群组或直接成员资格获得访问权限"
|
||||
|
||||
#. SharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:44
|
||||
msgid "{SpaceSharer} invited you to join {SpaceName}"
|
||||
msgstr "{SpaceSharer}邀请您加入{SpaceName}"
|
||||
|
||||
#. UnsharedSpace email template, Subject field (resolves directly)
|
||||
#: pkg/email/templates.go:57
|
||||
msgid "{SpaceSharer} removed you from {SpaceName}"
|
||||
msgstr "{SpaceSharer}已将您从{SpaceName}中移除"
|
||||
@@ -89,6 +89,7 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
ocdav.WithTraceProvider(traceProvider),
|
||||
ocdav.RegisterTTL(registry.GetRegisterTTL()),
|
||||
ocdav.RegisterInterval(registry.GetRegisterInterval()),
|
||||
ocdav.URLSigningSharedSecret(cfg.URLSigningSharedSecret),
|
||||
}
|
||||
|
||||
s, err := ocdav.Service(opts...)
|
||||
|
||||
@@ -34,8 +34,9 @@ type Config struct {
|
||||
|
||||
MachineAuthAPIKey string `yaml:"machine_auth_api_key" env:"OC_MACHINE_AUTH_API_KEY;OCDAV_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary for the access to resources from other services." introductionVersion:"1.0.0"`
|
||||
|
||||
Context context.Context `yaml:"-"`
|
||||
Status Status `yaml:"-"`
|
||||
URLSigningSharedSecret string `yaml:"url_signing_shared_secret" env:"OC_URL_SIGNING_SHARED_SECRET" desc:"The shared secret used to sign URLs." introductionVersion:"4.0.0"`
|
||||
Context context.Context `yaml:"-"`
|
||||
Status Status `yaml:"-"`
|
||||
|
||||
AllowPropfindDepthInfinity bool `yaml:"allow_propfind_depth_infinity" env:"OCDAV_ALLOW_PROPFIND_DEPTH_INFINITY" desc:"Allow the use of depth infinity in PROPFINDS. When enabled, a propfind will traverse through all subfolders. If many subfolders are expected, depth infinity can cause heavy server load and/or delayed response times." introductionVersion:"1.0.0"`
|
||||
}
|
||||
|
||||
@@ -119,3 +119,17 @@ Depending if you want to restart/resume all or defined failed uploads, different
|
||||
opencloud postprocessing resume -s "finished" # Equivalent to the above
|
||||
opencloud postprocessing resume -s "virusscan" # Resume all uploads currently in virusscan step
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
The postprocessing service exposes the following prometheus metrics at `<debug_endpoint>/metrics` (as configured using the `POSTPROCESSING_DEBUG_ADDR` env var):
|
||||
|
||||
| Metric Name | Type | Description | Labels |
|
||||
| --- | --- | --- | --- |
|
||||
| `opencloud_postprocessing_build_info` | Gauge | Build information | `version` |
|
||||
| `opencloud_postprocessing_events_outstanding_acks` | Gauge | Number of outstanding acks for events | |
|
||||
| `opencloud_postprocessing_events_unprocessed` | Gauge | Number of unprocessed events | |
|
||||
| `opencloud_postprocessing_events_redelivered` | Gauge | Number of redelivered events | |
|
||||
| `opencloud_postprocessing_in_progress` | Gauge | Number of postprocessing events in progress | |
|
||||
| `opencloud_postprocessing_finished` | Counter | Number of finished postprocessing events | `status` |
|
||||
| `opencloud_postprocessing_duration_seconds` | Histogram | Duration of postprocessing operations in seconds | `status` |
|
||||
|
||||
@@ -40,7 +40,15 @@ func RestartPostprocessing(cfg *config.Config) *cli.Command {
|
||||
return configlog.ReturnFatal(parser.ParseConfig(cfg))
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
stream, err := stream.NatsFromConfig(cfg.Service.Name, false, stream.NatsConfig(cfg.Postprocessing.Events))
|
||||
stream, err := stream.NatsFromConfig(cfg.Service.Name, false, stream.NatsConfig{
|
||||
Endpoint: cfg.Postprocessing.Events.Endpoint,
|
||||
Cluster: cfg.Postprocessing.Events.Cluster,
|
||||
EnableTLS: cfg.Postprocessing.Events.EnableTLS,
|
||||
TLSInsecure: cfg.Postprocessing.Events.TLSInsecure,
|
||||
TLSRootCACertificate: cfg.Postprocessing.Events.TLSRootCACertificate,
|
||||
AuthUsername: cfg.Postprocessing.Events.AuthUsername,
|
||||
AuthPassword: cfg.Postprocessing.Events.AuthPassword,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/oklog/run"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
"github.com/urfave/cli/v2"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
@@ -47,11 +46,6 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
}
|
||||
|
||||
{
|
||||
bus, err := stream.NatsFromConfig(cfg.Service.Name, false, stream.NatsConfig(cfg.Postprocessing.Events))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
st := store.Create(
|
||||
store.Store(cfg.Store.Store),
|
||||
store.TTL(cfg.Store.TTL),
|
||||
@@ -61,7 +55,7 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
store.Authentication(cfg.Store.AuthUsername, cfg.Store.AuthPassword),
|
||||
)
|
||||
|
||||
svc, err := service.NewPostprocessingService(ctx, bus, logger, st, traceProvider, cfg.Postprocessing)
|
||||
svc, err := service.NewPostprocessingService(ctx, logger, st, traceProvider, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ type Events struct {
|
||||
EnableTLS bool `yaml:"enable_tls" env:"OC_EVENTS_ENABLE_TLS;POSTPROCESSING_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the OpenCloud service which receives and delivers events between the services." introductionVersion:"1.0.0"`
|
||||
AuthUsername string `yaml:"username" env:"OC_EVENTS_AUTH_USERNAME;POSTPROCESSING_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the OpenCloud service which receives and delivers events between the services." introductionVersion:"1.0.0"`
|
||||
AuthPassword string `yaml:"password" env:"OC_EVENTS_AUTH_PASSWORD;POSTPROCESSING_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the OpenCloud service which receives and delivers events between the services." introductionVersion:"1.0.0"`
|
||||
|
||||
MaxAckPending int `yaml:"max_ack_pending" env:"SEARCH_EVENTS_MAX_ACK_PENDING" desc:"The maximum number of unacknowledged messages. This is used to limit the number of messages that can be in flight at the same time." introductionVersion:"%%NEXT%%"`
|
||||
AckWait time.Duration `yaml:"ack_wait" env:"SEARCH_EVENTS_ACK_WAIT" desc:"The time to wait for an ack before the message is redelivered. This is used to ensure that messages are not lost if the consumer crashes." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// Debug defines the available debug configuration.
|
||||
|
||||
@@ -28,8 +28,10 @@ func DefaultConfig() *config.Config {
|
||||
},
|
||||
Postprocessing: config.Postprocessing{
|
||||
Events: config.Events{
|
||||
Endpoint: "127.0.0.1:9233",
|
||||
Cluster: "opencloud-cluster",
|
||||
Endpoint: "127.0.0.1:9233",
|
||||
Cluster: "opencloud-cluster",
|
||||
MaxAckPending: 10_000,
|
||||
AckWait: 1 * time.Minute,
|
||||
},
|
||||
Workers: 3,
|
||||
RetryBackoffDuration: 5 * time.Second,
|
||||
|
||||
85
services/postprocessing/pkg/metrics/metrics.go
Normal file
85
services/postprocessing/pkg/metrics/metrics.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var (
|
||||
// Namespace defines the namespace for the defines metrics.
|
||||
Namespace = "opencloud"
|
||||
|
||||
// Subsystem defines the subsystem for the defines metrics.
|
||||
Subsystem = "postprocessing"
|
||||
|
||||
buildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "build_info",
|
||||
Help: "Build information",
|
||||
}, []string{"version"})
|
||||
eventsOutstandingAcks = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "events_outstanding_acks",
|
||||
Help: "Number of outstanding acks for events",
|
||||
})
|
||||
eventsUnprocessed = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "events_unprocessed",
|
||||
Help: "Number of unprocessed events",
|
||||
})
|
||||
eventsRedelivered = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "events_redelivered",
|
||||
Help: "Number of redelivered events",
|
||||
})
|
||||
inProgress = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "in_progress",
|
||||
Help: "Number of postprocessing events in progress",
|
||||
})
|
||||
finished = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "finished",
|
||||
Help: "Number of finished postprocessing events",
|
||||
}, []string{"status"})
|
||||
duration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "duration_seconds",
|
||||
Help: "Duration of postprocessing operations in seconds",
|
||||
Buckets: []float64{0.1, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300, 600, 1200},
|
||||
}, []string{"status"})
|
||||
)
|
||||
|
||||
// Metrics defines the available metrics of this service.
|
||||
type Metrics struct {
|
||||
// Counter *prometheus.CounterVec
|
||||
BuildInfo *prometheus.GaugeVec
|
||||
EventsOutstandingAcks prometheus.Gauge
|
||||
EventsUnprocessed prometheus.Gauge
|
||||
EventsRedelivered prometheus.Gauge
|
||||
InProgress prometheus.Gauge
|
||||
Finished *prometheus.CounterVec
|
||||
Duration *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
// New initializes the available metrics.
|
||||
func New() *Metrics {
|
||||
m := &Metrics{
|
||||
BuildInfo: buildInfo,
|
||||
EventsOutstandingAcks: eventsOutstandingAcks,
|
||||
EventsUnprocessed: eventsUnprocessed,
|
||||
EventsRedelivered: eventsRedelivered,
|
||||
InProgress: inProgress,
|
||||
Finished: finished,
|
||||
Duration: duration,
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -24,6 +24,7 @@ type Postprocessing struct {
|
||||
Failures int
|
||||
InitiatorID string
|
||||
Finished bool
|
||||
StartTime time.Time
|
||||
|
||||
config config.Postprocessing
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/version"
|
||||
"github.com/opencloud-eu/opencloud/services/postprocessing/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/postprocessing/pkg/metrics"
|
||||
"github.com/opencloud-eu/opencloud/services/postprocessing/pkg/postprocessing"
|
||||
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/raw"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"go-micro.dev/v4/store"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
@@ -20,14 +24,15 @@ import (
|
||||
|
||||
// PostprocessingService is an instance of the service handling postprocessing of files
|
||||
type PostprocessingService struct {
|
||||
ctx context.Context
|
||||
log log.Logger
|
||||
events <-chan events.Event
|
||||
pub events.Publisher
|
||||
steps []events.Postprocessingstep
|
||||
store store.Store
|
||||
c config.Postprocessing
|
||||
tp trace.TracerProvider
|
||||
ctx context.Context
|
||||
log log.Logger
|
||||
events <-chan raw.Event
|
||||
pub events.Publisher
|
||||
steps []events.Postprocessingstep
|
||||
store store.Store
|
||||
c config.Postprocessing
|
||||
tp trace.TracerProvider
|
||||
metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -40,27 +45,56 @@ var (
|
||||
)
|
||||
|
||||
// NewPostprocessingService returns a new instance of a postprocessing service
|
||||
func NewPostprocessingService(ctx context.Context, stream events.Stream, logger log.Logger, sto store.Store, tp trace.TracerProvider, c config.Postprocessing) (*PostprocessingService, error) {
|
||||
evs, err := events.Consume(stream, "postprocessing",
|
||||
func NewPostprocessingService(ctx context.Context, logger log.Logger, sto store.Store, tp trace.TracerProvider, cfg *config.Config) (*PostprocessingService, error) {
|
||||
pub, err := stream.NatsFromConfig(cfg.Service.Name, false, stream.NatsConfig{
|
||||
Endpoint: cfg.Postprocessing.Events.Endpoint,
|
||||
Cluster: cfg.Postprocessing.Events.Cluster,
|
||||
EnableTLS: cfg.Postprocessing.Events.EnableTLS,
|
||||
TLSInsecure: cfg.Postprocessing.Events.TLSInsecure,
|
||||
TLSRootCACertificate: cfg.Postprocessing.Events.TLSRootCACertificate,
|
||||
AuthUsername: cfg.Postprocessing.Events.AuthUsername,
|
||||
AuthPassword: cfg.Postprocessing.Events.AuthPassword,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, err := raw.FromConfig(ctx, cfg.Service.Name, raw.Config{
|
||||
Endpoint: cfg.Postprocessing.Events.Endpoint,
|
||||
Cluster: cfg.Postprocessing.Events.Cluster,
|
||||
EnableTLS: cfg.Postprocessing.Events.EnableTLS,
|
||||
TLSInsecure: cfg.Postprocessing.Events.TLSInsecure,
|
||||
TLSRootCACertificate: cfg.Postprocessing.Events.TLSRootCACertificate,
|
||||
AuthUsername: cfg.Postprocessing.Events.AuthUsername,
|
||||
AuthPassword: cfg.Postprocessing.Events.AuthPassword,
|
||||
MaxAckPending: cfg.Postprocessing.Events.MaxAckPending,
|
||||
AckWait: cfg.Postprocessing.Events.AckWait,
|
||||
})
|
||||
|
||||
evs, err := raw.Consume("postprocessing-pull",
|
||||
events.BytesReceived{},
|
||||
events.StartPostprocessingStep{},
|
||||
events.UploadReady{},
|
||||
events.PostprocessingStepFinished{},
|
||||
events.ResumePostprocessing{},
|
||||
)
|
||||
events.ResumePostprocessing{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := metrics.New()
|
||||
m.BuildInfo.WithLabelValues(version.GetString()).Set(1)
|
||||
monitorMetrics(raw, "postprocessing-pull", m, logger)
|
||||
|
||||
return &PostprocessingService{
|
||||
ctx: ctx,
|
||||
log: logger,
|
||||
events: evs,
|
||||
pub: stream,
|
||||
steps: getSteps(c),
|
||||
store: sto,
|
||||
c: c,
|
||||
tp: tp,
|
||||
ctx: ctx,
|
||||
log: logger,
|
||||
events: evs,
|
||||
pub: pub,
|
||||
steps: getSteps(cfg.Postprocessing),
|
||||
store: sto,
|
||||
c: cfg.Postprocessing,
|
||||
tp: tp,
|
||||
metrics: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -91,7 +125,7 @@ func (pps *PostprocessingService) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pps *PostprocessingService) processEvent(e events.Event) error {
|
||||
func (pps *PostprocessingService) processEvent(e raw.Event) error {
|
||||
var (
|
||||
next interface{}
|
||||
pp *postprocessing.Postprocessing
|
||||
@@ -102,7 +136,16 @@ func (pps *PostprocessingService) processEvent(e events.Event) error {
|
||||
ctx, span := pps.tp.Tracer("postprocessing").Start(ctx, "processEvent")
|
||||
defer span.End()
|
||||
|
||||
switch ev := e.Event.(type) {
|
||||
ackEvent := true
|
||||
defer func() {
|
||||
if ackEvent {
|
||||
if err := e.Ack(); err != nil {
|
||||
pps.log.Error().Err(err).Msg("unable to ack event")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
switch ev := e.Event.Event.(type) {
|
||||
case events.BytesReceived:
|
||||
pp = &postprocessing.Postprocessing{
|
||||
ID: ev.UploadID,
|
||||
@@ -114,7 +157,9 @@ func (pps *PostprocessingService) processEvent(e events.Event) error {
|
||||
Steps: pps.steps,
|
||||
InitiatorID: e.InitiatorID,
|
||||
ImpersonatingUser: ev.ImpersonatingUser,
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
pps.metrics.InProgress.Inc()
|
||||
next = pp.Init(ev)
|
||||
case events.PostprocessingStepFinished:
|
||||
if ev.UploadID == "" {
|
||||
@@ -165,17 +210,27 @@ func (pps *PostprocessingService) processEvent(e events.Event) error {
|
||||
}
|
||||
})
|
||||
case events.UploadReady:
|
||||
pps.metrics.InProgress.Dec()
|
||||
// the upload failed - let's keep it around for a while - but mark it as finished
|
||||
pp, err = pps.getPP(pps.store, ev.UploadID)
|
||||
if err != nil {
|
||||
pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot get upload")
|
||||
return fmt.Errorf("%w: cannot get upload", ErrEvent)
|
||||
}
|
||||
|
||||
if ev.Failed {
|
||||
// the upload failed - let's keep it around for a while - but mark it as finished
|
||||
pp, err = pps.getPP(pps.store, ev.UploadID)
|
||||
if err != nil {
|
||||
pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot get upload")
|
||||
return fmt.Errorf("%w: cannot get upload", ErrEvent)
|
||||
pps.metrics.Finished.WithLabelValues("failed").Inc()
|
||||
if !pp.StartTime.IsZero() {
|
||||
pps.metrics.Duration.WithLabelValues("failed").Observe(time.Since(pp.StartTime).Seconds())
|
||||
}
|
||||
pp.Finished = true
|
||||
return storePP(pps.store, pp)
|
||||
}
|
||||
|
||||
pps.metrics.Finished.WithLabelValues("succeeded").Inc()
|
||||
if !pp.StartTime.IsZero() {
|
||||
pps.metrics.Duration.WithLabelValues("succeeded").Observe(time.Since(pp.StartTime).Seconds())
|
||||
}
|
||||
// the storage provider thinks the upload is done - so no need to keep it any more
|
||||
if err := pps.store.Delete(ev.UploadID); err != nil {
|
||||
pps.log.Error().Str("uploadID", ev.UploadID).Err(err).Msg("cannot delete upload")
|
||||
@@ -189,6 +244,7 @@ func (pps *PostprocessingService) processEvent(e events.Event) error {
|
||||
ctx = ctxpkg.ContextSetInitiator(ctx, pp.InitiatorID)
|
||||
|
||||
if err := storePP(pps.store, pp); err != nil {
|
||||
ackEvent = false
|
||||
pps.log.Error().Str("uploadID", pp.ID).Err(err).Msg("cannot store upload")
|
||||
return fmt.Errorf("%w: cannot store upload", ErrEvent)
|
||||
}
|
||||
@@ -324,3 +380,26 @@ func (pps *PostprocessingService) findUploadsByStep(step events.Postprocessingst
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
func monitorMetrics(stream raw.Stream, name string, m *metrics.Metrics, logger log.Logger) {
|
||||
ctx := context.Background()
|
||||
consumer, err := stream.JetStream().Consumer(ctx, name)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to get consumer")
|
||||
}
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
info, err := consumer.Info(ctx)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to get consumer")
|
||||
continue
|
||||
}
|
||||
|
||||
m.EventsOutstandingAcks.Set(float64(info.NumAckPending))
|
||||
m.EventsUnprocessed.Set(float64(info.NumPending))
|
||||
m.EventsRedelivered.Set(float64(info.NumRedelivered))
|
||||
logger.Trace().Msg("updated postprocessing event metrics")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/signedurl"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go-micro.dev/v4/selector"
|
||||
@@ -316,6 +317,16 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config,
|
||||
Logger: logger,
|
||||
RevaGatewaySelector: gatewaySelector,
|
||||
})
|
||||
|
||||
var signURLVerifier signedurl.Verifier
|
||||
|
||||
if cfg.PreSignedURL.JWTSigningSharedSecret != "" {
|
||||
var err error
|
||||
signURLVerifier, err = signedurl.NewJWTSignedURL(signedurl.WithSecret(cfg.PreSignedURL.JWTSigningSharedSecret))
|
||||
if err != nil {
|
||||
logger.Fatal().Err(err).Msg("Failed to initialize signed URL configuration.")
|
||||
}
|
||||
}
|
||||
authenticators = append(authenticators, middleware.SignedURLAuthenticator{
|
||||
Logger: logger,
|
||||
PreSignedURLConfig: cfg.PreSignedURL,
|
||||
@@ -323,6 +334,7 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config,
|
||||
UserRoleAssigner: roleAssigner,
|
||||
Store: signingKeyStore,
|
||||
Now: time.Now,
|
||||
URLVerifier: signURLVerifier,
|
||||
})
|
||||
|
||||
cspConfig, err := middleware.LoadCSPConfig(cfg)
|
||||
|
||||
@@ -114,7 +114,7 @@ const (
|
||||
type OIDC struct {
|
||||
Issuer string `yaml:"issuer" env:"OC_URL;OC_OIDC_ISSUER;PROXY_OIDC_ISSUER" desc:"URL of the OIDC issuer. It defaults to URL of the builtin IDP." introductionVersion:"1.0.0"`
|
||||
Insecure bool `yaml:"insecure" env:"OC_INSECURE;PROXY_OIDC_INSECURE" desc:"Disable TLS certificate validation for connections to the IDP. Note that this is not recommended for production environments." introductionVersion:"1.0.0"`
|
||||
AccessTokenVerifyMethod string `yaml:"access_token_verify_method" env:"PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD" desc:"Sets how OIDC access tokens should be verified. Possible values are 'none' and 'jwt'. When using 'none', no special validation apart from using it for accessing the IPD's userinfo endpoint will be done. When using 'jwt', it tries to parse the access token as a jwt token and verifies the signature using the keys published on the IDP's 'jwks_uri'." introductionVersion:"1.0.0"`
|
||||
AccessTokenVerifyMethod string `yaml:"access_token_verify_method" env:"PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD" desc:"Sets how OIDC access tokens should be verified. Possible values are 'none' and 'jwt'. When using 'none', no special validation apart from using it for accessing the IDP's userinfo endpoint will be done. When using 'jwt', it tries to parse the access token as a jwt token and verifies the signature using the keys published on the IDP's 'jwks_uri'." introductionVersion:"1.0.0"`
|
||||
SkipUserInfo bool `yaml:"skip_user_info" env:"PROXY_OIDC_SKIP_USER_INFO" desc:"Do not look up user claims at the userinfo endpoint and directly read them from the access token. Incompatible with 'PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD=none'." introductionVersion:"1.0.0"`
|
||||
UserinfoCache *Cache `yaml:"user_info_cache"`
|
||||
JWKS JWKS `yaml:"jwks"`
|
||||
@@ -180,9 +180,10 @@ type StaticSelectorConf struct {
|
||||
|
||||
// PreSignedURL is the config for the pre-signed url middleware
|
||||
type PreSignedURL struct {
|
||||
AllowedHTTPMethods []string `yaml:"allowed_http_methods"`
|
||||
Enabled bool `yaml:"enabled" env:"PROXY_ENABLE_PRESIGNEDURLS" desc:"Allow OCS to get a signing key to sign requests." introductionVersion:"1.0.0"`
|
||||
SigningKeys *SigningKeys `yaml:"signing_keys"`
|
||||
AllowedHTTPMethods []string `yaml:"allowed_http_methods"`
|
||||
Enabled bool `yaml:"enabled" env:"PROXY_ENABLE_PRESIGNEDURLS" desc:"Allow OCS to get a signing key to sign requests." introductionVersion:"1.0.0"`
|
||||
SigningKeys *SigningKeys `yaml:"signing_keys"`
|
||||
JWTSigningSharedSecret string `yaml:"url_signing_shared_secret" env:"OC_URL_SIGNING_SHARED_SECRET" desc:"The shared secret used to sign URLs." introductionVersion:"4.0.0"`
|
||||
}
|
||||
|
||||
// SigningKeys is a store configuration.
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/user/backend"
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/signedurl"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
@@ -26,6 +27,7 @@ const (
|
||||
_paramOCExpires = "OC-Expires"
|
||||
_paramOCVerb = "OC-Verb"
|
||||
_paramOCAlgo = "OC-Algo"
|
||||
_paramOCJWTSig = "oc-jwt-sig"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -46,15 +48,23 @@ type SignedURLAuthenticator struct {
|
||||
UserRoleAssigner userroles.UserRoleAssigner
|
||||
Store microstore.Store
|
||||
Now func() time.Time
|
||||
URLVerifier signedurl.Verifier
|
||||
}
|
||||
|
||||
func (m SignedURLAuthenticator) shouldServe(req *http.Request) bool {
|
||||
func (m SignedURLAuthenticator) shouldServeLegacy(req *http.Request) bool {
|
||||
if !m.PreSignedURLConfig.Enabled {
|
||||
return false
|
||||
}
|
||||
return req.URL.Query().Get(_paramOCSignature) != ""
|
||||
}
|
||||
|
||||
func (m SignedURLAuthenticator) shouldServe(req *http.Request) bool {
|
||||
if m.URLVerifier == nil {
|
||||
return false
|
||||
}
|
||||
return req.URL.Query().Get(_paramOCJWTSig) != ""
|
||||
}
|
||||
|
||||
func (m SignedURLAuthenticator) validate(req *http.Request) (err error) {
|
||||
query := req.URL.Query()
|
||||
|
||||
@@ -216,10 +226,61 @@ func (m SignedURLAuthenticator) createSignature(url string, signingKey []byte) s
|
||||
|
||||
// Authenticate implements the authenticator interface to authenticate requests via signed URL auth.
|
||||
func (m SignedURLAuthenticator) Authenticate(r *http.Request) (*http.Request, bool) {
|
||||
if !m.shouldServe(r) {
|
||||
return nil, false
|
||||
switch {
|
||||
case m.shouldServeLegacy(r):
|
||||
return m.authenticateLegacy(r)
|
||||
case m.shouldServe(r):
|
||||
return m.authenticate(r)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (m SignedURLAuthenticator) authenticate(r *http.Request) (*http.Request, bool) {
|
||||
u := r.URL.String()
|
||||
if !r.URL.IsAbs() {
|
||||
u = "https://" + r.Host + u
|
||||
}
|
||||
|
||||
userid, err := m.URLVerifier.Verify(u)
|
||||
if err != nil {
|
||||
m.Logger.Error().
|
||||
Err(err).
|
||||
Str("authenticator", "signed_url_jwt").
|
||||
Str("path", r.URL.Path).
|
||||
Str("url", u).
|
||||
Msg("Could not verify JWT signature")
|
||||
return nil, false
|
||||
}
|
||||
user, _, err := m.UserProvider.GetUserByClaims(r.Context(), "userid", userid)
|
||||
if err != nil {
|
||||
m.Logger.Error().
|
||||
Err(err).
|
||||
Str("authenticator", "signed_url_jwt").
|
||||
Str("path", r.URL.Path).
|
||||
Msg("Could not get user by claim")
|
||||
return nil, false
|
||||
}
|
||||
user, err = m.UserRoleAssigner.ApplyUserRole(r.Context(), user)
|
||||
if err != nil {
|
||||
m.Logger.Error().
|
||||
Err(err).
|
||||
Str("authenticator", "signed_url").
|
||||
Str("path", r.URL.Path).
|
||||
Msg("Could not get user by claim")
|
||||
return nil, false
|
||||
}
|
||||
ctx := revactx.ContextSetUser(r.Context(), user)
|
||||
r = r.WithContext(ctx)
|
||||
m.Logger.Debug().
|
||||
Str("authenticator", "signed_url").
|
||||
Str("path", r.URL.Path).
|
||||
Msg("successfully authenticated request")
|
||||
return r, true
|
||||
}
|
||||
|
||||
// authenticateLegacy is a helper function to authenticate requests that use the legacy
|
||||
// client side signed URLs
|
||||
func (m SignedURLAuthenticator) authenticateLegacy(r *http.Request) (*http.Request, bool) {
|
||||
user, _, err := m.UserProvider.GetUserByClaims(r.Context(), "username", r.URL.Query().Get(_paramOCCredential))
|
||||
if err != nil {
|
||||
m.Logger.Error().
|
||||
@@ -254,7 +315,6 @@ func (m SignedURLAuthenticator) Authenticate(r *http.Request) (*http.Request, bo
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// TODO: set user in context
|
||||
m.Logger.Debug().
|
||||
Str("authenticator", "signed_url").
|
||||
Str("path", r.URL.Path).
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/config"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/signedurl"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go-micro.dev/v4/store"
|
||||
)
|
||||
|
||||
func TestSignedURLAuth_shouldServe(t *testing.T) {
|
||||
func TestSignedURLAuthLegacy_shouldServe(t *testing.T) {
|
||||
pua := SignedURLAuthenticator{}
|
||||
tests := []struct {
|
||||
url string
|
||||
@@ -29,6 +30,39 @@ func TestSignedURLAuth_shouldServe(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
pua.PreSignedURLConfig.Enabled = tt.enabled
|
||||
r := httptest.NewRequest("", tt.url, nil)
|
||||
result := pua.shouldServeLegacy(r)
|
||||
|
||||
if result != tt.expected {
|
||||
t.Errorf("with %s expected %t got %t", tt.url, tt.expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedURLAuth_shouldServe(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
secret string
|
||||
enabled bool
|
||||
expected bool
|
||||
}{
|
||||
{"https://example.com/example.jpg", "", true, false},
|
||||
{"https://example.com/example.jpg", "", false, false},
|
||||
{"https://example.com/example.jpg?oc-jwt-sig=something1", "secret", true, true},
|
||||
{"https://example.com/example.jpg?oc-jwt-sig=something2", "", true, false},
|
||||
{"https://example.com/example.jpg?oc-jwt-sig=something3", "secret", false, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
pua := SignedURLAuthenticator{}
|
||||
pua.PreSignedURLConfig.Enabled = tt.enabled
|
||||
if tt.secret != "" {
|
||||
signURLVerifier, err := signedurl.NewJWTSignedURL(signedurl.WithSecret(tt.secret))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create signed URL verifier: %v", err)
|
||||
}
|
||||
pua.URLVerifier = signURLVerifier
|
||||
}
|
||||
r := httptest.NewRequest("", tt.url, nil)
|
||||
result := pua.shouldServe(r)
|
||||
|
||||
if result != tt.expected {
|
||||
|
||||
@@ -15,4 +15,4 @@ packages:
|
||||
Retriever: {}
|
||||
github.com/opencloud-eu/opencloud/services/search/pkg/search:
|
||||
interfaces:
|
||||
Searcher: {}
|
||||
Searcher: {}
|
||||
@@ -162,3 +162,16 @@ The indexing process tries to be self-healing in some situations.
|
||||
In the following example, let's assume a file tree `foo/bar/baz` exists.
|
||||
If the folder `bar` gets renamed to `new-bar`, the path to `baz` is no longer `foo/bar/baz` but `foo/new-bar/baz`.
|
||||
The search service checks the change and either just updates the path in the index or creates a new index for all items affected if none was present.
|
||||
|
||||
## Metrics
|
||||
|
||||
The search service exposes the following prometheus metrics at `<debug_endpoint>/metrics` (as configured using the `SEARCH_DEBUG_ADDR` env var):
|
||||
|
||||
| Metric Name | Type | Description | Labels |
|
||||
| --- | --- | --- | --- |
|
||||
| `opencloud_search_build_info` | Gauge | Build information | `version` |
|
||||
| `opencloud_search_events_outstanding_acks` | Gauge | Number of outstanding acks for events | |
|
||||
| `opencloud_search_events_unprocessed` | Gauge | Number of unprocessed events | |
|
||||
| `opencloud_search_events_redelivered` | Gauge | Number of redelivered events | |
|
||||
| `opencloud_search_search_duration_seconds` | Histogram | Duration of search operations in seconds | `status` |
|
||||
| `opencloud_search_index_duration_seconds` | Histogram | Duration of indexing operations in seconds | `status` |
|
||||
|
||||
@@ -28,6 +28,7 @@ type Config struct {
|
||||
Engine Engine `yaml:"engine"`
|
||||
Extractor Extractor `yaml:"extractor"`
|
||||
ContentExtractionSizeLimit uint64 `yaml:"content_extraction_size_limit" env:"SEARCH_CONTENT_EXTRACTION_SIZE_LIMIT" desc:"Maximum file size in bytes that is allowed for content extraction." introductionVersion:"1.0.0"`
|
||||
BatchSize int `yaml:"batch_size" env:"SEARCH_BATCH_SIZE" desc:"The number of documents to process in a single batch. Defaults to 500." introductionVersion:"1.0.0"`
|
||||
|
||||
ServiceAccount ServiceAccount `yaml:"service_account"`
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package defaults
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
@@ -53,8 +54,11 @@ func DefaultConfig() *config.Config {
|
||||
DebounceDuration: 1000,
|
||||
AsyncUploads: true,
|
||||
EnableTLS: false,
|
||||
MaxAckPending: 1000,
|
||||
AckWait: 1 * time.Minute,
|
||||
},
|
||||
ContentExtractionSizeLimit: 20 * 1024 * 1024, // Limit content extraction to <20MB files by default
|
||||
BatchSize: 500,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// Events combines the configuration options for the event bus.
|
||||
type Events struct {
|
||||
Endpoint string `yaml:"endpoint" env:"OC_EVENTS_ENDPOINT;SEARCH_EVENTS_ENDPOINT" desc:"The address of the event system. The event system is the message queuing service. It is used as message broker for the microservice architecture." introductionVersion:"1.0.0"`
|
||||
@@ -13,4 +15,7 @@ type Events struct {
|
||||
EnableTLS bool `yaml:"enable_tls" env:"OC_EVENTS_ENABLE_TLS;SEARCH_EVENTS_ENABLE_TLS" desc:"Enable TLS for the connection to the events broker. The events broker is the OpenCloud service which receives and delivers events between the services." introductionVersion:"1.0.0"`
|
||||
AuthUsername string `yaml:"username" env:"OC_EVENTS_AUTH_USERNAME;SEARCH_EVENTS_AUTH_USERNAME" desc:"The username to authenticate with the events broker. The events broker is the OpenCloud service which receives and delivers events between the services." introductionVersion:"1.0.0"`
|
||||
AuthPassword string `yaml:"password" env:"OC_EVENTS_AUTH_PASSWORD;SEARCH_EVENTS_AUTH_PASSWORD" desc:"The password to authenticate with the events broker. The events broker is the OpenCloud service which receives and delivers events between the services." introductionVersion:"1.0.0"`
|
||||
|
||||
MaxAckPending int `yaml:"max_ack_pending" env:"SEARCH_EVENTS_MAX_ACK_PENDING" desc:"The maximum number of unacknowledged messages. This is used to limit the number of messages that can be in flight at the same time." introductionVersion:"%%NEXT%%"`
|
||||
AckWait time.Duration `yaml:"ack_wait" env:"SEARCH_EVENTS_ACK_WAIT" desc:"The time to wait for an ack before the message is redelivered. This is used to ensure that messages are not lost if the consumer crashes." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
@@ -20,10 +21,11 @@ import (
|
||||
"github.com/blevesearch/bleve/v2/mapping"
|
||||
"github.com/blevesearch/bleve/v2/search/query"
|
||||
storageProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
searchMessage "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/search/v0"
|
||||
@@ -32,10 +34,16 @@ import (
|
||||
searchQuery "github.com/opencloud-eu/opencloud/services/search/pkg/query"
|
||||
)
|
||||
|
||||
const _batchSize = 500
|
||||
|
||||
// Bleve represents a search engine which utilizes bleve to search and store resources.
|
||||
type Bleve struct {
|
||||
index bleve.Index
|
||||
queryCreator searchQuery.Creator[query.Query]
|
||||
batch *bleve.Batch
|
||||
batchSize int
|
||||
m sync.Mutex // batch operations in bleve are not thread-safe
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewBleveIndex returns a new bleve index
|
||||
@@ -60,10 +68,11 @@ func NewBleveIndex(root string) (bleve.Index, error) {
|
||||
}
|
||||
|
||||
// NewBleveEngine creates a new Bleve instance
|
||||
func NewBleveEngine(index bleve.Index, queryCreator searchQuery.Creator[query.Query]) *Bleve {
|
||||
func NewBleveEngine(index bleve.Index, queryCreator searchQuery.Creator[query.Query], log log.Logger) *Bleve {
|
||||
return &Bleve{
|
||||
index: index,
|
||||
queryCreator: queryCreator,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,8 +242,60 @@ func (b *Bleve) Search(ctx context.Context, sir *searchService.SearchIndexReques
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Bleve) StartBatch(batchSize int) error {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
|
||||
if batchSize <= 0 {
|
||||
return errors.New("batch size must be greater than 0")
|
||||
}
|
||||
|
||||
if b.batch != nil {
|
||||
b.log.Debug().Msg("reusing another batch that has already been started")
|
||||
return nil
|
||||
}
|
||||
|
||||
b.log.Debug().Msg("Starting new batch")
|
||||
b.batch = b.index.NewBatch()
|
||||
b.batchSize = batchSize
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bleve) EndBatch() error {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
|
||||
if b.batch == nil {
|
||||
return errors.New("no batch started")
|
||||
}
|
||||
|
||||
b.log.Debug().Int("size", b.batch.Size()).Msg("Ending batch")
|
||||
if err := b.index.Batch(b.batch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.batch = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert indexes or stores Resource data fields.
|
||||
func (b *Bleve) Upsert(id string, r Resource) error {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
|
||||
if b.batch != nil {
|
||||
if err := b.batch.Index(id, r); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.batch.Size() >= b.batchSize {
|
||||
b.log.Debug().Int("size", b.batch.Size()).Msg("Committing batch")
|
||||
if err := b.index.Batch(b.batch); err != nil {
|
||||
return err
|
||||
}
|
||||
b.batch = b.index.NewBatch()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return b.index.Index(id, r)
|
||||
}
|
||||
|
||||
@@ -298,6 +359,19 @@ func (b *Bleve) Restore(id string) error {
|
||||
|
||||
// Purge removes a resource from the index, irreversible operation.
|
||||
func (b *Bleve) Purge(id string) error {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
|
||||
if b.batch != nil {
|
||||
b.batch.Delete(id)
|
||||
if b.batch.Size() >= b.batchSize {
|
||||
if err := b.index.Batch(b.batch); err != nil {
|
||||
return err
|
||||
}
|
||||
b.batch = b.index.NewBatch()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return b.index.Delete(id)
|
||||
}
|
||||
|
||||
@@ -452,7 +526,7 @@ func (b *Bleve) updateEntity(id string, mutateFunc func(r *Resource)) (*Resource
|
||||
|
||||
mutateFunc(it)
|
||||
|
||||
return it, b.index.Index(it.ID, it)
|
||||
return it, b.Upsert(id, *it)
|
||||
}
|
||||
|
||||
func (b *Bleve) setDeleted(id string, deleted bool) error {
|
||||
@@ -468,6 +542,7 @@ func (b *Bleve) setDeleted(id string, deleted bool) error {
|
||||
bleve.NewQueryStringQuery("RootID:"+it.RootID),
|
||||
bleve.NewQueryStringQuery("Path:"+escapeQuery(it.Path+"/*")),
|
||||
)
|
||||
|
||||
bleveReq := bleve.NewSearchRequest(q)
|
||||
bleveReq.Size = math.MaxInt
|
||||
bleveReq.Fields = []string{"*"}
|
||||
@@ -476,6 +551,8 @@ func (b *Bleve) setDeleted(id string, deleted bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
b.StartBatch(_batchSize)
|
||||
defer b.EndBatch()
|
||||
for _, h := range res.Hits {
|
||||
_, err := b.updateEntity(h.ID, func(r *Resource) {
|
||||
r.Deleted = deleted
|
||||
@@ -484,6 +561,7 @@ func (b *Bleve) setDeleted(id string, deleted bool) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
b.EndBatch()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
searchmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/search/v0"
|
||||
searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/content"
|
||||
@@ -53,6 +54,7 @@ var _ = Describe("Bleve", func() {
|
||||
rootResource engine.Resource
|
||||
parentResource engine.Resource
|
||||
childResource engine.Resource
|
||||
childResource2 engine.Resource
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
@@ -62,7 +64,7 @@ var _ = Describe("Bleve", func() {
|
||||
idx, err = bleveSearch.NewMemOnly(mapping)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
eng = engine.NewBleveEngine(idx, bleve.DefaultCreator)
|
||||
eng = engine.NewBleveEngine(idx, bleve.DefaultCreator, log.Logger{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
rootResource = engine.Resource{
|
||||
@@ -89,11 +91,20 @@ var _ = Describe("Bleve", func() {
|
||||
Type: uint64(sprovider.ResourceType_RESOURCE_TYPE_FILE),
|
||||
Document: content.Document{Name: "child.pdf"},
|
||||
}
|
||||
|
||||
childResource2 = engine.Resource{
|
||||
ID: "1$2!5",
|
||||
ParentID: parentResource.ID,
|
||||
RootID: rootResource.ID,
|
||||
Path: "./parent d!r/child2.pdf",
|
||||
Type: uint64(sprovider.ResourceType_RESOURCE_TYPE_FILE),
|
||||
Document: content.Document{Name: "child2.pdf"},
|
||||
}
|
||||
})
|
||||
|
||||
Describe("New", func() {
|
||||
It("returns a new index instance", func() {
|
||||
b := engine.NewBleveEngine(idx, bleve.DefaultCreator)
|
||||
b := engine.NewBleveEngine(idx, bleve.DefaultCreator, log.Logger{})
|
||||
Expect(b).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
@@ -486,6 +497,55 @@ var _ = Describe("Bleve", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("StartBatch", func() {
|
||||
It("starts a new batch", func() {
|
||||
err := eng.StartBatch(100)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = eng.Upsert(childResource.ID, childResource)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
count, err := idx.DocCount()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(Equal(uint64(0)))
|
||||
|
||||
err = eng.EndBatch()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
count, err = idx.DocCount()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(Equal(uint64(1)))
|
||||
|
||||
query := bleveSearch.NewMatchQuery("child.pdf")
|
||||
res, err := idx.Search(bleveSearch.NewSearchRequest(query))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(res.Hits.Len()).To(Equal(1))
|
||||
})
|
||||
|
||||
It("doesn't overwrite batches that are already in progress", func() {
|
||||
err := eng.StartBatch(100)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = eng.Upsert(childResource.ID, childResource)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
count, err := idx.DocCount()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(Equal(uint64(0)))
|
||||
|
||||
err = eng.StartBatch(100)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = eng.Upsert(childResource2.ID, childResource2)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(eng.EndBatch()).To(Succeed())
|
||||
count, err = idx.DocCount()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(Equal(uint64(2)))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("File type specific metadata", func() {
|
||||
|
||||
Context("with audio metadata", func() {
|
||||
|
||||
@@ -23,6 +23,9 @@ type Engine interface {
|
||||
Restore(id string) error
|
||||
Purge(id string) error
|
||||
DocCount() (uint64, error)
|
||||
|
||||
StartBatch(batchSize int) error
|
||||
EndBatch() error
|
||||
}
|
||||
|
||||
// Resource is the entity that is stored in the index.
|
||||
|
||||
@@ -143,6 +143,50 @@ func (_c *Engine_DocCount_Call) RunAndReturn(run func() (uint64, error)) *Engine
|
||||
return _c
|
||||
}
|
||||
|
||||
// EndBatch provides a mock function for the type Engine
|
||||
func (_mock *Engine) EndBatch() error {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for EndBatch")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Engine_EndBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EndBatch'
|
||||
type Engine_EndBatch_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// EndBatch is a helper method to define mock.On call
|
||||
func (_e *Engine_Expecter) EndBatch() *Engine_EndBatch_Call {
|
||||
return &Engine_EndBatch_Call{Call: _e.mock.On("EndBatch")}
|
||||
}
|
||||
|
||||
func (_c *Engine_EndBatch_Call) Run(run func()) *Engine_EndBatch_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Engine_EndBatch_Call) Return(err error) *Engine_EndBatch_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Engine_EndBatch_Call) RunAndReturn(run func() error) *Engine_EndBatch_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Move provides a mock function for the type Engine
|
||||
func (_mock *Engine) Move(id string, parentid string, target string) error {
|
||||
ret := _mock.Called(id, parentid, target)
|
||||
@@ -376,6 +420,57 @@ func (_c *Engine_Search_Call) RunAndReturn(run func(ctx context.Context, req *v0
|
||||
return _c
|
||||
}
|
||||
|
||||
// StartBatch provides a mock function for the type Engine
|
||||
func (_mock *Engine) StartBatch(batchSize int) error {
|
||||
ret := _mock.Called(batchSize)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for StartBatch")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(int) error); ok {
|
||||
r0 = returnFunc(batchSize)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Engine_StartBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartBatch'
|
||||
type Engine_StartBatch_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// StartBatch is a helper method to define mock.On call
|
||||
// - batchSize int
|
||||
func (_e *Engine_Expecter) StartBatch(batchSize interface{}) *Engine_StartBatch_Call {
|
||||
return &Engine_StartBatch_Call{Call: _e.mock.On("StartBatch", batchSize)}
|
||||
}
|
||||
|
||||
func (_c *Engine_StartBatch_Call) Run(run func(batchSize int)) *Engine_StartBatch_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 int
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(int)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Engine_StartBatch_Call) Return(err error) *Engine_StartBatch_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Engine_StartBatch_Call) RunAndReturn(run func(batchSize int) error) *Engine_StartBatch_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Upsert provides a mock function for the type Engine
|
||||
func (_mock *Engine) Upsert(id string, r engine.Resource) error {
|
||||
ret := _mock.Called(id, r)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package metrics
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
)
|
||||
|
||||
var (
|
||||
// Namespace defines the namespace for the defines metrics.
|
||||
@@ -8,26 +11,68 @@ var (
|
||||
|
||||
// Subsystem defines the subsystem for the defines metrics.
|
||||
Subsystem = "search"
|
||||
|
||||
buildInfo = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "build_info",
|
||||
Help: "Build information",
|
||||
}, []string{"version"})
|
||||
eventsOutstandingAcks = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "events_outstanding_acks",
|
||||
Help: "Number of outstanding acks for events",
|
||||
})
|
||||
eventsUnprocessed = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "events_unprocessed",
|
||||
Help: "Number of unprocessed events",
|
||||
})
|
||||
eventsRedelivered = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "events_redelivered",
|
||||
Help: "Number of redelivered events",
|
||||
})
|
||||
searchDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "search_duration_seconds",
|
||||
Help: "Duration of search operations in seconds",
|
||||
Buckets: []float64{0.1, 0.5, 1, 2.5, 5, 10, 30, 60},
|
||||
}, []string{"status"})
|
||||
indexDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "index_duration_seconds",
|
||||
Help: "Duration of indexing operations in seconds",
|
||||
Buckets: []float64{0.1, 0.5, 1, 2.5, 5, 10, 30, 60, 120, 300, 600, 1200},
|
||||
}, []string{"status"})
|
||||
)
|
||||
|
||||
// Metrics defines the available metrics of this service.
|
||||
type Metrics struct {
|
||||
// Counter *prometheus.CounterVec
|
||||
BuildInfo *prometheus.GaugeVec
|
||||
BuildInfo *prometheus.GaugeVec
|
||||
EventsOutstandingAcks prometheus.Gauge
|
||||
EventsUnprocessed prometheus.Gauge
|
||||
EventsRedelivered prometheus.Gauge
|
||||
SearchDuration *prometheus.HistogramVec
|
||||
IndexDuration *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
// New initializes the available metrics.
|
||||
func New() *Metrics {
|
||||
m := &Metrics{
|
||||
BuildInfo: prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: Namespace,
|
||||
Subsystem: Subsystem,
|
||||
Name: "build_info",
|
||||
Help: "Build information",
|
||||
}, []string{"version"}),
|
||||
BuildInfo: buildInfo,
|
||||
EventsOutstandingAcks: eventsOutstandingAcks,
|
||||
EventsUnprocessed: eventsUnprocessed,
|
||||
EventsRedelivered: eventsRedelivered,
|
||||
SearchDuration: searchDuration,
|
||||
IndexDuration: indexDuration,
|
||||
}
|
||||
|
||||
_ = prometheus.Register(m.BuildInfo)
|
||||
// TODO: implement metrics
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -5,48 +5,92 @@ import (
|
||||
"time"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
)
|
||||
|
||||
// SpaceDebouncer debounces operations on spaces for a configurable amount of time
|
||||
type SpaceDebouncer struct {
|
||||
after time.Duration
|
||||
timeout time.Duration
|
||||
f func(id *provider.StorageSpaceId)
|
||||
pending map[string]*time.Timer
|
||||
pending map[string]*workItem
|
||||
inProgress sync.Map
|
||||
|
||||
mutex sync.Mutex
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
type workItem struct {
|
||||
t *time.Timer
|
||||
timeout *time.Timer
|
||||
|
||||
work func()
|
||||
}
|
||||
|
||||
type AckFunc func() error
|
||||
|
||||
// NewSpaceDebouncer returns a new SpaceDebouncer instance
|
||||
func NewSpaceDebouncer(d time.Duration, f func(id *provider.StorageSpaceId)) *SpaceDebouncer {
|
||||
func NewSpaceDebouncer(d time.Duration, timeout time.Duration, f func(id *provider.StorageSpaceId), logger log.Logger) *SpaceDebouncer {
|
||||
return &SpaceDebouncer{
|
||||
after: d,
|
||||
timeout: timeout,
|
||||
f: f,
|
||||
pending: map[string]*time.Timer{},
|
||||
pending: map[string]*workItem{},
|
||||
inProgress: sync.Map{},
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Debounce restars the debounce timer for the given space
|
||||
func (d *SpaceDebouncer) Debounce(id *provider.StorageSpaceId) {
|
||||
func (d *SpaceDebouncer) Debounce(id *provider.StorageSpaceId, ack AckFunc) {
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
if t := d.pending[id.OpaqueId]; t != nil {
|
||||
t.Stop()
|
||||
if wi := d.pending[id.OpaqueId]; wi != nil {
|
||||
if ack != nil {
|
||||
go ack() // Acknowledge the event immediately, the according space is already scheduled for indexing
|
||||
}
|
||||
wi.t.Reset(d.after)
|
||||
return
|
||||
}
|
||||
|
||||
d.pending[id.OpaqueId] = time.AfterFunc(d.after, func() {
|
||||
wi := &workItem{}
|
||||
wi.work = func() {
|
||||
if _, ok := d.inProgress.Load(id.OpaqueId); ok {
|
||||
// Reschedule this run for when the previous run has finished
|
||||
d.mutex.Lock()
|
||||
d.pending[id.OpaqueId].Reset(d.after)
|
||||
if wi := d.pending[id.OpaqueId]; wi != nil {
|
||||
wi.t.Reset(d.after)
|
||||
}
|
||||
d.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
d.mutex.Lock()
|
||||
wi.timeout.Stop() // stop the timeout timer if it is running
|
||||
delete(d.pending, id.OpaqueId)
|
||||
d.inProgress.Store(id.OpaqueId, true)
|
||||
defer d.inProgress.Delete(id.OpaqueId)
|
||||
defer func() {
|
||||
d.inProgress.Delete(id.OpaqueId)
|
||||
}()
|
||||
d.mutex.Unlock() // release the lock early to allow other goroutines to debounce
|
||||
|
||||
d.f(id)
|
||||
go func() {
|
||||
if ack != nil {
|
||||
if err := ack(); err != nil {
|
||||
d.log.Error().Err(err).Msg("error while acknowledging event")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wi.t = time.AfterFunc(d.after, wi.work)
|
||||
wi.timeout = time.AfterFunc(d.timeout, func() {
|
||||
d.log.Debug().Msg("timeout while waiting for space debouncer to finish")
|
||||
wi.t.Stop()
|
||||
wi.work()
|
||||
})
|
||||
|
||||
d.pending[id.OpaqueId] = wi
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
|
||||
)
|
||||
|
||||
@@ -23,30 +24,30 @@ var _ = Describe("SpaceDebouncer", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
callCount = atomic.Int32{}
|
||||
debouncer = search.NewSpaceDebouncer(50*time.Millisecond, func(id *sprovider.StorageSpaceId) {
|
||||
debouncer = search.NewSpaceDebouncer(50*time.Millisecond, 10*time.Second, func(id *sprovider.StorageSpaceId) {
|
||||
if id.OpaqueId == "spaceid" {
|
||||
callCount.Add(1)
|
||||
}
|
||||
})
|
||||
}, log.NewLogger())
|
||||
})
|
||||
|
||||
It("debounces", func() {
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
Eventually(func() int {
|
||||
return int(callCount.Load())
|
||||
}, "200ms").Should(Equal(1))
|
||||
})
|
||||
|
||||
It("works multiple times", func() {
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
|
||||
Eventually(func() int {
|
||||
return int(callCount.Load())
|
||||
@@ -54,21 +55,135 @@ var _ = Describe("SpaceDebouncer", func() {
|
||||
})
|
||||
|
||||
It("doesn't trigger twice simultaneously", func() {
|
||||
debouncer = search.NewSpaceDebouncer(50*time.Millisecond, func(id *sprovider.StorageSpaceId) {
|
||||
debouncer = search.NewSpaceDebouncer(50*time.Millisecond, 5*time.Second, func(id *sprovider.StorageSpaceId) {
|
||||
if id.OpaqueId == "spaceid" {
|
||||
callCount.Add(1)
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
})
|
||||
debouncer.Debounce(spaceid)
|
||||
}, log.NewLogger())
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
time.Sleep(100 * time.Millisecond) // Let it trigger once
|
||||
|
||||
debouncer.Debounce(spaceid)
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
time.Sleep(100 * time.Millisecond) // shouldn't trigger as the other run is still in progress
|
||||
Expect(int(callCount.Load())).To(Equal(1))
|
||||
|
||||
Eventually(func() int {
|
||||
return int(callCount.Load())
|
||||
}, "500ms").Should(Equal(2))
|
||||
}, "2000ms").Should(Equal(2))
|
||||
})
|
||||
|
||||
It("fires at the timeout even when continuously debounced", func() {
|
||||
debouncer = search.NewSpaceDebouncer(100*time.Millisecond, 250*time.Millisecond, func(id *sprovider.StorageSpaceId) {
|
||||
if id.OpaqueId == "spaceid" {
|
||||
callCount.Add(1)
|
||||
}
|
||||
}, log.NewLogger())
|
||||
|
||||
// Initial call to start the timers
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
|
||||
// Continuously reset the debounce timer using a ticker, at an interval
|
||||
// shorter than the debounce time.
|
||||
ticker := time.NewTicker(50 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// The debounce timer (100ms) should be reset every 50ms and thus never fire.
|
||||
// The timeout timer (250ms) should fire regardless.
|
||||
Eventually(func() int {
|
||||
return int(callCount.Load())
|
||||
}, "300ms").Should(Equal(1))
|
||||
|
||||
// Stop the ticker goroutine
|
||||
close(done)
|
||||
|
||||
// And it should not fire again
|
||||
Consistently(func() int {
|
||||
return int(callCount.Load())
|
||||
}, "300ms").Should(Equal(1))
|
||||
})
|
||||
|
||||
It("doesn't run the timeout function if the work function has been called", func() {
|
||||
debouncer = search.NewSpaceDebouncer(100*time.Millisecond, 250*time.Millisecond, func(id *sprovider.StorageSpaceId) {
|
||||
if id.OpaqueId == "spaceid" {
|
||||
callCount.Add(1)
|
||||
}
|
||||
}, log.NewLogger())
|
||||
|
||||
// Initial call to start the timers
|
||||
debouncer.Debounce(spaceid, nil)
|
||||
|
||||
// Wait for the debounce timer to fire
|
||||
Eventually(func() int {
|
||||
return int(callCount.Load())
|
||||
}, "200ms").Should(Equal(1))
|
||||
|
||||
// The timeout function should not be called
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
Expect(int(callCount.Load())).To(Equal(1))
|
||||
})
|
||||
|
||||
It("calls the ack function when the debounce fires", func() {
|
||||
var ackCalled atomic.Bool
|
||||
ackFunc := func() error {
|
||||
ackCalled.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
debouncer.Debounce(spaceid, ackFunc)
|
||||
|
||||
Eventually(func() int {
|
||||
return int(callCount.Load())
|
||||
}, "200ms").Should(Equal(1))
|
||||
Eventually(func() bool {
|
||||
return ackCalled.Load()
|
||||
}, "200ms").Should(BeTrue())
|
||||
})
|
||||
|
||||
It("calls the ack function immediately for subsequent calls", func() {
|
||||
var firstAckCalled atomic.Bool
|
||||
firstAckFunc := func() error {
|
||||
firstAckCalled.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
var secondAckCalled atomic.Bool
|
||||
secondAckFunc := func() error {
|
||||
secondAckCalled.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// First call, sets up the trigger
|
||||
debouncer.Debounce(spaceid, firstAckFunc)
|
||||
Expect(firstAckCalled.Load()).To(BeFalse())
|
||||
Expect(secondAckCalled.Load()).To(BeFalse())
|
||||
|
||||
// Second call, should call its ack immediately
|
||||
debouncer.Debounce(spaceid, secondAckFunc)
|
||||
Eventually(func() bool {
|
||||
return secondAckCalled.Load()
|
||||
}, "50ms").Should(BeTrue())
|
||||
// The first ack is not yet called.
|
||||
Expect(firstAckCalled.Load()).To(BeFalse())
|
||||
|
||||
// After the debounce period, the trigger fires, calling the main function and the first ack.
|
||||
Eventually(func() int {
|
||||
return int(callCount.Load())
|
||||
}, "200ms").Should(Equal(1))
|
||||
Eventually(func() bool {
|
||||
return firstAckCalled.Load()
|
||||
}, "200ms").Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/metrics"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/raw"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
)
|
||||
|
||||
// HandleEvents listens to the needed events,
|
||||
// it handles the whole resource indexing livecycle.
|
||||
func HandleEvents(s Searcher, bus events.Consumer, logger log.Logger, cfg *config.Config) error {
|
||||
func HandleEvents(s Searcher, stream raw.Stream, cfg *config.Config, m *metrics.Metrics, logger log.Logger) error {
|
||||
evts := []events.Unmarshaller{
|
||||
events.ItemTrashed{},
|
||||
events.ItemRestored{},
|
||||
@@ -31,11 +34,15 @@ func HandleEvents(s Searcher, bus events.Consumer, logger log.Logger, cfg *confi
|
||||
evts = append(evts, events.FileUploaded{})
|
||||
}
|
||||
|
||||
ch, err := events.Consume(bus, "search", evts...)
|
||||
ch, err := stream.Consume("search-pull", evts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m != nil {
|
||||
monitorMetrics(stream, "search-pull", m, logger)
|
||||
}
|
||||
|
||||
if cfg.Events.NumConsumers == 0 {
|
||||
cfg.Events.NumConsumers = 1
|
||||
}
|
||||
@@ -51,45 +58,46 @@ func HandleEvents(s Searcher, bus events.Consumer, logger log.Logger, cfg *confi
|
||||
}
|
||||
}
|
||||
|
||||
indexSpaceDebouncer := NewSpaceDebouncer(time.Duration(cfg.Events.DebounceDuration)*time.Millisecond, func(id *provider.StorageSpaceId) {
|
||||
indexSpaceDebouncer := NewSpaceDebouncer(time.Duration(cfg.Events.DebounceDuration)*time.Millisecond, 30*time.Second, func(id *provider.StorageSpaceId) {
|
||||
if err := s.IndexSpace(id); err != nil {
|
||||
logger.Error().Err(err).Interface("spaceID", id).Msg("error while indexing a space")
|
||||
}
|
||||
})
|
||||
}, logger)
|
||||
|
||||
for i := 0; i < cfg.Events.NumConsumers; i++ {
|
||||
go func(s Searcher, ch <-chan events.Event) {
|
||||
go func(s Searcher, ch <-chan raw.Event) {
|
||||
for event := range ch {
|
||||
e := event
|
||||
go func() {
|
||||
e.InProgress() // let nats know that we are processing this event
|
||||
logger.Debug().Interface("event", e).Msg("updating index")
|
||||
|
||||
switch ev := e.Event.(type) {
|
||||
switch ev := e.Event.Event.(type) {
|
||||
case events.ItemTrashed:
|
||||
s.TrashItem(ev.ID)
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref))
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), e.Ack)
|
||||
case events.ItemMoved:
|
||||
s.MoveItem(ev.Ref)
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref))
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), e.Ack)
|
||||
case events.ItemRestored:
|
||||
s.RestoreItem(ev.Ref)
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref))
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), e.Ack)
|
||||
case events.ContainerCreated:
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref))
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), e.Ack)
|
||||
case events.FileTouched:
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref))
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), e.Ack)
|
||||
case events.FileVersionRestored:
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref))
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), e.Ack)
|
||||
case events.TagsAdded:
|
||||
s.UpsertItem(ev.Ref)
|
||||
case events.TagsRemoved:
|
||||
s.UpsertItem(ev.Ref)
|
||||
case events.FileUploaded:
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref))
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.Ref), e.Ack)
|
||||
case events.UploadReady:
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.FileRef))
|
||||
indexSpaceDebouncer.Debounce(getSpaceID(ev.FileRef), e.Ack)
|
||||
case events.SpaceRenamed:
|
||||
indexSpaceDebouncer.Debounce(ev.ID)
|
||||
indexSpaceDebouncer.Debounce(ev.ID, e.Ack)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -101,3 +109,26 @@ func HandleEvents(s Searcher, bus events.Consumer, logger log.Logger, cfg *confi
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func monitorMetrics(stream raw.Stream, name string, m *metrics.Metrics, logger log.Logger) {
|
||||
ctx := context.Background()
|
||||
consumer, err := stream.JetStream().Consumer(ctx, name)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to get consumer")
|
||||
}
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
info, err := consumer.Info(ctx)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("failed to get consumer")
|
||||
continue
|
||||
}
|
||||
|
||||
m.EventsOutstandingAcks.Set(float64(info.NumAckPending))
|
||||
m.EventsUnprocessed.Set(float64(info.NumPending))
|
||||
m.EventsRedelivered.Set(float64(info.NumRedelivered))
|
||||
logger.Trace().Msg("updated search event metrics")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package search_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
@@ -12,24 +11,27 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/search"
|
||||
searchMocks "github.com/opencloud-eu/opencloud/services/search/pkg/search/mocks"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/raw"
|
||||
rawMocks "github.com/opencloud-eu/reva/v2/pkg/events/raw/mocks"
|
||||
"github.com/stretchr/testify/mock"
|
||||
mEvents "go-micro.dev/v4/events"
|
||||
)
|
||||
|
||||
var _ = DescribeTable("events",
|
||||
func(mcks []string, e interface{}, asyncUploads bool) {
|
||||
func(mcks []string, e any, asyncUploads bool) {
|
||||
var (
|
||||
s = &searchMocks.Searcher{}
|
||||
calls atomic.Int32
|
||||
)
|
||||
|
||||
bus, _ := mEvents.NewStream()
|
||||
stream := rawMocks.NewStream(GinkgoT())
|
||||
ch := make(chan raw.Event, 1)
|
||||
stream.EXPECT().Consume(mock.Anything, mock.Anything).Return((<-chan raw.Event)(ch), nil)
|
||||
|
||||
search.HandleEvents(s, bus, log.NewLogger(), &config.Config{
|
||||
search.HandleEvents(s, stream, &config.Config{
|
||||
Events: config.Events{
|
||||
AsyncUploads: asyncUploads,
|
||||
},
|
||||
})
|
||||
}, nil, log.NewLogger())
|
||||
|
||||
for _, mck := range mcks {
|
||||
s.On(mck, mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
|
||||
@@ -37,9 +39,10 @@ var _ = DescribeTable("events",
|
||||
})
|
||||
}
|
||||
|
||||
err := events.Publish(context.Background(), bus, e)
|
||||
ch <- raw.Event{
|
||||
Event: events.Event{Event: e},
|
||||
}
|
||||
|
||||
Expect(err).To(BeNil())
|
||||
Eventually(func() int {
|
||||
return int(calls.Load())
|
||||
}, "2s").Should(Equal(len(mcks)))
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
@@ -21,7 +22,6 @@ import (
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/walker"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/content"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/engine"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -59,23 +60,29 @@ type Service struct {
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
engine engine.Engine
|
||||
extractor content.Extractor
|
||||
metrics *metrics.Metrics
|
||||
|
||||
serviceAccountID string
|
||||
serviceAccountSecret string
|
||||
|
||||
batchSize int
|
||||
}
|
||||
|
||||
var errSkipSpace error
|
||||
|
||||
// NewService creates a new Provider instance.
|
||||
func NewService(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], eng engine.Engine, extractor content.Extractor, logger log.Logger, cfg *config.Config) *Service {
|
||||
func NewService(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], eng engine.Engine, extractor content.Extractor, metrics *metrics.Metrics, logger log.Logger, cfg *config.Config) *Service {
|
||||
var s = &Service{
|
||||
gatewaySelector: gatewaySelector,
|
||||
engine: eng,
|
||||
logger: logger,
|
||||
extractor: extractor,
|
||||
metrics: metrics,
|
||||
|
||||
serviceAccountID: cfg.ServiceAccount.ServiceAccountID,
|
||||
serviceAccountSecret: cfg.ServiceAccount.ServiceAccountSecret,
|
||||
|
||||
batchSize: cfg.BatchSize,
|
||||
}
|
||||
|
||||
return s
|
||||
@@ -85,6 +92,21 @@ func NewService(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], eng e
|
||||
func (s *Service) Search(ctx context.Context, req *searchsvc.SearchRequest) (*searchsvc.SearchResponse, error) {
|
||||
s.logger.Debug().Str("query", req.Query).Msg("performing a search")
|
||||
|
||||
// collect metrics
|
||||
startTime := time.Now()
|
||||
success := false
|
||||
defer func() {
|
||||
if s.metrics == nil {
|
||||
return
|
||||
}
|
||||
|
||||
status := "success"
|
||||
if !success {
|
||||
status = "error"
|
||||
}
|
||||
s.metrics.SearchDuration.WithLabelValues(status).Observe(time.Since(startTime).Seconds())
|
||||
}()
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -255,6 +277,7 @@ func (s *Service) Search(ctx context.Context, req *searchsvc.SearchRequest) (*se
|
||||
matches = matches[0:limit]
|
||||
}
|
||||
|
||||
success = true
|
||||
return &searchsvc.SearchResponse{
|
||||
Matches: matches,
|
||||
TotalMatches: total,
|
||||
@@ -425,7 +448,27 @@ func (s *Service) IndexSpace(spaceID *provider.StorageSpaceId) error {
|
||||
}
|
||||
rootID.OpaqueId = rootID.SpaceId
|
||||
|
||||
// Collect metrics
|
||||
startTime := time.Now()
|
||||
success := false
|
||||
defer func() {
|
||||
if s.metrics == nil {
|
||||
return
|
||||
}
|
||||
status := "success"
|
||||
if !success {
|
||||
status = "error"
|
||||
}
|
||||
s.metrics.IndexDuration.WithLabelValues(status).Observe(time.Since(startTime).Seconds())
|
||||
}()
|
||||
|
||||
w := walker.NewWalker(s.gatewaySelector)
|
||||
s.engine.StartBatch(s.batchSize)
|
||||
defer func() {
|
||||
if err := s.engine.EndBatch(); err != nil {
|
||||
s.logger.Error().Err(err).Msg("failed to end batch")
|
||||
}
|
||||
}()
|
||||
err = w.Walk(ownerCtx, &rootID, func(wd string, info *provider.ResourceInfo, err error) error {
|
||||
if err != nil {
|
||||
s.logger.Error().Err(err).Msg("error walking the tree")
|
||||
@@ -465,6 +508,7 @@ func (s *Service) IndexSpace(spaceID *provider.StorageSpaceId) error {
|
||||
}
|
||||
|
||||
logDocCount(s.engine, s.logger)
|
||||
success = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ var _ = Describe("Searchprovider", func() {
|
||||
indexClient = &engineMocks.Engine{}
|
||||
extractor = &contentMocks.Extractor{}
|
||||
|
||||
s = search.NewService(gatewaySelector, indexClient, extractor, logger, &config.Config{})
|
||||
s = search.NewService(gatewaySelector, indexClient, extractor, nil, logger, &config.Config{})
|
||||
|
||||
gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
|
||||
Status: status.NewOK(ctx),
|
||||
@@ -110,7 +110,7 @@ var _ = Describe("Searchprovider", func() {
|
||||
|
||||
Describe("New", func() {
|
||||
It("returns a new instance", func() {
|
||||
s := search.NewService(gatewaySelector, indexClient, extractor, logger, &config.Config{})
|
||||
s := search.NewService(gatewaySelector, indexClient, extractor, nil, logger, &config.Config{})
|
||||
Expect(s).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
@@ -122,6 +122,8 @@ var _ = Describe("Searchprovider", func() {
|
||||
User: user,
|
||||
}, nil)
|
||||
extractor.On("Extract", mock.Anything, mock.Anything, mock.Anything).Return(content.Document{}, nil)
|
||||
indexClient.On("StartBatch", mock.Anything, mock.Anything).Return(nil)
|
||||
indexClient.On("EndBatch", mock.Anything, mock.Anything).Return(nil)
|
||||
indexClient.On("Upsert", mock.Anything, mock.Anything).Return(nil)
|
||||
indexClient.On("Search", mock.Anything, mock.Anything).Return(&searchsvc.SearchIndexResponse{}, nil)
|
||||
gatewayClient.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{
|
||||
|
||||
@@ -3,6 +3,7 @@ package debug
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/checks"
|
||||
"github.com/opencloud-eu/opencloud/pkg/handlers"
|
||||
@@ -22,7 +23,11 @@ func Server(opts ...Option) (*http.Server, error) {
|
||||
WithCheck("nats reachability", checks.NewNatsCheck(options.Config.Events.Endpoint)).
|
||||
WithCheck("tika-check", func(ctx context.Context) error {
|
||||
if options.Config.Extractor.Type == "tika" {
|
||||
return checks.NewTCPCheck(options.Config.Extractor.Tika.TikaURL)(ctx)
|
||||
u, err := url.Parse(options.Config.Extractor.Tika.TikaURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return checks.NewTCPCheck(u.Host)(ctx)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -36,6 +36,7 @@ func Server(opts ...Option) (grpc.Service, func(), error) {
|
||||
svc.Logger(options.Logger),
|
||||
svc.JWTSecret(options.JWTSecret),
|
||||
svc.TracerProvider(options.TraceProvider),
|
||||
svc.Metrics(options.Metrics),
|
||||
)
|
||||
if err != nil {
|
||||
options.Logger.Error().
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/search/pkg/metrics"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
@@ -15,6 +16,7 @@ type Options struct {
|
||||
Config *config.Config
|
||||
JWTSecret string
|
||||
TracerProvider trace.TracerProvider
|
||||
Metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
func newOptions(opts ...Option) Options {
|
||||
@@ -54,3 +56,12 @@ func TracerProvider(val trace.TracerProvider) Option {
|
||||
o.TracerProvider = val
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics provides a function to set the Metrics option.
|
||||
func Metrics(val *metrics.Metrics) Option {
|
||||
return func(o *Options) {
|
||||
if val != nil {
|
||||
o.Metrics = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/jellydator/ttlcache/v2"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/stream"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events/raw"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/token"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/token/manager/jwt"
|
||||
@@ -53,7 +53,7 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, func(), error)
|
||||
_ = idx.Close()
|
||||
}
|
||||
|
||||
eng = engine.NewBleveEngine(idx, bleve.DefaultCreator)
|
||||
eng = engine.NewBleveEngine(idx, bleve.DefaultCreator, logger)
|
||||
default:
|
||||
return nil, teardown, fmt.Errorf("unknown search engine: %s", cfg.Engine.Type)
|
||||
}
|
||||
@@ -79,7 +79,11 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, func(), error)
|
||||
return nil, teardown, fmt.Errorf("unknown search extractor: %s", cfg.Extractor.Type)
|
||||
}
|
||||
|
||||
bus, err := stream.NatsFromConfig(cfg.Service.Name, false, stream.NatsConfig{
|
||||
ss := search.NewService(selector, eng, extractor, options.Metrics, logger, cfg)
|
||||
|
||||
// setup event handling
|
||||
|
||||
stream, err := raw.FromConfig(context.Background(), cfg.Service.Name, raw.Config{
|
||||
Endpoint: cfg.Events.Endpoint,
|
||||
Cluster: cfg.Events.Cluster,
|
||||
EnableTLS: cfg.Events.EnableTLS,
|
||||
@@ -87,15 +91,14 @@ func NewHandler(opts ...Option) (searchsvc.SearchProviderHandler, func(), error)
|
||||
TLSRootCACertificate: cfg.Events.TLSRootCACertificate,
|
||||
AuthUsername: cfg.Events.AuthUsername,
|
||||
AuthPassword: cfg.Events.AuthPassword,
|
||||
MaxAckPending: cfg.Events.MaxAckPending,
|
||||
AckWait: cfg.Events.AckWait,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, teardown, err
|
||||
}
|
||||
|
||||
ss := search.NewService(selector, eng, extractor, logger, cfg)
|
||||
|
||||
// setup event handling
|
||||
if err := search.HandleEvents(ss, bus, logger, cfg); err != nil {
|
||||
if err := search.HandleEvents(ss, stream, cfg, options.Metrics, logger); err != nil {
|
||||
return nil, teardown, err
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Ivan Fustero, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Ivan Fustero, 2025\n"
|
||||
"Language-Team: Catalan (https://app.transifex.com/opencloud-eu/teams/204053/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ca\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. name of the notification option 'Space Shared'
|
||||
#: pkg/store/defaults/templates.go:20
|
||||
msgid "Added as space member"
|
||||
msgstr "S'ha afegit com a membre de l'espai"
|
||||
|
||||
#. translation for the 'daily' email interval option
|
||||
#: pkg/store/defaults/templates.go:50
|
||||
msgid "Daily"
|
||||
msgstr "Diàriament"
|
||||
|
||||
#. name of the notification option 'Email Interval'
|
||||
#: pkg/store/defaults/templates.go:44
|
||||
msgid "Email sending interval"
|
||||
msgstr "Interval d'enviament de correu"
|
||||
|
||||
#. name of the notification option 'File Rejected'
|
||||
#: pkg/store/defaults/templates.go:40
|
||||
msgid "File rejected"
|
||||
msgstr "S'ha rebutjat el fitxer"
|
||||
|
||||
#. translation for the 'instant' email interval option
|
||||
#: pkg/store/defaults/templates.go:48
|
||||
msgid "Instant"
|
||||
msgstr "Immediat"
|
||||
|
||||
#. translation for the 'never' email interval option
|
||||
#: pkg/store/defaults/templates.go:54
|
||||
msgid "Never"
|
||||
msgstr "Mai"
|
||||
|
||||
#. description of the notification option 'Space Shared'
|
||||
#: pkg/store/defaults/templates.go:22
|
||||
msgid "Notify when I have been added as a member to a space"
|
||||
msgstr "Notifica'm quan m'hagin afegit com a membre a un espai"
|
||||
|
||||
#. description of the notification option 'Space Unshared'
|
||||
#: pkg/store/defaults/templates.go:26
|
||||
msgid "Notify when I have been removed as member from a space"
|
||||
msgstr "Notifica'm quan m'hagin tret com a membre d'un espai"
|
||||
|
||||
#. description of the notification option 'Share Received'
|
||||
#: pkg/store/defaults/templates.go:10
|
||||
msgid "Notify when I have received a share"
|
||||
msgstr "Notifica quan rebi una compartició"
|
||||
|
||||
#. description of the notification option 'File Rejected'
|
||||
#: pkg/store/defaults/templates.go:42
|
||||
msgid ""
|
||||
"Notify when a file I uploaded was rejected because of a virus infection or "
|
||||
"policy violation"
|
||||
msgstr ""
|
||||
"Notifica'm quan un fitxer que he pujat ha estat rebutjat per una infecció "
|
||||
"per virus o una violació de la política"
|
||||
|
||||
#. description of the notification option 'Share Removed'
|
||||
#: pkg/store/defaults/templates.go:14
|
||||
msgid "Notify when a received share has been removed"
|
||||
msgstr "Notifica'm quan s'hagi eliminat una compartició rebuda"
|
||||
|
||||
#. description of the notification option 'Share Expired'
|
||||
#: pkg/store/defaults/templates.go:18
|
||||
msgid "Notify when a received share has expired"
|
||||
msgstr "Notifica'm quan hagi caducat una compartició rebuda"
|
||||
|
||||
#. description of the notification option 'Space Deleted'
|
||||
#: pkg/store/defaults/templates.go:38
|
||||
msgid "Notify when a space I am member of has been deleted"
|
||||
msgstr "Notifica'm quan s'hagi suprimit un espai del qual soc membre"
|
||||
|
||||
#. description of the notification option 'Space Disabled'
|
||||
#: pkg/store/defaults/templates.go:34
|
||||
msgid "Notify when a space I am member of has been disabled"
|
||||
msgstr "Notifica'm quan s'hagi desactivat un espai del qual soc membre"
|
||||
|
||||
#. description of the notification option 'Space Membership Expired'
|
||||
#: pkg/store/defaults/templates.go:30
|
||||
msgid "Notify when a space membership has expired"
|
||||
msgstr "Notifica'm quan hagi caducat la pertinença a un espai"
|
||||
|
||||
#. name of the notification option 'Space Unshared'
|
||||
#: pkg/store/defaults/templates.go:24
|
||||
msgid "Removed as space member"
|
||||
msgstr "Eliminat com a membre de l'espai"
|
||||
|
||||
#. description of the notification option 'Email Interval'
|
||||
#: pkg/store/defaults/templates.go:46
|
||||
msgid "Selected value:"
|
||||
msgstr "Valor seleccionat:"
|
||||
|
||||
#. name of the notification option 'Share Expired'
|
||||
#: pkg/store/defaults/templates.go:16
|
||||
msgid "Share Expired"
|
||||
msgstr "Compartició caducada"
|
||||
|
||||
#. name of the notification option 'Share Received'
|
||||
#: pkg/store/defaults/templates.go:8
|
||||
msgid "Share Received"
|
||||
msgstr "Compartició rebuda"
|
||||
|
||||
#. name of the notification option 'Share Removed'
|
||||
#: pkg/store/defaults/templates.go:12
|
||||
msgid "Share Removed"
|
||||
msgstr "Compartició eliminada"
|
||||
|
||||
#. name of the notification option 'Space Deleted'
|
||||
#: pkg/store/defaults/templates.go:36
|
||||
msgid "Space deleted"
|
||||
msgstr "Espai eliminat"
|
||||
|
||||
#. name of the notification option 'Space Disabled'
|
||||
#: pkg/store/defaults/templates.go:32
|
||||
msgid "Space disabled"
|
||||
msgstr "Espai desactivat"
|
||||
|
||||
#. name of the notification option 'Space Membership Expired'
|
||||
#: pkg/store/defaults/templates.go:28
|
||||
msgid "Space membership expired"
|
||||
msgstr "La pertinença a l'espai ha expirat"
|
||||
|
||||
#. translation for the 'weekly' email interval option
|
||||
#: pkg/store/defaults/templates.go:52
|
||||
msgid "Weekly"
|
||||
msgstr "Setmanalment"
|
||||
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-01-27 11:01+0100\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Jörn Friedrich Dreyer <jfd@butonic.de>, 2025\n"
|
||||
"Language-Team: German (https://app.transifex.com/opencloud-eu/teams/204053/de/)\n"
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# Elías Martín, 2025
|
||||
# Alejandro Robles, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Alejandro Robles, 2025\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#. name of the notification option 'Space Shared'
|
||||
#: pkg/store/defaults/templates.go:20
|
||||
msgid "Added as space member"
|
||||
msgstr "Incluido como miembro del espacio"
|
||||
|
||||
#. translation for the 'daily' email interval option
|
||||
#: pkg/store/defaults/templates.go:50
|
||||
msgid "Daily"
|
||||
msgstr "Diario"
|
||||
|
||||
#. name of the notification option 'Email Interval'
|
||||
#: pkg/store/defaults/templates.go:44
|
||||
msgid "Email sending interval"
|
||||
msgstr "Intervalo de envío de correos"
|
||||
|
||||
#. name of the notification option 'File Rejected'
|
||||
#: pkg/store/defaults/templates.go:40
|
||||
msgid "File rejected"
|
||||
msgstr "Archivo rechazado"
|
||||
|
||||
#. translation for the 'instant' email interval option
|
||||
#: pkg/store/defaults/templates.go:48
|
||||
msgid "Instant"
|
||||
msgstr "Instantaneo"
|
||||
|
||||
#. translation for the 'never' email interval option
|
||||
#: pkg/store/defaults/templates.go:54
|
||||
msgid "Never"
|
||||
msgstr "Nunca"
|
||||
|
||||
#. description of the notification option 'Space Shared'
|
||||
#: pkg/store/defaults/templates.go:22
|
||||
msgid "Notify when I have been added as a member to a space"
|
||||
msgstr "Notificar cuando me han agregado como miembro a un espacio"
|
||||
|
||||
#. description of the notification option 'Space Unshared'
|
||||
#: pkg/store/defaults/templates.go:26
|
||||
msgid "Notify when I have been removed as member from a space"
|
||||
msgstr "Notificar cuando he sido eliminado como miembro de un espacio"
|
||||
|
||||
#. description of the notification option 'Share Received'
|
||||
#: pkg/store/defaults/templates.go:10
|
||||
msgid "Notify when I have received a share"
|
||||
msgstr "Notificarme cuando he recibido un compartido"
|
||||
|
||||
#. description of the notification option 'File Rejected'
|
||||
#: pkg/store/defaults/templates.go:42
|
||||
msgid ""
|
||||
"Notify when a file I uploaded was rejected because of a virus infection or "
|
||||
"policy violation"
|
||||
msgstr ""
|
||||
"Notificar cuando un archivo que he subido ha sido rechazado por contener "
|
||||
"virus o violar alguna política"
|
||||
|
||||
#. description of the notification option 'Share Removed'
|
||||
#: pkg/store/defaults/templates.go:14
|
||||
msgid "Notify when a received share has been removed"
|
||||
msgstr "Notificar cuando un compartido que he recibido ha sido eliminado"
|
||||
|
||||
#. description of the notification option 'Share Expired'
|
||||
#: pkg/store/defaults/templates.go:18
|
||||
msgid "Notify when a received share has expired"
|
||||
msgstr "Notificar cuando un compartido recibido ha expirado."
|
||||
|
||||
#. description of the notification option 'Space Deleted'
|
||||
#: pkg/store/defaults/templates.go:38
|
||||
msgid "Notify when a space I am member of has been deleted"
|
||||
msgstr "Notificar cuando un espacio del que soy miembro ha sido eliminado"
|
||||
|
||||
#. description of the notification option 'Space Disabled'
|
||||
#: pkg/store/defaults/templates.go:34
|
||||
msgid "Notify when a space I am member of has been disabled"
|
||||
msgstr "Notificar cuando un espacio del que soy miembro ha sido deshabilitado"
|
||||
|
||||
#. description of the notification option 'Space Membership Expired'
|
||||
#: pkg/store/defaults/templates.go:30
|
||||
msgid "Notify when a space membership has expired"
|
||||
msgstr "Notificar cuando la membresía a un espacio ha caducado"
|
||||
|
||||
#. name of the notification option 'Space Unshared'
|
||||
#: pkg/store/defaults/templates.go:24
|
||||
msgid "Removed as space member"
|
||||
msgstr "Eliminado como miembro del espacio"
|
||||
|
||||
#. description of the notification option 'Email Interval'
|
||||
#: pkg/store/defaults/templates.go:46
|
||||
msgid "Selected value:"
|
||||
msgstr "Valor seleccionado:"
|
||||
|
||||
#. name of the notification option 'Share Expired'
|
||||
#: pkg/store/defaults/templates.go:16
|
||||
msgid "Share Expired"
|
||||
msgstr "Compartido expirado"
|
||||
|
||||
#. name of the notification option 'Share Received'
|
||||
#: pkg/store/defaults/templates.go:8
|
||||
msgid "Share Received"
|
||||
msgstr "Compartido recibido"
|
||||
|
||||
#. name of the notification option 'Share Removed'
|
||||
#: pkg/store/defaults/templates.go:12
|
||||
msgid "Share Removed"
|
||||
msgstr "Compartido eliminado"
|
||||
|
||||
#. name of the notification option 'Space Deleted'
|
||||
#: pkg/store/defaults/templates.go:36
|
||||
msgid "Space deleted"
|
||||
msgstr "Espacio eliminado"
|
||||
|
||||
#. name of the notification option 'Space Disabled'
|
||||
#: pkg/store/defaults/templates.go:32
|
||||
msgid "Space disabled"
|
||||
msgstr "Espacio inhabilitado"
|
||||
|
||||
#. name of the notification option 'Space Membership Expired'
|
||||
#: pkg/store/defaults/templates.go:28
|
||||
msgid "Space membership expired"
|
||||
msgstr "Membresia al espacio expirada"
|
||||
|
||||
#. translation for the 'weekly' email interval option
|
||||
#: pkg/store/defaults/templates.go:52
|
||||
msgid "Weekly"
|
||||
msgstr "Semanal"
|
||||
@@ -0,0 +1,148 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# eric_G <junk.eg@free.fr>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-07-30 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: eric_G <junk.eg@free.fr>, 2025\n"
|
||||
"Language-Team: French (https://app.transifex.com/opencloud-eu/teams/204053/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#. name of the notification option 'Space Shared'
|
||||
#: pkg/store/defaults/templates.go:20
|
||||
msgid "Added as space member"
|
||||
msgstr "Ajouté comme membre de l'Espace"
|
||||
|
||||
#. translation for the 'daily' email interval option
|
||||
#: pkg/store/defaults/templates.go:50
|
||||
msgid "Daily"
|
||||
msgstr "Quotidiennement"
|
||||
|
||||
#. name of the notification option 'Email Interval'
|
||||
#: pkg/store/defaults/templates.go:44
|
||||
msgid "Email sending interval"
|
||||
msgstr "Intervalle d'envoi des e-mails"
|
||||
|
||||
#. name of the notification option 'File Rejected'
|
||||
#: pkg/store/defaults/templates.go:40
|
||||
msgid "File rejected"
|
||||
msgstr "Fichier rejeté"
|
||||
|
||||
#. translation for the 'instant' email interval option
|
||||
#: pkg/store/defaults/templates.go:48
|
||||
msgid "Instant"
|
||||
msgstr "Instantané"
|
||||
|
||||
#. translation for the 'never' email interval option
|
||||
#: pkg/store/defaults/templates.go:54
|
||||
msgid "Never"
|
||||
msgstr "Jamais"
|
||||
|
||||
#. description of the notification option 'Space Shared'
|
||||
#: pkg/store/defaults/templates.go:22
|
||||
msgid "Notify when I have been added as a member to a space"
|
||||
msgstr ""
|
||||
"Recevoir une notification lorsque j'ai été ajouté en tant que membre à un "
|
||||
"espace"
|
||||
|
||||
#. description of the notification option 'Space Unshared'
|
||||
#: pkg/store/defaults/templates.go:26
|
||||
msgid "Notify when I have been removed as member from a space"
|
||||
msgstr "M'avertir lorsque j'ai été retiré d'un Espace en tant que membre"
|
||||
|
||||
#. description of the notification option 'Share Received'
|
||||
#: pkg/store/defaults/templates.go:10
|
||||
msgid "Notify when I have received a share"
|
||||
msgstr "Recevoir une notification lorsque j'ai reçu un partage"
|
||||
|
||||
#. description of the notification option 'File Rejected'
|
||||
#: pkg/store/defaults/templates.go:42
|
||||
msgid ""
|
||||
"Notify when a file I uploaded was rejected because of a virus infection or "
|
||||
"policy violation"
|
||||
msgstr ""
|
||||
"Notifier lorsqu'un fichier que j'ai téléchargé a été rejeté en raison d'une "
|
||||
"infection virale ou d'une violation de politique."
|
||||
|
||||
#. description of the notification option 'Share Removed'
|
||||
#: pkg/store/defaults/templates.go:14
|
||||
msgid "Notify when a received share has been removed"
|
||||
msgstr "Notifier la suppression d'un partage reçu"
|
||||
|
||||
#. description of the notification option 'Share Expired'
|
||||
#: pkg/store/defaults/templates.go:18
|
||||
msgid "Notify when a received share has expired"
|
||||
msgstr "Notifier l'expiration d'un partage reçue"
|
||||
|
||||
#. description of the notification option 'Space Deleted'
|
||||
#: pkg/store/defaults/templates.go:38
|
||||
msgid "Notify when a space I am member of has been deleted"
|
||||
msgstr "Notifier lorsqu'un espace dont je suis membre a été supprimé"
|
||||
|
||||
#. description of the notification option 'Space Disabled'
|
||||
#: pkg/store/defaults/templates.go:34
|
||||
msgid "Notify when a space I am member of has been disabled"
|
||||
msgstr "Notifier lorsqu'un Espace dont je suis membre a été désactivé"
|
||||
|
||||
#. description of the notification option 'Space Membership Expired'
|
||||
#: pkg/store/defaults/templates.go:30
|
||||
msgid "Notify when a space membership has expired"
|
||||
msgstr "Notifier l'expiration d'une adhésion à un Espace"
|
||||
|
||||
#. name of the notification option 'Space Unshared'
|
||||
#: pkg/store/defaults/templates.go:24
|
||||
msgid "Removed as space member"
|
||||
msgstr "Supprimé en tant que membre de l'Espace"
|
||||
|
||||
#. description of the notification option 'Email Interval'
|
||||
#: pkg/store/defaults/templates.go:46
|
||||
msgid "Selected value:"
|
||||
msgstr "Valeur sélectionnée :"
|
||||
|
||||
#. name of the notification option 'Share Expired'
|
||||
#: pkg/store/defaults/templates.go:16
|
||||
msgid "Share Expired"
|
||||
msgstr "Partage expiré"
|
||||
|
||||
#. name of the notification option 'Share Received'
|
||||
#: pkg/store/defaults/templates.go:8
|
||||
msgid "Share Received"
|
||||
msgstr "Partage reçue"
|
||||
|
||||
#. name of the notification option 'Share Removed'
|
||||
#: pkg/store/defaults/templates.go:12
|
||||
msgid "Share Removed"
|
||||
msgstr "Partage supprimée"
|
||||
|
||||
#. name of the notification option 'Space Deleted'
|
||||
#: pkg/store/defaults/templates.go:36
|
||||
msgid "Space deleted"
|
||||
msgstr "Espace supprimé"
|
||||
|
||||
#. name of the notification option 'Space Disabled'
|
||||
#: pkg/store/defaults/templates.go:32
|
||||
msgid "Space disabled"
|
||||
msgstr "Espace désactivé"
|
||||
|
||||
#. name of the notification option 'Space Membership Expired'
|
||||
#: pkg/store/defaults/templates.go:28
|
||||
msgid "Space membership expired"
|
||||
msgstr "L'adhésion à l'espace a expiré"
|
||||
|
||||
#. translation for the 'weekly' email interval option
|
||||
#: pkg/store/defaults/templates.go:52
|
||||
msgid "Weekly"
|
||||
msgstr "Hebdomadaire"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user