Page MenuHomePhorge

D1384.1775387790.diff
No OneTemporary

Authored By
Unknown
Size
29 KB
Referenced Files
None
Subscribers
None

D1384.1775387790.diff

diff --git a/src/public/favicon.ico b/src/public/favicon.ico
deleted file mode 100644
diff --git a/src/public/images/favicon.ico b/src/public/images/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
literal 0
Hc$@<O00001
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -162,6 +162,11 @@
return this.price(cost * units) + '/month' + index
},
+ clickRecord(event) {
+ if (!/^(a|button|svg|path)$/i.test(event.target.nodeName)) {
+ $(event.target).closest('tr').find('a')[0].click()
+ }
+ },
domainStatusClass(domain) {
if (domain.isDeleted) {
return 'text-muted'
diff --git a/src/resources/js/bootstrap.js b/src/resources/js/bootstrap.js
--- a/src/resources/js/bootstrap.js
+++ b/src/resources/js/bootstrap.js
@@ -52,7 +52,11 @@
window.router = new VueRouter({
mode: 'history',
- routes: window.routes
+ routes: window.routes,
+ scrollBehavior (to, from, savedPosition) {
+ // Scroll the page to top, but not on Back action
+ return savedPosition || { x: 0, y: 0 }
+ }
})
router.beforeEach((to, from, next) => {
@@ -71,8 +75,10 @@
})
router.afterEach((to, from) => {
- // Remove the (old) error page when changing a page
- $('#error-page').remove()
+ // When changing a page remove old:
+ // - error page
+ // - modal backdrop
+ $('#error-page,.modal-backdrop.show').remove()
})
/**
diff --git a/src/resources/sass/app.scss b/src/resources/sass/app.scss
--- a/src/resources/sass/app.scss
+++ b/src/resources/sass/app.scss
@@ -1,11 +1,5 @@
-// Fonts
-
-// Variables
@import 'variables';
-
-// Bootstrap
-@import '~bootstrap/scss/bootstrap';
-
+@import 'bootstrap';
@import 'menu';
@import 'toast';
@import 'forms';
@@ -149,6 +143,10 @@
padding-right: 0;
}
}
+
+ button {
+ line-height: 1;
+ }
}
.btn-action {
@@ -266,15 +264,66 @@
}
}
-// Bootstrap style fix
-.btn-link {
- border: 0;
-}
+// Various improvements for mobile
+@include media-breakpoint-down(sm) {
+ .card {
+ border: 0;
+ }
-.table thead th {
- border: 0;
-}
+ .card-body {
+ padding: 0.5rem 0;
+ }
+
+ .form-group {
+ margin-bottom: 0.5rem;
+ }
+
+ .tab-content {
+ margin-top: 0.5rem;
+ }
+
+ .col-form-label {
+ color: #666;
+ font-size: 95%;
+ }
+
+ .form-group.plaintext .col-form-label {
+ padding-bottom: 0;
+ }
+
+ form.read-only.short label {
+ width: 35%;
-small {
- font-size: 0.875em;
+ & + * {
+ width: 65%;
+ }
+ }
+
+ #app > div.container {
+ margin-bottom: 1rem;
+ margin-top: 1rem;
+ }
+
+ #header-menu-navbar {
+ padding: 0;
+ }
+
+ #dashboard-nav > a {
+ width: 135px;
+ }
+
+ .table-sm:not(.form-list) {
+ tbody td {
+ padding: 0.75rem 0.5rem;
+
+ svg {
+ vertical-align: -0.175em;
+ }
+
+ & > svg {
+ font-size: 125%;
+ margin-right: 0.25rem;
+ }
+ }
+ }
}
diff --git a/src/resources/sass/bootstrap.scss b/src/resources/sass/bootstrap.scss
new file mode 100644
--- /dev/null
+++ b/src/resources/sass/bootstrap.scss
@@ -0,0 +1,15 @@
+// Bootstrap
+@import '~bootstrap/scss/bootstrap';
+
+// Bootstrap style fixes
+.btn-link {
+ border: 0;
+}
+
+.table thead th {
+ border: 0;
+}
+
+small {
+ font-size: 0.875em;
+}
diff --git a/src/resources/sass/forms.scss b/src/resources/sass/forms.scss
--- a/src/resources/sass/forms.scss
+++ b/src/resources/sass/forms.scss
@@ -1,4 +1,3 @@
-
.list-input {
& > div {
&:not(:last-child) {
@@ -23,6 +22,10 @@
input.is-invalid {
z-index: 2;
}
+
+ .btn svg {
+ vertical-align: middle;
+ }
}
.range-input {
diff --git a/src/resources/sass/menu.scss b/src/resources/sass/menu.scss
--- a/src/resources/sass/menu.scss
+++ b/src/resources/sass/menu.scss
@@ -116,6 +116,16 @@
@include media-breakpoint-down(sm) {
#header-menu {
padding: 0 1em;
+
+ .navbar-nav {
+ display: block;
+ width: 100%;
+ padding: 0;
+
+ li {
+ border-top: 1px solid #eee;
+ }
+ }
}
#footer-menu {
diff --git a/src/resources/sass/toast.scss b/src/resources/sass/toast.scss
--- a/src/resources/sass/toast.scss
+++ b/src/resources/sass/toast.scss
@@ -5,6 +5,11 @@
margin: 0.5rem;
width: 320px;
z-index: 1055; // above Bootstrap's modal backdrop and dialogs
+
+ @media (max-width: 375px) {
+ left: 0;
+ width: auto;
+ }
}
.toast {
@@ -13,6 +18,10 @@
&:not(:last-child) {
margin-bottom: 0.3rem;
}
+
+ @media (max-width: 375px) {
+ max-width: 100%;
+ }
}
.toast-header {
diff --git a/src/resources/views/layouts/app.blade.php b/src/resources/views/layouts/app.blade.php
--- a/src/resources/views/layouts/app.blade.php
+++ b/src/resources/views/layouts/app.blade.php
@@ -9,6 +9,7 @@
<title>{{ config('app.name') }} -- @yield('title')</title>
{{-- TODO: PWA disabled for now: @laravelPWA --}}
+ <link rel="icon" type="image/x-icon" href="{{ asset('images/favicon.ico') }}">
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -4,8 +4,8 @@
<div class="card-body">
<h1 class="card-title">{{ user.email }}</h1>
<div class="card-text">
- <form class="read-only">
- <div v-if="user.wallet.user_id != user.id" class="form-group row">
+ <form class="read-only short">
+ <div v-if="user.wallet.user_id != user.id" class="form-group row plaintext">
<label for="manager" class="col-sm-4 col-form-label">Managed by</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="manager">
@@ -13,7 +13,7 @@
</span>
</div>
</div>
- <div class="form-group row">
+ <div class="form-group row plaintext">
<label for="userid" class="col-sm-4 col-form-label">ID <span class="text-muted">(Created at)</span></label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="userid">
@@ -21,7 +21,7 @@
</span>
</div>
</div>
- <div class="form-group row">
+ <div class="form-group row plaintext">
<label for="status" class="col-sm-4 col-form-label">Status</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="status">
@@ -29,31 +29,31 @@
</span>
</div>
</div>
- <div class="form-group row" v-if="user.first_name">
+ <div class="form-group row plaintext" v-if="user.first_name">
<label for="first_name" class="col-sm-4 col-form-label">First name</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="first_name">{{ user.first_name }}</span>
</div>
</div>
- <div class="form-group row" v-if="user.last_name">
+ <div class="form-group row plaintext" v-if="user.last_name">
<label for="last_name" class="col-sm-4 col-form-label">Last name</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="last_name">{{ user.last_name }}</span>
</div>
</div>
- <div class="form-group row" v-if="user.organization">
+ <div class="form-group row plaintext" v-if="user.organization">
<label for="organization" class="col-sm-4 col-form-label">Organization</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="organization">{{ user.organization }}</span>
</div>
</div>
- <div class="form-group row" v-if="user.phone">
+ <div class="form-group row plaintext" v-if="user.phone">
<label for="phone" class="col-sm-4 col-form-label">Phone</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="phone">{{ user.phone }}</span>
</div>
</div>
- <div class="form-group row">
+ <div class="form-group row plaintext">
<label for="external_email" class="col-sm-4 col-form-label">External email</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="external_email">
@@ -62,13 +62,13 @@
</span>
</div>
</div>
- <div class="form-group row" v-if="user.billing_address">
+ <div class="form-group row plaintext" v-if="user.billing_address">
<label for="billing_address" class="col-sm-4 col-form-label">Address</label>
<div class="col-sm-8">
<span class="form-control-plaintext" style="white-space:pre" id="billing_address">{{ user.billing_address }}</span>
</div>
</div>
- <div class="form-group row">
+ <div class="form-group row plaintext">
<label for="country" class="col-sm-4 col-form-label">Country</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="country">{{ user.country }}</span>
@@ -114,7 +114,7 @@
<div class="card-body">
<h2 class="card-title">Account balance <span :class="wallet.balance < 0 ? 'text-danger' : 'text-success'"><strong>{{ $root.price(wallet.balance) }}</strong></span></h2>
<div class="card-text">
- <form class="read-only">
+ <form class="read-only short">
<div class="form-group row">
<label class="col-sm-4 col-form-label">Discount</label>
<div class="col-sm-8">
@@ -212,7 +212,7 @@
</tr>
</thead>
<tbody>
- <tr v-for="domain in domains" :id="'domain' + domain.id" :key="domain.id">
+ <tr v-for="domain in domains" :id="'domain' + domain.id" :key="domain.id" @click="$root.clickRecord">
<td>
<svg-icon icon="globe" :class="$root.domainStatusClass(domain)" :title="$root.domainStatusText(domain)"></svg-icon>
<router-link :to="{ path: '/domain/' + domain.id }">{{ domain.namespace }}</router-link>
@@ -238,7 +238,7 @@
</tr>
</thead>
<tbody>
- <tr v-for="item in users" :id="'user' + item.id" :key="item.id">
+ <tr v-for="item in users" :id="'user' + item.id" :key="item.id" @click="$root.clickRecord">
<td>
<svg-icon icon="user" :class="$root.userStatusClass(item)" :title="$root.userStatusText(item)"></svg-icon>
<router-link :to="{ path: '/user/' + item.id }">{{ item.email }}</router-link>
@@ -319,7 +319,7 @@
<div class="modal-body">
<form data-validation-prefix="oneoff_">
<div class="form-group">
- <label for="oneoff_amount">Amount</label>
+ <label for="oneoff_amount" class="col-form-label">Amount</label>
<div class="input-group">
<input type="text" class="form-control" id="oneoff_amount" v-model="oneoff_amount" required>
<span class="input-group-append">
@@ -328,7 +328,7 @@
</div>
</div>
<div class="form-group">
- <label for="oneoff_description">Description</label>
+ <label for="oneoff_description" class="col-form-label">Description</label>
<input class="form-control" id="oneoff_description" v-model="oneoff_description" required>
</div>
</form>
diff --git a/src/resources/vue/Domain/List.vue b/src/resources/vue/Domain/List.vue
--- a/src/resources/vue/Domain/List.vue
+++ b/src/resources/vue/Domain/List.vue
@@ -12,7 +12,7 @@
</tr>
</thead>
<tbody>
- <tr v-for="domain in domains" :key="domain.id">
+ <tr v-for="domain in domains" :key="domain.id" @click="$root.clickRecord">
<td>
<svg-icon icon="globe" :class="$root.domainStatusClass(domain)" :title="$root.domainStatusText(domain)"></svg-icon>
<router-link :to="{ path: 'domain/' + domain.id }">{{ domain.namespace }}</router-link>
diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue
--- a/src/resources/vue/User/Info.vue
+++ b/src/resources/vue/User/Info.vue
@@ -8,7 +8,7 @@
<div class="card-title" v-if="user_id === 'new'">New user account</div>
<div class="card-text">
<form @submit.prevent="submit">
- <div v-if="user_id !== 'new'" class="form-group row">
+ <div v-if="user_id !== 'new'" class="form-group row plaintext">
<label for="first_name" class="col-sm-4 col-form-label">Status</label>
<div class="col-sm-8">
<span :class="$root.userStatusClass(user) + ' form-control-plaintext'" id="status">{{ $root.userStatusText(user) }}</span>
@@ -71,10 +71,14 @@
<tbody>
<tr v-for="pkg in packages" :id="'p' + pkg.id" :key="pkg.id">
<td class="selection">
- <input type="checkbox" :value="pkg.id" @click="selectPackage" :checked="pkg.id == package_id">
+ <input type="checkbox" @click="selectPackage"
+ :value="pkg.id"
+ :checked="pkg.id == package_id"
+ :id="'pkg-input-' + pkg.id"
+ >
</td>
<td class="name">
- {{ pkg.name }}
+ <label :for="'pkg-input-' + pkg.id">{{ pkg.name }}</label>
</td>
<td class="price text-nowrap">
{{ $root.priceLabel(pkg.cost, 1, discount) }}
@@ -113,11 +117,11 @@
:value="sku.id"
:disabled="sku.readonly"
:checked="sku.enabled"
- :dusk="'sku-input-' + sku.title"
+ :id="'sku-input-' + sku.title"
>
</td>
<td class="name">
- <span class="name">{{ sku.name }}</span>
+ <label :for="'sku-input-' + sku.title">{{ sku.name }}</label>
<div v-if="sku.range" class="range-input">
<label class="text-nowrap">{{ sku.range.min }} {{ sku.range.unit }}</label>
<input
diff --git a/src/resources/vue/User/List.vue b/src/resources/vue/User/List.vue
--- a/src/resources/vue/User/List.vue
+++ b/src/resources/vue/User/List.vue
@@ -17,7 +17,7 @@
</tr>
</thead>
<tbody>
- <tr v-for="user in users" :id="'user' + user.id" :key="user.id">
+ <tr v-for="user in users" :id="'user' + user.id" :key="user.id" @click="$root.clickRecord">
<td>
<svg-icon icon="user" :class="$root.userStatusClass(user)" :title="$root.userStatusText(user)"></svg-icon>
<router-link :to="{ path: 'user/' + user.id }">{{ user.email }}</router-link>
diff --git a/src/resources/vue/Widgets/Status.vue b/src/resources/vue/Widgets/Status.vue
--- a/src/resources/vue/Widgets/Status.vue
+++ b/src/resources/vue/Widgets/Status.vue
@@ -22,7 +22,7 @@
Verify your domain to finish the setup process.
</p>
<div v-if="scope == 'domain'">
- <button id="status-verify" class="btn btn-secondary" @click="confirmDomain">
+ <button id="status-verify" class="btn btn-secondary text-nowrap" @click="confirmDomain">
<svg-icon icon="sync-alt"></svg-icon> Verify
</button>
</div>
diff --git a/src/tests/Browser/Admin/LogonTest.php b/src/tests/Browser/Admin/LogonTest.php
--- a/src/tests/Browser/Admin/LogonTest.php
+++ b/src/tests/Browser/Admin/LogonTest.php
@@ -99,7 +99,7 @@
// Click the Logout button
$browser->within(new Menu(), function ($browser) {
- $browser->click('.link-logout');
+ $browser->clickMenuItem('logout');
});
// We expect the logon page
diff --git a/src/tests/Browser/Components/Menu.php b/src/tests/Browser/Components/Menu.php
--- a/src/tests/Browser/Components/Menu.php
+++ b/src/tests/Browser/Components/Menu.php
@@ -46,12 +46,16 @@
*
* @param \Laravel\Dusk\Browser $browser
* @param array $items List of menu items
+ * @param string $active Expected active item
*
* @return void
*/
- public function assertMenuItems($browser, array $items)
+ public function assertMenuItems($browser, array $items, string $active = null)
{
- // TODO: On mobile the links will not be visible
+ // On mobile the links are not visible, show them first (wait for transition)
+ if ($browser->isPhone()) {
+ $browser->click('@toggler')->waitFor('.navbar-collapse.show');
+ }
foreach ($items as $item) {
$browser->assertVisible('.link-' . $item);
@@ -59,21 +63,36 @@
// Check number of items, to make sure there's no extra items
PHPUnit::assertCount(count($items), $browser->elements('li'));
+
+ if ($active) {
+ $browser->assertPresent(".link-{$active}.active");
+ }
+
+ if ($browser->isPhone()) {
+ $browser->click('@toggler')->waitUntilMissing('.navbar-collapse.show');
+ }
}
/**
- * Assert that specified menu item is active
+ * Click menu link.
*
- * @param \Laravel\Dusk\Browser $browser
- * @param string $item Menu item name
+ * @param \Laravel\Dusk\Browser $browser The browser object
+ * @param string $name Menu item name
*
* @return void
*/
- public function assertActiveItem($browser, string $item)
+ public function clickMenuItem($browser, string $name)
{
- // TODO: On mobile the links will not be visible
+ // On mobile the links are not visible, show them first (wait for transition)
+ if ($browser->isPhone()) {
+ $browser->click('@toggler')->waitFor('.navbar-collapse.show');
+ }
- $browser->assertVisible(".link-{$item}.active");
+ $browser->click('.link-' . $name);
+
+ if ($browser->isPhone()) {
+ $browser->waitUntilMissing('.navbar-collapse.show');
+ }
}
/**
diff --git a/src/tests/Browser/LogonTest.php b/src/tests/Browser/LogonTest.php
--- a/src/tests/Browser/LogonTest.php
+++ b/src/tests/Browser/LogonTest.php
@@ -22,10 +22,15 @@
$browser->visit(new Home())
->within(new Menu(), function ($browser) {
$browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'webmail']);
- })
- ->within(new Menu('footer'), function ($browser) {
+ });
+
+ if ($browser->isDesktop()) {
+ $browser->within(new Menu('footer'), function ($browser) {
$browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'webmail']);
});
+ } else {
+ $browser->assertMissing('#footer-menu .navbar-nav');
+ }
});
}
@@ -73,11 +78,17 @@
$browser->on(new Dashboard())
->within(new Menu(), function ($browser) {
$browser->assertMenuItems(['support', 'contact', 'webmail', 'logout']);
- })
- ->within(new Menu('footer'), function ($browser) {
+ });
+
+ if ($browser->isDesktop()) {
+ $browser->within(new Menu('footer'), function ($browser) {
$browser->assertMenuItems(['support', 'contact', 'webmail', 'logout']);
- })
- ->assertUser('john@kolab.org');
+ });
+ } else {
+ $browser->assertMissing('#footer-menu .navbar-nav');
+ }
+
+ $browser->assertUser('john@kolab.org');
// Assert no "Account status" for this account
$browser->assertMissing('@status');
@@ -107,7 +118,7 @@
// Click the Logout button
$browser->within(new Menu(), function ($browser) {
- $browser->click('.link-logout');
+ $browser->clickMenuItem('logout');
});
// We expect the logon page
diff --git a/src/tests/Browser/SignupTest.php b/src/tests/Browser/SignupTest.php
--- a/src/tests/Browser/SignupTest.php
+++ b/src/tests/Browser/SignupTest.php
@@ -107,8 +107,7 @@
->assertMissing('@step3');
$browser->within(new Menu(), function ($browser) {
- $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']);
- $browser->assertActiveItem('signup');
+ $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login'], 'signup');
});
$browser->waitFor('@step0 .plan-selector > .plan-box');
@@ -155,14 +154,8 @@
public function testSignupStep1(): void
{
$this->browse(function (Browser $browser) {
- $browser->visit('/signup/individual')->onWithoutAssert(new Signup());
-
- $browser->assertVisible('@step1');
-
- $browser->within(new Menu(), function ($browser) {
- $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']);
- $browser->assertActiveItem('signup');
- });
+ $browser->visit('/signup/individual')
+ ->onWithoutAssert(new Signup());
// Here we expect two text inputs and Back and Continue buttons
$browser->with('@step1', function ($step) {
@@ -182,6 +175,10 @@
$step->assertFocused('#signup_email');
});
+ $browser->within(new Menu(), function ($browser) {
+ $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login'], 'signup');
+ });
+
// Submit invalid email, and first_name
// We expect both inputs to have is-invalid class added, with .invalid-feedback element
$browser->with('@step1', function ($step) use ($browser) {
@@ -375,7 +372,9 @@
->assertUser('signuptestdusk@' . \config('app.domain'));
// Logout the user
- $browser->click('a.link-logout');
+ $browser->within(new Menu(), function ($browser) {
+ $browser->clickMenuItem('logout');
+ });
});
}
@@ -469,7 +468,9 @@
->on(new Dashboard())
->assertUser('admin@user-domain-signup.com');
- $browser->click('a.link-logout');
+ $browser->within(new Menu(), function ($browser) {
+ $browser->clickMenuItem('logout');
+ });
});
}
@@ -523,7 +524,9 @@
->on(new Dashboard())
->assertUser('signuptestdusk@' . \config('app.domain'))
// Logout the user
- ->click('a.link-logout');
+ ->within(new Menu(), function ($browser) {
+ $browser->clickMenuItem('logout');
+ });
});
$user = $this->getTestUser('signuptestdusk@' . \config('app.domain'));
diff --git a/src/tests/Browser/UsersTest.php b/src/tests/Browser/UsersTest.php
--- a/src/tests/Browser/UsersTest.php
+++ b/src/tests/Browser/UsersTest.php
@@ -280,25 +280,25 @@
$browser->with('@form', function (Browser $browser) {
$browser->with('@skus', function ($browser) {
// Uncheck 'groupware', expect activesync unchecked
- $browser->click('@sku-input-groupware')
- ->assertNotChecked('@sku-input-groupware')
- ->assertNotChecked('@sku-input-activesync')
- ->assertEnabled('@sku-input-activesync')
- ->assertNotReadonly('@sku-input-activesync')
+ $browser->click('#sku-input-groupware')
+ ->assertNotChecked('#sku-input-groupware')
+ ->assertNotChecked('#sku-input-activesync')
+ ->assertEnabled('#sku-input-activesync')
+ ->assertNotReadonly('#sku-input-activesync')
// Check 'activesync', expect an alert
- ->click('@sku-input-activesync')
+ ->click('#sku-input-activesync')
->assertDialogOpened('Activesync requires Groupware Features.')
->acceptDialog()
- ->assertNotChecked('@sku-input-activesync')
+ ->assertNotChecked('#sku-input-activesync')
// Check '2FA', expect 'activesync' unchecked and readonly
- ->click('@sku-input-2fa')
- ->assertChecked('@sku-input-2fa')
- ->assertNotChecked('@sku-input-activesync')
- ->assertReadonly('@sku-input-activesync')
+ ->click('#sku-input-2fa')
+ ->assertChecked('#sku-input-2fa')
+ ->assertNotChecked('#sku-input-activesync')
+ ->assertReadonly('#sku-input-activesync')
// Uncheck '2FA'
- ->click('@sku-input-2fa')
- ->assertNotChecked('@sku-input-2fa')
- ->assertNotReadonly('@sku-input-activesync');
+ ->click('#sku-input-2fa')
+ ->assertNotChecked('#sku-input-2fa')
+ ->assertNotReadonly('#sku-input-activesync');
});
});
});

File Metadata

Mime Type
text/plain
Expires
Sun, Apr 5, 11:16 AM (18 h, 56 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18833279
Default Alt Text
D1384.1775387790.diff (29 KB)

Event Timeline