Auth: Extend account settings with user details and avatar upload #98
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
2cf420d04a
commit
837669f796
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2022-08-01 15:07+0000\n"
|
"POT-Creation-Date: 2022-10-17 14:51+0000\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -17,331 +17,359 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=CHARSET\n"
|
"Content-Type: text/plain; charset=CHARSET\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: messages.go:87
|
#: messages.go:94
|
||||||
msgid "Unexpected error, please try again"
|
msgid "Unexpected error, please try again"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:88
|
#: messages.go:95
|
||||||
msgid "Invalid request"
|
msgid "Invalid request"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:89
|
#: messages.go:96
|
||||||
msgid "Changes could not be saved"
|
msgid "Changes could not be saved"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:90
|
#: messages.go:97
|
||||||
msgid "Could not be deleted"
|
msgid "Could not be deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:91
|
#: messages.go:98
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s already exists"
|
msgid "%s already exists"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:92
|
#: messages.go:99
|
||||||
msgid "Not found"
|
msgid "Not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:93
|
#: messages.go:100
|
||||||
msgid "File not found"
|
msgid "File not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:94
|
|
||||||
msgid "Originals folder is empty"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:95
|
|
||||||
msgid "Selection not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:96
|
|
||||||
msgid "Entity not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:97
|
|
||||||
msgid "Account not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:98
|
|
||||||
msgid "User not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:99
|
|
||||||
msgid "Label not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:100
|
|
||||||
msgid "Album not found"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:101
|
#: messages.go:101
|
||||||
msgid "Subject not found"
|
msgid "File too large"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:102
|
#: messages.go:102
|
||||||
msgid "Person not found"
|
msgid "Wrong file type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:103
|
#: messages.go:103
|
||||||
msgid "Face not found"
|
msgid "Originals folder is empty"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:104
|
#: messages.go:104
|
||||||
msgid "Not available in public mode"
|
msgid "Selection not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:105
|
#: messages.go:105
|
||||||
msgid "not available in read-only mode"
|
msgid "Entity not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:106
|
#: messages.go:106
|
||||||
msgid "Please log in and try again"
|
msgid "Account not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:107
|
#: messages.go:107
|
||||||
msgid "Upload might be offensive"
|
msgid "User not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:108
|
#: messages.go:108
|
||||||
msgid "No items selected"
|
msgid "Label not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:109
|
#: messages.go:109
|
||||||
msgid "Failed creating file, please check permissions"
|
msgid "Album not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:110
|
#: messages.go:110
|
||||||
msgid "Failed creating folder, please check permissions"
|
msgid "Subject not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:111
|
#: messages.go:111
|
||||||
msgid "Could not connect, please try again"
|
msgid "Person not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:112
|
#: messages.go:112
|
||||||
msgid "Invalid password, please try again"
|
msgid "Face not found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:113
|
#: messages.go:113
|
||||||
msgid "Feature disabled"
|
msgid "Not available in public mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:114
|
#: messages.go:114
|
||||||
msgid "No labels selected"
|
msgid "Not available in read-only mode"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:115
|
#: messages.go:115
|
||||||
msgid "No albums selected"
|
msgid "Please log in to your account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:116
|
#: messages.go:116
|
||||||
msgid "No files available for download"
|
msgid "Permission denied"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:117
|
#: messages.go:117
|
||||||
msgid "Failed to create zip file"
|
msgid "Upload might be offensive"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:118
|
#: messages.go:118
|
||||||
msgid "Invalid credentials"
|
msgid "Upload failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:119
|
#: messages.go:119
|
||||||
msgid "Invalid link"
|
msgid "No items selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:120
|
#: messages.go:120
|
||||||
msgid "Invalid name"
|
msgid "RunFailed creating file, please check permissions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:121
|
#: messages.go:121
|
||||||
msgid "Busy, please try again later"
|
msgid "RunFailed creating folder, please check permissions"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:122
|
#: messages.go:122
|
||||||
#, c-format
|
msgid "Could not connect, please try again"
|
||||||
msgid "The wakeup interval is %s, but must be 1h or less"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:123
|
#: messages.go:123
|
||||||
msgid "Your account could not be connected"
|
msgid "Invalid password, please try again"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:124
|
||||||
|
msgid "Feature disabled"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:125
|
||||||
|
msgid "No labels selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:126
|
#: messages.go:126
|
||||||
msgid "Changes successfully saved"
|
msgid "No albums selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:127
|
#: messages.go:127
|
||||||
msgid "Album created"
|
msgid "No files available for download"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:128
|
#: messages.go:128
|
||||||
msgid "Album saved"
|
msgid "RunFailed to create zip file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:129
|
#: messages.go:129
|
||||||
#, c-format
|
msgid "Invalid credentials"
|
||||||
msgid "Album %s deleted"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:130
|
#: messages.go:130
|
||||||
msgid "Album contents cloned"
|
msgid "Invalid link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:131
|
#: messages.go:131
|
||||||
msgid "File removed from stack"
|
msgid "Invalid name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:132
|
#: messages.go:132
|
||||||
msgid "File deleted"
|
msgid "Busy, please try again later"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:133
|
#: messages.go:133
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Selection added to %s"
|
msgid "The wakeup interval is %s, but must be 1h or less"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:134
|
#: messages.go:134
|
||||||
#, c-format
|
msgid "Your account could not be connected"
|
||||||
msgid "One entry added to %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:135
|
|
||||||
#, c-format
|
|
||||||
msgid "%d entries added to %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: messages.go:136
|
|
||||||
#, c-format
|
|
||||||
msgid "One entry removed from %s"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:137
|
#: messages.go:137
|
||||||
#, c-format
|
msgid "Changes successfully saved"
|
||||||
msgid "%d entries removed from %s"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:138
|
#: messages.go:138
|
||||||
msgid "Account created"
|
msgid "Album created"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:139
|
#: messages.go:139
|
||||||
msgid "Account saved"
|
msgid "Album saved"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:140
|
#: messages.go:140
|
||||||
msgid "Account deleted"
|
#, c-format
|
||||||
|
msgid "Album %s deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:141
|
#: messages.go:141
|
||||||
msgid "Settings saved"
|
msgid "Album contents cloned"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:142
|
#: messages.go:142
|
||||||
msgid "Password changed"
|
msgid "File removed from stack"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:143
|
#: messages.go:143
|
||||||
#, c-format
|
msgid "File deleted"
|
||||||
msgid "Import completed in %d s"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:144
|
#: messages.go:144
|
||||||
msgid "Import canceled"
|
#, c-format
|
||||||
|
msgid "Selection added to %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:145
|
#: messages.go:145
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Indexing completed in %d s"
|
msgid "One entry added to %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:146
|
#: messages.go:146
|
||||||
msgid "Indexing originals..."
|
#, c-format
|
||||||
|
msgid "%d entries added to %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:147
|
#: messages.go:147
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Indexing files in %s"
|
msgid "One entry removed from %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:148
|
#: messages.go:148
|
||||||
msgid "Indexing canceled"
|
#, c-format
|
||||||
|
msgid "%d entries removed from %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:149
|
#: messages.go:149
|
||||||
#, c-format
|
msgid "Account created"
|
||||||
msgid "Removed %d files and %d photos"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:150
|
#: messages.go:150
|
||||||
#, c-format
|
msgid "Account saved"
|
||||||
msgid "Moving files from %s"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:151
|
#: messages.go:151
|
||||||
#, c-format
|
msgid "Account deleted"
|
||||||
msgid "Copying files from %s"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:152
|
#: messages.go:152
|
||||||
msgid "Labels deleted"
|
msgid "Settings saved"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:153
|
#: messages.go:153
|
||||||
msgid "Label saved"
|
msgid "Password changed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:154
|
#: messages.go:154
|
||||||
msgid "Subject saved"
|
#, c-format
|
||||||
|
msgid "Import completed in %d s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:155
|
#: messages.go:155
|
||||||
msgid "Subject deleted"
|
msgid "Import canceled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:156
|
#: messages.go:156
|
||||||
msgid "Person saved"
|
#, c-format
|
||||||
|
msgid "Indexing completed in %d s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:157
|
#: messages.go:157
|
||||||
msgid "Person deleted"
|
msgid "Indexing originals..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:158
|
#: messages.go:158
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%d files uploaded in %d s"
|
msgid "Indexing files in %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:159
|
#: messages.go:159
|
||||||
msgid "Selection approved"
|
msgid "Indexing canceled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:160
|
#: messages.go:160
|
||||||
msgid "Selection archived"
|
#, c-format
|
||||||
|
msgid "Removed %d files and %d photos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:161
|
#: messages.go:161
|
||||||
msgid "Selection restored"
|
#, c-format
|
||||||
|
msgid "Moving files from %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:162
|
#: messages.go:162
|
||||||
msgid "Selection marked as private"
|
#, c-format
|
||||||
|
msgid "Copying files from %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:163
|
#: messages.go:163
|
||||||
msgid "Albums deleted"
|
msgid "Labels deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:164
|
#: messages.go:164
|
||||||
|
msgid "Label saved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:165
|
||||||
|
msgid "Subject saved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:166
|
||||||
|
msgid "Subject deleted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:167
|
||||||
|
msgid "Person saved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:168
|
||||||
|
msgid "Person deleted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:169
|
||||||
|
msgid "File uploaded"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:170
|
||||||
|
#, c-format
|
||||||
|
msgid "%d files uploaded in %d s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:171
|
||||||
|
msgid "Processing upload..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:172
|
||||||
|
msgid "Upload has been processed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:173
|
||||||
|
msgid "Selection approved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:174
|
||||||
|
msgid "Selection archived"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:175
|
||||||
|
msgid "Selection restored"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:176
|
||||||
|
msgid "Selection marked as private"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:177
|
||||||
|
msgid "Albums deleted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: messages.go:178
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "Zip created in %d s"
|
msgid "Zip created in %d s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:165
|
#: messages.go:179
|
||||||
msgid "Permanently deleted"
|
msgid "Permanently deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: messages.go:166
|
#: messages.go:180
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s has been restored"
|
msgid "%s has been restored"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
230
frontend/package-lock.json
generated
230
frontend/package-lock.json
generated
|
@ -2350,9 +2350,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "18.8.5",
|
"version": "18.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz",
|
||||||
"integrity": "sha512-Bq7G3AErwe5A/Zki5fdD3O6+0zDChhg671NfPjtIcbtzDNZTv4NPKMRFr7gtYPG7y+B8uTiNK4Ngd9T0FTar6Q=="
|
"integrity": "sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
@ -2364,42 +2364,37 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz",
|
||||||
"integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ=="
|
"integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@ungap/promise-all-settled": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q=="
|
|
||||||
},
|
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz",
|
||||||
"integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
|
"integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.16.4",
|
"@babel/parser": "^7.16.4",
|
||||||
"@vue/shared": "3.2.40",
|
"@vue/shared": "3.2.41",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map": "^0.6.1"
|
"source-map": "^0.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-dom": {
|
"node_modules/@vue/compiler-dom": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz",
|
||||||
"integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
|
"integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.2.40",
|
"@vue/compiler-core": "3.2.41",
|
||||||
"@vue/shared": "3.2.40"
|
"@vue/shared": "3.2.41"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-sfc": {
|
"node_modules/@vue/compiler-sfc": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz",
|
||||||
"integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
|
"integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.16.4",
|
"@babel/parser": "^7.16.4",
|
||||||
"@vue/compiler-core": "3.2.40",
|
"@vue/compiler-core": "3.2.41",
|
||||||
"@vue/compiler-dom": "3.2.40",
|
"@vue/compiler-dom": "3.2.41",
|
||||||
"@vue/compiler-ssr": "3.2.40",
|
"@vue/compiler-ssr": "3.2.41",
|
||||||
"@vue/reactivity-transform": "3.2.40",
|
"@vue/reactivity-transform": "3.2.41",
|
||||||
"@vue/shared": "3.2.40",
|
"@vue/shared": "3.2.41",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.25.7",
|
"magic-string": "^0.25.7",
|
||||||
"postcss": "^8.1.10",
|
"postcss": "^8.1.10",
|
||||||
|
@ -2407,12 +2402,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-ssr": {
|
"node_modules/@vue/compiler-ssr": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz",
|
||||||
"integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
|
"integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.2.40",
|
"@vue/compiler-dom": "3.2.41",
|
||||||
"@vue/shared": "3.2.40"
|
"@vue/shared": "3.2.41"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/component-compiler-utils": {
|
"node_modules/@vue/component-compiler-utils": {
|
||||||
|
@ -2455,21 +2450,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/reactivity-transform": {
|
"node_modules/@vue/reactivity-transform": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz",
|
||||||
"integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
|
"integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.16.4",
|
"@babel/parser": "^7.16.4",
|
||||||
"@vue/compiler-core": "3.2.40",
|
"@vue/compiler-core": "3.2.41",
|
||||||
"@vue/shared": "3.2.40",
|
"@vue/shared": "3.2.41",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.25.7"
|
"magic-string": "^0.25.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/shared": {
|
"node_modules/@vue/shared": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz",
|
||||||
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
|
"integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw=="
|
||||||
},
|
},
|
||||||
"node_modules/@vvo/tzdb": {
|
"node_modules/@vvo/tzdb": {
|
||||||
"version": "6.71.0",
|
"version": "6.71.0",
|
||||||
|
@ -3070,9 +3065,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz",
|
||||||
"integrity": "sha512-bznQyETwElsXl2RK7HLLwb5GPpOLlycxHCtrpDR/4RqqBzjARaOTo3jz4IgtntWUYee7Ne4S8UHd92VCuzPaWA==",
|
"integrity": "sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.0",
|
"follow-redirects": "^1.15.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
|
@ -3503,9 +3498,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001419",
|
"version": "1.0.30001420",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001419.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz",
|
||||||
"integrity": "sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==",
|
"integrity": "sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -4645,9 +4640,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.282",
|
"version": "1.4.283",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.283.tgz",
|
||||||
"integrity": "sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg=="
|
"integrity": "sha512-g6RQ9zCOV+U5QVHW9OpFR7rdk/V7xfopNXnyAamdpFgCHgZ1sjI8VuR1+zG2YG/TZk+tQ8mpNkug4P8FU0fuOA=="
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
|
@ -8538,11 +8533,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mocha": {
|
"node_modules/mocha": {
|
||||||
"version": "10.0.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
|
||||||
"integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==",
|
"integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ungap/promise-all-settled": "1.1.2",
|
|
||||||
"ansi-colors": "4.1.1",
|
"ansi-colors": "4.1.1",
|
||||||
"browser-stdout": "1.3.1",
|
"browser-stdout": "1.3.1",
|
||||||
"chokidar": "3.5.3",
|
"chokidar": "3.5.3",
|
||||||
|
@ -11489,9 +11483,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socket.io": {
|
"node_modules/socket.io": {
|
||||||
"version": "4.5.2",
|
"version": "4.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz",
|
||||||
"integrity": "sha512-6fCnk4ARMPZN448+SQcnn1u8OHUC72puJcNtSgg2xS34Cu7br1gQ09YKkO1PFfDn/wyUE9ZgMAwosJed003+NQ==",
|
"integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.4",
|
"accepts": "~1.3.4",
|
||||||
"base64id": "~2.0.0",
|
"base64id": "~2.0.0",
|
||||||
|
@ -12170,9 +12164,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ua-parser-js": {
|
"node_modules/ua-parser-js": {
|
||||||
"version": "0.7.31",
|
"version": "0.7.32",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz",
|
||||||
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==",
|
"integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -12382,15 +12376,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/util": {
|
"node_modules/util": {
|
||||||
"version": "0.12.4",
|
"version": "0.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||||
"integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==",
|
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"is-arguments": "^1.0.4",
|
"is-arguments": "^1.0.4",
|
||||||
"is-generator-function": "^1.0.7",
|
"is-generator-function": "^1.0.7",
|
||||||
"is-typed-array": "^1.1.3",
|
"is-typed-array": "^1.1.3",
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"which-typed-array": "^1.1.2"
|
"which-typed-array": "^1.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14877,9 +14870,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "18.8.5",
|
"version": "18.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz",
|
||||||
"integrity": "sha512-Bq7G3AErwe5A/Zki5fdD3O6+0zDChhg671NfPjtIcbtzDNZTv4NPKMRFr7gtYPG7y+B8uTiNK4Ngd9T0FTar6Q=="
|
"integrity": "sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w=="
|
||||||
},
|
},
|
||||||
"@types/parse-json": {
|
"@types/parse-json": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
@ -14891,42 +14884,37 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz",
|
||||||
"integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ=="
|
"integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ=="
|
||||||
},
|
},
|
||||||
"@ungap/promise-all-settled": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q=="
|
|
||||||
},
|
|
||||||
"@vue/compiler-core": {
|
"@vue/compiler-core": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz",
|
||||||
"integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
|
"integrity": "sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.16.4",
|
"@babel/parser": "^7.16.4",
|
||||||
"@vue/shared": "3.2.40",
|
"@vue/shared": "3.2.41",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map": "^0.6.1"
|
"source-map": "^0.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-dom": {
|
"@vue/compiler-dom": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz",
|
||||||
"integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
|
"integrity": "sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-core": "3.2.40",
|
"@vue/compiler-core": "3.2.41",
|
||||||
"@vue/shared": "3.2.40"
|
"@vue/shared": "3.2.41"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-sfc": {
|
"@vue/compiler-sfc": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz",
|
||||||
"integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
|
"integrity": "sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.16.4",
|
"@babel/parser": "^7.16.4",
|
||||||
"@vue/compiler-core": "3.2.40",
|
"@vue/compiler-core": "3.2.41",
|
||||||
"@vue/compiler-dom": "3.2.40",
|
"@vue/compiler-dom": "3.2.41",
|
||||||
"@vue/compiler-ssr": "3.2.40",
|
"@vue/compiler-ssr": "3.2.41",
|
||||||
"@vue/reactivity-transform": "3.2.40",
|
"@vue/reactivity-transform": "3.2.41",
|
||||||
"@vue/shared": "3.2.40",
|
"@vue/shared": "3.2.41",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.25.7",
|
"magic-string": "^0.25.7",
|
||||||
"postcss": "^8.1.10",
|
"postcss": "^8.1.10",
|
||||||
|
@ -14934,12 +14922,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/compiler-ssr": {
|
"@vue/compiler-ssr": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz",
|
||||||
"integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
|
"integrity": "sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@vue/compiler-dom": "3.2.40",
|
"@vue/compiler-dom": "3.2.41",
|
||||||
"@vue/shared": "3.2.40"
|
"@vue/shared": "3.2.41"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/component-compiler-utils": {
|
"@vue/component-compiler-utils": {
|
||||||
|
@ -14975,21 +14963,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/reactivity-transform": {
|
"@vue/reactivity-transform": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz",
|
||||||
"integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
|
"integrity": "sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/parser": "^7.16.4",
|
"@babel/parser": "^7.16.4",
|
||||||
"@vue/compiler-core": "3.2.40",
|
"@vue/compiler-core": "3.2.41",
|
||||||
"@vue/shared": "3.2.40",
|
"@vue/shared": "3.2.41",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.25.7"
|
"magic-string": "^0.25.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@vue/shared": {
|
"@vue/shared": {
|
||||||
"version": "3.2.40",
|
"version": "3.2.41",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.40.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz",
|
||||||
"integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
|
"integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw=="
|
||||||
},
|
},
|
||||||
"@vvo/tzdb": {
|
"@vvo/tzdb": {
|
||||||
"version": "6.71.0",
|
"version": "6.71.0",
|
||||||
|
@ -15441,9 +15429,9 @@
|
||||||
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
|
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
|
||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz",
|
||||||
"integrity": "sha512-bznQyETwElsXl2RK7HLLwb5GPpOLlycxHCtrpDR/4RqqBzjARaOTo3jz4IgtntWUYee7Ne4S8UHd92VCuzPaWA==",
|
"integrity": "sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.15.0",
|
"follow-redirects": "^1.15.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
|
@ -15775,9 +15763,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001419",
|
"version": "1.0.30001420",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001419.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz",
|
||||||
"integrity": "sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw=="
|
"integrity": "sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A=="
|
||||||
},
|
},
|
||||||
"chai": {
|
"chai": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
|
@ -16596,9 +16584,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"electron-to-chromium": {
|
"electron-to-chromium": {
|
||||||
"version": "1.4.282",
|
"version": "1.4.283",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.283.tgz",
|
||||||
"integrity": "sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg=="
|
"integrity": "sha512-g6RQ9zCOV+U5QVHW9OpFR7rdk/V7xfopNXnyAamdpFgCHgZ1sjI8VuR1+zG2YG/TZk+tQ8mpNkug4P8FU0fuOA=="
|
||||||
},
|
},
|
||||||
"emoji-regex": {
|
"emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
|
@ -19440,11 +19428,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mocha": {
|
"mocha": {
|
||||||
"version": "10.0.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
|
||||||
"integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==",
|
"integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ungap/promise-all-settled": "1.1.2",
|
|
||||||
"ansi-colors": "4.1.1",
|
"ansi-colors": "4.1.1",
|
||||||
"browser-stdout": "1.3.1",
|
"browser-stdout": "1.3.1",
|
||||||
"chokidar": "3.5.3",
|
"chokidar": "3.5.3",
|
||||||
|
@ -21429,9 +21416,9 @@
|
||||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
|
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
|
||||||
},
|
},
|
||||||
"socket.io": {
|
"socket.io": {
|
||||||
"version": "4.5.2",
|
"version": "4.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz",
|
||||||
"integrity": "sha512-6fCnk4ARMPZN448+SQcnn1u8OHUC72puJcNtSgg2xS34Cu7br1gQ09YKkO1PFfDn/wyUE9ZgMAwosJed003+NQ==",
|
"integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"accepts": "~1.3.4",
|
"accepts": "~1.3.4",
|
||||||
"base64id": "~2.0.0",
|
"base64id": "~2.0.0",
|
||||||
|
@ -21930,9 +21917,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ua-parser-js": {
|
"ua-parser-js": {
|
||||||
"version": "0.7.31",
|
"version": "0.7.32",
|
||||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz",
|
||||||
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ=="
|
"integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw=="
|
||||||
},
|
},
|
||||||
"uglify-js": {
|
"uglify-js": {
|
||||||
"version": "3.17.3",
|
"version": "3.17.3",
|
||||||
|
@ -22053,15 +22040,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"util": {
|
"util": {
|
||||||
"version": "0.12.4",
|
"version": "0.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||||
"integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==",
|
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"is-arguments": "^1.0.4",
|
"is-arguments": "^1.0.4",
|
||||||
"is-generator-function": "^1.0.7",
|
"is-generator-function": "^1.0.7",
|
||||||
"is-typed-array": "^1.1.3",
|
"is-typed-array": "^1.1.3",
|
||||||
"safe-buffer": "^5.1.2",
|
|
||||||
"which-typed-array": "^1.1.2"
|
"which-typed-array": "^1.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"watch": "webpack --watch",
|
"watch": "webpack --watch",
|
||||||
"build": "webpack --node-env=production",
|
"build": "webpack --node-env=production",
|
||||||
"trace": "webpack --stats-children",
|
"trace": "webpack --stats-children",
|
||||||
|
"debug": "webpack --stats-error-details",
|
||||||
"lint": "eslint --cache src/ *.js",
|
"lint": "eslint --cache src/ *.js",
|
||||||
"fmt": "eslint --cache --fix src/ *.js .eslintrc.js",
|
"fmt": "eslint --cache --fix src/ *.js .eslintrc.js",
|
||||||
"test": "karma start",
|
"test": "karma start",
|
||||||
|
|
|
@ -23,23 +23,23 @@ Additional information can be found in our Developer Guide:
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Photos from "pages/photos.vue";
|
import Photos from "page/photos.vue";
|
||||||
import Albums from "pages/albums.vue";
|
import Albums from "page/albums.vue";
|
||||||
import AlbumPhotos from "pages/album/photos.vue";
|
import AlbumPhotos from "page/album/photos.vue";
|
||||||
import Places from "pages/places.vue";
|
import Places from "page/places.vue";
|
||||||
import Browse from "pages/files/browse.vue";
|
import Browse from "page/library/browse.vue";
|
||||||
import Errors from "pages/files/errors.vue";
|
import Errors from "page/library/errors.vue";
|
||||||
import Labels from "pages/labels.vue";
|
import Labels from "page/labels.vue";
|
||||||
import People from "pages/people.vue";
|
import People from "page/people.vue";
|
||||||
import Files from "pages/files.vue";
|
import Library from "page/library.vue";
|
||||||
import Settings from "pages/settings.vue";
|
import Settings from "page/settings.vue";
|
||||||
import Login from "pages/login.vue";
|
import Login from "page/login.vue";
|
||||||
import Connect from "pages/connect.vue";
|
import Connect from "page/connect.vue";
|
||||||
import Discover from "pages/discover.vue";
|
import Discover from "page/discover.vue";
|
||||||
import About from "pages/about/about.vue";
|
import About from "page/about/about.vue";
|
||||||
import Feedback from "pages/about/feedback.vue";
|
import Feedback from "page/about/feedback.vue";
|
||||||
import License from "pages/about/license.vue";
|
import License from "page/about/license.vue";
|
||||||
import Help from "pages/help.vue";
|
import Help from "page/help.vue";
|
||||||
import { $gettext } from "common/vm";
|
import { $gettext } from "common/vm";
|
||||||
import { config, session } from "./session";
|
import { config, session } from "./session";
|
||||||
|
|
||||||
|
@ -319,25 +319,25 @@ export default [
|
||||||
meta: { title: $gettext("People"), auth: true, background: "application-light" },
|
meta: { title: $gettext("People"), auth: true, background: "application-light" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "index",
|
name: "library_index",
|
||||||
path: "/index",
|
path: "/index",
|
||||||
component: Files,
|
component: Library,
|
||||||
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||||
props: { tab: "index" },
|
props: { tab: "library_index" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "index_import",
|
name: "library_import",
|
||||||
path: "/import",
|
path: "/import",
|
||||||
component: Files,
|
component: Library,
|
||||||
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||||
props: { tab: "import" },
|
props: { tab: "library_import" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "index_logs",
|
name: "library_logs",
|
||||||
path: "/logs",
|
path: "/logs",
|
||||||
component: Files,
|
component: Library,
|
||||||
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
meta: { title: $gettext("Library"), auth: true, background: "application-light" },
|
||||||
props: { tab: "logs" },
|
props: { tab: "library_logs" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "settings",
|
name: "settings",
|
||||||
|
@ -352,8 +352,8 @@ export default [
|
||||||
props: { tab: "settings-general" },
|
props: { tab: "settings-general" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "settings_files",
|
name: "settings_media",
|
||||||
path: "/settings/files",
|
path: "/settings/media",
|
||||||
component: Settings,
|
component: Settings,
|
||||||
meta: {
|
meta: {
|
||||||
title: $gettext("Settings"),
|
title: $gettext("Settings"),
|
||||||
|
@ -362,7 +362,7 @@ export default [
|
||||||
settings: true,
|
settings: true,
|
||||||
background: "application-light",
|
background: "application-light",
|
||||||
},
|
},
|
||||||
props: { tab: "settings-files" },
|
props: { tab: "settings-media" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "settings_advanced",
|
name: "settings_advanced",
|
||||||
|
|
|
@ -209,6 +209,14 @@ export default class Session {
|
||||||
return this.user;
|
return this.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUserUID() {
|
||||||
|
if (this.user && this.user.UID) {
|
||||||
|
return this.user.UID;
|
||||||
|
} else {
|
||||||
|
return "u000000000000001"; // Unknown.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loginRequired() {
|
loginRequired() {
|
||||||
return !this.config.isPublic() && !this.isUser();
|
return !this.config.isPublic() && !this.isUser();
|
||||||
}
|
}
|
||||||
|
|
|
@ -387,7 +387,7 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile v-if="isMini && $config.feature('library')" :to="{ name: 'index' }" class="nav-library" @click.stop="">
|
<v-list-tile v-if="isMini && $config.feature('library')" :to="{ name: 'library_index' }" class="nav-library" @click.stop="">
|
||||||
<v-list-tile-action :title="$gettext('Library')">
|
<v-list-tile-action :title="$gettext('Library')">
|
||||||
<v-icon>camera_roll</v-icon>
|
<v-icon>camera_roll</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -401,7 +401,7 @@
|
||||||
|
|
||||||
<v-list-group v-if="!isMini && $config.feature('library')" prepend-icon="camera_roll" no-action>
|
<v-list-group v-if="!isMini && $config.feature('library')" prepend-icon="camera_roll" no-action>
|
||||||
<template #activator>
|
<template #activator>
|
||||||
<v-list-tile :to="{ name: 'index' }" class="nav-library" @click.stop="">
|
<v-list-tile :to="{ name: 'library_index' }" class="nav-library" @click.stop="">
|
||||||
<v-list-tile-content>
|
<v-list-tile-content>
|
||||||
<v-list-tile-title class="p-flex-menuitem">
|
<v-list-tile-title class="p-flex-menuitem">
|
||||||
<translate key="Library">Library</translate>
|
<translate key="Library">Library</translate>
|
||||||
|
@ -519,9 +519,12 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile v-show="auth && !isPublic && $config.feature('settings')" to="/settings/account" class="p-profile">
|
<v-list-tile v-show="auth && !isPublic && $config.feature('settings')" class="p-profile" @click.stop="onAccount">
|
||||||
<v-list-tile-avatar color="grey" size="36">
|
<v-list-tile-avatar size="36">
|
||||||
<span class="white--text headline">{{ !!displayName ? displayName[0].toUpperCase() : "E" }}</span>
|
<v-img :src="user.getAvatarURL()"
|
||||||
|
:alt="displayName" aspect-ratio="1"
|
||||||
|
class="grey lighten-1 elevation-0 clickable"
|
||||||
|
></v-img>
|
||||||
</v-list-tile-avatar>
|
</v-list-tile-avatar>
|
||||||
|
|
||||||
<v-list-tile-content>
|
<v-list-tile-content>
|
||||||
|
@ -532,13 +535,13 @@
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
|
|
||||||
<v-list-tile-action :title="$gettext('Logout')">
|
<v-list-tile-action :title="$gettext('Logout')">
|
||||||
<v-btn icon @click.stop.prevent="logout">
|
<v-btn icon @click.stop.prevent="onLogout">
|
||||||
<v-icon>power_settings_new</v-icon>
|
<v-icon>power_settings_new</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile v-show="isMini && auth && !isPublic" class="nav-logout" @click.stop.prevent="logout">
|
<v-list-tile v-show="isMini && auth && !isPublic" class="nav-logout" @click.stop.prevent="onLogout">
|
||||||
<v-list-tile-action :title="$gettext('Logout')">
|
<v-list-tile-action :title="$gettext('Logout')">
|
||||||
<v-icon>power_settings_new</v-icon>
|
<v-icon>power_settings_new</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
@ -555,19 +558,15 @@
|
||||||
<div class="menu-content grow-top-right">
|
<div class="menu-content grow-top-right">
|
||||||
<div class="menu-icons">
|
<div class="menu-icons">
|
||||||
<a v-if="auth && !isPublic" href="#" :title="$gettext('Logout')" class="menu-action nav-logout"
|
<a v-if="auth && !isPublic" href="#" :title="$gettext('Logout')" class="menu-action nav-logout"
|
||||||
@click.prevent="logout">
|
@click.prevent="onLogout">
|
||||||
<v-icon>power_settings_new</v-icon>
|
<v-icon>power_settings_new</v-icon>
|
||||||
</a>
|
</a>
|
||||||
<a href="#" :title="$gettext('Reload')" class="menu-action nav-reload" @click.prevent="reloadApp">
|
<a href="#" :title="$gettext('Reload')" class="menu-action nav-reload" @click.prevent="reloadApp">
|
||||||
<v-icon>refresh</v-icon>
|
<v-icon>refresh</v-icon>
|
||||||
</a>
|
</a>
|
||||||
<router-link v-if="!auth && !isPublic" :to="{ name: 'login' }" :title="$gettext('Login')"
|
<router-link v-if="auth && $config.feature('account')"
|
||||||
class="menu-action nav-login">
|
:to="{ name: 'settings_account' }" :title="$gettext('Account')" class="menu-action nav-account">
|
||||||
<v-icon>login</v-icon>
|
<v-icon>admin_panel_settings</v-icon>
|
||||||
</router-link>
|
|
||||||
<router-link v-if="auth && !routeName('index') && $config.feature('library') && $config.feature('logs')"
|
|
||||||
:to="{ name: 'logs' }" :title="$gettext('Logs')" class="menu-action nav-logs">
|
|
||||||
<v-icon>grading</v-icon>
|
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link v-if="auth && $config.feature('settings') && !routeName('settings')" :to="{ name: 'settings' }"
|
<router-link v-if="auth && $config.feature('settings') && !routeName('settings')" :to="{ name: 'settings' }"
|
||||||
:title="$gettext('Settings')" class="menu-action nav-settings">
|
:title="$gettext('Settings')" class="menu-action nav-settings">
|
||||||
|
@ -577,6 +576,10 @@
|
||||||
class="menu-action nav-upload" @click.prevent="openUpload()">
|
class="menu-action nav-upload" @click.prevent="openUpload()">
|
||||||
<v-icon>cloud_upload</v-icon>
|
<v-icon>cloud_upload</v-icon>
|
||||||
</a>
|
</a>
|
||||||
|
<router-link v-if="!auth && !isPublic" :to="{ name: 'login' }" :title="$gettext('Login')"
|
||||||
|
class="menu-action nav-login">
|
||||||
|
<v-icon>login</v-icon>
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-actions">
|
<div class="menu-actions">
|
||||||
<div v-if="auth && !routeName('browse')&& $config.feature('search')" class="menu-action nav-search">
|
<div v-if="auth && !routeName('browse')&& $config.feature('search')" class="menu-action nav-search">
|
||||||
|
@ -610,16 +613,16 @@
|
||||||
<translate>Files</translate>
|
<translate>Files</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="auth && !routeName('index') && $config.feature('library')" class="menu-action nav-library">
|
<div v-if="auth && !routeName('library_index') && $config.feature('library')" class="menu-action nav-index">
|
||||||
<router-link :to="{ name: 'index' }">
|
<router-link :to="{ name: 'library_index' }">
|
||||||
<v-icon>camera_roll</v-icon>
|
<v-icon>camera_roll</v-icon>
|
||||||
<translate>Index</translate>
|
<translate>Index</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="auth && $config.feature('sync') && !routeName('settings')" class="menu-action nav-sync">
|
<div v-if="auth && !routeName('index') && $config.feature('library') && $config.feature('logs')" class="menu-action nav-logs">
|
||||||
<router-link :to="{ name: 'settings_sync' }">
|
<router-link :to="{ name: 'library_logs' }">
|
||||||
<v-icon>sync</v-icon>
|
<v-icon>assignment</v-icon>
|
||||||
<translate>Sync</translate>
|
<translate>Logs</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<!-- div v-if="auth && $config.feature('account') && !routeName('settings')" class="menu-action nav-account">
|
<!-- div v-if="auth && $config.feature('account') && !routeName('settings')" class="menu-action nav-account">
|
||||||
|
@ -660,6 +663,8 @@
|
||||||
<script>
|
<script>
|
||||||
import Album from "model/album";
|
import Album from "model/album";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
|
import Notify from "../common/notify";
|
||||||
|
import User from "../model/user";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PNavigation",
|
name: "PNavigation",
|
||||||
|
@ -708,6 +713,7 @@ export default {
|
||||||
session: this.$session,
|
session: this.$session,
|
||||||
config: this.$config.values,
|
config: this.$config.values,
|
||||||
page: this.$config.page,
|
page: this.$config.page,
|
||||||
|
user: this.$session.getUser(),
|
||||||
reload: {
|
reload: {
|
||||||
dialog: false,
|
dialog: false,
|
||||||
},
|
},
|
||||||
|
@ -808,7 +814,10 @@ export default {
|
||||||
this.isMini = !this.isMini;
|
this.isMini = !this.isMini;
|
||||||
localStorage.setItem('last_navigation_mode', `${this.isMini}`);
|
localStorage.setItem('last_navigation_mode', `${this.isMini}`);
|
||||||
},
|
},
|
||||||
logout() {
|
onAccount: function () {
|
||||||
|
this.$router.push({name: "settings_account"});
|
||||||
|
},
|
||||||
|
onLogout() {
|
||||||
this.$session.logout();
|
this.$session.logout();
|
||||||
},
|
},
|
||||||
onIndex(ev) {
|
onIndex(ev) {
|
||||||
|
|
|
@ -345,6 +345,7 @@ ol, ul {
|
||||||
|
|
||||||
.v-menu__content,
|
.v-menu__content,
|
||||||
.v-btn.v-btn--depressed:not(.v-btn--round):not(.v-btn--icon),
|
.v-btn.v-btn--depressed:not(.v-btn--round):not(.v-btn--icon),
|
||||||
|
.v-text-field.v-text-field--box > .v-input__control > .v-input__slot,
|
||||||
.v-text-field.v-text-field--solo > .v-input__control > .v-input__slot,
|
.v-text-field.v-text-field--solo > .v-input__control > .v-input__slot,
|
||||||
#photoprism .v-dialog .v-responsive.v-image,
|
#photoprism .v-dialog .v-responsive.v-image,
|
||||||
#photoprism .cards-view .result,
|
#photoprism .cards-view .result,
|
||||||
|
@ -355,7 +356,9 @@ ol, ul {
|
||||||
|
|
||||||
.v-alert,
|
.v-alert,
|
||||||
#photoprism div.v-dialog > div.v-card,
|
#photoprism div.v-dialog > div.v-card,
|
||||||
#photoprism div.v-dialog > div.v-card > .v-card__text .v-expansion-panel__container {
|
#photoprism div.v-dialog > div.v-card > .v-card__text .v-expansion-panel__container,
|
||||||
|
#photoprism div.v-dialog > form > div.v-card,
|
||||||
|
#photoprism div.v-dialog > form > div.v-card > .v-card__text .v-expansion-panel__container {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,3 +77,19 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Account Page */
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 150px;
|
||||||
|
max-height: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
|
@ -29,11 +29,57 @@ body.dark-theme .theme--light.v-list a:hover {
|
||||||
background: rgba(255,255,255,0.3) !important;
|
background: rgba(255,255,255,0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.dark-theme .v-input input::placeholder {
|
||||||
|
color: rgba(255,255,255,0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-text-field>.v-input__control>.v-input__slot:after,
|
||||||
|
.v-text-field>.v-input__control>.v-input__slot:before {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
body.dark-theme .v-text-field.v-text-field--enclosed .v-text-field__details {
|
||||||
|
padding-left: 0;
|
||||||
|
}*/
|
||||||
|
|
||||||
.theme--light.v-list .v-list__tile--highlighted,
|
.theme--light.v-list .v-list__tile--highlighted,
|
||||||
.theme--light.v-list a:hover {
|
.theme--light.v-list a:hover {
|
||||||
background: rgba(155,155,155,0.3) !important;
|
background: rgba(155,155,155,0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.dark-theme #photoprism .map-control .theme--light.v-input:not(.v-input--is-disabled) i,
|
||||||
|
body.dark-theme #photoprism .map-control .theme--light.v-input:not(.v-input--is-disabled) input {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-theme #photoprism .theme--light.v-chip,
|
||||||
|
body.dark-theme #photoprism .v-card .theme--light.v-text-field--box>.v-input__control>.v-input__slot,
|
||||||
|
body.dark-theme #photoprism .v-card .theme--light.v-text-field--solo>.v-input__control>.v-input__slot {
|
||||||
|
background: #00000033;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-theme #photoprism,
|
||||||
|
body.dark-theme #photoprism .p-page a,
|
||||||
|
body.dark-theme #photoprism .v-datatable a,
|
||||||
|
body.dark-theme #photoprism .theme--light.v-expansion-panel .v-expansion-panel__container,
|
||||||
|
body.dark-theme #photoprism .theme--light.v-tabs__bar .v-tabs__div,
|
||||||
|
body.dark-theme #photoprism .theme--light {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-theme #photoprism .theme--light.v-label {
|
||||||
|
color: #ffffffaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-theme #photoprism .theme--light.v-select .v-select__selections {
|
||||||
|
color: #ffffffee;
|
||||||
|
}
|
||||||
|
|
||||||
|
#photoprism .v-select.v-text-field--box .v-select__selections {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Exceptions */
|
/* Exceptions */
|
||||||
|
|
||||||
#photoprism .theme--light.v-text-field--solo.background-inherit>.v-input__control>.v-input__slot {
|
#photoprism .theme--light.v-text-field--solo.background-inherit>.v-input__control>.v-input__slot {
|
||||||
|
|
135
frontend/src/dialog/account/password.vue
Normal file
135
frontend/src/dialog/account/password.vue
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<template>
|
||||||
|
<v-dialog :value="show" lazy persistent max-width="500" class="modal-dialog p-account-password-dialog" @keydown.esc="cancel">
|
||||||
|
<v-form ref="form" dense class="form-password" accept-charset="UTF-8">
|
||||||
|
<v-card raised elevation="24">
|
||||||
|
<v-card-title primary-title class="pa-2">
|
||||||
|
<v-layout row wrap class="pa-2">
|
||||||
|
<v-flex xs9 class="text-xs-left">
|
||||||
|
<h3 class="headline pa-0"><translate>Change Password</translate></h3>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs3 class="text-xs-right">
|
||||||
|
<v-icon size="28" color="primary">lock</v-icon>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text class="py-0 px-2">
|
||||||
|
<v-layout wrap align-top>
|
||||||
|
<v-flex xs12 class="px-2 pb-2 caption">
|
||||||
|
<translate>Please note that changing your password will log you out on other devices and browsers.</translate>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 class="px-2 py-1">
|
||||||
|
<v-text-field
|
||||||
|
v-model="oldPassword"
|
||||||
|
hide-details required box flat
|
||||||
|
type="password"
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Current Password')"
|
||||||
|
class="input-current-password"
|
||||||
|
color="secondary-dark"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
|
||||||
|
<v-flex xs12 class="px-2 py-1">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newPassword"
|
||||||
|
required counter persistent-hint box flat
|
||||||
|
type="password"
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('New Password')"
|
||||||
|
class="input-new-password"
|
||||||
|
color="secondary-dark"
|
||||||
|
:hint="$gettext('Must have at least 8 characters.')"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
|
||||||
|
<v-flex xs12 class="px-2 py-1">
|
||||||
|
<v-text-field
|
||||||
|
v-model="confirmPassword"
|
||||||
|
required counter persistent-hint box flat
|
||||||
|
type="password"
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Retype Password')"
|
||||||
|
class="input-retype-password"
|
||||||
|
color="secondary-dark"
|
||||||
|
:hint="$gettext('Please confirm your new password.')"
|
||||||
|
@keyup.enter.native="confirm"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="pt-1 pb-2 px-2">
|
||||||
|
<v-layout row wrap class="pa-2">
|
||||||
|
<v-flex xs12 text-xs-right>
|
||||||
|
<v-btn depressed color="secondary-light"
|
||||||
|
class="action-cancel ml-0"
|
||||||
|
@click.stop="cancel">
|
||||||
|
<translate>Cancel</translate>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn depressed color="primary-button"
|
||||||
|
class="action-confirm white--text compact mr-0"
|
||||||
|
:disabled="disabled()"
|
||||||
|
@click.stop="confirm">
|
||||||
|
<translate>Save</translate>
|
||||||
|
</v-btn>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-form>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PAccountPasswordDialog',
|
||||||
|
props: {
|
||||||
|
show: Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
busy: false,
|
||||||
|
isDemo: this.$config.get("demo"),
|
||||||
|
isPublic: this.$config.get("public"),
|
||||||
|
oldPassword: "",
|
||||||
|
newPassword: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
rtl: this.$rtl,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
created() {
|
||||||
|
if(this.isPublic && !this.isDemo) {
|
||||||
|
this.$emit('cancel');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
disabled() {
|
||||||
|
return (this.isDemo || this.busy || this.oldPassword === "" || this.newPassword.length < 8 || (this.newPassword !== this.confirmPassword));
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
this.busy = true;
|
||||||
|
this.$session.getUser().changePassword(this.oldPassword, this.newPassword).then(() => {
|
||||||
|
this.$notify.success(this.$gettext("Password changed"));
|
||||||
|
this.$emit('confirm');
|
||||||
|
}).finally(() => {
|
||||||
|
this.busy = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
if (this.busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('cancel');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -20,7 +20,7 @@
|
||||||
<v-layout row wrap>
|
<v-layout row wrap>
|
||||||
<v-flex v-if="album.Type !== 'month'" xs12 pa-2>
|
<v-flex v-if="album.Type !== 'month'" xs12 pa-2>
|
||||||
<v-text-field v-model="model.Title"
|
<v-text-field v-model="model.Title"
|
||||||
hide-details autofocus
|
hide-details autofocus box flat
|
||||||
:rules="[titleRule]"
|
:rules="[titleRule]"
|
||||||
:label="$gettext('Name')"
|
:label="$gettext('Name')"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs12 pa-2>
|
<v-flex xs12 pa-2>
|
||||||
<v-text-field v-model="model.Location"
|
<v-text-field v-model="model.Location"
|
||||||
hide-details
|
hide-details box flat
|
||||||
:label="$gettext('Location')"
|
:label="$gettext('Location')"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
<v-flex xs12 pa-2>
|
<v-flex xs12 pa-2>
|
||||||
<v-textarea :key="growDesc" v-model="model.Description"
|
<v-textarea :key="growDesc" v-model="model.Description"
|
||||||
auto-grow
|
auto-grow
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('Description')"
|
:label="$gettext('Description')"
|
||||||
:rows="1"
|
:rows="1"
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
</v-textarea>
|
</v-textarea>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs12 md6 pa-2>
|
<v-flex xs12 md6 pa-2>
|
||||||
<v-combobox v-model="model.Category" hide-details
|
<v-combobox v-model="model.Category" hide-details box flat
|
||||||
:search-input.sync="model.Category"
|
:search-input.sync="model.Category"
|
||||||
:items="categories"
|
:items="categories"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
<v-select
|
<v-select
|
||||||
v-model="model.Order"
|
v-model="model.Order"
|
||||||
:label="$gettext('Sort Order')"
|
:label="$gettext('Sort Order')"
|
||||||
hide-details
|
hide-details box flat
|
||||||
:items="sorting"
|
:items="sorting"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
|
@ -78,7 +78,7 @@
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions class="pt-0">
|
<v-card-actions class="pt-0 px-3">
|
||||||
<v-layout row wrap class="pa-2">
|
<v-layout row wrap class="pa-2">
|
||||||
<v-flex xs12 text-xs-right>
|
<v-flex xs12 text-xs-right>
|
||||||
<v-btn depressed color="secondary-light"
|
<v-btn depressed color="secondary-light"
|
||||||
|
|
|
@ -23,9 +23,9 @@ Additional information can be found in our Developer Guide:
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import PAccountAddDialog from "dialog/service/add.vue";
|
import PServiceAddDialog from "dialog/service/add.vue";
|
||||||
import PAccountRemoveDialog from "dialog/service/remove.vue";
|
import PServiceRemoveDialog from "dialog/service/remove.vue";
|
||||||
import PAccountEditDialog from "dialog/service/edit.vue";
|
import PServiceEditDialog from "dialog/service/edit.vue";
|
||||||
import PPhotoArchiveDialog from "dialog/photo/archive.vue";
|
import PPhotoArchiveDialog from "dialog/photo/archive.vue";
|
||||||
import PPhotoAlbumDialog from "dialog/photo/album.vue";
|
import PPhotoAlbumDialog from "dialog/photo/album.vue";
|
||||||
import PPhotoEditDialog from "dialog/photo/edit.vue";
|
import PPhotoEditDialog from "dialog/photo/edit.vue";
|
||||||
|
@ -47,9 +47,9 @@ import PConfirmDialog from "dialog/confirm.vue";
|
||||||
const dialogs = {};
|
const dialogs = {};
|
||||||
|
|
||||||
dialogs.install = (Vue) => {
|
dialogs.install = (Vue) => {
|
||||||
Vue.component("PAccountAddDialog", PAccountAddDialog);
|
Vue.component("PServiceAddDialog", PServiceAddDialog);
|
||||||
Vue.component("PAccountRemoveDialog", PAccountRemoveDialog);
|
Vue.component("PServiceRemoveDialog", PServiceRemoveDialog);
|
||||||
Vue.component("PAccountEditDialog", PAccountEditDialog);
|
Vue.component("PServiceEditDialog", PServiceEditDialog);
|
||||||
Vue.component("PPhotoArchiveDialog", PPhotoArchiveDialog);
|
Vue.component("PPhotoArchiveDialog", PPhotoArchiveDialog);
|
||||||
Vue.component("PPhotoAlbumDialog", PPhotoAlbumDialog);
|
Vue.component("PPhotoAlbumDialog", PPhotoAlbumDialog);
|
||||||
Vue.component("PPhotoEditDialog", PPhotoEditDialog);
|
Vue.component("PPhotoEditDialog", PPhotoEditDialog);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<v-dialog :value="show" lazy persistent max-width="350" class="p-photo-album-dialog" @keydown.esc="cancel">
|
<v-dialog :value="show" lazy persistent max-width="350" class="p-photo-album-dialog" @keydown.esc="cancel">
|
||||||
<v-card raised elevation="24">
|
<v-card raised elevation="24">
|
||||||
<v-container fluid class="pb-2 pr-2 pl-2">
|
<v-card-text class="pt-3 pl-0 pr-2">
|
||||||
<v-layout row wrap>
|
<v-layout row wrap>
|
||||||
<v-flex xs3 text-xs-center>
|
<v-flex xs3 text-xs-center>
|
||||||
<v-icon size="56" color="secondary-dark lighten-1">photo_album</v-icon>
|
<v-icon size="56" color="secondary-dark lighten-1">photo_album</v-icon>
|
||||||
|
@ -15,30 +15,33 @@
|
||||||
:items="items"
|
:items="items"
|
||||||
:search-input.sync="search"
|
:search-input.sync="search"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
hide-details
|
hide-no-data hide-details box flat
|
||||||
hide-no-data
|
|
||||||
item-text="Title"
|
item-text="Title"
|
||||||
item-value="UID"
|
item-value="UID"
|
||||||
:label="$gettext('Album Name')"
|
:label="$gettext('Album Name')"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
flat solo
|
|
||||||
class="input-album"
|
class="input-album"
|
||||||
@keyup.enter.native="confirm"
|
@keyup.enter.native="confirm"
|
||||||
>
|
>
|
||||||
</v-autocomplete>
|
</v-autocomplete>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs12 text-xs-right class="pt-3">
|
</v-layout>
|
||||||
<v-btn depressed color="secondary-light" class="action-cancel" @click.stop="cancel">
|
</v-card-text>
|
||||||
|
<v-card-actions class="pt-1 pb-2 px-2">
|
||||||
|
<v-layout row wrap class="pa-0">
|
||||||
|
<v-flex xs12 text-xs-right>
|
||||||
|
<v-btn depressed color="secondary-light" class="action-cancel ml-0" @click.stop="cancel">
|
||||||
<translate>Cancel</translate>
|
<translate>Cancel</translate>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn color="primary-button" depressed dark class="action-confirm"
|
<v-btn depressed color="primary-button"
|
||||||
|
class="action-confirm white--text compact mr-0"
|
||||||
@click.stop="confirm">
|
@click.stop="confirm">
|
||||||
<span v-if="!album">{{ labels.createAlbum }}</span>
|
<span v-if="!album">{{ labels.createAlbum }}</span>
|
||||||
<span v-else>{{ labels.addToAlbum }}</span>
|
<span v-else>{{ labels.addToAlbum }}</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-container>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
@ -129,7 +132,7 @@ export default {
|
||||||
this.$nextTick(() => this.$refs.input.focus());
|
this.$nextTick(() => this.$refs.input.focus());
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -109,11 +109,11 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Photo from "model/photo";
|
import Photo from "model/photo";
|
||||||
import PhotoDetails from "details.vue";
|
import PhotoDetails from "./edit/details.vue";
|
||||||
import PhotoLabels from "labels.vue";
|
import PhotoLabels from "./edit/labels.vue";
|
||||||
import PhotoPeople from "people.vue";
|
import PhotoPeople from "./edit/people.vue";
|
||||||
import PhotoFiles from "files.vue";
|
import PhotoFiles from "./edit/files.vue";
|
||||||
import PhotoInfo from "info.vue";
|
import PhotoInfo from "./edit/info.vue";
|
||||||
import Event from "pubsub-js";
|
import Event from "pubsub-js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -126,7 +126,10 @@ export default {
|
||||||
'p-tab-photo-info': PhotoInfo,
|
'p-tab-photo-info': PhotoInfo,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
index: Number,
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
show: Boolean,
|
show: Boolean,
|
||||||
selection: {
|
selection: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
:append-icon="model.TitleSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.TitleSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:rules="[textRule]"
|
:rules="[textRule]"
|
||||||
hide-details
|
hide-details box flat
|
||||||
:label="$gettext('Title')"
|
:label="$gettext('Title')"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
:error="invalidDate"
|
:error="invalidDate"
|
||||||
:label="$gettext('Day')"
|
:label="$gettext('Day')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details hide-no-data
|
hide-details box flat hide-no-data
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:items="options.Days()"
|
:items="options.Days()"
|
||||||
class="input-day"
|
class="input-day"
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
:error="invalidDate"
|
:error="invalidDate"
|
||||||
:label="$gettext('Month')"
|
:label="$gettext('Month')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details hide-no-data
|
hide-details box flat hide-no-data
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:items="options.MonthsShort()"
|
:items="options.MonthsShort()"
|
||||||
class="input-month"
|
class="input-month"
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
:error="invalidDate"
|
:error="invalidDate"
|
||||||
:label="$gettext('Year')"
|
:label="$gettext('Year')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details hide-no-data
|
hide-details box flat hide-no-data
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:items="options.Years()"
|
:items="options.Years()"
|
||||||
class="input-year"
|
class="input-year"
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
hide-details
|
hide-details box flat
|
||||||
return-masked-value mask="##:##:##"
|
return-masked-value mask="##:##:##"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
class="input-local-time"
|
class="input-local-time"
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:label="$gettext('Time Zone')"
|
:label="$gettext('Time Zone')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details hide-no-data
|
hide-details box flat hide-no-data
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-value="ID"
|
item-value="ID"
|
||||||
item-text="Name"
|
item-text="Name"
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:readonly="!!(model.Lat || model.Lng)"
|
:readonly="!!(model.Lat || model.Lng)"
|
||||||
:label="$gettext('Country')" hide-details
|
:label="$gettext('Country')" hide-details box flat
|
||||||
hide-no-data
|
hide-no-data
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
|
@ -138,7 +138,7 @@
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.Altitude"
|
v-model="model.Altitude"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
v-model="model.Lat"
|
v-model="model.Lat"
|
||||||
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
v-model="model.Lng"
|
v-model="model.Lng"
|
||||||
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.PlaceSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
|
@ -188,7 +188,7 @@
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:label="$gettext('Camera')"
|
:label="$gettext('Camera')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-value="ID"
|
item-value="ID"
|
||||||
item-text="Name"
|
item-text="Name"
|
||||||
|
@ -201,7 +201,7 @@
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.Iso"
|
v-model="model.Iso"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
|
@ -216,7 +216,7 @@
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.Exposure"
|
v-model="model.Exposure"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
|
@ -234,7 +234,7 @@
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:label="$gettext('Lens')"
|
:label="$gettext('Lens')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-value="ID"
|
item-value="ID"
|
||||||
item-text="Name"
|
item-text="Name"
|
||||||
|
@ -247,7 +247,7 @@
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.FNumber"
|
v-model="model.FNumber"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
|
@ -262,7 +262,7 @@
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.FocalLength"
|
v-model="model.FocalLength"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('Focal Length')"
|
:label="$gettext('Focal Length')"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
|
@ -277,7 +277,7 @@
|
||||||
:append-icon="model.Details.SubjectSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.Details.SubjectSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:rules="[textRule]"
|
:rules="[textRule]"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
auto-grow
|
auto-grow
|
||||||
:label="$gettext('Subject')"
|
:label="$gettext('Subject')"
|
||||||
|
@ -294,7 +294,7 @@
|
||||||
:append-icon="model.Details.ArtistSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.Details.ArtistSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:rules="[textRule]"
|
:rules="[textRule]"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('Artist')"
|
:label="$gettext('Artist')"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
|
@ -309,7 +309,7 @@
|
||||||
:append-icon="model.Details.CopyrightSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.Details.CopyrightSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:rules="[textRule]"
|
:rules="[textRule]"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('Copyright')"
|
:label="$gettext('Copyright')"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
|
@ -324,7 +324,7 @@
|
||||||
:append-icon="model.Details.LicenseSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.Details.LicenseSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:rules="[textRule]"
|
:rules="[textRule]"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
auto-grow
|
auto-grow
|
||||||
:label="$gettext('License')"
|
:label="$gettext('License')"
|
||||||
|
@ -340,7 +340,7 @@
|
||||||
v-model="model.Description"
|
v-model="model.Description"
|
||||||
:append-icon="model.DescriptionSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.DescriptionSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
auto-grow
|
auto-grow
|
||||||
:label="$gettext('Description')"
|
:label="$gettext('Description')"
|
||||||
|
@ -356,7 +356,7 @@
|
||||||
v-model="model.Details.Keywords"
|
v-model="model.Details.Keywords"
|
||||||
:append-icon="model.Details.KeywordsSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.Details.KeywordsSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
auto-grow
|
auto-grow
|
||||||
:label="$gettext('Keywords')"
|
:label="$gettext('Keywords')"
|
||||||
|
@ -372,7 +372,7 @@
|
||||||
v-model="model.Details.Notes"
|
v-model="model.Details.Notes"
|
||||||
:append-icon="model.Details.NotesSrc === 'manual' ? 'check' : ''"
|
:append-icon="model.Details.NotesSrc === 'manual' ? 'check' : ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
auto-grow
|
auto-grow
|
||||||
:label="$gettext('Notes')"
|
:label="$gettext('Notes')"
|
||||||
|
@ -411,13 +411,20 @@
|
||||||
<script>
|
<script>
|
||||||
import countries from "options/countries.json";
|
import countries from "options/countries.json";
|
||||||
import Thumb from "model/thumb";
|
import Thumb from "model/thumb";
|
||||||
|
import Photo from "model/photo";
|
||||||
import * as options from "options/options";
|
import * as options from "options/options";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PTabPhotoDetails',
|
name: 'PTabPhotoDetails',
|
||||||
props: {
|
props: {
|
||||||
model: Object,
|
model: {
|
||||||
uid: String,
|
type: Object,
|
||||||
|
default: () => new Photo(false),
|
||||||
|
},
|
||||||
|
uid: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
|
@ -1,19 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<v-dialog :value="show" lazy persistent max-width="500" class="p-account-create-dialog" @keydown.esc="cancel">
|
<v-dialog :value="show" lazy persistent max-width="500" class="p-account-create-dialog" @keydown.esc="cancel">
|
||||||
<v-card raised elevation="24">
|
<v-card raised elevation="24">
|
||||||
<v-card-title primary-title>
|
<v-card-title primary-title class="pa-2">
|
||||||
<div>
|
<v-layout row wrap>
|
||||||
<h3 class="headline mx-2 my-0">
|
<v-flex xs12 class="pa-2">
|
||||||
<translate>Add Server</translate>
|
<h3 class="headline pa-0">
|
||||||
|
<translate>Add Account</translate>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text class="pt-0">
|
<v-card-text class="pb-0 pt-0 px-2">
|
||||||
<v-layout row wrap>
|
<v-layout row wrap>
|
||||||
<v-flex xs12 class="pa-2">
|
<v-flex xs12 class="pa-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.AccURL"
|
v-model="model.AccURL"
|
||||||
hide-details autofocus
|
hide-details autofocus box flat
|
||||||
:label="$gettext('Service URL')"
|
:label="$gettext('Service URL')"
|
||||||
placeholder="https://www.example.com/"
|
placeholder="https://www.example.com/"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
|
@ -24,7 +26,7 @@
|
||||||
<v-flex xs12 sm6 class="pa-2">
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.AccUser"
|
v-model="model.AccUser"
|
||||||
hide-details
|
hide-details box flat
|
||||||
:label="$gettext('Username')"
|
:label="$gettext('Username')"
|
||||||
placeholder="optional"
|
placeholder="optional"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
|
@ -35,7 +37,7 @@
|
||||||
<v-flex xs12 sm6 class="pa-2">
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.AccPass"
|
v-model="model.AccPass"
|
||||||
hide-details
|
hide-details box flat
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
:label="$gettext('Password')"
|
:label="$gettext('Password')"
|
||||||
|
@ -46,22 +48,26 @@
|
||||||
@click:append="showPassword = !showPassword"
|
@click:append="showPassword = !showPassword"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs12 text-xs-left class="pa-2 caption">
|
</v-layout>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="pt-1 pb-2 px-2">
|
||||||
|
<v-layout row wrap class="pa-2">
|
||||||
|
<v-flex xs12 text-xs-left class="caption">
|
||||||
<translate>Note: Only WebDAV servers, like Nextcloud or PhotoPrism, can be configured as remote service for backup and file upload.</translate>
|
<translate>Note: Only WebDAV servers, like Nextcloud or PhotoPrism, can be configured as remote service for backup and file upload.</translate>
|
||||||
<translate>Support for additional services, like Google Drive, will be added over time.</translate>
|
<translate>Support for additional services, like Google Drive, will be added over time.</translate>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs12 text-xs-right class="px-2 pt-2 pb-0">
|
<v-flex xs12 text-xs-right class="pt-2">
|
||||||
<v-btn depressed color="secondary-light" class="action-cancel mr-2"
|
<v-btn depressed color="secondary-light" class="action-cancel mr-2"
|
||||||
@click.stop="cancel">
|
@click.stop="cancel">
|
||||||
<span>{{ label.cancel }}</span>
|
<span>{{ label.cancel }}</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn depressed dark color="primary-button" class="action-confirm ma-0"
|
<v-btn depressed dark color="primary-button" class="action-confirm compact ma-0"
|
||||||
@click.stop="confirm">
|
@click.stop="confirm">
|
||||||
<span>{{ label.confirm }}</span>
|
<span>{{ label.confirm }}</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-card-text>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<v-dialog :value="show" lazy persistent max-width="500" class="p-account-edit-dialog" @keydown.esc="cancel">
|
<v-dialog :value="show" lazy persistent max-width="500" class="p-account-edit-dialog" @keydown.esc="cancel">
|
||||||
<v-card raised elevation="24">
|
<v-card raised elevation="24">
|
||||||
<v-card-title primary-title>
|
<v-card-title primary-title class="pa-2">
|
||||||
<v-layout v-if="scope === 'sharing'" row wrap>
|
<v-layout v-if="scope === 'sharing'" row wrap class="py-2 pr-0 pl-2">
|
||||||
<v-flex xs9>
|
<v-flex xs9>
|
||||||
<h3 class="headline mx-2 my-0">{{ $gettext('Manual Upload') }}</h3>
|
<h3 class="headline ma-0 pa-0">{{ $gettext('Manual Upload') }}</h3>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs3 text-xs-right>
|
<v-flex xs3 text-xs-right>
|
||||||
<v-switch
|
<v-switch
|
||||||
|
@ -12,9 +12,8 @@
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:true-value="true"
|
:true-value="true"
|
||||||
:false-value="false"
|
:false-value="false"
|
||||||
:label="model.AccShare ? $gettext('Enabled') : $gettext('Disabled')"
|
|
||||||
:disabled="model.AccType !== 'webdav'"
|
:disabled="model.AccType !== 'webdav'"
|
||||||
class="ma-0 hidden-xs-only"
|
class="ma-0 hidden-xs-only float-right"
|
||||||
hide-details
|
hide-details
|
||||||
></v-switch>
|
></v-switch>
|
||||||
<v-switch
|
<v-switch
|
||||||
|
@ -23,14 +22,14 @@
|
||||||
:true-value="true"
|
:true-value="true"
|
||||||
:false-value="false"
|
:false-value="false"
|
||||||
:disabled="model.AccType !== 'webdav'"
|
:disabled="model.AccType !== 'webdav'"
|
||||||
class="ma-0 hidden-sm-and-up"
|
class="ma-0 hidden-sm-and-up float-right"
|
||||||
hide-details
|
hide-details
|
||||||
></v-switch>
|
></v-switch>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
<v-layout v-else-if="scope === 'sync'" row wrap>
|
<v-layout v-else-if="scope === 'sync'" row wrap class="pa-2">
|
||||||
<v-flex xs9>
|
<v-flex xs9>
|
||||||
<h3 class="headline mx-2 my-0">{{ $gettext('Remote Sync') }}</h3>
|
<h3 class="headline ma-0 pa-0">{{ $gettext('Remote Sync') }}</h3>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs3 text-xs-right>
|
<v-flex xs3 text-xs-right>
|
||||||
<v-switch
|
<v-switch
|
||||||
|
@ -38,10 +37,9 @@
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:true-value="true"
|
:true-value="true"
|
||||||
:false-value="false"
|
:false-value="false"
|
||||||
:label="model.AccSync ? $gettext('Enabled') : $gettext('Disabled')"
|
|
||||||
:disabled="model.AccType !== 'webdav'"
|
:disabled="model.AccType !== 'webdav'"
|
||||||
class="mt-0 hidden-xs-only"
|
class="mt-0 hidden-xs-only float-right"
|
||||||
hide-details
|
hide-details box flat
|
||||||
></v-switch>
|
></v-switch>
|
||||||
<v-switch
|
<v-switch
|
||||||
v-model="model.AccSync"
|
v-model="model.AccSync"
|
||||||
|
@ -49,14 +47,14 @@
|
||||||
:true-value="true"
|
:true-value="true"
|
||||||
:false-value="false"
|
:false-value="false"
|
||||||
:disabled="model.AccType !== 'webdav'"
|
:disabled="model.AccType !== 'webdav'"
|
||||||
class="mt-0 hidden-sm-and-up"
|
class="mt-0 hidden-sm-and-up float-right"
|
||||||
hide-details
|
hide-details box flat
|
||||||
></v-switch>
|
></v-switch>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
<v-layout v-else row wrap>
|
<v-layout v-else row wrap class="pt-2 pr-0 pl-2">
|
||||||
<v-flex xs10>
|
<v-flex xs10>
|
||||||
<h3 class="headline mx-2 my-0">{{ $gettext('Edit Account') }}</h3>
|
<h3 class="headline ma-0 pa-0">{{ $gettext('Edit Account') }}</h3>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs2 text-xs-right>
|
<v-flex xs2 text-xs-right>
|
||||||
<v-btn icon flat :ripple="false"
|
<v-btn icon flat :ripple="false"
|
||||||
|
@ -67,13 +65,12 @@
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text class="pt-0">
|
<v-card-text class="py-0 px-2">
|
||||||
<v-layout v-if="scope === 'sharing'" row wrap>
|
<v-layout v-if="scope === 'sharing'" row wrap>
|
||||||
<v-flex xs12 class="pa-2">
|
<v-flex xs12 class="pa-2">
|
||||||
<v-autocomplete
|
<v-autocomplete
|
||||||
v-model="model.SharePath"
|
v-model="model.SharePath"
|
||||||
color="secondary-dark" hide-details hide-no-data
|
color="secondary-dark" hide-details hide-no-data box flat
|
||||||
flat
|
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hint="Folder"
|
hint="Folder"
|
||||||
:search-input.sync="search"
|
:search-input.sync="search"
|
||||||
|
@ -92,7 +89,7 @@
|
||||||
:disabled="!model.AccShare"
|
:disabled="!model.AccShare"
|
||||||
:label="$gettext('Size')"
|
:label="$gettext('Size')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-text="text"
|
item-text="text"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
|
@ -105,7 +102,7 @@
|
||||||
:disabled="!model.AccShare"
|
:disabled="!model.AccShare"
|
||||||
:label="$gettext('Expires')"
|
:label="$gettext('Expires')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-text="text"
|
item-text="text"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
|
@ -117,8 +114,7 @@
|
||||||
<v-flex xs12 sm6 class="pa-2">
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
<v-autocomplete
|
<v-autocomplete
|
||||||
v-model="model.SyncPath"
|
v-model="model.SyncPath"
|
||||||
color="secondary-dark" hide-details hide-no-data
|
color="secondary-dark" hide-details hide-no-data box flat
|
||||||
flat
|
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:hint="$gettext('Folder')"
|
:hint="$gettext('Folder')"
|
||||||
:search-input.sync="search"
|
:search-input.sync="search"
|
||||||
|
@ -137,7 +133,7 @@
|
||||||
:disabled="!model.AccSync"
|
:disabled="!model.AccSync"
|
||||||
:label="$gettext('Interval')"
|
:label="$gettext('Interval')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-text="text"
|
item-text="text"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
|
@ -148,7 +144,7 @@
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
v-model="model.SyncDownload"
|
v-model="model.SyncDownload"
|
||||||
:disabled="!model.AccSync || readonly"
|
:disabled="!model.AccSync || readonly"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
on-icon="radio_button_checked"
|
on-icon="radio_button_checked"
|
||||||
off-icon="radio_button_unchecked"
|
off-icon="radio_button_unchecked"
|
||||||
|
@ -160,7 +156,7 @@
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
v-model="model.SyncFilenames"
|
v-model="model.SyncFilenames"
|
||||||
:disabled="!model.AccSync"
|
:disabled="!model.AccSync"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:label="$gettext('Preserve filenames')"
|
:label="$gettext('Preserve filenames')"
|
||||||
></v-checkbox>
|
></v-checkbox>
|
||||||
|
@ -169,7 +165,7 @@
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
v-model="model.SyncUpload"
|
v-model="model.SyncUpload"
|
||||||
:disabled="!model.AccSync"
|
:disabled="!model.AccSync"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
on-icon="radio_button_checked"
|
on-icon="radio_button_checked"
|
||||||
off-icon="radio_button_unchecked"
|
off-icon="radio_button_unchecked"
|
||||||
|
@ -181,17 +177,17 @@
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
v-model="model.SyncRaw"
|
v-model="model.SyncRaw"
|
||||||
:disabled="!model.AccSync"
|
:disabled="!model.AccSync"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:label="$gettext('Sync raw and video files')"
|
:label="$gettext('Sync raw and video files')"
|
||||||
></v-checkbox>
|
></v-checkbox>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
<v-layout v-else row wrap>
|
<v-layout v-else row wrap class="pt-0">
|
||||||
<v-flex xs12 class="pa-2">
|
<v-flex xs12 class="pa-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.AccName"
|
v-model="model.AccName"
|
||||||
hide-details autofocus
|
hide-details autofocus box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('Name')"
|
:label="$gettext('Name')"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
|
@ -202,7 +198,7 @@
|
||||||
<v-flex xs12 class="pa-2">
|
<v-flex xs12 class="pa-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.AccURL"
|
v-model="model.AccURL"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('Service URL')"
|
:label="$gettext('Service URL')"
|
||||||
placeholder="https://www.example.com/"
|
placeholder="https://www.example.com/"
|
||||||
|
@ -212,7 +208,7 @@
|
||||||
<v-flex xs12 sm6 class="pa-2">
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.AccUser"
|
v-model="model.AccUser"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('Username')"
|
:label="$gettext('Username')"
|
||||||
placeholder="optional"
|
placeholder="optional"
|
||||||
|
@ -222,7 +218,7 @@
|
||||||
<v-flex xs12 sm6 class="pa-2">
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.AccPass"
|
v-model="model.AccPass"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('Password')"
|
:label="$gettext('Password')"
|
||||||
placeholder="optional"
|
placeholder="optional"
|
||||||
|
@ -235,7 +231,7 @@
|
||||||
<v-flex xs12 sm6 class="pa-2">
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="model.AccKey"
|
v-model="model.AccKey"
|
||||||
hide-details
|
hide-details box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
:label="$gettext('API Key')"
|
:label="$gettext('API Key')"
|
||||||
placeholder="optional"
|
placeholder="optional"
|
||||||
|
@ -248,7 +244,7 @@
|
||||||
v-model="model.AccType"
|
v-model="model.AccType"
|
||||||
:label="$gettext('Type')"
|
:label="$gettext('Type')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-text="text"
|
item-text="text"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
|
@ -260,7 +256,7 @@
|
||||||
v-model="model.AccTimeout"
|
v-model="model.AccTimeout"
|
||||||
:label="$gettext('Timeout')"
|
:label="$gettext('Timeout')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-text="text"
|
item-text="text"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
|
@ -272,7 +268,7 @@
|
||||||
v-model="model.RetryLimit"
|
v-model="model.RetryLimit"
|
||||||
:label="$gettext('Retry Limit')"
|
:label="$gettext('Retry Limit')"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-text="text"
|
item-text="text"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
|
@ -280,19 +276,21 @@
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
<v-layout row wrap>
|
</v-card-text>
|
||||||
|
<v-card-actions class="pt-0 pb-2 px-2">
|
||||||
|
<v-layout row wrap class="pa-2">
|
||||||
<v-flex xs12 text-xs-right class="pt-3 pb-0">
|
<v-flex xs12 text-xs-right class="pt-3 pb-0">
|
||||||
<v-btn depressed color="secondary-light" class="action-cancel"
|
<v-btn depressed color="secondary-light" class="action-cancel"
|
||||||
@click.stop="cancel">
|
@click.stop="cancel">
|
||||||
<translate>Cancel</translate>
|
<translate>Cancel</translate>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn depressed dark color="primary-button" class="action-save"
|
<v-btn depressed dark color="primary-button" class="action-save compact"
|
||||||
@click.stop="save">
|
@click.stop="save">
|
||||||
<translate>Save</translate>
|
<translate>Save</translate>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-card-text>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
@ -303,7 +301,10 @@ export default {
|
||||||
name: 'PAccountEditDialog',
|
name: 'PAccountEditDialog',
|
||||||
props: {
|
props: {
|
||||||
show: Boolean,
|
show: Boolean,
|
||||||
scope: String,
|
scope: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
model: {
|
model: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {
|
default: () => {
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
readonly
|
readonly
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
class="input-url"
|
class="input-url"
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
v-model="link.Expires"
|
v-model="link.Expires"
|
||||||
:label="expires(link)"
|
:label="expires(link)"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hide-details
|
hide-details box flat
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
item-text="text"
|
item-text="text"
|
||||||
item-value="value"
|
item-value="value"
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs6 :text-xs-right="!rtl" :text-xs-left="rtl" class="pa-2">
|
<v-flex xs6 :text-xs-right="!rtl" :text-xs-left="rtl" class="pa-2">
|
||||||
<v-btn depressed dark color="primary-button" class="ma-0 action-save"
|
<v-btn depressed dark color="primary-button" class="ma-0 compact action-save"
|
||||||
@click.stop.exact="update(link)">
|
@click.stop.exact="update(link)">
|
||||||
<translate>Save</translate>
|
<translate>Save</translate>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
<translate>Alternatively, you can upload files directly to WebDAV servers like Nextcloud.</translate>
|
<translate>Alternatively, you can upload files directly to WebDAV servers like Nextcloud.</translate>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions class="pt-0">
|
<v-card-actions class="pt-0 px-3">
|
||||||
<v-layout row wrap class="pa-2">
|
<v-layout row wrap class="pa-2">
|
||||||
<v-flex xs6>
|
<v-flex xs6>
|
||||||
<v-btn depressed color="secondary-light" class="action-webdav"
|
<v-btn depressed color="secondary-light" class="action-webdav"
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<v-select
|
<v-select
|
||||||
v-model="service"
|
v-model="service"
|
||||||
color="secondary-dark" hide-details hide-no-data
|
color="secondary-dark" hide-details hide-no-data
|
||||||
flat
|
box flat
|
||||||
:label="$gettext('Account')"
|
:label="$gettext('Account')"
|
||||||
item-text="AccName"
|
item-text="AccName"
|
||||||
item-value="ID"
|
item-value="ID"
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<v-autocomplete
|
<v-autocomplete
|
||||||
v-model="path"
|
v-model="path"
|
||||||
color="secondary-dark" hide-details hide-no-data
|
color="secondary-dark" hide-details hide-no-data
|
||||||
flat
|
box flat
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
hint="Folder"
|
hint="Folder"
|
||||||
:search-input.sync="search"
|
:search-input.sync="search"
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<translate>Feel free to contact us at hello@photoprism.app if you have any questions.</translate>
|
<translate>Feel free to contact us at hello@photoprism.app if you have any questions.</translate>
|
||||||
</p>
|
</p>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions class="pt-0">
|
<v-card-actions class="pt-0 px-3">
|
||||||
<v-layout row wrap class="px-2">
|
<v-layout row wrap class="px-2">
|
||||||
<v-flex xs12 sm4 text-xs-right text-sm-left class="py-2">
|
<v-flex xs12 sm4 text-xs-right text-sm-left class="py-2">
|
||||||
<v-btn depressed color="secondary-light"
|
<v-btn depressed color="secondary-light"
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-container grid-list-xs ext-xs-left fluid>
|
<v-container grid-list-xs ext-xs-left fluid>
|
||||||
<v-form ref="form" class="p-photo-upload" lazy-validation dense @submit.prevent="submit">
|
<v-form ref="form" class="p-photo-upload" lazy-validation dense @submit.prevent="submit">
|
||||||
<input ref="upload" type="file" multiple class="d-none input-upload" @change.stop="upload()">
|
<input ref="upload" type="file" multiple class="d-none input-upload" @change.stop="onUpload()">
|
||||||
|
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<p class="subheading">
|
<p class="subheading">
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
color="primary-button"
|
color="primary-button"
|
||||||
class="white--text ml-0 mt-2 action-upload"
|
class="white--text ml-0 mt-2 action-upload"
|
||||||
depressed
|
depressed
|
||||||
@click.stop="uploadDialog()"
|
@click.stop="onUploadDialog()"
|
||||||
>
|
>
|
||||||
<translate key="Upload">Upload</translate>
|
<translate key="Upload">Upload</translate>
|
||||||
<v-icon :right="!rtl" :left="rtl" dark>cloud_upload</v-icon>
|
<v-icon :right="!rtl" :left="rtl" dark>cloud_upload</v-icon>
|
||||||
|
@ -146,7 +146,7 @@ export default {
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
if (this.busy) {
|
if (this.busy) {
|
||||||
Notify.info(this.$gettext("Uploading photos…"));
|
Notify.info(this.$gettext("Uploading…"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ export default {
|
||||||
},
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
if (this.busy) {
|
if (this.busy) {
|
||||||
Notify.info(this.$gettext("Uploading photos…"));
|
Notify.info(this.$gettext("Uploading…"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,9 +163,6 @@ export default {
|
||||||
submit() {
|
submit() {
|
||||||
// DO NOTHING
|
// DO NOTHING
|
||||||
},
|
},
|
||||||
uploadDialog() {
|
|
||||||
this.$refs.upload.click();
|
|
||||||
},
|
|
||||||
reset() {
|
reset() {
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
this.selected = [];
|
this.selected = [];
|
||||||
|
@ -177,7 +174,10 @@ export default {
|
||||||
this.completed = 0;
|
this.completed = 0;
|
||||||
this.token = "";
|
this.token = "";
|
||||||
},
|
},
|
||||||
upload() {
|
onUploadDialog() {
|
||||||
|
this.$refs.upload.click();
|
||||||
|
},
|
||||||
|
onUpload() {
|
||||||
if (this.busy) {
|
if (this.busy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,9 @@ export default {
|
||||||
this.completed = 0;
|
this.completed = 0;
|
||||||
this.uploads = [];
|
this.uploads = [];
|
||||||
|
|
||||||
Notify.info(this.$gettext("Uploading photos…"));
|
let userUid = this.$session.getUserUID();
|
||||||
|
|
||||||
|
Notify.info(this.$gettext("Uploading…"));
|
||||||
|
|
||||||
let addToAlbums = [];
|
let addToAlbums = [];
|
||||||
|
|
||||||
|
@ -222,7 +224,7 @@ export default {
|
||||||
|
|
||||||
formData.append('files', file);
|
formData.append('files', file);
|
||||||
|
|
||||||
await Api.post('upload/' + ctx.token,
|
await Api.post(`users/${userUid}/upload/${ctx.token}`,
|
||||||
formData,
|
formData,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -240,9 +242,7 @@ export default {
|
||||||
performUpload(this).then(() => {
|
performUpload(this).then(() => {
|
||||||
this.indexing = true;
|
this.indexing = true;
|
||||||
const ctx = this;
|
const ctx = this;
|
||||||
|
Api.post(`users/${userUid}/upload/${ctx.token}`,{
|
||||||
Api.post('import/upload/' + this.token, {
|
|
||||||
move: true,
|
|
||||||
albums: addToAlbums,
|
albums: addToAlbums,
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
ctx.reset();
|
ctx.reset();
|
||||||
|
@ -250,7 +250,7 @@ export default {
|
||||||
ctx.$emit('confirm');
|
ctx.$emit('confirm');
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
ctx.reset();
|
ctx.reset();
|
||||||
Notify.error(ctx.$gettext("Failure while importing uploaded files"));
|
Notify.error(ctx.$gettext("Upload failed"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -80,6 +80,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
|
user: this.$session.getUser(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -111,14 +112,29 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
webdavUrl() {
|
webdavUrl() {
|
||||||
return `${window.location.protocol}//admin@${window.location.host}/originals/`;
|
let baseUrl = `${window.location.protocol}//${this.user.Name}@${window.location.host}/originals/`;
|
||||||
|
|
||||||
|
if (this.user.BasePath) {
|
||||||
|
baseUrl = `${baseUrl}${this.user.BasePath}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
},
|
},
|
||||||
windowsUrl() {
|
windowsUrl() {
|
||||||
|
let baseUrl = "";
|
||||||
|
|
||||||
if (window.location.protocol === "https") {
|
if (window.location.protocol === "https") {
|
||||||
return `\\\\${window.location.host}@SSL\\originals\\`;
|
baseUrl = `\\\\${window.location.host}@SSL\\originals\\`;
|
||||||
} else {
|
} else {
|
||||||
return `\\\\${window.location.host}\\originals\\`;
|
baseUrl = `\\\\${window.location.host}\\originals\\`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.user.BasePath) {
|
||||||
|
const basePath = this.user.BasePath.replace(/\//g, '\\');
|
||||||
|
baseUrl = `${baseUrl}${basePath}\\`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl;
|
||||||
},
|
},
|
||||||
windowsHelp(ev) {
|
windowsHelp(ev) {
|
||||||
window.open('https://docs.photoprism.app/user-guide/sync/webdav/#connect-to-a-webdav-server', '_blank');
|
window.open('https://docs.photoprism.app/user-guide/sync/webdav/#connect-to-a-webdav-server', '_blank');
|
||||||
|
|
|
@ -71,7 +71,19 @@ msgstr "Abyss"
|
||||||
#: src/component/navigation.vue:73 src/dialog/share/upload.vue:112
|
#: src/component/navigation.vue:73 src/dialog/share/upload.vue:112
|
||||||
#: src/model/account.js:98 src/pages/settings.vue:74
|
#: src/model/account.js:98 src/pages/settings.vue:74
|
||||||
msgid "Account"
|
msgid "Account"
|
||||||
msgstr "Profil"
|
msgstr "Konto"
|
||||||
|
|
||||||
|
msgid "Services"
|
||||||
|
msgstr "Dienste"
|
||||||
|
|
||||||
|
msgid "Change Password"
|
||||||
|
msgstr "Passwort ändern"
|
||||||
|
|
||||||
|
msgid "Add Account"
|
||||||
|
msgstr "Konto hinzufügen"
|
||||||
|
|
||||||
|
msgid "Please note that changing your password will log you out on other devices and browsers."
|
||||||
|
msgstr "Bitte beachte, dass du beim Ändern deines Passworts auf anderen Geräten und Browsern abgemeldet wirst."
|
||||||
|
|
||||||
#: src/dialog/photo/info.vue:159
|
#: src/dialog/photo/info.vue:159
|
||||||
msgid "Accuracy"
|
msgid "Accuracy"
|
||||||
|
@ -306,7 +318,7 @@ msgstr "Diese Kategorien wirklich löschen?"
|
||||||
|
|
||||||
#: src/dialog/account/remove.vue:10
|
#: src/dialog/account/remove.vue:10
|
||||||
msgid "Are you sure you want to delete this account?"
|
msgid "Are you sure you want to delete this account?"
|
||||||
msgstr "Diesen Account wirklich löschen?"
|
msgstr "Dieses Konto wirklich löschen?"
|
||||||
|
|
||||||
#: src/dialog/photo/delete.vue:10
|
#: src/dialog/photo/delete.vue:10
|
||||||
msgid "Are you sure you want to permanently delete these pictures?"
|
msgid "Are you sure you want to permanently delete these pictures?"
|
||||||
|
@ -822,7 +834,7 @@ msgstr "%{name} bearbeiten"
|
||||||
|
|
||||||
#: src/dialog/account/edit.vue:140
|
#: src/dialog/account/edit.vue:140
|
||||||
msgid "Edit Account"
|
msgid "Edit Account"
|
||||||
msgstr "Account bearbeiten"
|
msgstr "Konto bearbeiten"
|
||||||
|
|
||||||
#: src/dialog/photo/edit.vue:51
|
#: src/dialog/photo/edit.vue:51
|
||||||
msgid "Edit Photo"
|
msgid "Edit Photo"
|
||||||
|
@ -1906,7 +1918,7 @@ msgstr "Wird neu geladen…"
|
||||||
|
|
||||||
#: src/dialog/account/edit.vue:96
|
#: src/dialog/account/edit.vue:96
|
||||||
msgid "Remote Sync"
|
msgid "Remote Sync"
|
||||||
msgstr "Fern-Synchronisation"
|
msgstr "Synchronisation"
|
||||||
|
|
||||||
#: src/dialog/photo/people.vue:108
|
#: src/dialog/photo/people.vue:108
|
||||||
msgid "Remove"
|
msgid "Remove"
|
||||||
|
@ -2029,7 +2041,7 @@ msgstr "Server"
|
||||||
#: src/dialog/account/add.vue:65 src/dialog/account/edit.vue:420
|
#: src/dialog/account/add.vue:65 src/dialog/account/edit.vue:420
|
||||||
#: src/dialog/share.vue:22
|
#: src/dialog/share.vue:22
|
||||||
msgid "Service URL"
|
msgid "Service URL"
|
||||||
msgstr "Service URL"
|
msgstr "Dienst-URL"
|
||||||
|
|
||||||
#: src/app/routes.js:326 src/app/routes.js:338 src/app/routes.js:350
|
#: src/app/routes.js:326 src/app/routes.js:338 src/app/routes.js:350
|
||||||
#: src/app/routes.js:362 src/app/routes.js:374 src/component/navigation.vue:407
|
#: src/app/routes.js:362 src/app/routes.js:374 src/component/navigation.vue:407
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -28,6 +28,7 @@ import Form from "common/form";
|
||||||
import Util from "common/util";
|
import Util from "common/util";
|
||||||
import Api from "common/api";
|
import Api from "common/api";
|
||||||
import { T, $gettext } from "common/vm";
|
import { T, $gettext } from "common/vm";
|
||||||
|
import { config } from "app/session";
|
||||||
|
|
||||||
export class User extends RestModel {
|
export class User extends RestModel {
|
||||||
getDefaults() {
|
getDefaults() {
|
||||||
|
@ -66,33 +67,35 @@ export class User extends RestModel {
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
},
|
},
|
||||||
Details: {
|
Details: {
|
||||||
|
IdURL: "",
|
||||||
SubjUID: "",
|
SubjUID: "",
|
||||||
SubjSrc: "",
|
SubjSrc: "",
|
||||||
PlaceID: "",
|
PlaceID: "",
|
||||||
PlaceSrc: "",
|
PlaceSrc: "",
|
||||||
CellID: "",
|
CellID: "",
|
||||||
IdURL: "",
|
BirthYear: -1,
|
||||||
AvatarURL: "",
|
BirthMonth: -1,
|
||||||
SiteURL: "",
|
BirthDay: -1,
|
||||||
FeedURL: "",
|
|
||||||
UserGender: "",
|
|
||||||
NamePrefix: "",
|
NamePrefix: "",
|
||||||
GivenName: "",
|
GivenName: "",
|
||||||
MiddleName: "",
|
MiddleName: "",
|
||||||
FamilyName: "",
|
FamilyName: "",
|
||||||
NameSuffix: "",
|
NameSuffix: "",
|
||||||
NickName: "",
|
NickName: "",
|
||||||
UserPhone: "",
|
Gender: "",
|
||||||
UserAddress: "",
|
Bio: "",
|
||||||
UserCountry: "",
|
Location: "",
|
||||||
UserBio: "",
|
Country: "",
|
||||||
JobTitle: "",
|
Phone: "",
|
||||||
Department: "",
|
SiteURL: "",
|
||||||
Company: "",
|
ProfileURL: "",
|
||||||
CompanyURL: "",
|
FeedURL: "",
|
||||||
BirthYear: -1,
|
AvatarURL: "",
|
||||||
BirthMonth: -1,
|
OrgName: "",
|
||||||
BirthDay: -1,
|
OrgTitle: "",
|
||||||
|
OrgEmail: "",
|
||||||
|
OrgPhone: "",
|
||||||
|
OrgURL: "",
|
||||||
CreatedAt: "",
|
CreatedAt: "",
|
||||||
UpdatedAt: "",
|
UpdatedAt: "",
|
||||||
},
|
},
|
||||||
|
@ -149,6 +152,36 @@ export class User extends RestModel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAvatarURL(size) {
|
||||||
|
if (!this.Thumb) {
|
||||||
|
return `${config.contentUri}/svg/user`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!size) {
|
||||||
|
size = "tile_500";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${config.contentUri}/t/${this.Thumb}/${config.previewToken}/${size}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadAvatar(files) {
|
||||||
|
if (this.busy) {
|
||||||
|
return;
|
||||||
|
} else if (!files || files.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = files[0];
|
||||||
|
let formData = new FormData();
|
||||||
|
let formConf = { headers: { "Content-Type": "multipart/form-data" } };
|
||||||
|
|
||||||
|
formData.append("files", file);
|
||||||
|
|
||||||
|
return Api.post(this.getEntityResource() + `/avatar`, formData, formConf).then((response) =>
|
||||||
|
Promise.resolve(this.setValues(response.data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getProfileForm() {
|
getProfileForm() {
|
||||||
return Api.options(this.getEntityResource() + "/profile").then((response) =>
|
return Api.options(this.getEntityResource() + "/profile").then((response) =>
|
||||||
Promise.resolve(new Form(response.data))
|
Promise.resolve(new Form(response.data))
|
||||||
|
@ -162,8 +195,8 @@ export class User extends RestModel {
|
||||||
}).then((response) => Promise.resolve(response.data));
|
}).then((response) => Promise.resolve(response.data));
|
||||||
}
|
}
|
||||||
|
|
||||||
saveProfile() {
|
save() {
|
||||||
return Api.post(this.getEntityResource() + "/profile", this.getValues()).then((response) =>
|
return Api.post(this.getEntityResource(), this.getValues()).then((response) =>
|
||||||
Promise.resolve(this.setValues(response.data))
|
Promise.resolve(this.setValues(response.data))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,8 +46,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import tabColors from "pages/discover/colors.vue";
|
import tabColors from "page/discover/colors.vue";
|
||||||
import tabTodo from "pages/discover/todo.vue";
|
import tabTodo from "page/discover/todo.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PPageSettings',
|
name: 'PPageSettings',
|
|
@ -8,7 +8,7 @@
|
||||||
slider-color="secondary-dark"
|
slider-color="secondary-dark"
|
||||||
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
||||||
>
|
>
|
||||||
<v-tab v-for="(item, index) in tabs" :id="'tab-' + item.name" :key="manage" :class="item.class" ripple
|
<v-tab v-for="(item, index) in tabs" :id="'tab-' + item.name" :key="index" :class="item.class" ripple
|
||||||
@click="changePath(item.path)">
|
@click="changePath(item.path)">
|
||||||
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="item.label">{{ item.icon }}</v-icon>
|
<v-icon v-if="$vuetify.breakpoint.smAndDown" :title="item.label">{{ item.icon }}</v-icon>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
</v-tab>
|
</v-tab>
|
||||||
|
|
||||||
<v-tabs-items touchless>
|
<v-tabs-items touchless>
|
||||||
<v-tab-item v-for="(item, index) in tabs" :key="manage" lazy>
|
<v-tab-item v-for="(item, index) in tabs" :key="index" lazy>
|
||||||
<component :is="item.component"></component>
|
<component :is="item.component"></component>
|
||||||
</v-tab-item>
|
</v-tab-item>
|
||||||
</v-tabs-items>
|
</v-tabs-items>
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Import from "pages/files/import.vue";
|
import Import from "page/library/import.vue";
|
||||||
import Index from "pages/files/index.vue";
|
import Index from "page/library/index.vue";
|
||||||
import Logs from "pages/files/logs.vue";
|
import Logs from "page/library/logs.vue";
|
||||||
|
|
||||||
function initTabs(flag, tabs) {
|
function initTabs(flag, tabs) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
@ -58,7 +58,7 @@ export default {
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
'name': 'index',
|
'name': 'library_index',
|
||||||
'component': Index,
|
'component': Index,
|
||||||
'label': this.$gettext('Index'),
|
'label': this.$gettext('Index'),
|
||||||
'class': '',
|
'class': '',
|
||||||
|
@ -68,7 +68,7 @@ export default {
|
||||||
'demo': true,
|
'demo': true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'import',
|
'name': 'library_import',
|
||||||
'component': Import,
|
'component': Import,
|
||||||
'label': this.$gettext('Import'),
|
'label': this.$gettext('Import'),
|
||||||
'class': '',
|
'class': '',
|
||||||
|
@ -81,12 +81,12 @@ export default {
|
||||||
|
|
||||||
if(this.$config.feature('logs')) {
|
if(this.$config.feature('logs')) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
'name': 'logs',
|
'name': 'library_logs',
|
||||||
'component': Logs,
|
'component': Logs,
|
||||||
'label': this.$gettext('Logs'),
|
'label': this.$gettext('Logs'),
|
||||||
'class': '',
|
'class': '',
|
||||||
'path': '/logs',
|
'path': '/logs',
|
||||||
'icon': 'grading',
|
'icon': 'assignment',
|
||||||
'readonly': true,
|
'readonly': true,
|
||||||
'demo': true,
|
'demo': true,
|
||||||
});
|
});
|
||||||
|
@ -102,7 +102,9 @@ export default {
|
||||||
|
|
||||||
let active = 0;
|
let active = 0;
|
||||||
|
|
||||||
if (typeof this.tab === 'string' && this.tab !== '') {
|
if (typeof this.$route.name === 'string' && this.$route.name !== '') {
|
||||||
|
active = tabs.findIndex((t) => t.name === this.$route.name);
|
||||||
|
} else if (typeof this.tab === 'string' && this.tab !== '') {
|
||||||
active = tabs.findIndex((t) => t.name === this.tab);
|
active = tabs.findIndex((t) => t.name === this.tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,6 +118,24 @@ export default {
|
||||||
rtl: this.$rtl,
|
rtl: this.$rtl,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'$route'() {
|
||||||
|
let active = this.active;
|
||||||
|
|
||||||
|
if (typeof this.$route.name === 'string' && this.$route.name !== '') {
|
||||||
|
active = this.tabs.findIndex((t) => t.name === this.$route.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active >= 0) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.tabs || this.tabs.length === 0) {
|
||||||
|
this.$router.push({ name: "albums" });
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
changePath: function (path) {
|
changePath: function (path) {
|
||||||
if (this.$route.path !== path) {
|
if (this.$route.path !== path) {
|
|
@ -4,6 +4,7 @@
|
||||||
v-model="active"
|
v-model="active"
|
||||||
flat
|
flat
|
||||||
grow
|
grow
|
||||||
|
touchless
|
||||||
color="secondary"
|
color="secondary"
|
||||||
slider-color="secondary-dark"
|
slider-color="secondary-dark"
|
||||||
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
:height="$vuetify.breakpoint.smAndDown ? 48 : 64"
|
||||||
|
@ -33,8 +34,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Recognized from "pages/people/recognized.vue";
|
import Recognized from "page/people/recognized.vue";
|
||||||
import NewFaces from "pages/people/new.vue";
|
import NewFaces from "page/people/new.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PPagePeople',
|
name: 'PPagePeople',
|
|
@ -112,7 +112,7 @@
|
||||||
</v-layout>
|
</v-layout>
|
||||||
<div class="text-xs-center mt-3 mb-2">
|
<div class="text-xs-center mt-3 mb-2">
|
||||||
<v-btn
|
<v-btn
|
||||||
color="secondary" round
|
color="secondary" round depressed
|
||||||
:to="{name: 'all', query: { q: 'face:new' }}"
|
:to="{name: 'all', query: { q: 'face:new' }}"
|
||||||
>
|
>
|
||||||
<translate>Show all new faces</translate>
|
<translate>Show all new faces</translate>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="$config.aclClasses('settings')" class="p-page p-page-settings">
|
<div class="p-page p-page-settings" :class="$config.aclClasses('settings')">
|
||||||
<v-tabs
|
<v-tabs
|
||||||
v-model="active"
|
v-model="active"
|
||||||
flat
|
flat
|
||||||
|
@ -28,11 +28,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import General from "pages/settings/general.vue";
|
import General from "page/settings/general.vue";
|
||||||
import Files from "pages/settings/files.vue";
|
import Library from "page/settings/library.vue";
|
||||||
import Advanced from "pages/settings/advanced.vue";
|
import Advanced from "page/settings/advanced.vue";
|
||||||
import Services from "pages/settings/services.vue";
|
import Services from "page/settings/services.vue";
|
||||||
import Account from "pages/settings/account.vue";
|
import Account from "page/settings/account.vue";
|
||||||
import {config} from "app/session";
|
import {config} from "app/session";
|
||||||
|
|
||||||
function initTabs(flag, tabs) {
|
function initTabs(flag, tabs) {
|
||||||
|
@ -60,7 +60,7 @@ export default {
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
'name': 'settings-general',
|
'name': 'settings_general',
|
||||||
'component': General,
|
'component': General,
|
||||||
'label': this.$gettext('General'),
|
'label': this.$gettext('General'),
|
||||||
'class': '',
|
'class': '',
|
||||||
|
@ -72,11 +72,11 @@ export default {
|
||||||
'show': config.feature('settings'),
|
'show': config.feature('settings'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'settings-files',
|
'name': 'settings_media',
|
||||||
'component': Files,
|
'component': Library,
|
||||||
'label': this.$gettext('Library'),
|
'label': this.$gettext('Library'),
|
||||||
'class': '',
|
'class': '',
|
||||||
'path': '/settings/files',
|
'path': '/settings/media',
|
||||||
'icon': 'camera_roll',
|
'icon': 'camera_roll',
|
||||||
'public': true,
|
'public': true,
|
||||||
'admin': true,
|
'admin': true,
|
||||||
|
@ -84,7 +84,7 @@ export default {
|
||||||
'show': config.allow("config", "manage"),
|
'show': config.allow("config", "manage"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'settings-advanced',
|
'name': 'settings_advanced',
|
||||||
'component': Advanced,
|
'component': Advanced,
|
||||||
'label': this.$gettext('Advanced'),
|
'label': this.$gettext('Advanced'),
|
||||||
'class': '',
|
'class': '',
|
||||||
|
@ -96,7 +96,7 @@ export default {
|
||||||
'show': config.allow("config", "manage"),
|
'show': config.allow("config", "manage"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'settings-services',
|
'name': 'settings_services',
|
||||||
'component': Services,
|
'component': Services,
|
||||||
'label': this.$gettext('Services'),
|
'label': this.$gettext('Services'),
|
||||||
'class': '',
|
'class': '',
|
||||||
|
@ -108,7 +108,7 @@ export default {
|
||||||
'show': config.feature('services') && config.allow("services", "manage"),
|
'show': config.feature('services') && config.allow("services", "manage"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'settings-account',
|
'name': 'settings_account',
|
||||||
'component': Account,
|
'component': Account,
|
||||||
'label': this.$gettext('Account'),
|
'label': this.$gettext('Account'),
|
||||||
'class': '',
|
'class': '',
|
||||||
|
@ -131,7 +131,9 @@ export default {
|
||||||
|
|
||||||
let active = 0;
|
let active = 0;
|
||||||
|
|
||||||
if (typeof this.tab === 'string' && this.tab !== '') {
|
if (typeof this.$route.name === 'string' && this.$route.name !== '') {
|
||||||
|
active = tabs.findIndex((t) => t.name === this.$route.name);
|
||||||
|
} else if (typeof this.tab === 'string' && this.tab !== '') {
|
||||||
active = tabs.findIndex((t) => t.name === this.tab);
|
active = tabs.findIndex((t) => t.name === this.tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +146,24 @@ export default {
|
||||||
rtl: this.$rtl,
|
rtl: this.$rtl,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'$route'() {
|
||||||
|
let active = this.active;
|
||||||
|
|
||||||
|
if (typeof this.$route.name === 'string' && this.$route.name !== '') {
|
||||||
|
active = this.tabs.findIndex((t) => t.name === this.$route.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active >= 0) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.tabs || this.tabs.length === 0) {
|
||||||
|
this.$router.push({ name: "albums" });
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
changePath: function (path) {
|
changePath: function (path) {
|
||||||
if (this.$route.path !== path) {
|
if (this.$route.path !== path) {
|
352
frontend/src/page/settings/account.vue
Normal file
352
frontend/src/page/settings/account.vue
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
<template>
|
||||||
|
<div class="p-tab p-settings-account">
|
||||||
|
<v-form ref="form" lazy-validation
|
||||||
|
dense class="p-form-account" accept-charset="UTF-8"
|
||||||
|
@submit.prevent="onChange">
|
||||||
|
<input ref="upload" type="file" class="d-none input-upload" @change.stop="onUploadAvatar()">
|
||||||
|
<v-card flat tile class="mt-2 px-1 application">
|
||||||
|
<v-card-actions>
|
||||||
|
<v-layout row wrap align-top>
|
||||||
|
<v-flex
|
||||||
|
class="p-photo pa-3 text-xs-center"
|
||||||
|
xs4 sm3 md2 xl1 fill-height
|
||||||
|
>
|
||||||
|
<div class="user-avatar" @click.exact="onChangeAvatar()">
|
||||||
|
<v-img :src="user.getAvatarURL()"
|
||||||
|
:alt="displayName" aspect-ratio="1"
|
||||||
|
class="grey lighten-1 elevation-0 clickable"
|
||||||
|
></v-img>
|
||||||
|
</div>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs8 sm9 md10 xl11 fill-height class="pa-0">
|
||||||
|
<v-layout wrap align-top>
|
||||||
|
<v-flex xs12 md3 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Name"
|
||||||
|
hide-details required box flat readonly
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Username')"
|
||||||
|
class="input-name"
|
||||||
|
color="secondary-dark"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 md9 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.DisplayName"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Display Name')"
|
||||||
|
class="input-display-name"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Email"
|
||||||
|
hide-details required box flat
|
||||||
|
type="email"
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Email')"
|
||||||
|
class="input-email"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<v-card flat tile class="mt-0 px-1 application">
|
||||||
|
<v-card-title primary-title class="pb-1">
|
||||||
|
<h3 class="body-2 mb-0">
|
||||||
|
<translate>Security and Access</translate>
|
||||||
|
</h3>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-layout wrap align-top>
|
||||||
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
|
<v-btn block depressed color="secondary-light" class="action-change-password compact" :disabled="isPublic || isDemo || user.Name === ''"
|
||||||
|
@click.stop="showDialog('password')">
|
||||||
|
<translate>Change Password</translate>
|
||||||
|
<v-icon :right="!rtl" :left="rtl" dark>lock</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
|
<v-btn block depressed color="secondary-light" class="action-webdav-dialog compact"
|
||||||
|
:disabled="isPublic || isDemo || !user.WebDAV" @click.stop="showDialog('webdav')">
|
||||||
|
<translate>Connect via WebDAV</translate>
|
||||||
|
<v-icon :right="!rtl" :left="rtl" dark>sync_alt</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<v-card flat tile class="mt-0 px-1 application">
|
||||||
|
<v-card-title primary-title class="pb-1">
|
||||||
|
<h3 class="body-2 mb-0">
|
||||||
|
<translate>Work Details</translate>
|
||||||
|
</h3>
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-layout wrap align-top>
|
||||||
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Details.OrgName"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Organization')"
|
||||||
|
class="input-org-name"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs6 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Details.OrgURL"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
type="url"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('URL')"
|
||||||
|
class="input-org-url"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs6 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Details.OrgTitle"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Title')"
|
||||||
|
class="input-position"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Details.OrgEmail"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
type="email"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Email')"
|
||||||
|
class="input-org-email"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<v-card flat tile class="mt-0 px-1 application">
|
||||||
|
<v-card-title primary-title class="pb-1">
|
||||||
|
<h3 class="body-2 mb-0">
|
||||||
|
<translate>Contact Details</translate>
|
||||||
|
</h3>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-layout wrap align-top>
|
||||||
|
<v-flex xs12 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Details.Location"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Location')"
|
||||||
|
class="input-location"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
|
<v-autocomplete
|
||||||
|
v-model="user.Details.Country"
|
||||||
|
:disabled="busy"
|
||||||
|
:label="$gettext('Country')" hide-details box flat
|
||||||
|
hide-no-data
|
||||||
|
browser-autocomplete="off"
|
||||||
|
color="secondary-dark"
|
||||||
|
item-value="Code"
|
||||||
|
item-text="Name"
|
||||||
|
:items="countries"
|
||||||
|
class="input-country"
|
||||||
|
@change="onChange"
|
||||||
|
>
|
||||||
|
</v-autocomplete>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Details.Phone"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Phone')"
|
||||||
|
class="input-phone"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Details.ProfileURL"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
type="url"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Profile')"
|
||||||
|
class="input-profile-url"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 sm6 class="pa-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="user.Details.FeedURL"
|
||||||
|
hide-details required box flat
|
||||||
|
:disabled="busy"
|
||||||
|
type="url"
|
||||||
|
browser-autocomplete="off"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:label="$gettext('Feed')"
|
||||||
|
class="input-feed-url"
|
||||||
|
color="secondary-dark"
|
||||||
|
@change="onChange"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 class="pa-2">
|
||||||
|
<v-textarea v-model="user.Details.Bio" auto-grow flat box hide-details
|
||||||
|
rows="3" class="input-bio" color="secondary-dark"
|
||||||
|
autocorrect="off" autocapitalize="none" browser-autocomplete="off"
|
||||||
|
:disabled="busy"
|
||||||
|
:label="$gettext('Bio')"
|
||||||
|
@change="onChange"></v-textarea>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-form>
|
||||||
|
<p-about-footer></p-about-footer>
|
||||||
|
<p-account-password-dialog :show="dialog.password" @cancel="dialog.password = false" @confirm="dialog.password = false"></p-account-password-dialog>
|
||||||
|
<p-webdav-dialog :show="dialog.webdav" @close="dialog.webdav = false"></p-webdav-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PAccountPasswordDialog from "dialog/account/password.vue";
|
||||||
|
import countries from "options/countries.json";
|
||||||
|
import Notify from "common/notify";
|
||||||
|
import Api from "common/api";
|
||||||
|
import User from "model/user";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PSettingsAccount',
|
||||||
|
components: {PAccountPasswordDialog},
|
||||||
|
data() {
|
||||||
|
const isDemo = this.$config.isDemo();
|
||||||
|
const isPublic = this.$config.isPublic();
|
||||||
|
return {
|
||||||
|
busy: isDemo || isPublic,
|
||||||
|
isDemo: isDemo,
|
||||||
|
isPublic: isPublic,
|
||||||
|
rtl: this.$rtl,
|
||||||
|
user: new User(this.$session.getUser()),
|
||||||
|
countries: countries,
|
||||||
|
dialog: {
|
||||||
|
password: false,
|
||||||
|
webdav: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if(this.isPublic && !this.isDemo) {
|
||||||
|
this.$router.push({ name: "settings" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayName() {
|
||||||
|
const user = this.$session.getUser();
|
||||||
|
if (user) {
|
||||||
|
return user.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.$gettext("Unregistered");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showDialog(name) {
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.dialog[name] = true;
|
||||||
|
},
|
||||||
|
disabled() {
|
||||||
|
return (this.isDemo || this.busy);
|
||||||
|
},
|
||||||
|
onChangeAvatar() {
|
||||||
|
if (this.busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$refs.upload.click();
|
||||||
|
},
|
||||||
|
onLogout() {
|
||||||
|
this.$session.logout();
|
||||||
|
},
|
||||||
|
onChange() {
|
||||||
|
if (this.busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.busy = true;
|
||||||
|
this.user.update().then((u) => {
|
||||||
|
this.user = new User(u);
|
||||||
|
this.$session.setUser(u);
|
||||||
|
this.$notify.success(this.$gettext("Settings saved"));
|
||||||
|
}).finally(() => this.busy = false);
|
||||||
|
},
|
||||||
|
onUploadAvatar() {
|
||||||
|
if (this.busy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.busy = true;
|
||||||
|
|
||||||
|
Notify.info(this.$gettext("Uploading…"));
|
||||||
|
|
||||||
|
this.user.uploadAvatar(this.$refs.upload.files).then((u) => {
|
||||||
|
this.user = new User(u);
|
||||||
|
this.$session.setUser(u);
|
||||||
|
this.$notify.success(this.$gettext("Settings saved"));
|
||||||
|
}).finally(() => this.busy = false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-tab p-settings-general">
|
<div class="p-tab p-settings-advanced">
|
||||||
<v-form ref="form" lazy-validation
|
<v-form ref="form" lazy-validation
|
||||||
dense class="p-form-settings" accept-charset="UTF-8"
|
dense class="p-form-settings" accept-charset="UTF-8"
|
||||||
@submit.prevent="onChange">
|
@submit.prevent="onChange">
|
||||||
|
@ -332,7 +332,7 @@ import ConfigOptions from "model/config-options";
|
||||||
import * as options from "options/options";
|
import * as options from "options/options";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PSettingsServer',
|
name: 'PSettingsAdvanced',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
busy: this.$config.get("demo"),
|
busy: this.$config.get("demo"),
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-tab p-settings-general">
|
<div class="p-tab p-settings-library">
|
||||||
<v-form ref="form" lazy-validation
|
<v-form ref="form" lazy-validation
|
||||||
dense class="p-form-settings" accept-charset="UTF-8"
|
dense class="p-form-settings" accept-charset="UTF-8"
|
||||||
@submit.prevent="onChange">
|
@submit.prevent="onChange">
|
||||||
|
@ -160,7 +160,7 @@ export default {
|
||||||
this.$config.load().then(() => {
|
this.$config.load().then(() => {
|
||||||
this.settings.setValues(this.$config.settings());
|
this.settings.setValues(this.$config.settings());
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
onChange() {
|
onChange() {
|
||||||
const reload = this.settings.changed("ui", "language");
|
const reload = this.settings.changed("ui", "language");
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="p-tab p-settings-sync">
|
<div class="p-tab p-settings-services">
|
||||||
<v-data-table
|
<v-data-table
|
||||||
v-model="selected"
|
v-model="selected"
|
||||||
:headers="listColumns"
|
:headers="listColumns"
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
disable-initial-sort
|
disable-initial-sort
|
||||||
class="elevation-0 p-accounts p-accounts-list p-results"
|
class="elevation-0 p-accounts p-accounts-list p-results"
|
||||||
item-key="ID"
|
item-key="ID"
|
||||||
:no-data-text="$gettext('No servers configured.')"
|
:no-data-text="$gettext('No services configured.')"
|
||||||
>
|
>
|
||||||
<template #items="props">
|
<template #items="props">
|
||||||
<td class="p-account">
|
<td class="p-account">
|
||||||
|
@ -64,27 +64,28 @@
|
||||||
dense class="p-form-settings mt-2" accept-charset="UTF-8"
|
dense class="p-form-settings mt-2" accept-charset="UTF-8"
|
||||||
@submit.prevent="add">
|
@submit.prevent="add">
|
||||||
|
|
||||||
<v-btn depressed color="secondary-light" class="action-webdav-dialog compact ml-0 my-2 mr-2"
|
<v-btn v-if="user.WebDAV" depressed color="secondary-light" class="action-webdav-dialog compact ml-0 my-2 mr-2"
|
||||||
:disabled="isPublic || isDemo" @click.stop="webdavDialog">
|
:disabled="isPublic || isDemo" @click.stop="webdavDialog">
|
||||||
<translate>Connect via WebDAV</translate>
|
<translate>Connect via WebDAV</translate>
|
||||||
|
<v-icon :right="rtl" :left="!rtl" dark>sync_alt</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
<v-btn color="primary-button"
|
<v-btn color="primary-button"
|
||||||
class="white--text compact ml-0 my-2 mr-2"
|
class="white--text compact ml-0 my-2 mr-2"
|
||||||
:disabled="isPublic || isDemo"
|
:disabled="isPublic || isDemo"
|
||||||
depressed @click.stop="add">
|
depressed @click.stop="add">
|
||||||
<translate>Add Server</translate>
|
<translate>Connect</translate>
|
||||||
<v-icon :right="!rtl" :left="rtl" dark>add</v-icon>
|
<v-icon :right="!rtl" :left="rtl" dark>add</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-container>
|
</v-container>
|
||||||
<p-account-add-dialog :show="dialog.add" @cancel="onCancel('add')"
|
<p-service-add-dialog :show="dialog.add" @cancel="onCancel('add')"
|
||||||
@confirm="onAdded"></p-account-add-dialog>
|
@confirm="onAdded"></p-service-add-dialog>
|
||||||
<p-account-remove-dialog :show="dialog.remove" :model="model" @cancel="onCancel('remove')"
|
<p-service-remove-dialog :show="dialog.remove" :model="model" @cancel="onCancel('remove')"
|
||||||
@confirm="onRemoved"></p-account-remove-dialog>
|
@confirm="onRemoved"></p-service-remove-dialog>
|
||||||
<p-account-edit-dialog :show="dialog.edit" :model="model" :scope="editScope" @remove="remove(model)"
|
<p-service-edit-dialog :show="dialog.edit" :model="model" :scope="editScope" @remove="remove(model)"
|
||||||
@cancel="onCancel('edit')"
|
@cancel="onCancel('edit')"
|
||||||
@confirm="onEdited"></p-account-edit-dialog>
|
@confirm="onEdited"></p-service-edit-dialog>
|
||||||
<p-webdav-dialog :show="dialog.webdav" @close="dialog.webdav = false"></p-webdav-dialog>
|
<p-webdav-dialog :show="dialog.webdav" @close="dialog.webdav = false"></p-webdav-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -95,7 +96,7 @@ import Service from "model/service";
|
||||||
import {DateTime} from "luxon";
|
import {DateTime} from "luxon";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PSettingsSync',
|
name: 'PSettingsServices',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isDemo: this.$config.get("demo"),
|
isDemo: this.$config.get("demo"),
|
||||||
|
@ -107,6 +108,7 @@ export default {
|
||||||
results: [],
|
results: [],
|
||||||
labels: {},
|
labels: {},
|
||||||
selected: [],
|
selected: [],
|
||||||
|
user: this.$session.getUser(),
|
||||||
dialog: {
|
dialog: {
|
||||||
add: false,
|
add: false,
|
||||||
remove: false,
|
remove: false,
|
||||||
|
@ -114,7 +116,7 @@ export default {
|
||||||
},
|
},
|
||||||
editScope: "main",
|
editScope: "main",
|
||||||
listColumns: [
|
listColumns: [
|
||||||
{text: this.$gettext('Server'), value: 'AccName', sortable: false, align: 'left'},
|
{text: this.$gettext('Name'), value: 'AccName', sortable: false, align: 'left'},
|
||||||
{text: this.$gettext('Upload'), value: 'AccShare', sortable: false, align: 'center'},
|
{text: this.$gettext('Upload'), value: 'AccShare', sortable: false, align: 'center'},
|
||||||
{text: this.$gettext('Sync'), value: 'AccSync', sortable: false, align: 'center'},
|
{text: this.$gettext('Sync'), value: 'AccSync', sortable: false, align: 'center'},
|
||||||
{
|
{
|
|
@ -1,114 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="p-tab p-settings-account">
|
|
||||||
<v-form ref="form" dense class="form-password" accept-charset="UTF-8">
|
|
||||||
<v-card flat tile class="ma-2 application">
|
|
||||||
<v-card-actions>
|
|
||||||
<v-layout wrap align-top>
|
|
||||||
<v-flex xs12 class="pa-2">
|
|
||||||
<v-text-field
|
|
||||||
v-model="oldPassword"
|
|
||||||
hide-details required
|
|
||||||
type="password"
|
|
||||||
:disabled="busy"
|
|
||||||
browser-autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
autocapitalize="none"
|
|
||||||
:label="$gettext('Current Password')"
|
|
||||||
class="input-current-password"
|
|
||||||
color="secondary-dark"
|
|
||||||
placeholder="••••••••"
|
|
||||||
></v-text-field>
|
|
||||||
</v-flex>
|
|
||||||
|
|
||||||
<v-flex xs12 class="pa-2">
|
|
||||||
<v-text-field
|
|
||||||
v-model="newPassword"
|
|
||||||
required counter persistent-hint
|
|
||||||
type="password"
|
|
||||||
:disabled="busy"
|
|
||||||
browser-autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
autocapitalize="none"
|
|
||||||
:label="$gettext('New Password')"
|
|
||||||
class="input-new-password"
|
|
||||||
color="secondary-dark"
|
|
||||||
placeholder="••••••••"
|
|
||||||
:hint="$gettext('Must have at least 8 characters.')"
|
|
||||||
></v-text-field>
|
|
||||||
</v-flex>
|
|
||||||
|
|
||||||
<v-flex xs12 class="pa-2">
|
|
||||||
<v-text-field
|
|
||||||
v-model="confirmPassword"
|
|
||||||
required counter persistent-hint
|
|
||||||
type="password"
|
|
||||||
:disabled="busy"
|
|
||||||
browser-autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
autocapitalize="none"
|
|
||||||
:label="$gettext('Retype Password')"
|
|
||||||
class="input-retype-password"
|
|
||||||
color="secondary-dark"
|
|
||||||
placeholder="••••••••"
|
|
||||||
:hint="$gettext('Please confirm your new password.')"
|
|
||||||
@keyup.enter.native="confirm"
|
|
||||||
></v-text-field>
|
|
||||||
</v-flex>
|
|
||||||
|
|
||||||
<v-flex xs12 class="pa-2">
|
|
||||||
<p class="caption pa-0">
|
|
||||||
<translate>Note: Updating your password will invalidate other browser sessions, so you will have to log in again.</translate>
|
|
||||||
</p>
|
|
||||||
</v-flex>
|
|
||||||
|
|
||||||
<v-flex xs12 class="pa-2">
|
|
||||||
<v-btn depressed color="primary-button"
|
|
||||||
:disabled="disabled()"
|
|
||||||
class="action-confirm compact white--text ma-0"
|
|
||||||
@click.stop="confirm">
|
|
||||||
<translate>Change</translate>
|
|
||||||
<v-icon :right="!rtl" :left="rtl" dark>keyboard_return</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-flex>
|
|
||||||
</v-layout>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-form>
|
|
||||||
|
|
||||||
<p-about-footer></p-about-footer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PSettingsAccount',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
busy: false,
|
|
||||||
isDemo: this.$config.get("demo"),
|
|
||||||
isPublic: this.$config.get("public"),
|
|
||||||
oldPassword: "",
|
|
||||||
newPassword: "",
|
|
||||||
confirmPassword: "",
|
|
||||||
rtl: this.$rtl,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if(this.isPublic && !this.isDemo) {
|
|
||||||
this.$router.push({ name: "settings" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
disabled() {
|
|
||||||
return (this.isDemo || this.busy || this.oldPassword === "" || this.newPassword.length < 8 || (this.newPassword !== this.confirmPassword));
|
|
||||||
},
|
|
||||||
confirm() {
|
|
||||||
this.busy = true;
|
|
||||||
this.$session.getUser().changePassword(this.oldPassword, this.newPassword).then(() => {
|
|
||||||
this.$notify.success(this.$gettext("Password changed"));
|
|
||||||
}).finally(() => this.busy = false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -188,14 +188,14 @@ func UpdateService(router *gin.RouterGroup) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Update form with values from request
|
// 2) Update form with values from request
|
||||||
if err := c.BindJSON(&f); err != nil {
|
if err = c.BindJSON(&f); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
AbortBadRequest(c)
|
AbortBadRequest(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Save model with values from form
|
// 3) Save model with values from form
|
||||||
if err := m.SaveForm(f); err != nil {
|
if err = m.SaveForm(f); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
AbortSaveFailed(c)
|
AbortSaveFailed(c)
|
||||||
return
|
return
|
||||||
|
|
|
@ -6,6 +6,15 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var userIconSvg = []byte(`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M11.1 35.25q3.15-2.2 6.25-3.375Q20.45 30.7 24 30.7q3.55 0 6.675 1.175t6.275 3.375q2.2-2.7 3.125-5.45Q41 27.05 41 24q0-7.25-4.875-12.125T24 7q-7.25 0-12.125 4.875T7 24q0 3.05.95 5.8t3.15 5.45ZM24 25.5q-2.9 0-4.875-1.975T17.15 18.65q0-2.9 1.975-4.875T24 11.8q2.9 0 4.875 1.975t1.975 4.875q0 2.9-1.975 4.875T24 25.5ZM24 44q-4.1 0-7.75-1.575-3.65-1.575-6.375-4.3-2.725-2.725-4.3-6.375Q4 28.1 4 24q0-4.15 1.575-7.775t4.3-6.35q2.725-2.725 6.375-4.3Q19.9 4 24 4q4.15 0 7.775 1.575t6.35 4.3q2.725 2.725 4.3 6.35Q44 19.85 44 24q0 4.1-1.575 7.75-1.575 3.65-4.3 6.375-2.725 2.725-6.35 4.3Q28.15 44 24 44Zm0-3q2.75 0 5.375-.8t5.175-2.8q-2.55-1.8-5.2-2.75-2.65-.95-5.35-.95-2.7 0-5.35.95-2.65.95-5.2 2.75 2.55 2 5.175 2.8Q21.25 41 24 41Zm0-18.5q1.7 0 2.775-1.075t1.075-2.775q0-1.7-1.075-2.775T24 14.8q-1.7 0-2.775 1.075T20.15 18.65q0 1.7 1.075 2.775T24 22.5Zm0-3.85Zm0 18.7Z"/></svg>`)
|
||||||
|
|
||||||
|
var faceIconSvg = []byte(`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M31.3 21.35q1.15 0 1.925-.8.775-.8.775-1.9 0-1.15-.775-1.925-.775-.775-1.925-.775-1.1 0-1.9.775-.8.775-.8 1.925 0 1.1.8 1.9.8.8 1.9.8Zm-14.6 0q1.15 0 1.925-.8.775-.8.775-1.9 0-1.15-.775-1.925-.775-.775-1.925-.775-1.1 0-1.9.775-.8.775-.8 1.925 0 1.1.8 1.9.8.8 1.9.8Zm7.3 13.6q3.3 0 6.075-1.775Q32.85 31.4 34.1 28.35h-2.6q-1.15 2-3.15 3.075-2 1.075-4.3 1.075-2.35 0-4.375-1.05t-3.125-3.1H13.9q1.3 3.05 4.05 4.825Q20.7 34.95 24 34.95ZM24 44q-4.15 0-7.8-1.575-3.65-1.575-6.35-4.275-2.7-2.7-4.275-6.35Q4 28.15 4 24t1.575-7.8Q7.15 12.55 9.85 9.85q2.7-2.7 6.35-4.275Q19.85 4 24 4t7.8 1.575q3.65 1.575 6.35 4.275 2.7 2.7 4.275 6.35Q44 19.85 44 24t-1.575 7.8q-1.575 3.65-4.275 6.35-2.7 2.7-6.35 4.275Q28.15 44 24 44Zm0-20Zm0 17q7.1 0 12.05-4.95Q41 31.1 41 24q0-7.1-4.95-12.05Q31.1 7 24 7q-7.1 0-12.05 4.95Q7 16.9 7 24q0 7.1 4.95 12.05Q16.9 41 24 41Z"/></svg>`)
|
||||||
|
|
||||||
|
var cameraIconSvg = []byte(`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M24 34.7q3.6 0 6.05-2.45 2.45-2.45 2.45-6.05 0-3.65-2.45-6.075Q27.6 17.7 24 17.7q-3.65 0-6.075 2.425Q15.5 22.55 15.5 26.2q0 3.6 2.425 6.05Q20.35 34.7 24 34.7ZM7 42q-1.2 0-2.1-.9Q4 40.2 4 39V13.35q0-1.15.9-2.075.9-.925 2.1-.925h7.35L18 6h12l3.65 4.35H41q1.15 0 2.075.925Q44 12.2 44 13.35V39q0 1.2-.925 2.1-.925.9-2.075.9Z"/></svg>`)
|
||||||
|
|
||||||
var photoIconSvg = []byte(`
|
var photoIconSvg = []byte(`
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/>
|
||||||
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86l-3 3.87L9 13.14 6 17h12l-3.86-5.14z"/></svg>`)
|
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86l-3 3.87L9 13.14 6 17h12l-3.86-5.14z"/></svg>`)
|
||||||
|
@ -41,8 +50,22 @@ var uncachedIconSvg = []byte(`
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/>
|
||||||
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>`)
|
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>`)
|
||||||
|
|
||||||
|
// GetSvg returns SVG placeholder symbols.
|
||||||
|
//
|
||||||
// GET /api/v1/svg/*
|
// GET /api/v1/svg/*
|
||||||
func GetSvg(router *gin.RouterGroup) {
|
func GetSvg(router *gin.RouterGroup) {
|
||||||
|
router.GET("/svg/user", func(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "image/svg+xml", userIconSvg)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/svg/face", func(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "image/svg+xml", faceIconSvg)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/svg/camera", func(c *gin.Context) {
|
||||||
|
c.Data(http.StatusOK, "image/svg+xml", cameraIconSvg)
|
||||||
|
})
|
||||||
|
|
||||||
router.GET("/svg/photo", func(c *gin.Context) {
|
router.GET("/svg/photo", func(c *gin.Context) {
|
||||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/photoprism/photoprism/internal/acl"
|
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
|
||||||
"github.com/photoprism/photoprism/internal/get"
|
|
||||||
"github.com/photoprism/photoprism/internal/i18n"
|
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Upload adds files to the import folder, from where supported file types are moved to the originals folders.
|
|
||||||
//
|
|
||||||
// POST /api/v1/upload/:path
|
|
||||||
func Upload(router *gin.RouterGroup) {
|
|
||||||
router.POST("/upload/:token", func(c *gin.Context) {
|
|
||||||
conf := get.Config()
|
|
||||||
|
|
||||||
if conf.ReadOnly() || !conf.Settings().Features.Upload {
|
|
||||||
Abort(c, http.StatusForbidden, i18n.ErrReadOnly)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s := AuthAny(c, acl.ResourceFiles, acl.Permissions{acl.ActionManage, acl.ActionUpload})
|
|
||||||
|
|
||||||
if s.Abort(c) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
token := clean.Token(c.Param("token"))
|
|
||||||
|
|
||||||
f, err := c.MultipartForm()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("upload: %s", err)
|
|
||||||
AbortBadRequest(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
event.Publish("upload.start", event.Data{"time": start})
|
|
||||||
|
|
||||||
files := f.File["files"]
|
|
||||||
uploaded := len(files)
|
|
||||||
|
|
||||||
var uploads []string
|
|
||||||
|
|
||||||
uploadDir := path.Join(conf.ImportPath(), UploadPath, s.RefID+token)
|
|
||||||
|
|
||||||
if err = os.MkdirAll(uploadDir, os.ModePerm); err != nil {
|
|
||||||
log.Errorf("upload: failed creating folder %s", clean.Log(filepath.Base(uploadDir)))
|
|
||||||
AbortBadRequest(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
filename := path.Join(uploadDir, filepath.Base(file.Filename))
|
|
||||||
|
|
||||||
log.Debugf("upload: saving file %s", clean.Log(file.Filename))
|
|
||||||
|
|
||||||
if err := c.SaveUploadedFile(file, filename); err != nil {
|
|
||||||
log.Errorf("upload: failed saving file %s", clean.Log(filepath.Base(file.Filename)))
|
|
||||||
AbortBadRequest(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uploads = append(uploads, filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !conf.UploadNSFW() {
|
|
||||||
nd := get.NsfwDetector()
|
|
||||||
|
|
||||||
containsNSFW := false
|
|
||||||
|
|
||||||
for _, filename := range uploads {
|
|
||||||
labels, err := nd.File(filename)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Debug(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if labels.IsSafe() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("nsfw: %s might be offensive", clean.Log(filename))
|
|
||||||
|
|
||||||
containsNSFW = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if containsNSFW {
|
|
||||||
for _, filename := range uploads {
|
|
||||||
if err := os.Remove(filename); err != nil {
|
|
||||||
log.Errorf("nsfw: could not delete %s", clean.Log(filename))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Abort(c, http.StatusForbidden, i18n.ErrOffensiveUpload)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
elapsed := int(time.Since(start).Seconds())
|
|
||||||
|
|
||||||
msg := i18n.Msg(i18n.MsgFilesUploadedIn, uploaded, elapsed)
|
|
||||||
|
|
||||||
log.Info(msg)
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, i18n.Response{Code: http.StatusOK, Msg: msg})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUpload(t *testing.T) {
|
|
||||||
t.Run("forbidden", func(t *testing.T) {
|
|
||||||
app, router, _ := NewApiTest()
|
|
||||||
Upload(router)
|
|
||||||
r := PerformRequest(app, "POST", "/api/v1/upload/xxx")
|
|
||||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
|
||||||
})
|
|
||||||
}
|
|
119
internal/api/users_avatar.go
Normal file
119
internal/api/users_avatar.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/gabriel-vasile/mimetype"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
"github.com/photoprism/photoprism/internal/photoprism"
|
||||||
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/acl"
|
||||||
|
"github.com/photoprism/photoprism/internal/get"
|
||||||
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UploadUserAvatar updates the avatar image of the currently authenticated user.
|
||||||
|
//
|
||||||
|
// POST /api/v1/users/:uid/avatar
|
||||||
|
func UploadUserAvatar(router *gin.RouterGroup) {
|
||||||
|
router.POST("/users/:uid/avatar", func(c *gin.Context) {
|
||||||
|
conf := get.Config()
|
||||||
|
|
||||||
|
if conf.Demo() || conf.DisableSettings() {
|
||||||
|
AbortForbidden(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := AuthAny(c, acl.ResourceUsers, acl.Permissions{acl.ActionManage, acl.AccessOwn})
|
||||||
|
|
||||||
|
if s.Abort(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := clean.UID(c.Param("uid"))
|
||||||
|
|
||||||
|
// Users may only change their own avatar.
|
||||||
|
if s.User().UserUID != uid {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "user uid does not match"}, s.RefID)
|
||||||
|
AbortForbidden(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := c.MultipartForm()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "%s"}, s.RefID, err)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
files := f.File["files"]
|
||||||
|
|
||||||
|
if len(files) != 1 {
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadDir, err := conf.UserUploadPath(s.UserUID, "")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "failed to create folder", "%s"}, s.RefID, err)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file := files[0]
|
||||||
|
|
||||||
|
// Uploaded images must be JPEGs with a maximum file size of 10 MB.
|
||||||
|
if file.Size > 10000000 {
|
||||||
|
event.AuditWarn([]string{ClientIP(c), "session %s", "upload avatar", "file size exceeded"}, s.RefID)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrFileTooLarge)
|
||||||
|
return
|
||||||
|
} else if fReader, fErr := file.Open(); fErr != nil {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "%s"}, s.RefID, err)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
} else if mimeType, mimeErr := mimetype.DetectReader(fReader); mimeErr != nil {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "%s"}, s.RefID, err)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
} else if !mimeType.Is(fs.MimeTypeJpeg) {
|
||||||
|
event.AuditWarn([]string{ClientIP(c), "session %s", "upload avatar", "only jpeg supported"}, s.RefID)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrWrongFileType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := "avatar.jpg"
|
||||||
|
filePath := path.Join(uploadDir, fileName)
|
||||||
|
|
||||||
|
if err = c.SaveUploadedFile(file, filePath); err != nil {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "failed to save %s"}, s.RefID, clean.Log(filePath))
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
event.AuditInfo([]string{ClientIP(c), "session %s", "upload avatar", "saved as %s"}, s.RefID, clean.Log(filePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
if mediaFile, mediaErr := photoprism.NewMediaFile(filePath); mediaErr != nil {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "%s"}, s.RefID, err)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrWrongFileType)
|
||||||
|
return
|
||||||
|
} else if err = mediaFile.CreateThumbnails(conf.ThumbCachePath(), false); err != nil {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "%s"}, s.RefID, err)
|
||||||
|
} else if err = s.User().SetAvatar(mediaFile.Hash(), entity.SrcManual); err != nil {
|
||||||
|
event.AuditErr([]string{ClientIP(c), "session %s", "upload avatar", "%s"}, s.RefID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the session cache, as it contains user information.
|
||||||
|
s.ClearCache()
|
||||||
|
|
||||||
|
log.Info(i18n.Msg(i18n.MsgFileUploaded))
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, entity.FindUserByUID(uid))
|
||||||
|
})
|
||||||
|
}
|
22
internal/api/users_avatar_test.go
Normal file
22
internal/api/users_avatar_test.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUploadUserAvatar(t *testing.T) {
|
||||||
|
t.Run("Forbidden", func(t *testing.T) {
|
||||||
|
app, router, _ := NewApiTest()
|
||||||
|
adminUid := entity.Admin.UserUID
|
||||||
|
reqUrl := fmt.Sprintf("/api/v1/users/%s/avatar", adminUid)
|
||||||
|
UploadUserAvatar(router)
|
||||||
|
r := PerformRequestWithBody(app, "POST", reqUrl, "{foo:123}")
|
||||||
|
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||||
|
})
|
||||||
|
}
|
|
@ -15,10 +15,10 @@ import (
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChangePassword changes the password of the currently authenticated user.
|
// UpdateUserPassword changes the password of the currently authenticated user.
|
||||||
//
|
//
|
||||||
// PUT /api/v1/users/:uid/password
|
// PUT /api/v1/users/:uid/password
|
||||||
func ChangePassword(router *gin.RouterGroup) {
|
func UpdateUserPassword(router *gin.RouterGroup) {
|
||||||
router.PUT("/users/:uid/password", func(c *gin.Context) {
|
router.PUT("/users/:uid/password", func(c *gin.Context) {
|
||||||
conf := get.Config()
|
conf := get.Config()
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
func TestChangePassword(t *testing.T) {
|
func TestChangePassword(t *testing.T) {
|
||||||
t.Run("NonExistentUser", func(t *testing.T) {
|
t.Run("NonExistentUser", func(t *testing.T) {
|
||||||
app, router, _ := NewApiTest()
|
app, router, _ := NewApiTest()
|
||||||
ChangePassword(router)
|
UpdateUserPassword(router)
|
||||||
r := PerformRequestWithBody(app, "PUT", "/api/v1/users/xxx/password", `{}`)
|
r := PerformRequestWithBody(app, "PUT", "/api/v1/users/xxx/password", `{}`)
|
||||||
assert.Equal(t, http.StatusForbidden, r.Code)
|
assert.Equal(t, http.StatusForbidden, r.Code)
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,7 @@ func TestChangePassword(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
conf.SetAuthMode(config.AuthModePasswd)
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
defer conf.SetAuthMode(config.AuthModePublic)
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
ChangePassword(router)
|
UpdateUserPassword(router)
|
||||||
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
|
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
|
||||||
|
|
||||||
f := form.ChangePassword{
|
f := form.ChangePassword{
|
||||||
|
@ -43,7 +43,7 @@ func TestChangePassword(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
conf.SetAuthMode(config.AuthModePasswd)
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
defer conf.SetAuthMode(config.AuthModePublic)
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
ChangePassword(router)
|
UpdateUserPassword(router)
|
||||||
|
|
||||||
oldPassword := "PleaseChange$42"
|
oldPassword := "PleaseChange$42"
|
||||||
newPassword := "SoftwareDevelopmentIsAYoungProfession1234567890!@#$%^&*()_+[]{}|:<>?/.,"
|
newPassword := "SoftwareDevelopmentIsAYoungProfession1234567890!@#$%^&*()_+[]{}|:<>?/.,"
|
||||||
|
@ -81,7 +81,7 @@ func TestChangePassword(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
conf.SetAuthMode(config.AuthModePasswd)
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
defer conf.SetAuthMode(config.AuthModePublic)
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
ChangePassword(router)
|
UpdateUserPassword(router)
|
||||||
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
|
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
|
||||||
|
|
||||||
f := form.ChangePassword{
|
f := form.ChangePassword{
|
||||||
|
@ -101,7 +101,7 @@ func TestChangePassword(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
conf.SetAuthMode(config.AuthModePasswd)
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
defer conf.SetAuthMode(config.AuthModePublic)
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
ChangePassword(router)
|
UpdateUserPassword(router)
|
||||||
sessId := AuthenticateUser(app, router, "bob", "Bobbob123!")
|
sessId := AuthenticateUser(app, router, "bob", "Bobbob123!")
|
||||||
|
|
||||||
f := form.ChangePassword{
|
f := form.ChangePassword{
|
||||||
|
@ -121,7 +121,7 @@ func TestChangePassword(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
conf.SetAuthMode(config.AuthModePasswd)
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
defer conf.SetAuthMode(config.AuthModePublic)
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
ChangePassword(router)
|
UpdateUserPassword(router)
|
||||||
sessId := AuthenticateUser(app, router, "friend", "!Friend321")
|
sessId := AuthenticateUser(app, router, "friend", "!Friend321")
|
||||||
|
|
||||||
f := form.ChangePassword{
|
f := form.ChangePassword{
|
||||||
|
@ -141,7 +141,7 @@ func TestChangePassword(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
conf.SetAuthMode(config.AuthModePasswd)
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
defer conf.SetAuthMode(config.AuthModePublic)
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
ChangePassword(router)
|
UpdateUserPassword(router)
|
||||||
sessId := AuthenticateUser(app, router, "bob", "Bobbob123!")
|
sessId := AuthenticateUser(app, router, "bob", "Bobbob123!")
|
||||||
|
|
||||||
f := form.ChangePassword{
|
f := form.ChangePassword{
|
82
internal/api/users_update.go
Normal file
82
internal/api/users_update.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/acl"
|
||||||
|
"github.com/photoprism/photoprism/internal/get"
|
||||||
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateUser updates the profile information of the currently authenticated user.
|
||||||
|
//
|
||||||
|
// PUT /api/v1/users/:uid
|
||||||
|
func UpdateUser(router *gin.RouterGroup) {
|
||||||
|
router.PUT("/users/:uid", func(c *gin.Context) {
|
||||||
|
conf := get.Config()
|
||||||
|
|
||||||
|
if conf.Demo() || conf.DisableSettings() {
|
||||||
|
AbortForbidden(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := AuthAny(c, acl.ResourceUsers, acl.Permissions{acl.ActionManage, acl.AccessOwn, acl.ActionUpdate})
|
||||||
|
|
||||||
|
if s.Abort(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := clean.UID(c.Param("uid"))
|
||||||
|
|
||||||
|
m := entity.FindUserByUID(uid)
|
||||||
|
|
||||||
|
if m == nil {
|
||||||
|
Abort(c, http.StatusNotFound, i18n.ErrUserNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Init form with model values
|
||||||
|
f, err := form.NewUser(m)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
AbortSaveFailed(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Update form with values from request
|
||||||
|
if err = c.BindJSON(&f); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
AbortBadRequest(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Save model with values from form
|
||||||
|
if err = m.SaveForm(f); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
AbortSaveFailed(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the session cache, as it contains user information.
|
||||||
|
s.ClearCache()
|
||||||
|
|
||||||
|
event.SuccessMsg(i18n.MsgChangesSaved)
|
||||||
|
|
||||||
|
m = entity.FindUserByUID(uid)
|
||||||
|
|
||||||
|
if m == nil {
|
||||||
|
AbortEntityNotFound(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, m)
|
||||||
|
})
|
||||||
|
}
|
36
internal/api/users_update_test.go
Normal file
36
internal/api/users_update_test.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateUser(t *testing.T) {
|
||||||
|
t.Run("Alice", func(t *testing.T) {
|
||||||
|
app, router, conf := NewApiTest()
|
||||||
|
conf.SetAuthMode(config.AuthModePasswd)
|
||||||
|
defer conf.SetAuthMode(config.AuthModePublic)
|
||||||
|
UpdateUser(router)
|
||||||
|
sessId := AuthenticateUser(app, router, "alice", "Alice123!")
|
||||||
|
adminUid := entity.Admin.UserUID
|
||||||
|
reqUrl := fmt.Sprintf("/api/v1/users/%s", adminUid)
|
||||||
|
t.Logf("Request URL: %s", reqUrl)
|
||||||
|
r := AuthenticatedRequestWithBody(app, "PUT", reqUrl, "{Email:\"admin@example.com\",Details:{Location:\"WebStorm\"}}", sessId)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Forbidden", func(t *testing.T) {
|
||||||
|
app, router, _ := NewApiTest()
|
||||||
|
adminUid := entity.Admin.UserUID
|
||||||
|
reqUrl := fmt.Sprintf("/api/v1/users/%s", adminUid)
|
||||||
|
UpdateUser(router)
|
||||||
|
r := PerformRequestWithBody(app, "PUT", reqUrl, "{foo:123}")
|
||||||
|
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||||
|
})
|
||||||
|
}
|
245
internal/api/users_upload.go
Normal file
245
internal/api/users_upload.go
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize/english"
|
||||||
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
|
"github.com/photoprism/photoprism/internal/photoprism"
|
||||||
|
"github.com/photoprism/photoprism/internal/query"
|
||||||
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/photoprism/photoprism/internal/acl"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
"github.com/photoprism/photoprism/internal/get"
|
||||||
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UploadUserFiles adds files to the user upload folder, from where they can be moved and indexed.
|
||||||
|
//
|
||||||
|
// POST /users/:uid/upload/:token
|
||||||
|
func UploadUserFiles(router *gin.RouterGroup) {
|
||||||
|
router.POST("/users/:uid/upload/:token", func(c *gin.Context) {
|
||||||
|
conf := get.Config()
|
||||||
|
|
||||||
|
if conf.ReadOnly() || !conf.Settings().Features.Upload {
|
||||||
|
Abort(c, http.StatusForbidden, i18n.ErrReadOnly)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := AuthAny(c, acl.ResourceFiles, acl.Permissions{acl.ActionManage, acl.ActionUpload})
|
||||||
|
|
||||||
|
if s.Abort(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users may only upload their own files.
|
||||||
|
if s.User().UserUID != clean.UID(c.Param("uid")) {
|
||||||
|
AbortForbidden(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
token := clean.Token(c.Param("token"))
|
||||||
|
|
||||||
|
f, err := c.MultipartForm()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("upload: %s", err)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Publish("upload.start", event.Data{"time": start})
|
||||||
|
|
||||||
|
files := f.File["files"]
|
||||||
|
uploaded := len(files)
|
||||||
|
|
||||||
|
var uploads []string
|
||||||
|
|
||||||
|
uploadDir, err := conf.UserUploadPath(s.UserUID, s.RefID+token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("upload: failed to create storage folder (%s)", err)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
filename := path.Join(uploadDir, filepath.Base(file.Filename))
|
||||||
|
|
||||||
|
log.Debugf("upload: saving file %s", clean.Log(file.Filename))
|
||||||
|
|
||||||
|
if err := c.SaveUploadedFile(file, filename); err != nil {
|
||||||
|
log.Errorf("upload: failed saving file %s", clean.Log(filepath.Base(file.Filename)))
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploads = append(uploads, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !conf.UploadNSFW() {
|
||||||
|
nd := get.NsfwDetector()
|
||||||
|
|
||||||
|
containsNSFW := false
|
||||||
|
|
||||||
|
for _, filename := range uploads {
|
||||||
|
labels, err := nd.File(filename)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if labels.IsSafe() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("nsfw: %s might be offensive", clean.Log(filename))
|
||||||
|
|
||||||
|
containsNSFW = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if containsNSFW {
|
||||||
|
for _, filename := range uploads {
|
||||||
|
if err := os.Remove(filename); err != nil {
|
||||||
|
log.Errorf("nsfw: could not delete %s", clean.Log(filename))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Abort(c, http.StatusForbidden, i18n.ErrOffensiveUpload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := int(time.Since(start).Seconds())
|
||||||
|
|
||||||
|
msg := i18n.Msg(i18n.MsgFilesUploadedIn, uploaded, elapsed)
|
||||||
|
|
||||||
|
log.Info(msg)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, i18n.Response{Code: http.StatusOK, Msg: msg})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessUserUpload triggers processing once all files have been uploaded.
|
||||||
|
//
|
||||||
|
// PUT /users/:uid/upload/:token
|
||||||
|
func ProcessUserUpload(router *gin.RouterGroup) {
|
||||||
|
router.PUT("/users/:uid/upload/:token", func(c *gin.Context) {
|
||||||
|
s := AuthAny(c, acl.ResourceFiles, acl.Permissions{acl.ActionManage, acl.ActionUpload})
|
||||||
|
|
||||||
|
if s.Abort(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Users may only upload their own files.
|
||||||
|
if s.User().UserUID != clean.UID(c.Param("uid")) {
|
||||||
|
AbortForbidden(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := get.Config()
|
||||||
|
|
||||||
|
if conf.ReadOnly() || !conf.Settings().Features.Import {
|
||||||
|
AbortFeatureDisabled(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
var f form.UploadOptions
|
||||||
|
|
||||||
|
if err := c.BindJSON(&f); err != nil {
|
||||||
|
AbortBadRequest(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := clean.Token(c.Param("token"))
|
||||||
|
uploadPath, err := conf.UserUploadPath(s.UserUID, s.RefID+token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("upload: failed to create storage folder (%s)", err)
|
||||||
|
Abort(c, http.StatusBadRequest, i18n.ErrUploadFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
imp := get.Import()
|
||||||
|
|
||||||
|
var destFolder string
|
||||||
|
if destFolder = s.User().UploadPath; destFolder == "" {
|
||||||
|
destFolder = conf.ImportDest()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move uploaded files to the destination folder.
|
||||||
|
event.InfoMsg(i18n.MsgProcessingUpload)
|
||||||
|
opt := photoprism.ImportOptionsUpload(uploadPath, destFolder)
|
||||||
|
|
||||||
|
// Add imported files to albums if allowed.
|
||||||
|
if len(f.Albums) > 0 &&
|
||||||
|
acl.Resources.AllowAny(acl.ResourceAlbums, s.User().AclRole(), acl.Permissions{acl.ActionCreate, acl.ActionUpload}) {
|
||||||
|
log.Debugf("upload: adding files to album %s", clean.Log(strings.Join(f.Albums, " and ")))
|
||||||
|
opt.Albums = f.Albums
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set user UID if known.
|
||||||
|
if s.UserUID != "" {
|
||||||
|
opt.UserUID = s.UserUID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start import.
|
||||||
|
imported := imp.Start(opt)
|
||||||
|
|
||||||
|
// Delete empty import directory.
|
||||||
|
if fs.DirIsEmpty(uploadPath) {
|
||||||
|
if err := os.Remove(uploadPath); err != nil {
|
||||||
|
log.Errorf("upload: failed deleting empty folder %s: %s", clean.Log(uploadPath), err)
|
||||||
|
} else {
|
||||||
|
log.Infof("upload: deleted empty folder %s", clean.Log(uploadPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update moments if files have been imported.
|
||||||
|
if n := len(imported); n == 0 {
|
||||||
|
log.Infof("upload: no new files imported", clean.Log(uploadPath))
|
||||||
|
} else {
|
||||||
|
log.Infof("upload: imported %s", english.Plural(n, "file", "files"))
|
||||||
|
if moments := get.Moments(); moments == nil {
|
||||||
|
log.Warnf("upload: moments service not set - possible bug")
|
||||||
|
} else if err := moments.Start(); err != nil {
|
||||||
|
log.Warnf("moments: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := int(time.Since(start).Seconds())
|
||||||
|
|
||||||
|
// Show success message.
|
||||||
|
msg := i18n.Msg(i18n.MsgUploadProcessed)
|
||||||
|
|
||||||
|
event.Success(msg)
|
||||||
|
event.Publish("import.completed", event.Data{"path": uploadPath, "seconds": elapsed})
|
||||||
|
event.Publish("index.completed", event.Data{"path": uploadPath, "seconds": elapsed})
|
||||||
|
|
||||||
|
for _, uid := range f.Albums {
|
||||||
|
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the user interface.
|
||||||
|
UpdateClientConfig()
|
||||||
|
|
||||||
|
// Update album, label, and subject cover thumbs.
|
||||||
|
if err := query.UpdateCovers(); err != nil {
|
||||||
|
log.Warnf("upload: %s (update covers)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, i18n.Response{Code: http.StatusOK, Msg: msg})
|
||||||
|
})
|
||||||
|
}
|
23
internal/api/users_upload_test.go
Normal file
23
internal/api/users_upload_test.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUploadUserFiles(t *testing.T) {
|
||||||
|
t.Run("Forbidden", func(t *testing.T) {
|
||||||
|
app, router, _ := NewApiTest()
|
||||||
|
adminUid := entity.Admin.UserUID
|
||||||
|
reqUrl := fmt.Sprintf("/api/v1/users/%s/upload/abc123456789", adminUid)
|
||||||
|
// t.Logf("Request URL: %s", reqUrl)
|
||||||
|
UploadUserFiles(router)
|
||||||
|
r := PerformRequestWithBody(app, "POST", reqUrl, "{foo:123}")
|
||||||
|
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||||
|
})
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
)
|
)
|
||||||
|
@ -87,6 +89,18 @@ func (c *Config) CreateDirectories() error {
|
||||||
return createError(c.StoragePath(), err)
|
return createError(c.StoragePath(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.FilesPath() == "" {
|
||||||
|
return notFoundError("files")
|
||||||
|
} else if err := os.MkdirAll(c.FilesPath(), os.ModePerm); err != nil {
|
||||||
|
return createError(c.FilesPath(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UsersPath() == "" {
|
||||||
|
return notFoundError("users")
|
||||||
|
} else if err := os.MkdirAll(c.UsersPath(), os.ModePerm); err != nil {
|
||||||
|
return createError(c.UsersPath(), err)
|
||||||
|
}
|
||||||
|
|
||||||
if c.CmdCachePath() == "" {
|
if c.CmdCachePath() == "" {
|
||||||
return notFoundError("cmd cache")
|
return notFoundError("cmd cache")
|
||||||
} else if err := os.MkdirAll(c.CmdCachePath(), os.ModePerm); err != nil {
|
} else if err := os.MkdirAll(c.CmdCachePath(), os.ModePerm); err != nil {
|
||||||
|
@ -296,6 +310,72 @@ func (c *Config) SidecarWritable() bool {
|
||||||
return !c.ReadOnly() || c.SidecarPathIsAbs()
|
return !c.ReadOnly() || c.SidecarPathIsAbs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilesPath returns the storage base path for files that should not be indexed.
|
||||||
|
func (c *Config) FilesPath() string {
|
||||||
|
// Set default.
|
||||||
|
if c.options.FilesPath == "" {
|
||||||
|
c.options.FilesPath = filepath.Join(c.StoragePath(), "files")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.options.FilesPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilePath returns the file storage path based on the hash provided.
|
||||||
|
func (c *Config) FilePath(fileHash string) string {
|
||||||
|
if !rnd.IsHex(fileHash) || len(fileHash) < 4 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Join(c.FilesPath(), fileHash[0:1], fileHash[1:2], fileHash[2:3])
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(dir, fileHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsersPath returns the storage base path for user assets like
|
||||||
|
// avatar images and other media that should not be indexed.
|
||||||
|
func (c *Config) UsersPath() string {
|
||||||
|
// Set default.
|
||||||
|
if c.options.UsersPath == "" {
|
||||||
|
c.options.UsersPath = filepath.Join(c.StoragePath(), "users")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.options.UsersPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserPath returns the storage path for user assets.
|
||||||
|
func (c *Config) UserPath(userUid string) string {
|
||||||
|
if !rnd.IsUID(userUid, 0) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Join(c.UsersPath(), userUid)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserUploadPath returns the upload path for the specified user.
|
||||||
|
func (c *Config) UserUploadPath(userUid, token string) (string, error) {
|
||||||
|
if !rnd.IsUID(userUid, 0) {
|
||||||
|
return "", fmt.Errorf("invalid uid")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Join(c.UserPath(userUid), "upload", clean.Token(token))
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TempPath returns the cached temporary directory name e.g. for uploads and downloads.
|
// TempPath returns the cached temporary directory name e.g. for uploads and downloads.
|
||||||
func (c *Config) TempPath() string {
|
func (c *Config) TempPath() string {
|
||||||
// Return cached value?
|
// Return cached value?
|
||||||
|
|
|
@ -23,6 +23,53 @@ func TestConfig_SidecarPath(t *testing.T) {
|
||||||
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/sidecar", c.SidecarPath())
|
assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/sidecar", c.SidecarPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfig_FilePath(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
t.Run("Valid", func(t *testing.T) {
|
||||||
|
s := c.FilePath("c476503628b4543c9ef97d69a6daa700b05d19bc")
|
||||||
|
assert.True(t, strings.HasSuffix(s, "/c/4/7/c476503628b4543c9ef97d69a6daa700b05d19bc"))
|
||||||
|
})
|
||||||
|
t.Run("InvalidHash", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "", c.FilePath("YE"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_UsersPath(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
assert.Contains(t, c.UsersPath(), "testdata/users")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_UserPath(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
assert.Equal(t, "", c.UserPath(""))
|
||||||
|
assert.Equal(t, "", c.UserPath("etaetyget"))
|
||||||
|
assert.Contains(t, c.UserPath("urjult03ceelhw6k"), "testdata/users/urjult03ceelhw6k")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_UserUploadPath(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
if dir, err := c.UserUploadPath("", ""); err == nil {
|
||||||
|
t.Error("error expected")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, "", dir)
|
||||||
|
}
|
||||||
|
if dir, err := c.UserUploadPath("etaetyget", ""); err == nil {
|
||||||
|
t.Error("error expected")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, "", dir)
|
||||||
|
}
|
||||||
|
if dir, err := c.UserUploadPath("urjult03ceelhw6k", ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
assert.Contains(t, dir, "testdata/users/urjult03ceelhw6k/upload")
|
||||||
|
}
|
||||||
|
if dir, err := c.UserUploadPath("urjult03ceelhw6k", "foo"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
assert.Contains(t, dir, "testdata/users/urjult03ceelhw6k/upload/foo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfig_SidecarPathIsAbs(t *testing.T) {
|
func TestConfig_SidecarPathIsAbs(t *testing.T) {
|
||||||
c := NewConfig(CliTestContext())
|
c := NewConfig(CliTestContext())
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,11 @@ var Flags = CliFlags{
|
||||||
Usage: "custom relative or absolute sidecar `PATH` *optional*",
|
Usage: "custom relative or absolute sidecar `PATH` *optional*",
|
||||||
EnvVar: "PHOTOPRISM_SIDECAR_PATH",
|
EnvVar: "PHOTOPRISM_SIDECAR_PATH",
|
||||||
}}, {
|
}}, {
|
||||||
|
Flag: cli.StringFlag{
|
||||||
|
Name: "users-path",
|
||||||
|
Usage: "custom users storage `PATH` *optional*",
|
||||||
|
EnvVar: "PHOTOPRISM_USERS_PATH",
|
||||||
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "backup-path, ba",
|
Name: "backup-path, ba",
|
||||||
Usage: "custom backup `PATH` for index backup files *optional*",
|
Usage: "custom backup `PATH` for index backup files *optional*",
|
||||||
|
|
|
@ -45,6 +45,8 @@ type Options struct {
|
||||||
ResolutionLimit int `yaml:"ResolutionLimit" json:"ResolutionLimit" flag:"resolution-limit"`
|
ResolutionLimit int `yaml:"ResolutionLimit" json:"ResolutionLimit" flag:"resolution-limit"`
|
||||||
StoragePath string `yaml:"StoragePath" json:"-" flag:"storage-path"`
|
StoragePath string `yaml:"StoragePath" json:"-" flag:"storage-path"`
|
||||||
SidecarPath string `yaml:"SidecarPath" json:"-" flag:"sidecar-path"`
|
SidecarPath string `yaml:"SidecarPath" json:"-" flag:"sidecar-path"`
|
||||||
|
FilesPath string `yaml:"FilesPath" json:"-" flag:"files-path"`
|
||||||
|
UsersPath string `yaml:"UsersPath" json:"-" flag:"users-path"`
|
||||||
BackupPath string `yaml:"BackupPath" json:"-" flag:"backup-path"`
|
BackupPath string `yaml:"BackupPath" json:"-" flag:"backup-path"`
|
||||||
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
|
CachePath string `yaml:"CachePath" json:"-" flag:"cache-path"`
|
||||||
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
|
ImportPath string `yaml:"ImportPath" json:"-" flag:"import-path"`
|
||||||
|
|
|
@ -48,6 +48,8 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
||||||
// Other paths.
|
// Other paths.
|
||||||
{"storage-path", c.StoragePath()},
|
{"storage-path", c.StoragePath()},
|
||||||
{"sidecar-path", c.SidecarPath()},
|
{"sidecar-path", c.SidecarPath()},
|
||||||
|
{"files-path", c.FilesPath()},
|
||||||
|
{"users-path", c.UsersPath()},
|
||||||
{"albums-path", c.AlbumsPath()},
|
{"albums-path", c.AlbumsPath()},
|
||||||
{"backup-path", c.BackupPath()},
|
{"backup-path", c.BackupPath()},
|
||||||
{"cache-path", c.CachePath()},
|
{"cache-path", c.CachePath()},
|
||||||
|
|
|
@ -158,6 +158,11 @@ func (m *Session) Cache() {
|
||||||
m.CacheDuration(sessionCacheExpiration)
|
m.CacheDuration(sessionCacheExpiration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearCache deletes the session from the cache.
|
||||||
|
func (m *Session) ClearCache() {
|
||||||
|
DeleteFromSessionCache(m.ID)
|
||||||
|
}
|
||||||
|
|
||||||
// Create new entity in the database.
|
// Create new entity in the database.
|
||||||
func (m *Session) Create() (err error) {
|
func (m *Session) Create() (err error) {
|
||||||
if err = Db().Create(m).Error; err == nil && rnd.IsSessionID(m.ID) {
|
if err = Db().Create(m).Error; err == nil && rnd.IsSessionID(m.ID) {
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ulule/deepcopier"
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/acl"
|
"github.com/photoprism/photoprism/internal/acl"
|
||||||
|
@ -66,8 +68,8 @@ type User struct {
|
||||||
ResetToken string `gorm:"type:VARBINARY(64);" json:"-" yaml:"-"`
|
ResetToken string `gorm:"type:VARBINARY(64);" json:"-" yaml:"-"`
|
||||||
PreviewToken string `gorm:"type:VARBINARY(64);column:preview_token;" json:"-" yaml:"-"`
|
PreviewToken string `gorm:"type:VARBINARY(64);column:preview_token;" json:"-" yaml:"-"`
|
||||||
DownloadToken string `gorm:"type:VARBINARY(64);column:download_token;" json:"-" yaml:"-"`
|
DownloadToken string `gorm:"type:VARBINARY(64);column:download_token;" json:"-" yaml:"-"`
|
||||||
Thumb string `gorm:"type:VARBINARY(128);index;default:'';" json:"Thumb,omitempty" yaml:"Thumb,omitempty"`
|
Thumb string `gorm:"type:VARBINARY(128);index;default:'';" json:"Thumb" yaml:"Thumb,omitempty"`
|
||||||
ThumbSrc string `gorm:"type:VARBINARY(8);default:'';" json:"ThumbSrc,omitempty" yaml:"ThumbSrc,omitempty"`
|
ThumbSrc string `gorm:"type:VARBINARY(8);default:'';" json:"ThumbSrc" yaml:"ThumbSrc,omitempty"`
|
||||||
RefID string `gorm:"type:VARBINARY(16);" json:"-" yaml:"-"`
|
RefID string `gorm:"type:VARBINARY(16);" json:"-" yaml:"-"`
|
||||||
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
||||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
||||||
|
@ -764,3 +766,42 @@ func (m *User) RedeemToken(token string) (n int) {
|
||||||
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveForm updates the entity using form data and stores it in the database.
|
||||||
|
func (m *User) SaveForm(f form.User) error {
|
||||||
|
if m.UserName == "" || m.ID <= 0 {
|
||||||
|
return fmt.Errorf("system users cannot be updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deepcopier.Copy(m.UserDetails).From(f.UserDetails); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := clean.Name(f.DisplayName); n != "" && n != m.DisplayName {
|
||||||
|
m.DisplayName = n
|
||||||
|
}
|
||||||
|
|
||||||
|
if email := clean.Email(f.UserEmail); email != "" && email != m.UserEmail {
|
||||||
|
m.UserEmail = email
|
||||||
|
m.VerifiedAt = nil
|
||||||
|
m.VerifyToken = GenerateToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAvatar updates the user avatar image.
|
||||||
|
func (m *User) SetAvatar(thumb, thumbSrc string) error {
|
||||||
|
if m.UserName == "" || m.ID <= 0 {
|
||||||
|
return fmt.Errorf("system user avatars cannot be changed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if SrcPriority[thumbSrc] < SrcPriority[m.ThumbSrc] && m.Thumb != "" {
|
||||||
|
return fmt.Errorf("no permission to change avatar")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Thumb = thumb
|
||||||
|
m.ThumbSrc = thumbSrc
|
||||||
|
|
||||||
|
return m.Updates(Values{"Thumb": m.Thumb, "ThumbSrc": m.ThumbSrc})
|
||||||
|
}
|
||||||
|
|
|
@ -16,34 +16,36 @@ const (
|
||||||
|
|
||||||
// UserDetails represents user profile information.
|
// UserDetails represents user profile information.
|
||||||
type UserDetails struct {
|
type UserDetails struct {
|
||||||
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"-" yaml:"UserUID"`
|
UserUID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"-" yaml:"-"`
|
||||||
SubjUID string `gorm:"type:VARBINARY(42);index;" json:"SubjUID,omitempty" yaml:"SubjUID,omitempty"`
|
|
||||||
SubjSrc string `gorm:"type:VARBINARY(8);default:'';" json:"SubjSrc,omitempty" yaml:"SubjSrc,omitempty"`
|
|
||||||
PlaceID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"PlaceID,omitempty" yaml:"-"`
|
|
||||||
PlaceSrc string `gorm:"type:VARBINARY(8);" json:"PlaceSrc,omitempty" yaml:"PlaceSrc,omitempty"`
|
|
||||||
CellID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"CellID,omitempty" yaml:"CellID,omitempty"`
|
|
||||||
IdURL string `gorm:"type:VARBINARY(512);column:id_url;" json:"IdURL,omitempty" yaml:"IdURL,omitempty"`
|
IdURL string `gorm:"type:VARBINARY(512);column:id_url;" json:"IdURL,omitempty" yaml:"IdURL,omitempty"`
|
||||||
AvatarURL string `gorm:"type:VARBINARY(512);column:avatar_url" json:"AvatarURL,omitempty" yaml:"AvatarURL,omitempty"`
|
SubjUID string `gorm:"type:VARBINARY(42);index;" json:"SubjUID,omitempty" yaml:"SubjUID,omitempty"`
|
||||||
SiteURL string `gorm:"type:VARBINARY(512);column:site_url" json:"SiteURL,omitempty" yaml:"SiteURL,omitempty"`
|
SubjSrc string `gorm:"type:VARBINARY(8);default:'';" json:"-" yaml:"SubjSrc,omitempty"`
|
||||||
|
PlaceID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"-" yaml:"-"`
|
||||||
|
PlaceSrc string `gorm:"type:VARBINARY(8);" json:"-" yaml:"PlaceSrc,omitempty"`
|
||||||
|
CellID string `gorm:"type:VARBINARY(42);index;default:'zz'" json:"-" yaml:"CellID,omitempty"`
|
||||||
|
BirthYear int `gorm:"default:-1;" json:"BirthYear" yaml:"BirthYear,omitempty"`
|
||||||
|
BirthMonth int `gorm:"default:-1;" json:"BirthMonth" yaml:"BirthMonth,omitempty"`
|
||||||
|
BirthDay int `gorm:"default:-1;" json:"BirthDay" yaml:"BirthDay,omitempty"`
|
||||||
|
NamePrefix string `gorm:"size:32;" json:"NamePrefix" yaml:"NamePrefix,omitempty"`
|
||||||
|
GivenName string `gorm:"size:64;" json:"GivenName" yaml:"GivenName,omitempty"`
|
||||||
|
MiddleName string `gorm:"size:64;" json:"MiddleName" yaml:"MiddleName,omitempty"`
|
||||||
|
FamilyName string `gorm:"size:64;" json:"FamilyName" yaml:"FamilyName,omitempty"`
|
||||||
|
NameSuffix string `gorm:"size:32;" json:"NameSuffix" yaml:"NameSuffix,omitempty"`
|
||||||
|
NickName string `gorm:"size:64;" json:"NickName" yaml:"NickName,omitempty"`
|
||||||
|
UserGender string `gorm:"size:16;" json:"Gender" yaml:"Gender,omitempty"`
|
||||||
|
UserBio string `gorm:"size:512;" json:"Bio" yaml:"Bio,omitempty"`
|
||||||
|
UserLocation string `gorm:"size:512;" json:"Location" yaml:"Location,omitempty"`
|
||||||
|
UserCountry string `gorm:"type:VARBINARY(2);" json:"Country" yaml:"Country,omitempty"`
|
||||||
|
UserPhone string `gorm:"size:32;" json:"Phone" yaml:"Phone,omitempty"`
|
||||||
|
SiteURL string `gorm:"type:VARBINARY(512);column:site_url" json:"SiteURL" yaml:"SiteURL,omitempty"`
|
||||||
|
ProfileURL string `gorm:"type:VARBINARY(512);column:profile_url" json:"ProfileURL" yaml:"ProfileURL,omitempty"`
|
||||||
FeedURL string `gorm:"type:VARBINARY(512);column:feed_url" json:"FeedURL,omitempty" yaml:"FeedURL,omitempty"`
|
FeedURL string `gorm:"type:VARBINARY(512);column:feed_url" json:"FeedURL,omitempty" yaml:"FeedURL,omitempty"`
|
||||||
UserGender string `gorm:"size:16;" json:"Gender,omitempty" yaml:"Gender,omitempty"`
|
AvatarURL string `gorm:"type:VARBINARY(512);column:avatar_url" json:"AvatarURL,omitempty" yaml:"AvatarURL,omitempty"`
|
||||||
NamePrefix string `gorm:"size:32;" json:"NamePrefix,omitempty" yaml:"NamePrefix,omitempty"`
|
OrgName string `gorm:"size:128;" json:"OrgName" yaml:"OrgName,omitempty"`
|
||||||
GivenName string `gorm:"size:64;" json:"GivenName,omitempty" yaml:"GivenName,omitempty"`
|
OrgTitle string `gorm:"size:64;" json:"OrgTitle" yaml:"OrgTitle,omitempty"`
|
||||||
MiddleName string `gorm:"size:64;" json:"MiddleName,omitempty" yaml:"MiddleName,omitempty"`
|
OrgEmail string `gorm:"size:255;index;" json:"OrgEmail" yaml:"OrgEmail,omitempty"`
|
||||||
FamilyName string `gorm:"size:64;" json:"FamilyName,omitempty" yaml:"FamilyName,omitempty"`
|
OrgPhone string `gorm:"size:32;" json:"OrgPhone" yaml:"OrgPhone,omitempty"`
|
||||||
NameSuffix string `gorm:"size:32;" json:"NameSuffix,omitempty" yaml:"NameSuffix,omitempty"`
|
OrgURL string `gorm:"type:VARBINARY(512);column:org_url" json:"OrgURL" yaml:"OrgURL,omitempty"`
|
||||||
NickName string `gorm:"size:64;" json:"NickName,omitempty" yaml:"NickName,omitempty"`
|
|
||||||
UserPhone string `gorm:"size:32;" json:"Phone,omitempty" yaml:"Phone,omitempty"`
|
|
||||||
UserAddress string `gorm:"size:512;" json:"Address,omitempty" yaml:"Address,omitempty"`
|
|
||||||
UserCountry string `gorm:"type:VARBINARY(2);" json:"Country,omitempty" yaml:"Country,omitempty"`
|
|
||||||
UserBio string `gorm:"size:512;" json:"Bio,omitempty" yaml:"Bio,omitempty"`
|
|
||||||
JobTitle string `gorm:"size:64;" json:"JobTitle,omitempty" yaml:"JobTitle,omitempty"`
|
|
||||||
Department string `gorm:"size:128;" json:"Department,omitempty" yaml:"Department,omitempty"`
|
|
||||||
Company string `gorm:"size:128;" json:"Company,omitempty" yaml:"Company,omitempty"`
|
|
||||||
CompanyURL string `gorm:"type:VARBINARY(512);column:company_url" json:"CompanyURL,omitempty" yaml:"CompanyURL,omitempty"`
|
|
||||||
BirthYear int `gorm:"default:-1;" json:"BirthYear,omitempty" yaml:"BirthYear,omitempty"`
|
|
||||||
BirthMonth int `gorm:"default:-1;" json:"BirthMonth,omitempty" yaml:"BirthMonth,omitempty"`
|
|
||||||
BirthDay int `gorm:"default:-1;" json:"BirthDay,omitempty" yaml:"BirthDay,omitempty"`
|
|
||||||
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
|
||||||
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -735,3 +735,49 @@ func TestUser_SharedUIDs(t *testing.T) {
|
||||||
assert.Equal(t, UIDs{"at9lxuqxpogaaba9"}, result)
|
assert.Equal(t, UIDs{"at9lxuqxpogaaba9"}, result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUser_SaveForm(t *testing.T) {
|
||||||
|
t.Run("UnknownUser", func(t *testing.T) {
|
||||||
|
frm, err := form.NewUser(UnknownUser)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = UnknownUser.SaveForm(frm)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("Admin", func(t *testing.T) {
|
||||||
|
frm, err := form.NewUser(Admin)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
frm.UserEmail = "admin@example.com"
|
||||||
|
frm.UserDetails.UserLocation = "GoLand"
|
||||||
|
err = Admin.SaveForm(frm)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "admin@example.com", Admin.UserEmail)
|
||||||
|
assert.Equal(t, "GoLand", Admin.Details().UserLocation)
|
||||||
|
|
||||||
|
m := FindUserByUID(Admin.UserUID)
|
||||||
|
assert.Equal(t, "admin@example.com", m.UserEmail)
|
||||||
|
assert.Equal(t, "GoLand", m.Details().UserLocation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUser_SetAvatar(t *testing.T) {
|
||||||
|
t.Run("Visitor", func(t *testing.T) {
|
||||||
|
err := Visitor.SetAvatar("ebfc0aea7d3fd018b5fff57c76806b35181855ed", SrcManual)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("UnknownUser", func(t *testing.T) {
|
||||||
|
err := UnknownUser.SetAvatar("ebfc0aea7d3fd018b5fff57c76806b35181855ed", SrcManual)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
t.Run("Admin", func(t *testing.T) {
|
||||||
|
err := Admin.SetAvatar("ebfc0aea7d3fd018b5fff57c76806b35181855ed", SrcManual)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "ebfc0aea7d3fd018b5fff57c76806b35181855ed", Admin.Thumb)
|
||||||
|
assert.Equal(t, SrcManual, Admin.ThumbSrc)
|
||||||
|
|
||||||
|
m := FindUserByUID(Admin.UserUID)
|
||||||
|
assert.Equal(t, "ebfc0aea7d3fd018b5fff57c76806b35181855ed", m.Thumb)
|
||||||
|
assert.Equal(t, SrcManual, m.ThumbSrc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -104,10 +104,10 @@ class auth_users_details {
|
||||||
varbinary(42) place_id
|
varbinary(42) place_id
|
||||||
varbinary(8) place_src
|
varbinary(8) place_src
|
||||||
varbinary(42) cell_id
|
varbinary(42) cell_id
|
||||||
varbinary(512) id_url
|
varchar(512) user_bio
|
||||||
varbinary(512) avatar_url
|
varchar(32) user_phone
|
||||||
varbinary(512) site_url
|
varchar(512) user_location
|
||||||
varbinary(512) feed_url
|
varbinary(2) user_country
|
||||||
varchar(16) user_gender
|
varchar(16) user_gender
|
||||||
varchar(32) name_prefix
|
varchar(32) name_prefix
|
||||||
varchar(64) given_name
|
varchar(64) given_name
|
||||||
|
@ -115,14 +115,13 @@ class auth_users_details {
|
||||||
varchar(64) family_name
|
varchar(64) family_name
|
||||||
varchar(32) name_suffix
|
varchar(32) name_suffix
|
||||||
varchar(64) nick_name
|
varchar(64) nick_name
|
||||||
varchar(32) user_phone
|
varchar(64) org_title
|
||||||
varchar(512) user_address
|
varchar(128) org_name
|
||||||
varbinary(2) user_country
|
varbinary(512) org_url
|
||||||
varchar(512) user_bio
|
varbinary(512) id_url
|
||||||
varchar(64) job_title
|
varbinary(512) site_url
|
||||||
varchar(128) department
|
varbinary(512) feed_url
|
||||||
varchar(128) company
|
varbinary(512) avatar_url
|
||||||
varbinary(512) company_url
|
|
||||||
int(11) birth_year
|
int(11) birth_year
|
||||||
int(11) birth_month
|
int(11) birth_month
|
||||||
int(11) birth_day
|
int(11) birth_day
|
||||||
|
|
|
@ -158,10 +158,10 @@ CREATE TABLE `auth_users_details` (
|
||||||
`place_id` varbinary(42) DEFAULT 'zz',
|
`place_id` varbinary(42) DEFAULT 'zz',
|
||||||
`place_src` varbinary(8) DEFAULT NULL,
|
`place_src` varbinary(8) DEFAULT NULL,
|
||||||
`cell_id` varbinary(42) DEFAULT 'zz',
|
`cell_id` varbinary(42) DEFAULT 'zz',
|
||||||
`id_url` varbinary(512) DEFAULT NULL,
|
`user_bio` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`avatar_url` varbinary(512) DEFAULT NULL,
|
`user_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`site_url` varbinary(512) DEFAULT NULL,
|
`user_location` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`feed_url` varbinary(512) DEFAULT NULL,
|
`user_country` varbinary(2) DEFAULT NULL,
|
||||||
`user_gender` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`user_gender` varchar(16) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`name_prefix` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`name_prefix` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`given_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`given_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
|
@ -169,14 +169,13 @@ CREATE TABLE `auth_users_details` (
|
||||||
`family_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`family_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`name_suffix` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`name_suffix` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`nick_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`nick_name` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`user_phone` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`org_title` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`user_address` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`org_name` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||||
`user_country` varbinary(2) DEFAULT NULL,
|
`org_url` varbinary(512) DEFAULT NULL,
|
||||||
`user_bio` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`id_url` varbinary(512) DEFAULT NULL,
|
||||||
`job_title` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`site_url` varbinary(512) DEFAULT NULL,
|
||||||
`department` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`feed_url` varbinary(512) DEFAULT NULL,
|
||||||
`company` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
`avatar_url` varbinary(512) DEFAULT NULL,
|
||||||
`company_url` varbinary(512) DEFAULT NULL,
|
|
||||||
`birth_year` int(11) DEFAULT -1,
|
`birth_year` int(11) DEFAULT -1,
|
||||||
`birth_month` int(11) DEFAULT -1,
|
`birth_month` int(11) DEFAULT -1,
|
||||||
`birth_day` int(11) DEFAULT -1,
|
`birth_day` int(11) DEFAULT -1,
|
||||||
|
|
|
@ -18,6 +18,7 @@ const (
|
||||||
SrcMeta = "meta" // Prio 16
|
SrcMeta = "meta" // Prio 16
|
||||||
SrcXmp = "xmp" // Prio 32
|
SrcXmp = "xmp" // Prio 32
|
||||||
SrcManual = "manual" // Prio 64
|
SrcManual = "manual" // Prio 64
|
||||||
|
SrcAdmin = "admin" // Prio 128
|
||||||
)
|
)
|
||||||
|
|
||||||
// SrcString returns a source string for logging.
|
// SrcString returns a source string for logging.
|
||||||
|
@ -43,4 +44,5 @@ var SrcPriority = Priorities{
|
||||||
SrcMeta: 16,
|
SrcMeta: 16,
|
||||||
SrcXmp: 32,
|
SrcXmp: 32,
|
||||||
SrcManual: 64,
|
SrcManual: 64,
|
||||||
|
SrcAdmin: 128,
|
||||||
}
|
}
|
||||||
|
|
5
internal/form/upload_options.go
Normal file
5
internal/form/upload_options.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package form
|
||||||
|
|
||||||
|
type UploadOptions struct {
|
||||||
|
Albums []string `json:"albums"`
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package form
|
package form
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/ulule/deepcopier"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
@ -19,6 +20,13 @@ type User struct {
|
||||||
BasePath string `json:"BasePath,omitempty" yaml:"BasePath,omitempty"`
|
BasePath string `json:"BasePath,omitempty" yaml:"BasePath,omitempty"`
|
||||||
UploadPath string `json:"UploadPath,omitempty" yaml:"UploadPath,omitempty"`
|
UploadPath string `json:"UploadPath,omitempty" yaml:"UploadPath,omitempty"`
|
||||||
Password string `json:"Password,omitempty" yaml:"Password,omitempty"`
|
Password string `json:"Password,omitempty" yaml:"Password,omitempty"`
|
||||||
|
UserDetails UserDetails `json:"Details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUser creates a new user account form.
|
||||||
|
func NewUser(m interface{}) (f User, err error) {
|
||||||
|
err = deepcopier.Copy(m).To(&f)
|
||||||
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserFromCli creates a new form with values from a CLI context.
|
// NewUserFromCli creates a new form with values from a CLI context.
|
||||||
|
|
27
internal/form/user_details.go
Normal file
27
internal/form/user_details.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package form
|
||||||
|
|
||||||
|
// UserDetails represents a user details form.
|
||||||
|
type UserDetails struct {
|
||||||
|
BirthYear int `json:"BirthYear"`
|
||||||
|
BirthMonth int `json:"BirthMonth"`
|
||||||
|
BirthDay int `json:"BirthDay"`
|
||||||
|
NamePrefix string `json:"NamePrefix"`
|
||||||
|
GivenName string `json:"GivenName"`
|
||||||
|
MiddleName string `json:"MiddleName"`
|
||||||
|
FamilyName string `json:"FamilyName"`
|
||||||
|
NameSuffix string `json:"NameSuffix"`
|
||||||
|
NickName string `json:"NickName"`
|
||||||
|
UserGender string `json:"Gender"`
|
||||||
|
UserBio string `json:"Bio"`
|
||||||
|
UserLocation string `json:"Location"`
|
||||||
|
UserCountry string `json:"Country"`
|
||||||
|
UserPhone string `json:"Phone"`
|
||||||
|
SiteURL string `json:"SiteURL"`
|
||||||
|
ProfileURL string `json:"ProfileURL"`
|
||||||
|
FeedURL string `json:"FeedURL"`
|
||||||
|
OrgName string `json:"OrgName"`
|
||||||
|
OrgTitle string `json:"OrgTitle"`
|
||||||
|
OrgEmail string `json:"OrgEmail"`
|
||||||
|
OrgPhone string `json:"OrgPhone"`
|
||||||
|
OrgURL string `json:"OrgURL"`
|
||||||
|
}
|
|
@ -14,6 +14,19 @@ func TestUser(t *testing.T) {
|
||||||
assert.Equal(t, "passwd", form.Password)
|
assert.Equal(t, "passwd", form.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewUser(t *testing.T) {
|
||||||
|
val := &User{UserName: "foobar", UserEmail: "test@test.com", Password: "passwd"}
|
||||||
|
form, err := NewUser(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "foobar", form.UserName)
|
||||||
|
assert.Equal(t, "test@test.com", form.UserEmail)
|
||||||
|
assert.Equal(t, "passwd", form.Password)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUser_Name(t *testing.T) {
|
func TestUser_Name(t *testing.T) {
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("Empty", func(t *testing.T) {
|
||||||
form := &User{UserName: "", UserEmail: "test@test.com", Password: "passwd"}
|
form := &User{UserName: "", UserEmail: "test@test.com", Password: "passwd"}
|
||||||
|
|
|
@ -8,6 +8,8 @@ const (
|
||||||
ErrAlreadyExists
|
ErrAlreadyExists
|
||||||
ErrNotFound
|
ErrNotFound
|
||||||
ErrFileNotFound
|
ErrFileNotFound
|
||||||
|
ErrFileTooLarge
|
||||||
|
ErrWrongFileType
|
||||||
ErrOriginalsEmpty
|
ErrOriginalsEmpty
|
||||||
ErrSelectionNotFound
|
ErrSelectionNotFound
|
||||||
ErrEntityNotFound
|
ErrEntityNotFound
|
||||||
|
@ -23,6 +25,7 @@ const (
|
||||||
ErrUnauthorized
|
ErrUnauthorized
|
||||||
ErrForbidden
|
ErrForbidden
|
||||||
ErrOffensiveUpload
|
ErrOffensiveUpload
|
||||||
|
ErrUploadFailed
|
||||||
ErrNoItemsSelected
|
ErrNoItemsSelected
|
||||||
ErrCreateFile
|
ErrCreateFile
|
||||||
ErrCreateFolder
|
ErrCreateFolder
|
||||||
|
@ -72,7 +75,10 @@ const (
|
||||||
MsgSubjectDeleted
|
MsgSubjectDeleted
|
||||||
MsgPersonSaved
|
MsgPersonSaved
|
||||||
MsgPersonDeleted
|
MsgPersonDeleted
|
||||||
|
MsgFileUploaded
|
||||||
MsgFilesUploadedIn
|
MsgFilesUploadedIn
|
||||||
|
MsgProcessingUpload
|
||||||
|
MsgUploadProcessed
|
||||||
MsgSelectionApproved
|
MsgSelectionApproved
|
||||||
MsgSelectionArchived
|
MsgSelectionArchived
|
||||||
MsgSelectionRestored
|
MsgSelectionRestored
|
||||||
|
@ -92,6 +98,8 @@ var Messages = MessageMap{
|
||||||
ErrAlreadyExists: gettext("%s already exists"),
|
ErrAlreadyExists: gettext("%s already exists"),
|
||||||
ErrNotFound: gettext("Not found"),
|
ErrNotFound: gettext("Not found"),
|
||||||
ErrFileNotFound: gettext("File not found"),
|
ErrFileNotFound: gettext("File not found"),
|
||||||
|
ErrFileTooLarge: gettext("File too large"),
|
||||||
|
ErrWrongFileType: gettext("Wrong file type"),
|
||||||
ErrOriginalsEmpty: gettext("Originals folder is empty"),
|
ErrOriginalsEmpty: gettext("Originals folder is empty"),
|
||||||
ErrSelectionNotFound: gettext("Selection not found"),
|
ErrSelectionNotFound: gettext("Selection not found"),
|
||||||
ErrEntityNotFound: gettext("Entity not found"),
|
ErrEntityNotFound: gettext("Entity not found"),
|
||||||
|
@ -107,6 +115,7 @@ var Messages = MessageMap{
|
||||||
ErrUnauthorized: gettext("Please log in to your account"),
|
ErrUnauthorized: gettext("Please log in to your account"),
|
||||||
ErrForbidden: gettext("Permission denied"),
|
ErrForbidden: gettext("Permission denied"),
|
||||||
ErrOffensiveUpload: gettext("Upload might be offensive"),
|
ErrOffensiveUpload: gettext("Upload might be offensive"),
|
||||||
|
ErrUploadFailed: gettext("Upload failed"),
|
||||||
ErrNoItemsSelected: gettext("No items selected"),
|
ErrNoItemsSelected: gettext("No items selected"),
|
||||||
ErrCreateFile: gettext("RunFailed creating file, please check permissions"),
|
ErrCreateFile: gettext("RunFailed creating file, please check permissions"),
|
||||||
ErrCreateFolder: gettext("RunFailed creating folder, please check permissions"),
|
ErrCreateFolder: gettext("RunFailed creating folder, please check permissions"),
|
||||||
|
@ -157,7 +166,10 @@ var Messages = MessageMap{
|
||||||
MsgSubjectDeleted: gettext("Subject deleted"),
|
MsgSubjectDeleted: gettext("Subject deleted"),
|
||||||
MsgPersonSaved: gettext("Person saved"),
|
MsgPersonSaved: gettext("Person saved"),
|
||||||
MsgPersonDeleted: gettext("Person deleted"),
|
MsgPersonDeleted: gettext("Person deleted"),
|
||||||
|
MsgFileUploaded: gettext("File uploaded"),
|
||||||
MsgFilesUploadedIn: gettext("%d files uploaded in %d s"),
|
MsgFilesUploadedIn: gettext("%d files uploaded in %d s"),
|
||||||
|
MsgProcessingUpload: gettext("Processing upload..."),
|
||||||
|
MsgUploadProcessed: gettext("Upload has been processed"),
|
||||||
MsgSelectionApproved: gettext("Selection approved"),
|
MsgSelectionApproved: gettext("Selection approved"),
|
||||||
MsgSelectionArchived: gettext("Selection archived"),
|
MsgSelectionArchived: gettext("Selection archived"),
|
||||||
MsgSelectionRestored: gettext("Selection restored"),
|
MsgSelectionRestored: gettext("Selection restored"),
|
||||||
|
|
|
@ -5,6 +5,7 @@ type ImportOptions struct {
|
||||||
Albums []string
|
Albums []string
|
||||||
Path string
|
Path string
|
||||||
Move bool
|
Move bool
|
||||||
|
NonBlocking bool
|
||||||
UserUID string
|
UserUID string
|
||||||
DestFolder string
|
DestFolder string
|
||||||
RemoveDotFiles bool
|
RemoveDotFiles bool
|
||||||
|
@ -17,6 +18,7 @@ func ImportOptionsCopy(importPath, destFolder string) ImportOptions {
|
||||||
result := ImportOptions{
|
result := ImportOptions{
|
||||||
Path: importPath,
|
Path: importPath,
|
||||||
Move: false,
|
Move: false,
|
||||||
|
NonBlocking: false,
|
||||||
DestFolder: destFolder,
|
DestFolder: destFolder,
|
||||||
RemoveDotFiles: false,
|
RemoveDotFiles: false,
|
||||||
RemoveExistingFiles: false,
|
RemoveExistingFiles: false,
|
||||||
|
@ -31,6 +33,22 @@ func ImportOptionsMove(importPath, destFolder string) ImportOptions {
|
||||||
result := ImportOptions{
|
result := ImportOptions{
|
||||||
Path: importPath,
|
Path: importPath,
|
||||||
Move: true,
|
Move: true,
|
||||||
|
NonBlocking: false,
|
||||||
|
DestFolder: destFolder,
|
||||||
|
RemoveDotFiles: true,
|
||||||
|
RemoveExistingFiles: true,
|
||||||
|
RemoveEmptyDirectories: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportOptionsUpload returns options for importing user uploads.
|
||||||
|
func ImportOptionsUpload(uploadPath, destFolder string) ImportOptions {
|
||||||
|
result := ImportOptions{
|
||||||
|
Path: uploadPath,
|
||||||
|
Move: true,
|
||||||
|
NonBlocking: true,
|
||||||
DestFolder: destFolder,
|
DestFolder: destFolder,
|
||||||
RemoveDotFiles: true,
|
RemoveDotFiles: true,
|
||||||
RemoveExistingFiles: true,
|
RemoveExistingFiles: true,
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/mutex"
|
"github.com/photoprism/photoprism/internal/mutex"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
)
|
)
|
||||||
|
|
|
@ -67,6 +67,7 @@ func ThumbHashMap() (result HashMap, err error) {
|
||||||
entity.Label{}.TableName(),
|
entity.Label{}.TableName(),
|
||||||
entity.Marker{}.TableName(),
|
entity.Marker{}.TableName(),
|
||||||
entity.Subject{}.TableName(),
|
entity.Subject{}.TableName(),
|
||||||
|
entity.User{}.TableName(),
|
||||||
}
|
}
|
||||||
|
|
||||||
result = make(HashMap)
|
result = make(HashMap)
|
||||||
|
|
|
@ -56,7 +56,12 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||||
// JSON-REST API Version 1
|
// JSON-REST API Version 1
|
||||||
v1 := router.Group(conf.BaseUri(config.ApiUri))
|
v1 := router.Group(conf.BaseUri(config.ApiUri))
|
||||||
{
|
{
|
||||||
// Global App Config.
|
// Authentication.
|
||||||
|
api.CreateSession(v1)
|
||||||
|
api.GetSession(v1)
|
||||||
|
api.DeleteSession(v1)
|
||||||
|
|
||||||
|
// Global Config.
|
||||||
api.GetConfigOptions(v1)
|
api.GetConfigOptions(v1)
|
||||||
api.SaveConfigOptions(v1)
|
api.SaveConfigOptions(v1)
|
||||||
|
|
||||||
|
@ -65,13 +70,14 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||||
api.GetSettings(v1)
|
api.GetSettings(v1)
|
||||||
api.SaveSettings(v1)
|
api.SaveSettings(v1)
|
||||||
|
|
||||||
// User Authentication.
|
// Profile and Uploads.
|
||||||
api.ChangePassword(v1)
|
api.UploadUserFiles(v1)
|
||||||
api.CreateSession(v1)
|
api.ProcessUserUpload(v1)
|
||||||
api.GetSession(v1)
|
api.UploadUserAvatar(v1)
|
||||||
api.DeleteSession(v1)
|
api.UpdateUserPassword(v1)
|
||||||
|
api.UpdateUser(v1)
|
||||||
|
|
||||||
// External Account Management.
|
// Service Accounts.
|
||||||
api.SearchServices(v1)
|
api.SearchServices(v1)
|
||||||
api.GetService(v1)
|
api.GetService(v1)
|
||||||
api.GetServiceFolders(v1)
|
api.GetServiceFolders(v1)
|
||||||
|
@ -80,21 +86,24 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
||||||
api.DeleteService(v1)
|
api.DeleteService(v1)
|
||||||
api.UpdateService(v1)
|
api.UpdateService(v1)
|
||||||
|
|
||||||
// Thumbs, Downloads, and Videos.
|
// Thumbnail Images.
|
||||||
api.GetThumb(v1)
|
api.GetThumb(v1)
|
||||||
api.GetDownload(v1)
|
|
||||||
|
// Video Streaming.
|
||||||
api.GetVideo(v1)
|
api.GetVideo(v1)
|
||||||
|
|
||||||
|
// Downloads.
|
||||||
|
api.GetDownload(v1)
|
||||||
api.ZipCreate(v1)
|
api.ZipCreate(v1)
|
||||||
api.ZipDownload(v1)
|
api.ZipDownload(v1)
|
||||||
|
|
||||||
// Index, Upload, and Import.
|
// Index and Import.
|
||||||
api.Upload(v1)
|
|
||||||
api.StartImport(v1)
|
api.StartImport(v1)
|
||||||
api.CancelImport(v1)
|
api.CancelImport(v1)
|
||||||
api.StartIndexing(v1)
|
api.StartIndexing(v1)
|
||||||
api.CancelIndexing(v1)
|
api.CancelIndexing(v1)
|
||||||
|
|
||||||
// Search & Organization.
|
// Photo Search and Organization.
|
||||||
api.SearchPhotos(v1)
|
api.SearchPhotos(v1)
|
||||||
api.SearchGeo(v1)
|
api.SearchGeo(v1)
|
||||||
api.GetPhoto(v1)
|
api.GetPhoto(v1)
|
||||||
|
|
Loading…
Reference in a new issue