Added the ability to create, edit and remove external services. Implemented an OAuth2 flow for authentication to Dropbox. #106
This commit is contained in:
parent
09b4bc60dd
commit
d97b790264
@ -6,18 +6,36 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class ExternalService extends Model
|
class ExternalService extends Model
|
||||||
{
|
{
|
||||||
public const DROPBOX = 'Dropbox';
|
public const DROPBOX = 'dropbox';
|
||||||
public const FACEBOOK = 'Facebook';
|
public const FACEBOOK = 'facebook';
|
||||||
public const GOOGLE = 'Google';
|
public const GOOGLE = 'google';
|
||||||
public const TWITTER = 'Twitter';
|
public const TWITTER = 'twitter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the details for the given service type.
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = ['name', 'service_type'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all possible service configurations for the given service type.
|
||||||
* @param $serviceType
|
* @param $serviceType
|
||||||
* @return ExternalService
|
* @return ExternalService[]
|
||||||
*/
|
*/
|
||||||
public static function getForService($serviceType)
|
public static function getForService($serviceType)
|
||||||
{
|
{
|
||||||
return ExternalService::where('service_type', $serviceType)->first();
|
return ExternalService::where('service_type', $serviceType)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasOAuthStandardOptions()
|
||||||
|
{
|
||||||
|
// This list must be mirrored in external_services.js
|
||||||
|
return in_array($this->service_type, [
|
||||||
|
self::DROPBOX,
|
||||||
|
self::FACEBOOK,
|
||||||
|
self::GOOGLE,
|
||||||
|
self::TWITTER
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,9 +2,239 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\ExternalService;
|
||||||
|
use App\Facade\Theme;
|
||||||
|
use App\Facade\UserConfig;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\StoreServiceRequest;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
|
|
||||||
class ServiceController extends Controller
|
class ServiceController extends Controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* List of fields that must be encrypted before being saved.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $fieldsToEncrypt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of fields that depend on the service_type being configured.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
private $serviceTypeDependentFields;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
View::share('is_admin', true);
|
||||||
|
|
||||||
|
$this->serviceTypeDependentFields = ['app_id', 'app_secret'];
|
||||||
|
$this->fieldsToEncrypt = ['app_id', 'app_secret'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||||
|
|
||||||
|
return Theme::render('admin.create_service', [
|
||||||
|
'service' => new ExternalService(),
|
||||||
|
'serviceTypes' => $this->serviceTypeList()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-users');
|
||||||
|
|
||||||
|
$service = ExternalService::where('id', intval($id))->first();
|
||||||
|
if (is_null($service))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isServiceInUse($service))
|
||||||
|
{
|
||||||
|
$request->session()->flash('warning', trans('admin.cannot_delete_service_in_use'));
|
||||||
|
return redirect(route('services.index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Theme::render('admin.delete_service', ['service' => $service]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function destroy(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||||
|
|
||||||
|
$service = ExternalService::where('id', intval($id))->first();
|
||||||
|
if (is_null($service))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isServiceInUse($service))
|
||||||
|
{
|
||||||
|
$request->session()->flash('warning', trans('admin.cannot_delete_service_in_use'));
|
||||||
|
return redirect(route('services.index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$service->delete();
|
||||||
|
$request->session()->flash('success', trans('admin.service_deletion_successful', [
|
||||||
|
'name' => $service->name
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
$request->session()->flash('error', trans('admin.service_deletion_failed', [
|
||||||
|
'error_message' => $ex->getMessage(),
|
||||||
|
'name' => $service->name
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(route('services.index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function edit(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||||
|
|
||||||
|
$service = ExternalService::where('id', intval($id))->first();
|
||||||
|
if (is_null($service))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the fields that are stored as encrypted in the DB
|
||||||
|
foreach ($this->fieldsToEncrypt as $field)
|
||||||
|
{
|
||||||
|
if (!empty($service->$field))
|
||||||
|
{
|
||||||
|
$service->$field = decrypt($service->$field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Theme::render('admin.edit_service', [
|
||||||
|
'service' => $service,
|
||||||
|
'serviceTypes' => $this->serviceTypeList()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||||
|
|
||||||
|
$services = ExternalService::orderBy('name')
|
||||||
|
->paginate(UserConfig::get('items_per_page'));
|
||||||
|
|
||||||
|
return Theme::render('admin.list_services', [
|
||||||
|
'error' => $request->session()->get('error'),
|
||||||
|
'services' => $services,
|
||||||
|
'success' => $request->session()->get('success'),
|
||||||
|
'warning' => $request->session()->get('warning')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function store(StoreServiceRequest $request)
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||||
|
|
||||||
|
$service = new ExternalService($request->only(['name', 'service_type']));
|
||||||
|
|
||||||
|
foreach ($this->serviceTypeDependentFields as $field)
|
||||||
|
{
|
||||||
|
if ($request->has($field))
|
||||||
|
{
|
||||||
|
$service->$field = in_array($field, $this->fieldsToEncrypt)
|
||||||
|
? encrypt($request->get($field))
|
||||||
|
: $request->get($field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$service->save();
|
||||||
|
|
||||||
|
return redirect(route('services.index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param int $id
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function update(StoreServiceRequest $request, $id)
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-services');
|
||||||
|
|
||||||
|
$service = ExternalService::where('id', intval($id))->first();
|
||||||
|
if (is_null($service))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$service->fill($request->only(['name', 'service_type']));
|
||||||
|
|
||||||
|
foreach ($this->serviceTypeDependentFields as $field)
|
||||||
|
{
|
||||||
|
if ($request->has($field))
|
||||||
|
{
|
||||||
|
$service->$field = in_array($field, $this->fieldsToEncrypt)
|
||||||
|
? encrypt($request->get($field))
|
||||||
|
: $request->get($field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$service->save();
|
||||||
|
|
||||||
|
return redirect(route('services.index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isServiceInUse(ExternalService $service)
|
||||||
|
{
|
||||||
|
// TODO check if the service is in use anywhere else and prevent it being deleted if so
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serviceTypeList()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
ExternalService::DROPBOX => trans(sprintf('services.%s', ExternalService::DROPBOX)),
|
||||||
|
ExternalService::FACEBOOK => trans(sprintf('services.%s', ExternalService::FACEBOOK)),
|
||||||
|
ExternalService::GOOGLE => trans(sprintf('services.%s', ExternalService::GOOGLE)),
|
||||||
|
ExternalService::TWITTER => trans(sprintf('services.%s', ExternalService::TWITTER))
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\ExternalService;
|
||||||
use App\Facade\Theme;
|
use App\Facade\Theme;
|
||||||
use App\Facade\UserConfig;
|
use App\Facade\UserConfig;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests;
|
use App\Http\Requests;
|
||||||
|
use App\Services\DropboxService;
|
||||||
use App\Storage;
|
use App\Storage;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
@ -26,6 +28,59 @@ class StorageController extends Controller
|
|||||||
$this->encryptedFields = ['password', 'access_key', 'secret_key', 'access_token'];
|
$this->encryptedFields = ['password', 'access_key', 'secret_key', 'access_token'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function authoriseService($id)
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-storage');
|
||||||
|
|
||||||
|
$storage = Storage::where('id', intval($id))->first();
|
||||||
|
if (is_null($storage))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($storage->externalService))
|
||||||
|
{
|
||||||
|
App::abort(400, 'Storage does not support an external service');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($storage->externalService->service_type)
|
||||||
|
{
|
||||||
|
case ExternalService::DROPBOX:
|
||||||
|
$dropbox = new DropboxService();
|
||||||
|
return redirect($dropbox->authoriseUrl($storage));
|
||||||
|
|
||||||
|
default:
|
||||||
|
App::abort(400, 'External service does not support authorisation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function completeServiceAuthorisation(Request $request, $id)
|
||||||
|
{
|
||||||
|
$this->authorizeAccessToAdminPanel('admin:manage-storage');
|
||||||
|
|
||||||
|
$storage = Storage::where('id', intval($id))->first();
|
||||||
|
if (is_null($storage))
|
||||||
|
{
|
||||||
|
App::abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($storage->externalService))
|
||||||
|
{
|
||||||
|
App::abort(400, 'Storage does not support an external service');
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($storage->externalService->service_type)
|
||||||
|
{
|
||||||
|
case ExternalService::DROPBOX:
|
||||||
|
$dropbox = new DropboxService();
|
||||||
|
$dropbox->handleAuthenticationResponse($request, $storage);
|
||||||
|
return redirect(route('storage.index'));
|
||||||
|
|
||||||
|
default:
|
||||||
|
App::abort(400, 'External service does not support authorisation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* Display a listing of the resource.
|
||||||
*
|
*
|
||||||
@ -60,6 +115,7 @@ class StorageController extends Controller
|
|||||||
|
|
||||||
return Theme::render('admin.create_storage', [
|
return Theme::render('admin.create_storage', [
|
||||||
'album_sources' => UserConfig::albumSources(),
|
'album_sources' => UserConfig::albumSources(),
|
||||||
|
'dropbox_services' => ExternalService::getForService(ExternalService::DROPBOX),
|
||||||
'filesystem_default_location' => $filesystemDefaultLocation,
|
'filesystem_default_location' => $filesystemDefaultLocation,
|
||||||
'info' => $request->session()->get('info'),
|
'info' => $request->session()->get('info'),
|
||||||
'storage' => $storage
|
'storage' => $storage
|
||||||
@ -92,7 +148,7 @@ class StorageController extends Controller
|
|||||||
'access_key',
|
'access_key',
|
||||||
'secret_key',
|
'secret_key',
|
||||||
'b2_bucket_type',
|
'b2_bucket_type',
|
||||||
'access_token'
|
'external_service_id'
|
||||||
]));
|
]));
|
||||||
$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');
|
||||||
@ -191,7 +247,10 @@ class StorageController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Theme::render('admin.edit_storage', ['storage' => $storage]);
|
return Theme::render('admin.edit_storage', [
|
||||||
|
'dropbox_services' => ExternalService::getForService(ExternalService::DROPBOX),
|
||||||
|
'storage' => $storage
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,8 +283,7 @@ class StorageController extends Controller
|
|||||||
'access_key',
|
'access_key',
|
||||||
'secret_key',
|
'secret_key',
|
||||||
'b2_bucket_type',
|
'b2_bucket_type',
|
||||||
'access_token',
|
'external_service_id'
|
||||||
's3_signed_urls'
|
|
||||||
]));
|
]));
|
||||||
$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');
|
||||||
|
74
app/Http/Requests/StoreServiceRequest.php
Normal file
74
app/Http/Requests/StoreServiceRequest.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\ExternalService;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreServiceRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
switch ($this->method())
|
||||||
|
{
|
||||||
|
case 'POST':
|
||||||
|
$result = [
|
||||||
|
'name' => 'required|unique:external_services|max:255',
|
||||||
|
'service_type' => 'required|max:255',
|
||||||
|
];
|
||||||
|
|
||||||
|
switch ($this->get('service_type'))
|
||||||
|
{
|
||||||
|
case ExternalService::DROPBOX:
|
||||||
|
case ExternalService::FACEBOOK:
|
||||||
|
case ExternalService::GOOGLE:
|
||||||
|
case ExternalService::TWITTER:
|
||||||
|
// Standard OAuth services
|
||||||
|
$result['app_id'] = 'sometimes|required';
|
||||||
|
$result['app_secret'] = 'sometimes|required';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'PATCH':
|
||||||
|
case 'PUT':
|
||||||
|
$serviceId = intval($this->segment(3));
|
||||||
|
$service = ExternalService::find($serviceId);
|
||||||
|
$result = [
|
||||||
|
'name' => 'required|max:255|unique:external_services,name,' . $serviceId
|
||||||
|
];
|
||||||
|
|
||||||
|
switch ($service->service_type)
|
||||||
|
{
|
||||||
|
case ExternalService::DROPBOX:
|
||||||
|
case ExternalService::FACEBOOK:
|
||||||
|
case ExternalService::GOOGLE:
|
||||||
|
case ExternalService::TWITTER:
|
||||||
|
// Standard OAuth services
|
||||||
|
$result['app_id'] = 'sometimes|required';
|
||||||
|
$result['app_secret'] = 'sometimes|required';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
@ -73,9 +73,7 @@ class StoreStorageRequest extends FormRequest
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'DropboxSource':
|
case 'DropboxSource':
|
||||||
$result['access_key'] = 'sometimes|required';
|
$result['external_service_id'] = 'sometimes|required';
|
||||||
$result['secret_key'] = 'sometimes|required';
|
|
||||||
$result['access_token'] = 'sometimes|required';
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -123,9 +121,7 @@ class StoreStorageRequest extends FormRequest
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'DropboxSource':
|
case 'DropboxSource':
|
||||||
$result['access_key'] = 'sometimes|required';
|
$result['external_service_id'] = 'sometimes|required';
|
||||||
$result['secret_key'] = 'sometimes|required';
|
|
||||||
$result['access_token'] = 'sometimes|required';
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Storage;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class DropboxService
|
class DropboxService
|
||||||
@ -22,6 +24,19 @@ class DropboxService
|
|||||||
$this->config = config('services.dropbox');
|
$this->config = config('services.dropbox');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function authoriseUrl(Storage $storage)
|
||||||
|
{
|
||||||
|
$service = $storage->externalService;
|
||||||
|
$redirectUrl = route('storage.completeServiceAuthorisation', ['storage' => $storage->id]);
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'%s?client_id=%s&response_type=code&redirect_uri=%s',
|
||||||
|
$this->config['authorise_url'],
|
||||||
|
urlencode(decrypt($service->app_id)),
|
||||||
|
urlencode($redirectUrl)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function downloadFile($pathOnStorage)
|
public function downloadFile($pathOnStorage)
|
||||||
{
|
{
|
||||||
$dropboxArgs = ['path' => $pathOnStorage];
|
$dropboxArgs = ['path' => $pathOnStorage];
|
||||||
@ -41,6 +56,16 @@ class DropboxService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function handleAuthenticationResponse(Request $request, Storage $storage)
|
||||||
|
{
|
||||||
|
$authorisationCode = $request->query('code');
|
||||||
|
|
||||||
|
$storage->access_token = encrypt($this->convertAuthorisationCodeToToken($authorisationCode, $storage));
|
||||||
|
$storage->save();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $accessToken
|
* @param string $accessToken
|
||||||
*/
|
*/
|
||||||
@ -71,6 +96,40 @@ class DropboxService
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function convertAuthorisationCodeToToken($authorisationCode, Storage $storage)
|
||||||
|
{
|
||||||
|
$service = $storage->externalService;
|
||||||
|
$credentials = sprintf('%s:%s', decrypt($service->app_id), decrypt($service->app_secret));
|
||||||
|
$redirectUrl = route('storage.completeServiceAuthorisation', ['storage' => $storage->id]);
|
||||||
|
|
||||||
|
$httpHeaders = [
|
||||||
|
'Accept: application/json',
|
||||||
|
sprintf('Authorization: Basic %s', base64_encode($credentials))
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init($this->config['token_url']);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, [
|
||||||
|
'code' => $authorisationCode,
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'redirect_uri' => $redirectUrl
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = json_decode(curl_exec($ch));
|
||||||
|
if (is_null($response) || $response === false)
|
||||||
|
{
|
||||||
|
throw new \Exception('Unable to read the response from Dropbox');
|
||||||
|
}
|
||||||
|
else if (isset($response->error_description))
|
||||||
|
{
|
||||||
|
throw new \Exception(sprintf('Error from Dropbox: %s', $response->error_description));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->access_token;
|
||||||
|
}
|
||||||
|
|
||||||
private function getBasicHttpClient($url, $method = 'GET', array $httpHeaders = [])
|
private function getBasicHttpClient($url, $method = 'GET', array $httpHeaders = [])
|
||||||
{
|
{
|
||||||
$httpHeaders = array_merge(
|
$httpHeaders = array_merge(
|
||||||
|
@ -32,11 +32,16 @@ class Storage extends Model
|
|||||||
'access_key',
|
'access_key',
|
||||||
'secret_key',
|
'secret_key',
|
||||||
'b2_bucket_type',
|
'b2_bucket_type',
|
||||||
'access_token'
|
'external_service_id'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function albums()
|
public function albums()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Album::class);
|
return $this->hasMany(Album::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function externalService()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ExternalService::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "pandy06269/blue-twilight",
|
"name": "aheathershaw/blue-twilight",
|
||||||
"description": "Blue Twilight - self-hosted photo gallery software.",
|
"description": "Blue Twilight - self-hosted photo gallery software.",
|
||||||
"keywords": ["blue", "twilight", "photo", "photograph", "portfolio", "gallery", "self-hosted"],
|
"keywords": ["blue", "twilight", "photo", "photograph", "portfolio", "gallery", "self-hosted"],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -20,7 +20,9 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
'dropbox' => [
|
'dropbox' => [
|
||||||
|
'authorise_url' => 'https://www.dropbox.com/oauth2/authorize',
|
||||||
'download_url' => 'https://content.dropboxapi.com/2/files/download',
|
'download_url' => 'https://content.dropboxapi.com/2/files/download',
|
||||||
|
'token_url' => 'https://api.dropbox.com/oauth2/token',
|
||||||
'upload_url' => 'https://content.dropboxapi.com/2/files/upload'
|
'upload_url' => 'https://content.dropboxapi.com/2/files/upload'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ class CreateExternalServicesTable extends Migration
|
|||||||
Schema::create('external_services', function (Blueprint $table) {
|
Schema::create('external_services', function (Blueprint $table) {
|
||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->string('service_type', 50);
|
$table->string('service_type', 50);
|
||||||
|
$table->string('name');
|
||||||
$table->text('app_id')->nullable();
|
$table->text('app_id')->nullable();
|
||||||
$table->text('app_secret')->nullable();
|
$table->text('app_secret')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddServiceToStoragesTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('storages', function (Blueprint $table) {
|
||||||
|
$table->unsignedInteger('external_service_id')->nullable();
|
||||||
|
|
||||||
|
$table->foreign('external_service_id')
|
||||||
|
->references('id')
|
||||||
|
->on('external_services');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('storages', function (Blueprint $table) {
|
||||||
|
$table->dropForeign('storages_external_service_id_foreign');
|
||||||
|
$table->dropColumn('external_service_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
17
resources/js/external_services.js
Normal file
17
resources/js/external_services.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
function ExternalServiceViewModel()
|
||||||
|
{
|
||||||
|
this.el = '#external-service-options';
|
||||||
|
this.data = {
|
||||||
|
service_type: ''
|
||||||
|
};
|
||||||
|
this.computed = {
|
||||||
|
hasOAuthStandardOptions()
|
||||||
|
{
|
||||||
|
// This list must be mirrored in App\ExternalService
|
||||||
|
return this.service_type === 'dropbox' ||
|
||||||
|
this.service_type === 'facebook' ||
|
||||||
|
this.service_type === 'google' ||
|
||||||
|
this.service_type === 'twitter';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -68,6 +68,7 @@ return [
|
|||||||
'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.',
|
'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.',
|
||||||
'bulk_photos_changed_queued' => 'Your requested change has been queued. :number photo will be updated shortly.|Your requested change has been queued. :number photos will be updated shortly.',
|
'bulk_photos_changed_queued' => 'Your requested change has been queued. :number photo will be updated shortly.|Your requested change has been queued. :number photos will be updated shortly.',
|
||||||
'cannot_delete_own_user_account' => 'It is not possible to delete your own user account. Please ask another administrator to delete it for you.',
|
'cannot_delete_own_user_account' => 'It is not possible to delete your own user account. Please ask another administrator to delete it for you.',
|
||||||
|
'cannot_delete_service_in_use' => 'This service is still in use and cannot be deleted. Please remove any references to it in your configuration and try again.',
|
||||||
'cannot_remove_own_admin' => 'You cannot remove your own administrator permissions. Please ask another administrator to remove the administrator permissions for you.',
|
'cannot_remove_own_admin' => 'You cannot remove your own administrator permissions. Please ask another administrator to remove the administrator permissions for you.',
|
||||||
'change_album_message' => 'Please select the album to move the photo(s) to:',
|
'change_album_message' => 'Please select the album to move the photo(s) to:',
|
||||||
'change_album_title' => 'Move photo(s) to another album',
|
'change_album_title' => 'Move photo(s) to another album',
|
||||||
@ -91,6 +92,9 @@ return [
|
|||||||
'create_redirect_heading' => 'Add a Redirect',
|
'create_redirect_heading' => 'Add a Redirect',
|
||||||
'create_redirect_success_message' => 'The redirect was added successfully.',
|
'create_redirect_success_message' => 'The redirect was added successfully.',
|
||||||
'create_redirect_text' => 'Enter the source address you would like to redirect and click the Create button to add a new redirect to this album.',
|
'create_redirect_text' => 'Enter the source address you would like to redirect and click the Create button to add a new redirect to this album.',
|
||||||
|
'create_service' => 'Create service',
|
||||||
|
'create_service_intro' => 'You can use the form below to create a service. Your service provider (e.g. Facebook, Twitter, Dropbox) will provide you with the details required.',
|
||||||
|
'create_service_title' => 'Create a service',
|
||||||
'create_storage' => 'Create storage location',
|
'create_storage' => 'Create storage location',
|
||||||
'create_storage_intro' => 'Complete the form below to create a new storage location to hold your photos. You can then select this storage location when you create an album.',
|
'create_storage_intro' => 'Complete the form below to create a new storage location to hold your photos. You can then select this storage location when you create an album.',
|
||||||
'create_user' => 'Create user',
|
'create_user' => 'Create user',
|
||||||
@ -129,6 +133,9 @@ return [
|
|||||||
'delete_redirect_confirm_message' => 'Are you sure you want to remove this redirect?',
|
'delete_redirect_confirm_message' => 'Are you sure you want to remove this redirect?',
|
||||||
'delete_redirect_confirm_title' => 'Delete Album Redirect',
|
'delete_redirect_confirm_title' => 'Delete Album Redirect',
|
||||||
'delete_redirect_success_message' => 'The redirect was deleted successfully.',
|
'delete_redirect_success_message' => 'The redirect was deleted successfully.',
|
||||||
|
'delete_service' => 'Delete service: :name',
|
||||||
|
'delete_service_confirm' => 'Are you sure you want to permanently remove the :name service?',
|
||||||
|
'delete_service_warning' => 'This is a permanent action that cannot be reversed!',
|
||||||
'delete_storage' => 'Delete storage location: :name',
|
'delete_storage' => 'Delete storage location: :name',
|
||||||
'delete_storage_confirm' => 'Are you sure you want to permanently remove this storage location?',
|
'delete_storage_confirm' => 'Are you sure you want to permanently remove this storage location?',
|
||||||
'delete_storage_existing_albums' => 'At least one album is still using the storage location. Please delete all albums before removing the storage location.',
|
'delete_storage_existing_albums' => 'At least one album is still using the storage location. Please delete all albums before removing the storage location.',
|
||||||
@ -145,6 +152,8 @@ return [
|
|||||||
'edit_album_intro2' => 'Complete the form below to edit the properties of the album: :album_name.',
|
'edit_album_intro2' => 'Complete the form below to edit the properties of the album: :album_name.',
|
||||||
'edit_group_intro' => 'You can use the form below to edit the above group. Changes take effect immediately.',
|
'edit_group_intro' => 'You can use the form below to edit the above group. Changes take effect immediately.',
|
||||||
'edit_group_title' => 'Edit group: :group_name',
|
'edit_group_title' => 'Edit group: :group_name',
|
||||||
|
'edit_service_intro' => 'You can use the form below to edit the above services. Changes take effect the next time Blue Twilight accesses the service.',
|
||||||
|
'edit_service_title' => 'Edit service: :name',
|
||||||
'edit_storage' => 'Edit storage location: :storage_name',
|
'edit_storage' => 'Edit storage location: :storage_name',
|
||||||
'edit_storage_intro' => 'Use the form below to update the details of the :storage_name storage location.',
|
'edit_storage_intro' => 'Use the form below to update the details of the :storage_name storage location.',
|
||||||
'edit_user_intro' => 'You can use the form below to edit the above user account. Changes take effect immediately.',
|
'edit_user_intro' => 'You can use the form below to edit the above user account. Changes take effect immediately.',
|
||||||
@ -177,6 +186,8 @@ return [
|
|||||||
'list_groups_title' => 'Groups',
|
'list_groups_title' => 'Groups',
|
||||||
'list_labels_intro' => 'Organise your photos differently using labels. Assign one or more labels to your photos and your visitors can view all photos with a specific tag in a single view.',
|
'list_labels_intro' => 'Organise your photos differently using labels. Assign one or more labels to your photos and your visitors can view all photos with a specific tag in a single view.',
|
||||||
'list_labels_title' => 'Labels',
|
'list_labels_title' => 'Labels',
|
||||||
|
'list_services_intro' => 'External services link your Blue Twilight application to other providers such as Facebook, Twitter and Dropbox. Configure your application\'s credentials for these services here.',
|
||||||
|
'list_services_title' => 'Services',
|
||||||
'list_storages_intro' => 'Storage locations specify the physical location where your photograph files are held. This may be on your local server\'s filesystem, or on a cloud storage provider such as Rackspace or Amazon S3.',
|
'list_storages_intro' => 'Storage locations specify the physical location where your photograph files are held. This may be on your local server\'s filesystem, or on a cloud storage provider such as Rackspace or Amazon S3.',
|
||||||
'list_storages_title' => 'Storage Locations',
|
'list_storages_title' => 'Storage Locations',
|
||||||
'list_users_intro' => 'User accounts allow people to login to your gallery to manage your albums. If you have disabled self-registration, you can create user accounts here to allow people to login.',
|
'list_users_intro' => 'User accounts allow people to login to your gallery to manage your albums. If you have disabled self-registration, you can create user accounts here to allow people to login.',
|
||||||
@ -212,6 +223,8 @@ return [
|
|||||||
'no_labels_text' => 'You have no labels yet. Use the form below to create one.',
|
'no_labels_text' => 'You have no labels yet. Use the form below to create one.',
|
||||||
'no_labels_title' => 'No Labels',
|
'no_labels_title' => 'No Labels',
|
||||||
'no_photo_selected_message' => 'Please select at least one photo.',
|
'no_photo_selected_message' => 'Please select at least one photo.',
|
||||||
|
'no_services_text' => 'You have no services yet. Click the Create button to add connection details to an external service such as Facebook or Dropbox.',
|
||||||
|
'no_services_title' => 'No External Services',
|
||||||
'no_storages_text' => 'You need a storage location to store your uploaded photographs.',
|
'no_storages_text' => 'You need a storage location to store your uploaded photographs.',
|
||||||
'no_storages_text2' => 'This can be on your server\'s local filesystem or a cloud location such as Amazon S3 or Rackspace.',
|
'no_storages_text2' => 'This can be on your server\'s local filesystem or a cloud location such as Amazon S3 or Rackspace.',
|
||||||
'no_storages_title' => 'No storage locations defined',
|
'no_storages_title' => 'No storage locations defined',
|
||||||
@ -256,6 +269,9 @@ return [
|
|||||||
'visible_action' => 'Only those visible'
|
'visible_action' => 'Only those visible'
|
||||||
],
|
],
|
||||||
'select_none_action' => 'Clear selection',
|
'select_none_action' => 'Clear selection',
|
||||||
|
'service_deletion_failed' => 'An error occurred while removing the :name service: :error_message',
|
||||||
|
'service_deletion_successful' => 'The :name service was removed successfully.',
|
||||||
|
'services_title' => 'External services',
|
||||||
'settings' => [
|
'settings' => [
|
||||||
'albums_menu_heading' => 'Albums Navigation Menu',
|
'albums_menu_heading' => 'Albums Navigation Menu',
|
||||||
'albums_menu_number_items' => 'Number of albums to display:',
|
'albums_menu_number_items' => 'Number of albums to display:',
|
||||||
@ -324,6 +340,9 @@ return [
|
|||||||
'users' => 'user|users',
|
'users' => 'user|users',
|
||||||
],
|
],
|
||||||
'storage_auth_url_label_help' => 'Leave blank to authenticate with Amazon S3. For an S3-compatible provider, enter your provider\'s authentication URL here.',
|
'storage_auth_url_label_help' => 'Leave blank to authenticate with Amazon S3. For an S3-compatible provider, enter your provider\'s authentication URL here.',
|
||||||
|
'storage_authorise_external_service_authorised' => 'Authorised',
|
||||||
|
'storage_authorise_external_service_refresh_authentication' => 'Refresh authentication',
|
||||||
|
'storage_authorise_external_service_required' => 'Authorisation required',
|
||||||
'storage_backblaze_access_key_id_help' => 'To use your account\'s master key, enter your account ID here.',
|
'storage_backblaze_access_key_id_help' => 'To use your account\'s master key, enter your account ID here.',
|
||||||
'storage_s3_signed_urls_help' => 'When enabled, Blue Twilight will upload your photos with a private ACL and will use signed URLs to display the photos to your visitors.',
|
'storage_s3_signed_urls_help' => 'When enabled, Blue Twilight will upload your photos with a private ACL and will use signed URLs to display the photos to your visitors.',
|
||||||
'storage_s3_signed_urls_tooltip' => 'This location is set to use private images with signed URLs.',
|
'storage_s3_signed_urls_tooltip' => 'This location is set to use private images with signed URLs.',
|
||||||
|
@ -57,8 +57,11 @@ return [
|
|||||||
'remove_action' => 'Remove',
|
'remove_action' => 'Remove',
|
||||||
'review_photo_comment_action' => 'Approve/reject comment',
|
'review_photo_comment_action' => 'Approve/reject comment',
|
||||||
'save_action' => 'Save Changes',
|
'save_action' => 'Save Changes',
|
||||||
|
'service_type_label' => 'Type of service:',
|
||||||
'select' => 'Select',
|
'select' => 'Select',
|
||||||
'select_current_text' => '(current)',
|
'select_current_text' => '(current)',
|
||||||
|
'service_app_id_label' => 'Application ID / client ID:',
|
||||||
|
'service_app_secret_label' => 'Application ID / client secret:',
|
||||||
'settings_allow_photo_comments' => 'Allow comments on photos',
|
'settings_allow_photo_comments' => 'Allow comments on photos',
|
||||||
'settings_allow_photo_comments_anonymous' => 'Allow anonymous users to comment on photos',
|
'settings_allow_photo_comments_anonymous' => 'Allow anonymous users to comment on photos',
|
||||||
'settings_allow_photo_comments_anonymous_help' => 'With this option enabled, users can post comments without being logged in.',
|
'settings_allow_photo_comments_anonymous_help' => 'With this option enabled, users can post comments without being logged in.',
|
||||||
@ -114,6 +117,7 @@ return [
|
|||||||
'storage_container_name_label' => 'Container name:',
|
'storage_container_name_label' => 'Container name:',
|
||||||
'storage_driver_label' => 'Storage driver:',
|
'storage_driver_label' => 'Storage driver:',
|
||||||
'storage_endpoint_url_label' => 'Endpoint URL (leave blank if using Amazon):',
|
'storage_endpoint_url_label' => 'Endpoint URL (leave blank if using Amazon):',
|
||||||
|
'storage_external_service_label' => 'Service:',
|
||||||
'storage_location_label' => 'Physical location:',
|
'storage_location_label' => 'Physical location:',
|
||||||
'storage_s3_signed_urls' => 'Upload files privately and use signed URLs',
|
'storage_s3_signed_urls' => 'Upload files privately and use signed URLs',
|
||||||
'storage_secret_key_label' => 'Secret key:',
|
'storage_secret_key_label' => 'Secret key:',
|
||||||
|
@ -9,6 +9,7 @@ return [
|
|||||||
'comments' => 'Comments',
|
'comments' => 'Comments',
|
||||||
'create_album' => 'Create album',
|
'create_album' => 'Create album',
|
||||||
'create_group' => 'Create group',
|
'create_group' => 'Create group',
|
||||||
|
'create_service' => 'Create service',
|
||||||
'create_storage' => 'Create storage',
|
'create_storage' => 'Create storage',
|
||||||
'create_user' => 'Create user',
|
'create_user' => 'Create user',
|
||||||
'default_album_permissions' => 'Default album permissions',
|
'default_album_permissions' => 'Default album permissions',
|
||||||
@ -16,10 +17,12 @@ return [
|
|||||||
'delete_comment' => 'Delete comment',
|
'delete_comment' => 'Delete comment',
|
||||||
'delete_group' => 'Delete group',
|
'delete_group' => 'Delete group',
|
||||||
'delete_label' => 'Delete label',
|
'delete_label' => 'Delete label',
|
||||||
|
'delete_service' => 'Delete service',
|
||||||
'delete_storage' => 'Delete storage location',
|
'delete_storage' => 'Delete storage location',
|
||||||
'delete_user' => 'Delete user',
|
'delete_user' => 'Delete user',
|
||||||
'edit_album' => 'Edit album',
|
'edit_album' => 'Edit album',
|
||||||
'edit_group' => 'Edit group',
|
'edit_group' => 'Edit group',
|
||||||
|
'edit_service' => 'Edit service',
|
||||||
'edit_storage' => 'Edit storage location',
|
'edit_storage' => 'Edit storage location',
|
||||||
'edit_user' => 'Edit user',
|
'edit_user' => 'Edit user',
|
||||||
'exif_data' => 'Exif Data',
|
'exif_data' => 'Exif Data',
|
||||||
|
7
resources/lang/en/services.php
Normal file
7
resources/lang/en/services.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'dropbox' => 'Dropbox',
|
||||||
|
'facebook' => 'Facebook',
|
||||||
|
'google' => 'Google',
|
||||||
|
'twitter' => 'Twitter'
|
||||||
|
];
|
76
resources/views/themes/base/admin/create_service.blade.php
Normal file
76
resources/views/themes/base/admin/create_service.blade.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
@extends(Theme::viewName('layout'))
|
||||||
|
@section('title', trans('admin.create_service'))
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('services.index') }}">@lang('navigation.breadcrumb.services')</a></li>
|
||||||
|
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.create_service')</li>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1>@lang('admin.create_service_title')</h1>
|
||||||
|
<p>@lang('admin.create_service_intro')</p>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<form action="{{ route('services.store') }}" method="post" id="external-service-options">
|
||||||
|
{{ csrf_field() }}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-control-label" for="service-type">@lang('forms.service_type_label')</label>
|
||||||
|
<select class="form-control{{ $errors->has('service_type') ? ' is-invalid' : '' }}" id="service-type" name="service_type" value="{{ old('service_type') }}" v-model="service_type">
|
||||||
|
<option value="">@lang('forms.please_select')</option>
|
||||||
|
|
||||||
|
@foreach ($serviceTypes as $serviceTypeKey => $serviceTypeDescription)
|
||||||
|
<option value="{{ $serviceTypeKey }}">{{ $serviceTypeDescription }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
|
||||||
|
@if ($errors->has('service_type'))
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('service_type') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-control-label" for="service-name">@lang('forms.name_label')</label>
|
||||||
|
<input type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" id="service-name" name="name" value="{{ old('name') }}" />
|
||||||
|
|
||||||
|
@if ($errors->has('name'))
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('name') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="hasOAuthStandardOptions">
|
||||||
|
@include(Theme::viewName('partials.admin_services_oauth_options'))
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-right">
|
||||||
|
<a href="{{ route('services.index') }}" class="btn btn-link">@lang('forms.cancel_action')</a>
|
||||||
|
<button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.create_action')</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function()
|
||||||
|
{
|
||||||
|
var viewModel = new ExternalServiceViewModel();
|
||||||
|
var app = new Vue(viewModel);
|
||||||
|
|
||||||
|
@if (strlen(old('service_type')) > 0)
|
||||||
|
app.service_type = '{{ old('service_type') }}';
|
||||||
|
@endif
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
34
resources/views/themes/base/admin/delete_service.blade.php
Normal file
34
resources/views/themes/base/admin/delete_service.blade.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
@extends(Theme::viewName('layout'))
|
||||||
|
@section('title', trans('admin.delete_service', ['name' => $service->name]))
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('services.index') }}">@lang('navigation.breadcrumb.services')</a></li>
|
||||||
|
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.delete_service')</li>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 ml-md-auto mr-md-auto">
|
||||||
|
<div class="card bg-danger">
|
||||||
|
<div class="card-header text-white">@yield('title')</div>
|
||||||
|
<div class="card-body bg-light">
|
||||||
|
<p>@lang('admin.delete_service_confirm', ['name' => $service->name])</p>
|
||||||
|
<p class="text-danger"><b>@lang('admin.delete_service_warning')</b></p>
|
||||||
|
|
||||||
|
<div class="text-right">
|
||||||
|
<form action="{{ route('services.destroy', [$service->id]) }}" method="post">
|
||||||
|
{{ csrf_field() }}
|
||||||
|
{{ method_field('DELETE') }}
|
||||||
|
<a href="{{ route('services.index') }}" class="btn btn-link">@lang('forms.cancel_action')</a>
|
||||||
|
<button type="submit" class="btn btn-danger"><i class="fa fa-fw fa-trash"></i> @lang('forms.delete_action')</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
63
resources/views/themes/base/admin/edit_service.blade.php
Normal file
63
resources/views/themes/base/admin/edit_service.blade.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
@extends(Theme::viewName('layout'))
|
||||||
|
@section('title', trans('admin.edit_service_title', ['name' => $service->name]))
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('services.index') }}">@lang('navigation.breadcrumb.services')</a></li>
|
||||||
|
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.edit_service')</li>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1>@yield('title')</h1>
|
||||||
|
<p>@lang('admin.edit_service_intro')</p>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<form action="{{ route('services.update', [$service->id]) }}" method="post">
|
||||||
|
{{ csrf_field() }}
|
||||||
|
{{ method_field('PUT') }}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-control-label" for="service-type">@lang('forms.service_type_label')</label>
|
||||||
|
<select class="form-control{{ $errors->has('service_type') ? ' is-invalid' : '' }}" id="service-type" name="service_type" value="{{ old('service_type', $service->service_type) }}">
|
||||||
|
<option value="">@lang('forms.please_select')</option>
|
||||||
|
|
||||||
|
@foreach ($serviceTypes as $serviceTypeKey => $serviceTypeDescription)
|
||||||
|
<option value="{{ $serviceTypeKey }}"@if ($service->service_type == $serviceTypeKey) selected="selected"@endif>{{ $serviceTypeDescription }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
|
||||||
|
@if ($errors->has('service_type'))
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('service_type') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-control-label" for="service-name">@lang('forms.name_label')</label>
|
||||||
|
<input type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" id="service-name" name="name" value="{{ old('name', $service->name) }}" />
|
||||||
|
|
||||||
|
@if ($errors->has('name'))
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('name') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($service->hasOAuthStandardOptions())
|
||||||
|
@include(Theme::viewName('partials.admin_services_oauth_options'))
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="text-right" style="margin-top: 20px;">
|
||||||
|
<a href="{{ route('services.index') }}" class="btn btn-link">@lang('forms.cancel_action')</a>
|
||||||
|
<button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -66,8 +66,13 @@
|
|||||||
@include(Theme::viewName('partials.admin_storages_backblaze_b2_options'))
|
@include(Theme::viewName('partials.admin_storages_backblaze_b2_options'))
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if ($storage->source == 'DropboxSource')
|
||||||
|
<hr/>
|
||||||
|
@include(Theme::viewName('partials.admin_storages_dropbox_options'))
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<a href="{{ route('storage.index') }}" class="btn btn-default">@lang('forms.cancel_action')</a>
|
<a href="{{ route('storage.index') }}" class="btn btn-link">@lang('forms.cancel_action')</a>
|
||||||
<button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button>
|
<button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
55
resources/views/themes/base/admin/list_services.blade.php
Normal file
55
resources/views/themes/base/admin/list_services.blade.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
@extends(Theme::viewName('layout'))
|
||||||
|
@section('title', trans('admin.services_title'))
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
|
||||||
|
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.services')</li>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1>@lang('admin.list_services_title')</h1>
|
||||||
|
<div class="alert alert-info" style="margin-bottom: 30px;">
|
||||||
|
<i class="fa fa-fw fa-info"></i> @lang('admin.list_services_intro')
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (count($services) == 0)
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h4 class="text-danger"><b>@lang('admin.no_services_title')</b></h4>
|
||||||
|
<p>@lang('admin.no_services_text')</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<table class="table table-hover table-striped">
|
||||||
|
<tbody>
|
||||||
|
@foreach ($services as $service)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span style="font-size: 1.3em;"><a href="{{ route('services.edit', ['service' => $service->id]) }}">{{ $service->name }}</a></span><br/>
|
||||||
|
{{ trans(sprintf('services.%s', $service->service_type)) }}
|
||||||
|
@if ($service->service_type == \App\ExternalService::DROPBOX)
|
||||||
|
· {{ decrypt($service->app_id) }}
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<a href="{{ route('services.delete', ['service' => $service->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
{{ $services->links() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="text-right" style="margin-top: 10px;">
|
||||||
|
<a href="{{ route('services.create') }}" class="btn btn-success"><i class="fa fa-fw fa-plus"></i> @lang('admin.create_service')</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
@ -38,16 +38,24 @@
|
|||||||
</span><br/>
|
</span><br/>
|
||||||
<span style="color: #888; font-style: italic;">
|
<span style="color: #888; font-style: italic;">
|
||||||
@if ($storage->source == 'LocalFilesystemSource'){{ $storage->location }}@endif
|
@if ($storage->source == 'LocalFilesystemSource'){{ $storage->location }}@endif
|
||||||
@if ($storage->source == 'OpenStackSource'){{ $storage->container_name }} - {{ $storage->service_name }}, {{ $storage->service_region }}@endif
|
@if ($storage->source == 'OpenStackSource'){{ $storage->container_name }} · {{ $storage->service_name }}, {{ $storage->service_region }}@endif
|
||||||
@if ($storage->source == 'AmazonS3Source')
|
@if ($storage->source == 'AmazonS3Source')
|
||||||
{{ $storage->container_name }} - {{ $storage->service_region }}
|
{{ $storage->container_name }} · {{ $storage->service_region }}
|
||||||
@if ($storage->s3_signed_urls)
|
@if ($storage->s3_signed_urls)
|
||||||
<i class="fa fa-key ml-2" data-toggle="tooltip" title="@lang('admin.storage_s3_signed_urls_tooltip')"></i>
|
<i class="fa fa-key ml-2" data-toggle="tooltip" title="@lang('admin.storage_s3_signed_urls_tooltip')"></i>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
@if ($storage->source == 'RackspaceSource'){{ $storage->container_name }} - {{ $storage->service_region }}@endif
|
@if ($storage->source == 'RackspaceSource'){{ $storage->container_name }} · {{ $storage->service_region }}@endif
|
||||||
|
@if ($storage->source == 'DropboxSource')
|
||||||
|
@if (empty($storage->access_token))
|
||||||
|
<a href="{{ route('storage.authoriseService', ['storage' => $storage->id]) }}" class="text-danger"><b>@lang('admin.storage_authorise_external_service_required')</b></a>
|
||||||
|
@else
|
||||||
|
<span class="text-success">@lang('admin.storage_authorise_external_service_authorised')</span> · <a href="{{ route('storage.authoriseService', ['storage' => $storage->id]) }}">@lang('admin.storage_authorise_external_service_refresh_authentication')</a>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<p></p>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
@if (!$storage->is_internal)
|
@if (!$storage->is_internal)
|
||||||
<a href="{{ route('storage.delete', ['storage' => $storage->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
|
<a href="{{ route('storage.delete', ['storage' => $storage->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-control-label" for="access-key">@lang('forms.service_app_id_label')</label>
|
||||||
|
<input type="text" class="form-control{{ $errors->has('app_id') ? ' is-invalid' : '' }}" id="app-id" name="app_id" value="{{ old('app_id', $service->app_id) }}">
|
||||||
|
|
||||||
|
@if ($errors->has('app_id'))
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('app_id') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-control-label" for="secret-key">@lang('forms.service_app_secret_label')</label>
|
||||||
|
<input type="text" class="form-control{{ $errors->has('app_secret') ? ' is-invalid' : '' }}" id="app-secret" name="app_secret" value="{{ old('app_secret', $service->app_secret) }}">
|
||||||
|
|
||||||
|
@if ($errors->has('app_secret'))
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<strong>{{ $errors->first('app_secret') }}</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,41 +1,16 @@
|
|||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-control-label" for="access-key">@lang('forms.storage_application_key_label')</label>
|
<label for="external-service">@lang('forms.storage_external_service_label')</label>
|
||||||
<input type="text" class="form-control{{ $errors->has('access_key') ? ' is-invalid' : '' }}" id="access-key" name="access_key" value="{{ old('access_key', $storage->access_key) }}">
|
<select class="form-control{{ $errors->has('external_service_id') ? ' is-invalid' : '' }}" id="external-service" name="external_service_id">
|
||||||
|
<option value="">@lang('forms.please_select')</option>
|
||||||
|
|
||||||
@if ($errors->has('access_key'))
|
@foreach ($dropbox_services as $service)
|
||||||
|
<option value="{{ $service->id }}"@if (old('external_service_id', $storage->external_service_id == $service->id)) selected="selected"@endif>{{ $service->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
|
||||||
|
@if ($errors->has('external_service_id'))
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
<strong>{{ $errors->first('access_key') }}</strong>
|
<strong>{{ $errors->first('external_service_id') }}</strong>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-control-label" for="secret-key">@lang('forms.storage_application_secret_label')</label>
|
|
||||||
<input type="text" class="form-control{{ $errors->has('secret_key') ? ' is-invalid' : '' }}" id="secret-key" name="secret_key" value="{{ old('secret_key', $storage->secret_key) }}">
|
|
||||||
|
|
||||||
@if ($errors->has('secret_key'))
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
<strong>{{ $errors->first('secret_key') }}</strong>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-control-label" for="access-token">@lang('forms.storage_access_token_label')</label>
|
|
||||||
<input type="text" class="form-control{{ $errors->has('access_token') ? ' is-invalid' : '' }}" id="access-token" name="access_token" value="{{ old('access_token', $storage->access_token) }}">
|
|
||||||
|
|
||||||
@if ($errors->has('access_key'))
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
<strong>{{ $errors->first('access_token') }}</strong>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -5,7 +5,7 @@
|
|||||||
<p style="font-size: smaller;">
|
<p style="font-size: smaller;">
|
||||||
<b>
|
<b>
|
||||||
@lang('global.powered_by', [
|
@lang('global.powered_by', [
|
||||||
'link_start' => '<a href="https://andysh.uk/software/blue-twilight-php-photo-gallery/" target="_blank">',
|
'link_start' => '<a href="https://showmy.photos/" target="_blank">',
|
||||||
'link_end' => '</a>',
|
'link_end' => '</a>',
|
||||||
])
|
])
|
||||||
</b><br/>
|
</b><br/>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<p style="font-size: smaller;">
|
<p style="font-size: smaller;">
|
||||||
<b>
|
<b>
|
||||||
@lang('global.powered_by', [
|
@lang('global.powered_by', [
|
||||||
'link_start' => '<a href="https://andysh.uk/software/blue-twilight-php-photo-gallery/" target="_blank">',
|
'link_start' => '<a href="https://showmy.photos/" target="_blank">',
|
||||||
'link_end' => '</a>',
|
'link_end' => '</a>',
|
||||||
])
|
])
|
||||||
</b><br/>
|
</b><br/>
|
||||||
|
@ -55,6 +55,8 @@ Route::group(['prefix' => 'admin'], function () {
|
|||||||
Route::resource('labels', 'Admin\LabelController');
|
Route::resource('labels', 'Admin\LabelController');
|
||||||
|
|
||||||
// Storage management
|
// Storage management
|
||||||
|
Route::get('storage/{storage}/authorise-service', 'Admin\StorageController@authoriseService')->name('storage.authoriseService');
|
||||||
|
Route::get('storage/{storage}/complete-service-authorisation', 'Admin\StorageController@completeServiceAuthorisation')->name('storage.completeServiceAuthorisation');
|
||||||
Route::get('storage/{storage}/delete', 'Admin\StorageController@delete')->name('storage.delete');
|
Route::get('storage/{storage}/delete', 'Admin\StorageController@delete')->name('storage.delete');
|
||||||
Route::resource('storage', 'Admin\StorageController');
|
Route::resource('storage', 'Admin\StorageController');
|
||||||
|
|
||||||
@ -81,7 +83,7 @@ Route::group(['prefix' => 'admin'], function () {
|
|||||||
Route::resource('comments', 'Admin\PhotoCommentController');
|
Route::resource('comments', 'Admin\PhotoCommentController');
|
||||||
|
|
||||||
// Services management
|
// Services management
|
||||||
Route::get('services/{services}/delete', 'Admin\ServiceController@delete')->name('services.delete');
|
Route::get('services/{service}/delete', 'Admin\ServiceController@delete')->name('services.delete');
|
||||||
Route::resource('services', 'Admin\ServiceController');
|
Route::resource('services', 'Admin\ServiceController');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user