fix data setting get datatable using api token
This commit is contained in:
45
app/Console/Commands/ClearDatabaseSessions.php
Normal file
45
app/Console/Commands/ClearDatabaseSessions.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class ClearDatabaseSessions extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'session:clear-db {--force : Force the operation without confirmation}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Clear all database sessions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if (!$this->option('force') && !$this->confirm('Are you sure you want to clear all database sessions?')) {
|
||||||
|
$this->info('Operation cancelled.');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$count = DB::table('sessions')->count();
|
||||||
|
DB::table('sessions')->delete();
|
||||||
|
|
||||||
|
$this->info("Successfully cleared {$count} database sessions.");
|
||||||
|
return 0;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error('Failed to clear database sessions: ' . $e->getMessage());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,13 +36,68 @@ class AuthenticatedSessionController extends Controller
|
|||||||
// Ambil user yang sedang login
|
// Ambil user yang sedang login
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
// Buat token untuk API
|
// Hapus token lama jika ada
|
||||||
$token = $user->createToken(env('APP_KEY'))->plainTextToken;
|
$user->tokens()->delete();
|
||||||
|
|
||||||
|
// Buat token untuk API dengan scope dan expiration
|
||||||
|
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||||
|
|
||||||
|
// Token dengan scope (opsional)
|
||||||
|
$token = $user->createToken($tokenName, ['*'], now()->addDays(30))->plainTextToken;
|
||||||
|
|
||||||
|
// Simpan token di session untuk digunakan di frontend
|
||||||
session(['api_token' => $token]);
|
session(['api_token' => $token]);
|
||||||
|
|
||||||
return redirect()->intended(RouteServiceProvider::HOME);
|
return redirect()->intended(RouteServiceProvider::HOME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate API token for authenticated user
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function generateApiToken(Request $request)
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return response()->json(['error' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete existing tokens
|
||||||
|
$user->tokens()->delete();
|
||||||
|
|
||||||
|
// Generate new token
|
||||||
|
$tokenName = config('app.name', 'Laravel') . '-' . $user->id . '-' . time();
|
||||||
|
$token = $user->createToken($tokenName, ['*'], now()->addDays(30))->plainTextToken;
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'token' => $token,
|
||||||
|
'token_type' => 'Bearer',
|
||||||
|
'expires_in' => 30 * 24 * 60 * 60, // 30 days in seconds
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke API token for authenticated user
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function revokeApiToken(Request $request)
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return response()->json(['error' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->tokens()->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'All tokens revoked successfully']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy an authenticated session.
|
* Destroy an authenticated session.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ class DataSettingResource extends JsonResource
|
|||||||
'key' => $this->key,
|
'key' => $this->key,
|
||||||
'value' => $this->value,
|
'value' => $this->value,
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'created_at' => $this->created_at->toDateTimeString(),
|
'created_at' => $this->created_at ? $this->created_at->toDateTimeString() : null,
|
||||||
'updated_at' => $this->updated_at->toDateTimeString(),
|
'updated_at' => $this->updated_at ? $this->updated_at->toDateTimeString() : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -10,16 +10,81 @@ class DataSettings {
|
|||||||
this.toastElement = document.getElementById("toastNotification");
|
this.toastElement = document.getElementById("toastNotification");
|
||||||
this.toast = new bootstrap.Toast(this.toastElement);
|
this.toast = new bootstrap.Toast(this.toastElement);
|
||||||
this.table = null;
|
this.table = null;
|
||||||
|
|
||||||
|
// Initialize immediately
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize functions
|
/**
|
||||||
|
* Initialize the DataSettings class
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
this.initTableDataSettings();
|
this.initTableDataSettings();
|
||||||
this.initEvents();
|
this.initEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get API token from meta tag
|
||||||
|
* @returns {string|null}
|
||||||
|
*/
|
||||||
|
getApiToken() {
|
||||||
|
const tokenMeta = document.querySelector('meta[name="api-token"]');
|
||||||
|
return tokenMeta ? tokenMeta.getAttribute('content') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get authentication headers for API requests
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
getAuthHeaders() {
|
||||||
|
const token = this.getApiToken();
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
headers['Authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csrfToken) {
|
||||||
|
headers['X-CSRF-TOKEN'] = csrfToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make API request with authentication
|
||||||
|
* @param {string} url
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
async makeApiRequest(url, options = {}) {
|
||||||
|
const defaultOptions = {
|
||||||
|
headers: this.getAuthHeaders(),
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, defaultOptions);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API Request failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
initEvents() {
|
initEvents() {
|
||||||
document.body.addEventListener("click", async (event) => {
|
document.body.addEventListener("click", async (event) => {
|
||||||
const deleteButton = event.target.closest(
|
const deleteButton = event.target.closest(".btn-delete-data-settings");
|
||||||
".btn-delete-data-settings"
|
|
||||||
);
|
|
||||||
if (deleteButton) {
|
if (deleteButton) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
await this.handleDelete(deleteButton);
|
await this.handleDelete(deleteButton);
|
||||||
@@ -34,7 +99,8 @@ class DataSettings {
|
|||||||
let canUpdate = tableContainer.getAttribute("data-updater") === "1";
|
let canUpdate = tableContainer.getAttribute("data-updater") === "1";
|
||||||
let canDelete = tableContainer.getAttribute("data-destroyer") === "1";
|
let canDelete = tableContainer.getAttribute("data-destroyer") === "1";
|
||||||
let menuId = tableContainer.getAttribute("data-menuId");
|
let menuId = tableContainer.getAttribute("data-menuId");
|
||||||
// Create a new Grid.js instance only if it doesn't exist
|
|
||||||
|
// Create a new Grid.js instance
|
||||||
this.table = new Grid({
|
this.table = new Grid({
|
||||||
columns: [
|
columns: [
|
||||||
"ID",
|
"ID",
|
||||||
@@ -77,9 +143,7 @@ class DataSettings {
|
|||||||
limit: 15,
|
limit: 15,
|
||||||
server: {
|
server: {
|
||||||
url: (prev, page) =>
|
url: (prev, page) =>
|
||||||
`${prev}${prev.includes("?") ? "&" : "?"}page=${
|
`${prev}${prev.includes("?") ? "&" : "?"}page=${page + 1}`,
|
||||||
page + 1
|
|
||||||
}`,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sort: true,
|
sort: true,
|
||||||
@@ -91,13 +155,7 @@ class DataSettings {
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
url: `${GlobalConfig.apiHost}/api/data-settings`,
|
url: `${GlobalConfig.apiHost}/api/data-settings`,
|
||||||
headers: {
|
headers: this.getAuthHeaders(),
|
||||||
"X-CSRF-TOKEN": document
|
|
||||||
.querySelector('meta[name="csrf-token"]')
|
|
||||||
.getAttribute("content"),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Accept": "application/json",
|
|
||||||
},
|
|
||||||
then: (data) =>
|
then: (data) =>
|
||||||
data.data.map((item) => [
|
data.data.map((item) => [
|
||||||
item.id,
|
item.id,
|
||||||
@@ -110,6 +168,7 @@ class DataSettings {
|
|||||||
},
|
},
|
||||||
}).render(tableContainer);
|
}).render(tableContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDelete(deleteButton) {
|
async handleDelete(deleteButton) {
|
||||||
const id = deleteButton.getAttribute("data-id");
|
const id = deleteButton.getAttribute("data-id");
|
||||||
|
|
||||||
@@ -125,46 +184,29 @@ class DataSettings {
|
|||||||
|
|
||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
try {
|
try {
|
||||||
let response = await fetch(
|
const response = await this.makeApiRequest(
|
||||||
`${GlobalConfig.apiHost}/api/data-settings/${id}`,
|
`${GlobalConfig.apiHost}/api/data-settings/${id}`,
|
||||||
{
|
{ method: "DELETE" }
|
||||||
method: "DELETE",
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"X-CSRF-TOKEN": document
|
|
||||||
.querySelector('meta[name="csrf-token"]')
|
|
||||||
.getAttribute("content"),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Accept": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.ok) {
|
const result = await response.json();
|
||||||
let result = await response.json();
|
this.toastMessage.innerText = result.message || "Deleted successfully!";
|
||||||
this.toastMessage.innerText =
|
this.toast.show();
|
||||||
result.message || "Deleted successfully!";
|
|
||||||
this.toast.show();
|
|
||||||
|
|
||||||
// Refresh Grid.js table
|
// Refresh Grid.js table
|
||||||
if (typeof this.table !== "undefined") {
|
if (this.table) {
|
||||||
this.table.updateConfig({}).forceRender();
|
this.table.updateConfig({}).forceRender();
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let error = await response.json();
|
|
||||||
console.error("Delete failed:", error);
|
|
||||||
this.toastMessage.innerText =
|
|
||||||
error.message || "Delete failed!";
|
|
||||||
this.toast.show();
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting item:", error);
|
console.error("Error deleting item:", error);
|
||||||
this.toastMessage.innerText = "An error occurred!";
|
this.toastMessage.innerText = error.message || "An error occurred!";
|
||||||
this.toast.show();
|
this.toast.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener("DOMContentLoaded", function (e) {
|
|
||||||
|
// Initialize when DOM is ready
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
new DataSettings();
|
new DataSettings();
|
||||||
});
|
});
|
||||||
|
|||||||
198
resources/js/utils/api-token-manager.js
Normal file
198
resources/js/utils/api-token-manager.js
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* API Token Manager Utility
|
||||||
|
* Handles API token generation, storage, and usage
|
||||||
|
*/
|
||||||
|
class ApiTokenManager {
|
||||||
|
constructor() {
|
||||||
|
this.token = null;
|
||||||
|
this.tokenKey = 'api_token';
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize token manager
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
// Try to get token from meta tag first (session-based)
|
||||||
|
const metaToken = document.querySelector('meta[name="api-token"]');
|
||||||
|
if (metaToken && metaToken.getAttribute('content')) {
|
||||||
|
this.token = metaToken.getAttribute('content');
|
||||||
|
} else {
|
||||||
|
// Try to get from localStorage as fallback
|
||||||
|
this.token = localStorage.getItem(this.tokenKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate new API token
|
||||||
|
* @returns {Promise<string|null>}
|
||||||
|
*/
|
||||||
|
async generateToken() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api-tokens/generate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||||||
|
},
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.token) {
|
||||||
|
this.setToken(data.token);
|
||||||
|
return data.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('No token received');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to generate token:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke current API token
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async revokeToken() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api-tokens/revoke', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
||||||
|
'Authorization': `Bearer ${this.token}`
|
||||||
|
},
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
this.clearToken();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to revoke token:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set token and update storage
|
||||||
|
* @param {string} token
|
||||||
|
*/
|
||||||
|
setToken(token) {
|
||||||
|
this.token = token;
|
||||||
|
localStorage.setItem(this.tokenKey, token);
|
||||||
|
|
||||||
|
// Update meta tag if exists
|
||||||
|
const metaTag = document.querySelector('meta[name="api-token"]');
|
||||||
|
if (metaTag) {
|
||||||
|
metaTag.setAttribute('content', token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current token
|
||||||
|
* @returns {string|null}
|
||||||
|
*/
|
||||||
|
getToken() {
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear token from storage
|
||||||
|
*/
|
||||||
|
clearToken() {
|
||||||
|
this.token = null;
|
||||||
|
localStorage.removeItem(this.tokenKey);
|
||||||
|
|
||||||
|
// Clear meta tag if exists
|
||||||
|
const metaTag = document.querySelector('meta[name="api-token"]');
|
||||||
|
if (metaTag) {
|
||||||
|
metaTag.setAttribute('content', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if token exists
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
hasToken() {
|
||||||
|
return this.token !== null && this.token !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get authorization header
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
getAuthHeader() {
|
||||||
|
if (!this.hasToken()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'Authorization': `Bearer ${this.token}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make authenticated API request
|
||||||
|
* @param {string} url
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {Promise<Response>}
|
||||||
|
*/
|
||||||
|
async request(url, options = {}) {
|
||||||
|
const defaultOptions = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...this.getAuthHeader(),
|
||||||
|
...(options.headers || {})
|
||||||
|
},
|
||||||
|
credentials: 'include'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergedOptions = {
|
||||||
|
...defaultOptions,
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
...defaultOptions.headers,
|
||||||
|
...(options.headers || {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, mergedOptions);
|
||||||
|
|
||||||
|
// If unauthorized, try to generate new token
|
||||||
|
if (response.status === 401 && this.hasToken()) {
|
||||||
|
console.log('Token expired, generating new token...');
|
||||||
|
const newToken = await this.generateToken();
|
||||||
|
|
||||||
|
if (newToken) {
|
||||||
|
// Retry request with new token
|
||||||
|
mergedOptions.headers.Authorization = `Bearer ${newToken}`;
|
||||||
|
return fetch(url, mergedOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API request failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
const apiTokenManager = new ApiTokenManager();
|
||||||
|
window.ApiTokenManager = apiTokenManager;
|
||||||
|
|
||||||
|
export default apiTokenManager;
|
||||||
166
resources/js/utils/modern-api-helper.js
Normal file
166
resources/js/utils/modern-api-helper.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* Modern API Helper with Token Management
|
||||||
|
*/
|
||||||
|
import ApiTokenManager from './api-token-manager.js';
|
||||||
|
|
||||||
|
class ApiHelper {
|
||||||
|
constructor() {
|
||||||
|
this.baseUrl = window.GlobalConfig?.apiHost || '';
|
||||||
|
this.tokenManager = ApiTokenManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make GET request
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async get(endpoint, options = {}) {
|
||||||
|
return this.request(endpoint, {
|
||||||
|
method: 'GET',
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make POST request
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {object} data
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async post(endpoint, data = null, options = {}) {
|
||||||
|
return this.request(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data ? JSON.stringify(data) : null,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make PUT request
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {object} data
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async put(endpoint, data = null, options = {}) {
|
||||||
|
return this.request(endpoint, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: data ? JSON.stringify(data) : null,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make DELETE request
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async delete(endpoint, options = {}) {
|
||||||
|
return this.request(endpoint, {
|
||||||
|
method: 'DELETE',
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make authenticated request using token manager
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async request(endpoint, options = {}) {
|
||||||
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.tokenManager.request(url, options);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API request failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload file with authentication
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {FormData} formData
|
||||||
|
* @param {object} options
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async upload(endpoint, formData, options = {}) {
|
||||||
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
|
const uploadOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
// Don't set Content-Type for FormData, let browser set it
|
||||||
|
...this.tokenManager.getAuthHeader(),
|
||||||
|
...(options.headers || {})
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, uploadOptions);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upload failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download file with authentication
|
||||||
|
* @param {string} endpoint
|
||||||
|
* @param {string} filename
|
||||||
|
* @param {object} options
|
||||||
|
*/
|
||||||
|
async download(endpoint, filename = null, options = {}) {
|
||||||
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.tokenManager.request(url, {
|
||||||
|
method: 'GET',
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.download = filename || 'download';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Download failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
const apiHelper = new ApiHelper();
|
||||||
|
window.ApiHelper = apiHelper;
|
||||||
|
|
||||||
|
export default apiHelper;
|
||||||
@@ -14,15 +14,22 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card w-100">
|
<div class="card w-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex flex-wrap justify-content-end align-items-center mb-2">
|
<div class="d-flex flex-wrap justify-content-between align-items-center mb-2">
|
||||||
@if ($creator)
|
<div class="d-flex gap-2">
|
||||||
<a href="{{ route('data-settings.create', ['menu_id' => $menuId])}}" class="btn btn-success btn-sm d-block d-sm-inline w-auto">Create</a>
|
<!-- Space for future buttons if needed -->
|
||||||
@endif
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
@if ($creator)
|
||||||
|
<a href="{{ route('data-settings.create', ['menu_id' => $menuId])}}" class="btn btn-success btn-sm d-block d-sm-inline w-auto">Create</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="table-data-settings"
|
<div id="table-data-settings"
|
||||||
data-updater="{{ $updater }}"
|
data-updater="{{ $updater }}"
|
||||||
data-destroyer="{{ $destroyer }}"
|
data-destroyer="{{ $destroyer }}"
|
||||||
data-menuId="{{ $menuId }}">
|
data-menuId="{{ $menuId }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,8 +8,21 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="robots" content="index, follow" />
|
<meta name="robots" content="index, follow" />
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
|
<!-- Authentication Tokens -->
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
<meta name="api-token" content="{{ session('api_token') }}">
|
@auth
|
||||||
|
<!-- API Token from session (primary method) -->
|
||||||
|
<meta name="api-token" content="{{ session('api_token') }}">
|
||||||
|
|
||||||
|
<!-- Alternative: User ID for token generation -->
|
||||||
|
<meta name="user-id" content="{{ auth()->id() }}">
|
||||||
|
|
||||||
|
<!-- Token expiration timestamp (if available) -->
|
||||||
|
@if(session('api_token_expires'))
|
||||||
|
<meta name="api-token-expires" content="{{ session('api_token_expires') }}">
|
||||||
|
@endif
|
||||||
|
@endauth
|
||||||
|
|
||||||
<!-- App favicon -->
|
<!-- App favicon -->
|
||||||
<link rel="shortcut icon" href="/images/dputr.ico">
|
<link rel="shortcut icon" href="/images/dputr.ico">
|
||||||
@@ -48,6 +48,12 @@ Route::group(['middleware' => 'auth'], function(){
|
|||||||
Route::get('', [BigDataController::class, 'index'])->name('any');
|
Route::get('', [BigDataController::class, 'index'])->name('any');
|
||||||
Route::get('/home', [HomeController::class, 'index'])->name('home');
|
Route::get('/home', [HomeController::class, 'index'])->name('home');
|
||||||
|
|
||||||
|
// API Token Management
|
||||||
|
Route::prefix('api-tokens')->group(function() {
|
||||||
|
Route::post('/generate', [\App\Http\Controllers\Auth\AuthenticatedSessionController::class, 'generateApiToken'])->name('api-tokens.generate');
|
||||||
|
Route::delete('/revoke', [\App\Http\Controllers\Auth\AuthenticatedSessionController::class, 'revokeApiToken'])->name('api-tokens.revoke');
|
||||||
|
});
|
||||||
|
|
||||||
//dashboards
|
//dashboards
|
||||||
Route::group(['prefix' => '/dashboards'], function(){
|
Route::group(['prefix' => '/dashboards'], function(){
|
||||||
Route::get('/bigdata', [BigDataController::class, 'index'])->name('dashboard.home');
|
Route::get('/bigdata', [BigDataController::class, 'index'])->name('dashboard.home');
|
||||||
|
|||||||
Reference in New Issue
Block a user