feat(dashboard): bulk import Grok CLI accounts from JSON

Add a "Bulk Add" flow for the grok-cli provider, mirroring the existing
codex one: paste a JSON array/object or drag-drop multiple .json files,
then create all OAuth connections in a single call.

- BulkImportGrokCliModal: flexible JSON parsing (array, single object,
  {accounts:[...]}, concatenated objects) + multi-file upload
- POST /api/oauth/grok-cli/bulk-import: serial createProviderConnection,
  snake_case/camelCase token fields, email backfilled from id_token or
  access_token, authMethod "device_code" to match the login flow
This commit is contained in:
ariesho2903 committed 2026-08-27 15:49:35 +07:00
1 parent 699edac327
commit abb20d9f39
3 files changed
+427

No files matched your search

@@ -22,6 +22,7 @@ import AddApiKeyModal from "./AddApiKeyModal";
import EditCompatibleNodeModal from "./EditCompatibleNodeModal";
import AddCustomModelModal from "./AddCustomModelModal";
import BulkImportCodexModal from "./BulkImportCodexModal";
import BulkImportGrokCliModal from "./BulkImportGrokCliModal";
const ONE_BY_ONE_DELAY_MS = 1000;
@@ -48,6 +49,7 @@ export default function ProviderDetailPage() {
const [showAddApiKeyModal, setShowAddApiKeyModal] = useState(false);
const [addConnectionError, setAddConnectionError] = useState("");
const [showBulkImportCodex, setShowBulkImportCodex] = useState(false);
const [showBulkImportGrokCli, setShowBulkImportGrokCli] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [showEditNodeModal, setShowEditNodeModal] = useState(false);
const [showBulkProxyModal, setShowBulkProxyModal] = useState(false);
@@ -1529,6 +1531,11 @@ export default function ProviderDetailPage() {
{translate("Bulk Add")}
</Button>
)}
{providerId === "grok-cli" && (
<Button size="sm" icon="playlist_add" variant="secondary" onClick={() => setShowBulkImportGrokCli(true)}>
{translate("Bulk Add")}
</Button>
)}
<Button
size="sm"
icon="add"
@@ -1598,6 +1605,18 @@ export default function ProviderDetailPage() {
{translate("Bulk Add")}
</Button>
)}
{providerId === "grok-cli" && (
<Button
size="sm"
icon="playlist_add"
variant="secondary"
onClick={() => setShowBulkImportGrokCli(true)}
title={translate("Bulk import Grok CLI accounts from JSON")}
className="w-full sm:w-auto"
>
{translate("Bulk Add")}
</Button>
)}
{hasDualAuthModes ? (
<>
<Button
@@ -1778,6 +1797,14 @@ export default function ProviderDetailPage() {
/>
)}
{providerId === "grok-cli" && (
<BulkImportGrokCliModal
isOpen={showBulkImportGrokCli}
onClose={() => setShowBulkImportGrokCli(false)}
onSuccess={fetchConnections}
/>
)}
{/* AG Risk Confirmation Modal */}
<ConfirmModal
isOpen={showAgRiskModal}