BLUE-8: The OpenStack driver now works completely with all operations - flip, rotate, refresh thumbnails. It's also possible to move photos between albums across different storage providers.
This commit is contained in:
parent
005c5eb645
commit
640828e244
@ -5,6 +5,7 @@ namespace App\AlbumSources;
|
|||||||
use App\Album;
|
use App\Album;
|
||||||
use App\Photo;
|
use App\Photo;
|
||||||
use App\Storage;
|
use App\Storage;
|
||||||
|
use Guzzle\Http\EntityBody;
|
||||||
use Symfony\Component\HttpFoundation\File\File;
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
interface IAlbumSource
|
interface IAlbumSource
|
||||||
@ -23,20 +24,20 @@ interface IAlbumSource
|
|||||||
*/
|
*/
|
||||||
function deleteThumbnail(Photo $photo, $thumbnail = null);
|
function deleteThumbnail(Photo $photo, $thumbnail = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the contents of a thumbnail for a photo.
|
||||||
|
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||||
|
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||||
|
* @return EntityBody
|
||||||
|
*/
|
||||||
|
function fetchPhotoContent(Photo $photo, $thumbnail = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the name of this album source.
|
* Gets the name of this album source.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function getName();
|
function getName();
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the absolute path to the given photo file.
|
|
||||||
* @param Photo $photo Photo to get the path to.
|
|
||||||
* @param string $thumbnail Thumbnail to get the image to.
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
function getPathToPhoto(Photo $photo, $thumbnail = null);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the absolute URL to the given photo file.
|
* Gets the absolute URL to the given photo file.
|
||||||
* @param Photo $photo Photo to get the URL to.
|
* @param Photo $photo Photo to get the URL to.
|
||||||
@ -45,30 +46,14 @@ interface IAlbumSource
|
|||||||
*/
|
*/
|
||||||
function getUrlToPhoto(Photo $photo, $thumbnail = null);
|
function getUrlToPhoto(Photo $photo, $thumbnail = null);
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the original photo to its permanent location.
|
|
||||||
* @param Photo $photo Photo the original relates to.
|
|
||||||
* @param $tempFilename Filename containing the original image.
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
function saveOriginal(Photo $photo, $tempFilename);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a generated thumbnail to its permanent location.
|
* Saves a generated thumbnail to its permanent location.
|
||||||
* @param Photo $photo Photo the thumbnail relates to.
|
* @param Photo $photo Photo the image relates to.
|
||||||
* @param string $thumbnailInfo Information about the thumbnail.
|
* @param string $tempFilename Filename containing the image.
|
||||||
* @param string $tempFilename Filename containing the thumbnail.
|
* @param string $thumbnail Name of the thumbnail (or null for the original.)
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
function saveThumbnail(Photo $photo, $thumbnailInfo, $tempFilename);
|
function saveThumbnail(Photo $photo, $tempFilename, $thumbnail = null);
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves an uploaded file to the container and returns the filename.
|
|
||||||
* @param File $uploadedFile The photo uploaded
|
|
||||||
* @param string $overrideFilename Specific file name to use, or null to randomly generate one.
|
|
||||||
* @return File
|
|
||||||
*/
|
|
||||||
function saveUploadedPhoto(File $uploadedFile, $overrideFilename = null);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Album $album
|
* @param Album $album
|
||||||
|
@ -6,6 +6,7 @@ use App\Album;
|
|||||||
use App\Helpers\MiscHelper;
|
use App\Helpers\MiscHelper;
|
||||||
use App\Photo;
|
use App\Photo;
|
||||||
use App\Services\PhotoService;
|
use App\Services\PhotoService;
|
||||||
|
use Guzzle\Http\EntityBody;
|
||||||
use Symfony\Component\HttpFoundation\File\File;
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,12 +25,33 @@ class LocalFilesystemSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
|
|
||||||
public function deleteThumbnail(Photo $photo, $thumbnail = null)
|
public function deleteThumbnail(Photo $photo, $thumbnail = null)
|
||||||
{
|
{
|
||||||
return @unlink($this->getPathToPhoto($photo, $thumbnail));
|
return @unlink(
|
||||||
|
join(DIRECTORY_SEPARATOR, [
|
||||||
|
$this->getPathToAlbum(),
|
||||||
|
is_null($thumbnail) ? $this->getOriginalsFolder() : $thumbnail,
|
||||||
|
$photo->storage_file_name
|
||||||
|
])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOriginalsFolder()
|
/**
|
||||||
|
* Fetches the contents of a thumbnail for a photo.
|
||||||
|
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||||
|
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||||
|
* @return EntityBody
|
||||||
|
*/
|
||||||
|
public function fetchPhotoContent(Photo $photo, $thumbnail = null)
|
||||||
{
|
{
|
||||||
return '_originals';
|
$fh = fopen(
|
||||||
|
join(DIRECTORY_SEPARATOR, [
|
||||||
|
$this->getPathToAlbum(),
|
||||||
|
is_null($thumbnail) ? $this->getOriginalsFolder() : $thumbnail,
|
||||||
|
$photo->storage_file_name
|
||||||
|
]),
|
||||||
|
'r+'
|
||||||
|
);
|
||||||
|
|
||||||
|
return EntityBody::factory($fh);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getName()
|
public function getName()
|
||||||
@ -37,16 +59,6 @@ class LocalFilesystemSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
return 'global.album_sources.filesystem';
|
return 'global.album_sources.filesystem';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPathToPhoto(Photo $photo, $thumbnail = null)
|
|
||||||
{
|
|
||||||
if (is_null($thumbnail))
|
|
||||||
{
|
|
||||||
$thumbnail = $this->getOriginalsFolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf('%s/%s/%s', $this->getPathToAlbum(), $thumbnail, $photo->storage_file_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
||||||
{
|
{
|
||||||
$photoUrl = route('downloadPhoto', [
|
$photoUrl = route('downloadPhoto', [
|
||||||
@ -62,38 +74,21 @@ class LocalFilesystemSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
return $photoUrl;
|
return $photoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveOriginal(Photo $photo, $tempFilename)
|
public function saveThumbnail(Photo $photo, $tempFilename, $thumbnail = null)
|
||||||
{
|
|
||||||
$this->saveThumbnail($photo, $tempFilename, $this->getOriginalsFolder());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveThumbnail(Photo $photo, $tempFilename, $thumbnail)
|
|
||||||
{
|
{
|
||||||
$fileInfo = new File($tempFilename);
|
$fileInfo = new File($tempFilename);
|
||||||
$fileInfo->move(sprintf('%s/%s', $this->getPathToAlbum(), $thumbnail), $photo->storage_file_name);
|
$fileInfo->move(
|
||||||
|
join(DIRECTORY_SEPARATOR, [
|
||||||
|
$this->getPathToAlbum(),
|
||||||
|
is_null($thumbnail) ? $this->getOriginalsFolder() : $thumbnail
|
||||||
|
]),
|
||||||
|
$photo->storage_file_name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveUploadedPhoto(File $uploadedFile, $overrideFilename = null)
|
private function getOriginalsFolder()
|
||||||
{
|
{
|
||||||
$tempFilename = sprintf(
|
return '_originals';
|
||||||
'%s/%s/%s',
|
|
||||||
$this->getPathToAlbum(),
|
|
||||||
$this->getOriginalsFolder(),
|
|
||||||
is_null($overrideFilename) ? MiscHelper::randomString(20) : basename($overrideFilename)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Only add an extension if an override filename was not given, assume this is present
|
|
||||||
if (is_null($overrideFilename))
|
|
||||||
{
|
|
||||||
$extension = $uploadedFile->guessExtension();
|
|
||||||
if (!is_null($extension))
|
|
||||||
{
|
|
||||||
$tempFilename .= '.' . $extension;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$uploadedFile->move(dirname($tempFilename), basename($tempFilename));
|
|
||||||
return new File($tempFilename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getPathToAlbum()
|
private function getPathToAlbum()
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
namespace App\AlbumSources;
|
namespace App\AlbumSources;
|
||||||
use App\Photo;
|
use App\Photo;
|
||||||
|
use Guzzle\Http\EntityBody;
|
||||||
|
use Guzzle\Http\Exception\ClientErrorResponseException;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use OpenCloud\ObjectStore\Exception\ObjectNotFoundException;
|
||||||
use OpenCloud\OpenStack;
|
use OpenCloud\OpenStack;
|
||||||
use Symfony\Component\HttpFoundation\File\File;
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
@ -17,7 +21,8 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
*/
|
*/
|
||||||
public function deleteAlbumContents()
|
public function deleteAlbumContents()
|
||||||
{
|
{
|
||||||
// TODO: Implement deleteAlbumContents() method.
|
// Because all photos live in a single container, there is nothing "global" to delete for the entire album
|
||||||
|
// The delete routine will have already removed all photos
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,7 +33,30 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
*/
|
*/
|
||||||
public function deleteThumbnail(Photo $photo, $thumbnail = null)
|
public function deleteThumbnail(Photo $photo, $thumbnail = null)
|
||||||
{
|
{
|
||||||
// TODO: Implement deleteThumbnail() method.
|
$photoPath = $this->getPathToPhoto($photo, $thumbnail);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->getContainer()->deleteObject($photoPath);
|
||||||
|
}
|
||||||
|
catch (ClientErrorResponseException $ex)
|
||||||
|
{
|
||||||
|
// Don't worry if the file no longer exists
|
||||||
|
Log::warning('Failed deleting image from OpenStack.', ['error' => $ex->getMessage(), 'path' => $photoPath]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the contents of a thumbnail for a photo.
|
||||||
|
* @param Photo $photo Photo to fetch the thumbnail for.
|
||||||
|
* @param string $thumbnail Thumbnail to fetch (or null to fetch the original.)
|
||||||
|
* @return EntityBody
|
||||||
|
*/
|
||||||
|
public function fetchPhotoContent(Photo $photo, $thumbnail = null)
|
||||||
|
{
|
||||||
|
$object = $this->getContainer()->getObject($this->getPathToPhoto($photo, $thumbnail));
|
||||||
|
|
||||||
|
return $object->getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,17 +68,6 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
return 'global.album_sources.openstack';
|
return 'global.album_sources.openstack';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the absolute path to the given photo file.
|
|
||||||
* @param Photo $photo Photo to get the path to.
|
|
||||||
* @param string $thumbnail Thumbnail to get the image to.
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getPathToPhoto(Photo $photo, $thumbnail = null)
|
|
||||||
{
|
|
||||||
// TODO: Implement getPathToPhoto() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the absolute URL to the given photo file.
|
* Gets the absolute URL to the given photo file.
|
||||||
* @param Photo $photo Photo to get the URL to.
|
* @param Photo $photo Photo to get the URL to.
|
||||||
@ -59,48 +76,47 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
*/
|
*/
|
||||||
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
public function getUrlToPhoto(Photo $photo, $thumbnail = null)
|
||||||
{
|
{
|
||||||
return sprintf(
|
$cdnUrl = $this->configuration->cdn_url;
|
||||||
'%s/%s/%s/%s',
|
if (strlen($cdnUrl) > 0)
|
||||||
'https://photos-dev-898b0644.cdn.memsites.com',
|
{
|
||||||
$this->album->url_alias,
|
if (substr($cdnUrl, strlen($cdnUrl) - 1, 1) == '/')
|
||||||
is_null($thumbnail) ? $this->getOriginalsFolder() : $thumbnail,
|
{
|
||||||
$photo->storage_file_name
|
$cdnUrl = substr($cdnUrl, 0, strlen($cdnUrl) - 1);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function saveOriginal(Photo $photo, $tempFilename)
|
return sprintf('%s/%s', $cdnUrl, $this->getPathToPhoto($photo, $thumbnail));
|
||||||
{
|
}
|
||||||
$this->saveThumbnail($photo, $tempFilename, $this->getOriginalsFolder());
|
|
||||||
|
// Not using a CDN - use the standard download controller
|
||||||
|
$photoUrl = route('downloadPhoto', [
|
||||||
|
'albumUrlAlias' => $this->album->url_alias,
|
||||||
|
'photoFilename' => $photo->storage_file_name
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!is_null($thumbnail))
|
||||||
|
{
|
||||||
|
$photoUrl .= sprintf('?t=%s', urlencode($thumbnail));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $photoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a generated thumbnail to its permanent location.
|
* Saves a generated thumbnail to its permanent location.
|
||||||
* @param Photo $photo Photo the thumbnail relates to.
|
* @param Photo $photo Photo the image relates to.
|
||||||
* @param string $tempFilename Filename containing the thumbnail.
|
* @param string $tempFilename Filename containing the image.
|
||||||
* @param string $thumbnail Name of the thumbnail.
|
* @param string $thumbnail Name of the thumbnail (or null for the original.)
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function saveThumbnail(Photo $photo, $tempFilename, $thumbnail)
|
public function saveThumbnail(Photo $photo, $tempFilename, $thumbnail = null)
|
||||||
{
|
{
|
||||||
|
$photoPath = $this->getPathToPhoto($photo, $thumbnail);
|
||||||
|
|
||||||
$container = $this->getContainer();
|
$container = $this->getContainer();
|
||||||
$container->uploadObject(
|
$container->uploadObject($photoPath, fopen($tempFilename, 'r+'));
|
||||||
sprintf('%s/%s/%s', $this->album->url_alias, $thumbnail, $photo->storage_file_name),
|
|
||||||
fopen($tempFilename, 'r+')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function getClient()
|
||||||
* Saves an uploaded file to the container and returns the filename.
|
|
||||||
* @param File $uploadedFile The photo uploaded
|
|
||||||
* @param string $overrideFilename Specific file name to use, or null to randomly generate one.
|
|
||||||
* @return File
|
|
||||||
*/
|
|
||||||
public function saveUploadedPhoto(File $uploadedFile, $overrideFilename = null)
|
|
||||||
{
|
|
||||||
// TODO: Implement saveUploadedPhoto() method.
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getClient()
|
|
||||||
{
|
{
|
||||||
return new OpenStack($this->configuration->auth_url, [
|
return new OpenStack($this->configuration->auth_url, [
|
||||||
'username' => $this->configuration->username,
|
'username' => $this->configuration->username,
|
||||||
@ -109,17 +125,12 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getContainer()
|
protected function getContainer()
|
||||||
{
|
{
|
||||||
return $this->getStorageService()->getContainer($this->configuration->container_name);
|
return $this->getStorageService()->getContainer($this->configuration->container_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getOriginalsFolder()
|
protected function getStorageService()
|
||||||
{
|
|
||||||
return '_originals';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getStorageService()
|
|
||||||
{
|
{
|
||||||
return $this->getClient()->objectStoreService(
|
return $this->getClient()->objectStoreService(
|
||||||
$this->configuration->service_name,
|
$this->configuration->service_name,
|
||||||
@ -127,4 +138,19 @@ class OpenStackSource extends AlbumSourceBase implements IAlbumSource
|
|||||||
'publicURL'
|
'publicURL'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getOriginalsFolder()
|
||||||
|
{
|
||||||
|
return '_originals';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPathToPhoto(Photo $photo, $thumbnail = null)
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'%s/%s/%s',
|
||||||
|
$this->album->url_alias,
|
||||||
|
is_null($thumbnail) ? $this->getOriginalsFolder() : $thumbnail,
|
||||||
|
$photo->storage_file_name
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
@ -112,7 +112,8 @@ class PhotoController extends Controller
|
|||||||
{
|
{
|
||||||
$this->authorize('admin-access');
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
settype($direction, 'boolean');
|
settype($horizontal, 'boolean');
|
||||||
|
settype($vertical, 'boolean');
|
||||||
|
|
||||||
$photo = Photo::where('id', intval($photoId))->first();
|
$photo = Photo::where('id', intval($photoId))->first();
|
||||||
if (is_null($photo))
|
if (is_null($photo))
|
||||||
@ -407,9 +408,7 @@ class PhotoController extends Controller
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$numberChanged = 0;
|
if ($request->has('bulk-action'))
|
||||||
|
|
||||||
if ($request->has('bulk-apply'))
|
|
||||||
{
|
{
|
||||||
$numberChanged = $this->applyBulkActions($request, $album);
|
$numberChanged = $this->applyBulkActions($request, $album);
|
||||||
}
|
}
|
||||||
@ -445,6 +444,7 @@ class PhotoController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$photoService = new PhotoService($photo);
|
$photoService = new PhotoService($photo);
|
||||||
|
$doNotSave = false;
|
||||||
switch (strtolower($action))
|
switch (strtolower($action))
|
||||||
{
|
{
|
||||||
case 'change_album':
|
case 'change_album':
|
||||||
@ -466,6 +466,7 @@ class PhotoController extends Controller
|
|||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
$photoService->delete();
|
$photoService->delete();
|
||||||
|
$doNotSave = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'flip_both':
|
case 'flip_both':
|
||||||
@ -493,7 +494,10 @@ class PhotoController extends Controller
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$photo->save();
|
if (!$doNotSave)
|
||||||
|
{
|
||||||
|
$photo->save();
|
||||||
|
}
|
||||||
|
|
||||||
$numberChanged++;
|
$numberChanged++;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,8 @@ class StorageController extends Controller
|
|||||||
'password',
|
'password',
|
||||||
'service_name',
|
'service_name',
|
||||||
'service_region',
|
'service_region',
|
||||||
'container_name'
|
'container_name',
|
||||||
|
'cdn_url'
|
||||||
]));
|
]));
|
||||||
$storage->is_active = true;
|
$storage->is_active = true;
|
||||||
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
||||||
@ -164,6 +165,11 @@ class StorageController extends Controller
|
|||||||
App::abort(404);
|
App::abort(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($storage->password) && strlen($storage->password) > 0)
|
||||||
|
{
|
||||||
|
$storage->password = decrypt($storage->password);
|
||||||
|
}
|
||||||
|
|
||||||
return Theme::render('admin.edit_storage', ['storage' => $storage]);
|
return Theme::render('admin.edit_storage', ['storage' => $storage]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +190,17 @@ class StorageController extends Controller
|
|||||||
App::abort(404);
|
App::abort(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$storage->fill($request->only(['name']));
|
$storage->fill($request->only([
|
||||||
|
'name',
|
||||||
|
'auth_url',
|
||||||
|
'tenant_name',
|
||||||
|
'username',
|
||||||
|
'password',
|
||||||
|
'service_name',
|
||||||
|
'service_region',
|
||||||
|
'container_name',
|
||||||
|
'cdn_url'
|
||||||
|
]));
|
||||||
$storage->is_active = (strtolower($request->get('is_active')) == 'on');
|
$storage->is_active = (strtolower($request->get('is_active')) == 'on');
|
||||||
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
$storage->is_default = (strtolower($request->get('is_default')) == 'on');
|
||||||
|
|
||||||
@ -193,6 +209,11 @@ class StorageController extends Controller
|
|||||||
$storage->is_default = false;
|
$storage->is_default = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($storage->password) && !empty($storage->password))
|
||||||
|
{
|
||||||
|
$storage->password = encrypt($storage->password);
|
||||||
|
}
|
||||||
|
|
||||||
$storage->save();
|
$storage->save();
|
||||||
|
|
||||||
if ($storage->is_default)
|
if ($storage->is_default)
|
||||||
|
@ -11,6 +11,7 @@ use app\Http\Controllers\Admin\AlbumController;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Middleware\VerifyCsrfToken;
|
use App\Http\Middleware\VerifyCsrfToken;
|
||||||
use App\Photo;
|
use App\Photo;
|
||||||
|
use Guzzle\Http\Mimetypes;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@ -53,10 +54,23 @@ class PhotoController extends Controller
|
|||||||
$thumbnail = $request->get('t');
|
$thumbnail = $request->get('t');
|
||||||
if (is_null($thumbnail))
|
if (is_null($thumbnail))
|
||||||
{
|
{
|
||||||
Gate::forUser($this->getUser())->authorize('photo.download_original', $photo);
|
$this->authorizeForUser($this->getUser(), 'photo.download_original', $photo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->file($album->getAlbumSource()->getPathToPhoto($photo, $thumbnail));
|
$photoStream = $album->getAlbumSource()->fetchPhotoContent($photo, $thumbnail);
|
||||||
|
$mimeType = Mimetypes::getInstance()->fromFilename($photo->storage_file_name);
|
||||||
|
|
||||||
|
return response()->stream(
|
||||||
|
function() use ($photoStream)
|
||||||
|
{
|
||||||
|
echo $photoStream;
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Content-Length' => $photoStream->getContentLength(),
|
||||||
|
'Content-Type' => $mimeType
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Request $request, $albumUrlAlias, $photoFilename)
|
public function show(Request $request, $albumUrlAlias, $photoFilename)
|
||||||
|
@ -24,6 +24,8 @@ class StoreStorageRequest extends FormRequest
|
|||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
switch ($this->method())
|
switch ($this->method())
|
||||||
{
|
{
|
||||||
case 'POST':
|
case 'POST':
|
||||||
@ -46,19 +48,35 @@ class StoreStorageRequest extends FormRequest
|
|||||||
$result['service_name'] = 'sometimes|required';
|
$result['service_name'] = 'sometimes|required';
|
||||||
$result['service_region'] = 'sometimes|required';
|
$result['service_region'] = 'sometimes|required';
|
||||||
$result['container_name'] = 'sometimes|required';
|
$result['container_name'] = 'sometimes|required';
|
||||||
|
$result['cdn_url'] = 'sometimes|url';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
return $result;
|
|
||||||
|
|
||||||
case 'PATCH':
|
case 'PATCH':
|
||||||
case 'PUT':
|
case 'PUT':
|
||||||
$storageId = intval($this->segment(3));
|
$storageId = intval($this->segment(3));
|
||||||
|
$storage = Storage::find($storageId);
|
||||||
return [
|
$result = [
|
||||||
'name' => 'required|max:255|unique:storages,name,' . $storageId
|
'name' => 'required|max:255|unique:storages,name,' . $storageId
|
||||||
];
|
];
|
||||||
|
|
||||||
|
switch ($storage->source)
|
||||||
|
{
|
||||||
|
case 'OpenStackSource':
|
||||||
|
$result['auth_url'] = 'sometimes|required';
|
||||||
|
$result['tenant_name'] = 'sometimes|required';
|
||||||
|
$result['username'] = 'sometimes|required';
|
||||||
|
$result['password'] = 'sometimes|required';
|
||||||
|
$result['service_name'] = 'sometimes|required';
|
||||||
|
$result['service_region'] = 'sometimes|required';
|
||||||
|
$result['container_name'] = 'sometimes|required';
|
||||||
|
$result['cdn_url'] = 'sometimes|url';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -117,7 +117,7 @@ class PhotoService
|
|||||||
$this->photo->save();
|
$this->photo->save();
|
||||||
|
|
||||||
// Save the original
|
// Save the original
|
||||||
$this->albumSource->saveOriginal($this->photo, $photoFile);
|
$this->albumSource->saveThumbnail($this->photo, $photoFile);
|
||||||
|
|
||||||
$this->regenerateThumbnails($originalPhotoResource);
|
$this->regenerateThumbnails($originalPhotoResource);
|
||||||
}
|
}
|
||||||
@ -128,11 +128,11 @@ class PhotoService
|
|||||||
$currentSource = $this->photo->album->getAlbumSource();
|
$currentSource = $this->photo->album->getAlbumSource();
|
||||||
$newSource = $newAlbum->getAlbumSource();
|
$newSource = $newAlbum->getAlbumSource();
|
||||||
|
|
||||||
// Get the current photo's path
|
// First export the original photo from the storage provider
|
||||||
$file = new File($currentSource->getPathToPhoto($this->photo));
|
$photoPath = $this->downloadToTemporaryFolder();
|
||||||
|
|
||||||
// Save to the new album
|
// Save to the new album
|
||||||
$newSource->saveUploadedPhoto($file, $this->photo->storage_file_name);
|
$newSource->saveThumbnail($this->photo, $photoPath);
|
||||||
|
|
||||||
// Delete the original
|
// Delete the original
|
||||||
$this->delete();
|
$this->delete();
|
||||||
@ -169,25 +169,39 @@ class PhotoService
|
|||||||
|
|
||||||
public function flip($horizontal, $vertical)
|
public function flip($horizontal, $vertical)
|
||||||
{
|
{
|
||||||
|
// First export the original photo from the storage provider
|
||||||
|
$photoPath = $this->downloadToTemporaryFolder();
|
||||||
|
|
||||||
$imageInfo = array();
|
$imageInfo = array();
|
||||||
$photoPath = $this->albumSource->getPathToPhoto($this->photo);
|
|
||||||
$originalPhotoImage = $this->imageHelper->openImage($photoPath, $imageInfo);
|
$originalPhotoImage = $this->imageHelper->openImage($photoPath, $imageInfo);
|
||||||
if ($this->imageHelper->flipImage($originalPhotoImage, boolval($horizontal), boolval($vertical)))
|
if ($this->imageHelper->flipImage($originalPhotoImage, boolval($horizontal), boolval($vertical)))
|
||||||
{
|
{
|
||||||
$this->imageHelper->saveImage($originalPhotoImage, $photoPath, $imageInfo);
|
$this->imageHelper->saveImage($originalPhotoImage, $photoPath, $imageInfo);
|
||||||
|
|
||||||
|
// Update and save the original image back to the storage provider
|
||||||
|
$this->albumSource->saveThumbnail($this->photo, $photoPath);
|
||||||
|
|
||||||
|
// Re-create the thumbnails
|
||||||
$this->regenerateThumbnails($originalPhotoImage);
|
$this->regenerateThumbnails($originalPhotoImage);
|
||||||
|
|
||||||
$this->photo->save();
|
$this->photo->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the temp file
|
||||||
|
@unlink($photoPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function regenerateThumbnails($originalPhotoResource = null)
|
public function regenerateThumbnails($originalPhotoResource = null)
|
||||||
{
|
{
|
||||||
|
$photoPath = null;
|
||||||
|
|
||||||
if (is_null($originalPhotoResource))
|
if (is_null($originalPhotoResource))
|
||||||
{
|
{
|
||||||
|
// First export the original photo from the storage provider
|
||||||
|
$photoPath = $this->downloadToTemporaryFolder();
|
||||||
|
|
||||||
$imageInfo = null;
|
$imageInfo = null;
|
||||||
$originalPhotoResource = $this->imageHelper->openImage($this->albumSource->getPathToPhoto($this->photo), $imageInfo);
|
$originalPhotoResource = $this->imageHelper->openImage($photoPath, $imageInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate and save thumbnails
|
// Generate and save thumbnails
|
||||||
@ -200,16 +214,28 @@ class PhotoService
|
|||||||
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $this->photo, $thumbnail);
|
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $this->photo, $thumbnail);
|
||||||
$this->albumSource->saveThumbnail($this->photo, $generatedThumbnailPath, $thumbnail['name']);
|
$this->albumSource->saveThumbnail($this->photo, $generatedThumbnailPath, $thumbnail['name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_null($originalPhotoResource) && !is_null($photoPath))
|
||||||
|
{
|
||||||
|
// Remove the temp file
|
||||||
|
@unlink($photoPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rotate($angle)
|
public function rotate($angle)
|
||||||
{
|
{
|
||||||
$imageInfo = array();
|
$imageInfo = array();
|
||||||
$photoPath = $this->albumSource->getPathToPhoto($this->photo);
|
|
||||||
|
// First export the photo from the storage provider
|
||||||
|
$photoPath = $this->downloadToTemporaryFolder();
|
||||||
|
|
||||||
$originalPhotoImage = $this->imageHelper->openImage($photoPath, $imageInfo);
|
$originalPhotoImage = $this->imageHelper->openImage($photoPath, $imageInfo);
|
||||||
$originalPhotoImage = $this->imageHelper->rotateImage($originalPhotoImage, intval($angle));
|
$originalPhotoImage = $this->imageHelper->rotateImage($originalPhotoImage, intval($angle));
|
||||||
$this->imageHelper->saveImage($originalPhotoImage, $photoPath, $imageInfo);
|
$this->imageHelper->saveImage($originalPhotoImage, $photoPath, $imageInfo);
|
||||||
|
|
||||||
|
// Update and save the original image back to the storage provider
|
||||||
|
$this->albumSource->saveThumbnail($this->photo, $photoPath);
|
||||||
|
|
||||||
if ($angle == 90 || $angle == 270)
|
if ($angle == 90 || $angle == 270)
|
||||||
{
|
{
|
||||||
$width = $this->photo->width;
|
$width = $this->photo->width;
|
||||||
@ -220,6 +246,27 @@ class PhotoService
|
|||||||
$this->regenerateThumbnails($originalPhotoImage);
|
$this->regenerateThumbnails($originalPhotoImage);
|
||||||
|
|
||||||
$this->photo->save();
|
$this->photo->save();
|
||||||
|
|
||||||
|
// Remove the temp file
|
||||||
|
@unlink($photoPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function downloadToTemporaryFolder()
|
||||||
|
{
|
||||||
|
$photoPath = tempnam(sys_get_temp_dir(), 'BlueTwilight_');
|
||||||
|
$photoHandle = fopen($photoPath, 'w');
|
||||||
|
|
||||||
|
$stream = $this->albumSource->fetchPhotoContent($this->photo);
|
||||||
|
$stream->rewind();
|
||||||
|
while (!$stream->feof())
|
||||||
|
{
|
||||||
|
fwrite($photoHandle, $stream->read(4096));
|
||||||
|
}
|
||||||
|
fflush($photoHandle);
|
||||||
|
fclose($photoHandle);
|
||||||
|
$stream->close();
|
||||||
|
|
||||||
|
return $photoPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function metadataCameraMake(array $exifData)
|
private function metadataCameraMake(array $exifData)
|
||||||
|
@ -27,7 +27,8 @@ class Storage extends Model
|
|||||||
'password',
|
'password',
|
||||||
'service_name',
|
'service_name',
|
||||||
'service_region',
|
'service_region',
|
||||||
'container_name'
|
'container_name',
|
||||||
|
'cdn_url'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function albums()
|
public function albums()
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddCdnUrlStorageColumn extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('storages', function (Blueprint $table) {
|
||||||
|
$table->string('cdn_url')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('storages', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('cdn_url');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -93,10 +93,16 @@ function EditPhotosViewModel(album_id, language, urls) {
|
|||||||
self.albums = ko.observableArray();
|
self.albums = ko.observableArray();
|
||||||
self.bulkModifyMethod = ko.observable();
|
self.bulkModifyMethod = ko.observable();
|
||||||
self.photoIDs = ko.observableArray();
|
self.photoIDs = ko.observableArray();
|
||||||
|
self.isSubmitting = false;
|
||||||
|
|
||||||
/* Called when the Apply button on the "bulk apply selected actions" form is clicked */
|
/* Called when the Apply button on the "bulk apply selected actions" form is clicked */
|
||||||
self.bulkModifySelected = function()
|
self.bulkModifySelected = function()
|
||||||
{
|
{
|
||||||
|
if (self.isSubmitting)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var bulk_form = $('form#bulk-modify-form');
|
var bulk_form = $('form#bulk-modify-form');
|
||||||
|
|
||||||
if (self.bulkModifyMethod() == 'change_album')
|
if (self.bulkModifyMethod() == 'change_album')
|
||||||
@ -104,8 +110,11 @@ function EditPhotosViewModel(album_id, language, urls) {
|
|||||||
// Prompt for the new album to move to
|
// Prompt for the new album to move to
|
||||||
self.promptForNewAlbum(function(dialog) {
|
self.promptForNewAlbum(function(dialog) {
|
||||||
var album_id = $('select', dialog).val();
|
var album_id = $('select', dialog).val();
|
||||||
$('input[name="new-album-id"]', form).val(album_id);
|
$('input[name="new-album-id"]', bulk_form).val(album_id);
|
||||||
bulk_form.submit();
|
|
||||||
|
self.isSubmitting = true;
|
||||||
|
$('button[name="bulk-apply"]', bulk_form).click();
|
||||||
|
_bt_showLoadingModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -125,7 +134,9 @@ function EditPhotosViewModel(album_id, language, urls) {
|
|||||||
label: language.action_delete,
|
label: language.action_delete,
|
||||||
className: "btn-danger",
|
className: "btn-danger",
|
||||||
callback: function() {
|
callback: function() {
|
||||||
bulk_form.submit();
|
self.isSubmitting = true;
|
||||||
|
$('button[name="bulk-apply"]', bulk_form).click();
|
||||||
|
_bt_showLoadingModal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,6 +162,7 @@ function EditPhotosViewModel(album_id, language, urls) {
|
|||||||
{
|
{
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
_bt_showLoadingModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -31,6 +31,7 @@ return [
|
|||||||
'settings_restrict_originals_download_help' => 'With this option enabled, only the photo\'s owner can download the original high-resolution images.',
|
'settings_restrict_originals_download_help' => 'With this option enabled, only the photo\'s owner can download the original high-resolution images.',
|
||||||
'storage_active_label' => 'Location is active. Uncheck to prevent creating new albums in this location.',
|
'storage_active_label' => 'Location is active. Uncheck to prevent creating new albums in this location.',
|
||||||
'storage_auth_url_label' => 'Authentication URL:',
|
'storage_auth_url_label' => 'Authentication URL:',
|
||||||
|
'storage_cdn_url_label' => 'Public CDN URL (if supported and enabled):',
|
||||||
'storage_container_name_label' => 'Container name:',
|
'storage_container_name_label' => 'Container name:',
|
||||||
'storage_driver_label' => 'Storage driver:',
|
'storage_driver_label' => 'Storage driver:',
|
||||||
'storage_location_label' => 'Physical location:',
|
'storage_location_label' => 'Physical location:',
|
||||||
|
@ -8,6 +8,7 @@ return [
|
|||||||
'copyright' => '© :years :link_startAndy Heathershaw:link_end',
|
'copyright' => '© :years :link_startAndy Heathershaw:link_end',
|
||||||
'licensed_to' => 'Licensed to :name (:number)',
|
'licensed_to' => 'Licensed to :name (:number)',
|
||||||
'version_number' => 'Version :version',
|
'version_number' => 'Version :version',
|
||||||
|
'please_wait' => 'Please wait...',
|
||||||
'post_max_exceeded' => 'Your upload exceeded the maximum size the web server is configured to allow. Please check the value of the "post_max_size" parameter in php.ini.',
|
'post_max_exceeded' => 'Your upload exceeded the maximum size the web server is configured to allow. Please check the value of the "post_max_size" parameter in php.ini.',
|
||||||
'powered_by' => 'Powered by :link_startBlue Twilight:link_end - the self-hosted photo gallery software.',
|
'powered_by' => 'Powered by :link_startBlue Twilight:link_end - the self-hosted photo gallery software.',
|
||||||
'units' => [
|
'units' => [
|
||||||
|
@ -145,6 +145,17 @@
|
|||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group{{ $errors->has('cdn_url') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('cdn_url', trans('forms.storage_cdn_url_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('cdn_url', old('cdn_url'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('cdn_url'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('cdn_url') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -48,6 +48,112 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if ($storage->source == 'OpenStackSource')
|
||||||
|
<hr/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group{{ $errors->has('auth_url') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('auth_url', trans('forms.storage_auth_url_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('auth_url', old('auth_url'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('auth_url'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('auth_url') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group{{ $errors->has('tenant_name') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('tenant_name', trans('forms.storage_tenant_name_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('tenant_name', old('tenant_name'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('tenant_name'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('tenant_name') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group{{ $errors->has('username') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('username', trans('forms.username_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('username', old('username'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('username'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('username') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('password', trans('forms.password_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('password', old('password'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('password'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('password') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group{{ $errors->has('service_name') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('service_name', trans('forms.storage_service_name_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('service_name', old('service_name'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('service_name'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('service_name') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group{{ $errors->has('service_region') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('service_region', trans('forms.storage_service_region_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('service_region', old('service_region'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('service_region'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('service_region') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group{{ $errors->has('container_name') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('container_name', trans('forms.storage_container_name_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('container_name', old('container_name'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('container_name'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('container_name') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group{{ $errors->has('cdn_url') ? ' has-error' : '' }}">
|
||||||
|
{!! Form::label('cdn_url', trans('forms.storage_cdn_url_label'), ['class' => 'control-label']) !!}
|
||||||
|
{!! Form::text('cdn_url', old('cdn_url'), ['class' => 'form-control']) !!}
|
||||||
|
|
||||||
|
@if ($errors->has('cdn_url'))
|
||||||
|
<span class="help-block">
|
||||||
|
<strong>{{ $errors->first('cdn_url') }}</strong>
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<a href="{{ route('storage.index') }}" class="btn btn-default">@lang('forms.cancel_action')</a>
|
<a href="{{ route('storage.index') }}" class="btn btn-default">@lang('forms.cancel_action')</a>
|
||||||
{!! Form::submit(trans('forms.save_action'), ['class' => 'btn btn-success']) !!}
|
{!! Form::submit(trans('forms.save_action'), ['class' => 'btn btn-success']) !!}
|
||||||
|
@ -78,6 +78,14 @@
|
|||||||
<script src="themes/base/js/knockout.min.js?v={{ urlencode(config('app.version')) }}"></script>
|
<script src="themes/base/js/knockout.min.js?v={{ urlencode(config('app.version')) }}"></script>
|
||||||
<script src="themes/base/js/app.js?v={{ urlencode(config('app.version')) }}"></script>
|
<script src="themes/base/js/app.js?v={{ urlencode(config('app.version')) }}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
function _bt_showLoadingModal() {
|
||||||
|
bootbox.dialog({
|
||||||
|
closeButton: false,
|
||||||
|
message: '<center><img src="{{ asset('ripple.svg') }}"/></center>',
|
||||||
|
title: '@lang('global.please_wait')'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
||||||
|
Loading…
Reference in New Issue
Block a user