Frontend: Fix account management issues #225

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-03-30 09:17:46 +02:00
parent b592e67dfa
commit 9f400a826c
5 changed files with 171 additions and 64 deletions

View file

@ -2,16 +2,59 @@
<v-dialog lazy v-model="show" persistent max-width="500" class="p-account-edit-dialog" @keydown.esc="cancel">
<v-card raised elevation="24">
<v-card-title primary-title>
<div>
<h3 class="headline mb-0" v-if="scope === 'sharing'">Upload Settings</h3>
<h3 class="headline mb-0" v-else-if="scope === 'sync'">Continuous Sync</h3>
<v-layout row wrap v-if="scope === 'sharing'">
<v-flex xs9>
<h3 class="headline mb-0">Upload Settings</h3>
</v-flex>
<v-flex xs3 text-xs-right>
<v-switch
v-model="model.AccShare"
color="success"
:true-value="true"
:false-value="false"
:label="model.AccShare ? 'enabled' : 'disabled'"
:disabled="model.AccType !== 'webdav'"
class="mt-0"
hide-details
></v-switch>
</v-flex>
</v-layout>
<v-layout row wrap v-else-if="scope === 'sync'">
<v-flex xs9>
<h3 class="headline mb-0">Sync Settings</h3>
</v-flex>
<v-flex xs3 text-xs-right>
<v-switch
v-model="model.AccSync"
color="success"
:true-value="true"
:false-value="false"
:label="model.AccSync ? 'enabled' : 'disabled'"
:disabled="model.AccType !== 'webdav'"
class="mt-0"
hide-details
></v-switch>
</v-flex>
</v-layout>
<v-layout row wrap v-else>
<v-flex xs10>
<h3 class="headline mb-0">Edit Account</h3>
</v-flex>
<v-flex xs2 text-xs-right>
<v-btn icon flat :ripple="false"
class="action-remove mt-0"
@click.stop.prevent="remove()">
<v-icon color="remove">delete</v-icon>
</v-btn>
</v-flex>
</v-layout>
<h3 class="headline mb-0" v-else>Edit Account</h3>
</div>
</v-card-title>
<v-container fluid class="pt-0 pb-2 pr-2 pl-2">
<v-layout row wrap v-if="scope === 'sharing'">
<v-flex xs12 class="pa-2">
<v-text-field
:disabled="!model.AccShare"
hide-details
:label="label.SharePath"
placeholder=""
@ -22,6 +65,7 @@
</v-flex>
<v-flex xs12 sm6 pa-2 class="input-share-size">
<v-select
:disabled="!model.AccShare"
:label="label.ShareSize"
hide-details
color="secondary-dark"
@ -33,6 +77,7 @@
</v-flex>
<v-flex xs12 sm6 class="pa-2">
<v-select
:disabled="!model.AccShare"
:label="label.ShareExpires"
hide-details
color="secondary-dark"
@ -44,6 +89,7 @@
</v-flex>
<v-flex xs12 sm6 class="pa-2">
<v-checkbox
:disabled="!model.AccShare"
hide-details
color="secondary-dark"
:label="label.ShareExif"
@ -52,6 +98,7 @@
</v-flex>
<v-flex xs12 sm6 class="pa-2">
<v-checkbox
:disabled="!model.AccShare"
hide-details
color="secondary-dark"
:label="label.ShareSidecar"
@ -60,8 +107,9 @@
</v-flex>
</v-layout>
<v-layout row wrap v-else-if="scope === 'sync'">
<v-flex xs12 class="pa-2">
<v-flex xs12 sm6 class="px-2">
<v-text-field
:disabled="!model.AccSync"
hide-details
:label="label.SyncPath"
placeholder=""
@ -70,8 +118,21 @@
required
></v-text-field>
</v-flex>
<v-flex xs12 sm6 class="px-2">
<v-select
:disabled="!model.AccSync"
:label="label.SyncInterval"
hide-details
color="secondary-dark"
item-text="text"
item-value="value"
v-model="model.SyncInterval"
:items="items.intervals">
</v-select>
</v-flex>
<v-flex xs12 sm6 class="px-2">
<v-checkbox
:disabled="!model.AccSync"
hide-details
color="secondary-dark"
:label="label.SyncDownload"
@ -80,6 +141,7 @@
</v-flex>
<v-flex xs12 sm6 class="px-2">
<v-checkbox
:disabled="!model.AccSync"
hide-details
color="secondary-dark"
:label="label.SyncUpload"
@ -96,6 +158,7 @@
</v-flex -->
<v-flex xs12 sm6 class="px-2">
<v-checkbox
:disabled="!model.AccSync"
hide-details
color="secondary-dark"
:label="label.SyncRaw"
@ -104,6 +167,7 @@
</v-flex>
<v-flex xs12 sm6 class="px-2">
<v-checkbox
:disabled="!model.AccSync"
hide-details
color="secondary-dark"
:label="label.SyncVideo"
@ -112,6 +176,7 @@
</v-flex>
<v-flex xs12 sm6 class="px-2">
<v-checkbox
:disabled="!model.AccSync"
hide-details
color="secondary-dark"
:label="label.SyncSidecar"
@ -170,6 +235,17 @@
required
></v-text-field>
</v-flex>
<v-flex xs12 sm6 pa-2 class="input-account-type">
<v-select
:label="label.AccType"
hide-details
color="secondary-dark"
item-text="text"
item-value="value"
v-model="model.AccType"
:items="items.types">
</v-select>
</v-flex>
<!-- v-flex xs12 sm6 class="pa-2">
<v-text-field
hide-details
@ -184,35 +260,11 @@
<v-layout row wrap>
<v-flex xs12 text-xs-right class="pt-3">
<v-btn @click.stop="cancel" depressed color="grey lighten-3"
class="action-cancel">
class="action-cancel hidden-xs-only">
<span>{{ label.cancel }}</span>
</v-btn>
<v-btn @click.stop="disable('AccShare')" depressed color="grey lighten-3"
class="action-disable" v-if="model.AccShare && scope === 'sharing'">
<span>{{ label.disable }}</span>
</v-btn>
<v-btn @click.stop="disable('AccSync')" depressed color="grey lighten-3"
class="action-disable" v-if="model.AccSync && scope === 'sync'">
<span>{{ label.disable }}</span>
</v-btn>
<v-btn color="blue-grey lighten-2" depressed dark @click.stop="enable('AccShare')"
class="action-enable" v-if="!model.AccShare && scope === 'sharing'">
<span>{{ label.enable }}</span>
</v-btn>
<v-btn color="blue-grey lighten-2" depressed dark @click.stop="enable('AccSync')"
class="action-enable" v-if="!model.AccSync && scope === 'sync'">
<span>{{ label.enable }}</span>
</v-btn>
<v-btn color="blue-grey lighten-2" depressed dark @click.stop="confirm"
class="action-confirm" v-if="model.AccShare && scope === 'sharing'">
<span>{{ label.save }}</span>
</v-btn>
<v-btn color="blue-grey lighten-2" depressed dark @click.stop="confirm"
class="action-confirm" v-if="model.AccSync && scope === 'sync'">
<span>{{ label.save }}</span>
</v-btn>
<v-btn color="blue-grey lighten-2" depressed dark @click.stop="confirm"
class="action-confirm" v-if="scope === 'account'">
<v-btn color="blue-grey lighten-2" depressed dark @click.stop="save"
class="action-save">
<span>{{ label.save }}</span>
</v-btn>
</v-flex>
@ -243,15 +295,38 @@
items: {
thumbs: thumbs,
sizes: this.sizes(thumbs),
types: [
{"value": "web", "text": "Web"},
{"value": "webdav", "text": "WebDAV / Nextcloud"},
{"value": "facebook", "text": "Facebook"},
{"value": "twitter", "text": "Twitter"},
{"value": "flickr", "text": "Flickr"},
{"value": "instagram", "text": "Instagram"},
{"value": "eyeem", "text": "EyeEm"},
{"value": "telegram", "text": "Telegram"},
{"value": "whatsapp", "text": "WhatsApp"},
{"value": "gphotos", "text": "Google Photos"},
{"value": "gdrive", "text": "Google Drive"},
{"value": "onedrive", "text": "Microsoft OneDrive"},
],
intervals: [
{"value": 0, "text": "Never"},
{"value": 3600, "text": "1 hour"},
{"value": 3600 * 4, "text": "4 hours"},
{"value": 3600 * 12, "text": "12 hours"},
{"value": 86400, "text": "Daily"},
{"value": 86400 * 2, "text": "Every two days"},
{"value": 86400 * 7, "text": "Once a week"},
],
expires: [
{ "value": 0, "text": "Never" },
{ "value": 86400, "text": "After 1 day" },
{ "value": 86400 * 3, "text": "After 3 days" },
{ "value": 86400 * 7, "text": "After 7 days" },
{ "value": 86400 * 14, "text": "After two weeks" },
{ "value": 86400 * 31, "text": "After one month" },
{ "value": 86400 * 60, "text": "After two months" },
{ "value": 86400 * 365, "text": "After one year" },
{"value": 0, "text": "Never"},
{"value": 86400, "text": "After 1 day"},
{"value": 86400 * 3, "text": "After 3 days"},
{"value": 86400 * 7, "text": "After 7 days"},
{"value": 86400 * 14, "text": "After two weeks"},
{"value": 86400 * 31, "text": "After one month"},
{"value": 86400 * 60, "text": "After two months"},
{"value": 86400 * 365, "text": "After one year"},
],
},
readonly: this.$config.getValue("readonly"),
@ -268,18 +343,19 @@
pass: this.$gettext("Password"),
owner: this.$gettext("Owner"),
apiKey: this.$gettext("API Key"),
SharePath: this.$gettext("Folder"),
AccType: this.$gettext("Type"),
SharePath: this.$gettext("Default Directory"),
ShareSize: this.$gettext("Size"),
ShareExpires: this.$gettext("Expires"),
ShareExif: this.$gettext("Include metadata"),
ShareSidecar: this.$gettext("Include sidecar files"),
SyncPath: this.$gettext("Folder"),
SyncPath: this.$gettext("Remote Directory"),
SyncInterval: this.$gettext("Interval"),
SyncStart: this.$gettext("Start"),
SyncDownload: this.$gettext("Download new files"),
SyncUpload: this.$gettext("Upload local files"),
SyncDelete: this.$gettext("Remote delete"),
SyncRaw: this.$gettext("Sync RAW images"),
SyncRaw: this.$gettext("Sync RAW photos"),
SyncVideo: this.$gettext("Sync videos"),
SyncSidecar: this.$gettext("Sync sidecar files"),
}
@ -289,6 +365,9 @@
cancel() {
this.$emit('cancel');
},
remove() {
this.$emit('remove');
},
confirm() {
this.model.AccShare = true;
this.save();
@ -311,12 +390,12 @@
},
sizes(thumbs) {
const result = [
{"text" : this.$gettext("Original"), "value": ""}
{"text": this.$gettext("Original"), "value": ""}
];
for(let i in thumbs) {
for (let i in thumbs) {
const s = thumbs[i];
result.push({"text" : s["Width"] + 'x' + s["Height"], "value": s["Name"]});
result.push({"text": s["Width"] + 'x' + s["Height"], "value": s["Name"]});
}
return result;

View file

@ -30,8 +30,8 @@
<v-icon v-if="props.item.AccSync" color="secondary-dark">sync</v-icon>
<v-icon v-else color="secondary-dark">sync_disabled</v-icon>
</v-btn></td>
<td class="hidden-md-and-down">{{ formatDate(props.item.SyncedAt) }}</td>
<!-- td class="text-xs-right" nowrap>
<td class="hidden-sm-and-down">{{ formatDate(props.item.SyncedAt) }}</td>
<td class="hidden-xs-only text-xs-right" nowrap>
<v-btn icon small flat :ripple="false"
class="p-account-remove"
@click.stop.prevent="remove(props.item)">
@ -42,7 +42,7 @@
@click.stop.prevent="edit(props.item)">
<v-icon color="secondary-dark">edit</v-icon>
</v-btn>
</td -->
</td>
</template>
</v-data-table>
<v-container fluid>
@ -62,7 +62,7 @@
@confirm="onAdded"></p-account-add-dialog>
<p-account-remove-dialog :show="dialog.remove" :model="model" @cancel="onCancel('remove')"
@confirm="onRemoved"></p-account-remove-dialog>
<p-account-edit-dialog :show="dialog.edit" :model="model" :scope="editScope" @cancel="onCancel('edit')"
<p-account-edit-dialog :show="dialog.edit" :model="model" :scope="editScope" @remove="remove(model)" @cancel="onCancel('edit')"
@confirm="onEdited"></p-account-edit-dialog>
</div>
</template>
@ -94,8 +94,8 @@
{text: this.$gettext('Name'), value: 'AccName', sortable: false, align: 'left'},
{text: this.$gettext('Share'), value: 'AccShare', sortable: false, align: 'center'},
{text: this.$gettext('Sync'), value: 'AccSync', sortable: false, align: 'center'},
{text: this.$gettext('Synced'), value: 'SyncedAt', sortable: false, class: 'hidden-md-and-down', align: 'left'},
//{text: '', value: '', sortable: false, align: 'right'},
{text: this.$gettext('Synced'), value: 'SyncedAt', sortable: false, class: 'hidden-sm-and-down', align: 'left'},
{text: '', value: '', sortable: false, class: 'hidden-xs-only', align: 'right'},
],
};
},
@ -113,13 +113,15 @@
Account.search({count: 100}).then(r => this.results = r.models);
},
remove(model) {
this.model = model;
this.model = model.clone();
this.dialog.edit = false;
this.dialog.remove = true;
},
onRemoved() {
this.dialog.remove = false;
this.model = {};
this.load();
},
editSharing(model) {
this.model = model.clone();

View file

@ -6,6 +6,7 @@ import (
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/service"
"github.com/ulule/deepcopier"
)
@ -47,11 +48,7 @@ type Account struct {
func CreateAccount(form form.Account, db *gorm.DB) (model *Account, err error) {
model = &Account{}
if err := deepcopier.Copy(model).From(form); err != nil {
return model, err
}
err = db.Save(&model).Error
err = model.Save(form, db)
return model, err
}
@ -62,6 +59,20 @@ func (m *Account) Save(form form.Account, db *gorm.DB) error {
return err
}
if m.AccType != string(service.TypeWebDAV) {
// TODO: Only WebDAV supported at the moment
m.AccShare = false
m.AccSync = false
}
if m.SharePath == "" {
m.SharePath = "/"
}
if m.SyncPath == "" {
m.SyncPath = "/"
}
return db.Save(m).Error
}

View file

@ -14,6 +14,7 @@ import (
"strings"
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/txt"
)
var log = event.Log
@ -27,6 +28,10 @@ const (
TypeFacebook Type = "facebook"
TypeTwitter Type = "twitter"
TypeFlickr Type = "flickr"
TypeInstagram Type = "instagram"
TypeEyeEm Type = "eyeem"
TypeTelegram Type = "telegram"
TypeWhatsApp Type = "whatsapp"
TypeGooglePhotos Type = "gphotos"
TypeGoogleDrive Type = "gdrive"
TypeOneDrive Type = "onedrive"
@ -52,6 +57,10 @@ var Heuristics = []Heuristic{
{TypeFacebook, []string{"facebook.com", "www.facebook.com"}, []string{}, "GET"},
{TypeTwitter, []string{"twitter.com"}, []string{}, "GET"},
{TypeFlickr, []string{"flickr.com", "www.flickr.com"}, []string{}, "GET"},
{TypeInstagram, []string{"instagram.com", "www.instagram.com"}, []string{}, "GET"},
{TypeEyeEm, []string{"eyeem.com", "www.eyeem.com"}, []string{}, "GET"},
{TypeTelegram, []string{"web.telegram.org", "www.telegram.org", "telegram.org"}, []string{}, "GET"},
{TypeWhatsApp, []string{"web.whatsapp.com", "www.whatsapp.com", "whatsapp.com"}, []string{}, "GET"},
{TypeOneDrive, []string{"onedrive.live.com"}, []string{}, "GET"},
{TypeGoogleDrive, []string{"drive.google.com"}, []string{}, "GET"},
{TypeGooglePhotos, []string{"photos.google.com"}, []string{}, "GET"},
@ -153,7 +162,13 @@ func Discover(rawUrl, user, pass string) (result Account, err error) {
if serviceUrl := h.Discover(u.String(), result.AccUser); serviceUrl != nil {
serviceUrl.User = nil
if w := txt.Keywords(serviceUrl.Host); len(w) > 0 {
result.AccName = strings.Title(w[0])
} else {
result.AccName = serviceUrl.Host
}
result.AccType = string(h.ServiceType)
result.AccURL = serviceUrl.String()

View file

@ -14,7 +14,7 @@ func TestDiscover(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, "webdav-dummy", r.AccName)
assert.Equal(t, "Webdav", r.AccName)
assert.Equal(t, "webdav", r.AccType)
assert.Equal(t, "http://webdav-dummy/", r.AccURL)
assert.Equal(t, "admin", r.AccUser)
@ -28,7 +28,7 @@ func TestDiscover(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, "webdav-dummy", r.AccName)
assert.Equal(t, "Webdav", r.AccName)
assert.Equal(t, "webdav", r.AccType)
assert.Equal(t, "http://webdav-dummy/", r.AccURL)
assert.Equal(t, "admin", r.AccUser)
@ -42,7 +42,7 @@ func TestDiscover(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, "dl.photoprism.org", r.AccName)
assert.Equal(t, "Photoprism", r.AccName)
assert.Equal(t, "web", r.AccType)
assert.Equal(t, "https://dl.photoprism.org/fixtures/testdata/import/", r.AccURL)
assert.Equal(t, "", r.AccUser)
@ -56,7 +56,7 @@ func TestDiscover(t *testing.T) {
t.Fatal(err)
}
assert.Equal(t, "www.facebook.com", r.AccName)
assert.Equal(t, "Facebook", r.AccName)
assert.Equal(t, "facebook", r.AccType)
assert.Equal(t, "https://www.facebook.com/ob.boris.palmer", r.AccURL)
assert.Equal(t, "", r.AccUser)